From ee90463ac00db8d976dc97b7c9b04f5fef4cf39c Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 18:12:30 +1100 Subject: [PATCH 1/7] Tidy up player stats classes --- src/ST-Events/TriggerEndTouch.cs | 20 +- src/ST-Events/TriggerStartTouch.cs | 2 +- src/ST-Player/PlayerHUD.cs | 8 +- src/ST-Player/PlayerStats.cs | 330 ---------------------- src/ST-Player/PlayerStats/Checkpoint.cs | 106 +++++++ src/ST-Player/PlayerStats/CurrentRun.cs | 99 +++++++ src/ST-Player/PlayerStats/PersonalBest.cs | 58 ++++ src/ST-Player/PlayerStats/PlayerStats.cs | 66 +++++ 8 files changed, 344 insertions(+), 345 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/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..3701798 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -88,7 +88,7 @@ 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].Checkpoint[0].SaveCurrentRunCheckpoints(player, DB); // Save the Checkpoints PB data + player.Stats.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints 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/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..fef587e --- /dev/null +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -0,0 +1,106 @@ +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; + } + + /// + /// 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 + } +} \ 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..77732ba --- /dev/null +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -0,0 +1,99 @@ +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 `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..61cedaf --- /dev/null +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -0,0 +1,58 @@ +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; + } + + /// + /// 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 + } +} \ 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..50100f6 --- /dev/null +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -0,0 +1,66 @@ +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(); + } +} \ No newline at end of file From 39e44bf8ceab70339b4b352677e82a30b7d51ddf Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 18:20:58 +1100 Subject: [PATCH 2/7] Move Checkpoints load method to PlayerStats --- src/ST-Events/Players.cs | 2 +- src/ST-Events/TriggerStartTouch.cs | 4 +- src/ST-Player/PlayerStats/Checkpoint.cs | 82 ----------------------- src/ST-Player/PlayerStats/CurrentRun.cs | 23 +++++++ src/ST-Player/PlayerStats/PersonalBest.cs | 23 ------- src/ST-Player/PlayerStats/PlayerStats.cs | 82 +++++++++++++++++++++++ 6 files changed, 108 insertions(+), 108 deletions(-) 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/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 3701798..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.ThisRun.SaveCurrentRunCheckpoints(player, DB); // Save this run's checkpoints - 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.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-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index fef587e..30921f5 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -21,86 +21,4 @@ public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float sta EndTouch = endTouch; Attempts = 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 - } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 77732ba..bca549b 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -44,6 +44,29 @@ public void Reset() // 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 diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 61cedaf..181d126 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -32,27 +32,4 @@ public PersonalBest() 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 - } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 50100f6..65dcc39 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -63,4 +63,86 @@ public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, } 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 530a360dc5a9c348590936a89e8f7631a6d3f273 Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 18:51:27 +1100 Subject: [PATCH 3/7] Use info_teleport_destinations for player spawn locations --- src/ST-Map/Map.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 9a026b7..77d4bbe 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -56,12 +56,13 @@ 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); foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) { + 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); + break; } } } @@ -75,15 +76,15 @@ 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); - // Find an info_destination_teleport inside this zone to grab angles from foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) { + this.StageStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); 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 + break; } } } @@ -93,21 +94,20 @@ 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); - // Find an info_destination_teleport inside this zone to grab angles from foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!)) { + this.BonusStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); 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 + break; } } } From 732bc5581f29d51f2755ed7084079122d6729f28 Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 18:58:29 +1100 Subject: [PATCH 4/7] Look for info_teleport_destinations with guideline names --- src/ST-Map/Map.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 77d4bbe..0dc3b92 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -58,7 +58,11 @@ internal Map(string Name, TimerDatabase DB) { 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("map_start") || + teleport.Entity!.Name.Contains("stage1_start") || + teleport.Entity!.Name.Contains("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); @@ -76,13 +80,15 @@ 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) { + 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 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!) || Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == stage)) { - this.StageStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - 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 break; } @@ -99,13 +105,15 @@ internal Map(string Name, TimerDatabase DB) // Bonus start zones else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) { + 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 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!) || Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == bonus)) { - this.BonusStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - 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 break; } From ad8a7b2713f7f3899bd77207a78df0bd49aed710 Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 19:11:04 +1100 Subject: [PATCH 5/7] Fallback if associated info_teleport_destination cannot be found --- src/ST-Map/Map.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 0dc3b92..8e3cfc6 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -56,6 +56,7 @@ internal Map(string Name, TimerDatabase DB) trigger.Entity!.Name.Contains("stage1_start") || trigger.Entity!.Name.Contains("s1_start")) { + bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && @@ -66,9 +67,15 @@ internal Map(string Name, TimerDatabase DB) { 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 @@ -81,7 +88,9 @@ internal Map(string Name, TimerDatabase DB) else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) { 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 && @@ -90,9 +99,15 @@ internal Map(string Name, TimerDatabase DB) 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) @@ -106,7 +121,9 @@ internal Map(string Name, TimerDatabase DB) else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) { 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 && @@ -115,9 +132,15 @@ internal Map(string Name, TimerDatabase DB) 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) From 5355d94881b10fdf63a527081dbcc8a548d47708 Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 19:22:19 +1100 Subject: [PATCH 6/7] Append `spawn_` to info_teleport_destination standards --- src/ST-Map/Map.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 8e3cfc6..0ad0d13 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -61,9 +61,9 @@ internal Map(string Name, TimerDatabase DB) { if (teleport.Entity!.Name != null && (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || - teleport.Entity!.Name.Contains("map_start") || - teleport.Entity!.Name.Contains("stage1_start") || - teleport.Entity!.Name.Contains("s1_start"))) + 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); @@ -94,7 +94,7 @@ internal Map(string Name, TimerDatabase DB) foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && - (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == stage)) + (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.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); @@ -127,7 +127,7 @@ internal Map(string Name, TimerDatabase DB) foreach (CBaseEntity teleport in teleports) { if (teleport.Entity!.Name != null && - (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || Int32.Parse(Regex.Match(teleport.Entity.Name, "[0-9][0-9]?").Value) == bonus)) + (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.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); From 8c4b5f7a08afcddf8becd5e87a192f8682ea22ca Mon Sep 17 00:00:00 2001 From: "Liam Z. Charles" Date: Tue, 9 Jan 2024 19:24:56 +1100 Subject: [PATCH 7/7] Resolve warnings --- src/ST-Player/PlayerStats/CurrentRun.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index bca549b..3ddf620 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -77,15 +77,15 @@ public void SaveMapTime(Player player, TimerDatabase DB, int mapId = 0) // To-do 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; + 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}");