From 7929056db92b9155c803b6fe84b812d0a9626798 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:00:49 +0200 Subject: [PATCH 01/56] update time formatting - support hours --- src/ST-Player/PlayerHUD.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index a92978c..86a87f6 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -32,7 +32,17 @@ public string FormatTime(int ticks) // https://github.com/DEAFPS/SharpTimer/blob { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); - return $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + + // Handle hours in times + if (time.TotalHours < 1) + return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + + // No leading 0s + string secondsFormat = time.TotalSeconds < 10 ? "D1" : "D2"; + string minutesFormat = time.TotalMinutes < 10 ? "D1" : "D2"; + + // Don't show 00: in times + return time.TotalMinutes < 1 ? $"{time.Seconds.ToString(secondsFormat)}.{millis:D3}" : $"{time.Minutes.ToString(minutesFormat)}:{time.Seconds:D2}.{millis:D3}"; } public void Display() @@ -51,14 +61,14 @@ public void Display() string timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); // Velocity Module - To-do: Make velocity module configurable (XY or XYZ velocity) - float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X - + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X + + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("000"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0,0] > 0 ? FormatTime(_player.Stats.PB[0,0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0, 0] > 0 ? FormatTime(_player.Stats.PB[0, 0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // Build HUD From cb1a50d82a530cca503ede0db1bd9a85aaee1bb7 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Dec 2023 20:24:14 +0200 Subject: [PATCH 02/56] Revert "update time formatting - support hours" This reverts commit 7929056db92b9155c803b6fe84b812d0a9626798. --- src/ST-Player/PlayerHUD.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 86a87f6..a92978c 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -32,17 +32,7 @@ public string FormatTime(int ticks) // https://github.com/DEAFPS/SharpTimer/blob { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); - - // Handle hours in times - if (time.TotalHours < 1) - return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; - - // No leading 0s - string secondsFormat = time.TotalSeconds < 10 ? "D1" : "D2"; - string minutesFormat = time.TotalMinutes < 10 ? "D1" : "D2"; - - // Don't show 00: in times - return time.TotalMinutes < 1 ? $"{time.Seconds.ToString(secondsFormat)}.{millis:D3}" : $"{time.Minutes.ToString(minutesFormat)}:{time.Seconds:D2}.{millis:D3}"; + return $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; } public void Display() @@ -61,14 +51,14 @@ public void Display() string timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); // Velocity Module - To-do: Make velocity module configurable (XY or XYZ velocity) - float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X - + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X + + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("000"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0, 0] > 0 ? FormatTime(_player.Stats.PB[0, 0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0,0] > 0 ? FormatTime(_player.Stats.PB[0,0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // Build HUD From ad56633ece69af0117c0ca6850b5365102ec508b Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Dec 2023 20:27:37 +0200 Subject: [PATCH 03/56] time formatting - support hours --- src/ST-Player/PlayerHUD.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index a92978c..1a2d40d 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -32,7 +32,17 @@ public string FormatTime(int ticks) // https://github.com/DEAFPS/SharpTimer/blob { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); - return $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + + // Handle hours in times + if (time.TotalHours >= 1) + return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + + // No leading 0s + string secondsFormat = time.TotalSeconds < 10 ? "D1" : "D2"; + string minutesFormat = time.TotalMinutes < 10 ? "D1" : "D2"; + + // Don't show 00: in times + return time.TotalMinutes < 1 ? $"{time.Seconds.ToString(secondsFormat)}.{millis:D3}" : $"{time.Minutes.ToString(minutesFormat)}:{time.Seconds:D2}.{millis:D3}"; } public void Display() From 20b84a5a388c81b8f08b43d4206dfcee1571cab1 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:25:28 +0200 Subject: [PATCH 04/56] pre speed for stages --- src/ST-Events/TriggerEndTouch.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index e36280a..866c12d 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -54,6 +54,12 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); #endif + + // Show Prespeed for stages - will be enabled/disabled by the user? + float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X + + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y + + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); + player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } } From 1d89780b31ae4ed9eb94a711c2205b3e7eba1cca Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:53:17 +0200 Subject: [PATCH 05/56] color country --- src/ST-Events/Players.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 011cbaf..0133785 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -104,7 +104,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i Profile); // Print join messages - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); return HookResult.Continue; } From 4faf972e495015e3a7bed3832002742f84cc8811 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:04:22 +0200 Subject: [PATCH 06/56] CP prints - mostly hardcoded - store cps for run --- src/ST-Events/TriggerStartTouch.cs | 71 +++++++++++++++++++++++++++++- src/ST-Player/PlayerTimer.cs | 7 +++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index dee74ba..54c84bc 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -2,6 +2,7 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using System.Text.Json; namespace SurfTimer; @@ -27,6 +28,15 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); #endif + // Get the velocity of the player - we will be using this values to compare and write to DB + float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X + + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y + + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); + float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; + float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; + float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; + + if (trigger.Entity!.Name != null) { // Map end zones -- hook into map_end @@ -39,7 +49,26 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Stats.PB[0,0] == 0 || player.Timer.Ticks < player.Stats.PB[0,0]) player.Stats.PB[0,0] = player.Timer.Ticks; player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {player.HUD.FormatTime(player.Stats.PB[0,0])}!"); - // player.Timer.Reset(); + + foreach (var item in player.Timer.CurrentRunCheckpoints) + { + int cp = item.GetProperty("cp").GetInt32(); + string time = item.GetProperty("time").GetString(); + int ticks = item.GetProperty("ticks").GetInt32(); + double speed = item.GetProperty("speed").GetDouble(); + double velX = item.GetProperty("velX").GetDouble(); + double velY = item.GetProperty("velY").GetDouble(); + double velZ = item.GetProperty("velZ").GetDouble(); + + Console.WriteLine($"CP: {cp} | Time: {time} | Ticks: {ticks} | Speed: {speed} | VelX: {velX} | VelY: {velY} | VelZ: {velZ}"); + + // Write new CPs to database + // Transactions? + // Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` (`maptime_id`, `cp`, `runtime`, `velX`, `velY`, `velZ`) VALUES ('0', {cp}, '{ticks}', {velX}, {velY}, {velZ});"); + // int newPbTaskRows = newPbTask.Result; + // if (newPbTaskRows != 1) + // throw new Exception($"CS2 Surf ERROR >> Inserting Checkpoints."); + } } #if DEBUG @@ -66,7 +95,25 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; player.Timer.Stage = stage; - // To-do: checkpoint functionality because stages = checkpoints + // To-do: checkpoint functionality because stages = checkpoints when in a run on a Staged map + // To-do: This triggers more than once at random :monkaHmm: + // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < stage* + if (player.Timer.IsRunning && !player.Timer.StageMode && player.Timer.CurrentRunCheckpoints.Count < stage) + { + player.Controller.PrintToChat( + $"{PluginPrefix} CP [{ChatColors.Yellow}{stage}{ChatColors.Default}]: " + + $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + + $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + + $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + + $"{ChatColors.Red}(-1234){ChatColors.Default} | " + + $"WR: {ChatColors.Red}+00:00.000{ChatColors.Default} " + + $"{ChatColors.Green}(+1234){ChatColors.Default}]"); + + // .... store in an array to INSERT/UPDATE in DB at the end of run? + string jsonString = $"{{ \"cp\": {stage}, \"time\": \"{player.HUD.FormatTime(player.Timer.Ticks)}\", \"ticks\": {player.Timer.Ticks}, \"speed\": {velocity}, \"velX\": {velocity_x}, \"velY\": {velocity_y}, \"velZ\": {velocity_z} }}"; + JsonElement currRunCps = JsonDocument.Parse(jsonString).RootElement; + player.Timer.CurrentRunCheckpoints.Add(currRunCps); + } #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); @@ -76,7 +123,27 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map checkpoint zones -- hook into map_(c)heck(p)oint# else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) { + int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; + player.Timer.Checkpoint = checkpoint; + // To-do: checkpoint functionality + if (player.Timer.IsRunning && !player.Timer.StageMode && player.Timer.CurrentRunCheckpoints.Count < checkpoint) + { + player.Controller.PrintToChat( + $"{PluginPrefix} CP [{ChatColors.Yellow}{checkpoint}{ChatColors.Default}]: " + + $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + + $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + + $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + + $"{ChatColors.Red}(-1234){ChatColors.Default} | " + + $"WR: {ChatColors.Red}+00:00.000{ChatColors.Default} " + + $"{ChatColors.Green}(+1234){ChatColors.Default}]"); + + // .... store in an array to INSERT/UPDATE in DB at the end of run? + string jsonString = $"{{ \"cp\": {checkpoint}, \"time\": \"{player.HUD.FormatTime(player.Timer.Ticks)}\", \"ticks\": {player.Timer.Ticks}, \"speed\": {velocity}, \"velX\": {velocity_x}, \"velY\": {velocity_y}, \"velZ\": {velocity_z} }}"; + JsonElement currRunCps = JsonDocument.Parse(jsonString).RootElement; + player.Timer.CurrentRunCheckpoints.Add(currRunCps); + // .... store in an array to INSERT/UPDATE in DB at the end of run? + } #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.LightBlue}Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Zone"); diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 97df4f4..a4dc6ca 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -1,3 +1,6 @@ +using System.Runtime.InteropServices; +using System.Text.Json; + namespace SurfTimer; internal class PlayerTimer @@ -13,6 +16,8 @@ internal class PlayerTimer // Tracking public int Stage {get; set;} = 0; // Current stage tracker + public int Checkpoint {get; set;} = 0; // Current checkpoint tracker + public List CurrentRunCheckpoints { get; set; } = new List(); // Current run cps list public int Bonus {get; set;} = 0; // Current bonus tracker - To-do: bonus implementation // public int Style = 0; // To-do: style implementation @@ -25,8 +30,10 @@ public void Reset() this.Stop(); this.Ticks = 0; this.Stage = 0; + this.Checkpoint = 0; this.Paused = false; this.PracticeMode = false; + this.CurrentRunCheckpoints.Clear(); } public void Pause() From 31b062cf94e2b2adedf48e958c989f9eb10a83d7 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:23:19 +0200 Subject: [PATCH 07/56] 3 types of time formatting --- src/ST-Player/PlayerHUD.cs | 28 +++++++++++++++++++++++----- src/ST-Player/PlayerTimer.cs | 26 +++++++++++++++++--------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index a92978c..0ce6755 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -28,11 +28,29 @@ private string FormatHUDElementHTML(string title, string body, string color, str } } - public string FormatTime(int ticks) // https://github.com/DEAFPS/SharpTimer/blob/e4ef24fff29a33c36722d23961355742d507441f/Utils.cs#L38 + /// + /// Formats the given time in ticks into a readable time string. + /// Unless specified differently, the default formatting will be `Verbose`. + /// Check for all formatting types. + /// + public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Verbose) { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); - return $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + + switch (style) + { + case PlayerTimer.TimeFormatStyle.Compact: + return time.TotalMinutes < 1 + ? $"{time.Seconds:D2}.{millis:D3}" + : $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + case PlayerTimer.TimeFormatStyle.Full: + return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + case PlayerTimer.TimeFormatStyle.Verbose: + return $"{time.Hours}h {time.Minutes}m {time.Seconds}s {millis}ms"; + default: + throw new ArgumentException("Invalid time format style"); + } } public void Display() @@ -51,14 +69,14 @@ public void Display() string timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); // Velocity Module - To-do: Make velocity module configurable (XY or XYZ velocity) - float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X - + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X + + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("000"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0,0] > 0 ? FormatTime(_player.Stats.PB[0,0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0, 0] > 0 ? FormatTime(_player.Stats.PB[0, 0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // Build HUD diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 97df4f4..f153908 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -3,21 +3,29 @@ namespace SurfTimer; internal class PlayerTimer { // Status - public bool Enabled {get; set;} = true; // Enable toggle for entire timer - public bool Paused {get; set;} = false; // Pause toggle for timer - public bool IsRunning {get; set;} = false; // Is the timer currently running? + public bool Enabled { get; set; } = true; // Enable toggle for entire timer + public bool Paused { get; set; } = false; // Pause toggle for timer + public bool IsRunning { get; set; } = false; // Is the timer currently running? // Modes - public bool PracticeMode {get; set;} = false; // Practice mode toggle - public bool StageMode {get; set;} = false; // Stage mode toggle + public bool PracticeMode { get; set; } = false; // Practice mode toggle + public bool StageMode { get; set; } = false; // Stage mode toggle // Tracking - public int Stage {get; set;} = 0; // Current stage tracker - public int Bonus {get; set;} = 0; // Current bonus tracker - To-do: bonus implementation + public int Stage { get; set; } = 0; // Current stage tracker + public int Bonus { get; set; } = 0; // Current bonus tracker - To-do: bonus implementation // public int Style = 0; // To-do: style implementation // Timing - public int Ticks {get; set;} = 0; // To-do: sub-tick counting? This currently goes on OnTick, which is not sub-tick I believe? Needs investigating + public int Ticks { get; set; } = 0; // To-do: sub-tick counting? This currently goes on OnTick, which is not sub-tick I believe? Needs investigating + + // Time Formatting + public enum TimeFormatStyle + { + Compact, + Full, + Verbose + } // Methods public void Reset() @@ -53,7 +61,7 @@ public void Tick() // without worry for any timing restrictions (eg: Paused, Enabled, etc) if (this.Paused || !this.Enabled || !this.IsRunning) return; - + this.Ticks++; } } \ No newline at end of file From 1f8946edff3439d38b0c99a34d7a8a7c7e5f1fc8 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:45:52 +0200 Subject: [PATCH 08/56] forgot leading 0s for compact type --- src/ST-Player/PlayerHUD.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 0ce6755..ecb9a95 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -42,8 +42,8 @@ public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTi { case PlayerTimer.TimeFormatStyle.Compact: return time.TotalMinutes < 1 - ? $"{time.Seconds:D2}.{millis:D3}" - : $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + ? $"{time.Seconds:D1}.{millis:D3}" + : $"{time.Minutes:D1}:{time.Seconds:D1}.{millis:D3}"; case PlayerTimer.TimeFormatStyle.Full: return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; case PlayerTimer.TimeFormatStyle.Verbose: From 37a07ea2fbea8e7d0bf0b3c42bc99874a36e6a44 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:25:03 +0200 Subject: [PATCH 09/56] Revert "Merge branch 'chat-and-hud-messages' into time-format" This reverts commit f6a59892017bd3e73a25b93011111612a9ef362e, reversing changes made to 1f8946edff3439d38b0c99a34d7a8a7c7e5f1fc8. --- src/ST-Events/Players.cs | 4 +- src/ST-Events/TriggerEndTouch.cs | 6 --- src/ST-Events/TriggerStartTouch.cs | 71 +----------------------------- src/ST-Player/PlayerTimer.cs | 11 +---- 4 files changed, 6 insertions(+), 86 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 0133785..fe93343 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -104,7 +104,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i Profile); // Print join messages - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); return HookResult.Continue; } @@ -123,7 +123,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { // Update data in Player DB table - Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', `last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); + Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', `lastseen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); if (updatePlayerTask.Result != 1) throw new Exception($"CS2 Surf ERROR >> OnPlayerDisconnect -> Failed to update player data in database. Player: {player.PlayerName} ({player.SteamID})"); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 866c12d..e36280a 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -54,12 +54,6 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); #endif - - // Show Prespeed for stages - will be enabled/disabled by the user? - float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X - + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y - + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); - player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 54c84bc..dee74ba 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -2,7 +2,6 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; -using System.Text.Json; namespace SurfTimer; @@ -28,15 +27,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); #endif - // Get the velocity of the player - we will be using this values to compare and write to DB - float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X - + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y - + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); - float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; - float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; - float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; - - if (trigger.Entity!.Name != null) { // Map end zones -- hook into map_end @@ -49,26 +39,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Stats.PB[0,0] == 0 || player.Timer.Ticks < player.Stats.PB[0,0]) player.Stats.PB[0,0] = player.Timer.Ticks; player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {player.HUD.FormatTime(player.Stats.PB[0,0])}!"); - - foreach (var item in player.Timer.CurrentRunCheckpoints) - { - int cp = item.GetProperty("cp").GetInt32(); - string time = item.GetProperty("time").GetString(); - int ticks = item.GetProperty("ticks").GetInt32(); - double speed = item.GetProperty("speed").GetDouble(); - double velX = item.GetProperty("velX").GetDouble(); - double velY = item.GetProperty("velY").GetDouble(); - double velZ = item.GetProperty("velZ").GetDouble(); - - Console.WriteLine($"CP: {cp} | Time: {time} | Ticks: {ticks} | Speed: {speed} | VelX: {velX} | VelY: {velY} | VelZ: {velZ}"); - - // Write new CPs to database - // Transactions? - // Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` (`maptime_id`, `cp`, `runtime`, `velX`, `velY`, `velZ`) VALUES ('0', {cp}, '{ticks}', {velX}, {velY}, {velZ});"); - // int newPbTaskRows = newPbTask.Result; - // if (newPbTaskRows != 1) - // throw new Exception($"CS2 Surf ERROR >> Inserting Checkpoints."); - } + // player.Timer.Reset(); } #if DEBUG @@ -95,25 +66,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; player.Timer.Stage = stage; - // To-do: checkpoint functionality because stages = checkpoints when in a run on a Staged map - // To-do: This triggers more than once at random :monkaHmm: - // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < stage* - if (player.Timer.IsRunning && !player.Timer.StageMode && player.Timer.CurrentRunCheckpoints.Count < stage) - { - player.Controller.PrintToChat( - $"{PluginPrefix} CP [{ChatColors.Yellow}{stage}{ChatColors.Default}]: " + - $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + - $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + - $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + - $"{ChatColors.Red}(-1234){ChatColors.Default} | " + - $"WR: {ChatColors.Red}+00:00.000{ChatColors.Default} " + - $"{ChatColors.Green}(+1234){ChatColors.Default}]"); - - // .... store in an array to INSERT/UPDATE in DB at the end of run? - string jsonString = $"{{ \"cp\": {stage}, \"time\": \"{player.HUD.FormatTime(player.Timer.Ticks)}\", \"ticks\": {player.Timer.Ticks}, \"speed\": {velocity}, \"velX\": {velocity_x}, \"velY\": {velocity_y}, \"velZ\": {velocity_z} }}"; - JsonElement currRunCps = JsonDocument.Parse(jsonString).RootElement; - player.Timer.CurrentRunCheckpoints.Add(currRunCps); - } + // To-do: checkpoint functionality because stages = checkpoints #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); @@ -123,27 +76,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map checkpoint zones -- hook into map_(c)heck(p)oint# else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) { - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; - player.Timer.Checkpoint = checkpoint; - // To-do: checkpoint functionality - if (player.Timer.IsRunning && !player.Timer.StageMode && player.Timer.CurrentRunCheckpoints.Count < checkpoint) - { - player.Controller.PrintToChat( - $"{PluginPrefix} CP [{ChatColors.Yellow}{checkpoint}{ChatColors.Default}]: " + - $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + - $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + - $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + - $"{ChatColors.Red}(-1234){ChatColors.Default} | " + - $"WR: {ChatColors.Red}+00:00.000{ChatColors.Default} " + - $"{ChatColors.Green}(+1234){ChatColors.Default}]"); - - // .... store in an array to INSERT/UPDATE in DB at the end of run? - string jsonString = $"{{ \"cp\": {checkpoint}, \"time\": \"{player.HUD.FormatTime(player.Timer.Ticks)}\", \"ticks\": {player.Timer.Ticks}, \"speed\": {velocity}, \"velX\": {velocity_x}, \"velY\": {velocity_y}, \"velZ\": {velocity_z} }}"; - JsonElement currRunCps = JsonDocument.Parse(jsonString).RootElement; - player.Timer.CurrentRunCheckpoints.Add(currRunCps); - // .... store in an array to INSERT/UPDATE in DB at the end of run? - } #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.LightBlue}Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Zone"); diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 8ded292..f153908 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -1,6 +1,3 @@ -using System.Runtime.InteropServices; -using System.Text.Json; - namespace SurfTimer; internal class PlayerTimer @@ -15,10 +12,8 @@ internal class PlayerTimer public bool StageMode { get; set; } = false; // Stage mode toggle // Tracking - public int Stage {get; set;} = 0; // Current stage tracker - public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public List CurrentRunCheckpoints { get; set; } = new List(); // Current run cps list - public int Bonus {get; set;} = 0; // Current bonus tracker - To-do: bonus implementation + public int Stage { get; set; } = 0; // Current stage tracker + public int Bonus { get; set; } = 0; // Current bonus tracker - To-do: bonus implementation // public int Style = 0; // To-do: style implementation // Timing @@ -38,10 +33,8 @@ public void Reset() this.Stop(); this.Ticks = 0; this.Stage = 0; - this.Checkpoint = 0; this.Paused = false; this.PracticeMode = false; - this.CurrentRunCheckpoints.Clear(); } public void Pause() From e68c66a6040ebcbfa1133959d47293e98047a1ed Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:28:00 +0200 Subject: [PATCH 10/56] . --- src/ST-Events/Players.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index fe93343..011cbaf 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -123,7 +123,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { // Update data in Player DB table - Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', `lastseen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); + Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', `last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); if (updatePlayerTask.Result != 1) throw new Exception($"CS2 Surf ERROR >> OnPlayerDisconnect -> Failed to update player data in database. Player: {player.PlayerName} ({player.SteamID})"); From e8036ecfc516d31374eee8b9e30efadca0fcf789 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 20 Dec 2023 21:50:49 +0200 Subject: [PATCH 11/56] write and read PB runs --- src/ST-Events/Players.cs | 34 ++++++++++++++++++-- src/ST-Events/TriggerStartTouch.cs | 27 ++++++++++++++-- src/ST-Player/PlayerHUD.cs | 2 +- src/ST-Player/PlayerStats.cs | 50 +++++++++++++++++++++++++++--- 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 011cbaf..63ad0a9 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -58,7 +58,6 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> Returning player {name} ({player.SteamID}) loaded from database with ID {dbID}"); #endif } - else { playerData.Close(); @@ -96,6 +95,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> New player {name} ({player.SteamID}) added to database with ID {dbID}"); #endif } + PlayerProfile Profile = new PlayerProfile(dbID, name, player.SteamID, country, joinDate, lastSeen, connections); // Create Player object @@ -103,8 +103,38 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), Profile); + // Load player stats from database + // To-do: add types + // Console.WriteLine($"=================== SELECT * FROM `MapTimes` WHERE `player_id` = {Profile.ID} AND `map_id` = {CurrentMap.ID};"); + Task dbTask2 = DB.Query($"SELECT * FROM `MapTimes` WHERE `player_id` = {Profile.ID} AND `map_id` = {CurrentMap.ID};"); + MySqlDataReader playerStats = dbTask2.Result; + while (playerStats.HasRows && playerStats.Read()) + { + // Player has a current map completion in database + int style = playerStats.GetInt32("style"); + var stats = playerList[player.UserId ?? 0].Stats; + + stats.PB[style].ID = playerStats.GetInt32("id"); + stats.PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + stats.PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + stats.PB[style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + stats.PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + stats.PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + stats.PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + stats.PB[style].RunTime = playerStats.GetInt32("run_time"); + stats.PB[style].RunDate = playerStats.GetInt32("run_date"); + + Console.WriteLine($"============== CS2 Surf DEBUG >> {stats.PB[style].ID} | {stats.PB[style].RunTime} | {stats.PB[style].StartVelX} | {stats.PB[style].StartVelY} | {stats.PB[style].StartVelZ} | {stats.PB[style].EndVelX} | {stats.PB[style].EndVelY} | {stats.PB[style].EndVelZ} | {stats.PB[style].RunDate}"); + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> PlayerStats (ID {stats.PB[style].ID}) loaded from DB for {name} '{Profile.SteamID}' ({Profile.ID})"); + #endif + } + if (!playerStats.HasRows) + Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> No PB entries found for {name}"); + + playerStats.Close(); // Print join messages - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); return HookResult.Continue; } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index dee74ba..029f9a1 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -27,6 +27,11 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); #endif + // Get velocities for DB queries + float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; + float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; + float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; + if (trigger.Entity!.Name != null) { // Map end zones -- hook into map_end @@ -36,9 +41,25 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Timer.IsRunning) { player.Timer.Stop(); - if (player.Stats.PB[0,0] == 0 || player.Timer.Ticks < player.Stats.PB[0,0]) - player.Stats.PB[0,0] = player.Timer.Ticks; - player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {player.HUD.FormatTime(player.Stats.PB[0,0])}!"); + // To-do: make Style (currently 0) be dynamic + if (player.Stats.PB[0].RunTime == 0 || player.Timer.Ticks < player.Stats.PB[0].RunTime) + { + player.Stats.PB[0].RunTime = player.Timer.Ticks; + player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {player.HUD.FormatTime(player.Stats.PB[0].RunTime)} ({player.Timer.Ticks})!"); + } + else + { + player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {player.HUD.FormatTime(player.Stats.PB[0].RunTime)} ({player.Timer.Ticks})!"); + } + + // Add entry in DB for the run + // To-do: add `type` + // To-do: get the `start_vel` values for the run from CP implementation in other repository implementation of checkpoints and their speeds + // Console.WriteLine($"============== INSERT INTO `MapTimes` (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, 123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) ON DUPLICATE KEY UPDATE player_id=VALUES(player_id), map_id=VALUES(map_id), style=VALUES(style), type=VALUES(type), stage=VALUES(stage), run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, 123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) ON DUPLICATE KEY UPDATE player_id=VALUES(player_id), map_id=VALUES(map_id), style=VALUES(style), type=VALUES(type), stage=VALUES(stage), run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + if (updatePlayerRunTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + // player.Timer.Reset(); } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index ecb9a95..3a9fe85 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -76,7 +76,7 @@ public void Display() // Rank Module string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0, 0] > 0 ? FormatTime(_player.Stats.PB[0, 0]) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].RunTime > 0 ? FormatTime(_player.Stats.PB[0].RunTime) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // Build HUD diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 4f9c005..64180c9 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -1,14 +1,54 @@ namespace SurfTimer; +// To-do: make Style (currently 0) be dynamic +// To-do: add `Type` +internal class PersonalBest +{ + public int ID { get; set; } + public int RunTime { get; set; } + // public int Type { get; set; } + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public int RunDate { get; set; } + // Add other properties as needed + + // Constructor + public PersonalBest(int id, int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) + { + ID = id; + RunTime = runTime; + // Type = type; + StartVelX = startVelX; + StartVelY = startVelY; + StartVelZ = startVelZ; + EndVelX = endVelX; + EndVelY = endVelY; + EndVelZ = endVelZ; + RunDate = runDate; + } +} + internal class PlayerStats { // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. // Temporarily, we store ticks + basic info so we can experiment // These account for future style support and a relevant index. - public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) - public int[,] Rank {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) - public int[,] Checkpoints {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: checkpoint index - public int[,] StagePB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: stage index - public int[,] StageRank {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: stage index + // public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) + public Dictionary PB { get; set; } = new Dictionary(); + + // Initialize default styles (e.g., 0 for normal) + public PlayerStats() + { + PB[0] = new PersonalBest(0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); + // Add more styles as needed + } + public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) + public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index + public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index } \ No newline at end of file From 7f6a4fc0ee2469b22a5651d87e5af7aa32f93b5a Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:04:18 +0200 Subject: [PATCH 12/56] CPs and PBs;new db schema --- src/ST-Commands/PlayerCommands.cs | 2 +- src/ST-Events/Players.cs | 74 +++++------ src/ST-Events/TriggerEndTouch.cs | 46 ++++++- src/ST-Events/TriggerStartTouch.cs | 203 +++++++++++++++++++++++++---- src/ST-Map/Map.cs | 14 +- src/ST-Player/PlayerHUD.cs | 99 +++++++++++++- src/ST-Player/PlayerStats.cs | 155 +++++++++++++++++++++- src/ST-Player/PlayerTimer.cs | 24 ++-- src/SurfTimer.cs | 3 +- 9 files changed, 533 insertions(+), 87 deletions(-) diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 251a421..bac46fd 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -75,7 +75,7 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[stage], CurrentMap.StageStartZoneAngles[stage], new Vector(0,0,0))); playerList[player.UserId ?? 0].Timer.Reset(); - playerList[player.UserId ?? 0].Timer.StageMode = true; + playerList[player.UserId ?? 0].Timer.IsStageMode = true; // To-do: If you run this while you're in the start zone, endtouch for the start zone runs after you've teleported // causing the timer to start. This needs to be fixed. diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 63ad0a9..9139cb4 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -39,7 +39,10 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i country = "XX"; geoipDB.Dispose(); - // Load player data from database (or create an entry if first time connecting) + if (DB == null) + throw new Exception("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldnt happen."); + + // Load player profile data from database (or create an entry if first time connecting) Task dbTask = DB.Query($"SELECT * FROM `Player` WHERE `steam_id` = {player.SteamID} LIMIT 1;"); MySqlDataReader playerData = dbTask.Result; if (playerData.HasRows && playerData.Read()) @@ -72,7 +75,8 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i int newPlayerTaskRows = newPlayerTask.Result; if (newPlayerTaskRows != 1) throw new Exception($"CS2 Surf ERROR >> OnPlayerConnect -> Failed to write new player to database, this shouldnt happen. Player: {name} ({player.SteamID})"); - + newPlayerTask.Dispose(); + // Get new player's database ID Task newPlayerDataTask = DB.Query($"SELECT `id` FROM `Player` WHERE `steam_id` = {player.SteamID} LIMIT 1;"); MySqlDataReader newPlayerData = newPlayerDataTask.Result; @@ -95,44 +99,22 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> New player {name} ({player.SteamID}) added to database with ID {dbID}"); #endif } + dbTask.Dispose(); + // Create Player object and add to playerList PlayerProfile Profile = new PlayerProfile(dbID, name, player.SteamID, country, joinDate, lastSeen, connections); - - // Create Player object playerList[player.UserId ?? 0] = new Player(player, new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), Profile); - // Load player stats from database - // To-do: add types - // Console.WriteLine($"=================== SELECT * FROM `MapTimes` WHERE `player_id` = {Profile.ID} AND `map_id` = {CurrentMap.ID};"); - Task dbTask2 = DB.Query($"SELECT * FROM `MapTimes` WHERE `player_id` = {Profile.ID} AND `map_id` = {CurrentMap.ID};"); - MySqlDataReader playerStats = dbTask2.Result; - while (playerStats.HasRows && playerStats.Read()) - { - // Player has a current map completion in database - int style = playerStats.GetInt32("style"); - var stats = playerList[player.UserId ?? 0].Stats; - - stats.PB[style].ID = playerStats.GetInt32("id"); - stats.PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - stats.PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - stats.PB[style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - stats.PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - stats.PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - stats.PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - stats.PB[style].RunTime = playerStats.GetInt32("run_time"); - stats.PB[style].RunDate = playerStats.GetInt32("run_date"); + #if DEBUG + Console.WriteLine($"=================================== SELECT * FROM `MapTimes` WHERE `player_id` = {playerList[player.UserId ?? 0].Profile.ID} AND `map_id` = {CurrentMap.ID};"); + #endif + // To-do: hardcoded Style value + // Load MapTimes for the player's PB and their Checkpoints + playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0].Profile.ID, CurrentMap.ID, DB); + playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(playerList[player.UserId ?? 0].Stats.PB[0].ID, DB); - Console.WriteLine($"============== CS2 Surf DEBUG >> {stats.PB[style].ID} | {stats.PB[style].RunTime} | {stats.PB[style].StartVelX} | {stats.PB[style].StartVelY} | {stats.PB[style].StartVelZ} | {stats.PB[style].EndVelX} | {stats.PB[style].EndVelY} | {stats.PB[style].EndVelZ} | {stats.PB[style].RunDate}"); - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> PlayerStats (ID {stats.PB[style].ID}) loaded from DB for {name} '{Profile.SteamID}' ({Profile.ID})"); - #endif - } - if (!playerStats.HasRows) - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> No PB entries found for {name}"); - - playerStats.Close(); // Print join messages Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); @@ -152,15 +134,27 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { - // Update data in Player DB table - Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', `last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); - if (updatePlayerTask.Result != 1) - throw new Exception($"CS2 Surf ERROR >> OnPlayerDisconnect -> Failed to update player data in database. Player: {player.PlayerName} ({player.SteamID})"); + if (DB == null) + throw new Exception("CS2 Surf ERROR >> OnPlayerDisconnect -> DB object is null, this shouldnt happen."); - // Player disconnection to-do + if (!playerList.ContainsKey(player.UserId ?? 0)) + { + Console.WriteLine($"CS2 Surf ERROR >> OnPlayerDisconnect -> Player playerList does NOT contain player.UserId, this shouldn't happen. Player: {player.PlayerName} ({player.UserId})"); + } + else + { + // Update data in Player DB table + Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', " + + $"`last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 " + + $"WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); + if (updatePlayerTask.Result != 1) + throw new Exception($"CS2 Surf ERROR >> OnPlayerDisconnect -> Failed to update player data in database. Player: {player.PlayerName} ({player.SteamID})"); + // Player disconnection to-do + updatePlayerTask.Dispose(); - // Remove player data from playerList - playerList.Remove(player.UserId ?? 0); + // Remove player data from playerList + playerList.Remove(player.UserId ?? 0); + } return HookResult.Continue; } } diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index e36280a..32cf6ce 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -2,6 +2,7 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API; namespace SurfTimer; @@ -29,6 +30,15 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) if (trigger.Entity!.Name != null) { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X + + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y + + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); + float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; + float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; + float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; + // Map start zones -- hook into map_start, (s)tage1_start if (trigger.Entity.Name.Contains("map_start") || trigger.Entity.Name.Contains("s1_start") || @@ -37,10 +47,18 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) // MAP START ZONE player.Timer.Start(); + // Wonky Prespeed check + // To-do: make the teleportation a bit more elegant (method in a class or something) + if (velocity > 666.0) + { + player.Controller.PrintToChat( + $"{PluginPrefix} {ChatColors.Red}You are going too fast! ({velocity.ToString("0")} u/s)"); + player.Timer.Reset(); + if (CurrentMap.StartZone != new Vector(0,0,0)) + Server.NextFrame(() => player.Controller.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0,0,0), new Vector(0,0,0))); + } + // Prespeed display - float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X - + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y - + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); #if DEBUG @@ -53,7 +71,29 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) { #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Timer.CurrentRunCheckpoints.Count {player.Timer.CurrentRunCheckpoints.Count}"); #endif + + if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Timer.CurrentRunCheckpoints.Count) + { + var currentCheckpoint = player.Timer.CurrentRunCheckpoints[player.Timer.Checkpoint - 1]; + #if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {currentCheckpoint.EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {currentCheckpoint.EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {currentCheckpoint.EndVelZ} - velocity_z {velocity_z}"); + #endif + + // Update the values + currentCheckpoint.EndVelX = velocity_x; + currentCheckpoint.EndVelY = velocity_y; + currentCheckpoint.EndVelZ = velocity_z; + currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.Attempts += 1; + } + else + { + // Handle the case where the index is out of bounds + } } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 029f9a1..114a2fd 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using System.Text.Json; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; @@ -13,27 +14,53 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) CBaseTrigger trigger = handler.GetParam(0); CBaseEntity entity = handler.GetParam(1); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - - if (client.IsBot || !client.IsValid) + if (client.IsBot || !client.IsValid || !client.PawnIsAlive) { return HookResult.Continue; } - - else + else { + // To-do: Sometimes this triggers before `OnPlayerConnect` and `playerList` does not contain the player how is this possible :thonk: + if (!playerList.ContainsKey(client.UserId ?? 0)) + { + /* + CS2 Surf ERROR >> OnTriggerStartTouch -> Player playerList does NOT contain client.UserId, this shouldnt happen. Player: tttt (0) + 11:19:18 [EROR] (cssharp:Core) Error invoking callback + System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. + ---> System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary. + at System.Collections.Generic.Dictionary`2.get_Item(TKey key) + at SurfTimer.SurfTimer.OnTriggerStartTouch(DynamicHook handler) + at InvokeStub_Func`2.Invoke(Object, Object, IntPtr*) + at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) + --- End of inner exception stack trace --- + at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) + at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) + at System.Delegate.DynamicInvokeImpl(Object[] args) + at CounterStrikeSharp.API.Core.FunctionReference.<>c__DisplayClass3_0.<.ctor>b__0(fxScriptContext* context) in /home/runner/work/CounterStrikeSharp/CounterStrikeSharp/managed/CounterStrikeSharp.API/Core/FunctionReference.cs:line 82 + */ + // For some reason, this happens as soon as player connects to the server (randomly) + // Is an "entity" created for the player when they connect which triggers this??? + Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldnt happen. Player: {client.PlayerName} ({client.UserId})"); + // return HookResult.Continue; + } + // Implement Trigger Start Touch Here Player player = playerList[client.UserId ?? 0]; #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); #endif - // Get velocities for DB queries - float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; - float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; - float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; - if (trigger.Entity!.Name != null) { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X + + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y + + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); + float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; + float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; + float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; + // Map end zones -- hook into map_end if (trigger.Entity.Name == "map_end") { @@ -42,25 +69,93 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) { player.Timer.Stop(); // To-do: make Style (currently 0) be dynamic - if (player.Stats.PB[0].RunTime == 0 || player.Timer.Ticks < player.Stats.PB[0].RunTime) + if (player.Stats.PB[0].RunTime <= 0) // Player first ever PersonalBest for the map { - player.Stats.PB[0].RunTime = player.Timer.Ticks; - player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {player.HUD.FormatTime(player.Stats.PB[0].RunTime)} ({player.Timer.Ticks})!"); + player.Controller.PrintToChat($"{PluginPrefix} Congratulations on setting your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } - else + else if (player.Timer.Ticks < player.Stats.PB[0].RunTime) // Player beating their existing PersonalBest for the map { - player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {player.HUD.FormatTime(player.Stats.PB[0].RunTime)} ({player.Timer.Ticks})!"); + player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); } + else // Player did not beat their existing PersonalBest for the map + { + player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + return HookResult.Continue; + } + player.Stats.PB[0].RunTime = player.Timer.Ticks; + + if (DB == null) + throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldnt happen."); + + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + + $"============== INSERT INTO `MapTimes` " + + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + + $"123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + #endif // Add entry in DB for the run // To-do: add `type` // To-do: get the `start_vel` values for the run from CP implementation in other repository implementation of checkpoints and their speeds - // Console.WriteLine($"============== INSERT INTO `MapTimes` (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, 123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) ON DUPLICATE KEY UPDATE player_id=VALUES(player_id), map_id=VALUES(map_id), style=VALUES(style), type=VALUES(type), stage=VALUES(stage), run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); - Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, 123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) ON DUPLICATE KEY UPDATE player_id=VALUES(player_id), map_id=VALUES(map_id), style=VALUES(style), type=VALUES(type), stage=VALUES(stage), run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + + $"123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); if (updatePlayerRunTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + else + player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + updatePlayerRunTask.Dispose(); - // player.Timer.Reset(); + // To-do: Transactions? Server freezes for a bit here sometimes + // Loop through the checkpoints and insert/update them in the database for the run + foreach (var item in player.Timer.CurrentRunCheckpoints) + { + int cp = item.CP; + int runTime = item.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + int ticks = item.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD + double speed = item.Speed; + double startVelX = item.StartVelX; + double startVelY = item.StartVelY; + double startVelZ = item.StartVelZ; + double endVelX = item.EndVelX; + double endVelY = item.EndVelY; + double endVelZ = item.EndVelZ; + int attempts = item.Attempts; + + #if DEBUG + Console.WriteLine($"CP: {cp} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + + $"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + #endif + + // Insert/Update CPs to database + // To-do: Transactions? + // Check if the player has PB object initialized and if the player's character is currently active in the game + if (player.Stats.PB[0] != null && player.Controller.PlayerPawn.Value != null) + { + Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + if (newPbTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Checkpoint zones) -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); + newPbTask.Dispose(); + } + } + player.Stats.PB[0].LoadCheckpointsForRun(player.Stats.PB[0].ID, DB); // Load the Checkpoints PB data again } #if DEBUG @@ -69,9 +164,9 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) } // Map start zones -- hook into map_start, (s)tage1_start - else if (trigger.Entity.Name.Contains("map_start") || - trigger.Entity.Name.Contains("s1_start") || - trigger.Entity.Name.Contains("stage1_start")) + else if (trigger.Entity.Name.Contains("map_start") || + trigger.Entity.Name.Contains("s1_start") || + trigger.Entity.Name.Contains("stage1_start")) { player.Timer.Reset(); @@ -87,7 +182,35 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; player.Timer.Stage = stage; - // To-do: checkpoint functionality because stages = checkpoints + // To-do:* checkpoint functionality because stages = checkpoints when in a run on a Staged map + // To-do:* This triggers more than once at random :monkaHmm: *already posted in CS# about OnPlayerConnect being triggered after OnStartTouch* + // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Timer.CurrentRunCheckpoints.Count <= stage) + { + player.Timer.Checkpoint = stage; // Stage = Checkpoint when in a run on a Staged map + + #if DEBUG + Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); + #endif + + player.HUD.DisplayCheckpointMessages(PluginPrefix); + + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + PersonalBest.Checkpoint cp2 = new PersonalBest.Checkpoint(stage, + player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD + velocity, + velocity_x, + velocity_y, + velocity_z, + -1.0f, + -1.0f, + -1.0f, + -1.0f, + 0); + player.Timer.CurrentRunCheckpoints.Add(cp2); + } #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); @@ -97,7 +220,41 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map checkpoint zones -- hook into map_(c)heck(p)oint# else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) { - // To-do: checkpoint functionality + int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; + player.Timer.Checkpoint = checkpoint; + + // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < checkpoint* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Timer.CurrentRunCheckpoints.Count < checkpoint) + { + #if DEBUG + Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `checkpoint`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); + #endif + // Print checkpoint message + player.Controller.PrintToChat( + $"{PluginPrefix} CP [{ChatColors.Yellow}{checkpoint}{ChatColors.Default}]: " + + $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + + $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + + $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + + $"{ChatColors.Red}(N/A){ChatColors.Default} | " + + $"WR: {ChatColors.Red}+PL:CE.HLD{ChatColors.Default} " + + $"{ChatColors.Green}(+1234){ChatColors.Default}]"); + + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + PersonalBest.Checkpoint cp2 = new PersonalBest.Checkpoint(checkpoint, + player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD + velocity, + velocity_x, + velocity_y, + velocity_z, + -1.0f, + -1.0f, + -1.0f, + -1.0f, + 0); + player.Timer.CurrentRunCheckpoints.Add(cp2); + } #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.LightBlue}Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Zone"); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index ac0ed32..b9d9a94 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -9,13 +9,15 @@ namespace SurfTimer; public class Map { // Map information - public int ID {get; set;} = 0; + public int ID {get; set;} = -1; // Can we use this to re-trigger retrieving map information from the database?? (all db IDs are auto-incremented) public string Name {get; set;} = ""; public string Author {get; set;} = ""; public int Tier {get; set;} = 0; public int Stages {get; set;} = 0; + public int Bonuses {get; set;} = 0; public bool Ranked {get; set;} = false; public int DateAdded {get; set;} = 0; + public int LastPlayed {get; set;} = 0; // Zone Origin Information // Map start/end zones @@ -109,8 +111,10 @@ internal Map(string Name, TimerDatabase DB) this.Author = mapData.GetString("author") ?? "Unknown"; this.Tier = mapData.GetInt32("tier"); this.Stages = mapData.GetInt32("stages"); + this.Bonuses = mapData.GetInt32("bonuses"); this.Ranked = mapData.GetBoolean("ranked"); this.DateAdded = mapData.GetInt32("date_added"); + this.LastPlayed = mapData.GetInt32("last_played"); mapData.Close(); } @@ -131,10 +135,12 @@ internal Map(string Name, TimerDatabase DB) postWriteMapData.Close(); this.Name = Name; this.Author = "Unknown"; - this.Tier = 0; - this.Stages = 0; + this.Tier = -1; + this.Stages = -1; + this.Bonuses = -1; this.Ranked = false; - this.DateAdded = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + this.DateAdded = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + this.LastPlayed = this.DateAdded; return; } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 3a9fe85..8dfc75e 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -1,3 +1,5 @@ +using CounterStrikeSharp.API.Modules.Utils; + namespace SurfTimer; internal class PlayerHUD @@ -30,10 +32,10 @@ private string FormatHUDElementHTML(string title, string body, string color, str /// /// Formats the given time in ticks into a readable time string. - /// Unless specified differently, the default formatting will be `Verbose`. + /// Unless specified differently, the default formatting will be `Compact`. /// Check for all formatting types. /// - public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Verbose) + public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Compact) { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); @@ -61,7 +63,7 @@ public void Display() string timerColor = "#79d1ed"; if (_player.Timer.IsRunning) { - if (_player.Timer.PracticeMode) + if (_player.Timer.IsPracticeMode) timerColor = "#F2C94C"; else timerColor = "#2E9F65"; @@ -86,4 +88,95 @@ public void Display() _player.Controller.PrintToCenterHtml(hud); } } + + /// + /// Only calculates if the player has a PB, otherwise it will display N/A + /// + /// + public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefix should be accessible in here without passing it as a parameter + { + int pbTime; + float pbSpeed; + + int currentTime = _player.Timer.Ticks; + float currentSpeed = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X + + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); + + // Default values for the PB and WR differences in case no calculations can be made + string strPbDifference = $"{ChatColors.Grey}N/A{ChatColors.Default} ({ChatColors.Grey}N/A{ChatColors.Default})"; + string strWrDifference = $"{ChatColors.Grey}N/A{ChatColors.Default} ({ChatColors.Grey}N/A{ChatColors.Default})"; + + // We need to try/catch this because the player might not have a PB for this stage in this case but they will not have for the map as well + // Can check checkpoints count instead of try/catch + try + { + pbTime = _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].RunTime; // Should we use -1 here? + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelX * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelX + + _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelY * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelY + + _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelZ * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelZ); + + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoints[{_player.Timer.Checkpoint - 1} = {pbTime}]"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[0].Checkpoints[{_player.Timer.Checkpoint - 1}] = {pbSpeed}"); + #endif + } + catch (System.Exception ex) + { + // Handle the exception gracefully without stopping the application + // We assign default values to pbTime and pbSpeed + pbTime = -1; // This determines if we will calculate differences or not!!! + pbSpeed = 0.0f; + + #if DEBUG + Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred: {ex.Message}"); + Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred Player has no PB and therefore no Checkpoints | _player.Stats.PB[0].Checkpoints.Count = {_player.Stats.PB[0].Checkpoints.Count}"); + #endif + } + + // Calculate differences in PB (PB - Current) + if (pbTime != -1) + { + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting difference calculation... (pbTime != -1)"); + #endif + // Reset the string + strPbDifference = ""; + + // Calculate the time difference + if (pbTime - currentTime < 0.0) + { + strPbDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((pbTime - currentTime) * -1); // We multiply by -1 to get the positive value + } + else if (pbTime - currentTime >= 0.0) + { + strPbDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(pbTime - currentTime); + } + strPbDifference += ChatColors.Default + " "; + + // Calculate the speed difference + if (pbSpeed - currentSpeed <= 0.0) + { + strPbDifference += "(" + ChatColors.Green + "+" + ((pbSpeed - currentSpeed) * -1).ToString("0"); // We multiply by -1 to get the positive value + } + else if (pbSpeed - currentSpeed > 0.0) + { + strPbDifference += "(" + ChatColors.Red + "-" + (pbSpeed - currentSpeed).ToString("0"); + } + strPbDifference += ChatColors.Default + ")"; + } + + // Print checkpoint message + _player.Controller.PrintToChat( + $"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " + + $"{ChatColors.Yellow}{_player.HUD.FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + + $"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " + + $"[PB: {strPbDifference} | " + + $"WR: {strWrDifference}]"); + + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] PB: {pbTime} - CURR: {currentTime} = pbTime: {pbTime - currentTime}"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] PB: {pbSpeed} - CURR: {currentSpeed} = difference: {pbSpeed - currentSpeed}"); + #endif + } } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 64180c9..76d78bf 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -1,3 +1,5 @@ +using MySqlConnector; + namespace SurfTimer; // To-do: make Style (currently 0) be dynamic @@ -6,6 +8,7 @@ internal class PersonalBest { public int ID { get; set; } public int RunTime { get; set; } + public List Checkpoints { get; set; } // To-do: this should be a dictionary with CP as key and Checkpoint as value // public int Type { get; set; } public float StartVelX { get; set; } public float StartVelY { get; set; } @@ -16,11 +19,44 @@ internal class PersonalBest public int RunDate { get; set; } // Add other properties as needed + internal class Checkpoint + { + public int CP { get; set; } + public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + public int Ticks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + public float Speed { get; set; } + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public float EndTouch { get; set; } + public int Attempts { get; set; } + + public Checkpoint(int cp, int runTime, int ticks, float speed, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + { + CP = cp; + RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + Speed = speed; + StartVelX = startVelX; + StartVelY = startVelY; + StartVelZ = startVelZ; + EndVelX = endVelX; + EndVelY = endVelY; + EndVelZ = endVelZ; + EndTouch = endTouch; + Attempts = attempts; + } + } + // Constructor public PersonalBest(int id, int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) { ID = id; - RunTime = runTime; + RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Checkpoints = new List(); // To-do: this should be a dictionary with CP as key and Checkpoint as value // Type = type; StartVelX = startVelX; StartVelY = startVelY; @@ -30,6 +66,85 @@ public PersonalBest(int id, int runTime, float startVelX, float startVelY, float EndVelZ = endVelZ; RunDate = runDate; } + + // Executes the DB query to parse the checkpoints + public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) + { + Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {mapTimeId};"); + MySqlDataReader results = dbTask.Result; + if (this == null) + { + #if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> LoadCheckpointsForRun -> PersonalBest object is null."); + #endif + + results.Close(); + return; + } + + if (this.Checkpoints == null) + { + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); + #endif + + this.Checkpoints = new List(); // Initialize if null + } + + #if DEBUG + Console.WriteLine($"this.Checkpoints.Count {this.Checkpoints.Count} "); + Console.WriteLine($"this.ID {this.ID} "); + Console.WriteLine($"this.RunTime {this.RunTime} "); + Console.WriteLine($"this.RunDate {this.RunDate} "); + #endif + + if (!results.HasRows) + { + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> No checkpoints found for this mapTimeId {this.ID}."); + #endif + + results.Close(); + return; + } + + #if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints found for this mapTimeId"); + #endif + + while (results.Read()) + { + #if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetFloat("run_time")} "); + Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); + Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); + #endif + + Checkpoint cp = new(results.GetInt32("cp"), + results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + results.GetInt32("run_time"), // To-do: this was supposed to be the ticks but that is used for run_time for HUD + 666.666f, + results.GetFloat("start_vel_x"), + results.GetFloat("start_vel_y"), + results.GetFloat("start_vel_z"), + results.GetFloat("end_vel_x"), + results.GetFloat("end_vel_y"), + results.GetFloat("end_vel_z"), + results.GetFloat("end_touch"), + results.GetInt32("attempts")); + Checkpoints.Add(cp); + + #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.RunTime}."); + #endif + } + results.Close(); + + #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoints.Count}"); + #endif + } } internal class PlayerStats @@ -48,7 +163,43 @@ public PlayerStats() // Add more styles as needed } public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) - public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index + // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + + public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) + // public void LoadMapTimesData(MySqlDataReader results) + { + Task dbTask2 = DB.Query($"SELECT * FROM `MapTimes` WHERE `player_id` = {playerId} AND `map_id` = {mapId};"); + MySqlDataReader playerStats = dbTask2.Result; + int style = 0; + if (!playerStats.HasRows) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); + } + else + { + while (playerStats.Read()) + { + // style = playerStats.GetInt32("style"); // Uncomment when style is implemented + // Load data into PersonalBest object + PB[style].ID = playerStats.GetInt32("id"); + PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + PB[style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + PB[style].RunTime = playerStats.GetInt32("run_time"); + PB[style].RunDate = playerStats.GetInt32("run_date"); + + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> {PB[style].ID} | {PB[style].RunTime} | {PB[style].StartVelX} | {PB[style].StartVelY} | {PB[style].StartVelZ} | {PB[style].EndVelX} | {PB[style].EndVelY} | {PB[style].EndVelZ} | {PB[style].RunDate}"); + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats (ID {PB[style].ID}) loaded from DB."); + #endif + } + } + playerStats.Close(); + } + } \ No newline at end of file diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index f153908..249542f 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -3,17 +3,19 @@ namespace SurfTimer; internal class PlayerTimer { // Status - public bool Enabled { get; set; } = true; // Enable toggle for entire timer - public bool Paused { get; set; } = false; // Pause toggle for timer + public bool IsEnabled { get; set; } = true; // Enable toggle for entire timer + public bool IsPaused { get; set; } = false; // Pause toggle for timer public bool IsRunning { get; set; } = false; // Is the timer currently running? // Modes - public bool PracticeMode { get; set; } = false; // Practice mode toggle - public bool StageMode { get; set; } = false; // Stage mode toggle + public bool IsPracticeMode { get; set; } = false; // Practice mode toggle + public bool IsStageMode { get; set; } = false; // Stage mode toggle // Tracking public int Stage { get; set; } = 0; // Current stage tracker - public int Bonus { get; set; } = 0; // Current bonus tracker - To-do: bonus implementation + public int Checkpoint {get; set;} = 0; // Current checkpoint tracker + public List CurrentRunCheckpoints { get; set; } = new List(); // To-do: make this a dictionary? - Current run cps list + public int Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker // public int Style = 0; // To-do: style implementation // Timing @@ -33,19 +35,21 @@ public void Reset() this.Stop(); this.Ticks = 0; this.Stage = 0; - this.Paused = false; - this.PracticeMode = false; + this.Checkpoint = 0; + this.IsPaused = false; + this.IsPracticeMode = false; + this.CurrentRunCheckpoints.Clear(); } public void Pause() { - this.Paused = true; + this.IsPaused = true; } public void Start() { // Timer Start method - notes: OnStartTimerPress - if (this.Enabled) + if (this.IsEnabled) this.IsRunning = true; } @@ -59,7 +63,7 @@ public void Tick() { // Tick the timer - this checks for any restrictions, so can be conveniently called from anywhere // without worry for any timing restrictions (eg: Paused, Enabled, etc) - if (this.Paused || !this.Enabled || !this.IsRunning) + if (this.IsPaused || !this.IsEnabled || !this.IsRunning) return; this.Ticks++; diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index b81578a..5487106 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -56,9 +56,10 @@ public partial class SurfTimer : BasePlugin public void OnMapStart(string mapName) { // Initialise Map Object + // To-do: It seems like players connect very quickly and sometimes `CurrentMap` is null when it shouldn't be, lowered the timer ot 0.5 seconds for now if ((CurrentMap == null || CurrentMap.Name != mapName) && mapName.Contains("surf_")) { - AddTimer(3.0f, () => CurrentMap = new Map(mapName, DB!)); + AddTimer(1.0f, () => CurrentMap = new Map(mapName, DB!)); // Was 3 seconds, now 1 second } } From e338ba471d4bf75c5ae23977d3cde99d23df938e Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:11:18 +0200 Subject: [PATCH 13/56] use DisplayCheckpointMessages --- src/ST-Events/TriggerStartTouch.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 114a2fd..b5ff52f 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -194,6 +194,7 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); #endif + // Print checkpoint message player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality @@ -230,15 +231,9 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `checkpoint`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); #endif + // Print checkpoint message - player.Controller.PrintToChat( - $"{PluginPrefix} CP [{ChatColors.Yellow}{checkpoint}{ChatColors.Default}]: " + - $"{ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} " + - $"{ChatColors.Yellow}({velocity.ToString("0")}){ChatColors.Default} " + - $"[PB: {ChatColors.Green}-00:00.000{ChatColors.Default} " + - $"{ChatColors.Red}(N/A){ChatColors.Default} | " + - $"WR: {ChatColors.Red}+PL:CE.HLD{ChatColors.Default} " + - $"{ChatColors.Green}(+1234){ChatColors.Default}]"); + player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality PersonalBest.Checkpoint cp2 = new PersonalBest.Checkpoint(checkpoint, From 08f0ced6bc9d3010a1d1af33164b18ffa491df89 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:19:30 +0200 Subject: [PATCH 14/56] no warnings --- src/ST-Player/PlayerHUD.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 8dfc75e..6977d5d 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -121,7 +121,11 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[0].Checkpoints[{_player.Timer.Checkpoint - 1}] = {pbSpeed}"); #endif } + #if DEBUG catch (System.Exception ex) + #else + catch (System.Exception) + #endif { // Handle the exception gracefully without stopping the application // We assign default values to pbTime and pbSpeed From a97c3397a024125232bd3039a379f7ca0d3e59f1 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:23:04 +0200 Subject: [PATCH 15/56] show Prespeed for stages --- src/ST-Events/TriggerEndTouch.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 32cf6ce..6d9de6d 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -89,12 +89,17 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) currentCheckpoint.EndVelZ = velocity_z; currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? currentCheckpoint.Attempts += 1; + + // Show Prespeed for stages - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } else { // Handle the case where the index is out of bounds } } + + // To-do: Checkpoint zones -- hook into "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$" map_c(heck)p(oint) } return HookResult.Continue; From b04fc58165b3ed28a0a5f8a1c6a044930a5ee9f4 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:51:54 +0200 Subject: [PATCH 16/56] Checkpoint is now a Dictionary --- src/ST-Events/TriggerStartTouch.cs | 8 ++++---- src/ST-Player/PlayerHUD.cs | 14 +++++++------- src/ST-Player/PlayerStats.cs | 20 ++++++++++---------- src/ST-Player/PlayerTimer.cs | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index b5ff52f..998b398 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -191,14 +191,14 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) #if DEBUG Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); #endif // Print checkpoint message player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - PersonalBest.Checkpoint cp2 = new PersonalBest.Checkpoint(stage, + PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(stage, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD velocity, @@ -229,14 +229,14 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) { #if DEBUG Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `checkpoint`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoints.Count = {player.Stats.PB[0].Checkpoints.Count}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); #endif // Print checkpoint message player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - PersonalBest.Checkpoint cp2 = new PersonalBest.Checkpoint(checkpoint, + PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(checkpoint, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD velocity, diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 6977d5d..abf8fbd 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -111,14 +111,14 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].RunTime; // Should we use -1 here? - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelX * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelX - + _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelY * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelY - + _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelZ * _player.Stats.PB[0].Checkpoints[_player.Timer.Checkpoint - 1].StartVelZ); + pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].RunTime; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoints[{_player.Timer.Checkpoint - 1} = {pbTime}]"); - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[0].Checkpoints[{_player.Timer.Checkpoint - 1}] = {pbSpeed}"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint} = {pbTime}]"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint}] = {pbSpeed}"); #endif } #if DEBUG @@ -134,7 +134,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi #if DEBUG Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred: {ex.Message}"); - Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred Player has no PB and therefore no Checkpoints | _player.Stats.PB[0].Checkpoints.Count = {_player.Stats.PB[0].Checkpoints.Count}"); + Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred Player has no PB and therefore no Checkpoints | _player.Stats.PB[0].Checkpoint.Count = {_player.Stats.PB[0].Checkpoint.Count}"); #endif } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 76d78bf..38b121d 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -8,7 +8,7 @@ internal class PersonalBest { public int ID { get; set; } public int RunTime { get; set; } - public List Checkpoints { get; set; } // To-do: this should be a dictionary with CP as key and Checkpoint as value + public Dictionary Checkpoint { get; set; } // public int Type { get; set; } public float StartVelX { get; set; } public float StartVelY { get; set; } @@ -19,7 +19,7 @@ internal class PersonalBest public int RunDate { get; set; } // Add other properties as needed - internal class Checkpoint + internal class CheckpointObject { public int CP { get; set; } public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? @@ -34,7 +34,7 @@ internal class Checkpoint public float EndTouch { get; set; } public int Attempts { get; set; } - public Checkpoint(int cp, int runTime, int ticks, float speed, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + public CheckpointObject(int cp, int runTime, int ticks, float speed, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) { CP = cp; RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? @@ -56,7 +56,7 @@ public PersonalBest(int id, int runTime, float startVelX, float startVelY, float { ID = id; RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Checkpoints = new List(); // To-do: this should be a dictionary with CP as key and Checkpoint as value + Checkpoint = new Dictionary(); // Type = type; StartVelX = startVelX; StartVelY = startVelY; @@ -82,17 +82,17 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) return; } - if (this.Checkpoints == null) + if (this.Checkpoint == null) { #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); #endif - this.Checkpoints = new List(); // Initialize if null + this.Checkpoint = new Dictionary(); // Initialize if null } #if DEBUG - Console.WriteLine($"this.Checkpoints.Count {this.Checkpoints.Count} "); + Console.WriteLine($"this.Checkpoint.Count {this.Checkpoint.Count} "); Console.WriteLine($"this.ID {this.ID} "); Console.WriteLine($"this.RunTime {this.RunTime} "); Console.WriteLine($"this.RunDate {this.RunDate} "); @@ -121,7 +121,7 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); #endif - Checkpoint cp = new(results.GetInt32("cp"), + CheckpointObject cp = new(results.GetInt32("cp"), results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? results.GetInt32("run_time"), // To-do: this was supposed to be the ticks but that is used for run_time for HUD 666.666f, @@ -133,7 +133,7 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) results.GetFloat("end_vel_z"), results.GetFloat("end_touch"), results.GetInt32("attempts")); - Checkpoints.Add(cp); + Checkpoint[cp.CP] = cp; #if DEBUG Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.RunTime}."); @@ -142,7 +142,7 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) results.Close(); #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoints.Count}"); + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoint.Count}"); #endif } } diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 249542f..2db164b 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -14,7 +14,7 @@ internal class PlayerTimer // Tracking public int Stage { get; set; } = 0; // Current stage tracker public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public List CurrentRunCheckpoints { get; set; } = new List(); // To-do: make this a dictionary? - Current run cps list + public List CurrentRunCheckpoints { get; set; } = new List(); // To-do: make this a dictionary? - Current run cps list public int Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker // public int Style = 0; // To-do: style implementation From 16a8fc906ec4431166671a35b7d6695d8b9fd2c4 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:11:53 +0200 Subject: [PATCH 17/56] CurrentRunCheckpoints is now a Dictionary --- src/ST-Events/TriggerEndTouch.cs | 2 +- src/ST-Events/TriggerStartTouch.cs | 28 +++++++++++++++------------- src/ST-Player/PlayerTimer.cs | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 6d9de6d..19d584b 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -76,7 +76,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Timer.CurrentRunCheckpoints.Count) { - var currentCheckpoint = player.Timer.CurrentRunCheckpoints[player.Timer.Checkpoint - 1]; + var currentCheckpoint = player.Timer.CurrentRunCheckpoints[player.Timer.Checkpoint]; #if DEBUG Console.WriteLine($"currentCheckpoint.EndVelX {currentCheckpoint.EndVelX} - velocity_x {velocity_x}"); Console.WriteLine($"currentCheckpoint.EndVelY {currentCheckpoint.EndVelY} - velocity_y {velocity_y}"); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 998b398..cc48716 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -116,17 +116,17 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) // Loop through the checkpoints and insert/update them in the database for the run foreach (var item in player.Timer.CurrentRunCheckpoints) { - int cp = item.CP; - int runTime = item.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - int ticks = item.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD - double speed = item.Speed; - double startVelX = item.StartVelX; - double startVelY = item.StartVelY; - double startVelZ = item.StartVelZ; - double endVelX = item.EndVelX; - double endVelY = item.EndVelY; - double endVelZ = item.EndVelZ; - int attempts = item.Attempts; + int cp = item.Key; + int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + int ticks = item.Value.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD + double speed = item.Value.Speed; + double startVelX = item.Value.StartVelX; + double startVelY = item.Value.StartVelY; + double startVelZ = item.Value.StartVelZ; + double endVelX = item.Value.EndVelX; + double endVelY = item.Value.EndVelY; + double endVelZ = item.Value.EndVelZ; + int attempts = item.Value.Attempts; #if DEBUG Console.WriteLine($"CP: {cp} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); @@ -210,7 +210,8 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) -1.0f, -1.0f, 0); - player.Timer.CurrentRunCheckpoints.Add(cp2); + // player.Timer.CurrentRunCheckpoints.Add(cp2); + player.Timer.CurrentRunCheckpoints[stage] = cp2; } #if DEBUG @@ -248,7 +249,8 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) -1.0f, -1.0f, 0); - player.Timer.CurrentRunCheckpoints.Add(cp2); + // player.Timer.CurrentRunCheckpoints.Add(cp2); + player.Timer.CurrentRunCheckpoints[checkpoint] = cp2; } #if DEBUG diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 2db164b..975621e 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -14,7 +14,7 @@ internal class PlayerTimer // Tracking public int Stage { get; set; } = 0; // Current stage tracker public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public List CurrentRunCheckpoints { get; set; } = new List(); // To-do: make this a dictionary? - Current run cps list + public Dictionary CurrentRunCheckpoints { get; set; } = new Dictionary(); // Current RUN checkpoints tracker public int Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker // public int Style = 0; // To-do: style implementation From 66567d5a812ba6a42e57ef8ceb8ea477d9a488ee Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:13:01 +0200 Subject: [PATCH 18/56] --build tasks for vscode --- .gitignore | 4 +++- .vscode/tasks.json | 27 +++++++++++++++++++++++++++ src/SurfTimer.csproj | 5 +++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 7812496..3485604 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.vs -*.vscode +*.vscode/* +!.vscode/tasks.json *.idea src/bin/Debug/* +src/bin/Release/* src/obj/* src/SurfTimer.csproj \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..68c000f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build-debug", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/SurfTimer.csproj", + "/property:Configuration=Debug" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build-release", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/SurfTimer.csproj", + "/property:Configuration=Release" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 8d66987..9a0f5c3 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -6,6 +6,11 @@ enable + + + DEBUG + + From 09dea3f0b1235c1c5620502c994161a9d09fbd74 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 01:13:55 +0200 Subject: [PATCH 19/56] move CP saving to SaveCurrentRunCheckpoints --- src/ST-Events/TriggerStartTouch.cs | 44 +-------------------- src/ST-Player/PlayerStats.cs | 61 +++++++++++++++++++++++++++--- src/SurfTimer.cs | 2 +- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index cc48716..74b4a5d 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -112,49 +112,7 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) updatePlayerRunTask.Dispose(); - // To-do: Transactions? Server freezes for a bit here sometimes - // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Timer.CurrentRunCheckpoints) - { - int cp = item.Key; - int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - int ticks = item.Value.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD - double speed = item.Value.Speed; - double startVelX = item.Value.StartVelX; - double startVelY = item.Value.StartVelY; - double startVelZ = item.Value.StartVelZ; - double endVelX = item.Value.EndVelX; - double endVelY = item.Value.EndVelY; - double endVelZ = item.Value.EndVelZ; - int attempts = item.Value.Attempts; - - #if DEBUG - Console.WriteLine($"CP: {cp} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + - $"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); - #endif - - // Insert/Update CPs to database - // To-do: Transactions? - // Check if the player has PB object initialized and if the player's character is currently active in the game - if (player.Stats.PB[0] != null && player.Controller.PlayerPawn.Value != null) - { - Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); - if (newPbTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Checkpoint zones) -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); - newPbTask.Dispose(); - } - } + player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data player.Stats.PB[0].LoadCheckpointsForRun(player.Stats.PB[0].ID, DB); // Load the Checkpoints PB data again } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 38b121d..3be5525 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -17,6 +17,9 @@ internal class PersonalBest public float EndVelY { get; set; } public float EndVelZ { get; set; } public int RunDate { get; set; } + public float currentRunStartVelX { get; set; } + public float currentRunStartVelY { get; set; } + public float currentRunStartVelZ { get; set; } // Add other properties as needed internal class CheckpointObject @@ -67,7 +70,7 @@ public PersonalBest(int id, int runTime, float startVelX, float startVelY, float RunDate = runDate; } - // Executes the DB query to parse the checkpoints + // Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) { Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {mapTimeId};"); @@ -107,7 +110,7 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) results.Close(); return; } - + #if DEBUG Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints found for this mapTimeId"); #endif @@ -145,6 +148,54 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoint.Count}"); #endif } + + // To-do: Transactions? Server freezes for a bit here sometimes + // Saves the CurrentRunCheckpoints to the database + public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) + { + // Loop through the checkpoints and insert/update them in the database for the run + foreach (var item in player.Timer.CurrentRunCheckpoints) + { + int cp = item.Key; + int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + int ticks = item.Value.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD + double speed = item.Value.Speed; + double startVelX = item.Value.StartVelX; + double startVelY = item.Value.StartVelY; + double startVelZ = item.Value.StartVelZ; + double endVelX = item.Value.EndVelX; + double endVelY = item.Value.EndVelY; + double endVelZ = item.Value.EndVelZ; + int attempts = item.Value.Attempts; + + #if DEBUG + Console.WriteLine($"CP: {cp} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + + $"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + #endif + + // Insert/Update CPs to database + // To-do: Transactions? + // Check if the player has PB object initialized and if the player's character is currently active in the game + if (player.Stats.PB[0] != null && player.Controller.PlayerPawn.Value != null) + { + Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + if (newPbTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Checkpoint zones) -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); + newPbTask.Dispose(); + } + } + } } internal class PlayerStats @@ -167,8 +218,8 @@ public PlayerStats() public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + // This can populate all the `style` stats the player has for the map - currently only 1 style is supported public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) - // public void LoadMapTimesData(MySqlDataReader results) { Task dbTask2 = DB.Query($"SELECT * FROM `MapTimes` WHERE `player_id` = {playerId} AND `map_id` = {mapId};"); MySqlDataReader playerStats = dbTask2.Result; @@ -195,11 +246,11 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> {PB[style].ID} | {PB[style].RunTime} | {PB[style].StartVelX} | {PB[style].StartVelY} | {PB[style].StartVelZ} | {PB[style].EndVelX} | {PB[style].EndVelY} | {PB[style].EndVelZ} | {PB[style].RunDate}"); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats (ID {PB[style].ID}) loaded from DB."); + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif } } - playerStats.Close(); + playerStats.Close(); } } \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 5487106..64ffae0 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -56,7 +56,7 @@ public partial class SurfTimer : BasePlugin public void OnMapStart(string mapName) { // Initialise Map Object - // To-do: It seems like players connect very quickly and sometimes `CurrentMap` is null when it shouldn't be, lowered the timer ot 0.5 seconds for now + // To-do: It seems like players connect very quickly and sometimes `CurrentMap` is null when it shouldn't be, lowered the timer ot 1.0 seconds for now if ((CurrentMap == null || CurrentMap.Name != mapName) && mapName.Contains("surf_")) { AddTimer(1.0f, () => CurrentMap = new Map(mapName, DB!)); // Was 3 seconds, now 1 second From a090ebffd5e7353730f7f16a1e0ba7dacf55343c Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 02:08:16 +0200 Subject: [PATCH 20/56] new CurrentRun class --- src/ST-Events/TriggerEndTouch.cs | 6 ++-- src/ST-Events/TriggerStartTouch.cs | 14 +++++----- src/ST-Player/PlayerStats.cs | 45 +++++++++++++++++++++++++++++- src/ST-Player/PlayerTimer.cs | 4 +-- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 19d584b..ad993b8 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -71,12 +71,12 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) { #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); - Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Timer.CurrentRunCheckpoints.Count {player.Timer.CurrentRunCheckpoints.Count}"); + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoint.Count}"); #endif - if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Timer.CurrentRunCheckpoints.Count) + if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoint.Count) { - var currentCheckpoint = player.Timer.CurrentRunCheckpoints[player.Timer.Checkpoint]; + var currentCheckpoint = player.Stats.ThisRun.Checkpoint[player.Timer.Checkpoint]; #if DEBUG Console.WriteLine($"currentCheckpoint.EndVelX {currentCheckpoint.EndVelX} - velocity_x {velocity_x}"); Console.WriteLine($"currentCheckpoint.EndVelY {currentCheckpoint.EndVelY} - velocity_y {velocity_y}"); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 74b4a5d..5a4cbb1 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -142,8 +142,8 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) // To-do:* checkpoint functionality because stages = checkpoints when in a run on a Staged map // To-do:* This triggers more than once at random :monkaHmm: *already posted in CS# about OnPlayerConnect being triggered after OnStartTouch* - // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < stage* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Timer.CurrentRunCheckpoints.Count <= stage) + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoint.Count <= stage) { player.Timer.Checkpoint = stage; // Stage = Checkpoint when in a run on a Staged map @@ -169,7 +169,7 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) -1.0f, 0); // player.Timer.CurrentRunCheckpoints.Add(cp2); - player.Timer.CurrentRunCheckpoints[stage] = cp2; + player.Stats.ThisRun.Checkpoint[stage] = cp2; } #if DEBUG @@ -183,8 +183,8 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; player.Timer.Checkpoint = checkpoint; - // This should patch up re-triggering *player.Timer.CurrentRunCheckpoints.Count < checkpoint* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Timer.CurrentRunCheckpoints.Count < checkpoint) + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoint.Count < checkpoint) { #if DEBUG Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `checkpoint`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); @@ -207,8 +207,8 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) -1.0f, -1.0f, 0); - // player.Timer.CurrentRunCheckpoints.Add(cp2); - player.Timer.CurrentRunCheckpoints[checkpoint] = cp2; + // player.Timer.CurrentRunCheckpoints[checkpoint] = cp2; + player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; } #if DEBUG diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 3be5525..2f99ce9 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -2,6 +2,48 @@ namespace SurfTimer; +internal class CurrentRun +{ + public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public int RunTime { get; set; } // To-do: will be the last (any) zone end touch time + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public int RunDate { get; set; } + // Add other properties as needed + + // Constructor + public CurrentRun() + { + Checkpoint = new Dictionary(); + RunTime = 0; + StartVelX = 0.0f; + StartVelY = 0.0f; + StartVelZ = 0.0f; + EndVelX = 0.0f; + EndVelY = 0.0f; + EndVelZ = 0.0f; + RunDate = 0; + } + + public void Reset() + { + Checkpoint.Clear(); + RunTime = 0; + StartVelX = 0.0f; + StartVelY = 0.0f; + StartVelZ = 0.0f; + EndVelX = 0.0f; + EndVelY = 0.0f; + EndVelZ = 0.0f; + RunDate = 0; + // Reset other properties as needed + } +} + // To-do: make Style (currently 0) be dynamic // To-do: add `Type` internal class PersonalBest @@ -154,7 +196,7 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) { // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Timer.CurrentRunCheckpoints) + foreach (var item in player.Stats.ThisRun.Checkpoint) // player.Timer.CurrentRunCheckpoints { int cp = item.Key; int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? @@ -213,6 +255,7 @@ public PlayerStats() PB[0] = new PersonalBest(0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); // Add more styles as needed } + public CurrentRun ThisRun {get; set;} = new CurrentRun(); public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 975621e..b23f6e5 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -14,7 +14,7 @@ internal class PlayerTimer // Tracking public int Stage { get; set; } = 0; // Current stage tracker public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public Dictionary CurrentRunCheckpoints { get; set; } = new Dictionary(); // Current RUN checkpoints tracker + public CurrentRun CurrentRunData { get; set; } = new CurrentRun(); // Current RUN data tracker public int Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker // public int Style = 0; // To-do: style implementation @@ -38,7 +38,7 @@ public void Reset() this.Checkpoint = 0; this.IsPaused = false; this.IsPracticeMode = false; - this.CurrentRunCheckpoints.Clear(); + this.CurrentRunData.Reset(); } public void Pause() From ef15fb6568546ab5b69a92a6d8c534dbb7cf5bd1 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 11:29:33 +0200 Subject: [PATCH 21/56] print 1st completion message to all and beating PB as well --- src/ST-Events/TriggerStartTouch.cs | 9 ++++++--- src/ST-Player/PlayerStats.cs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 5a4cbb1..947026d 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -3,6 +3,7 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API; namespace SurfTimer; @@ -71,11 +72,13 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) // To-do: make Style (currently 0) be dynamic if (player.Stats.PB[0].RunTime <= 0) // Player first ever PersonalBest for the map { - player.Controller.PrintToChat($"{PluginPrefix} Congratulations on setting your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + // player.Controller.PrintToChat($"{PluginPrefix} Congratulations on setting your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } else if (player.Timer.Ticks < player.Stats.PB[0].RunTime) // Player beating their existing PersonalBest for the map { - player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); + // player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { @@ -191,7 +194,7 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); #endif - // Print checkpoint message + // Print checkpointfffffffffffffffffffffffffffffffffffffff message player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 2f99ce9..493f027 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -255,7 +255,7 @@ public PlayerStats() PB[0] = new PersonalBest(0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); // Add more styles as needed } - public CurrentRun ThisRun {get; set;} = new CurrentRun(); + public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index From 5e7edff241ad1df3ae6a3b2acc459da5f1df7646 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 11:54:37 +0200 Subject: [PATCH 22/56] add SaveMapTime;utilize CurrentRun class a bit more --- src/ST-Events/TriggerEndTouch.cs | 3 +++ src/ST-Events/TriggerStartTouch.cs | 20 ++++++------------- src/ST-Player/PlayerStats.cs | 31 +++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index ad993b8..4bcf6a9 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -60,6 +60,9 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) // Prespeed display player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); + player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the run + player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the run + player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the run #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 947026d..2fd2654 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -100,21 +100,13 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); #endif + // Populate speed values + player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run + player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run + player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run // Add entry in DB for the run - // To-do: add `type` - // To-do: get the `start_vel` values for the run from CP implementation in other repository implementation of checkpoints and their speeds - Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + - $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + - $"123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation - $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + - $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); - if (updatePlayerRunTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); - else - player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - updatePlayerRunTask.Dispose(); - + player.Stats.PB[0].SaveMapTime(player, DB, CurrentMap.ID); // Save the MapTime PB data + player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data player.Stats.PB[0].LoadCheckpointsForRun(player.Stats.PB[0].ID, DB); // Load the Checkpoints PB data again } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 493f027..f367c64 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -6,12 +6,12 @@ internal class CurrentRun { public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker public int RunTime { get; set; } // To-do: will be the last (any) zone end touch time - public float StartVelX { get; set; } - public float StartVelY { get; set; } - public float StartVelZ { get; set; } - public float EndVelX { get; set; } - public float EndVelY { get; set; } - public float EndVelZ { get; set; } + public float StartVelX { get; set; } // This will store MAP START VELOCITY X + public float StartVelY { get; set; } // This will store MAP START VELOCITY Y + public float StartVelZ { get; set; } // This will store MAP START VELOCITY Z + public float EndVelX { get; set; } // This will store MAP END VELOCITY X + public float EndVelY { get; set; } // This will store MAP END VELOCITY Y + public float EndVelZ { get; set; } // This will store MAP END VELOCITY Z public int RunDate { get; set; } // Add other properties as needed @@ -59,9 +59,6 @@ internal class PersonalBest public float EndVelY { get; set; } public float EndVelZ { get; set; } public int RunDate { get; set; } - public float currentRunStartVelX { get; set; } - public float currentRunStartVelY { get; set; } - public float currentRunStartVelZ { get; set; } // Add other properties as needed internal class CheckpointObject @@ -238,6 +235,22 @@ public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) } } } + + public void SaveMapTime(Player player, TimerDatabase DB, int mapId) + { + // Add entry in DB for the run + // To-do: add `type` + // To-do: get the `start_vel` values for the run from CP implementation in other repository implementation of checkpoints and their speeds + Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + + $"VALUES ({player.Profile.ID}, {mapId}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + if (updatePlayerRunTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + updatePlayerRunTask.Dispose(); + } } internal class PlayerStats From 69fd48bca49f1af2e097ee174929c6fbb087efae Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 12:23:11 +0200 Subject: [PATCH 23/56] don't forget to clear dictionaries --- src/ST-Events/TriggerStartTouch.cs | 6 ++++++ src/ST-Player/PlayerStats.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 2fd2654..631a704 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -135,6 +135,12 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; player.Timer.Stage = stage; + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> !player.Timer.IsStageMode: {!player.Timer.IsStageMode}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.ThisRun.Checkpoint.Count <= stage: {player.Stats.ThisRun.Checkpoint.Count <= stage}"); + #endif + // To-do:* checkpoint functionality because stages = checkpoints when in a run on a Staged map // To-do:* This triggers more than once at random :monkaHmm: *already posted in CS# about OnPlayerConnect being triggered after OnStartTouch* // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index f367c64..5a7dbd5 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -234,6 +234,7 @@ public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) newPbTask.Dispose(); } } + player.Stats.ThisRun.Checkpoint.Clear(); } public void SaveMapTime(Player player, TimerDatabase DB, int mapId) @@ -308,5 +309,4 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) } playerStats.Close(); } - } \ No newline at end of file From d164a5a18bbc1aaa464cc9b0337e00be92e75c86 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 12:41:51 +0200 Subject: [PATCH 24/56] delete some comments --- src/ST-Player/PlayerStats.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 5a7dbd5..22a2c0b 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -241,11 +241,10 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId) { // Add entry in DB for the run // To-do: add `type` - // To-do: get the `start_vel` values for the run from CP implementation in other repository implementation of checkpoints and their speeds Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + $"VALUES ({player.Profile.ID}, {mapId}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + - $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); if (updatePlayerRunTask.Result <= 0) From 9a50ac3ddb67b596250388151deded98d05c9ab2 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 16:31:09 +0200 Subject: [PATCH 25/56] comments --- src/ST-Player/PlayerHUD.cs | 4 ++-- src/ST-Player/PlayerStats.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index abf8fbd..fcdd58d 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -74,12 +74,12 @@ public void Display() float velocity = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X + _player.Controller.PlayerPawn.Value!.AbsVelocity.Y * _player.Controller.PlayerPawn.Value!.AbsVelocity.Y + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); - string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("000"), "#79d1ed") + " u/s"; + string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // PB & WR Modules string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].RunTime > 0 ? FormatTime(_player.Stats.PB[0].RunTime) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic - string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 22a2c0b..98eadf2 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -258,8 +258,6 @@ internal class PlayerStats // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. // Temporarily, we store ticks + basic info so we can experiment - // These account for future style support and a relevant index. - // public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) public Dictionary PB { get; set; } = new Dictionary(); // Initialize default styles (e.g., 0 for normal) @@ -269,8 +267,11 @@ public PlayerStats() // Add more styles as needed } public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run - public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) + + // These account for future style support and a relevant index. + // public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index + public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index @@ -300,7 +301,7 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) PB[style].RunTime = playerStats.GetInt32("run_time"); PB[style].RunDate = playerStats.GetInt32("run_date"); - Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> {PB[style].ID} | {PB[style].RunTime} | {PB[style].StartVelX} | {PB[style].StartVelY} | {PB[style].StartVelZ} | {PB[style].EndVelX} | {PB[style].EndVelY} | {PB[style].EndVelZ} | {PB[style].RunDate}"); + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {playerId} | {PB[style].ID} | {PB[style].RunTime} | {PB[style].StartVelX} | {PB[style].StartVelY} | {PB[style].StartVelZ} | {PB[style].EndVelX} | {PB[style].EndVelY} | {PB[style].EndVelZ} | {PB[style].RunDate}"); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif From 06fbab9e9a9b1a76224c800749b38294612d6e58 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 19:29:47 +0200 Subject: [PATCH 26/56] get and show player rank --- src/ST-Player/PlayerHUD.cs | 2 +- src/ST-Player/PlayerStats.cs | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index fcdd58d..2446c77 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -76,7 +76,7 @@ public void Display() + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module - string rankModule = FormatHUDElementHTML("Rank", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats + string rankModule = FormatHUDElementHTML("Rank", _player.Stats.PB[0].RunTime > 0 ? $"{_player.Stats.PB[0].Rank}" : "N/A", "#7882dd"); // To-do: show total rank count // PB & WR Modules string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].RunTime > 0 ? FormatTime(_player.Stats.PB[0].RunTime) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 98eadf2..b316ec8 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -50,6 +50,7 @@ internal class PersonalBest { public int ID { get; set; } public int RunTime { get; set; } + public int Rank { get; set; } public Dictionary Checkpoint { get; set; } // public int Type { get; set; } public float StartVelX { get; set; } @@ -94,10 +95,11 @@ public CheckpointObject(int cp, int runTime, int ticks, float speed, float start } // Constructor - public PersonalBest(int id, int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) + public PersonalBest(int id, int runTime, int rank, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) { ID = id; RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Rank = rank; Checkpoint = new Dictionary(); // Type = type; StartVelX = startVelX; @@ -260,27 +262,35 @@ internal class PlayerStats public Dictionary PB { get; set; } = new Dictionary(); - // Initialize default styles (e.g., 0 for normal) - public PlayerStats() - { - PB[0] = new PersonalBest(0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); - // Add more styles as needed - } public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run // These account for future style support and a relevant index. // public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index - public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) + // public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + // Initialize PersonalBest for each `style` (e.g., 0 for normal) - this is a temporary solution + public PlayerStats() + { + PB[0] = new PersonalBest(-1, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); + // Add more styles as needed + } + // This can populate all the `style` stats the player has for the map - currently only 1 style is supported + /// + /// Loads the player's MapTimes data from the database along with `Rank` for the run. + /// `Checkpoints` are loaded separately. + /// public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) { - Task dbTask2 = DB.Query($"SELECT * FROM `MapTimes` WHERE `player_id` = {playerId} AND `map_id` = {mapId};"); + Task dbTask2 = DB.Query($"SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery " + + $"WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` " + + $"AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery " + + $"WHERE mainquery.`player_id` = {playerId} AND mainquery.`map_id` = {mapId}; "); MySqlDataReader playerStats = dbTask2.Result; - int style = 0; + int style = 0; // To-do: implement styles if (!playerStats.HasRows) { Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); @@ -300,8 +310,9 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); PB[style].RunTime = playerStats.GetInt32("run_time"); PB[style].RunDate = playerStats.GetInt32("run_date"); + PB[style].Rank = playerStats.GetInt32("rank"); - Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {playerId} | {PB[style].ID} | {PB[style].RunTime} | {PB[style].StartVelX} | {PB[style].StartVelY} | {PB[style].StartVelZ} | {PB[style].EndVelX} | {PB[style].EndVelY} | {PB[style].EndVelZ} | {PB[style].RunDate}"); + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {playerId} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].RunTime} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif From 4453a44bf4704a5ae71599bbd14c67ec470ee7f8 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 19:33:49 +0200 Subject: [PATCH 27/56] get total completions and wr for map (not displaying yet) --- src/ST-Map/Map.cs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index b9d9a94..997c2bc 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -18,6 +18,8 @@ public class Map public bool Ranked {get; set;} = false; public int DateAdded {get; set;} = 0; public int LastPlayed {get; set;} = 0; + public int TotalCompletions {get; set;} = 0; + public int WrRunTime {get; set;} = 0; // Zone Origin Information // Map start/end zones @@ -151,6 +153,11 @@ internal Map(string Name, TimerDatabase DB) int lastPlayedUpdateRows = updater.Result; if (lastPlayedUpdateRows != 1) throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> update Map() -> Failed to update map in database, this shouldnt happen. Map: {Name}"); + updater.Dispose(); + + // Initiates getting the World Records for the map + // To-do: Will this check if no records exist for the map? (i.e. no rows returned) + GetMapRecordAndTotals(DB); // To-do: Implement styles } public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) @@ -162,4 +169,31 @@ public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnO else return false; } + + // Leaving this outside of the constructor for `Map` so we can call it to ONLY update the data when a new world record is set + internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: Implement styles + { + // Get map world records + Task reader = DB.Query($"SELECT * FROM `MapTimes` WHERE `map_id` = {this.ID} AND `style` = {style} ORDER BY `run_time` ASC;'"); + MySqlDataReader mapWrData = reader.Result; + int totalRows = 0; + + if (mapWrData.HasRows) + { + // To-do: Implement bonuses WR + // To-do: Implement stages WR + // To-do: Implement checkpoints WR + while (mapWrData.Read()) + { + if (totalRows == 0) + this.WrRunTime = mapWrData.GetInt32("run_time"); // Fastest run time (WR) for the Map and Style combo + + totalRows++; + } + } + + this.TotalCompletions = totalRows; // Total completions for the map and style + + mapWrData.Close(); + } } \ No newline at end of file From 8cd9008df6a542468731dbcced5c40dc89f65b8f Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:26:57 +0200 Subject: [PATCH 28/56] reload stats on maptime save;comments --- src/ST-Events/Players.cs | 2 +- src/ST-Events/TriggerStartTouch.cs | 36 ++++++----------------- src/ST-Map/Map.cs | 1 - src/ST-Player/PlayerStats.cs | 46 +++++++++++++++++++----------- 4 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 9139cb4..c32d673 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -113,7 +113,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i // To-do: hardcoded Style value // Load MapTimes for the player's PB and their Checkpoints playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0].Profile.ID, CurrentMap.ID, DB); - playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(playerList[player.UserId ?? 0].Stats.PB[0].ID, DB); + playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(DB); // Print join messages Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 631a704..baa90fd 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -24,25 +24,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // To-do: Sometimes this triggers before `OnPlayerConnect` and `playerList` does not contain the player how is this possible :thonk: if (!playerList.ContainsKey(client.UserId ?? 0)) { - /* - CS2 Surf ERROR >> OnTriggerStartTouch -> Player playerList does NOT contain client.UserId, this shouldnt happen. Player: tttt (0) - 11:19:18 [EROR] (cssharp:Core) Error invoking callback - System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. - ---> System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary. - at System.Collections.Generic.Dictionary`2.get_Item(TKey key) - at SurfTimer.SurfTimer.OnTriggerStartTouch(DynamicHook handler) - at InvokeStub_Func`2.Invoke(Object, Object, IntPtr*) - at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) - --- End of inner exception stack trace --- - at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) - at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) - at System.Delegate.DynamicInvokeImpl(Object[] args) - at CounterStrikeSharp.API.Core.FunctionReference.<>c__DisplayClass3_0.<.ctor>b__0(fxScriptContext* context) in /home/runner/work/CounterStrikeSharp/CounterStrikeSharp/managed/CounterStrikeSharp.API/Core/FunctionReference.cs:line 82 - */ - // For some reason, this happens as soon as player connects to the server (randomly) - // Is an "entity" created for the player when they connect which triggers this??? - Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldnt happen. Player: {client.PlayerName} ({client.UserId})"); - // return HookResult.Continue; + Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); } // Implement Trigger Start Touch Here @@ -69,6 +51,11 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) if (player.Timer.IsRunning) { player.Timer.Stop(); + player.Stats.PB[0].RunTime = player.Timer.Ticks; + player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run + player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run + player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run + // To-do: make Style (currently 0) be dynamic if (player.Stats.PB[0].RunTime <= 0) // Player first ever PersonalBest for the map { @@ -85,10 +72,9 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); return HookResult.Continue; } - player.Stats.PB[0].RunTime = player.Timer.Ticks; if (DB == null) - throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldnt happen."); + throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + @@ -100,15 +86,9 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); #endif - // Populate speed values - player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run - player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run - player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run // Add entry in DB for the run player.Stats.PB[0].SaveMapTime(player, DB, CurrentMap.ID); // Save the MapTime PB data player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data - player.Stats.PB[0].LoadCheckpointsForRun(player.Stats.PB[0].ID, DB); // Load the Checkpoints PB data again } #if DEBUG @@ -140,7 +120,7 @@ at System.Delegate.DynamicInvokeImpl(Object[] args) Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> !player.Timer.IsStageMode: {!player.Timer.IsStageMode}"); Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.ThisRun.Checkpoint.Count <= stage: {player.Stats.ThisRun.Checkpoint.Count <= stage}"); #endif - + // To-do:* checkpoint functionality because stages = checkpoints when in a run on a Staged map // To-do:* This triggers more than once at random :monkaHmm: *already posted in CS# about OnPlayerConnect being triggered after OnStartTouch* // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 997c2bc..a603a79 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -156,7 +156,6 @@ internal Map(string Name, TimerDatabase DB) updater.Dispose(); // Initiates getting the World Records for the map - // To-do: Will this check if no records exist for the map? (i.e. no rows returned) GetMapRecordAndTotals(DB); // To-do: Implement styles } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index b316ec8..e7a1b7c 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -2,6 +2,9 @@ namespace SurfTimer; +/// +/// This class stores data for the current run. +/// internal class CurrentRun { public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker @@ -111,10 +114,12 @@ public PersonalBest(int id, int runTime, int rank, float startVelX, float startV RunDate = runDate; } - // Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary - public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) + /// + /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary + /// + public void LoadCheckpointsForRun(TimerDatabase DB) { - Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {mapTimeId};"); + Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {this.ID};"); MySqlDataReader results = dbTask.Result; if (this == null) { @@ -190,12 +195,13 @@ public void LoadCheckpointsForRun(int mapTimeId, TimerDatabase DB) #endif } - // To-do: Transactions? Server freezes for a bit here sometimes - // Saves the CurrentRunCheckpoints to the database - public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) + /// + /// Saves the `CurrentRunCheckpoints` dictionary to the database + /// + public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // To-do: Transactions? Player sometimes rubberbands for a bit here { // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Stats.ThisRun.Checkpoint) // player.Timer.CurrentRunCheckpoints + foreach (var item in player.Stats.ThisRun.Checkpoint) { int cp = item.Key; int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? @@ -210,12 +216,12 @@ public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) int attempts = item.Value.Attempts; #if DEBUG - Console.WriteLine($"CP: {cp} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + + Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> SaveCurrentRunCheckpoints -> " + $"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"VALUES ({this.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); #endif @@ -223,35 +229,42 @@ public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // Insert/Update CPs to database // To-do: Transactions? // Check if the player has PB object initialized and if the player's character is currently active in the game - if (player.Stats.PB[0] != null && player.Controller.PlayerPawn.Value != null) + if (this != null && player.Controller.PlayerPawn.Value != null) { Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"VALUES ({this.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + + $"ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); if (newPbTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch (Checkpoint zones) -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); + throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); newPbTask.Dispose(); } } player.Stats.ThisRun.Checkpoint.Clear(); } - public void SaveMapTime(Player player, TimerDatabase DB, int mapId) + /// + /// Saves the player's run to the database and reloads the data for the player. + /// + public void SaveMapTime(Player player, TimerDatabase DB, int mapId) // To-do: make `CurrentMap.ID` accessible without passing it as a parameter? { // Add entry in DB for the run // To-do: add `type` Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {mapId}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + + $"VALUES ({player.Profile.ID}, {mapId}, 0, 0, 0, {this.RunTime}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); if (updatePlayerRunTask.Result <= 0) throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); updatePlayerRunTask.Dispose(); + + this.SaveCurrentRunCheckpoints(player, DB); // Save checkpoints for this run + this.LoadCheckpointsForRun(DB); // Re-Load checkpoints for this run } } @@ -299,8 +312,8 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) { while (playerStats.Read()) { - // style = playerStats.GetInt32("style"); // Uncomment when style is implemented // Load data into PersonalBest object + // style = playerStats.GetInt32("style"); // Uncomment when style is implemented PB[style].ID = playerStats.GetInt32("id"); PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); @@ -317,6 +330,7 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif } + } playerStats.Close(); } From 1980ad2c198ecd80b7522db5cd80955dc035d6a7 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:38:04 +0200 Subject: [PATCH 29/56] -.- --- src/ST-Events/TriggerStartTouch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index baa90fd..a4bbdc1 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -1,5 +1,4 @@ using System.Text.RegularExpressions; -using System.Text.Json; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; @@ -172,7 +171,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); #endif - // Print checkpointfffffffffffffffffffffffffffffffffffffff message + // Print checkpoint message player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality From bcbec71e341bd082697e4f8fef9343069052cace Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:56:02 +0200 Subject: [PATCH 30/56] comments;move stuff around --- src/ST-Events/TriggerStartTouch.cs | 4 +--- src/ST-Player/PlayerStats.cs | 21 ++++++++------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index a4bbdc1..0b946a6 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -59,17 +59,15 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Stats.PB[0].RunTime <= 0) // Player first ever PersonalBest for the map { Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); - // player.Controller.PrintToChat($"{PluginPrefix} Congratulations on setting your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } else if (player.Timer.Ticks < player.Stats.PB[0].RunTime) // Player beating their existing PersonalBest for the map { - // player.Controller.PrintToChat($"{PluginPrefix} You beat your PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); - return HookResult.Continue; + return HookResult.Continue; // Exit here so we don't write to DB } if (DB == null) diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index e7a1b7c..d010e87 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -272,29 +272,25 @@ internal class PlayerStats { // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. // Temporarily, we store ticks + basic info so we can experiment - - public Dictionary PB { get; set; } = new Dictionary(); - - public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run - // These account for future style support and a relevant index. - // public int[,] PB {get; set;} = {{0,0}}; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) - // public int[,] Checkpoints { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: checkpoint index - // public int[,] Rank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: map/bonus (0 = map, 1+ = bonus index) public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index - + // + + public Dictionary PB { get; set; } = new Dictionary(); + public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run // Initialize PersonalBest for each `style` (e.g., 0 for normal) - this is a temporary solution + // Here we can loop through all available styles at some point and initialize them public PlayerStats() { PB[0] = new PersonalBest(-1, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); // Add more styles as needed } - // This can populate all the `style` stats the player has for the map - currently only 1 style is supported /// /// Loads the player's MapTimes data from the database along with `Rank` for the run. - /// `Checkpoints` are loaded separately. + /// `Checkpoints` are loaded separately because inside the while loop we cannot run queries. + /// This can populate all the `style` stats the player has for the map - currently only 1 style is supported /// public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) { @@ -329,8 +325,7 @@ public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif - } - + } } playerStats.Close(); } From 98c82b62f18237d0317547ace1b471dbef6c2eaf Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:40:23 +0200 Subject: [PATCH 31/56] fix time saving --- src/ST-Events/TriggerStartTouch.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 0b946a6..06964df 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -25,7 +25,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) { Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); } - // Implement Trigger Start Touch Here Player player = playerList[client.UserId ?? 0]; #if DEBUG @@ -46,11 +45,11 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map end zones -- hook into map_end if (trigger.Entity.Name == "map_end") { + player.Controller.PrintToCenter($"Map End"); // MAP END ZONE if (player.Timer.IsRunning) { player.Timer.Stop(); - player.Stats.PB[0].RunTime = player.Timer.Ticks; player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run @@ -84,6 +83,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) #endif // Add entry in DB for the run + player.Stats.PB[0].RunTime = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query player.Stats.PB[0].SaveMapTime(player, DB, CurrentMap.ID); // Save the MapTime PB data player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) } @@ -99,6 +99,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) trigger.Entity.Name.Contains("stage1_start")) { player.Timer.Reset(); + player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); @@ -146,7 +147,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) -1.0f, -1.0f, 0); - // player.Timer.CurrentRunCheckpoints.Add(cp2); player.Stats.ThisRun.Checkpoint[stage] = cp2; } @@ -185,7 +185,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) -1.0f, -1.0f, 0); - // player.Timer.CurrentRunCheckpoints[checkpoint] = cp2; player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; } From d99413aef384cdadbb9d9f3fdb501b4a114079bf Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:04:29 +0200 Subject: [PATCH 32/56] spec mode error fix --- src/ST-Events/TriggerEndTouch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 4bcf6a9..1c5350c 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -14,8 +14,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) CBaseTrigger trigger = handler.GetParam(0); CBaseEntity entity = handler.GetParam(1); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - - if (client.IsBot || !client.IsValid || client.UserId == -1 || !client.PawnIsAlive) + if (!client.IsValid || client.UserId == -1 || !client.PawnIsAlive) // `client.IsBot` throws error in server console when going to spectator? { return HookResult.Continue; } From da281515745438f19935801cdb871ef80a131a08 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sun, 24 Dec 2023 12:11:08 +0200 Subject: [PATCH 33/56] wrong regex used for CP number;add onendtouch cp zones --- src/ST-Events/Players.cs | 5 +++-- src/ST-Events/TriggerEndTouch.cs | 35 +++++++++++++++++++++++++++++- src/ST-Events/TriggerStartTouch.cs | 4 +--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index c32d673..9cabd90 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -110,10 +110,11 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i #if DEBUG Console.WriteLine($"=================================== SELECT * FROM `MapTimes` WHERE `player_id` = {playerList[player.UserId ?? 0].Profile.ID} AND `map_id` = {CurrentMap.ID};"); #endif + // To-do: hardcoded Style value // Load MapTimes for the player's PB and their Checkpoints - playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0].Profile.ID, CurrentMap.ID, DB); - playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(DB); + playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0].Profile.ID, CurrentMap.ID, DB); // Will reload PB and Checkpoints for the player for all styles + playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 // Print join messages Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 1c5350c..b5992ab 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -76,6 +76,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoint.Count}"); #endif + // This will populate the End velocities for the given Checkpoint zone (Stage = Checkpoint when in a Map Run) if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoint.Count) { var currentCheckpoint = player.Stats.ThisRun.Checkpoint[player.Timer.Checkpoint]; @@ -101,7 +102,39 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) } } - // To-do: Checkpoint zones -- hook into "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$" map_c(heck)p(oint) + // Checkpoint zones -- hook into "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$" map_c(heck)p(oint) + else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) + { + #if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoint.Count}"); + #endif + + // This will populate the End velocities for the given Checkpoint zone (Stage = Checkpoint when in a Map Run) + if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoint.Count) + { + var currentCheckpoint = player.Stats.ThisRun.Checkpoint[player.Timer.Checkpoint]; + #if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {currentCheckpoint.EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {currentCheckpoint.EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {currentCheckpoint.EndVelZ} - velocity_z {velocity_z}"); + #endif + + // Update the values + currentCheckpoint.EndVelX = velocity_x; + currentCheckpoint.EndVelY = velocity_y; + currentCheckpoint.EndVelZ = velocity_z; + currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.Attempts += 1; + + // Show Prespeed for stages - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); + } + else + { + // Handle the case where the index is out of bounds + } + } } return HookResult.Continue; diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 06964df..701a2c7 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -119,8 +119,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.ThisRun.Checkpoint.Count <= stage: {player.Stats.ThisRun.Checkpoint.Count <= stage}"); #endif - // To-do:* checkpoint functionality because stages = checkpoints when in a run on a Staged map - // To-do:* This triggers more than once at random :monkaHmm: *already posted in CS# about OnPlayerConnect being triggered after OnStartTouch* // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoint.Count <= stage) { @@ -158,7 +156,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map checkpoint zones -- hook into map_(c)heck(p)oint# else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) { - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; + int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Value) - 1; player.Timer.Checkpoint = checkpoint; // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* From 8b6b2e6f2160c43b7d4e5a6807b24296a4072f58 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sun, 24 Dec 2023 20:50:20 +0200 Subject: [PATCH 34/56] don't re-load data in SaveMapTime yet --- src/ST-Events/TriggerStartTouch.cs | 2 ++ src/ST-Player/PlayerStats.cs | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 701a2c7..c76e1f0 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -86,6 +86,8 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Stats.PB[0].RunTime = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query player.Stats.PB[0].SaveMapTime(player, DB, CurrentMap.ID); // Save the MapTime PB data player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data + player.Stats.PB[0].LoadCheckpointsForRun(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here } #if DEBUG diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index d010e87..3981a1d 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -197,6 +197,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) /// /// Saves the `CurrentRunCheckpoints` dictionary to the database + /// We need the correct `this.ID` to be populated before calling this method otherwise Query will fail /// public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // To-do: Transactions? Player sometimes rubberbands for a bit here { @@ -248,6 +249,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) /// /// Saves the player's run to the database and reloads the data for the player. + /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated /// public void SaveMapTime(Player player, TimerDatabase DB, int mapId) // To-do: make `CurrentMap.ID` accessible without passing it as a parameter? { @@ -263,8 +265,9 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId) // To-do: ma throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); updatePlayerRunTask.Dispose(); - this.SaveCurrentRunCheckpoints(player, DB); // Save checkpoints for this run - this.LoadCheckpointsForRun(DB); // Re-Load checkpoints for this run + // Will have to LoadMapTimesData right here as well to get the ID of the run we just inserted + // this.SaveCurrentRunCheckpoints(player, DB); // Save checkpoints for this run + // this.LoadCheckpointsForRun(DB); // Re-Load checkpoints for this run } } From dc3d2933bc9da8d0f86a81b5f14e3320d38ccd1b Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:55:36 +0200 Subject: [PATCH 35/56] total comps;total completions;reload map data;throw error --- src/ST-Events/Players.cs | 4 ++-- src/ST-Events/TriggerStartTouch.cs | 13 +++++++++---- src/ST-Player/Player.cs | 6 +++++- src/ST-Player/PlayerHUD.cs | 12 ++++++++++-- src/ST-Player/PlayerStats.cs | 8 ++++---- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 9cabd90..287128c 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -105,7 +105,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i PlayerProfile Profile = new PlayerProfile(dbID, name, player.SteamID, country, joinDate, lastSeen, connections); playerList[player.UserId ?? 0] = new Player(player, new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), - Profile); + Profile, CurrentMap); #if DEBUG Console.WriteLine($"=================================== SELECT * FROM `MapTimes` WHERE `player_id` = {playerList[player.UserId ?? 0].Profile.ID} AND `map_id` = {CurrentMap.ID};"); @@ -113,7 +113,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i // To-do: hardcoded Style value // Load MapTimes for the player's PB and their Checkpoints - playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0].Profile.ID, CurrentMap.ID, DB); // Will reload PB and Checkpoints for the player for all styles + playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0], DB); // Will reload PB and Checkpoints for the player for all styles playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 // Print join messages diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index c76e1f0..42a63b3 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -24,6 +24,8 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (!playerList.ContainsKey(client.UserId ?? 0)) { Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + throw new Exception($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + // return HookResult.Continue; } // Implement Trigger Start Touch Here Player player = playerList[client.UserId ?? 0]; @@ -72,22 +74,25 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (DB == null) throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + + player.Stats.PB[0].RunTime = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + $"============== INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + - $"123.000, 456.000, 789.000, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); #endif // Add entry in DB for the run - player.Stats.PB[0].RunTime = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query - player.Stats.PB[0].SaveMapTime(player, DB, CurrentMap.ID); // Save the MapTime PB data - player.Stats.LoadMapTimesData(player.Profile.ID, CurrentMap.ID, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + player.Stats.PB[0].SaveMapTime(player, DB); // Save the MapTime PB data + player.Stats.LoadMapTimesData(player, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data player.Stats.PB[0].LoadCheckpointsForRun(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here + CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD } #if DEBUG diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 3243b29..d6ceab0 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -15,8 +15,11 @@ internal class Player // Player information public PlayerProfile Profile {get; set;} + // Map information + public Map CurrMap = null!; + // Constructor - public Player(CCSPlayerController Controller, CCSPlayer_MovementServices MovementServices, PlayerProfile Profile) + public Player(CCSPlayerController Controller, CCSPlayer_MovementServices MovementServices, PlayerProfile Profile, Map CurrMap) { this.Controller = Controller; this.MovementServices = MovementServices; @@ -27,5 +30,6 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen this.Stats = new PlayerStats(); this.HUD = new PlayerHUD(this); + this.CurrMap = CurrMap; } } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 2446c77..df6f630 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -76,10 +76,18 @@ public void Display() + _player.Controller.PlayerPawn.Value!.AbsVelocity.Z * _player.Controller.PlayerPawn.Value!.AbsVelocity.Z); string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module - string rankModule = FormatHUDElementHTML("Rank", _player.Stats.PB[0].RunTime > 0 ? $"{_player.Stats.PB[0].Rank}" : "N/A", "#7882dd"); // To-do: show total rank count + string rankModule = FormatHUDElementHTML("Rank", $"N/A", "#7882dd"); + if (_player.Stats.PB[0].RunTime > 0 && _player.CurrMap.WrRunTime > 0) + { + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[0].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); + } + else if (_player.CurrMap.WrRunTime >= 0) + { + rankModule = FormatHUDElementHTML("Rank", $"N/A/{_player.CurrMap.TotalCompletions}", "#7882dd"); + } // PB & WR Modules string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].RunTime > 0 ? FormatTime(_player.Stats.PB[0].RunTime) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic - string wrModule = FormatHUDElementHTML("WR", "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? + string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WrRunTime > 0 ? FormatTime(_player.CurrMap.WrRunTime) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 3981a1d..f4d9ba9 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -251,13 +251,13 @@ public void LoadCheckpointsForRun(TimerDatabase DB) /// Saves the player's run to the database and reloads the data for the player. /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated /// - public void SaveMapTime(Player player, TimerDatabase DB, int mapId) // To-do: make `CurrentMap.ID` accessible without passing it as a parameter? + public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) { // Add entry in DB for the run // To-do: add `type` Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {mapId}, 0, 0, 0, {this.RunTime}, " + + $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.RunTime}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -295,12 +295,12 @@ public PlayerStats() /// `Checkpoints` are loaded separately because inside the while loop we cannot run queries. /// This can populate all the `style` stats the player has for the map - currently only 1 style is supported /// - public void LoadMapTimesData(int playerId, int mapId, TimerDatabase DB) + public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) { Task dbTask2 = DB.Query($"SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery " + $"WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` " + $"AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery " + - $"WHERE mainquery.`player_id` = {playerId} AND mainquery.`map_id` = {mapId}; "); + $"WHERE mainquery.`player_id` = {player.Profile.ID} AND mainquery.`map_id` = {player.CurrMap.ID}; "); MySqlDataReader playerStats = dbTask2.Result; int style = 0; // To-do: implement styles if (!playerStats.HasRows) From b5064cf4c6c89d78e25fe3dd72b1932ba5f42a84 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:18:13 +0200 Subject: [PATCH 36/56] get map wr id;no `speed` for cp object; --- src/ST-Events/TriggerStartTouch.cs | 2 -- src/ST-Map/Map.cs | 2 ++ src/ST-Player/PlayerStats.cs | 18 ++++++++---------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 42a63b3..665c210 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -143,7 +143,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(stage, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD - velocity, velocity_x, velocity_y, velocity_z, @@ -181,7 +180,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(checkpoint, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD - velocity, velocity_x, velocity_y, velocity_z, diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index a603a79..65ece8a 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -20,6 +20,7 @@ public class Map public int LastPlayed {get; set;} = 0; public int TotalCompletions {get; set;} = 0; public int WrRunTime {get; set;} = 0; + public int WrId {get; set;} = 0; // Zone Origin Information // Map start/end zones @@ -186,6 +187,7 @@ internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: { if (totalRows == 0) this.WrRunTime = mapWrData.GetInt32("run_time"); // Fastest run time (WR) for the Map and Style combo + this.WrId = mapWrData.GetInt32("id"); // WR ID for the Map and Style combo totalRows++; } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index f4d9ba9..8aae94b 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -65,12 +65,12 @@ internal class PersonalBest public int RunDate { get; set; } // Add other properties as needed - internal class CheckpointObject + public class CheckpointObject { public int CP { get; set; } public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? public int Ticks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - public float Speed { get; set; } + // public float Speed { get; set; } // We shouldn't really need this, we can calculate it from the velocities public float StartVelX { get; set; } public float StartVelY { get; set; } public float StartVelZ { get; set; } @@ -80,12 +80,11 @@ internal class CheckpointObject public float EndTouch { get; set; } public int Attempts { get; set; } - public CheckpointObject(int cp, int runTime, int ticks, float speed, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + public CheckpointObject(int cp, int runTime, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) { CP = cp; RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - Speed = speed; StartVelX = startVelX; StartVelY = startVelY; StartVelZ = startVelZ; @@ -171,9 +170,8 @@ public void LoadCheckpointsForRun(TimerDatabase DB) #endif CheckpointObject cp = new(results.GetInt32("cp"), - results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - results.GetInt32("run_time"), // To-do: this was supposed to be the ticks but that is used for run_time for HUD - 666.666f, + results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + results.GetInt32("run_time"), // To-do: this was supposed to be the ticks but that is used for run_time for HUD results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y"), results.GetFloat("start_vel_z"), @@ -207,7 +205,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) int cp = item.Key; int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? int ticks = item.Value.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD - double speed = item.Value.Speed; + // double speed = item.Value.Speed; double startVelX = item.Value.StartVelX; double startVelY = item.Value.StartVelY; double startVelZ = item.Value.StartVelZ; @@ -217,7 +215,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) int attempts = item.Value.Attempts; #if DEBUG - Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | Speed: {speed} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> SaveCurrentRunCheckpoints -> " + $"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + @@ -324,7 +322,7 @@ public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, PB[style].RunDate = playerStats.GetInt32("run_date"); PB[style].Rank = playerStats.GetInt32("rank"); - Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {playerId} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].RunTime} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {player.Profile.ID} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].RunTime} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif From 62251f9451c4b6b21ce389c76bcdc82051541dd1 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 26 Dec 2023 20:36:39 +0200 Subject: [PATCH 37/56] get/write/update stages and bonuses for map in DB --- src/ST-Map/Map.cs | 49 ++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 65ece8a..ec4ed92 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -38,6 +38,8 @@ public class Map // Constructor internal Map(string Name, TimerDatabase DB) { + // Set map name + this.Name = Name; // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); // Gathering info_teleport_destinations from the map @@ -78,10 +80,12 @@ internal Map(string Name, TimerDatabase DB) if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) { this.StageStartZoneAngles[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.Stages++; // Count stage zones for the map to populate DB } } } - + + // Bonus start zones else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) { this.BonusStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); @@ -92,6 +96,7 @@ internal Map(string Name, TimerDatabase DB) if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) { this.BonusStartZoneAngles[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.Bonuses++; // Count bonus zones for the map to populate DB } } } @@ -107,53 +112,61 @@ internal Map(string Name, TimerDatabase DB) // Gather map information OR create entry Task reader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); MySqlDataReader mapData = reader.Result; - if (mapData.HasRows && mapData.Read()) + bool updateData = false; + if (mapData.HasRows && mapData.Read()) // In here we can check whether MapData in DB is the same as the newly extracted data, if not, update it (as hookzones may have changed on map updates) { this.ID = mapData.GetInt32("id"); - this.Name = Name; this.Author = mapData.GetString("author") ?? "Unknown"; this.Tier = mapData.GetInt32("tier"); - this.Stages = mapData.GetInt32("stages"); - this.Bonuses = mapData.GetInt32("bonuses"); + if (this.Stages != mapData.GetInt32("stages") || this.Bonuses != mapData.GetInt32("bonuses")) + updateData = true; + // this.Stages = mapData.GetInt32("stages"); // this should now be populated accordingly when looping through hookzones for the map + // this.Bonuses = mapData.GetInt32("bonuses"); // this should now be populated accordingly when looping through hookzones for the map this.Ranked = mapData.GetBoolean("ranked"); this.DateAdded = mapData.GetInt32("date_added"); this.LastPlayed = mapData.GetInt32("last_played"); + updateData = true; mapData.Close(); } else { mapData.Close(); - Task writer = DB.Write($"INSERT INTO Maps (name, author, tier, stages, ranked, date_added, last_played) VALUES ('{MySqlHelper.EscapeString(Name)}', 'Unknown', 0, 0, 0, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()})"); + Task writer = DB.Write($"INSERT INTO Maps (name, author, tier, stages, ranked, date_added, last_played) VALUES ('{MySqlHelper.EscapeString(Name)}', 'Unknown', {this.Stages}, {this.Bonuses}, 0, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()})"); int writerRows = writer.Result; if (writerRows != 1) - throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> new Map() -> Failed to write new map to database, this shouldnt happen. Map: {Name}"); + throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> new Map() -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); Task postWriteReader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); MySqlDataReader postWriteMapData = postWriteReader.Result; if (postWriteMapData.HasRows && postWriteMapData.Read()) { this.ID = postWriteMapData.GetInt32("id"); + this.Author = postWriteMapData.GetString("author"); + this.Tier = postWriteMapData.GetInt32("tier"); + // this.Stages = -1; // this should now be populated accordingly when looping through hookzones for the map + // this.Bonuses = -1; // this should now be populated accordingly when looping through hookzones for the map + this.Ranked = postWriteMapData.GetBoolean("ranked"); + this.DateAdded = postWriteMapData.GetInt32("date_added"); + this.LastPlayed = this.DateAdded; } postWriteMapData.Close(); - this.Name = Name; - this.Author = "Unknown"; - this.Tier = -1; - this.Stages = -1; - this.Bonuses = -1; - this.Ranked = false; - this.DateAdded = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - this.LastPlayed = this.DateAdded; return; } // Update the map's last played data in the DB - // Update last_played data - Task updater = DB.Write($"UPDATE Maps SET last_played={(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()} WHERE id = {this.ID}"); + // Update last_played data or update last_played, stages, and bonuses data + string query = $"UPDATE Maps SET last_played={(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()} WHERE id={this.ID}"; + if (updateData) query = $"UPDATE Maps SET last_played={(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, stages={this.Stages}, bonuses={this.Bonuses} WHERE id={this.ID}"; + #if DEBUG + Console.WriteLine($"CS2 Surf ERROR >> OnRoundStart -> update Map() -> Update MapData: {query}"); + #endif + + Task updater = DB.Write(query); int lastPlayedUpdateRows = updater.Result; if (lastPlayedUpdateRows != 1) - throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> update Map() -> Failed to update map in database, this shouldnt happen. Map: {Name}"); + throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> update Map() -> Failed to update map in database, this shouldnt happen. Map: {Name} | was it 'big' update? {updateData}"); updater.Dispose(); // Initiates getting the World Records for the map From ea37f83a3b6eb2886416c5eeffd6ac97901f7c21 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 26 Dec 2023 23:13:53 +0200 Subject: [PATCH 38/56] hook cp zones;wrong cp regex;messages --- src/ST-Commands/MapCommands.cs | 5 ++++- src/ST-Events/TriggerEndTouch.cs | 2 +- src/ST-Events/TriggerStartTouch.cs | 3 ++- src/ST-Map/Map.cs | 11 +++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 6ae6200..72fc191 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -19,7 +19,10 @@ public void MapTier(CCSPlayerController? player, CommandInfo command) if (player == null) return; - player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}"); + if (CurrentMap.Stages > 0) + player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Staged {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}"); + else + player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Linear {ChatColors.Yellow}{CurrentMap.Checkpoints} Checkpoints{ChatColors.Default}"); return; } diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index b5992ab..d13c071 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -128,7 +128,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) currentCheckpoint.Attempts += 1; // Show Prespeed for stages - will be enabled/disabled by the user? - player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); + player.Controller.PrintToCenter($"Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } else { diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 665c210..27cffd9 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -106,6 +106,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) trigger.Entity.Name.Contains("stage1_start")) { player.Timer.Reset(); + player.Stats.ThisRun.Checkpoint.Clear(); // I have the suspicion that the `Timer.Reset()` does not properly reset this object :thonk: player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); #if DEBUG @@ -162,7 +163,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Map checkpoint zones -- hook into map_(c)heck(p)oint# else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) { - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Value) - 1; + int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); player.Timer.Checkpoint = checkpoint; // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index ec4ed92..9a026b7 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -14,6 +14,7 @@ public class Map public string Author {get; set;} = ""; public int Tier {get; set;} = 0; public int Stages {get; set;} = 0; + public int Checkpoints {get; set;} = 0; public int Bonuses {get; set;} = 0; public bool Ranked {get; set;} = false; public int DateAdded {get; set;} = 0; @@ -34,6 +35,8 @@ public class Map public Vector[] BonusStartZone {get;} = Enumerable.Repeat(0, 99).Select(x => new Vector(0,0,0)).ToArray(); // To-do: Implement bonuses public QAngle[] BonusStartZoneAngles {get;} = Enumerable.Repeat(0, 99).Select(x => new QAngle(0,0,0)).ToArray(); // To-do: Implement bonuses public Vector[] BonusEndZone {get;} = Enumerable.Repeat(0, 99).Select(x => new Vector(0,0,0)).ToArray(); // To-do: Implement bonuses + // Map checkpoint zones + public Vector[] CheckpointStartZone {get;} = Enumerable.Repeat(0, 99).Select(x => new Vector(0,0,0)).ToArray(); // Constructor internal Map(string Name, TimerDatabase DB) @@ -84,6 +87,14 @@ internal Map(string Name, TimerDatabase DB) } } } + + // Checkpoint start zones (linear maps) + else if (Regex.Match(trigger.Entity.Name, "^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$").Success) + { + this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.Checkpoints++; // Might be useful to have this in DB entry + // Do we need `info_destination_teleport` data for Checkpoint zones? + } // Bonus start zones else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) From 03447cbdba4cb369c2a0fa92a3f79928e39f0549 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 27 Dec 2023 00:06:19 +0200 Subject: [PATCH 39/56] onmapend --- src/SurfTimer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 64ffae0..830290b 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -63,6 +63,13 @@ public void OnMapStart(string mapName) } } + public void OnMapEnd() + { + // Clear/reset stuff here + CurrentMap = null!; + playerList.Clear(); + } + [GameEventHandler] public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) { @@ -105,6 +112,8 @@ public override void Load(bool hotReload) // Map Start Hook RegisterListener(OnMapStart); + // Map End Hook + RegisterListener(OnMapEnd); // Tick listener RegisterListener(OnTick); From 61edcf24f92cdd2d5c07e7ae50a414a739409772 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:37:05 +0200 Subject: [PATCH 40/56] neater velocity calc --- src/ST-Events/TriggerEndTouch.cs | 6 ++---- src/ST-Events/TriggerStartTouch.cs | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index d13c071..963401a 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -31,13 +31,11 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X - + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y - + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; - + float velocity = (float)Math.Sqrt(velocity_x * velocity_x + velocity_y * velocity_y + velocity_z + velocity_z); + // Map start zones -- hook into map_start, (s)tage1_start if (trigger.Entity.Name.Contains("map_start") || trigger.Entity.Name.Contains("s1_start") || diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 27cffd9..0cb54a2 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -37,12 +37,10 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - float velocity = (float)Math.Sqrt(player.Controller.PlayerPawn.Value!.AbsVelocity.X * player.Controller.PlayerPawn.Value!.AbsVelocity.X - + player.Controller.PlayerPawn.Value!.AbsVelocity.Y * player.Controller.PlayerPawn.Value!.AbsVelocity.Y - + player.Controller.PlayerPawn.Value!.AbsVelocity.Z * player.Controller.PlayerPawn.Value!.AbsVelocity.Z); float velocity_x = player.Controller.PlayerPawn.Value!.AbsVelocity.X; float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; + float velocity = (float)Math.Sqrt(velocity_x * velocity_x + velocity_y * velocity_y + velocity_z + velocity_z); // Map end zones -- hook into map_end if (trigger.Entity.Name == "map_end") From ec76b0a012149561b4ac41185385accea2fb7bf4 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:41:19 +0200 Subject: [PATCH 41/56] disable speed check --- src/ST-Events/TriggerEndTouch.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 963401a..73ced38 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -44,6 +44,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) // MAP START ZONE player.Timer.Start(); + /* Revisit // Wonky Prespeed check // To-do: make the teleportation a bit more elegant (method in a class or something) if (velocity > 666.0) @@ -54,6 +55,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) if (CurrentMap.StartZone != new Vector(0,0,0)) Server.NextFrame(() => player.Controller.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0,0,0), new Vector(0,0,0))); } + */ // Prespeed display player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); From 0ee2e1c6a1015d273579c90a845eece3e35e3680 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:02:06 +0200 Subject: [PATCH 42/56] unnest CheckpointObject --- src/ST-Events/TriggerStartTouch.cs | 4 +- src/ST-Player/PlayerStats.cs | 76 +++++++++++++++--------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 0cb54a2..1403c00 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -139,7 +139,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(stage, + CheckpointObject cp2 = new CheckpointObject(stage, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD velocity_x, @@ -176,7 +176,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - PersonalBest.CheckpointObject cp2 = new PersonalBest.CheckpointObject(checkpoint, + CheckpointObject cp2 = new CheckpointObject(checkpoint, player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD velocity_x, diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 8aae94b..2960e78 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -7,7 +7,7 @@ namespace SurfTimer; /// internal class CurrentRun { - public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker public int RunTime { get; set; } // To-do: will be the last (any) zone end touch time public float StartVelX { get; set; } // This will store MAP START VELOCITY X public float StartVelY { get; set; } // This will store MAP START VELOCITY Y @@ -21,7 +21,7 @@ internal class CurrentRun // Constructor public CurrentRun() { - Checkpoint = new Dictionary(); + Checkpoint = new Dictionary(); RunTime = 0; StartVelX = 0.0f; StartVelY = 0.0f; @@ -47,6 +47,37 @@ public void Reset() } } +public class CheckpointObject +{ + public int CP { get; set; } + public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + public int Ticks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + // public float Speed { get; set; } // We shouldn't really need this, we can calculate it from the velocities + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public float EndTouch { get; set; } + public int Attempts { get; set; } + + public CheckpointObject(int cp, int runTime, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + { + CP = cp; + RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + StartVelX = startVelX; + StartVelY = startVelY; + StartVelZ = startVelZ; + EndVelX = endVelX; + EndVelY = endVelY; + EndVelZ = endVelZ; + EndTouch = endTouch; + Attempts = attempts; + } +} + // To-do: make Style (currently 0) be dynamic // To-do: add `Type` internal class PersonalBest @@ -65,37 +96,6 @@ internal class PersonalBest public int RunDate { get; set; } // Add other properties as needed - public class CheckpointObject - { - public int CP { get; set; } - public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - public int Ticks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - // public float Speed { get; set; } // We shouldn't really need this, we can calculate it from the velocities - public float StartVelX { get; set; } - public float StartVelY { get; set; } - public float StartVelZ { get; set; } - public float EndVelX { get; set; } - public float EndVelY { get; set; } - public float EndVelZ { get; set; } - public float EndTouch { get; set; } - public int Attempts { get; set; } - - public CheckpointObject(int cp, int runTime, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) - { - CP = cp; - RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - StartVelX = startVelX; - StartVelY = startVelY; - StartVelZ = startVelZ; - EndVelX = endVelX; - EndVelY = endVelY; - EndVelZ = endVelZ; - EndTouch = endTouch; - Attempts = attempts; - } - } - // Constructor public PersonalBest(int id, int runTime, int rank, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) { @@ -249,7 +249,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) /// Saves the player's run to the database and reloads the data for the player. /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated /// - public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) + public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) { // Add entry in DB for the run // To-do: add `type` @@ -260,7 +260,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); if (updatePlayerRunTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); updatePlayerRunTask.Dispose(); // Will have to LoadMapTimesData right here as well to get the ID of the run we just inserted @@ -277,9 +277,9 @@ internal class PlayerStats public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index // - + public Dictionary PB { get; set; } = new Dictionary(); - public CurrentRun ThisRun {get; set;} = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run + public CurrentRun ThisRun { get; set; } = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run // Initialize PersonalBest for each `style` (e.g., 0 for normal) - this is a temporary solution // Here we can loop through all available styles at some point and initialize them public PlayerStats() @@ -287,7 +287,7 @@ public PlayerStats() PB[0] = new PersonalBest(-1, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); // Add more styles as needed } - + /// /// Loads the player's MapTimes data from the database along with `Rank` for the run. /// `Checkpoints` are loaded separately because inside the while loop we cannot run queries. From e2464146178e3b524df782773cb0ea793f667177 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 4 Jan 2024 20:52:19 +1100 Subject: [PATCH 43/56] Some edits relevant to upstream draft PR (#3) * Inherit Checkpoint object, replace RunTime with Ticks * Fix errors with creating new Checkpoint * Simplify PersonalBest base class by excluding ID and Rank from constructor --- src/ST-Events/TriggerStartTouch.cs | 24 ++++---- src/ST-Player/PlayerHUD.cs | 6 +- src/ST-Player/PlayerStats.cs | 96 +++++++++++++----------------- 3 files changed, 55 insertions(+), 71 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 1403c00..680b519 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -55,13 +55,13 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run // To-do: make Style (currently 0) be dynamic - if (player.Stats.PB[0].RunTime <= 0) // Player first ever PersonalBest for the map + if (player.Stats.PB[0].Ticks <= 0) // Player first ever PersonalBest for the map { Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } - else if (player.Timer.Ticks < player.Stats.PB[0].RunTime) // Player beating their existing PersonalBest for the map + else if (player.Timer.Ticks < player.Stats.PB[0].Ticks) // Player beating their existing PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].RunTime)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].Ticks)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { @@ -73,13 +73,13 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); - player.Stats.PB[0].RunTime = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + player.Stats.PB[0].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + $"============== INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].RunTime}, " + + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -139,16 +139,16 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - CheckpointObject cp2 = new CheckpointObject(stage, - player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD + Checkpoint cp2 = new Checkpoint(stage, + player.Timer.Ticks, velocity_x, velocity_y, velocity_z, -1.0f, -1.0f, -1.0f, - -1.0f, + 0, + 0, 0); player.Stats.ThisRun.Checkpoint[stage] = cp2; } @@ -176,15 +176,15 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.HUD.DisplayCheckpointMessages(PluginPrefix); // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - CheckpointObject cp2 = new CheckpointObject(checkpoint, - player.Timer.Ticks, // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - player.Timer.Ticks, // To-do: this was supposed to be the ticks but that is used for run_time for HUD + Checkpoint cp2 = new Checkpoint(checkpoint, + player.Timer.Ticks, velocity_x, velocity_y, velocity_z, -1.0f, -1.0f, -1.0f, + 0, -1.0f, 0); player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index df6f630..321f512 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -77,7 +77,7 @@ public void Display() string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", $"N/A", "#7882dd"); - if (_player.Stats.PB[0].RunTime > 0 && _player.CurrMap.WrRunTime > 0) + if (_player.Stats.PB[0].Ticks > 0 && _player.CurrMap.WrRunTime > 0) { rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[0].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); } @@ -86,7 +86,7 @@ public void Display() rankModule = FormatHUDElementHTML("Rank", $"N/A/{_player.CurrMap.TotalCompletions}", "#7882dd"); } // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].RunTime > 0 ? FormatTime(_player.Stats.PB[0].RunTime) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].Ticks > 0 ? FormatTime(_player.Stats.PB[0].Ticks) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WrRunTime > 0 ? FormatTime(_player.CurrMap.WrRunTime) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? // Build HUD @@ -119,7 +119,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].RunTime; + pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].Ticks; pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 2960e78..107db8f 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -7,8 +7,8 @@ namespace SurfTimer; /// internal class CurrentRun { - public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker - public int RunTime { get; set; } // To-do: will be the last (any) zone end touch time + public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public int Ticks { get; set; } // To-do: will be the last (any) zone end touch time public float StartVelX { get; set; } // This will store MAP START VELOCITY X public float StartVelY { get; set; } // This will store MAP START VELOCITY Y public float StartVelZ { get; set; } // This will store MAP START VELOCITY Z @@ -21,8 +21,8 @@ internal class CurrentRun // Constructor public CurrentRun() { - Checkpoint = new Dictionary(); - RunTime = 0; + Checkpoint = new Dictionary(); + Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; StartVelZ = 0.0f; @@ -35,7 +35,7 @@ public CurrentRun() public void Reset() { Checkpoint.Clear(); - RunTime = 0; + Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; StartVelZ = 0.0f; @@ -47,45 +47,14 @@ public void Reset() } } -public class CheckpointObject -{ - public int CP { get; set; } - public int RunTime { get; set; } // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - public int Ticks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - // public float Speed { get; set; } // We shouldn't really need this, we can calculate it from the velocities - public float StartVelX { get; set; } - public float StartVelY { get; set; } - public float StartVelZ { get; set; } - public float EndVelX { get; set; } - public float EndVelY { get; set; } - public float EndVelZ { get; set; } - public float EndTouch { get; set; } - public int Attempts { get; set; } - - public CheckpointObject(int cp, int runTime, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) - { - CP = cp; - RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - StartVelX = startVelX; - StartVelY = startVelY; - StartVelZ = startVelZ; - EndVelX = endVelX; - EndVelY = endVelY; - EndVelZ = endVelZ; - EndTouch = endTouch; - Attempts = attempts; - } -} - // To-do: make Style (currently 0) be dynamic // To-do: add `Type` internal class PersonalBest { - public int ID { get; set; } - public int RunTime { get; set; } - public int Rank { get; set; } - public Dictionary Checkpoint { get; set; } + public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public int Ticks { get; set; } + public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public Dictionary Checkpoint { get; set; } // public int Type { get; set; } public float StartVelX { get; set; } public float StartVelY { get; set; } @@ -97,12 +66,10 @@ internal class PersonalBest // Add other properties as needed // Constructor - public PersonalBest(int id, int runTime, int rank, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) + public PersonalBest(int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) { - ID = id; - RunTime = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Rank = rank; - Checkpoint = new Dictionary(); + Ticks = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Checkpoint = new Dictionary(); // Type = type; StartVelX = startVelX; StartVelY = startVelY; @@ -136,13 +103,13 @@ public void LoadCheckpointsForRun(TimerDatabase DB) Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); #endif - this.Checkpoint = new Dictionary(); // Initialize if null + this.Checkpoint = new Dictionary(); // Initialize if null } #if DEBUG Console.WriteLine($"this.Checkpoint.Count {this.Checkpoint.Count} "); Console.WriteLine($"this.ID {this.ID} "); - Console.WriteLine($"this.RunTime {this.RunTime} "); + Console.WriteLine($"this.Ticks {this.Ticks} "); Console.WriteLine($"this.RunDate {this.RunDate} "); #endif @@ -169,21 +136,24 @@ public void LoadCheckpointsForRun(TimerDatabase DB) Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); #endif - CheckpointObject cp = new(results.GetInt32("cp"), + Checkpoint cp = new(results.GetInt32("cp"), results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - results.GetInt32("run_time"), // To-do: this was supposed to be the ticks but that is used for run_time for HUD results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y"), results.GetFloat("start_vel_z"), results.GetFloat("end_vel_x"), results.GetFloat("end_vel_y"), results.GetFloat("end_vel_z"), + results.GetInt32("run_date"), results.GetFloat("end_touch"), results.GetInt32("attempts")); + cp.ID = results.GetInt32("id"); + // To-do: cp.ID = calculate Rank # from DB + Checkpoint[cp.CP] = cp; #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.RunTime}."); + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); #endif } results.Close(); @@ -203,8 +173,8 @@ public void LoadCheckpointsForRun(TimerDatabase DB) foreach (var item in player.Stats.ThisRun.Checkpoint) { int cp = item.Key; - int runTime = item.Value.RunTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - int ticks = item.Value.Ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD + int ticks = item.Value.Ticks; + int runTime = item.Value.Ticks / 64; // Runtime in decimal // double speed = item.Value.Speed; double startVelX = item.Value.StartVelX; double startVelY = item.Value.StartVelY; @@ -255,7 +225,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do: add `type` Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.RunTime}, " + + $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -269,6 +239,20 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) } } +internal class Checkpoint : PersonalBest +{ + public int CP { get; set; } // Checkpoint number + public float EndTouch { get; set; } + public int Attempts { get; set; } + + public Checkpoint(int cp, int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate, float endTouch, int attempts) : base(runTime, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, runDate) + { + CP = cp; + EndTouch = endTouch; + Attempts = attempts; + } +} + internal class PlayerStats { // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. @@ -284,7 +268,7 @@ internal class PlayerStats // Here we can loop through all available styles at some point and initialize them public PlayerStats() { - PB[0] = new PersonalBest(-1, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); + PB[0] = new PersonalBest(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); // Add more styles as needed } @@ -318,11 +302,11 @@ public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - PB[style].RunTime = playerStats.GetInt32("run_time"); + PB[style].Ticks = playerStats.GetInt32("run_time"); PB[style].RunDate = playerStats.GetInt32("run_date"); PB[style].Rank = playerStats.GetInt32("rank"); - Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {player.Profile.ID} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].RunTime} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {player.Profile.ID} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].Ticks} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif From 378467840778f19bbe39ad25a234d3d439ae5b78 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:13:48 +0200 Subject: [PATCH 44/56] parameterless PersonalBest;Checkpoint inherits PersonalBest --- src/ST-Events/TriggerEndTouch.cs | 20 +++---- src/ST-Events/TriggerStartTouch.cs | 4 +- src/ST-Player/PlayerHUD.cs | 8 +-- src/ST-Player/PlayerStats.cs | 86 +++++++++++++++++------------- 4 files changed, 64 insertions(+), 54 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 73ced38..244ca68 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -87,11 +87,11 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #endif // Update the values - currentCheckpoint.EndVelX = velocity_x; - currentCheckpoint.EndVelY = velocity_y; - currentCheckpoint.EndVelZ = velocity_z; - currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? - currentCheckpoint.Attempts += 1; + currentCheckpoint.CpEndVelX = velocity_x; + currentCheckpoint.CpEndVelY = velocity_y; + currentCheckpoint.CpEndVelZ = velocity_z; + currentCheckpoint.CpEndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.CpAttempts += 1; // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); @@ -121,11 +121,11 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #endif // Update the values - currentCheckpoint.EndVelX = velocity_x; - currentCheckpoint.EndVelY = velocity_y; - currentCheckpoint.EndVelZ = velocity_z; - currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? - currentCheckpoint.Attempts += 1; + currentCheckpoint.CpEndVelX = velocity_x; + currentCheckpoint.CpEndVelY = velocity_y; + currentCheckpoint.CpEndVelZ = velocity_z; + currentCheckpoint.CpEndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.CpAttempts += 1; // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 680b519..bdcd9d8 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -147,8 +147,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) -1.0f, -1.0f, -1.0f, - 0, - 0, + -1.0f, 0); player.Stats.ThisRun.Checkpoint[stage] = cp2; } @@ -184,7 +183,6 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) -1.0f, -1.0f, -1.0f, - 0, -1.0f, 0); player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 321f512..dfe64ff 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -119,10 +119,10 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].Ticks; - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); + pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpTicks; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelX + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelY + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelZ); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint} = {pbTime}]"); diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 107db8f..66f7750 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -47,6 +47,34 @@ public void Reset() } } +internal class Checkpoint : PersonalBest +{ + public int CP { get; set; } + public int CpTicks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + public float CpStartVelX { get; set; } + public float CpStartVelY { get; set; } + public float CpStartVelZ { get; set; } + public float CpEndVelX { get; set; } + public float CpEndVelY { get; set; } + public float CpEndVelZ { get; set; } + public float CpEndTouch { get; set; } + public int CpAttempts { get; set; } + + public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + { + CP = cp; + CpTicks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + CpStartVelX = startVelX; + CpStartVelY = startVelY; + CpStartVelZ = startVelZ; + CpEndVelX = endVelX; + CpEndVelY = endVelY; + CpEndVelZ = endVelZ; + CpEndTouch = endTouch; + CpAttempts = attempts; + } +} + // To-do: make Style (currently 0) be dynamic // To-do: add `Type` internal class PersonalBest @@ -66,18 +94,18 @@ internal class PersonalBest // Add other properties as needed // Constructor - public PersonalBest(int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate) + public PersonalBest() { - Ticks = runTime; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Ticks = -1; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? Checkpoint = new Dictionary(); // Type = type; - StartVelX = startVelX; - StartVelY = startVelY; - StartVelZ = startVelZ; - EndVelX = endVelX; - EndVelY = endVelY; - EndVelZ = endVelZ; - RunDate = runDate; + StartVelX = -1.0f; + StartVelY = -1.0f; + StartVelZ = -1.0f; + EndVelX = -1.0f; + EndVelY = -1.0f; + EndVelZ = -1.0f; + RunDate = 0; } /// @@ -144,10 +172,9 @@ public void LoadCheckpointsForRun(TimerDatabase DB) results.GetFloat("end_vel_x"), results.GetFloat("end_vel_y"), results.GetFloat("end_vel_z"), - results.GetInt32("run_date"), results.GetFloat("end_touch"), results.GetInt32("attempts")); - cp.ID = results.GetInt32("id"); + cp.ID = results.GetInt32("cp"); // To-do: cp.ID = calculate Rank # from DB Checkpoint[cp.CP] = cp; @@ -173,16 +200,15 @@ public void LoadCheckpointsForRun(TimerDatabase DB) foreach (var item in player.Stats.ThisRun.Checkpoint) { int cp = item.Key; - int ticks = item.Value.Ticks; - int runTime = item.Value.Ticks / 64; // Runtime in decimal - // double speed = item.Value.Speed; - double startVelX = item.Value.StartVelX; - double startVelY = item.Value.StartVelY; - double startVelZ = item.Value.StartVelZ; - double endVelX = item.Value.EndVelX; - double endVelY = item.Value.EndVelY; - double endVelZ = item.Value.EndVelZ; - int attempts = item.Value.Attempts; + int ticks = item.Value.CpTicks; + int runTime = item.Value.CpTicks / 64; // Runtime in decimal + double startVelX = item.Value.CpStartVelX; + double startVelY = item.Value.CpStartVelY; + double startVelZ = item.Value.CpStartVelZ; + double endVelX = item.Value.CpEndVelX; + double endVelY = item.Value.CpEndVelY; + double endVelZ = item.Value.CpEndVelZ; + int attempts = item.Value.CpAttempts; #if DEBUG Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); @@ -239,20 +265,6 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) } } -internal class Checkpoint : PersonalBest -{ - public int CP { get; set; } // Checkpoint number - public float EndTouch { get; set; } - public int Attempts { get; set; } - - public Checkpoint(int cp, int runTime, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int runDate, float endTouch, int attempts) : base(runTime, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, runDate) - { - CP = cp; - EndTouch = endTouch; - Attempts = attempts; - } -} - internal class PlayerStats { // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. @@ -268,7 +280,7 @@ internal class PlayerStats // Here we can loop through all available styles at some point and initialize them public PlayerStats() { - PB[0] = new PersonalBest(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); + PB[0] = new PersonalBest(); // Add more styles as needed } @@ -310,7 +322,7 @@ public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); #endif - } + } } playerStats.Close(); } From 8bc3f66d3b818db0751e390b279768ed1e89427f Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:26:30 +0200 Subject: [PATCH 45/56] move Checkpoint methods --- src/ST-Events/Players.cs | 2 +- src/ST-Events/TriggerStartTouch.cs | 4 +- src/ST-Player/PlayerStats.cs | 85 +++++++++++++++--------------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 287128c..6364feb 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -114,7 +114,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i // To-do: hardcoded Style value // Load MapTimes for the player's PB and their Checkpoints playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0], DB); // Will reload PB and Checkpoints for the player for all styles - playerList[player.UserId ?? 0].Stats.PB[0].LoadCheckpointsForRun(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 + playerList[player.UserId ?? 0].Stats.PB[0].Checkpoint[0].LoadCheckpointsForRun(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 - regardless of index for `Checkpoint[X]` it will load all checkpoints // Print join messages Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index bdcd9d8..7348c7b 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -88,8 +88,8 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Add entry in DB for the run player.Stats.PB[0].SaveMapTime(player, DB); // Save the MapTime PB data player.Stats.LoadMapTimesData(player, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - player.Stats.PB[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data - player.Stats.PB[0].LoadCheckpointsForRun(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here + player.Stats.PB[0].Checkpoint[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data + player.Stats.PB[0].Checkpoint[0].LoadCheckpointsForRun(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD } diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs index 66f7750..cc2435a 100644 --- a/src/ST-Player/PlayerStats.cs +++ b/src/ST-Player/PlayerStats.cs @@ -73,40 +73,6 @@ public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float sta CpEndTouch = endTouch; CpAttempts = attempts; } -} - -// To-do: make Style (currently 0) be dynamic -// To-do: add `Type` -internal class PersonalBest -{ - public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public int Ticks { get; set; } - public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public Dictionary Checkpoint { get; set; } - // public int Type { get; set; } - public float StartVelX { get; set; } - public float StartVelY { get; set; } - public float StartVelZ { get; set; } - public float EndVelX { get; set; } - public float EndVelY { get; set; } - public float EndVelZ { get; set; } - public int RunDate { get; set; } - // Add other properties as needed - - // Constructor - public PersonalBest() - { - Ticks = -1; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Checkpoint = new Dictionary(); - // Type = type; - StartVelX = -1.0f; - StartVelY = -1.0f; - StartVelZ = -1.0f; - EndVelX = -1.0f; - EndVelY = -1.0f; - EndVelZ = -1.0f; - RunDate = 0; - } /// /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary @@ -118,7 +84,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) if (this == null) { #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> LoadCheckpointsForRun -> PersonalBest object is null."); + Console.WriteLine("CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> PersonalBest object is null."); #endif results.Close(); @@ -128,7 +94,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) if (this.Checkpoint == null) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); + Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); #endif this.Checkpoint = new Dictionary(); // Initialize if null @@ -144,7 +110,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) if (!results.HasRows) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> No checkpoints found for this mapTimeId {this.ID}."); + Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> No checkpoints found for this mapTimeId {this.ID}."); #endif results.Close(); @@ -152,7 +118,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) } #if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints found for this mapTimeId"); + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints found for this mapTimeId"); #endif while (results.Read()) @@ -180,13 +146,13 @@ public void LoadCheckpointsForRun(TimerDatabase DB) Checkpoint[cp.CP] = cp; #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); #endif } results.Close(); #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoint.Count}"); + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoint.Count}"); #endif } @@ -212,7 +178,7 @@ public void LoadCheckpointsForRun(TimerDatabase DB) #if DEBUG Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> SaveCurrentRunCheckpoints -> " + + Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + $"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + @@ -234,13 +200,48 @@ public void LoadCheckpointsForRun(TimerDatabase DB) $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); if (newPbTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); + throw new Exception($"CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); newPbTask.Dispose(); } } player.Stats.ThisRun.Checkpoint.Clear(); } +} + +// To-do: make Style (currently 0) be dynamic +// To-do: add `Type` +internal class PersonalBest +{ + public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public int Ticks { get; set; } + public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public Dictionary Checkpoint { get; set; } + // public int Type { get; set; } + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public int RunDate { get; set; } + // Add other properties as needed + + // Constructor + public PersonalBest() + { + Ticks = -1; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Checkpoint = new Dictionary(); + // Type = type; + StartVelX = -1.0f; + StartVelY = -1.0f; + StartVelZ = -1.0f; + EndVelX = -1.0f; + EndVelY = -1.0f; + EndVelZ = -1.0f; + RunDate = 0; + } + /// /// Saves the player's run to the database and reloads the data for the player. /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated From d3c0b2eada812271f12aaa7be5d3d825c717dfbc Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 9 Jan 2024 21:16:08 +1100 Subject: [PATCH 46/56] More edits to the PR: https://github.com/CS2Surf/Timer/pull/8 (#4) * Tidy up player stats classes * Move Checkpoints load method to PlayerStats * Use info_teleport_destinations for player spawn locations * Look for info_teleport_destinations with guideline names * Fallback if associated info_teleport_destination cannot be found * Append `spawn_` to info_teleport_destination standards * Resolve warnings --- src/ST-Events/Players.cs | 2 +- src/ST-Events/TriggerEndTouch.cs | 20 +- src/ST-Events/TriggerStartTouch.cs | 6 +- src/ST-Map/Map.cs | 51 +++- src/ST-Player/PlayerHUD.cs | 8 +- src/ST-Player/PlayerStats.cs | 330 ---------------------- src/ST-Player/PlayerStats/Checkpoint.cs | 24 ++ src/ST-Player/PlayerStats/CurrentRun.cs | 122 ++++++++ src/ST-Player/PlayerStats/PersonalBest.cs | 35 +++ src/ST-Player/PlayerStats/PlayerStats.cs | 148 ++++++++++ 10 files changed, 388 insertions(+), 358 deletions(-) delete mode 100644 src/ST-Player/PlayerStats.cs create mode 100644 src/ST-Player/PlayerStats/Checkpoint.cs create mode 100644 src/ST-Player/PlayerStats/CurrentRun.cs create mode 100644 src/ST-Player/PlayerStats/PersonalBest.cs create mode 100644 src/ST-Player/PlayerStats/PlayerStats.cs diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 6364feb..5d442ac 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -114,7 +114,7 @@ public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo i // To-do: hardcoded Style value // Load MapTimes for the player's PB and their Checkpoints playerList[player.UserId ?? 0].Stats.LoadMapTimesData(playerList[player.UserId ?? 0], DB); // Will reload PB and Checkpoints for the player for all styles - playerList[player.UserId ?? 0].Stats.PB[0].Checkpoint[0].LoadCheckpointsForRun(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 - regardless of index for `Checkpoint[X]` it will load all checkpoints + playerList[player.UserId ?? 0].Stats.LoadCheckpointsData(DB); // To-do: This really should go inside `LoadMapTimesData` imo cuz here we hardcoding load for Style 0 // Print join messages Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Green}{player.PlayerName}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 244ca68..73ced38 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -87,11 +87,11 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #endif // Update the values - currentCheckpoint.CpEndVelX = velocity_x; - currentCheckpoint.CpEndVelY = velocity_y; - currentCheckpoint.CpEndVelZ = velocity_z; - currentCheckpoint.CpEndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? - currentCheckpoint.CpAttempts += 1; + currentCheckpoint.EndVelX = velocity_x; + currentCheckpoint.EndVelY = velocity_y; + currentCheckpoint.EndVelZ = velocity_z; + currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.Attempts += 1; // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); @@ -121,11 +121,11 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) #endif // Update the values - currentCheckpoint.CpEndVelX = velocity_x; - currentCheckpoint.CpEndVelY = velocity_y; - currentCheckpoint.CpEndVelZ = velocity_z; - currentCheckpoint.CpEndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? - currentCheckpoint.CpAttempts += 1; + currentCheckpoint.EndVelX = velocity_x; + currentCheckpoint.EndVelY = velocity_y; + currentCheckpoint.EndVelZ = velocity_z; + currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? + currentCheckpoint.Attempts += 1; // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 7348c7b..a5ba595 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -86,10 +86,10 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) #endif // Add entry in DB for the run - player.Stats.PB[0].SaveMapTime(player, DB); // Save the MapTime PB data + player.Stats.ThisRun.SaveMapTime(player, DB); // Save the MapTime PB data player.Stats.LoadMapTimesData(player, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - player.Stats.PB[0].Checkpoint[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data - player.Stats.PB[0].Checkpoint[0].LoadCheckpointsForRun(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here + player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints + player.Stats.LoadCheckpointsData(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD } diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 9a026b7..0ad0d13 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -56,14 +56,26 @@ internal Map(string Name, TimerDatabase DB) trigger.Entity!.Name.Contains("stage1_start") || trigger.Entity!.Name.Contains("s1_start")) { - this.StartZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { - if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) + if (teleport.Entity!.Name != null && + (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || + teleport.Entity!.Name.Contains("spawn_map_start") || + teleport.Entity!.Name.Contains("spawn_stage1_start") || + teleport.Entity!.Name.Contains("spawn_s1_start"))) { + this.StartZone = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); this.StartZoneAngles = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + foundPlayerSpawn = true; + break; } } + + if (!foundPlayerSpawn) + { + this.StartZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + } } // Map end zone @@ -75,17 +87,27 @@ internal Map(string Name, TimerDatabase DB) // Stage start zones else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) { - this.StageStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); - + int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + // Find an info_destination_teleport inside this zone to grab angles from + bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { - if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) + if (teleport.Entity!.Name != null && + (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || (Regex.Match(teleport.Entity.Name, "^spawn_s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success && Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == stage))) { - this.StageStartZoneAngles[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.StageStartZone[stage - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StageStartZoneAngles[stage - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Stages++; // Count stage zones for the map to populate DB + foundPlayerSpawn = true; + break; } } + + if (!foundPlayerSpawn) + { + this.StageStartZone[stage - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + } } // Checkpoint start zones (linear maps) @@ -93,23 +115,32 @@ internal Map(string Name, TimerDatabase DB) { this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.Checkpoints++; // Might be useful to have this in DB entry - // Do we need `info_destination_teleport` data for Checkpoint zones? } // Bonus start zones else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) { - this.BonusStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + int bonus = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); // Find an info_destination_teleport inside this zone to grab angles from + bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { - if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) + if (teleport.Entity!.Name != null && + (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || (Regex.Match(teleport.Entity.Name, "^spawn_b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success && Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == bonus))) { - this.BonusStartZoneAngles[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.BonusStartZone[bonus - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.BonusStartZoneAngles[bonus - 1] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Bonuses++; // Count bonus zones for the map to populate DB + foundPlayerSpawn = true; + break; } } + + if (!foundPlayerSpawn) + { + this.BonusStartZone[bonus - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + } } else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index dfe64ff..321f512 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -119,10 +119,10 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpTicks; - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelX - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelY - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].CpStartVelZ); + pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].Ticks; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY + + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint} = {pbTime}]"); diff --git a/src/ST-Player/PlayerStats.cs b/src/ST-Player/PlayerStats.cs deleted file mode 100644 index cc2435a..0000000 --- a/src/ST-Player/PlayerStats.cs +++ /dev/null @@ -1,330 +0,0 @@ -using MySqlConnector; - -namespace SurfTimer; - -/// -/// This class stores data for the current run. -/// -internal class CurrentRun -{ - public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker - public int Ticks { get; set; } // To-do: will be the last (any) zone end touch time - public float StartVelX { get; set; } // This will store MAP START VELOCITY X - public float StartVelY { get; set; } // This will store MAP START VELOCITY Y - public float StartVelZ { get; set; } // This will store MAP START VELOCITY Z - public float EndVelX { get; set; } // This will store MAP END VELOCITY X - public float EndVelY { get; set; } // This will store MAP END VELOCITY Y - public float EndVelZ { get; set; } // This will store MAP END VELOCITY Z - public int RunDate { get; set; } - // Add other properties as needed - - // Constructor - public CurrentRun() - { - Checkpoint = new Dictionary(); - Ticks = 0; - StartVelX = 0.0f; - StartVelY = 0.0f; - StartVelZ = 0.0f; - EndVelX = 0.0f; - EndVelY = 0.0f; - EndVelZ = 0.0f; - RunDate = 0; - } - - public void Reset() - { - Checkpoint.Clear(); - Ticks = 0; - StartVelX = 0.0f; - StartVelY = 0.0f; - StartVelZ = 0.0f; - EndVelX = 0.0f; - EndVelY = 0.0f; - EndVelZ = 0.0f; - RunDate = 0; - // Reset other properties as needed - } -} - -internal class Checkpoint : PersonalBest -{ - public int CP { get; set; } - public int CpTicks { get; set; } // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - public float CpStartVelX { get; set; } - public float CpStartVelY { get; set; } - public float CpStartVelZ { get; set; } - public float CpEndVelX { get; set; } - public float CpEndVelY { get; set; } - public float CpEndVelZ { get; set; } - public float CpEndTouch { get; set; } - public int CpAttempts { get; set; } - - public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) - { - CP = cp; - CpTicks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? - CpStartVelX = startVelX; - CpStartVelY = startVelY; - CpStartVelZ = startVelZ; - CpEndVelX = endVelX; - CpEndVelY = endVelY; - CpEndVelZ = endVelZ; - CpEndTouch = endTouch; - CpAttempts = attempts; - } - - /// - /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary - /// - public void LoadCheckpointsForRun(TimerDatabase DB) - { - Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {this.ID};"); - MySqlDataReader results = dbTask.Result; - if (this == null) - { - #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> PersonalBest object is null."); - #endif - - results.Close(); - return; - } - - if (this.Checkpoint == null) - { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints list is not initialized."); - #endif - - this.Checkpoint = new Dictionary(); // Initialize if null - } - - #if DEBUG - Console.WriteLine($"this.Checkpoint.Count {this.Checkpoint.Count} "); - Console.WriteLine($"this.ID {this.ID} "); - Console.WriteLine($"this.Ticks {this.Ticks} "); - Console.WriteLine($"this.RunDate {this.RunDate} "); - #endif - - if (!results.HasRows) - { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> No checkpoints found for this mapTimeId {this.ID}."); - #endif - - results.Close(); - return; - } - - #if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints found for this mapTimeId"); - #endif - - while (results.Read()) - { - #if DEBUG - Console.WriteLine($"cp {results.GetInt32("cp")} "); - Console.WriteLine($"run_time {results.GetFloat("run_time")} "); - Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); - Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); - #endif - - Checkpoint cp = new(results.GetInt32("cp"), - results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - results.GetFloat("start_vel_x"), - results.GetFloat("start_vel_y"), - results.GetFloat("start_vel_z"), - results.GetFloat("end_vel_x"), - results.GetFloat("end_vel_y"), - results.GetFloat("end_vel_z"), - results.GetFloat("end_touch"), - results.GetInt32("attempts")); - cp.ID = results.GetInt32("cp"); - // To-do: cp.ID = calculate Rank # from DB - - Checkpoint[cp.CP] = cp; - - #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); - #endif - } - results.Close(); - - #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsForRun -> Checkpoints loaded from DB. Count: {Checkpoint.Count}"); - #endif - } - - /// - /// Saves the `CurrentRunCheckpoints` dictionary to the database - /// We need the correct `this.ID` to be populated before calling this method otherwise Query will fail - /// - public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // To-do: Transactions? Player sometimes rubberbands for a bit here - { - // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Stats.ThisRun.Checkpoint) - { - int cp = item.Key; - int ticks = item.Value.CpTicks; - int runTime = item.Value.CpTicks / 64; // Runtime in decimal - double startVelX = item.Value.CpStartVelX; - double startVelY = item.Value.CpStartVelY; - double startVelZ = item.Value.CpStartVelZ; - double endVelX = item.Value.CpEndVelX; - double endVelY = item.Value.CpEndVelY; - double endVelZ = item.Value.CpEndVelZ; - int attempts = item.Value.CpAttempts; - - #if DEBUG - Console.WriteLine($"CP: {cp} | MapTime ID: {this.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + - $"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({this.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); - #endif - - // Insert/Update CPs to database - // To-do: Transactions? - // Check if the player has PB object initialized and if the player's character is currently active in the game - if (this != null && player.Controller.PlayerPawn.Value != null) - { - Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({this.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + - $"ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); - if (newPbTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); - newPbTask.Dispose(); - } - } - player.Stats.ThisRun.Checkpoint.Clear(); - } - -} - -// To-do: make Style (currently 0) be dynamic -// To-do: add `Type` -internal class PersonalBest -{ - public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public int Ticks { get; set; } - public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public Dictionary Checkpoint { get; set; } - // public int Type { get; set; } - public float StartVelX { get; set; } - public float StartVelY { get; set; } - public float StartVelZ { get; set; } - public float EndVelX { get; set; } - public float EndVelY { get; set; } - public float EndVelZ { get; set; } - public int RunDate { get; set; } - // Add other properties as needed - - // Constructor - public PersonalBest() - { - Ticks = -1; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? - Checkpoint = new Dictionary(); - // Type = type; - StartVelX = -1.0f; - StartVelY = -1.0f; - StartVelZ = -1.0f; - EndVelX = -1.0f; - EndVelY = -1.0f; - EndVelZ = -1.0f; - RunDate = 0; - } - - /// - /// Saves the player's run to the database and reloads the data for the player. - /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated - /// - public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) - { - // Add entry in DB for the run - // To-do: add `type` - Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + - $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.Ticks}, " + - $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + - $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + - $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); - if (updatePlayerRunTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); - updatePlayerRunTask.Dispose(); - - // Will have to LoadMapTimesData right here as well to get the ID of the run we just inserted - // this.SaveCurrentRunCheckpoints(player, DB); // Save checkpoints for this run - // this.LoadCheckpointsForRun(DB); // Re-Load checkpoints for this run - } -} - -internal class PlayerStats -{ - // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. - // Temporarily, we store ticks + basic info so we can experiment - // These account for future style support and a relevant index. - public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index - public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index - // - - public Dictionary PB { get; set; } = new Dictionary(); - public CurrentRun ThisRun { get; set; } = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run - // Initialize PersonalBest for each `style` (e.g., 0 for normal) - this is a temporary solution - // Here we can loop through all available styles at some point and initialize them - public PlayerStats() - { - PB[0] = new PersonalBest(); - // Add more styles as needed - } - - /// - /// Loads the player's MapTimes data from the database along with `Rank` for the run. - /// `Checkpoints` are loaded separately because inside the while loop we cannot run queries. - /// This can populate all the `style` stats the player has for the map - currently only 1 style is supported - /// - public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) - { - Task dbTask2 = DB.Query($"SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery " + - $"WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` " + - $"AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery " + - $"WHERE mainquery.`player_id` = {player.Profile.ID} AND mainquery.`map_id` = {player.CurrMap.ID}; "); - MySqlDataReader playerStats = dbTask2.Result; - int style = 0; // To-do: implement styles - if (!playerStats.HasRows) - { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); - } - else - { - while (playerStats.Read()) - { - // Load data into PersonalBest object - // style = playerStats.GetInt32("style"); // Uncomment when style is implemented - PB[style].ID = playerStats.GetInt32("id"); - PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - PB[style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - PB[style].Ticks = playerStats.GetInt32("run_time"); - PB[style].RunDate = playerStats.GetInt32("run_date"); - PB[style].Rank = playerStats.GetInt32("rank"); - - Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {player.Profile.ID} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].Ticks} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); - #endif - } - } - playerStats.Close(); - } -} \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs new file mode 100644 index 0000000..30921f5 --- /dev/null +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -0,0 +1,24 @@ +using MySqlConnector; + +namespace SurfTimer; + +internal class Checkpoint : PersonalBest +{ + public int CP { get; set; } + public float EndTouch { get; set; } + public int Attempts { get; set; } + + public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, float endTouch, int attempts) + { + CP = cp; + Ticks = ticks; // To-do: this was supposed to be the ticks but that is used for run_time for HUD???? + StartVelX = startVelX; + StartVelY = startVelY; + StartVelZ = startVelZ; + EndVelX = endVelX; + EndVelY = endVelY; + EndVelZ = endVelZ; + EndTouch = endTouch; + Attempts = attempts; + } +} \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs new file mode 100644 index 0000000..3ddf620 --- /dev/null +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -0,0 +1,122 @@ +namespace SurfTimer; + +/// +/// This class stores data for the current run. +/// +internal class CurrentRun +{ + public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public int Ticks { get; set; } // To-do: will be the last (any) zone end touch time + public float StartVelX { get; set; } // This will store MAP START VELOCITY X + public float StartVelY { get; set; } // This will store MAP START VELOCITY Y + public float StartVelZ { get; set; } // This will store MAP START VELOCITY Z + public float EndVelX { get; set; } // This will store MAP END VELOCITY X + public float EndVelY { get; set; } // This will store MAP END VELOCITY Y + public float EndVelZ { get; set; } // This will store MAP END VELOCITY Z + public int RunDate { get; set; } + // Add other properties as needed + + // Constructor + public CurrentRun() + { + Checkpoint = new Dictionary(); + Ticks = 0; + StartVelX = 0.0f; + StartVelY = 0.0f; + StartVelZ = 0.0f; + EndVelX = 0.0f; + EndVelY = 0.0f; + EndVelZ = 0.0f; + RunDate = 0; + } + + public void Reset() + { + Checkpoint.Clear(); + Ticks = 0; + StartVelX = 0.0f; + StartVelY = 0.0f; + StartVelZ = 0.0f; + EndVelX = 0.0f; + EndVelY = 0.0f; + EndVelZ = 0.0f; + RunDate = 0; + // Reset other properties as needed + } + + /// + /// Saves the player's run to the database and reloads the data for the player. + /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated + /// + public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do: Styles + { + // Add entry in DB for the run + // To-do: add `type` + Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + + $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.Ticks}, " + + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + if (updatePlayerRunTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + updatePlayerRunTask.Dispose(); + + // Will have to LoadMapTimesData right here as well to get the ID of the run we just inserted + // this.SaveCurrentRunCheckpoints(player, DB); // Save checkpoints for this run + // this.LoadCheckpointsForRun(DB); // Re-Load checkpoints for this run + } + + /// + /// Saves the `CurrentRunCheckpoints` dictionary to the database + /// We need the correct `this.ID` to be populated before calling this method otherwise Query will fail + /// + public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // To-do: Transactions? Player sometimes rubberbands for a bit here + { + // Loop through the checkpoints and insert/update them in the database for the run + foreach (var item in player.Stats.ThisRun.Checkpoint) + { + int cp = item.Key; + int ticks = item.Value!.Ticks; + int runTime = item.Value!.Ticks / 64; // Runtime in decimal + double startVelX = item.Value!.StartVelX; + double startVelY = item.Value!.StartVelY; + double startVelZ = item.Value!.StartVelZ; + double endVelX = item.Value!.EndVelX; + double endVelY = item.Value!.EndVelY; + double endVelZ = item.Value!.EndVelZ; + int attempts = item.Value!.Attempts; + + #if DEBUG + Console.WriteLine($"CP: {cp} | MapTime ID: {item.Value.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + + $"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({item.Value.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + #endif + + // Insert/Update CPs to database + // To-do: Transactions? + // Check if the player has PB object initialized and if the player's character is currently active in the game + if (item.Value != null && player.Controller.PlayerPawn.Value != null) + { + Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + + $"VALUES ({item.Value.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + + $"ON DUPLICATE KEY UPDATE " + + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + if (newPbTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); + + newPbTask.Dispose(); + } + } + + player.Stats.ThisRun.Checkpoint.Clear(); + } +} diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs new file mode 100644 index 0000000..181d126 --- /dev/null +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -0,0 +1,35 @@ +namespace SurfTimer; + +// To-do: make Style (currently 0) be dynamic +// To-do: add `Type` +internal class PersonalBest +{ + public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public int Ticks { get; set; } + public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + public Dictionary Checkpoint { get; set; } + // public int Type { get; set; } + public float StartVelX { get; set; } + public float StartVelY { get; set; } + public float StartVelZ { get; set; } + public float EndVelX { get; set; } + public float EndVelY { get; set; } + public float EndVelZ { get; set; } + public int RunDate { get; set; } + // Add other properties as needed + + // Constructor + public PersonalBest() + { + Ticks = -1; // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + Checkpoint = new Dictionary(); + // Type = type; + StartVelX = -1.0f; + StartVelY = -1.0f; + StartVelZ = -1.0f; + EndVelX = -1.0f; + EndVelY = -1.0f; + EndVelZ = -1.0f; + RunDate = 0; + } +} \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs new file mode 100644 index 0000000..65dcc39 --- /dev/null +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -0,0 +1,148 @@ +using MySqlConnector; + +namespace SurfTimer; + +internal class PlayerStats +{ + // To-Do: Each stat should be a class of its own, with its own methods and properties - easier to work with. + // Temporarily, we store ticks + basic info so we can experiment + // These account for future style support and a relevant index. + public int[,] StagePB { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + public int[,] StageRank { get; set; } = { { 0, 0 } }; // First dimension: style (0 = normal), second dimension: stage index + // + + public Dictionary PB { get; set; } = new Dictionary(); + public CurrentRun ThisRun { get; set; } = new CurrentRun(); // This is a CurrenntRun object that tracks the data for the Player's current run + // Initialize PersonalBest for each `style` (e.g., 0 for normal) - this is a temporary solution + // Here we can loop through all available styles at some point and initialize them + public PlayerStats() + { + PB[0] = new PersonalBest(); + // Add more styles as needed + } + + /// + /// Loads the player's MapTimes data from the database along with `Rank` for the run. + /// `Checkpoints` are loaded separately because inside the while loop we cannot run queries. + /// This can populate all the `style` stats the player has for the map - currently only 1 style is supported + /// + public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) + { + Task dbTask2 = DB.Query($"SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery " + + $"WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` " + + $"AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery " + + $"WHERE mainquery.`player_id` = {player.Profile.ID} AND mainquery.`map_id` = {player.CurrMap.ID}; "); + MySqlDataReader playerStats = dbTask2.Result; + int style = 0; // To-do: implement styles + if (!playerStats.HasRows) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); + } + else + { + while (playerStats.Read()) + { + // Load data into PersonalBest object + // style = playerStats.GetInt32("style"); // Uncomment when style is implemented + PB[style].ID = playerStats.GetInt32("id"); + PB[style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + PB[style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + PB[style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + PB[style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + PB[style].Ticks = playerStats.GetInt32("run_time"); + PB[style].RunDate = playerStats.GetInt32("run_date"); + PB[style].Rank = playerStats.GetInt32("rank"); + + Console.WriteLine($"============== CS2 Surf DEBUG >> LoadMapTimesData -> PlayerID: {player.Profile.ID} | Rank: {PB[style].Rank} | ID: {PB[style].ID} | RunTime: {PB[style].Ticks} | SVX: {PB[style].StartVelX} | SVY: {PB[style].StartVelY} | SVZ: {PB[style].StartVelZ} | EVX: {PB[style].EndVelX} | EVY: {PB[style].EndVelY} | EVZ: {PB[style].EndVelZ} | Run Date (UNIX): {PB[style].RunDate}"); + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); + #endif + } + } + playerStats.Close(); + } + + /// + /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary + /// + public void LoadCheckpointsData(TimerDatabase DB) + { + Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {PB[0].ID};"); + MySqlDataReader results = dbTask.Result; + if (PB[0] == null) + { + #if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PlayerStats -> LoadCheckpointsData -> PersonalBest object is null."); + #endif + + results.Close(); + return; + } + + if (PB[0].Checkpoint == null) + { + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadCheckpointsData -> PB Checkpoints list is not initialized."); + #endif + + PB[0].Checkpoint = new Dictionary(); // Initialize if null + } + + #if DEBUG + Console.WriteLine($"this.Checkpoint.Count {PB[0].Checkpoint.Count} "); + Console.WriteLine($"this.ID {PB[0].ID} "); + Console.WriteLine($"this.Ticks {PB[0].Ticks} "); + Console.WriteLine($"this.RunDate {PB[0].RunDate} "); + #endif + + if (!results.HasRows) + { + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> No checkpoints found for this mapTimeId {PB[0].ID}."); + #endif + + results.Close(); + return; + } + + #if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Checkpoints found for this mapTimeId"); + #endif + + while (results.Read()) + { + #if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetFloat("run_time")} "); + Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); + Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); + #endif + + Checkpoint cp = new(results.GetInt32("cp"), + results.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + results.GetFloat("start_vel_x"), + results.GetFloat("start_vel_y"), + results.GetFloat("start_vel_z"), + results.GetFloat("end_vel_x"), + results.GetFloat("end_vel_y"), + results.GetFloat("end_vel_z"), + results.GetFloat("end_touch"), + results.GetInt32("attempts")); + cp.ID = results.GetInt32("cp"); + // To-do: cp.ID = calculate Rank # from DB + + PB[0].Checkpoint[cp.CP] = cp; + + #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); + #endif + } + results.Close(); + + #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Checkpoints loaded from DB. Count: {PB[0].Checkpoint.Count}"); + #endif + } +} \ No newline at end of file From 020d5357304b4db23fe383891c0d7f68f3322676 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:52:15 +0200 Subject: [PATCH 47/56] load and compare with WR times;fix cp attempts --- src/ST-Events/TriggerEndTouch.cs | 9 +++- src/ST-Events/TriggerStartTouch.cs | 5 +- src/ST-Map/Map.cs | 66 ++++++++++++++++++++----- src/ST-Player/PlayerHUD.cs | 53 +++++++++++++++++--- src/ST-Player/PlayerStats/CurrentRun.cs | 8 +-- 5 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 73ced38..e794af1 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -84,6 +84,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) Console.WriteLine($"currentCheckpoint.EndVelX {currentCheckpoint.EndVelX} - velocity_x {velocity_x}"); Console.WriteLine($"currentCheckpoint.EndVelY {currentCheckpoint.EndVelY} - velocity_y {velocity_y}"); Console.WriteLine($"currentCheckpoint.EndVelZ {currentCheckpoint.EndVelZ} - velocity_z {velocity_z}"); + Console.WriteLine($"currentCheckpoint.Attempts {currentCheckpoint.Attempts}"); #endif // Update the values @@ -92,7 +93,9 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) currentCheckpoint.EndVelZ = velocity_z; currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? currentCheckpoint.Attempts += 1; - + // Assign the updated currentCheckpoint back to the list as `currentCheckpoint` is supposedly a copy of the original object + player.Stats.ThisRun.Checkpoint[player.Timer.Checkpoint] = currentCheckpoint; + // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Stage {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } @@ -126,7 +129,9 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) currentCheckpoint.EndVelZ = velocity_z; currentCheckpoint.EndTouch = player.Timer.Ticks; // To-do: what type of value we store in DB ? currentCheckpoint.Attempts += 1; - + // Assign the updated currentCheckpoint back to the list as `currentCheckpoint` is supposedly a copy of the original object + player.Stats.ThisRun.Checkpoint[player.Timer.Checkpoint] = currentCheckpoint; + // Show Prespeed for stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.ToString("0")} u/s"); } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index a5ba595..207ed24 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -50,6 +50,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Timer.IsRunning) { player.Timer.Stop(); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the run player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run @@ -79,7 +80,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + $"============== INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.PB[0].Ticks}, " + + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.ThisRun.Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -126,7 +127,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) #endif // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoint.Count <= stage) + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoint.Count < stage) { player.Timer.Checkpoint = stage; // Stage = Checkpoint when in a run on a Staged map diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 0ad0d13..adc42a7 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -6,7 +6,7 @@ namespace SurfTimer; -public class Map +internal class Map { // Map information public int ID {get; set;} = -1; // Can we use this to re-trigger retrieving map information from the database?? (all db IDs are auto-incremented) @@ -20,8 +20,7 @@ public class Map public int DateAdded {get; set;} = 0; public int LastPlayed {get; set;} = 0; public int TotalCompletions {get; set;} = 0; - public int WrRunTime {get; set;} = 0; - public int WrId {get; set;} = 0; + public Dictionary WR { get; set; } = new Dictionary(); // Zone Origin Information // Map start/end zones @@ -43,6 +42,7 @@ internal Map(string Name, TimerDatabase DB) { // Set map name this.Name = Name; + this.WR[0] = new PersonalBest(); // To-do: Implement styles // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); // Gathering info_teleport_destinations from the map @@ -178,7 +178,8 @@ internal Map(string Name, TimerDatabase DB) int writerRows = writer.Result; if (writerRows != 1) throw new Exception($"CS2 Surf ERROR >> OnRoundStart -> new Map() -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); - + writer.Dispose(); + Task postWriteReader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); MySqlDataReader postWriteMapData = postWriteReader.Result; if (postWriteMapData.HasRows && postWriteMapData.Read()) @@ -229,7 +230,7 @@ public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnO internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: Implement styles { // Get map world records - Task reader = DB.Query($"SELECT * FROM `MapTimes` WHERE `map_id` = {this.ID} AND `style` = {style} ORDER BY `run_time` ASC;'"); + Task reader = DB.Query($"SELECT * FROM `MapTimes` WHERE `map_id` = {this.ID} AND `style` = {style} ORDER BY `run_time` ASC;"); MySqlDataReader mapWrData = reader.Result; int totalRows = 0; @@ -237,19 +238,60 @@ internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: { // To-do: Implement bonuses WR // To-do: Implement stages WR - // To-do: Implement checkpoints WR while (mapWrData.Read()) { - if (totalRows == 0) - this.WrRunTime = mapWrData.GetInt32("run_time"); // Fastest run time (WR) for the Map and Style combo - this.WrId = mapWrData.GetInt32("id"); // WR ID for the Map and Style combo - + if (totalRows == 0) // We are sorting by `run_time ASC` so the first row is always the fastest run for the map and style combo :) + { + this.WR[style].ID = mapWrData.GetInt32("id"); // WR ID for the Map and Style combo + this.WR[style].Ticks = mapWrData.GetInt32("run_time"); // Fastest run time (WR) for the Map and Style combo + this.WR[style].StartVelX = mapWrData.GetFloat("start_vel_x"); // Fastest run start velocity X for the Map and Style combo + this.WR[style].StartVelY = mapWrData.GetFloat("start_vel_y"); // Fastest run start velocity Y for the Map and Style combo + this.WR[style].StartVelZ = mapWrData.GetFloat("start_vel_z"); // Fastest run start velocity Z for the Map and Style combo + this.WR[style].EndVelX = mapWrData.GetFloat("end_vel_x"); // Fastest run end velocity X for the Map and Style combo + this.WR[style].EndVelY = mapWrData.GetFloat("end_vel_y"); // Fastest run end velocity Y for the Map and Style combo + this.WR[style].EndVelZ = mapWrData.GetFloat("end_vel_z"); // Fastest run end velocity Z for the Map and Style combo + this.WR[style].RunDate = mapWrData.GetInt32("run_date"); // Fastest run date for the Map and Style combo + } totalRows++; } } + mapWrData.Close(); + this.TotalCompletions = totalRows; // Total completions for the map and style - this should maybe be added to PersonalBest class - this.TotalCompletions = totalRows; // Total completions for the map and style + // Get map world record checkpoints + if (totalRows != 0) + { + Task cpReader = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {this.WR[style].ID};"); + MySqlDataReader cpWrData = cpReader.Result; + while (cpWrData.Read()) + { + #if DEBUG + Console.WriteLine($"cp {cpWrData.GetInt32("cp")} "); + Console.WriteLine($"run_time {cpWrData.GetFloat("run_time")} "); + Console.WriteLine($"sVelX {cpWrData.GetFloat("start_vel_x")} "); + Console.WriteLine($"sVelY {cpWrData.GetFloat("start_vel_y")} "); + #endif - mapWrData.Close(); + Checkpoint cp = new(cpWrData.GetInt32("cp"), + cpWrData.GetInt32("run_time"), // To-do: what type of value we use here? DB uses DECIMAL but `.Tick` is int??? + cpWrData.GetFloat("start_vel_x"), + cpWrData.GetFloat("start_vel_y"), + cpWrData.GetFloat("start_vel_z"), + cpWrData.GetFloat("end_vel_x"), + cpWrData.GetFloat("end_vel_y"), + cpWrData.GetFloat("end_vel_z"), + cpWrData.GetFloat("end_touch"), + cpWrData.GetInt32("attempts")); + cp.ID = cpWrData.GetInt32("cp"); + // To-do: cp.ID = calculate Rank # from DB + + this.WR[style].Checkpoint[cp.CP] = cp; + + #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal void GetMapRecordAndTotals : Map -> Loaded WR CP {cp.CP} with RunTime {cp.Ticks} for MapTimeID {WR[0].ID} (MapId = {this.ID})."); + #endif + } + cpWrData.Close(); + } } } \ No newline at end of file diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 321f512..23c1366 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -2,7 +2,7 @@ namespace SurfTimer; -internal class PlayerHUD +internal class PlayerHUD { private Player _player; @@ -55,7 +55,7 @@ public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTi } } - public void Display() + public void Display() // To-do: make Style (currently 0) be dynamic { if (_player.Controller.IsValid && _player.Controller.PawnIsAlive) { @@ -77,17 +77,17 @@ public void Display() string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", $"N/A", "#7882dd"); - if (_player.Stats.PB[0].Ticks > 0 && _player.CurrMap.WrRunTime > 0) + if (_player.Stats.PB[0].Ticks > 0 && _player.CurrMap.WR[0].Ticks > 0) { rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[0].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); } - else if (_player.CurrMap.WrRunTime >= 0) + else if (_player.CurrMap.WR[0].Ticks >= 0) { rankModule = FormatHUDElementHTML("Rank", $"N/A/{_player.CurrMap.TotalCompletions}", "#7882dd"); } // PB & WR Modules string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].Ticks > 0 ? FormatTime(_player.Stats.PB[0].Ticks) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic - string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WrRunTime > 0 ? FormatTime(_player.CurrMap.WrRunTime) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? + string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WR[0].Ticks > 0 ? FormatTime(_player.CurrMap.WR[0].Ticks) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; @@ -104,7 +104,9 @@ public void Display() public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefix should be accessible in here without passing it as a parameter { int pbTime; + int wrTime = -1; float pbSpeed; + float wrSpeed = -1.0f; int currentTime = _player.Timer.Ticks; float currentSpeed = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X @@ -150,7 +152,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi if (pbTime != -1) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting difference calculation... (pbTime != -1)"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting PB difference calculation... (pbTime != -1)"); #endif // Reset the string strPbDifference = ""; @@ -178,6 +180,43 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi strPbDifference += ChatColors.Default + ")"; } + if (_player.CurrMap.WR[0].Ticks > 0) // To-do: make Style (currently 0) be dynamic + { + // Calculate differences in WR (WR - Current) + #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (_player.CurrMap.WR[0].Ticks > 0)"); + #endif + + wrTime = _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].Ticks; + wrSpeed = (float)Math.Sqrt(_player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelX + + _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelY + + _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); + // Reset the string + strWrDifference = ""; + + // Calculate the WR time difference + if (wrTime - currentTime < 0.0) + { + strWrDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((wrTime - currentTime) * -1); // We multiply by -1 to get the positive value + } + else if (wrTime - currentTime >= 0.0) + { + strWrDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(wrTime - currentTime); + } + strWrDifference += ChatColors.Default + " "; + + // Calculate the WR speed difference + if (wrSpeed - currentSpeed <= 0.0) + { + strWrDifference += "(" + ChatColors.Green + "+" + ((wrSpeed - currentSpeed) * -1).ToString("0"); // We multiply by -1 to get the positive value + } + else if (wrSpeed - currentSpeed > 0.0) + { + strWrDifference += "(" + ChatColors.Red + "-" + (wrSpeed - currentSpeed).ToString("0"); + } + strWrDifference += ChatColors.Default + ")"; + } + // Print checkpoint message _player.Controller.PrintToChat( $"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " + @@ -189,6 +228,8 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] PB: {pbTime} - CURR: {currentTime} = pbTime: {pbTime - currentTime}"); Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] PB: {pbSpeed} - CURR: {currentSpeed} = difference: {pbSpeed - currentSpeed}"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] WR: {wrTime} - CURR: {currentTime} = difference: {wrTime - currentTime}"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] WR: {wrSpeed} - CURR: {currentSpeed} = difference: {wrSpeed - currentSpeed}"); #endif } } diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 3ddf620..3426a5b 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -54,7 +54,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do // To-do: add `type` Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {this.Ticks}, " + + $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {player.Stats.ThisRun.Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -88,12 +88,12 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do int attempts = item.Value!.Attempts; #if DEBUG - Console.WriteLine($"CP: {cp} | MapTime ID: {item.Value.ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CP: {cp} | MapTime ID: {player.Stats.PB[0].ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + $"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({item.Value.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); #endif @@ -106,7 +106,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({item.Value.ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + + $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + $"ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); From 535672a8f9921181ba5267d4b631acc4735d1e07 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:11:22 +0200 Subject: [PATCH 48/56] update readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dbf8ea6..12d6e2b 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ Bold & Italics = being worked on. - [X] Base timer class implementation - [X] Base timer HUD implementation - [X] Prespeed measurement and display - - [ ] Save/load times - - [ ] **_Save/load map personal bests_** - - [ ] **_Save/load map checkpoints_** - - [ ] **_Save/load bonus personal bests_** - - [ ] **_Save/load stage personal bests_** + - [X] Save/load times + - [X] **_Save/load map personal bests_** + - [X] **_Save/load map checkpoints_** + - [X] **_Save/load bonus personal bests_** + - [X] **_Save/load stage personal bests_** - [ ] Practice Mode implementation - [ ] Announce records to Discord - [ ] Stretch goal: sub-tick timing - [ ] Player Data - [X] Base player class - - [ ] Player stat classes + - [X] **_Player stat classes_** - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) From dee850caba88bc387b40ea5975f62018c2b674e4 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:22:17 +0200 Subject: [PATCH 49/56] some dynamic `style` --- src/ST-Events/TriggerStartTouch.cs | 13 ++++---- src/ST-Player/PlayerHUD.cs | 41 +++++++++++++------------ src/ST-Player/PlayerStats/CurrentRun.cs | 12 +++++--- src/ST-Player/PlayerTimer.cs | 2 +- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 207ed24..e605791 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -41,6 +41,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) float velocity_y = player.Controller.PlayerPawn.Value!.AbsVelocity.Y; float velocity_z = player.Controller.PlayerPawn.Value!.AbsVelocity.Z; float velocity = (float)Math.Sqrt(velocity_x * velocity_x + velocity_y * velocity_y + velocity_z + velocity_z); + int style = player.Timer.Style; // Map end zones -- hook into map_end if (trigger.Entity.Name == "map_end") @@ -56,13 +57,13 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run // To-do: make Style (currently 0) be dynamic - if (player.Stats.PB[0].Ticks <= 0) // Player first ever PersonalBest for the map + if (player.Stats.PB[style].Ticks <= 0) // Player first ever PersonalBest for the map { Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } - else if (player.Timer.Ticks < player.Stats.PB[0].Ticks) // Player beating their existing PersonalBest for the map + else if (player.Timer.Ticks < player.Stats.PB[style].Ticks) // Player beating their existing PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[0].Ticks)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[player.Timer.Style].Ticks)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { @@ -74,7 +75,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); - player.Stats.PB[0].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + player.Stats.PB[style].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + @@ -133,7 +134,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) #if DEBUG Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); #endif // Print checkpoint message @@ -169,7 +170,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) { #if DEBUG Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `checkpoint`: {Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[0].Checkpoint.Count = {player.Stats.PB[0].Checkpoint.Count}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); #endif // Print checkpoint message diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 23c1366..86a916e 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -55,12 +55,14 @@ public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTi } } - public void Display() // To-do: make Style (currently 0) be dynamic + public void Display() { if (_player.Controller.IsValid && _player.Controller.PawnIsAlive) { + int style = _player.Timer.Style; // Timer Module string timerColor = "#79d1ed"; + if (_player.Timer.IsRunning) { if (_player.Timer.IsPracticeMode) @@ -77,17 +79,17 @@ public void Display() // To-do: make Style (currently 0) be dynamic string velocityModule = FormatHUDElementHTML("Speed", velocity.ToString("0"), "#79d1ed") + " u/s"; // Rank Module string rankModule = FormatHUDElementHTML("Rank", $"N/A", "#7882dd"); - if (_player.Stats.PB[0].Ticks > 0 && _player.CurrMap.WR[0].Ticks > 0) + if (_player.Stats.PB[style].ID != -1 && _player.CurrMap.WR[style].ID != -1) { - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[0].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); } - else if (_player.CurrMap.WR[0].Ticks >= 0) + else if (_player.CurrMap.WR[style].ID != -1) { rankModule = FormatHUDElementHTML("Rank", $"N/A/{_player.CurrMap.TotalCompletions}", "#7882dd"); } // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[0].Ticks > 0 ? FormatTime(_player.Stats.PB[0].Ticks) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic - string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WR[0].Ticks > 0 ? FormatTime(_player.CurrMap.WR[0].Ticks) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].Ticks > 0 ? FormatTime(_player.Stats.PB[style].Ticks) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic + string wrModule = FormatHUDElementHTML("WR", _player.CurrMap.WR[style].Ticks > 0 ? FormatTime(_player.CurrMap.WR[style].Ticks) : "N/A", "#ffc61a"); // IMPLEMENT IN PlayerStats - This should be part of CurrentMap, not PlayerStats? // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; @@ -107,6 +109,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi int wrTime = -1; float pbSpeed; float wrSpeed = -1.0f; + int style = _player.Timer.Style; int currentTime = _player.Timer.Ticks; float currentSpeed = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X @@ -121,14 +124,14 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].Ticks; - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelX - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelY - + _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); + pbTime = _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].Ticks; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelX + + _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelY + + _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.Stats.PB[style].Checkpoint[_player.Timer.Checkpoint].StartVelZ); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint} = {pbTime}]"); - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[0].Checkpoint[{_player.Timer.Checkpoint}] = {pbSpeed}"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[{style}].Checkpoint[{_player.Timer.Checkpoint} = {pbTime}]"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[{style}].Checkpoint[{_player.Timer.Checkpoint}] = {pbSpeed}"); #endif } #if DEBUG @@ -144,7 +147,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi #if DEBUG Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred: {ex.Message}"); - Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred Player has no PB and therefore no Checkpoints | _player.Stats.PB[0].Checkpoint.Count = {_player.Stats.PB[0].Checkpoint.Count}"); + Console.WriteLine($"CS2 Surf CAUGHT EXCEPTION >> DisplayCheckpointMessages -> An error occurred Player has no PB and therefore no Checkpoints | _player.Stats.PB[{style}].Checkpoint.Count = {_player.Stats.PB[style].Checkpoint.Count}"); #endif } @@ -180,17 +183,17 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi strPbDifference += ChatColors.Default + ")"; } - if (_player.CurrMap.WR[0].Ticks > 0) // To-do: make Style (currently 0) be dynamic + if (_player.CurrMap.WR[style].Ticks > 0) { // Calculate differences in WR (WR - Current) #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (_player.CurrMap.WR[0].Ticks > 0)"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (_player.CurrMap.WR[{style}].Ticks > 0)"); #endif - wrTime = _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].Ticks; - wrSpeed = (float)Math.Sqrt(_player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelX - + _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelY - + _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.CurrMap.WR[0].Checkpoint[_player.Timer.Checkpoint].StartVelZ); + wrTime = _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].Ticks; + wrSpeed = (float)Math.Sqrt(_player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelX * _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelX + + _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelY * _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelY + + _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelZ * _player.CurrMap.WR[style].Checkpoint[_player.Timer.Checkpoint].StartVelZ); // Reset the string strWrDifference = ""; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 3426a5b..96962ee 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -48,13 +48,14 @@ public void Reset() /// Saves the player's run to the database and reloads the data for the player. /// NOTE: Not re-loading any data at this point as we need `LoadMapTimesData` to be called from here as well, otherwise we may not have the `this.ID` populated ///
- public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do: Styles + public void SaveMapTime(Player player, TimerDatabase DB) { // Add entry in DB for the run // To-do: add `type` + int style = player.Timer.Style; Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, 0, 0, 0, {player.Stats.ThisRun.Ticks}, " + + $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); @@ -73,6 +74,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do ///
public void SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) // To-do: Transactions? Player sometimes rubberbands for a bit here { + int style = player.Timer.Style; // Loop through the checkpoints and insert/update them in the database for the run foreach (var item in player.Stats.ThisRun.Checkpoint) { @@ -88,12 +90,12 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do int attempts = item.Value!.Attempts; #if DEBUG - Console.WriteLine($"CP: {cp} | MapTime ID: {player.Stats.PB[0].ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($"CP: {cp} | MapTime ID: {player.Stats.PB[style].ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + $"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + + $"VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); #endif @@ -106,7 +108,7 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[0].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + + $"VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + $"ON DUPLICATE KEY UPDATE " + $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index b23f6e5..4d7da97 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -16,7 +16,7 @@ internal class PlayerTimer public int Checkpoint {get; set;} = 0; // Current checkpoint tracker public CurrentRun CurrentRunData { get; set; } = new CurrentRun(); // Current RUN data tracker public int Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker - // public int Style = 0; // To-do: style implementation + public int Style { get; set; } = 0; // To-do: functionality for player to change this value and the actual styles implementation - Current style tracker // Timing public int Ticks { get; set; } = 0; // To-do: sub-tick counting? This currently goes on OnTick, which is not sub-tick I believe? Needs investigating From 1a6f24914608c7bdabe45a9fc98e9b7aa8c4ab91 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:30:23 +0200 Subject: [PATCH 50/56] ^ --- src/ST-Events/TriggerStartTouch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index e605791..f0c0870 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -63,7 +63,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) } else if (player.Timer.Ticks < player.Stats.PB[style].Ticks) // Player beating their existing PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[player.Timer.Style].Ticks)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { @@ -81,7 +81,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + $"============== INSERT INTO `MapTimes` " + $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, 0, 0, 0, {player.Stats.ThisRun.Ticks}, " + + $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, " + $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); From 21161ce934a3ab07c602c93ab6eb1e4ffe03c0ca Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:38:49 +0200 Subject: [PATCH 51/56] readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 12d6e2b..d4e55f1 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,14 @@ Bold & Italics = being worked on. - [X] Save/load times - [X] **_Save/load map personal bests_** - [X] **_Save/load map checkpoints_** - - [X] **_Save/load bonus personal bests_** - - [X] **_Save/load stage personal bests_** + - [ ] **_Save/load bonus personal bests_** + - [ ] **_Save/load stage personal bests_** - [ ] Practice Mode implementation - [ ] Announce records to Discord - [ ] Stretch goal: sub-tick timing - [ ] Player Data - [X] Base player class - - [X] **_Player stat classes_** + - [ ] **_Player stat classes_** - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) From 4fe1c9e41f65d119380b6811299bebf6da86d782 Mon Sep 17 00:00:00 2001 From: Shachar <110262023+shacharrr@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:21:08 +0200 Subject: [PATCH 52/56] Replay bot (#7) * Test commit, fix stages * Add files via upload * Updated * Delete src/bin/Debug/net7.0 directory * . * whoopsie? * quick review * Updated replay to work with bots - Fixed bot spamming errors with the TriggerStartZone/TriggerEndZone - Added reverse/pause replay (!rr/!rp) Still need to fix more bots than wanted joining, and make DB queries async * Working replays, not 100% done * Fixed bot spawning rate, TODO: * UPDATED: Default surf config to right configs TODO: Add stage/bonuses/etc support when done, update bot_quota to change upon loading map time * Bot changes name on replay loading * Used FormatTime in necessry places. Added cool HUD when spectating replays. Added css_spec/css_replaybotpause/css_replaybotflip * Fixed, error when no replay data --------- Co-authored-by: Shachar Co-authored-by: T <74899888+tslashd@users.noreply.github.com> --------- Bots rely on this https://github.com/ws-cs2/CS2Fixes/commit/2380ec67c5790736092552aeea47bf09c39822dd otherwise we get some wonky angles while the bot is surfing --- README.md | 2 +- cfg/SurfTimer/server_settings.cfg | 18 ++- src/ST-Commands/MapCommands.cs | 2 +- src/ST-Commands/PlayerCommands.cs | 51 +++++++- src/ST-Events/Players.cs | 35 +++++- src/ST-Events/Tick.cs | 7 ++ src/ST-Events/TriggerEndTouch.cs | 9 +- src/ST-Events/TriggerStartTouch.cs | 18 ++- src/ST-Map/Map.cs | 1 + src/ST-Player/Player.cs | 2 + src/ST-Player/PlayerHUD.cs | 26 ++-- src/ST-Player/Replay/ReplayFrame.cs | 14 +++ src/ST-Player/Replay/ReplayPlayer.cs | 131 ++++++++++++++++++++ src/ST-Player/Replay/ReplayRecorder.cs | 77 ++++++++++++ src/ST-UTILS/Compression.cs | 161 +++++++++++++++++++++++++ src/ST-UTILS/Schema.cs | 30 +++++ src/SurfTimer.cs | 2 + src/SurfTimer.csproj | 3 +- 18 files changed, 566 insertions(+), 23 deletions(-) create mode 100644 src/ST-Player/Replay/ReplayFrame.cs create mode 100644 src/ST-Player/Replay/ReplayPlayer.cs create mode 100644 src/ST-Player/Replay/ReplayRecorder.cs create mode 100644 src/ST-UTILS/Compression.cs create mode 100644 src/ST-UTILS/Schema.cs diff --git a/README.md b/README.md index d4e55f1..04093ad 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Bold & Italics = being worked on. - [ ] Zoning - [X] Start/End trigger touch hooks - [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping - - [ ] _**Support for stages (`/rs`, teleporting with `/s`)**_ + - [X] _**Support for stages (`/rs`, teleporting with `/s`)**_ - [ ] _**Support for bonuses (`/rs`, teleporting with `/b #`)**_ - [ ] _**Start/End touch hooks implemented for all zones**_ - [ ] Surf configs diff --git a/cfg/SurfTimer/server_settings.cfg b/cfg/SurfTimer/server_settings.cfg index fd4abc3..074b2f0 100644 --- a/cfg/SurfTimer/server_settings.cfg +++ b/cfg/SurfTimer/server_settings.cfg @@ -17,7 +17,8 @@ sv_deadtalk 1 sv_full_alltalk 1 // Movement Settings -sv_airaccelerate 150 +// some pussy shit = sv_airaccelerate 150 +sv_airaccelerate 2000 sv_gravity 800 sv_friction 5.2 sv_maxspeed 350 @@ -26,6 +27,16 @@ sv_enablebunnyhopping 1 sv_autobunnyhopping 1 sv_staminajumpcost 0 sv_staminalandcost 0 +sv_timebetweenducks 0 + +// Some replay bot shit (took so fucking long to debug) +bot_quota 1 // This is gonna be used to change the amount of bots allowed (per stages/bonuses/etc) when stages/bonuses/etc added +bot_quota_mode "normal" +bot_join_after_player 1 +bot_join_team CT +bot_zombie 1 +bot_stop 1 +bot_freeze 1 // Player Settings mp_spectators_max 64 @@ -36,8 +47,8 @@ mp_respawn_on_death_ct 1 mp_respawn_on_death_t 1 mp_ct_default_secondary weapon_usp_silencer mp_t_default_secondary weapon_usp_silencer -mp_autoteambalance 0 mp_limitteams 0 +mp_autoteambalance 0 mp_playercashawards 0 mp_teamcashawards 0 mp_death_drop_c4 1 @@ -63,8 +74,7 @@ mp_freezetime 0 mp_team_intro_time 0 mp_warmup_end mp_warmuptime 0 -bot_quota 0 sv_holiday_mode 0 sv_party_mode 0 -sv_cheats 0 +sv_cheats 0 \ No newline at end of file diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 72fc191..4bebc09 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -19,7 +19,7 @@ public void MapTier(CCSPlayerController? player, CommandInfo command) if (player == null) return; - if (CurrentMap.Stages > 0) + if (CurrentMap.Stages > 1) player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Staged {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}"); else player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Linear {ChatColors.Yellow}{CurrentMap.Checkpoints} Checkpoints{ChatColors.Default}"); diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index bac46fd..e92713e 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -60,7 +60,6 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); return; } - else if (CurrentMap.Stages <= 0) { player.PrintToChat($"{PluginPrefix} {ChatColors.Red}This map has no stages."); @@ -84,4 +83,54 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) else player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); } + + // // Test command + // [ConsoleCommand("css_savereplay", "Test")] + // public void SaveReplay(CCSPlayerController? player, CommandInfo command) { + // if(player == null) + // return; + + // foreach(var p in playerList.Values) { + // if(p.Replay.Frames.Count() > 0) { + // p.Replay.StopRecording(); + // p.Replay.SaveReplayData(p, DB!); + // break; + // } + // } + // } + + // Test command + [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] + public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) { + if(player == null || player.Team == CsTeam.Spectator) + return; + + player.ChangeTeam(CsTeam.Spectator); + } + + [ConsoleCommand("css_replaybotpause", "Pause the replay bot playback")] + [ConsoleCommand("css_rbpause", "Pause the replay bot playback")] + public void PauseReplay(CCSPlayerController? player, CommandInfo command) { + if(player == null + || player.Team != CsTeam.Spectator + || CurrentMap.ReplayBot.Controller == null + || !CurrentMap.ReplayBot.IsPlaying + || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + return; + + CurrentMap.ReplayBot.Pause(); + } + + [ConsoleCommand("css_replaybotflip", "Flips the replay bot between Forward/Backward playback")] + [ConsoleCommand("css_rbflip", "Flips the replay bot between Forward/Backward playback")] + public void ReverseReplay(CCSPlayerController? player, CommandInfo command) { + if(player == null + || player.Team != CsTeam.Spectator + || CurrentMap.ReplayBot.Controller == null + || !CurrentMap.ReplayBot.IsPlaying + || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + return; + + CurrentMap.ReplayBot.FrameTickIncrement *= -1; + } } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 5d442ac..d17e1d1 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -9,15 +9,41 @@ namespace SurfTimer; public partial class SurfTimer { - [GameEventHandler] // Player Connect Event - public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info) + [GameEventHandler(HookMode.Post)] + public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) + { + var controller = @event.Userid; + if(!controller.IsValid) + return HookResult.Continue; + + if (controller.IsBot && CurrentMap.ReplayBot.Controller == null) + { + CurrentMap.ReplayBot.Controller = controller; + // CurrentMap.ReplayBot.Controller.PlayerName = $"[REPLAY] {CurrentMap.Name}"; + + Server.PrintToChatAll($"{ChatColors.Lime} Loading replay data..."); // WHY COLORS NOT WORKING AHHHHH!!!!! + AddTimer(2f, () => { + CurrentMap.ReplayBot.Controller.RemoveWeapons(); + + CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap); + + CurrentMap.ReplayBot.Start(); + }); + } + + return HookResult.Continue; + } + + [GameEventHandler] + public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info) { var player = @event.Userid; #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> {player.PlayerName} / {player.UserId} / {player.SteamID}"); + Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> {player.PlayerName} / {player.UserId} / Bot Diff: {player.PawnBotDifficulty}"); #endif - if (player.IsBot || !player.IsValid) + if (player.IsBot || !player.IsValid) // IsBot might be broken so we can check for PawnBotDifficulty which is `-1` for real players { return HookResult.Continue; } @@ -128,6 +154,9 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo { var player = @event.Userid; + if (CurrentMap.ReplayBot.Controller != null&& CurrentMap.ReplayBot.Controller.Equals(player)) + CurrentMap.ReplayBot.Reset(); + if (player.IsBot || !player.IsValid) { return HookResult.Continue; diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 3a196fb..a7140cc 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -1,3 +1,6 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; + namespace SurfTimer; public partial class SurfTimer @@ -7,7 +10,11 @@ public void OnTick() foreach (var player in playerList.Values) { player.Timer.Tick(); + player.ReplayRecorder.Tick(player); player.HUD.Display(); } + + // Replay BOT Ticks + CurrentMap?.ReplayBot.Tick(); // When CurrentMap null the ? operator will terminate safely the operation } } \ No newline at end of file diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index e794af1..b330ee3 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -14,7 +14,7 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) CBaseTrigger trigger = handler.GetParam(0); CBaseEntity entity = handler.GetParam(1); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - if (!client.IsValid || client.UserId == -1 || !client.PawnIsAlive) // `client.IsBot` throws error in server console when going to spectator? + if (!client.IsValid || client.UserId == -1 || !client.PawnIsAlive || !playerList.ContainsKey((int)client.UserId!)) // `client.IsBot` throws error in server console when going to spectator? + !playerList.ContainsKey((int)client.UserId!) make sure to not check for user_id that doesnt exists { return HookResult.Continue; } @@ -41,6 +41,13 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) trigger.Entity.Name.Contains("s1_start") || trigger.Entity.Name.Contains("stage1_start")) { + // Replay + if(player.ReplayRecorder.IsRecording) + { + // Saveing 2 seconds before leaving the start zone + player.ReplayRecorder.Frames.RemoveRange(0, Math.Max(0, player.ReplayRecorder.Frames.Count - (64*2))); // Would like for someone to fact check the math :) + } + // MAP START ZONE player.Timer.Start(); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index f0c0870..bc131cb 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -14,7 +14,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) CBaseTrigger trigger = handler.GetParam(0); CBaseEntity entity = handler.GetParam(1); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - if (client.IsBot || !client.IsValid || !client.PawnIsAlive) + if (!client.IsValid || !client.PawnIsAlive || !playerList.ContainsKey((int)client.UserId!)) // !playerList.ContainsKey((int)client.UserId!) make sure to not check for user_id that doesnt exists { return HookResult.Continue; } @@ -51,6 +51,7 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Timer.IsRunning) { player.Timer.Stop(); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the run player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run @@ -59,15 +60,15 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // To-do: make Style (currently 0) be dynamic if (player.Stats.PB[style].Ticks <= 0) // Player first ever PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } else if (player.Timer.Ticks < player.Stats.PB[style].Ticks) // Player beating their existing PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{player.HUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { - player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{player.HUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); return HookResult.Continue; // Exit here so we don't write to DB } @@ -93,6 +94,13 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints player.Stats.LoadCheckpointsData(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD + + // Replay - Add end buffer for replay + AddTimer(1.5f, () => player.ReplayRecorder.SaveReplayData(player, DB)); + AddTimer(2f, () => { + CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap); + CurrentMap.ReplayBot.ResetReplay(); + }); } #if DEBUG @@ -105,6 +113,8 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) trigger.Entity.Name.Contains("s1_start") || trigger.Entity.Name.Contains("stage1_start")) { + player.ReplayRecorder.Start(); // Start replay recording + player.Timer.Reset(); player.Stats.ThisRun.Checkpoint.Clear(); // I have the suspicion that the `Timer.Reset()` does not properly reset this object :thonk: player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index adc42a7..4e93745 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -21,6 +21,7 @@ internal class Map public int LastPlayed {get; set;} = 0; public int TotalCompletions {get; set;} = 0; public Dictionary WR { get; set; } = new Dictionary(); + public ReplayPlayer ReplayBot { get; set; } = new ReplayPlayer(); // Zone Origin Information // Map start/end zones diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index d6ceab0..059d1cc 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -11,6 +11,7 @@ internal class Player public PlayerTimer Timer {get; set;} public PlayerStats Stats {get; set;} public PlayerHUD HUD {get; set;} + public ReplayRecorder ReplayRecorder { get; set; } // Player information public PlayerProfile Profile {get; set;} @@ -28,6 +29,7 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen this.Timer = new PlayerTimer(); this.Stats = new PlayerStats(); + this.ReplayRecorder = new ReplayRecorder(); this.HUD = new PlayerHUD(this); this.CurrMap = CurrMap; diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 86a916e..d2d8f3e 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -35,7 +35,7 @@ private string FormatHUDElementHTML(string title, string body, string color, str /// Unless specified differently, the default formatting will be `Compact`. /// Check for all formatting types. ///
- public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Compact) + public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTimer.TimeFormatStyle.Compact) { TimeSpan time = TimeSpan.FromSeconds(ticks / 64.0); int millis = (int)(ticks % 64 * (1000.0 / 64.0)); @@ -57,7 +57,10 @@ public string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = PlayerTi public void Display() { - if (_player.Controller.IsValid && _player.Controller.PawnIsAlive) + if(!_player.Controller.IsValid) + return; + + if (_player.Controller.PawnIsAlive) { int style = _player.Timer.Style; // Timer Module @@ -97,6 +100,15 @@ public void Display() // Display HUD _player.Controller.PrintToCenterHtml(hud); } + else if (_player.Controller.Team == CsTeam.Spectator) + { + if (_player.CurrMap.ReplayBot.Controller?.Pawn.SerialNum == _player.Controller.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + { + string elapsed_ticks = FormatHUDElementHTML("Tick", $"{_player.CurrMap.ReplayBot.CurrentFrameTick}/{_player.CurrMap.ReplayBot.Frames.Count}", "#7882dd"); + string hud = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}
{elapsed_ticks}"; + _player.Controller.PrintToCenterHtml(hud); + } + } } /// @@ -163,11 +175,11 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Calculate the time difference if (pbTime - currentTime < 0.0) { - strPbDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((pbTime - currentTime) * -1); // We multiply by -1 to get the positive value + strPbDifference += ChatColors.Red + "+" + FormatTime((pbTime - currentTime) * -1); // We multiply by -1 to get the positive value } else if (pbTime - currentTime >= 0.0) { - strPbDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(pbTime - currentTime); + strPbDifference += ChatColors.Green + "-" + FormatTime(pbTime - currentTime); } strPbDifference += ChatColors.Default + " "; @@ -200,11 +212,11 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Calculate the WR time difference if (wrTime - currentTime < 0.0) { - strWrDifference += ChatColors.Red + "+" + _player.HUD.FormatTime((wrTime - currentTime) * -1); // We multiply by -1 to get the positive value + strWrDifference += ChatColors.Red + "+" + FormatTime((wrTime - currentTime) * -1); // We multiply by -1 to get the positive value } else if (wrTime - currentTime >= 0.0) { - strWrDifference += ChatColors.Green + "-" + _player.HUD.FormatTime(wrTime - currentTime); + strWrDifference += ChatColors.Green + "-" + FormatTime(wrTime - currentTime); } strWrDifference += ChatColors.Default + " "; @@ -223,7 +235,7 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Print checkpoint message _player.Controller.PrintToChat( $"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " + - $"{ChatColors.Yellow}{_player.HUD.FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + + $"{ChatColors.Yellow}{FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + $"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " + $"[PB: {strPbDifference} | " + $"WR: {strWrDifference}]"); diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs new file mode 100644 index 0000000..18174b3 --- /dev/null +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -0,0 +1,14 @@ +namespace SurfTimer; +using CounterStrikeSharp.API.Modules.Utils; +using CounterStrikeSharp.API.Core; + + +[Serializable] +internal class ReplayFrame +{ + public Vector Pos { get; set; } = new Vector(0, 0, 0); + public QAngle Ang { get; set; } = new QAngle(0, 0, 0); + public ulong Button { get; set; } + public uint Flags { get; set; } + public MoveType_t MoveType { get; set; } +} diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs new file mode 100644 index 0000000..8fe9436 --- /dev/null +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -0,0 +1,131 @@ +using System.Text; +using System.Text.Json; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using MySqlConnector; + +namespace SurfTimer; + +internal class ReplayPlayer +{ + public bool IsPlaying { get; set; } = false; + public bool IsPaused { get; set; } = false; + public bool IsOnRepeat { get; set; } = true; // Currently should always repeat + + // Tracking + public List Frames { get; set; } = new List(); + + // Playing + public int CurrentFrameTick { get; set; } = 0; + public int FrameTickIncrement { get; set; } = 1; + + public CCSPlayerController? Controller { get; set; } + + public void ResetReplay() + { + this.CurrentFrameTick = 0; + this.FrameTickIncrement = 1; + } + + public void Reset() + { + this.IsPlaying = false; + this.IsPaused = false; + + this.Frames.Clear(); + + this.ResetReplay(); + + this.Controller = null; + } + + public void Start() + { + if (this.Controller == null) + return; + + this.IsPlaying = true; + this.Controller.Pawn.Value!.MoveType = MoveType_t.MOVETYPE_NOCLIP; + } + + public void Stop() + { + this.IsPlaying = false; + } + + public void Pause() + { + if (this.IsPlaying) + this.IsPaused = !this.IsPaused; + } + + public void Tick() + { + if (!this.IsPlaying || this.Controller == null || this.Frames.Count == 0) + return; + + ReplayFrame current_frame = this.Frames[this.CurrentFrameTick]; + var current_pos = this.Controller.PlayerPawn.Value!.AbsOrigin!; + + bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; + bool is_ducking = (current_frame.Flags & (uint)PlayerFlags.FL_DUCKING) != 0; + + Vector velocity = (current_frame.Pos - current_pos) * 64; + + if (is_on_ground) + this.Controller.PlayerPawn.Value.MoveType = MoveType_t.MOVETYPE_WALK; + else + this.Controller.PlayerPawn.Value.MoveType = MoveType_t.MOVETYPE_NOCLIP; + + if ((current_pos - current_frame.Pos).Length() > 200) + this.Controller.PlayerPawn.Value.Teleport(current_frame.Pos, current_frame.Ang, new Vector(nint.Zero)); + else + this.Controller.PlayerPawn.Value.Teleport(new Vector(nint.Zero), current_frame.Ang, velocity); + + + if (!this.IsPaused) + this.CurrentFrameTick = Math.Max(0, this.CurrentFrameTick + this.FrameTickIncrement); + + if(this.CurrentFrameTick >= this.Frames.Count) + this.ResetReplay(); + } + + public void LoadReplayData(TimerDatabase DB, Map current_map) + { + if (this.Controller == null) + return; + // TODO: make query for wr too + Task dbTask = DB.Query($"SELECT `replay_frames` FROM MapTimeReplay " + + $"WHERE `map_id`={current_map.ID} AND `maptime_id`={current_map.WR[0].ID} "); + MySqlDataReader mapTimeReplay = dbTask.Result; + if(!mapTimeReplay.HasRows) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerReplay -> Load -> No replay data found for Player."); + } + else + { + JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + while(mapTimeReplay.Read()) + { + string json = Compressor.Decompress(Encoding.UTF8.GetString((byte[])mapTimeReplay[0])); + this.Frames = JsonSerializer.Deserialize>(json, options)!; + } + } + mapTimeReplay.Close(); + dbTask.Dispose(); + + FormatBotName(current_map); + } + + private void FormatBotName(Map current_map) + { + if (this.Controller == null) + return; + + SchemaString bot_name = new SchemaString(this.Controller, "m_iszPlayerName"); + // Revisit, FORMAT CORECTLLY + bot_name.Set($"[WR] {PlayerHUD.FormatTime(current_map.WR[0].Ticks)}"); + Utilities.SetStateChanged(this.Controller, "CBasePlayerController", "m_iszPlayerName"); + } +} \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs new file mode 100644 index 0000000..b5c336b --- /dev/null +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; + +namespace SurfTimer; + +internal class ReplayRecorder +{ + public bool IsRecording { get; set; } = false; + public List Frames { get; set; } = new List(); + + public void Reset() + { + this.IsRecording = false; + this.Frames.Clear(); + } + + public void Start() + { + this.IsRecording = true; + } + + public void Stop() + { + this.IsRecording = false; + } + + public void Tick(Player player) + { + if (!this.IsRecording || player == null) + return; + + // Disabeling Recording if timer disabled + if (!player.Timer.IsEnabled) + { + this.Stop(); + this.Reset(); + return; + } + + var player_pos = player.Controller.Pawn.Value!.AbsOrigin!; + var player_angle = player.Controller.PlayerPawn.Value!.EyeAngles; + var player_button = player.Controller.Pawn.Value.MovementServices!.Buttons.ButtonStates[0]; + var player_flags = player.Controller.Pawn.Value.Flags; + var player_move_type = player.Controller.Pawn.Value.MoveType; + + var frame = new ReplayFrame + { + Pos = new Vector(player_pos.X, player_pos.Y, player_pos.Z), + Ang = new QAngle(player_angle.X, player_angle.Y, player_angle.Z), + Button = player_button, + Flags = player_flags, + MoveType = player_move_type, + }; + + this.Frames.Add(frame); + } + + /// + /// [ player_id | maptime_id | replay_frames ] + /// @ Adding a replay data for a run (PB/WR) + /// @ Data saved can be accessed with `ReplayPlayer.LoadReplayData` + /// + public void SaveReplayData(Player player, TimerDatabase DB) + { + JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + string replay_frames = JsonSerializer.Serialize(Frames, options); + string compressed_replay_frames = Compressor.Compress(replay_frames); + Task updatePlayerReplayTask = DB.Write($"INSERT INTO `MapTimeReplay` " + + $"(`player_id`, `maptime_id`, `map_id`, `replay_frames`) " + + $"VALUES ({player.Profile.ID}, {player.Stats.PB[0].ID}, {player.CurrMap.ID}, '{compressed_replay_frames}') " + + $"ON DUPLICATE KEY UPDATE replay_frames=VALUES(replay_frames)"); + if (updatePlayerReplayTask.Result <= 0) + throw new Exception($"CS2 Surf ERROR >> internal class PlayerReplay -> SaveReplayData -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + updatePlayerReplayTask.Dispose(); + } +} \ No newline at end of file diff --git a/src/ST-UTILS/Compression.cs b/src/ST-UTILS/Compression.cs new file mode 100644 index 0000000..25ff2b2 --- /dev/null +++ b/src/ST-UTILS/Compression.cs @@ -0,0 +1,161 @@ +using System.IO.Compression; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using CounterStrikeSharp.API.Modules.Utils; + +namespace SurfTimer; + +internal class VectorConverter : JsonConverter +{ + public override Vector Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Ensure that the reader is positioned at the start of an object + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Expected start of object."); + + float x = 0, y = 0, z = 0; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + reader.Read(); + + switch (propertyName) + { + case "X": + x = (float)reader.GetDouble(); + break; + case "Y": + y = (float)reader.GetDouble(); + break; + case "Z": + z = (float)reader.GetDouble(); + break; + } + } + } + + return new Vector { X = x, Y = y, Z = z }; + } + + public override void Write(Utf8JsonWriter writer, Vector value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber("X", value.X); + writer.WriteNumber("Y", value.Y); + writer.WriteNumber("Z", value.Z); + writer.WriteEndObject(); + } +} + +internal class QAngleConverter : JsonConverter +{ + public override QAngle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Ensure that the reader is positioned at the start of an object + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Expected start of object."); + + float X = 0, Y = 0, Z = 0; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + reader.Read(); + + switch (propertyName) + { + case "X": + X = (float)reader.GetDouble(); + break; + case "Y": + Y = (float)reader.GetDouble(); + break; + case "Z": + Z = (float)reader.GetDouble(); + break; + } + } + } + + return new QAngle { X = X, Y = Y, Z = Z }; + } + + public override void Write(Utf8JsonWriter writer, QAngle value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber("X", value.X); + writer.WriteNumber("Y", value.Y); + writer.WriteNumber("Z", value.Z); + writer.WriteEndObject(); + } +} + +internal class Compressor +{ + public static string Decompress(string input) + { + byte[] compressed = Convert.FromBase64String(input); + byte[] decompressed = Decompress(compressed); + return Encoding.UTF8.GetString(decompressed); + } + + public static string Compress(string input) + { + byte[] encoded = Encoding.UTF8.GetBytes(input); + byte[] compressed = Compress(encoded); + return Convert.ToBase64String(compressed); + } + + public static byte[] Decompress(byte[] input) + { + using (var source = new MemoryStream(input)) + { + byte[] lengthBytes = new byte[4]; + source.Read(lengthBytes, 0, 4); + + var length = BitConverter.ToInt32(lengthBytes, 0); + using (var decompressionStream = new GZipStream(source, + CompressionMode.Decompress)) + { + var result = new byte[length]; + int totalRead = 0, bytesRead; + while ((bytesRead = decompressionStream.Read(result, totalRead, length - totalRead)) > 0) + { + totalRead += bytesRead; + } + + return result; + } + } + } + + public static byte[] Compress(byte[] input) + { + using (var result = new MemoryStream()) + { + var lengthBytes = BitConverter.GetBytes(input.Length); + result.Write(lengthBytes, 0, 4); + + using (var compressionStream = new GZipStream(result, + CompressionMode.Compress)) + { + compressionStream.Write(input, 0, input.Length); + compressionStream.Flush(); + + } + return result.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/ST-UTILS/Schema.cs b/src/ST-UTILS/Schema.cs new file mode 100644 index 0000000..ba324df --- /dev/null +++ b/src/ST-UTILS/Schema.cs @@ -0,0 +1,30 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Memory; + +using System.Runtime.CompilerServices; +using System.Text; + +namespace SurfTimer; +public class SchemaString : NativeObject where SchemaClass : NativeObject +{ + public SchemaString(SchemaClass instance, string member) + : base(Schema.GetSchemaValue(instance.Handle, typeof(SchemaClass).Name!, member)) + { } + + public unsafe void Set(string str) + { + byte[] bytes = this.GetStringBytes(str); + + for (int i = 0; i < bytes.Length; i++) + { + Unsafe.Write((void*)(this.Handle.ToInt64() + i), bytes[i]); + } + + Unsafe.Write((void*)(this.Handle.ToInt64() + bytes.Length), 0); + } + + private byte[] GetStringBytes(string str) + { + return Encoding.UTF8.GetBytes(str); + } +} \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 830290b..2816032 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -32,6 +32,8 @@ You should have received a copy of the GNU Affero General Public License using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; +using CounterStrikeSharp.API.Modules.Timers; +using CounterStrikeSharp.API.Modules.Cvars; namespace SurfTimer; diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 9a0f5c3..173f3e3 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -4,6 +4,7 @@ net7.0 enable enable + true @@ -12,7 +13,7 @@ - + From 4faa2b55d9d8b9b2f34349a8ab6a8bdb7f921126 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:19:04 +0200 Subject: [PATCH 53/56] replay bot name --- src/ST-Map/Map.cs | 9 ++++++++- src/ST-Player/PlayerHUD.cs | 8 ++++++-- src/ST-Player/PlayerStats/PersonalBest.cs | 1 + src/ST-Player/Replay/ReplayPlayer.cs | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 4e93745..272de00 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -231,7 +231,13 @@ public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnO internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: Implement styles { // Get map world records - Task reader = DB.Query($"SELECT * FROM `MapTimes` WHERE `map_id` = {this.ID} AND `style` = {style} ORDER BY `run_time` ASC;"); + Task reader = DB.Query($@" + SELECT MapTimes.*, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.map_id = {this.ID} AND MapTimes.style = {style} + ORDER BY MapTimes.run_time ASC; + "); MySqlDataReader mapWrData = reader.Result; int totalRows = 0; @@ -252,6 +258,7 @@ internal void GetMapRecordAndTotals(TimerDatabase DB, int style = 0 ) // To-do: this.WR[style].EndVelY = mapWrData.GetFloat("end_vel_y"); // Fastest run end velocity Y for the Map and Style combo this.WR[style].EndVelZ = mapWrData.GetFloat("end_vel_z"); // Fastest run end velocity Z for the Map and Style combo this.WR[style].RunDate = mapWrData.GetInt32("run_date"); // Fastest run date for the Map and Style combo + this.WR[style].Name = mapWrData.GetString("name"); // Fastest run player name for the Map and Style combo } totalRows++; } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index d2d8f3e..dab8070 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -88,7 +88,7 @@ public void Display() } else if (_player.CurrMap.WR[style].ID != -1) { - rankModule = FormatHUDElementHTML("Rank", $"N/A/{_player.CurrMap.TotalCompletions}", "#7882dd"); + rankModule = FormatHUDElementHTML("Rank", $"-/{_player.CurrMap.TotalCompletions}", "#7882dd"); } // PB & WR Modules string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].Ticks > 0 ? FormatTime(_player.Stats.PB[style].Ticks) : "N/A", "#7882dd"); // IMPLEMENT IN PlayerStats // To-do: make Style (currently 0) be dynamic @@ -104,8 +104,12 @@ public void Display() { if (_player.CurrMap.ReplayBot.Controller?.Pawn.SerialNum == _player.Controller.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) { + // Replay HUD Modules + string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; + string nameModule = FormatHUDElementHTML($"{_player.CurrMap.WR[_player.Timer.Style].Name}", $"{FormatTime(_player.CurrMap.WR[_player.Timer.Style].Ticks)}", "#ffd500"); string elapsed_ticks = FormatHUDElementHTML("Tick", $"{_player.CurrMap.ReplayBot.CurrentFrameTick}/{_player.CurrMap.ReplayBot.Frames.Count}", "#7882dd"); - string hud = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}
{elapsed_ticks}"; + string hud = $"{replayModule}
{elapsed_ticks}
{nameModule}"; + _player.Controller.PrintToCenterHtml(hud); } } diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 181d126..2ee50bc 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -16,6 +16,7 @@ internal class PersonalBest public float EndVelY { get; set; } public float EndVelZ { get; set; } public int RunDate { get; set; } + public string Name { get; set; } = ""; // This is used only for WRs // Add other properties as needed // Constructor diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 8fe9436..1d821f1 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -125,7 +125,7 @@ private void FormatBotName(Map current_map) SchemaString bot_name = new SchemaString(this.Controller, "m_iszPlayerName"); // Revisit, FORMAT CORECTLLY - bot_name.Set($"[WR] {PlayerHUD.FormatTime(current_map.WR[0].Ticks)}"); + bot_name.Set($"[WR] {current_map.WR[0].Name} | {PlayerHUD.FormatTime(current_map.WR[0].Ticks)}"); Utilities.SetStateChanged(this.Controller, "CBasePlayerController", "m_iszPlayerName"); } } \ No newline at end of file From d978813ff1d9a8657246fe3bf98d5273f89a436f Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:44:42 +0200 Subject: [PATCH 54/56] more readable queries;formatting --- src/ST-Commands/PlayerCommands.cs | 54 +++++++++--------------- src/ST-Events/Players.cs | 13 ++++-- src/ST-Events/TriggerStartTouch.cs | 15 ++++--- src/ST-Player/PlayerStats/CurrentRun.cs | 47 ++++++++++++--------- src/ST-Player/PlayerStats/PlayerStats.cs | 10 +++-- src/ST-Player/Replay/ReplayPlayer.cs | 6 ++- src/ST-Player/Replay/ReplayRecorder.cs | 10 +++-- 7 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index e92713e..a480ae1 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -18,8 +18,8 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) // To-do: players[userid].Timer.Reset() -> teleport player playerList[player.UserId ?? 0].Timer.Reset(); - if (CurrentMap.StartZone != new Vector(0,0,0)) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0,0,0), new Vector(0,0,0))); + if (CurrentMap.StartZone != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0, 0, 0), new Vector(0, 0, 0))); return; } @@ -32,10 +32,10 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) // To-do: players[userid].Timer.Reset() -> teleport player Player SurfPlayer = playerList[player.UserId ?? 0]; - if (SurfPlayer.Timer.Stage != 0 && CurrentMap.StageStartZone[SurfPlayer.Timer.Stage] != new Vector(0,0,0)) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[SurfPlayer.Timer.Stage], CurrentMap.StageStartZoneAngles[SurfPlayer.Timer.Stage], new Vector(0,0,0))); + if (SurfPlayer.Timer.Stage != 0 && CurrentMap.StageStartZone[SurfPlayer.Timer.Stage] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[SurfPlayer.Timer.Stage], CurrentMap.StageStartZoneAngles[SurfPlayer.Timer.Stage], new Vector(0, 0, 0))); else // Reset back to map start - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0,0,0), new Vector(0,0,0))); + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0, 0, 0), new Vector(0, 0, 0))); return; } @@ -66,13 +66,13 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) return; } - if (CurrentMap.StageStartZone[stage] != new Vector(0,0,0)) + if (CurrentMap.StageStartZone[stage] != new Vector(0, 0, 0)) { if (stage == 0) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, CurrentMap.StartZoneAngles, new Vector(0,0,0))); + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, CurrentMap.StartZoneAngles, new Vector(0, 0, 0))); else - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[stage], CurrentMap.StageStartZoneAngles[stage], new Vector(0,0,0))); - + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[stage], CurrentMap.StageStartZoneAngles[stage], new Vector(0, 0, 0))); + playerList[player.UserId ?? 0].Timer.Reset(); playerList[player.UserId ?? 0].Timer.IsStageMode = true; @@ -80,29 +80,15 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) // causing the timer to start. This needs to be fixed. } - else + else player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); } - // // Test command - // [ConsoleCommand("css_savereplay", "Test")] - // public void SaveReplay(CCSPlayerController? player, CommandInfo command) { - // if(player == null) - // return; - - // foreach(var p in playerList.Values) { - // if(p.Replay.Frames.Count() > 0) { - // p.Replay.StopRecording(); - // p.Replay.SaveReplayData(p, DB!); - // break; - // } - // } - // } - // Test command [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] - public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) { - if(player == null || player.Team == CsTeam.Spectator) + public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) + { + if (player == null || player.Team == CsTeam.Spectator) return; player.ChangeTeam(CsTeam.Spectator); @@ -110,27 +96,29 @@ public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo comma [ConsoleCommand("css_replaybotpause", "Pause the replay bot playback")] [ConsoleCommand("css_rbpause", "Pause the replay bot playback")] - public void PauseReplay(CCSPlayerController? player, CommandInfo command) { - if(player == null + public void PauseReplay(CCSPlayerController? player, CommandInfo command) + { + if (player == null || player.Team != CsTeam.Spectator || CurrentMap.ReplayBot.Controller == null || !CurrentMap.ReplayBot.IsPlaying || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) return; - + CurrentMap.ReplayBot.Pause(); } [ConsoleCommand("css_replaybotflip", "Flips the replay bot between Forward/Backward playback")] [ConsoleCommand("css_rbflip", "Flips the replay bot between Forward/Backward playback")] - public void ReverseReplay(CCSPlayerController? player, CommandInfo command) { - if(player == null + public void ReverseReplay(CCSPlayerController? player, CommandInfo command) + { + if (player == null || player.Team != CsTeam.Spectator || CurrentMap.ReplayBot.Controller == null || !CurrentMap.ReplayBot.IsPlaying || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) return; - + CurrentMap.ReplayBot.FrameTickIncrement *= -1; } } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index d17e1d1..3d7e17b 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -97,7 +97,10 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn connections = 1; // Write new player to database - Task newPlayerTask = DB.Write($"INSERT INTO `Player` (`name`, `steam_id`, `country`, `join_date`, `last_seen`, `connections`) VALUES ('{MySqlHelper.EscapeString(name)}', {player.SteamID}, '{country}', {joinDate}, {lastSeen}, {connections});"); + Task newPlayerTask = DB.Write($@" + INSERT INTO `Player` (`name`, `steam_id`, `country`, `join_date`, `last_seen`, `connections`) + VALUES ('{MySqlHelper.EscapeString(name)}', {player.SteamID}, '{country}', {joinDate}, {lastSeen}, {connections}); + "); int newPlayerTaskRows = newPlayerTask.Result; if (newPlayerTaskRows != 1) throw new Exception($"CS2 Surf ERROR >> OnPlayerConnect -> Failed to write new player to database, this shouldnt happen. Player: {name} ({player.SteamID})"); @@ -174,9 +177,11 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { // Update data in Player DB table - Task updatePlayerTask = DB.Write($"UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', " + - $"`last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 " + - $"WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1;"); + Task updatePlayerTask = DB.Write($@" + UPDATE `Player` SET country = '{playerList[player.UserId ?? 0].Profile.Country}', + `last_seen` = {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, `connections` = `connections` + 1 + WHERE `id` = {playerList[player.UserId ?? 0].Profile.ID} LIMIT 1; + "); if (updatePlayerTask.Result != 1) throw new Exception($"CS2 Surf ERROR >> OnPlayerDisconnect -> Failed to update player data in database. Player: {player.PlayerName} ({player.SteamID})"); // Player disconnection to-do diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index bc131cb..2f7633c 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -79,13 +79,14 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) player.Stats.PB[style].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> " + - $"============== INSERT INTO `MapTimes` " + - $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {CurrentMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, " + - $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + // To-do: get the `start_vel` values for the run from CP implementation - $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + - $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + Console.WriteLine($@"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> + ============== INSERT INTO `MapTimes` + (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) + VALUES ({player.Profile.ID}, {CurrentMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, + {player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {velocity_x}, {velocity_y}, {velocity_z}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) + ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), + start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date); + "); #endif // Add entry in DB for the run diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 96962ee..ddacc1a 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -53,12 +53,14 @@ public void SaveMapTime(Player player, TimerDatabase DB) // Add entry in DB for the run // To-do: add `type` int style = player.Timer.Style; - Task updatePlayerRunTask = DB.Write($"INSERT INTO `MapTimes` " + - $"(`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) " + - $"VALUES ({player.Profile.ID}, {player.CurrMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, " + - $"{player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) " + - $"ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), " + - $"start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date);"); + Task updatePlayerRunTask = DB.Write($@" + INSERT INTO `MapTimes` + (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) + VALUES ({player.Profile.ID}, {player.CurrMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, + {player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) + ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), + start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date); + "); if (updatePlayerRunTask.Result <= 0) throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); updatePlayerRunTask.Dispose(); @@ -91,13 +93,14 @@ public void SaveMapTime(Player player, TimerDatabase DB) #if DEBUG Console.WriteLine($"CP: {cp} | MapTime ID: {player.Stats.PB[style].ID} | Time: {runTime} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> " + - $"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + Console.WriteLine($@"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> + INSERT INTO `Checkpoints` + (`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, + `end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) + VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) ON DUPLICATE KEY UPDATE + run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), + end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch); + "); #endif // Insert/Update CPs to database @@ -105,16 +108,18 @@ public void SaveMapTime(Player player, TimerDatabase DB) // Check if the player has PB object initialized and if the player's character is currently active in the game if (item.Value != null && player.Controller.PlayerPawn.Value != null) { - Task newPbTask = DB.Write($"INSERT INTO `Checkpoints` " + - $"(`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, " + - $"`end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) " + - $"VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) " + - $"ON DUPLICATE KEY UPDATE " + - $"run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), " + - $"end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch);"); + Task newPbTask = DB.Write($@" + INSERT INTO `Checkpoints` + (`maptime_id`, `cp`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, + `end_vel_x`, `end_vel_y`, `end_vel_z`, `attempts`, `end_touch`) + VALUES ({player.Stats.PB[style].ID}, {cp}, {runTime}, {startVelX}, {startVelY}, {startVelZ}, {endVelX}, {endVelY}, {endVelZ}, {attempts}, {ticks}) + ON DUPLICATE KEY UPDATE + run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), start_vel_z=VALUES(start_vel_z), + end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), attempts=VALUES(attempts), end_touch=VALUES(end_touch); + "); if (newPbTask.Result <= 0) throw new Exception($"CS2 Surf ERROR >> internal class Checkpoint : PersonalBest -> SaveCurrentRunCheckpoints -> Inserting Checkpoints. CP: {cp} | Name: {player.Profile.Name}"); - + newPbTask.Dispose(); } } diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 65dcc39..48eed71 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -28,10 +28,12 @@ public PlayerStats() ///
public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) { - Task dbTask2 = DB.Query($"SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery " + - $"WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` " + - $"AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery " + - $"WHERE mainquery.`player_id` = {player.Profile.ID} AND mainquery.`map_id` = {player.CurrMap.ID}; "); + Task dbTask2 = DB.Query($@" + SELECT mainquery.*, (SELECT COUNT(*) FROM `MapTimes` AS subquery + WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` + AND subquery.`run_time` <= mainquery.`run_time`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`player_id` = {player.Profile.ID} AND mainquery.`map_id` = {player.CurrMap.ID}; + "); MySqlDataReader playerStats = dbTask2.Result; int style = 0; // To-do: implement styles if (!playerStats.HasRows) diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 1d821f1..b258182 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -96,8 +96,10 @@ public void LoadReplayData(TimerDatabase DB, Map current_map) if (this.Controller == null) return; // TODO: make query for wr too - Task dbTask = DB.Query($"SELECT `replay_frames` FROM MapTimeReplay " + - $"WHERE `map_id`={current_map.ID} AND `maptime_id`={current_map.WR[0].ID} "); + Task dbTask = DB.Query($@" + SELECT `replay_frames` FROM MapTimeReplay + WHERE `map_id`={current_map.ID} AND `maptime_id`={current_map.WR[0].ID} + "); MySqlDataReader mapTimeReplay = dbTask.Result; if(!mapTimeReplay.HasRows) { diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index b5c336b..dd610e0 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -66,10 +66,12 @@ public void SaveReplayData(Player player, TimerDatabase DB) JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; string replay_frames = JsonSerializer.Serialize(Frames, options); string compressed_replay_frames = Compressor.Compress(replay_frames); - Task updatePlayerReplayTask = DB.Write($"INSERT INTO `MapTimeReplay` " + - $"(`player_id`, `maptime_id`, `map_id`, `replay_frames`) " + - $"VALUES ({player.Profile.ID}, {player.Stats.PB[0].ID}, {player.CurrMap.ID}, '{compressed_replay_frames}') " + - $"ON DUPLICATE KEY UPDATE replay_frames=VALUES(replay_frames)"); + Task updatePlayerReplayTask = DB.Write($@" + INSERT INTO `MapTimeReplay` + (`player_id`, `maptime_id`, `map_id`, `replay_frames`) + VALUES ({player.Profile.ID}, {player.Stats.PB[0].ID}, {player.CurrMap.ID}, '{compressed_replay_frames}') + ON DUPLICATE KEY UPDATE replay_frames=VALUES(replay_frames) + "); if (updatePlayerReplayTask.Result <= 0) throw new Exception($"CS2 Surf ERROR >> internal class PlayerReplay -> SaveReplayData -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); updatePlayerReplayTask.Dispose(); From c6cacf277e01fcdffe5b076bbe121a99c57e8a61 Mon Sep 17 00:00:00 2001 From: Shachar <110262023+shacharrr@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:33:59 +0200 Subject: [PATCH 55/56] Fixing every error I find in the replays + adding saveloc and stage/bonus support (#10) * Fixed bot noclipin when no replay * Added saveloc/tele support * Added practice mark to messages when in practice mode * Now even when time not running, When tele it will start playing in practice mode * Forgot to add the tele args lol, Fixed now * Fixed error when !tele larger than amount of teles or not a number * Fixed Map stages counting. why wasn't this fix already :( * Added support for more than one replay bot + plugin no longer relies on Will's version of cs2fixes * Displaying time instead of ticks in bot replay now. would like for someone to check if there is a better way, but this works pretty well * Added support for Personal Best replays with !pbreplay * Updated readme * Removed not needed comments * Works also when no wr exists * FormatTime correctly * Reverted uneeded changes * Temp patch for maps changing bot_quota, need to be addressed * Cleaned code --- README.md | 3 +- cfg/SurfTimer/server_settings.cfg | 2 +- src/ST-Commands/PlayerCommands.cs | 206 ++++++++++++++++++++++-- src/ST-Events/Players.cs | 33 ++-- src/ST-Events/Tick.cs | 25 ++- src/ST-Events/TriggerEndTouch.cs | 4 +- src/ST-Events/TriggerStartTouch.cs | 43 +++-- src/ST-Map/Map.cs | 26 ++- src/ST-Player/Player.cs | 15 ++ src/ST-Player/PlayerHUD.cs | 16 +- src/ST-Player/PlayerStats/Checkpoint.cs | 2 - src/ST-Player/PlayerStats/CurrentRun.cs | 7 +- src/ST-Player/Replay/ReplayFrame.cs | 10 ++ src/ST-Player/Replay/ReplayPlayer.cs | 109 ++++++++++--- src/ST-Player/Replay/ReplayRecorder.cs | 26 +-- src/ST-Player/Saveloc/SavelocFrame.cs | 11 ++ src/ST-UTILS/ConVar.cs | 15 ++ src/SurfTimer.cs | 7 +- 18 files changed, 459 insertions(+), 101 deletions(-) create mode 100644 src/ST-Player/Saveloc/SavelocFrame.cs create mode 100644 src/ST-UTILS/ConVar.cs diff --git a/README.md b/README.md index 04093ad..123870d 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Bold & Italics = being worked on. - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) -- [ ] Run replays +- [X] Run replays +- [X] Saveloc/Tele - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) diff --git a/cfg/SurfTimer/server_settings.cfg b/cfg/SurfTimer/server_settings.cfg index 074b2f0..2ff13d0 100644 --- a/cfg/SurfTimer/server_settings.cfg +++ b/cfg/SurfTimer/server_settings.cfg @@ -30,7 +30,7 @@ sv_staminalandcost 0 sv_timebetweenducks 0 // Some replay bot shit (took so fucking long to debug) -bot_quota 1 // This is gonna be used to change the amount of bots allowed (per stages/bonuses/etc) when stages/bonuses/etc added +// bot_quota 1 No need for this, because the server handles it bot_quota_mode "normal" bot_join_after_player 1 bot_join_team CT diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index a480ae1..3e51527 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -47,7 +47,7 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) return; int stage = Int32.Parse(command.ArgByIndex(1)) - 1; - if (stage > CurrentMap.Stages - 1) + if (stage > CurrentMap.Stages - 1 && CurrentMap.Stages > 0) stage = CurrentMap.Stages - 1; // Must be 1 argument @@ -84,7 +84,6 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); } - // Test command [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) { @@ -94,31 +93,210 @@ public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo comma player.ChangeTeam(CsTeam.Spectator); } + /* + ######################### + Reaplay Commands + ######################### + */ [ConsoleCommand("css_replaybotpause", "Pause the replay bot playback")] [ConsoleCommand("css_rbpause", "Pause the replay bot playback")] public void PauseReplay(CCSPlayerController? player, CommandInfo command) { - if (player == null - || player.Team != CsTeam.Spectator - || CurrentMap.ReplayBot.Controller == null - || !CurrentMap.ReplayBot.IsPlaying - || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + if(player == null || player.Team != CsTeam.Spectator) return; - CurrentMap.ReplayBot.Pause(); + foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + { + if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + continue; + + rb.Pause(); + } } [ConsoleCommand("css_replaybotflip", "Flips the replay bot between Forward/Backward playback")] [ConsoleCommand("css_rbflip", "Flips the replay bot between Forward/Backward playback")] public void ReverseReplay(CCSPlayerController? player, CommandInfo command) { - if (player == null - || player.Team != CsTeam.Spectator - || CurrentMap.ReplayBot.Controller == null - || !CurrentMap.ReplayBot.IsPlaying - || CurrentMap.ReplayBot.Controller.Pawn.SerialNum != player.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + if(player == null || player.Team != CsTeam.Spectator) + return; + + foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + { + if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + continue; + + rb.FrameTickIncrement *= -1; + } + } + + [ConsoleCommand("css_pbreplay", "Allows for replay of player's PB")] + public void PbReplay(CCSPlayerController? player, CommandInfo command) + { + if(player == null) + return; + + int maptime_id = playerList[player!.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].ID; + if (command.ArgCount > 1) + { + try + { + maptime_id = int.Parse(command.ArgByIndex(1)); + } + catch {} + } + + if(maptime_id == -1 || !CurrentMap.ConnectedMapTimes.Contains(maptime_id)) + { + player.PrintToChat($"{PluginPrefix} {ChatColors.Red}No time was found"); + return; + } + + for(int i = 0; i < CurrentMap.ReplayBots.Count; i++) + { + if(CurrentMap.ReplayBots[i].Stat_MapTimeID == maptime_id) + { + player.PrintToChat($"{PluginPrefix} {ChatColors.Red}A bot of this run already playing"); + return; + } + } + + CurrentMap.ReplayBots = CurrentMap.ReplayBots.Prepend(new ReplayPlayer() { + Stat_MapTimeID = maptime_id, + Stat_Prefix = "PB" + }).ToList(); + + Server.NextFrame(() => { + Server.ExecuteCommand($"bot_quota {CurrentMap.ReplayBots.Count}"); + }); + } + + /* + ######################## + Saveloc Commands + ######################## + */ + [ConsoleCommand("css_saveloc", "Save current player location to be practiced")] + public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) + { + if(player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) + return; + + Player p = playerList[player.UserId ?? 0]; + if (!p.Timer.IsRunning) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Cannot save location while not in run"); + return; + } + + var player_pos = p.Controller.Pawn.Value!.AbsOrigin!; + var player_angle = p.Controller.PlayerPawn.Value!.EyeAngles; + var player_velocity = p.Controller.PlayerPawn.Value!.AbsVelocity; + + p.SavedLocations.Add(new SavelocFrame { + Pos = new Vector(player_pos.X, player_pos.Y, player_pos.Z), + Ang = new QAngle(player_angle.X, player_angle.Y, player_angle.Z), + Vel = new Vector(player_velocity.X, player_velocity.Y, player_velocity.Z), + Tick = p.Timer.Ticks + }); + p.CurrentSavedLocation = p.SavedLocations.Count-1; + + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Green}Saved location! {ChatColors.Default} use !tele {p.SavedLocations.Count-1} to teleport to this location"); + } + + [ConsoleCommand("css_tele", "Teleport player to current saved location")] + public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo command) + { + if(player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; - CurrentMap.ReplayBot.FrameTickIncrement *= -1; + Player p = playerList[player.UserId ?? 0]; + + if(p.SavedLocations.Count == 0) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + return; + } + + if(!p.Timer.IsRunning) + p.Timer.Start(); + + if (!p.Timer.IsPracticeMode) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Timer now on practice"); + p.Timer.IsPracticeMode = true; + } + + if(command.ArgCount > 1) + try + { + int tele_n = int.Parse(command.ArgByIndex(1)); + if (tele_n < p.SavedLocations.Count) + p.CurrentSavedLocation = tele_n; + } + catch { } + SavelocFrame location = p.SavedLocations[p.CurrentSavedLocation]; + Server.NextFrame(() => { + p.Controller.PlayerPawn.Value!.Teleport(location.Pos, location.Ang, location.Vel); + p.Timer.Ticks = location.Tick; + }); + + p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + } + + [ConsoleCommand("css_teleprev", "Teleport player to previous saved location")] + public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo command) + { + if(player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) + return; + + Player p = playerList[player.UserId ?? 0]; + + if(p.SavedLocations.Count == 0) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + return; + } + + if(p.CurrentSavedLocation == 0) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at first location"); + } + else + { + p.CurrentSavedLocation--; + } + + TeleportPlayerLocation(player, command); + + p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + } + + [ConsoleCommand("css_telenext", "Teleport player to next saved location")] + public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo command) + { + if(player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) + return; + + Player p = playerList[player.UserId ?? 0]; + + if(p.SavedLocations.Count == 0) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + return; + } + + if(p.CurrentSavedLocation == p.SavedLocations.Count-1) + { + p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at last location"); + } + else + { + p.CurrentSavedLocation++; + } + + TeleportPlayerLocation(player, command); + + p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); } } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 3d7e17b..a2162a2 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -9,26 +9,36 @@ namespace SurfTimer; public partial class SurfTimer { - [GameEventHandler(HookMode.Post)] + [GameEventHandler] public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) { var controller = @event.Userid; - if(!controller.IsValid) + if(!controller.IsValid || !controller.IsBot) return HookResult.Continue; - if (controller.IsBot && CurrentMap.ReplayBot.Controller == null) + for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) { - CurrentMap.ReplayBot.Controller = controller; - // CurrentMap.ReplayBot.Controller.PlayerName = $"[REPLAY] {CurrentMap.Name}"; + if(CurrentMap.ReplayBots[i].IsPlayable) + continue; - Server.PrintToChatAll($"{ChatColors.Lime} Loading replay data..."); // WHY COLORS NOT WORKING AHHHHH!!!!! + int repeats = -1; + if(CurrentMap.ReplayBots[i].Stat_Prefix == "PB") + repeats = 3; + + CurrentMap.ReplayBots[i].SetController(controller, repeats); + Server.PrintToChatAll($"{ChatColors.Lime} Loading replay data..."); AddTimer(2f, () => { - CurrentMap.ReplayBot.Controller.RemoveWeapons(); + if(!CurrentMap.ReplayBots[i].IsPlayable) + return; + + CurrentMap.ReplayBots[i].Controller!.RemoveWeapons(); - CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap); + CurrentMap.ReplayBots[i].LoadReplayData(DB!); - CurrentMap.ReplayBot.Start(); + CurrentMap.ReplayBots[i].Start(); }); + + return HookResult.Continue; } return HookResult.Continue; @@ -157,8 +167,9 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo { var player = @event.Userid; - if (CurrentMap.ReplayBot.Controller != null&& CurrentMap.ReplayBot.Controller.Equals(player)) - CurrentMap.ReplayBot.Reset(); + for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) + if (CurrentMap.ReplayBots[i].IsPlayable && CurrentMap.ReplayBots[i].Controller!.Equals(player)) + CurrentMap.ReplayBots[i].Reset(); if (player.IsBot || !player.IsValid) { diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index a7140cc..37703e4 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -1,5 +1,4 @@ -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; +using CounterStrikeSharp.API.Modules.Cvars; namespace SurfTimer; @@ -14,7 +13,25 @@ public void OnTick() player.HUD.Display(); } - // Replay BOT Ticks - CurrentMap?.ReplayBot.Tick(); // When CurrentMap null the ? operator will terminate safely the operation + if (CurrentMap == null) + return; + + // Need to disable maps from executing their cfgs. Currently idk how (But seriusly it a security issue) + ConVar? bot_quota = ConVar.Find("bot_quota"); + if (bot_quota != null) + { + int cbq = bot_quota.GetPrimitiveValue(); + if(cbq != CurrentMap.ReplayBots.Count) + { + bot_quota.SetValue(CurrentMap.ReplayBots.Count); + } + } + + for(int i = 0; i < CurrentMap!.ReplayBots.Count; i++) + { + CurrentMap.ReplayBots[i].Tick(); + if (CurrentMap.ReplayBots[i].RepeatCount == 0) + CurrentMap.KickReplayBot(i); + } } } \ No newline at end of file diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index b330ee3..b98ed13 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -2,7 +2,6 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; -using CounterStrikeSharp.API; namespace SurfTimer; @@ -45,11 +44,12 @@ internal HookResult OnTriggerEndTouch(DynamicHook handler) if(player.ReplayRecorder.IsRecording) { // Saveing 2 seconds before leaving the start zone - player.ReplayRecorder.Frames.RemoveRange(0, Math.Max(0, player.ReplayRecorder.Frames.Count - (64*2))); // Would like for someone to fact check the math :) + player.ReplayRecorder.Frames.RemoveRange(0, Math.Max(0, player.ReplayRecorder.Frames.Count - (64*2))); // Todo make a plugin convar for the time saved before start of run } // MAP START ZONE player.Timer.Start(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_RUN; /* Revisit // Wonky Prespeed check diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 2f7633c..3059d4e 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -51,24 +51,29 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) if (player.Timer.IsRunning) { player.Timer.Stop(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_RUN; player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the run player.Stats.ThisRun.EndVelX = velocity_x; // End pre speed for the run player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + // To-do: make Style (currently 0) be dynamic if (player.Stats.PB[style].Ticks <= 0) // Player first ever PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {player.Controller.PlayerName} finished the map in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + Server.PrintToChatAll($"{PluginPrefix} {PracticeString}{player.Controller.PlayerName} finished the map in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); } else if (player.Timer.Ticks < player.Stats.PB[style].Ticks) // Player beating their existing PersonalBest for the map { - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[style].Ticks)}{ChatColors.Default})!"); } else // Player did not beat their existing PersonalBest for the map { - player.Controller.PrintToChat($"{PluginPrefix} You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + player.Controller.PrintToChat($"{PluginPrefix} {PracticeString}You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); return HookResult.Continue; // Exit here so we don't write to DB } @@ -90,18 +95,26 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) #endif // Add entry in DB for the run - player.Stats.ThisRun.SaveMapTime(player, DB); // Save the MapTime PB data - player.Stats.LoadMapTimesData(player, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints - player.Stats.LoadCheckpointsData(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here - CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD - - // Replay - Add end buffer for replay - AddTimer(1.5f, () => player.ReplayRecorder.SaveReplayData(player, DB)); - AddTimer(2f, () => { - CurrentMap.ReplayBot.LoadReplayData(DB!, CurrentMap); - CurrentMap.ReplayBot.ResetReplay(); - }); + if(!player.Timer.IsPracticeMode) { + AddTimer(1.5f, () => { + player.Stats.ThisRun.SaveMapTime(player, DB); // Save the MapTime PB data + player.Stats.LoadMapTimesData(player, DB); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints + player.Stats.LoadCheckpointsData(DB); // Reload checkpoints for the run - we should really have this in `SaveMapTime` as well but we don't re-load PB data inside there so we need to do it here + CurrentMap.GetMapRecordAndTotals(DB); // Reload the Map record and totals for the HUD + }); + + // This section checks if the PB is better than WR + if(player.Timer.Ticks < CurrentMap.WR[player.Timer.Style].Ticks || CurrentMap.WR[player.Timer.Style].ID == -1) + { + int WrIndex = CurrentMap.ReplayBots.Count-1; // As the ReplaysBot is set, WR Index will always be at the end of the List + AddTimer(2f, () => { + CurrentMap.ReplayBots[WrIndex].Stat_MapTimeID = CurrentMap.WR[player.Timer.Style].ID; + CurrentMap.ReplayBots[WrIndex].LoadReplayData(DB!); + CurrentMap.ReplayBots[WrIndex].ResetReplay(); + }); + } + } } #if DEBUG diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 272de00..c1fb166 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -21,7 +21,8 @@ internal class Map public int LastPlayed {get; set;} = 0; public int TotalCompletions {get; set;} = 0; public Dictionary WR { get; set; } = new Dictionary(); - public ReplayPlayer ReplayBot { get; set; } = new ReplayPlayer(); + public List ConnectedMapTimes { get; set; } = new List(); + public List ReplayBots { get; set; } = new List { new ReplayPlayer() }; // Zone Origin Information // Map start/end zones @@ -150,6 +151,7 @@ internal Map(string Name, TimerDatabase DB) } } } + if (this.Stages > 0) this.Stages++; // You did not count the stages right :( Console.WriteLine($"[CS2 Surf] Identifying start zone: {this.StartZone.X},{this.StartZone.Y},{this.StartZone.Z}\nIdentifying end zone: {this.EndZone.X},{this.EndZone.Y},{this.EndZone.Z}"); // Gather map information OR create entry @@ -215,6 +217,26 @@ internal Map(string Name, TimerDatabase DB) // Initiates getting the World Records for the map GetMapRecordAndTotals(DB); // To-do: Implement styles + + this.ReplayBots[0].Stat_MapTimeID = this.WR[0].ID; // Sets WrIndex to WR maptime_id + if(this.Stages > 0) // If stages map adds bot + this.ReplayBots = this.ReplayBots.Prepend(new ReplayPlayer()).ToList(); + + if(this.Bonuses > 0) // If has bonuses adds bot + this.ReplayBots = this.ReplayBots.Prepend(new ReplayPlayer()).ToList(); + } + + public void KickReplayBot(int index) + { + if (!this.ReplayBots[index].IsPlayable) + return; + + int? id_to_kick = this.ReplayBots[index].Controller!.UserId; + if(id_to_kick == null) + return; + + this.ReplayBots.RemoveAt(index); + Server.ExecuteCommand($"kickid {id_to_kick}; bot_quota {this.ReplayBots.Count}"); } public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) @@ -245,6 +267,7 @@ FROM MapTimes { // To-do: Implement bonuses WR // To-do: Implement stages WR + this.ConnectedMapTimes.Clear(); while (mapWrData.Read()) { if (totalRows == 0) // We are sorting by `run_time ASC` so the first row is always the fastest run for the map and style combo :) @@ -260,6 +283,7 @@ FROM MapTimes this.WR[style].RunDate = mapWrData.GetInt32("run_date"); // Fastest run date for the Map and Style combo this.WR[style].Name = mapWrData.GetString("name"); // Fastest run player name for the Map and Style combo } + this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); totalRows++; } } diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 059d1cc..2bcfb83 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -12,6 +12,8 @@ internal class Player public PlayerStats Stats {get; set;} public PlayerHUD HUD {get; set;} public ReplayRecorder ReplayRecorder { get; set; } + public List SavedLocations { get; set; } + public int CurrentSavedLocation { get; set; } // Player information public PlayerProfile Profile {get; set;} @@ -30,8 +32,21 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen this.Timer = new PlayerTimer(); this.Stats = new PlayerStats(); this.ReplayRecorder = new ReplayRecorder(); + this.SavedLocations = new List(); + CurrentSavedLocation = 0; this.HUD = new PlayerHUD(this); this.CurrMap = CurrMap; } + + /// + /// Checks if current player is spcetating player

+ ///

+ public bool IsSpectating(CCSPlayerController p) + { + if(p == null || this.Controller == null || this.Controller.Team != CounterStrikeSharp.API.Modules.Utils.CsTeam.Spectator) + return false; + + return p.Pawn.SerialNum == this.Controller.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum; + } } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index dab8070..4ed6c2b 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -44,7 +44,7 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P { case PlayerTimer.TimeFormatStyle.Compact: return time.TotalMinutes < 1 - ? $"{time.Seconds:D1}.{millis:D3}" + ? $"{time.Seconds:D1}:{millis:D3}" : $"{time.Minutes:D1}:{time.Seconds:D1}.{millis:D3}"; case PlayerTimer.TimeFormatStyle.Full: return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; @@ -102,13 +102,17 @@ public void Display() } else if (_player.Controller.Team == CsTeam.Spectator) { - if (_player.CurrMap.ReplayBot.Controller?.Pawn.SerialNum == _player.Controller.ObserverPawn.Value!.ObserverServices!.ObserverTarget.SerialNum) + for (int i = 0; i < _player.CurrMap.ReplayBots.Count; i++) { - // Replay HUD Modules + if(!_player.CurrMap.ReplayBots[i].IsPlayable || !_player.IsSpectating(_player.CurrMap.ReplayBots[i].Controller!)) + continue; + string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; - string nameModule = FormatHUDElementHTML($"{_player.CurrMap.WR[_player.Timer.Style].Name}", $"{FormatTime(_player.CurrMap.WR[_player.Timer.Style].Ticks)}", "#ffd500"); - string elapsed_ticks = FormatHUDElementHTML("Tick", $"{_player.CurrMap.ReplayBot.CurrentFrameTick}/{_player.CurrMap.ReplayBot.Frames.Count}", "#7882dd"); - string hud = $"{replayModule}
{elapsed_ticks}
{nameModule}"; + + string nameModule = FormatHUDElementHTML($"{_player.CurrMap.ReplayBots[i].Stat_PlayerName}", $"{FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTime)}", "#ffd500"); + + string elapsed_time = FormatHUDElementHTML("Time", $"{PlayerHUD.FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTick)}", "#7882dd"); + string hud = $"{replayModule}
{elapsed_time}
{nameModule}"; _player.Controller.PrintToCenterHtml(hud); } diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index 30921f5..bf51165 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -1,5 +1,3 @@ -using MySqlConnector; - namespace SurfTimer; internal class Checkpoint : PersonalBest diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index ddacc1a..4609dd1 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -53,13 +53,14 @@ public void SaveMapTime(Player player, TimerDatabase DB) // Add entry in DB for the run // To-do: add `type` int style = player.Timer.Style; + string replay_frames = player.ReplayRecorder.SerializeReplay(); Task updatePlayerRunTask = DB.Write($@" INSERT INTO `MapTimes` - (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`) + (`player_id`, `map_id`, `style`, `type`, `stage`, `run_time`, `start_vel_x`, `start_vel_y`, `start_vel_z`, `end_vel_x`, `end_vel_y`, `end_vel_z`, `run_date`, `replay_frames`) VALUES ({player.Profile.ID}, {player.CurrMap.ID}, {style}, 0, 0, {player.Stats.ThisRun.Ticks}, - {player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}) + {player.Stats.ThisRun.StartVelX}, {player.Stats.ThisRun.StartVelY}, {player.Stats.ThisRun.StartVelZ}, {player.Stats.ThisRun.EndVelX}, {player.Stats.ThisRun.EndVelY}, {player.Stats.ThisRun.EndVelZ}, {(int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()}, '{replay_frames}') ON DUPLICATE KEY UPDATE run_time=VALUES(run_time), start_vel_x=VALUES(start_vel_x), start_vel_y=VALUES(start_vel_y), - start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date); + start_vel_z=VALUES(start_vel_z), end_vel_x=VALUES(end_vel_x), end_vel_y=VALUES(end_vel_y), end_vel_z=VALUES(end_vel_z), run_date=VALUES(run_date), replay_frames=VALUES(replay_frames); "); if (updatePlayerRunTask.Result <= 0) throw new Exception($"CS2 Surf ERROR >> internal class PersonalBest -> SaveMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index 18174b3..f67de67 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -2,12 +2,22 @@ namespace SurfTimer; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Core; +internal enum ReplayFrameSituation +{ + NONE, + START_RUN, + END_RUN, + TOUCH_CHECKPOINT, + START_STAGE, + END_STAGE +} [Serializable] internal class ReplayFrame { public Vector Pos { get; set; } = new Vector(0, 0, 0); public QAngle Ang { get; set; } = new QAngle(0, 0, 0); + public uint Situation { get; set; } = (uint)ReplayFrameSituation.NONE; public ulong Button { get; set; } public uint Flags { get; set; } public MoveType_t MoveType { get; set; } diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index b258182..98a44c4 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -11,7 +11,18 @@ internal class ReplayPlayer { public bool IsPlaying { get; set; } = false; public bool IsPaused { get; set; } = false; - public bool IsOnRepeat { get; set; } = true; // Currently should always repeat + public bool IsPlayable { get; set; } = false; + + // Tracking for replay counting + public int RepeatCount { get; set; } = -1; + + // Stats for replay displaying + public string Stat_Prefix { get; set; } = "WR"; + public string Stat_PlayerName { get; set; } = "N/A"; + public int Stat_MapTimeID { get; set; } = -1; + public int Stat_RunTime { get; set; } = 0; + public bool Stat_IsRunning { get; set; } = false; + public int Stat_RunTick { get; set; } = 0; // Tracking public List Frames { get; set; } = new List(); @@ -26,12 +37,19 @@ public void ResetReplay() { this.CurrentFrameTick = 0; this.FrameTickIncrement = 1; + if(this.RepeatCount > 0) + this.RepeatCount--; + + this.Stat_IsRunning = false; + this.Stat_RunTick = 0; } public void Reset() { this.IsPlaying = false; this.IsPaused = false; + this.IsPlayable = false; + this.RepeatCount = -1; this.Frames.Clear(); @@ -40,13 +58,19 @@ public void Reset() this.Controller = null; } + public void SetController(CCSPlayerController c, int repeat_count = -1) + { + this.Controller = c; + this.RepeatCount = repeat_count; + this.IsPlayable = true; + } + public void Start() { - if (this.Controller == null) + if (!this.IsPlayable) return; this.IsPlaying = true; - this.Controller.Pawn.Value!.MoveType = MoveType_t.MOVETYPE_NOCLIP; } public void Stop() @@ -56,20 +80,50 @@ public void Stop() public void Pause() { - if (this.IsPlaying) - this.IsPaused = !this.IsPaused; + if (!this.IsPlaying) + return; + + this.IsPaused = !this.IsPaused; + this.Stat_IsRunning = !this.Stat_IsRunning; } public void Tick() { - if (!this.IsPlaying || this.Controller == null || this.Frames.Count == 0) + if (!this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) return; ReplayFrame current_frame = this.Frames[this.CurrentFrameTick]; - var current_pos = this.Controller.PlayerPawn.Value!.AbsOrigin!; + + // SOME BLASHPEMY FOR YOU + if (this.FrameTickIncrement >= 0) + { + if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + { + this.Stat_IsRunning = true; + this.Stat_RunTick = 0; + } + else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + { + this.Stat_IsRunning = false; + } + } + else + { + if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + { + this.Stat_IsRunning = false; + } + else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + { + this.Stat_IsRunning = true; + this.Stat_RunTick = this.CurrentFrameTick - (64*2); // (64*2) counts for the 2 seconds before run actually starts + } + } + // END OF BLASPHEMY + + var current_pos = this.Controller!.PlayerPawn.Value!.AbsOrigin!; bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; - bool is_ducking = (current_frame.Flags & (uint)PlayerFlags.FL_DUCKING) != 0; Vector velocity = (current_frame.Pos - current_pos) * 64; @@ -85,21 +139,28 @@ public void Tick() if (!this.IsPaused) + { this.CurrentFrameTick = Math.Max(0, this.CurrentFrameTick + this.FrameTickIncrement); + if (this.Stat_IsRunning) + this.Stat_RunTick = Math.Max(0, this.Stat_RunTick + this.FrameTickIncrement); + } if(this.CurrentFrameTick >= this.Frames.Count) this.ResetReplay(); } - public void LoadReplayData(TimerDatabase DB, Map current_map) + public void LoadReplayData(TimerDatabase DB) { - if (this.Controller == null) + if (!this.IsPlayable) return; - // TODO: make query for wr too + Task dbTask = DB.Query($@" - SELECT `replay_frames` FROM MapTimeReplay - WHERE `map_id`={current_map.ID} AND `maptime_id`={current_map.WR[0].ID} + SELECT MapTimes.replay_frames, MapTimes.run_time, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.id={this.Stat_MapTimeID} "); + MySqlDataReader mapTimeReplay = dbTask.Result; if(!mapTimeReplay.HasRows) { @@ -112,22 +173,28 @@ public void LoadReplayData(TimerDatabase DB, Map current_map) { string json = Compressor.Decompress(Encoding.UTF8.GetString((byte[])mapTimeReplay[0])); this.Frames = JsonSerializer.Deserialize>(json, options)!; + + this.Stat_RunTime = mapTimeReplay.GetInt32("run_time"); + this.Stat_PlayerName = mapTimeReplay.GetString("name"); } + FormatBotName(); } mapTimeReplay.Close(); dbTask.Dispose(); - - FormatBotName(current_map); } - private void FormatBotName(Map current_map) + private void FormatBotName() { - if (this.Controller == null) + if (!this.IsPlayable) return; - SchemaString bot_name = new SchemaString(this.Controller, "m_iszPlayerName"); - // Revisit, FORMAT CORECTLLY - bot_name.Set($"[WR] {current_map.WR[0].Name} | {PlayerHUD.FormatTime(current_map.WR[0].Ticks)}"); - Utilities.SetStateChanged(this.Controller, "CBasePlayerController", "m_iszPlayerName"); + SchemaString bot_name = new SchemaString(this.Controller!, "m_iszPlayerName"); + + string replay_name = $"[{this.Stat_Prefix}] {this.Stat_PlayerName} | {PlayerHUD.FormatTime(this.Stat_RunTime)}"; + if(this.Stat_RunTime <= 0) + replay_name = $"[{this.Stat_Prefix}] {this.Stat_PlayerName}"; + + bot_name.Set(replay_name); + Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName"); } } \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index dd610e0..fd20ed5 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; @@ -7,6 +6,7 @@ namespace SurfTimer; internal class ReplayRecorder { public bool IsRecording { get; set; } = false; + public ReplayFrameSituation CurrentSituation { get; set; } = ReplayFrameSituation.NONE; public List Frames { get; set; } = new List(); public void Reset() @@ -48,32 +48,22 @@ public void Tick(Player player) { Pos = new Vector(player_pos.X, player_pos.Y, player_pos.Z), Ang = new QAngle(player_angle.X, player_angle.Y, player_angle.Z), + Situation = (uint)this.CurrentSituation, Button = player_button, Flags = player_flags, MoveType = player_move_type, }; this.Frames.Add(frame); + + // Every Situation should last for at most, 1 tick + this.CurrentSituation = ReplayFrameSituation.NONE; } - /// - /// [ player_id | maptime_id | replay_frames ] - /// @ Adding a replay data for a run (PB/WR) - /// @ Data saved can be accessed with `ReplayPlayer.LoadReplayData` - /// - public void SaveReplayData(Player player, TimerDatabase DB) + public string SerializeReplay() { JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; string replay_frames = JsonSerializer.Serialize(Frames, options); - string compressed_replay_frames = Compressor.Compress(replay_frames); - Task updatePlayerReplayTask = DB.Write($@" - INSERT INTO `MapTimeReplay` - (`player_id`, `maptime_id`, `map_id`, `replay_frames`) - VALUES ({player.Profile.ID}, {player.Stats.PB[0].ID}, {player.CurrMap.ID}, '{compressed_replay_frames}') - ON DUPLICATE KEY UPDATE replay_frames=VALUES(replay_frames) - "); - if (updatePlayerReplayTask.Result <= 0) - throw new Exception($"CS2 Surf ERROR >> internal class PlayerReplay -> SaveReplayData -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); - updatePlayerReplayTask.Dispose(); - } + return Compressor.Compress(replay_frames); + } } \ No newline at end of file diff --git a/src/ST-Player/Saveloc/SavelocFrame.cs b/src/ST-Player/Saveloc/SavelocFrame.cs new file mode 100644 index 0000000..a6bcd4c --- /dev/null +++ b/src/ST-Player/Saveloc/SavelocFrame.cs @@ -0,0 +1,11 @@ +using CounterStrikeSharp.API.Modules.Utils; + +namespace SurfTimer; + +internal class SavelocFrame +{ + public Vector Pos { get; set; } = new Vector(0, 0, 0); + public QAngle Ang { get; set; } = new QAngle(0, 0, 0); + public Vector Vel { get; set; } = new Vector(0, 0, 0); + public int Tick { get; set; } = 0; +} diff --git a/src/ST-UTILS/ConVar.cs b/src/ST-UTILS/ConVar.cs new file mode 100644 index 0000000..55206ba --- /dev/null +++ b/src/ST-UTILS/ConVar.cs @@ -0,0 +1,15 @@ +using CounterStrikeSharp.API.Modules.Cvars; + +namespace SurfTimer; + +internal class ConVarHelper +{ + public static void RemoveCheatFlagFromConVar(string cv_name) + { + ConVar? cv = ConVar.Find(cv_name); + if (cv == null || (cv.Flags & CounterStrikeSharp.API.ConVarFlags.FCVAR_CHEAT) == 0) + return; + + cv.Flags &= ~CounterStrikeSharp.API.ConVarFlags.FCVAR_CHEAT; + } +} \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 2816032..846a225 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -32,8 +32,6 @@ You should have received a copy of the GNU Affero General Public License using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Cvars; namespace SurfTimer; @@ -77,6 +75,11 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) { // Load cvars/other configs here // Execute server_settings.cfg + + ConVarHelper.RemoveCheatFlagFromConVar("bot_stop"); + ConVarHelper.RemoveCheatFlagFromConVar("bot_freeze"); + ConVarHelper.RemoveCheatFlagFromConVar("bot_zombie"); + Server.ExecuteCommand("execifexists SurfTimer/server_settings.cfg"); Console.WriteLine("[CS2 Surf] Executed configuration: server_settings.cfg"); return HookResult.Continue; From 69ee10859589953721a15c1bee21437b74c0155e Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:08:34 +0200 Subject: [PATCH 56/56] ig we zooming slower --- cfg/SurfTimer/server_settings.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/SurfTimer/server_settings.cfg b/cfg/SurfTimer/server_settings.cfg index 2ff13d0..b67b24f 100644 --- a/cfg/SurfTimer/server_settings.cfg +++ b/cfg/SurfTimer/server_settings.cfg @@ -17,8 +17,8 @@ sv_deadtalk 1 sv_full_alltalk 1 // Movement Settings -// some pussy shit = sv_airaccelerate 150 -sv_airaccelerate 2000 +sv_airaccelerate 150 +// sv_airaccelerate 2000 sv_gravity 800 sv_friction 5.2 sv_maxspeed 350