From 2f12703f6d549872e3923e4e1dd1cf8d42bc8750 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:35:12 +0200 Subject: [PATCH 01/52] [DB] Add Transactions (#12) * cs# version bump * add transaction method * save cps with transaction;dot for millis co-pilot = op --- src/ST-DB/DB.cs | 29 +++++++++++++++++++++++++ src/ST-Events/TriggerStartTouch.cs | 4 ++-- src/ST-Player/PlayerHUD.cs | 2 +- src/ST-Player/PlayerStats/CurrentRun.cs | 15 +++++-------- src/SurfTimer.csproj | 2 +- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs index 78ce507..ca58143 100644 --- a/src/ST-DB/DB.cs +++ b/src/ST-DB/DB.cs @@ -76,4 +76,33 @@ public async Task Write(string query) } }); } + + public async Task Transaction(List commands) + { + if (this._db == null) + { + throw new InvalidOperationException("Database connection is not open."); + } + + using (var transaction = await this._db.BeginTransactionAsync()) + { + try + { + foreach (var command in commands) + { + using (var cmd = new MySqlCommand(command, this._db, transaction)) + { + await cmd.ExecuteNonQueryAsync(); + } + } + + await transaction.CommitAsync(); + } + catch + { + await transaction.RollbackAsync(); + throw; // rethrow the exception to be handled by the caller + } + } + } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 3059d4e..4f29736 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -96,10 +96,10 @@ internal HookResult OnTriggerStartTouch(DynamicHook handler) // Add entry in DB for the run if(!player.Timer.IsPracticeMode) { - AddTimer(1.5f, () => { + AddTimer(1.5f, async () => { 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 + await 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-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 4ed6c2b..39ab08e 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}"; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 4609dd1..f6fd2d0 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -75,9 +75,10 @@ INSERT INTO `MapTimes` /// 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 + public async Task SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) { int style = player.Timer.Style; + List commands = new List(); // Loop through the checkpoints and insert/update them in the database for the run foreach (var item in player.Stats.ThisRun.Checkpoint) { @@ -105,11 +106,10 @@ INSERT INTO `Checkpoints` #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($@" + string command = $@" 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`) @@ -117,14 +117,11 @@ INSERT INTO `Checkpoints` 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(); + "; + commands.Add(command); } } - + await DB.Transaction(commands); player.Stats.ThisRun.Checkpoint.Clear(); } } diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 173f3e3..a35d1af 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -13,7 +13,7 @@ - + From 07b9eff165a58a8087d12288d3f0bd8af150c966 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:28:25 +0200 Subject: [PATCH 02/52] small patch --- src/ST-Events/Players.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index a2162a2..a4fe6c5 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -26,7 +26,7 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) repeats = 3; CurrentMap.ReplayBots[i].SetController(controller, repeats); - Server.PrintToChatAll($"{ChatColors.Lime} Loading replay data..."); + Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}Loading replay data..."); AddTimer(2f, () => { if(!CurrentMap.ReplayBots[i].IsPlayable) return; @@ -53,7 +53,7 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> {player.PlayerName} / {player.UserId} / Bot Diff: {player.PawnBotDifficulty}"); #endif - if (player.IsBot || !player.IsValid) // IsBot might be broken so we can check for PawnBotDifficulty which is `-1` for real players + if (player.IsBot || !player.IsValid) { return HookResult.Continue; } @@ -168,7 +168,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo var player = @event.Userid; for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) - if (CurrentMap.ReplayBots[i].IsPlayable && CurrentMap.ReplayBots[i].Controller!.Equals(player)) + if (CurrentMap.ReplayBots[i].IsPlayable && CurrentMap.ReplayBots[i].Controller!.Equals(player) && CurrentMap.ReplayBots[i].Stat_MapTimeID != -1) CurrentMap.ReplayBots[i].Reset(); if (player.IsBot || !player.IsValid) From 958fcf8ec6e5356a5bcae8c968070109db8c4abd Mon Sep 17 00:00:00 2001 From: 1zc Date: Wed, 7 Feb 2024 19:49:58 +1100 Subject: [PATCH 03/52] Change hook methods for OnTriggerStart/EndTouch --- src/ST-Events/TriggerEndTouch.cs | 10 ++++++---- src/ST-Events/TriggerStartTouch.cs | 10 ++++++---- src/SurfTimer.cs | 11 +++++++---- src/SurfTimer.csproj | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index b98ed13..a8cd4ca 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -1,17 +1,19 @@ using System.Text.RegularExpressions; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; -using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; namespace SurfTimer; public partial class SurfTimer { // Trigger end touch handler - CBaseTrigger_EndTouchFunc - internal HookResult OnTriggerEndTouch(DynamicHook handler) + // internal HookResult OnTriggerEndTouch(DynamicHook handler) + internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - CBaseTrigger trigger = handler.GetParam(0); - CBaseEntity entity = handler.GetParam(1); + // CBaseTrigger trigger = handler.GetParam(0); + CBaseTrigger trigger = new CBaseTrigger(caller.Handle); + // CBaseEntity entity = handler.GetParam(1); + CBaseEntity entity = new CBaseEntity(activator.Handle); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); 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 { diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 4f29736..c0af249 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -1,7 +1,6 @@ using System.Text.RegularExpressions; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; -using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API; namespace SurfTimer; @@ -9,10 +8,13 @@ namespace SurfTimer; public partial class SurfTimer { // Trigger start touch handler - CBaseTrigger_StartTouchFunc - internal HookResult OnTriggerStartTouch(DynamicHook handler) + // internal HookResult OnTriggerStartTouch(DynamicHook handler) + internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - CBaseTrigger trigger = handler.GetParam(0); - CBaseEntity entity = handler.GetParam(1); + // CBaseTrigger trigger = handler.GetParam(0); + CBaseTrigger trigger = new CBaseTrigger(caller.Handle); + // CBaseEntity entity = handler.GetParam(1); + CBaseEntity entity = new CBaseEntity(activator.Handle); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); 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 { diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 846a225..b1344df 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -122,9 +122,12 @@ public override void Load(bool hotReload) // Tick listener RegisterListener(OnTick); - // StartTouch Hook - VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(OnTriggerStartTouch, HookMode.Post); - // EndTouch Hook - VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(OnTriggerEndTouch, HookMode.Post); + // StartTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 + // VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(OnTriggerStartTouch, HookMode.Post); + // EndTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 + // VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(OnTriggerEndTouch, HookMode.Post); + + HookEntityOutput("trigger_multiple", "OnStartTouch", OnTriggerStartTouch); + HookEntityOutput("trigger_multiple", "OnEndTouch", OnTriggerEndTouch); } } diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index a35d1af..87e253f 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -13,7 +13,7 @@ - + From c8be7deb49faf201fcef02a5aef6cb8e6faf0086 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:24:33 +0200 Subject: [PATCH 04/52] don't start timer after `!s ` --- src/ST-Events/TriggerEndTouch.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index a8cd4ca..65ddfc4 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -50,8 +50,11 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti } // MAP START ZONE - player.Timer.Start(); - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_RUN; + if (!player.Timer.IsStageMode) + { + player.Timer.Start(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_RUN; + } /* Revisit // Wonky Prespeed check From c06c6cbe113494d371a672cba8c6ce917629ba1c Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:39:21 +0200 Subject: [PATCH 05/52] reset `IsStageMode` --- src/ST-Player/PlayerTimer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 4d7da97..5878205 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -38,6 +38,7 @@ public void Reset() this.Checkpoint = 0; this.IsPaused = false; this.IsPracticeMode = false; + this.IsStageMode = false; this.CurrentRunData.Reset(); } From dfbdd4db8256f123eda6afd2b63043799188d41a Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 4 Nov 2024 00:39:11 +0200 Subject: [PATCH 06/52] hi Summary of completed items: - Resolved hibernation issues with timers. - Implemented async queries and methods with multiple database connections. - Moved PlayerProfile and Map queries to respective classes. - Added ReplayFrame Situations for situation-specific replay handling. - Corrected Map Replays reload and ReplayFrameSituations usage. - Updated Checkpoints comparison messages for correct display. - Adjusted Stage Replays tracking, saving, and reloading during map runs. - Fixed checkpoint saving on first map run. - Refer to TODO for more information --- README.md | 20 +- TODO | 67 ++ src/ST-API/Api.cs | 41 + src/ST-API/Comms.cs | 85 ++ src/{ST-UTILS => ST-API}/ConVar.cs | 2 +- .../JsonConverters.cs} | 37 +- src/{ST-UTILS => ST-API}/Schema.cs | 0 src/ST-API/Structures.cs | 87 ++ src/ST-Commands/MapCommands.cs | 18 +- src/ST-Commands/PlayerCommands.cs | 453 +++++++--- src/ST-DB/DB.cs | 163 ++-- src/ST-Events/Players.cs | 280 +++--- src/ST-Events/Tick.cs | 56 +- src/ST-Events/TriggerEndTouch.cs | 209 +++-- src/ST-Events/TriggerStartTouch.cs | 660 ++++++++++++--- src/ST-Map/Map.cs | 798 +++++++++++++----- src/ST-Player/Player.cs | 2 +- src/ST-Player/PlayerHUD.cs | 151 ++-- src/ST-Player/PlayerProfile.cs | 129 ++- src/ST-Player/PlayerStats/Checkpoint.cs | 4 +- src/ST-Player/PlayerStats/CurrentRun.cs | 198 +++-- src/ST-Player/PlayerStats/PersonalBest.cs | 202 ++++- src/ST-Player/PlayerStats/PlayerStats.cs | 320 ++++--- src/ST-Player/PlayerTimer.cs | 8 +- src/ST-Player/Replay/ReplayFrame.cs | 45 +- src/ST-Player/Replay/ReplayManager.cs | 97 +++ src/ST-Player/Replay/ReplayPlayer.cs | 176 ++-- src/ST-Player/Replay/ReplayRecorder.cs | 257 +++++- src/ST-UTILS/Config.cs | 206 +++++ src/SurfTimer.cs | 51 +- src/SurfTimer.csproj | 6 +- src/bin/MaxMind.Db.dll | Bin 0 -> 44544 bytes src/bin/MaxMind.GeoIP2.dll | Bin 0 -> 58368 bytes src/bin/MySqlConnector.dll | Bin 0 -> 838504 bytes 34 files changed, 3698 insertions(+), 1130 deletions(-) create mode 100644 TODO create mode 100644 src/ST-API/Api.cs create mode 100644 src/ST-API/Comms.cs rename src/{ST-UTILS => ST-API}/ConVar.cs (91%) rename src/{ST-UTILS/Compression.cs => ST-API/JsonConverters.cs} (83%) rename src/{ST-UTILS => ST-API}/Schema.cs (100%) create mode 100644 src/ST-API/Structures.cs create mode 100644 src/ST-Player/Replay/ReplayManager.cs create mode 100644 src/ST-UTILS/Config.cs create mode 100644 src/bin/MaxMind.Db.dll create mode 100644 src/bin/MaxMind.GeoIP2.dll create mode 100644 src/bin/MySqlConnector.dll diff --git a/README.md b/README.md index 4663617..3dcf459 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Bold & Italics = being worked on. - [X] Base database class implementation - [ ] Maps - [X] Implement map info object (DB) - - [ ] Zoning + - [X] Zoning - [X] Hook zones from map triggers - [X] Map start/end zones - [X] Stage zones @@ -23,15 +23,15 @@ Bold & Italics = being worked on. - [X] Support for stages/checkpoints - [X] Hook to their start/end zones - [X] Save/Compare checkpoint times - - [ ] Save Stage times + - [X] Save Stage times - [X] Support for bonuses - [X] Hook to their start/end zones - - [ ] Save Bonus times + - [X] Save Bonus times - [X] Start/End trigger touch hooks - [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping - - [X] _**Support for stages (`/rs`, teleporting with `/s`)**_ - - [ ] _**Support for bonuses (`/rs`, teleporting with `/b #`)**_ - - [ ] _**Start/End touch hooks implemented for all zones**_ + - [X] Support for stages (`/rs`, teleporting with `/s`) + - [X] Support for bonuses (`/rs`, teleporting with `/b #`) + - [X] _**Start/End touch hooks implemented for all zones**_ - [ ] Surf configs - [X] Server settings configuration - [ ] Plugin configuration @@ -40,10 +40,10 @@ Bold & Italics = being worked on. - [X] Base timer class implementation - [X] Base timer HUD implementation - [X] Prespeed measurement and display - - [ ] Save/load times + - [X] Save/load times - [x] Map times - [x] Checkpoint times - - [ ] Stage times + - [X] Stage times - [ ] Bonus times - [X] Practice Mode implementation - [ ] Announce records to Discord @@ -57,11 +57,11 @@ Bold & Italics = being worked on. - [x] Replays - Not tracking Stage/Bonus times but Replay functionality for them is there - [x] Personal Best - [x] Map Record - - [ ] Stage Record + - [X] Stage Record - [ ] Bonus Record - [x] World Record - [X] Map Record - - [ ] Stage Record + - [X] Stage Record - [ ] Bonus Record - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) diff --git a/TODO b/TODO new file mode 100644 index 0000000..41eedc5 --- /dev/null +++ b/TODO @@ -0,0 +1,67 @@ +- Re-add the MySQL queries in code and make it switch between API and DB functions +- Map Time is NOT being saved with API +- Make configs generate themselves inside the `./configs/plugins/...` folder +- Fix loading MapTimes for each type (stage, bonus, maps) + - API + - DB +- Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin +- Change `DB_QUERY_PB_GET_RUNTIME` query in API + + +- Double check the Start/End velocities for End Zone,Checkpoints, Stages, Bonuses + + Maps + + Checkpoints + - Stages + - Try and get the Start/End velocity values from the Checkpoint object in Map Run + - Bonuses + - Stage times are being saved with the wrong values for velocities + + +- Using `!spec` from any start zone and going back to CT starts your timer ++ Trim different type of replays based on situations + + Map Replays + + Stage Replays during Map runs + + Stage Replays in `IsStageMode` + - Bonus Replays + + + +- Replay bots are not being spawned on `Linear` maps + +# Done ++ Hibernation fucks up the creation of `CurrentMap`, if nobody has joined the first map it was on and you change to another map, server segfaults. I guess a query is being held?? :think: + + Solution is to use `Server.NextWorldUpdate` as hibernation stops all `Timer`s :peepoHappy: ++ Introduce `async` for queries and some methods ++ Make Database spawn a new connection for each query to avoid `Connection in use` error + + Add notes to apply `using` blocks for certain methods for proper operation ++ Move **PlayerProfile** queries to the respective class ++ Move **Map** queries to the respective class ++ Total Completions for a map are wrong in HUD ++ Add `Situation` to each `ReplayFrame` and load it properly + + Fix `STAGE_EXIT_ZONE` situations - not triggered (logic in `TriggerEndTouch` is most likely bad) ++ Fix Map Replays + + Reload after setting a new WR + + Load if no replay existed before a new run + + Use the `ReplayFrameSituation` for determining where to cut replays + + Do not cut replay as soon as we enter end zone (determined by the AddTimer amount for `SaveMapTime`) ++ Double check the Checkpoints comparison messages + + 1st run: [CS2 Surf] CP [1]: 02.468 (1083) [PB: N/A (N/A) | WR: N/A (N/A)] + + 2nd run: [CS2 Surf] CP [1]: 02.421 (1128) [PB: +02.390 (+46) | WR: +02.390 (+46)] ++ Fix Stage replays + + Track the times even during Map Run on staged maps + + Include the pre-strafe + + Load all of them instead of overwriting and cycle (maybe make StageWR a Dictionary) + + Reload them after setting a new Stage WR + + Load if no replay existed before + + Use the `ReplayFrameSituation` for determining where to cut replays + + Save stage replays when `IsStageMode` is enabled + + Use seperate method + + Save the correct amount of `Ticks` for stage runs during map run + + Use seperate method + + Save the last stage time - triggered in `END_ZONE_ENTER` + + `IsStageMode` + + Use seperate method + + Map run + + Use seperate method + + When loading new stage replays the cycling of them does not work ++ First ever map run does not save Checkpoints, check if we wait to retrieve the MapTime_ID before trying to insert Checkpoints diff --git a/src/ST-API/Api.cs b/src/ST-API/Api.cs new file mode 100644 index 0000000..78a9fbe --- /dev/null +++ b/src/ST-API/Api.cs @@ -0,0 +1,41 @@ +using System.Net.Http.Json; + +namespace SurfTimer; + +internal class ApiCall +{ + public static async Task Api_Save_Stage_MapTime(Player player) + { + // This is a trick to record the time before the player exits the start zone + int last_exit_tick = player.ReplayRecorder.LastExitTick(); + int last_enter_tick = player.ReplayRecorder.LastEnterTick(); + + // player.Controller.PrintToChat($"CS2 Surf DEBUG >> OnTriggerStartTouch -> Last Exit Tick: {last_exit_tick} | Current Frame: {player.ReplayRecorder.Frames.Count}"); + + int stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this + int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; + + int tt = -1; + if (last_exit_tick - last_enter_tick > 2 * 64) + tt = last_exit_tick - 2 * 64; + else + tt = last_enter_tick; + + API_CurrentRun stage_time = new() + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = player.Timer.Style, + type = 2, + stage = player.Timer.Stage - 1, + run_time = stage_run_time, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) + + }; + + await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_CR_SAVE_STAGE_TIME, stage_time); + // player.Stats.LoadStageTime(player); + // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD + } +} \ No newline at end of file diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs new file mode 100644 index 0000000..82d3f73 --- /dev/null +++ b/src/ST-API/Comms.cs @@ -0,0 +1,85 @@ +using System.Net.Http.Json; + +namespace SurfTimer; + +internal class ApiMethod +{ + private ApiMethod() { } + + private static readonly HttpClient _client = new(); + private static readonly string base_addr = Config.ApiUrl; + + public static async Task GET(string url) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task GET -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.GetAsync(uri); + + try + { + System.Console.WriteLine($"[API] GET {url} => {response.StatusCode}"); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + Exception exception = new Exception("[API] GET - No data found"); + throw exception; + } + + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } + + public static async Task POST(string url, T body) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task POST -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.PostAsJsonAsync(uri, body); + + try + { + System.Console.WriteLine($"[API] POST {url} => {response.StatusCode}"); + response.EnsureSuccessStatusCode(); // BAD BAD BAD + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } + + public static async Task PUT(string url, T body) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task PUT -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.PutAsJsonAsync(uri, body); + + try + { + System.Console.WriteLine($"[API] PUT {url} => {response.StatusCode}"); + response.EnsureSuccessStatusCode(); // BAD BAD BAD + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } +} \ No newline at end of file diff --git a/src/ST-UTILS/ConVar.cs b/src/ST-API/ConVar.cs similarity index 91% rename from src/ST-UTILS/ConVar.cs rename to src/ST-API/ConVar.cs index 55206ba..c19eefc 100644 --- a/src/ST-UTILS/ConVar.cs +++ b/src/ST-API/ConVar.cs @@ -2,7 +2,7 @@ namespace SurfTimer; -internal class ConVarHelper +internal static class ConVarHelper { public static void RemoveCheatFlagFromConVar(string cv_name) { diff --git a/src/ST-UTILS/Compression.cs b/src/ST-API/JsonConverters.cs similarity index 83% rename from src/ST-UTILS/Compression.cs rename to src/ST-API/JsonConverters.cs index 25ff2b2..0e50238 100644 --- a/src/ST-UTILS/Compression.cs +++ b/src/ST-API/JsonConverters.cs @@ -6,6 +6,28 @@ namespace SurfTimer; +internal static class JsonSelp +{ + public static Dictionary ConstructJsonDictFromString(string str) + { + using (JsonDocument document = JsonDocument.Parse(str)) + { + // Access the root element + JsonElement root = document.RootElement; + + // Create a dictionary to store the parsed JSON data + Dictionary dictionary = new Dictionary(); + + foreach (JsonProperty property in root.EnumerateObject()) + { + dictionary[property.Name] = property.Value.ToString(); + } + + return dictionary; + } + } +} + internal class VectorConverter : JsonConverter { public override Vector Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -102,7 +124,7 @@ public override void Write(Utf8JsonWriter writer, QAngle value, JsonSerializerOp } } -internal class Compressor +internal static class Compressor { public static string Decompress(string input) { @@ -126,22 +148,20 @@ public static byte[] Decompress(byte[] input) source.Read(lengthBytes, 0, 4); var length = BitConverter.ToInt32(lengthBytes, 0); - using (var decompressionStream = new GZipStream(source, - CompressionMode.Decompress)) + 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; + totalRead += bytesRead; } - return result; } } } - public static byte[] Compress(byte[] input) + public static byte[] Compress(byte[] input) { using (var result = new MemoryStream()) { @@ -158,4 +178,9 @@ public static byte[] Compress(byte[] input) return result.ToArray(); } } + + internal static string Decompress(byte v) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/ST-UTILS/Schema.cs b/src/ST-API/Schema.cs similarity index 100% rename from src/ST-UTILS/Schema.cs rename to src/ST-API/Schema.cs diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs new file mode 100644 index 0000000..2b638df --- /dev/null +++ b/src/ST-API/Structures.cs @@ -0,0 +1,87 @@ +namespace SurfTimer; + +// Map Info structure +internal class API_PostResponseData +{ + public int inserted { get; set; } + public float xtime { get; set; } + public int last_id { get; set; } + public int? trx { get; set; } +} + +internal class API_Checkpoint +{ + public int cp { get; set; } + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int end_touch { get; set; } + public int attempts { get; set; } +} + +internal class API_CurrentRun +{ + public int player_id { get; set; } + public int map_id { get; set; } + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public List? checkpoints { get; set; } = null; + public string replay_frames { get; set; } = ""; // This needs to be checked touroughly + public int? run_date { get; set; } = null; +} + +internal class API_MapInfo +{ + public int id { get; set; } = 0; + public string name { get; set; } = "N/A"; + public string author { get; set; } = "Unknown"; + public int tier { get; set; } = 0; + public int stages { get; set; } = 0; + public int bonuses { get; set; } = 0; + public int ranked { get; set; } = 0; + public int? date_added { get; set; } = null; + public int? last_played { get; set; } = null; +} + +internal class API_MapTime +{ + public int id { get; set; } + public int player_id { get; set; } + public int map_id { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int run_date { get; set; } + public string replay_frames { get; set; } = ""; // This needs to be checked touroughly + public List? checkpoints { get; set; } = null; + public string name { get; set; } = "N/A"; +} + +internal class API_PlayerSurfProfile +{ + public string name { get; set; } = "N/A"; + public int steam_id { get; set; } + public string country { get; set; } = "N/A"; + public int join_date { get; set; } + public int last_seen { get; set; } + public int connections { get; set; } +} \ No newline at end of file diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 4bebc09..671a15e 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -19,11 +19,23 @@ public void MapTier(CCSPlayerController? player, CommandInfo command) if (player == null) return; + string msg = $"{Config.PluginPrefix} {CurrentMap.Name} - Tier {ChatColors.Green}{CurrentMap.Tier}{ChatColors.Default} - Author {ChatColors.Yellow}{CurrentMap.Author}{ChatColors.Default} - Added {ChatColors.Yellow}{DateTimeOffset.FromUnixTimeSeconds(CurrentMap.DateAdded).DateTime.ToString("dd.MM.yyyy HH:mm")}{ChatColors.Default}"; + if (CurrentMap.Stages > 1) - player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Staged {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}"); + { + msg = string.Concat(msg, " - ", $"Stages {ChatColors.Yellow}{CurrentMap.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; + { + msg = string.Concat(msg, " - ", $"Linear {ChatColors.Yellow}{CurrentMap.TotalCheckpoints} Checkpoints{ChatColors.Default}"); + } + + if (CurrentMap.Bonuses > 0) + { + msg = string.Concat(msg, " - ", $"Bonuses {ChatColors.Yellow}{CurrentMap.Bonuses}"); + } + + player.PrintToChat(msg); } [ConsoleCommand("css_triggers", "List all valid zone triggers in the map.")] diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 3e51527..df2215c 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -1,7 +1,6 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Utils; @@ -13,75 +12,167 @@ public partial class SurfTimer [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerReset(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) return; + Player oPlayer = playerList[player.UserId ?? 0]; + if (oPlayer.ReplayRecorder.IsSaving) + { + player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); + return; + } // 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))); - return; } [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you're in.")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + if (oPlayer.ReplayRecorder.IsSaving) + { + player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); return; + } // 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))); - else // Reset back to map start - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0, 0, 0), new Vector(0, 0, 0))); - return; + + if (SurfPlayer.Timer.IsBonusMode) + { + if (SurfPlayer.Timer.Bonus != 0 && CurrentMap.BonusStartZone[SurfPlayer.Timer.Bonus] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[SurfPlayer.Timer.Bonus], CurrentMap.BonusStartZoneAngles[SurfPlayer.Timer.Bonus], 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))); + } + + else + { + 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))); + } } [ConsoleCommand("css_s", "Teleport to a stage")] + [ConsoleCommand("css_stage", "Teleport to a stage")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) return; - int stage = Int32.Parse(command.ArgByIndex(1)) - 1; - if (stage > CurrentMap.Stages - 1 && CurrentMap.Stages > 0) - stage = CurrentMap.Stages - 1; + int stage; + try + { + stage = Int32.Parse(command.ArgByIndex(1)); + } + catch (System.Exception) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + return; + } // Must be 1 argument - if (command.ArgCount < 2 || stage < 0) + if (command.ArgCount < 2 || stage <= 0) { - #if DEBUG - player.PrintToChat($"CS2 Surf DEBUG >> css_s >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); - #endif +#if DEBUG + player.PrintToChat($"CS2 Surf DEBUG >> css_stage >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); +#endif - player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.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."); + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no stages."); + return; + } + + else if (stage > CurrentMap.Stages) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided, this map has {ChatColors.Green}{CurrentMap.Stages} stages."); return; } if (CurrentMap.StageStartZone[stage] != new Vector(0, 0, 0)) { - if (stage == 0) + playerList[player.UserId ?? 0].Timer.Reset(); + + if (stage == 1) + { Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, CurrentMap.StartZoneAngles, new Vector(0, 0, 0))); + } else + { + playerList[player.UserId ?? 0].Timer.Stage = stage; 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; + 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. } else - player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); + } + + [ConsoleCommand("css_b", "Teleport to a bonus")] + [ConsoleCommand("css_bonus", "Teleport to a bonus")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) + { + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + return; + + int bonus; + + // Check for argument count + if (command.ArgCount < 2) + { + if (CurrentMap.Bonuses > 0) + bonus = 1; + else + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!bonus "); + return; + } + } + + else + bonus = Int32.Parse(command.ArgByIndex(1)); + + if (CurrentMap.Bonuses <= 0) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no bonuses."); + return; + } + + else if (bonus > CurrentMap.Bonuses) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided, this map has {ChatColors.Green}{CurrentMap.Bonuses} bonuses."); + return; + } + + if (CurrentMap.BonusStartZone[bonus] != new Vector(0, 0, 0)) + { + playerList[player.UserId ?? 0].Timer.Reset(); + playerList[player.UserId ?? 0].Timer.IsBonusMode = true; + + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[bonus], CurrentMap.BonusStartZoneAngles[bonus], new Vector(0, 0, 0))); + } + + else + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided. Usage: {ChatColors.Green}!bonus "); } [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] @@ -93,85 +184,247 @@ public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo comma player.ChangeTeam(CsTeam.Spectator); } + [ConsoleCommand("css_rank", "Show the current rank of the player for the style they are in")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void PlayerRank(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + player.PrintToChat($"{Config.PluginPrefix} Your current rank for {ChatColors.Gold}{CurrentMap.Name}{ChatColors.Default} is {ChatColors.Green}{playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank}{ChatColors.Default} out of {ChatColors.Yellow}{playerList[player.UserId ?? 0].CurrMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]}"); + } + + [ConsoleCommand("css_testx", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void TestCmd(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + // player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Testing 'PB_LoadMapTimeData'"); + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); + player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); + player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); + player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); + player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); + player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); + player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); + player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].Ticks)}"); + // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Orange}====== MAP ======"); + player.PrintToChat($"{Config.PluginPrefix} Map ID: {ChatColors.Green}{CurrentMap.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Map Name: {ChatColors.Green}{CurrentMap.Name}"); + player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); + player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); + player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Purple}====== REPLAYS ======"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.Frames.Count: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.IsRecording: {ChatColors.Green}{oPlayer.ReplayRecorder.IsRecording}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.IsPlayable}"); + + /* + for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) + { + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].IsPlayable}"); + } + */ + + /* + for (int i = 0; i < CurrentMap.ReplayManager.MapWR.Frames.Count; i++) + { + ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + player.PrintToChat($"Start Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.PrintToChat($"Start Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.PrintToChat($"Stage Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.PrintToChat($"Stage Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.PrintToChat($"Checkpoint Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.PrintToChat($"Checkpoint Exit: {i} | Situation {x.Situation}"); + break; + } + } + */ + // for (int i = 0; i < CurrentMap.ReplayManager.MapWR.MapSituations.Count; i++) + // { + // ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + // switch (x.Situation) + // { + // case ReplayFrameSituation.START_ZONE_ENTER: + // player.PrintToChat($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.START_ZONE_EXIT: + // player.PrintToChat($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_ENTER: + // player.PrintToChat($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_EXIT: + // player.PrintToChat($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + // player.PrintToChat($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + // player.PrintToChat($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // } + // } + + // player.PrintToChat($"{Config.PluginPrefix} IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} Player.IsSpectating: {ChatColors.Green}{oPlayer.IsSpectating(CurrentMap.ReplayManager.MapWR.Controller!)}"); + // player.PrintToChat($"{Config.PluginPrefix} Name & MapTimeID: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordPlayerName} {CurrentMap.ReplayManager.MapWR.MapTimeID}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayCurrentRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.ReplayCurrentRunTime}"); + // player.PrintToChat($"{Config.PluginPrefix} RepeatCount: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RepeatCount}"); + // player.PrintToChat($"{Config.PluginPrefix} IsReplayOutsideZone: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsReplayOutsideZone}"); + // player.PrintToChat($"{Config.PluginPrefix} CurrentFrameTick: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.CurrentFrameTick}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayRecorder.Frames.Length: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + + // if (CurrentMap.ReplayManager.StageWR != null) + // { + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.MapTimeID - Stage: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.MapTimeID} - {CurrentMap.ReplayManager.StageWR.Stage}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsEnabled: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsEnabled}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPaused: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPaused}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.Controller Null?: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.Controller == null}"); + // } + } + /* ######################### - Reaplay Commands + Replay 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) + if (player == null || player.Team != CsTeam.Spectator) return; - foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + 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) + [ConsoleCommand("css_rbplay", "Start all replays from the start")] + public void PlayReplay(CCSPlayerController? player, CommandInfo command) { - if(player == null || player.Team != CsTeam.Spectator) + if (player == null || player.Team != CsTeam.Spectator) return; - foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + Player oPlayer = playerList[player.UserId ?? 0]; + CurrentMap.ReplayManager.MapWR.ResetReplay(); + CurrentMap.ReplayManager.MapWR.Start(); + + CurrentMap.ReplayManager.StageWR?.ResetReplay(); + CurrentMap.ReplayManager.StageWR?.Start(); + + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + if (!rb.IsPlayable || !rb.IsPlaying || !oPlayer.IsSpectating(rb.Controller!)) continue; - - rb.FrameTickIncrement *= -1; + + rb.Start(); } } - [ConsoleCommand("css_pbreplay", "Allows for replay of player's PB")] - public void PbReplay(CCSPlayerController? player, CommandInfo command) + [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) + if (player == null || player.Team != CsTeam.Spectator) return; - int maptime_id = playerList[player!.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].ID; - if (command.ArgCount > 1) + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - try - { - maptime_id = int.Parse(command.ArgByIndex(1)); - } - catch {} - } + if (!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + continue; - 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; - } + rb.FrameTickIncrement *= -1; } - - CurrentMap.ReplayBots = CurrentMap.ReplayBots.Prepend(new ReplayPlayer() { - Stat_MapTimeID = maptime_id, - Stat_Prefix = "PB" - }).ToList(); - - Server.NextFrame(() => { - Server.ExecuteCommand($"bot_quota {CurrentMap.ReplayBots.Count}"); - }); } - /* + // [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($"{Config.PluginPrefix} {ChatColors.Red}No time was found"); + // return; + // } + + // for(int i = 0; i < CurrentMap.ReplayBots.Count; i++) + // { + // if(CurrentMap.ReplayBots[i].MapTimeID == maptime_id) + // { + // player.PrintToChat($"{Config.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 ######################## @@ -179,13 +432,13 @@ 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)) + 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"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Cannot save location while not in run"); return; } @@ -193,74 +446,80 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) var player_angle = p.Controller.PlayerPawn.Value!.EyeAngles; var player_velocity = p.Controller.PlayerPawn.Value!.AbsVelocity; - p.SavedLocations.Add(new SavelocFrame { + 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.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"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(!p.Timer.IsRunning) + if (!p.Timer.IsRunning) p.Timer.Start(); if (!p.Timer.IsPracticeMode) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Timer now on practice"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Timer now on practice"); p.Timer.IsPracticeMode = true; } - if(command.ArgCount > 1) + if (command.ArgCount > 1) try { int tele_n = int.Parse(command.ArgByIndex(1)); if (tele_n < p.SavedLocations.Count) p.CurrentSavedLocation = tele_n; } - catch { } + catch + { + Exception exception = new("sum ting wong"); + throw exception; + } SavelocFrame location = p.SavedLocations[p.CurrentSavedLocation]; - Server.NextFrame(() => { + 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}"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(p.CurrentSavedLocation == 0) + if (p.CurrentSavedLocation == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at first location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at first location"); } else { @@ -269,26 +528,26 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(p.CurrentSavedLocation == p.SavedLocations.Count-1) + if (p.CurrentSavedLocation == p.SavedLocations.Count - 1) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at last location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at last location"); } else { @@ -297,6 +556,6 @@ public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.PluginPrefix} Teleported #{p.CurrentSavedLocation}"); } } \ No newline at end of file diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs index ca58143..1d6eea7 100644 --- a/src/ST-DB/DB.cs +++ b/src/ST-DB/DB.cs @@ -1,108 +1,123 @@ -namespace SurfTimer; +using MySqlConnector; -using System.Runtime.CompilerServices; -using CounterStrikeSharp.API; -using MySqlConnector; // https://dev.mysql.com/doc/connector-net/en/connector-net-connections-string.html +namespace SurfTimer; -// This will have functions for DB access and query sending internal class TimerDatabase { - private readonly MySqlConnection? _db; - private readonly string _connString = string.Empty; + private readonly string _connString; - public TimerDatabase() + public TimerDatabase(string connectionString) { - // Null'd + _connString = connectionString; } - - public TimerDatabase(string host, string database, string user, string password, int port, int timeout) + public void Dispose() { - this._connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; - this._db = new MySqlConnection(this._connString); - this._db.Open(); + Close(); } public void Close() { - if (this._db != null) - this._db!.Close(); + // Not needed } - public async Task Query(string query) + /// + /// Spawns a new connection to the database. + /// + /// DB Connection + private MySqlConnection GetConnection() { - return await Task.Run(async () => + var connection = new MySqlConnection(_connString); + try { - try - { - if (this._db == null) - { - throw new InvalidOperationException("Database connection is not open."); - } - - MySqlCommand cmd = new(query, this._db); - MySqlDataReader reader = await cmd.ExecuteReaderAsync(); + connection.Open(); + } + catch (MySqlException mysqlEx) // Specifically catch MySQL-related exceptions + { + Console.WriteLine($"[CS2 Surf] MySQL error when connecting: {mysqlEx.Message}"); + throw new InvalidOperationException("Could not establish a connection to the database.", mysqlEx); // Wrap the original exception with additional context + } + catch (System.Exception ex) // Catch all other exceptions + { + Console.WriteLine($"[CS2 Surf] General error when connecting to the database: {ex.Message}"); + throw; // Re-throw the exception without wrapping it + } - return reader; - } - catch (Exception ex) - { - Console.WriteLine($"Error executing query: {ex.Message}"); - throw; - } - }); + return connection; } - public async Task Write(string query) + /// + /// Always encapsulate the block with `using` when calling this method. + /// That way we ensure the proper disposal of the `MySqlDataReader` when we are finished with it. + /// + /// SELECT query to execute + public async Task QueryAsync(string query) { - return await Task.Run(async () => + try { - try - { - if (this._db == null) - { - throw new InvalidOperationException("Database connection is not open."); - } - - MySqlCommand cmd = new(query, this._db); - int rowsAffected = await cmd.ExecuteNonQueryAsync(); - - return rowsAffected; - } - catch (Exception ex) - { - Console.WriteLine($"Error executing write operation: {ex.Message}"); - throw; - } - }); + var connection = GetConnection(); + var cmd = new MySqlCommand(query, connection); + return await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.CloseConnection); + } + catch (Exception ex) + { + Console.WriteLine($"Error executing query {query}: {ex.Message}"); + throw; + } } - public async Task Transaction(List commands) + /// + /// Automatically disposes of the connection and command are disposed of after usage. + /// No need to encapsulate in `using` block. + /// + /// INSERT/UPDATE query to execute + public async Task WriteAsync(string query) { - if (this._db == null) + try { - throw new InvalidOperationException("Database connection is not open."); + using var connection = GetConnection(); + using var cmd = new MySqlCommand(query, connection); + return await cmd.ExecuteNonQueryAsync(); } - - using (var transaction = await this._db.BeginTransactionAsync()) + catch (Exception ex) { - try - { - foreach (var command in commands) - { - using (var cmd = new MySqlCommand(command, this._db, transaction)) - { - await cmd.ExecuteNonQueryAsync(); - } - } + Console.WriteLine($"Error executing write operation {query}: {ex.Message}"); + throw; + } + } - await transaction.CommitAsync(); - } - catch + /// + /// Begins a transaction and executes it on the database. + /// Used for inputting `Checkpoints` data after a run has been finished. + /// No need to encapsulate in a `using` block, method disposes of connection and data itself. + /// + /// INSERT/UPDATE queries to execute + public async Task TransactionAsync(List commands) + { + // Create a new connection and open it + using var connection = GetConnection(); + + // Begin a transaction on the connection + using var transaction = await connection.BeginTransactionAsync(); + + try + { + // Execute each command within the transaction + foreach (var commandText in commands) { - await transaction.RollbackAsync(); - throw; // rethrow the exception to be handled by the caller + using var cmd = new MySqlCommand(commandText, connection, transaction); + await cmd.ExecuteNonQueryAsync(); } + + // Commit the transaction + await transaction.CommitAsync(); + } + catch + { + // Roll back the transaction if an error occurs + await transaction.RollbackAsync(); + throw; } + // The connection and transaction are disposed here } } diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index a4fe6c5..283ce2f 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -13,34 +13,75 @@ public partial class SurfTimer public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) { var controller = @event.Userid; - if(!controller.IsValid || !controller.IsBot) + if (!controller.IsValid || !controller.IsBot || CurrentMap.ReplayManager.IsControllerConnectedToReplayPlayer(controller)) return HookResult.Continue; - for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) + // Set the controller for the MapWR bot + if (!CurrentMap.ReplayManager!.MapWR.IsPlayable) { - if(CurrentMap.ReplayBots[i].IsPlayable) - continue; - - int repeats = -1; - if(CurrentMap.ReplayBots[i].Stat_Prefix == "PB") - repeats = 3; - - CurrentMap.ReplayBots[i].SetController(controller, repeats); - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}Loading replay data..."); - AddTimer(2f, () => { - if(!CurrentMap.ReplayBots[i].IsPlayable) - return; - - CurrentMap.ReplayBots[i].Controller!.RemoveWeapons(); - - CurrentMap.ReplayBots[i].LoadReplayData(DB!); - - CurrentMap.ReplayBots[i].Start(); + CurrentMap.ReplayManager.MapWR.SetController(controller, -1); + CurrentMap.ReplayManager.MapWR.LoadReplayData(); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.MapWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.MapWR.Start(); + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + + return HookResult.Continue; + } + + // Set the controller for the StageWR bot + if (CurrentMap.ReplayManager.StageWR != null && !CurrentMap.ReplayManager.StageWR.IsPlayable) + { + CurrentMap.ReplayManager.StageWR.SetController(controller, 3); + CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.StageWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.StageWR.Start(); + CurrentMap.ReplayManager.StageWR.FormatBotName(); + }); + + return HookResult.Continue; + } + + // Spawn the BonusWR bot + if (CurrentMap.ReplayManager.BonusWR != null && !CurrentMap.ReplayManager.BonusWR.IsPlayable) + { + CurrentMap.ReplayManager.BonusWR.SetController(controller, 3); + CurrentMap.ReplayManager.BonusWR.LoadReplayData(); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.BonusWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.BonusWR.Start(); + CurrentMap.ReplayManager.BonusWR.FormatBotName(); }); - + return HookResult.Continue; } + // // Spawn the CustomReplays bot (for PB replays?) - T + // CurrentMap.ReplayManager.CustomReplays.ForEach(replay => + // { + // if (!replay.IsPlayable) + // { + // replay.SetController(controller, 3); + // replay.LoadReplayData(); + + // AddTimer(1.5f, () => { + // replay.Controller!.RemoveWeapons(); + // replay.Start(); + // replay.FormatBotName(); + // }); + + // return; + // } + // }); + return HookResult.Continue; } @@ -48,118 +89,46 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) 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) + string name = player.PlayerName; + string country = "XX"; + + // GeoIP + // Check if the IP is private before attempting GeoIP lookup + string ipAddress = player.IpAddress!.Split(":")[0]; + if (!IsPrivateIP(ipAddress)) { - return HookResult.Continue; + DatabaseReader geoipDB = new(Config.PluginPath + "data/GeoIP/GeoLite2-Country.mmdb"); + country = geoipDB.Country(ipAddress).Country.IsoCode ?? "XX"; + geoipDB.Dispose(); } else { - int dbID, joinDate, lastSeen, connections; - string name, country; - - // GeoIP - DatabaseReader geoipDB = new DatabaseReader(PluginPath + "data/GeoIP/GeoLite2-Country.mmdb"); - if (geoipDB.Country(player.IpAddress!.Split(":")[0]).Country.IsoCode is not null) - { - country = geoipDB.Country(player.IpAddress!.Split(":")[0]).Country.IsoCode!; - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> GeoIP -> {player.PlayerName} -> {player.IpAddress!.Split(":")[0]} -> {country}"); - #endif - } - else - country = "XX"; - geoipDB.Dispose(); + country = "LL"; // Handle local IP appropriately + } +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnectFull -> GeoIP -> {name} -> {player.IpAddress!.Split(":")[0]} -> {country}"); +#endif + if (DB == null) + { + Exception ex = new("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldn't happen."); + throw ex; + } - if (DB == null) - throw new Exception("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldnt happen."); + // Create Player object and add to playerList + PlayerProfile Profile = PlayerProfile.CreateAsync(player.SteamID, name, country).GetAwaiter().GetResult(); + playerList[player.UserId ?? 0] = new Player(player, + new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), + Profile, CurrentMap); - // 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()) - { - // Player exists in database - dbID = playerData.GetInt32("id"); - name = playerData.GetString("name"); - if (country == "XX" && playerData.GetString("country") != "XX") - country = playerData.GetString("country"); - joinDate = playerData.GetInt32("join_date"); - lastSeen = playerData.GetInt32("last_seen"); - connections = playerData.GetInt32("connections"); - playerData.Close(); - - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> Returning player {name} ({player.SteamID}) loaded from database with ID {dbID}"); - #endif - } - else - { - playerData.Close(); - // Player does not exist in database - name = player.PlayerName; - joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - lastSeen = joinDate; - 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}); - "); - 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; - if (newPlayerData.HasRows && newPlayerData.Read()) - { - #if DEBUG - // Iterate through data: - for (int i = 0; i < newPlayerData.FieldCount; i++) - { - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> newPlayerData[{i}] = {newPlayerData.GetValue(i)}"); - } - #endif - dbID = newPlayerData.GetInt32("id"); - } - else - throw new Exception($"CS2 Surf ERROR >> OnPlayerConnect -> Failed to get new player's database ID after writing, this shouldnt happen. Player: {name} ({player.SteamID})"); - newPlayerData.Close(); - - #if DEBUG - 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); - playerList[player.UserId ?? 0] = new Player(player, - new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), - Profile, CurrentMap); - - #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], DB); // Will reload PB and Checkpoints for the player for all styles - 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}."); - Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); - return HookResult.Continue; - } + // Load MapTimes for the player's PB and their Checkpoints + playerList[player.UserId ?? 0].Stats.LoadPlayerMapTimesData(playerList[player.UserId ?? 0]).GetAwaiter().GetResult(); // Holds here until result is available + + // Print join messages + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Green}{name}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); + Console.WriteLine($"[CS2 Surf] {name} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); + + return HookResult.Continue; } [GameEventHandler] // Player Disconnect Event @@ -167,15 +136,30 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo { var player = @event.Userid; - for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) - if (CurrentMap.ReplayBots[i].IsPlayable && CurrentMap.ReplayBots[i].Controller!.Equals(player) && CurrentMap.ReplayBots[i].Stat_MapTimeID != -1) - CurrentMap.ReplayBots[i].Reset(); + if (player == null) + { + Console.WriteLine($"CS2 Surf ERROR >> OnPlayerDisconnect -> Null ({player == null})"); + return HookResult.Continue; + } + + if (CurrentMap.ReplayManager.MapWR.Controller != null && CurrentMap.ReplayManager.MapWR.Controller.Equals(player) && CurrentMap.ReplayManager.MapWR.MapID != -1) + CurrentMap.ReplayManager.MapWR.Reset(); + + if (CurrentMap.ReplayManager.StageWR != null && CurrentMap.ReplayManager.StageWR.Controller != null && CurrentMap.ReplayManager.StageWR.Controller.Equals(player) && CurrentMap.ReplayManager.StageWR.MapID != -1) + CurrentMap.ReplayManager.StageWR.Reset(); + + if (CurrentMap.ReplayManager.BonusWR != null && CurrentMap.ReplayManager.BonusWR.Controller != null && CurrentMap.ReplayManager.BonusWR.Controller.Equals(player)) + CurrentMap.ReplayManager.BonusWR!.Reset(); + + for (int i = 0; i < CurrentMap.ReplayManager.CustomReplays.Count; i++) + if (CurrentMap.ReplayManager.CustomReplays[i].Controller != null && CurrentMap.ReplayManager.CustomReplays[i].Controller!.Equals(player)) + CurrentMap.ReplayManager.CustomReplays[i].Reset(); + if (player.IsBot || !player.IsValid) { return HookResult.Continue; } - else { if (DB == null) @@ -188,15 +172,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; - "); - 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(); + playerList[player.UserId ?? 0].Profile.Update_Player_Profile(player.PlayerName).GetAwaiter().GetResult(); // Hold the thread until player data is updated // Remove player data from playerList playerList.Remove(player.UserId ?? 0); @@ -204,4 +180,30 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo return HookResult.Continue; } } + + /// + /// Checks whether an IP is a local one. Allows testing the plugin in a local environment setup for GeoIP + /// + /// IP to check + /// True for Private IP + static bool IsPrivateIP(string ip) + { + var ipParts = ip.Split('.'); + int firstOctet = int.Parse(ipParts[0]); + int secondOctet = int.Parse(ipParts[1]); + + // 10.x.x.x range + if (firstOctet == 10) + return true; + + // 172.16.x.x to 172.31.x.x range + if (firstOctet == 172 && (secondOctet >= 16 && secondOctet <= 31)) + return true; + + // 192.168.x.x range + if (firstOctet == 192 && secondOctet == 168) + return true; + + return false; + } } \ No newline at end of file diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 37703e4..52516fe 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -21,17 +21,63 @@ public void OnTick() if (bot_quota != null) { int cbq = bot_quota.GetPrimitiveValue(); - if(cbq != CurrentMap.ReplayBots.Count) + + int replaybot_count = 1 + + (CurrentMap.ReplayManager.StageWR != null ? 1 : 0) + + (CurrentMap.ReplayManager.BonusWR != null ? 1 : 0) + + CurrentMap.ReplayManager.CustomReplays.Count; + + if (cbq != replaybot_count) { - bot_quota.SetValue(CurrentMap.ReplayBots.Count); + bot_quota.SetValue(replaybot_count); } } - for(int i = 0; i < CurrentMap!.ReplayBots.Count; i++) + // Console.WriteLine($"======== public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}"); + CurrentMap.ReplayManager.MapWR.Tick(); + CurrentMap.ReplayManager.StageWR?.Tick(); + CurrentMap.ReplayManager.BonusWR?.Tick(); + + // Here we will load the NEXT stage replay from AllStageWR + if (CurrentMap.ReplayManager.StageWR?.RepeatCount == 0) + { + int next_stage; + if (CurrentMap.ReplayManager.AllStageWR[(CurrentMap.ReplayManager.StageWR.Stage % CurrentMap.Stages) + 1][0].MapTimeID == -1) + next_stage = 1; + else + next_stage = (CurrentMap.ReplayManager.StageWR.Stage % CurrentMap.Stages) + 1; + + CurrentMap.ReplayManager.AllStageWR[next_stage][0].Controller = CurrentMap.ReplayManager.StageWR.Controller; + + // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for stage {CurrentMap.ReplayManager.StageWR.Stage}, changing to stage {next_stage}"); + CurrentMap.ReplayManager.StageWR = CurrentMap.ReplayManager.AllStageWR[next_stage][0]; + CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); + CurrentMap.ReplayManager.StageWR.FormatBotName(); + CurrentMap.ReplayManager.StageWR.Start(); + } + + if (CurrentMap.ReplayManager.BonusWR?.RepeatCount == 0) { - CurrentMap.ReplayBots[i].Tick(); - if (CurrentMap.ReplayBots[i].RepeatCount == 0) + CurrentMap.ReplayManager.BonusWR.Stage = (CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1; + CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); + + AddTimer(1.5f, () => CurrentMap.ReplayManager.BonusWR.FormatBotName()); + + // CurrentMap.ReplayManager.BonusWR.ResetReplay(); + // //CurrentMap.ReplayManager.BonusWR.FormatBotName(); + // CurrentMap.ReplayManager.BonusWR.RepeatCount = 3; + } + + for (int i = 0; i < CurrentMap.ReplayManager.CustomReplays.Count; i++) + { + if (CurrentMap.ReplayManager.CustomReplays[i].MapID != CurrentMap.ID) + CurrentMap.ReplayManager.CustomReplays[i].MapID = CurrentMap.ID; + + CurrentMap.ReplayManager.CustomReplays[i].Tick(); + if (CurrentMap.ReplayManager.CustomReplays[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 65ddfc4..e685bd7 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -6,27 +6,38 @@ namespace SurfTimer; public partial class SurfTimer { - // Trigger end touch handler - CBaseTrigger_EndTouchFunc - // internal HookResult OnTriggerEndTouch(DynamicHook handler) + /// + /// Handler for trigger end touch hook - CBaseTrigger_EndTouchFunc. + /// + /// Sometimes this gets triggered when a player joins the server (for the 2nd time) so we assign `client` to `null` to bypass the error. + /// - T + /// internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - // CBaseTrigger trigger = handler.GetParam(0); CBaseTrigger trigger = new CBaseTrigger(caller.Handle); - // CBaseEntity entity = handler.GetParam(1); CBaseEntity entity = new CBaseEntity(activator.Handle); - CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - 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 + CCSPlayerController client = null!; + + try { - return HookResult.Continue; + client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); + } + catch (System.Exception) + { + Console.WriteLine($"===================== [ERROR] OnTriggerEndTouch -> Could not assign `client` (name: {name})"); } + if (client == null || !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; + } else { // Implement Trigger End Touch Here Player player = playerList[client.UserId ?? 0]; - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_EndTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); - #endif +#endif if (trigger.Entity!.Name != null) { @@ -38,119 +49,147 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti 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") || - trigger.Entity.Name.Contains("stage1_start")) + if (trigger.Entity.Name.Contains("map_start") || + 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))); // Todo make a plugin convar for the time saved before start of run - } - // MAP START ZONE - if (!player.Timer.IsStageMode) + if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) { player.Timer.Start(); - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_RUN; - } - - /* Revisit - // 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))); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); } - */ // 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 + player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Map run - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); - #endif +#endif + } + + // Map end zones -- hook into map_end + else if (trigger.Entity.Name == "map_end") + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_EXIT; } // Stage start zones -- hook into (s)tage#_start else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) { - #if DEBUG +#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.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoint.Count}"); - #endif + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.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) + int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + + // Set replay situation + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; + player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); + + // Start the Stage timer + if (player.Timer.IsStageMode && player.Timer.Stage == stage) { - 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}"); - Console.WriteLine($"currentCheckpoint.Attempts {currentCheckpoint.Attempts}"); - #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; - // 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; + player.Timer.Start(); + // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); - // 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"); + // Show Prespeed for Stages - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.ToString("0")} u/s"); } - else + else if (player.Timer.IsRunning) { - // Handle the case where the index is out of bounds +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); + Console.WriteLine($"currentCheckpoint.Attempts {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts}"); +#endif + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; + + // Show Prespeed for Checkpoints - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.ToString("0")} u/s"); } } // 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 +#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 + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.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) + if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoints.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; - // 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; +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); +#endif + + if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_EXIT; + player.ReplayRecorder.CheckpointExitSituations.Add(player.Timer.Ticks); + } + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; // 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"); } - else + } + + // Bonus start zones -- hook into (b)onus#_start + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) + { +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Bonus {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); +#endif + + // 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))); // Todo make a plugin convar for the time saved before start of run + } + + // BONUS START ZONE + if (!player.Timer.IsStageMode && player.Timer.IsBonusMode) { - // Handle the case where the index is out of bounds + player.Timer.Start(); + // Set the CurrentRunData values + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); } + + // Prespeed display + player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); + player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Bonus run } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index c0af249..111955f 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -7,167 +7,365 @@ namespace SurfTimer; public partial class SurfTimer { - // Trigger start touch handler - CBaseTrigger_StartTouchFunc - // internal HookResult OnTriggerStartTouch(DynamicHook handler) + /// + /// Handler for trigger start touch hook - CBaseTrigger_StartTouchFunc + /// + /// CounterStrikeSharp.API.Core.HookResult + /// internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - // CBaseTrigger trigger = handler.GetParam(0); CBaseTrigger trigger = new CBaseTrigger(caller.Handle); - // CBaseEntity entity = handler.GetParam(1); CBaseEntity entity = new CBaseEntity(activator.Handle); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); 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; } - 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)) { - // 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)) - { - 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]; - #if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); - #endif + Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + Exception exception = new($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + throw exception; + } + // 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 + + if (DB == null) + { + Exception exception = new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + throw exception; + } - if (trigger.Entity!.Name != null) + 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_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; + int pStyle = player.Timer.Style; + + // Map end zones -- hook into map_end + if (trigger.Entity.Name == "map_end") { - // 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_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); - int style = player.Timer.Style; - - // 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.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_RUN; + player.Controller.PrintToCenter($"Map End"); - 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 + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run + player.Stats.ThisRun.EndVelX = velocity_x; // End speed for the Map run + player.Stats.ThisRun.EndVelY = velocity_y; // End speed for the Map run + player.Stats.ThisRun.EndVelZ = velocity_z; // End speed for the Map run - // 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} {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} {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} {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 - } - if (DB == null) - throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + // MAP END ZONE - Map RUN + if (player.Timer.IsRunning && !player.Timer.IsStageMode) + { + player.Timer.Stop(); + bool saveMapTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - - player.Stats.PB[style].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR + { + saveMapTime = true; + int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Map{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + } + else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map + { + saveMapTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Map{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } + else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map + { + saveMapTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Map{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); + } + else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map + { + saveMapTime = true; + int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Map{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})!"); + } + else // Player did not beat their existing PersonalBest for the map nor the map record + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } + + if (saveMapTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data + }); + } - #if DEBUG - Console.WriteLine($@"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> +#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}, + VALUES ({player.Profile.ID}, {CurrentMap.ID}, {pStyle}, 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 - if(!player.Timer.IsPracticeMode) { - AddTimer(1.5f, async () => { - 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) - await 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 +#endif + + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) + { + // Should we also save a last stage run? + if (CurrentMap.Stages > 0) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } + + // This section checks if the PB is better than WR + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => + { + Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); + // CurrentMap.ReplayManager.MapWR.LoadReplayData(); + CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); }); + } + + } + + // API + /* + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) { + API_CurrentRun? last_stage_time = null; + if (CurrentMap.Stages > 0) + { + int last_exit_tick = player.ReplayRecorder.LastExitTick(); + int last_enter_tick = player.ReplayRecorder.LastEnterTick(); - // 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 stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this + int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; + + int tt = -1; + if (last_exit_tick - last_enter_tick > 2*64) + tt = last_exit_tick - 2*64; + else + tt = last_enter_tick; + + last_stage_time = new API_CurrentRun { - 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(); + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = style, + type = 2, + stage = CurrentMap.Stages, + run_time = stage_run_time, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) + }; + } + AddTimer(1.5f, () => { + List checkpoints = new List(); + foreach (var cp in player.Stats.ThisRun.Checkpoint) + { + checkpoints.Add(new API_Checkpoint + { + cp = cp.Key, + run_time = cp.Value.Ticks, + start_vel_x = cp.Value.StartVelX, + start_vel_y = cp.Value.StartVelY, + start_vel_z = cp.Value.StartVelZ, + end_vel_x = cp.Value.EndVelX, + end_vel_y = cp.Value.EndVelY, + end_vel_z = cp.Value.EndVelZ, + end_touch = 0, // ????? + attempts = cp.Value.Attempts }); } + + API_CurrentRun map_time = new API_CurrentRun + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = style, + type = 0, + stage = 0, + run_time = player.Stats.ThisRun.Ticks, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + checkpoints = checkpoints, + replay_frames = player.ReplayRecorder.SerializeReplay() + }; + + Task.Run(async () => { + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved map time"); + await ApiCall.POST("/surftimer/savemaptime", map_time); + + if (last_stage_time != null) + { + await ApiCall.POST("/surftimer/savestagetime", last_stage_time); + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved last stage time"); + player.Stats.LoadStageTime(player); + } + + player.Stats.LoadMapTime(player); + await CurrentMap.ApiGetMapRecordAndTotals(); // 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[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => { + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR PB"); + CurrentMap.ReplayManager.MapWR.LoadReplayData(); + + AddTimer(1.5f, () => { + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + }); } } + */ + } + else if (player.Timer.IsStageMode) + { + player.Timer.Stop(); + + if (!player.Timer.IsPracticeMode) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - #if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); - #endif + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } } - // 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")) +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); +#endif + } + + // 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") + ) + { + // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts + if (!player.ReplayRecorder.IsSaving) { + player.ReplayRecorder.Reset(); // Start replay recording player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); player.Timer.Reset(); - player.Stats.ThisRun.Checkpoint.Clear(); // I have the suspicion that the `Timer.Reset()` does not properly reset this object :thonk: + player.Stats.ThisRun.Checkpoints.Clear(); player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); - // player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> KeyValues: {trigger.Entity.KeyValues3}"); - #endif +#endif + } + else + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Yellow}Please try restarting again, replay is still being saved."); } + } + + // Stage start zones -- hook into (s)tage#_start + 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); + + if (!player.ReplayRecorder.IsRecording) + player.ReplayRecorder.Start(); - // Stage start zones -- hook into (s)tage#_start - else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; + player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); + + bool failed_stage = false; + if (player.Timer.Stage == stage) + failed_stage = true; + + // Reset/Stop the Stage timer + // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) + if (player.Timer.IsStageMode) { - int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; - player.Timer.Stage = stage; + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks; + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } + player.Timer.Reset(); + player.Timer.IsStageMode = true; + // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); + } + + 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 +#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.Checkpoints.Count <= stage}"); +#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) + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) + { + // Save Stage MapTime during a Map run + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { - player.Timer.Checkpoint = stage; // Stage = Checkpoint when in a run on a Staged map + int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } - #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[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); - #endif + player.Timer.Checkpoint = stage - 1; // Stage = Checkpoint when in a run on a Staged map - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(PluginPrefix); +#if DEBUG + Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {stage} | player.Timer.Checkpoint: {stage - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - Checkpoint cp2 = new Checkpoint(stage, + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) + { + Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, player.Timer.Ticks, velocity_x, velocity_y, @@ -175,33 +373,46 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn -1.0f, -1.0f, -1.0f, - -1.0f, - 0); - player.Stats.ThisRun.Checkpoint[stage] = cp2; + 0, + 1); + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; } - - #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"); - #endif } - // 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) +#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"); +#endif + } + + // 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); + player.Timer.Checkpoint = checkpoint; + + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < checkpoint) { - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); - player.Timer.Checkpoint = 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)}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif - // 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 (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) { - #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[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); - #endif - - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(PluginPrefix); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; + player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); + } + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) + { // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality Checkpoint cp2 = new Checkpoint(checkpoint, player.Timer.Ticks, @@ -211,18 +422,189 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn -1.0f, -1.0f, -1.0f, - -1.0f, - 0); - player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; + 0, + 1); + player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; } + } + +#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"); +#endif + } + + // Bonus start zones -- hook into (b)onus#_start + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) + { + // We only want this working if they're in bonus mode, ignore otherwise. + if (player.Timer.IsBonusMode) + { + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + // player.ReplayRecorder.Frames[player.ReplayRecorder.Frames.Count].Situation = (byte)ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + + player.Timer.Reset(); + player.Timer.IsBonusMode = true; + int bonus = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + player.Timer.Bonus = bonus; + + player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); - #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"); - #endif +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); +#endif } } - return HookResult.Continue; + // Bonus end zones -- hook into (b)onus#_end + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success && player.Timer.IsBonusMode && player.Timer.IsRunning) + { + // To-do: verify the bonus trigger being hit! + int bonus_idx = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; + if (1 != player.Timer.Bonus) // Realy????!! + { + // Exit hook as this end zone is not relevant to the player's current bonus + return HookResult.Continue; + } + + player.Timer.Stop(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); + + 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.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PB for the bonus + { + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{player.Controller.PlayerName} finished bonus {bonus_idx + 1} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + } + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PB for the bonus + { + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their bonus {bonus_idx + 1} PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.BonusPB[bonus_idx][pStyle].Ticks)}{ChatColors.Default})!"); + } + else // Player did not beat their existing personal best for the bonus + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished bonus {bonus_idx + 1} in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + return HookResult.Continue; // Exit here so we don't write to DB + } + + if (DB == null) + { + Exception exception = new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Bonus end zone) -> DB object is null, this shouldn't happen."); + throw exception; + } + + player.Stats.BonusPB[bonus_idx][pStyle].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + + // To-do: save to DB + if (!player.Timer.IsPracticeMode) + { + AddTimer(1.5f, () => + { + API_CurrentRun bonus_time = new API_CurrentRun + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = pStyle, + type = 1, + stage = bonus_idx, + run_time = player.Stats.ThisRun.Ticks, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplay() + }; + + Task.Run(async () => + { + await ApiMethod.POST("/surftimer/savebonustime", bonus_time); + player.Stats.LoadBonusTime(player); + await CurrentMap.Get_Map_Record_Runs(); // Reload the Map record and totals for the HUD + // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD + }); + }); + + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks || CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) + { + AddTimer(2f, () => + { + CurrentMap.ReplayManager.BonusWR!.Stage = bonus_idx; + CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.BonusWR.FormatBotName(); + }); + }); + } + } + } + } + return HookResult.Continue; + } + + /// + /// Deals with saving a Stage MapTime (Type 2) in the Database. + /// Should deal with `IsStageMode` runs, Stages during Map Runs and also Last Stage. + /// + /// Player object + /// Stage to save + /// Is it the last stage? + /// Run Time (Ticks) for the stage run + void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool saveLastStage = false) + { + // player.Controller.PrintToChat($"{Config.PluginPrefix} SaveStageTime received: {player.Profile.Name}, {stage}, {stage_run_time}, {saveLastStage}"); + int pStyle = player.Timer.Style; + if ( + stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks || + CurrentMap.StageWR[stage][pStyle].ID == -1 || + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time + ) + { + if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR + { + int timeImprove = CurrentMap.StageWR[stage][pStyle].Ticks - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + } + else if (CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map + { + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}!"); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best + { + player.Controller.PrintToChat($"{Config.PluginPrefix} You finished {ChatColors.Yellow}Stage {stage}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, setting your new Personal Best!"); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time) // Player beating their existing Stage personal best + { + int timeImprove = player.Stats.StagePB[stage][pStyle].Ticks - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Stage {stage}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + } + + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + // Save stage run + Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); + await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data + }); + } + else if (stage_run_time > CurrentMap.StageWR[stage][pStyle].Ticks && player.Timer.IsStageMode) // Player is behind the Stage WR for the map + { + int timeImprove = stage_run_time - CurrentMap.StageWR[stage][pStyle].Ticks; + player.Controller.PrintToChat($"{Config.PluginPrefix} You are behind the {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Grey}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, missing the record by {ChatColors.Red}+{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default} ({ChatColors.Gold}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})!"); } } -} \ No newline at end of file +} + diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index c1fb166..95c8a74 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -1,3 +1,5 @@ +using System.Data; +using System.Text.Json; using System.Text.RegularExpressions; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; @@ -6,45 +8,126 @@ namespace SurfTimer; -internal 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) - public string Name {get; set;} = ""; - 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; - public int LastPlayed {get; set;} = 0; - public int TotalCompletions {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; } = string.Empty; + public string Author { get; set; } = ""; + public int Tier { get; set; } = 0; + public int Stages { get; set; } = 0; + public int TotalCheckpoints { 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; + /// + /// Map Completion Count - Refer to as MapCompletions[style] + /// + public Dictionary MapCompletions { get; set; } = new Dictionary(); + /// + /// Bonus Completion Count - Refer to as BonusCompletions[bonus#][style] + /// + public Dictionary[] BonusCompletions { get; set; } = new Dictionary[32]; + /// + /// Stage Completion Count - Refer to as StageCompletions[stage#][style] + /// + public Dictionary[] StageCompletions { get; set; } = new Dictionary[32]; + /// + /// Map World Record - Refer to as WR[style] + /// public Dictionary WR { get; set; } = new Dictionary(); + /// + /// Bonus World Record - Refer to as BonusWR[bonus#][style] + /// + public Dictionary[] BonusWR { get; set; } = new Dictionary[32]; + /// + /// Stage World Record - Refer to as StageWR[stage#][style] + /// + public Dictionary[] StageWR { get; set; } = new Dictionary[32]; + + /// + /// Not sure what this is for. + /// Guessing it's to do with Replays and the ability to play your PB replay. + /// + /// - T + /// public List ConnectedMapTimes { get; set; } = new List(); - public List ReplayBots { get; set; } = new List { new ReplayPlayer() }; // Zone Origin Information - // Map start/end zones - public Vector StartZone {get;} = new Vector(0,0,0); - public QAngle StartZoneAngles {get;} = new QAngle(0,0,0); - public Vector EndZone {get;} = new Vector(0,0,0); - // Map stage zones - public Vector[] StageStartZone {get;} = Enumerable.Repeat(0, 99).Select(x => new Vector(0,0,0)).ToArray(); - public QAngle[] StageStartZoneAngles {get;} = Enumerable.Repeat(0, 99).Select(x => new QAngle(0,0,0)).ToArray(); - // Map bonus zones - 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(); + /* Map Start/End zones */ + public Vector StartZone { get; set; } = new Vector(0, 0, 0); + public QAngle StartZoneAngles { get; set; } = new QAngle(0, 0, 0); + public Vector EndZone { get; set; } = new Vector(0, 0, 0); + /* Map Stage zones */ + public Vector[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector(0, 0, 0)).ToArray(); + public QAngle[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle(0, 0, 0)).ToArray(); + /* Map Bonus zones */ + 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(); + + public ReplayManager ReplayManager { get; set; } = null!; // Constructor - internal Map(string Name, TimerDatabase DB) + internal Map(string name) { // Set map name - this.Name = Name; - this.WR[0] = new PersonalBest(); // To-do: Implement styles + this.Name = name; + + // Initialize WR variables + foreach (int style in Config.Styles) + { + this.WR[style] = new PersonalBest(); + this.MapCompletions[style] = -1; + } + + for (int i = 0; i < 32; i++) + { + this.BonusWR[i] = new Dictionary(); + this.BonusWR[i][0] = new PersonalBest(); + this.BonusWR[i][0].Type = 1; + this.BonusCompletions[i] = new Dictionary(); + + this.StageWR[i] = new Dictionary(); + this.StageWR[i][0] = new PersonalBest(); + this.StageWR[i][0].Type = 2; + this.StageCompletions[i] = new Dictionary(); + } + } + + public static async Task CreateAsync(string name) + { + var map = new Map(name); + await map.InitializeAsync(); + return map; + } + + private async Task InitializeAsync() + { + // Load zones + Map_Load_Zones(); + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Zones have been loaded."); + + // Initialize ReplayManager with placeholder values + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Initializing ReplayManager(-1, {this.Stages > 0}, false, null!)"); + this.ReplayManager = new ReplayManager(-1, this.Stages > 0, false, null!); // Adjust values as needed + + await Get_Map_Info(); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> We got MapID = {ID} ({Name})"); + } + + /// + /// Loops through all the hookzones found in the map and loads the respective zones + /// + // To-do: This loops through all the triggers. While that's great and comprehensive, some maps have two triggers with the exact same name, because there are two + // for each side of the course (left and right, for example). We should probably work on automatically catching this. + // Maybe even introduce a new naming convention? + internal void Map_Load_Zones() + { // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); // Gathering info_teleport_destinations from the map @@ -54,17 +137,17 @@ internal Map(string Name, TimerDatabase DB) if (trigger.Entity!.Name != null) { // Map start zone - if (trigger.Entity!.Name.Contains("map_start") || - trigger.Entity!.Name.Contains("stage1_start") || - trigger.Entity!.Name.Contains("s1_start")) + if (trigger.Entity!.Name.Contains("map_start") || + 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 && - (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || - teleport.Entity!.Name.Contains("spawn_map_start") || - teleport.Entity!.Name.Contains("spawn_stage1_start") || + 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); @@ -81,25 +164,25 @@ internal Map(string Name, TimerDatabase DB) } // Map end zone - else if (trigger.Entity!.Name.Contains("map_end")) + else if (trigger.Entity!.Name.Contains("map_end")) { this.EndZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } // Stage start zones - else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) + 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 && + 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.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.StageStartZone[stage] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StageStartZoneAngles[stage] = 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; @@ -108,19 +191,20 @@ internal Map(string Name, TimerDatabase DB) if (!foundPlayerSpawn) { - this.StageStartZone[stage - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StageStartZone[stage] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.Stages++; } } // 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) + 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 + this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.TotalCheckpoints++; // Might be useful to have this in DB entry } - + // Bonus start zones - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) + 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); @@ -128,11 +212,11 @@ internal Map(string Name, TimerDatabase DB) bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { - if (teleport.Entity!.Name != null && + 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.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.BonusStartZone[bonus] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.BonusStartZoneAngles[bonus] = 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; @@ -141,189 +225,509 @@ internal Map(string Name, TimerDatabase DB) if (!foundPlayerSpawn) { - this.BonusStartZone[bonus - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusStartZone[bonus] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.Bonuses++; } } - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) { - this.BonusEndZone[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.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } } - if (this.Stages > 0) this.Stages++; // You did not count the stages right :( + + if (this.Stages > 0) // Account for stage 1, not counted above + this.Stages += 1; 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 - Task reader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); - MySqlDataReader mapData = reader.Result; - 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) + /// + /// Inserts a new map entry in the database. + /// Automatically detects whether to use API Calls or MySQL query. + /// + internal async Task Insert_Map_Info() + { + if (Config.API.GetApiOnly()) // API Calls { - this.ID = mapData.GetInt32("id"); - this.Author = mapData.GetString("author") ?? "Unknown"; - this.Tier = mapData.GetInt32("tier"); - 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(); - } + API_MapInfo inserted = new() + { + id = -1, // Shouldn't really use this at all at api side + name = Name, + author = "Unknown", + tier = this.Tier, + stages = this.Stages, + bonuses = this.Bonuses, + ranked = 0, + }; - else - { - mapData.Close(); - 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 shouldn't happen. Map: {Name}"); - writer.Dispose(); + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, inserted); - Task postWriteReader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); - MySqlDataReader postWriteMapData = postWriteReader.Result; - if (postWriteMapData.HasRows && postWriteMapData.Read()) + // Check if the response is not null and get the last_id + if (postResponse != null) { - 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; + Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Insert_Map_Info -> New map '{Name}' inserted, got ID {postResponse.last_id}"); + this.ID = postResponse.last_id; } - postWriteMapData.Close(); return; } + else // MySQL Queries + { + int writerRows = await SurfTimer.DB.WriteAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(Name), "Unknown", this.Stages, this.Bonuses, 0, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds())); + if (writerRows != 1) + { + Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); + throw exception; + } - // Update the map's last played data in the DB - // 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} | was it 'big' update? {updateData}"); - updater.Dispose(); - - // 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(); + await Get_Map_Info(false); + } } - public void KickReplayBot(int index) + /// + /// Updates last played, stages, bonuses for the map in the database. + /// Automatically detects whether to use API Calls or MySQL query. + /// + internal async Task Update_Map_Info() { - if (!this.ReplayBots[index].IsPlayable) - return; + if (Config.API.GetApiOnly()) // API Calls + { + API_MapInfo updated = new() + { + id = this.ID, + name = Name, + author = "Unknown", + tier = this.Tier, + stages = this.Stages, + bonuses = this.Bonuses, + ranked = 0, + last_played = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; - int? id_to_kick = this.ReplayBots[index].Controller!.UserId; - if(id_to_kick == null) - return; + _ = ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, updated).Result; + } + else // MySQL Queries + { + // Update the map's last played data in the DB + string updateQuery = string.Format(Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, + (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.Stages, this.Bonuses, this.ID); + +#if DEBUG + Console.WriteLine($"CS2 Surf >> internal class Map -> internal async Task Update_Map_Info -> Update MapData: {updateQuery}"); +#endif - this.ReplayBots.RemoveAt(index); - Server.ExecuteCommand($"kickid {id_to_kick}; bot_quota {this.ReplayBots.Count}"); + int lastPlayedUpdateRows = await SurfTimer.DB.WriteAsync(updateQuery); + if (lastPlayedUpdateRows != 1) + { + Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}"); + throw exception; + } + } } - public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) + /// + /// Load map info data using MySQL Queries and update the info as well or create a new entry. + /// Loads the record runs for the map as well. + /// Automatically detects whether to use API Calls or MySQL query. + /// + /// Should we run UPDATE query for the map + internal async Task Get_Map_Info(bool updateData = true) { - if (spawnOrigin.X >= zoneOrigin.X - zoneCollisionRadius && spawnOrigin.X <= zoneOrigin.X + zoneCollisionRadius && - spawnOrigin.Y >= zoneOrigin.Y - zoneCollisionRadius && spawnOrigin.Y <= zoneOrigin.Y + zoneCollisionRadius && - spawnOrigin.Z >= zoneOrigin.Z - zoneCollisionRadius && spawnOrigin.Z <= zoneOrigin.Z + zoneCollisionRadius) - return true; - else - return false; + bool newMap = false; + + if (Config.API.GetApiOnly()) // API Calls + { + // Gather map information OR create entry + var mapinfo = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, Name)); + if (mapinfo != null) + { + this.ID = mapinfo.id; + this.Author = mapinfo.author; + this.Tier = mapinfo.tier; + this.Ranked = mapinfo.ranked == 1; + this.DateAdded = (int)mapinfo.date_added!; + this.LastPlayed = (int)mapinfo.last_played!; + } + else + { + newMap = true; + } + } + else // MySQL queries + { + // Gather map information OR create entry + using (var mapData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(Name)))) + { + 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.Author = mapData.GetString("author") ?? "Unknown"; + this.Tier = mapData.GetInt32("tier"); + this.Ranked = mapData.GetBoolean("ranked"); + this.DateAdded = mapData.GetInt32("date_added"); + this.LastPlayed = mapData.GetInt32("last_played"); + } + else + { + newMap = true; + } + } + } + + // This is a new map + if (newMap) + { + await Insert_Map_Info(); + return; + } + + // this.ReplayManager = new ReplayManager(this.ID, this.Stages > 0, this.Bonuses > 0); + + // Will skip updating the data in the case where we have just inserted a new map with MySQL Queries and called this method again in order to get the Map ID + if (updateData) + await Update_Map_Info(); + + await Get_Map_Record_Runs(); } - // 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 + /// + /// Extracts Map, Bonus, Stage record runs and the total completions for each style. + /// (NOT TESTED WITH MORE THAN 1 STYLE) + /// For the Map WR it also gets the Checkpoints data. + /// Automatically detects whether to use API Calls or MySQL query. + /// TODO: Re-do the API with the new query and fix the API assign of values + /// + internal async Task Get_Map_Record_Runs() { - // Get map world records - 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; - - if (mapWrData.HasRows) - { - // To-do: Implement bonuses WR - // To-do: Implement stages WR - this.ConnectedMapTimes.Clear(); - while (mapWrData.Read()) + int totalMapRuns = 0; + int totalStageRuns = 0; + int totalBonusRuns = 0; + this.ConnectedMapTimes.Clear(); + + int qType; + int qStage; + int qStyle; + + // Replay Stuff + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + + if (Config.API.GetApiOnly()) // Need to update the query in API and re-do the assigning of data + { + // // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, style, type)); + // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, 0, 0)); + // if (maptimes == null) + // { + // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> No map runs found for {this.Name} (MapID {this.ID} | Style {qStyle} | Type {qStyle})"); + // this.MapCompletions[qStyle] = 0; + // return; + // } + + // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> Got {maptimes.Length} map runs for MapID {this.ID} (Style {qStyle} | Type {qStyle})"); + // // To-do: Implement bonuses WR + // // To-do: Implement stages WR + // foreach (var time in maptimes) + // { + // if (totalMapRuns == 0) // First row is always the fastest run for the map, style, type combo + // { + // this.WR[qStyle].ID = time.id; // WR ID for the Map and Style combo + // this.WR[qStyle].Ticks = time.run_time; // Fastest run time (WR) for the Map and Style combo + // this.WR[qStyle].StartVelX = time.start_vel_x; // Fastest run start velocity X for the Map and Style combo + // this.WR[qStyle].StartVelY = time.start_vel_y; // Fastest run start velocity Y for the Map and Style combo + // this.WR[qStyle].StartVelZ = time.start_vel_z; // Fastest run start velocity Z for the Map and Style combo + // this.WR[qStyle].EndVelX = time.end_vel_x; // Fastest run end velocity X for the Map and Style combo + // this.WR[qStyle].EndVelY = time.end_vel_y; // Fastest run end velocity Y for the Map and Style combo + // this.WR[qStyle].EndVelZ = time.end_vel_z; // Fastest run end velocity Z for the Map and Style combo + // this.WR[qStyle].RunDate = time.run_date; // Fastest run date for the Map and Style combo + // this.WR[qStyle].Name = time.name; // Fastest run player name for the Map and Style combo + // } + // this.ConnectedMapTimes.Add(time.id); + // totalMapRuns++; + // } + // // this.ConnectedMapTimes.Remove(this.WR[style].ID); // ?? + // // this.MapCompletions[style] = maptimes.Length; + } + else // MySQL Queries + { + // Get map world record + using (var mapWrData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, this.ID))) { - 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 - this.WR[style].Name = mapWrData.GetString("name"); // Fastest run player name for the Map and Style combo + if (mapWrData.HasRows) + { + while (mapWrData.Read()) + { + qType = mapWrData.GetInt32("type"); + qStage = mapWrData.GetInt32("stage"); + qStyle = mapWrData.GetInt32("style"); + + // Retrieve replay_frames as string from MySQL + string replayFramesBase64; + + // Option A: Try to get the string directly + try + { + replayFramesBase64 = mapWrData.GetString("replay_frames"); + } + catch (InvalidCastException) + { + // Option B: Get the data as byte[] and convert to string + byte[] replayFramesData = mapWrData.GetFieldValue("replay_frames"); + replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); + } + + // Populate parameters for all the MapTime rows found + switch (qType) + { + case 0: // Map WR data and total completions + this.WR[qStyle].ID = mapWrData.GetInt32("id"); + this.WR[qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.WR[qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.WR[qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.WR[qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.WR[qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.WR[qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.WR[qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.WR[qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.WR[qStyle].Name = mapWrData.GetString("name"); + totalMapRuns = mapWrData.GetInt32("total_count"); + this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); + this.MapCompletions[qStyle] = totalMapRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [MapWR] Sending style {qStyle} to `ReplayManager`: Map ID {this.ID} | Stages {this.Stages > 0} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); + + // Populate the ReplayManager for Map WR only if no replay exists or a new WR was set + if (this.ReplayManager.MapWR.MapID == -1 || this.WR[qStyle].Ticks < this.ReplayManager.MapWR.RecordRunTime) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } + break; + case 1: // Bonus WR data and total completions + this.BonusWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); + this.BonusWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.BonusWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.BonusWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.BonusWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.BonusWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.BonusWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.BonusWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.BonusWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.BonusWR[qStage][qStyle].Name = mapWrData.GetString("name"); + totalBonusRuns = mapWrData.GetInt32("total_count"); + this.BonusCompletions[qStage][qStyle] = totalBonusRuns; + break; + case 2: // Stage WR data and total completions + this.StageWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); + this.StageWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.StageWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.StageWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.StageWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.StageWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.StageWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.StageWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.StageWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.StageWR[qStage][qStyle].Name = mapWrData.GetString("name"); + totalStageRuns = mapWrData.GetInt32("total_count"); + this.StageCompletions[qStage][qStyle] = totalStageRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [StageWR] Sending style {qStyle} to `ReplayManager.StageWR`: Map ID {this.ID} | Stages {this.Stages > 0} - {qStage} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); + + // Populate the ReplayManager for all stages found and set the first stage to replay + if (this.ReplayManager.StageWR != null) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } + break; + } + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Map Completions for style {qStyle} {this.MapCompletions[qStyle]}"); + } } - this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); - totalRows++; } } - mapWrData.Close(); - this.TotalCompletions = totalRows; // Total completions for the map and style - this should maybe be added to PersonalBest class - // Get map world record checkpoints - if (totalRows != 0) + // Retrieve the checkpoints for each Style if it has been set. + foreach (int style in Config.Styles) { - Task cpReader = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {this.WR[style].ID};"); - MySqlDataReader cpWrData = cpReader.Result; - while (cpWrData.Read()) + // if (this.MapCompletions[style] > 0 && this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime) // This should also reload Checkpoints if a new MapWR is set + if ( + this.MapCompletions[style] > 0 && this.WR[style].ID != -1 || + this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime + ) // This should also reload Checkpoints if a new MapWR is set { - #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 - - 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 +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal async Task Get_Map_Record_Runs : Map -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Loaded {this.MapCompletions[style]} runs (MapID {this.ID} | Style {style}). WR by {this.WR[style].Name} - {PlayerHUD.FormatTime(this.WR[style].Ticks)}"); +#endif + await Get_Record_Run_Checkpoints(style); } - cpWrData.Close(); } } + + /// + /// Redirects to `PersonalBest.PB_LoadCheckpointsData()`. + /// Extracts all entries from Checkpoints table of the World Record for the given `style` + /// + /// Style to load + internal async Task Get_Record_Run_Checkpoints(int style = 0) + { + await this.WR[style].PB_LoadCheckpointsData(); + } + + /// + /// Sets the data for a replay that has been retrieved from MapTimes data. + /// Also sets the first Stage replay if no replays existed for stages until now. + /// + /// Type - 0 = Map, 1 = Bonus, 2 = Stage + /// Style to add + /// Stage to add + /// Base64 encoded string for the replay_frames + internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64) + { + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + + // Decompress the Base64 string + string json = Compressor.Decompress(replayFramesBase64); + + // Deserialize to List + List frames = JsonSerializer.Deserialize>(json, options)!; + + switch (type) + { + case 0: // Map Replays + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); + if (this.ReplayManager.MapWR.IsPlaying) + this.ReplayManager.MapWR.Stop(); + + this.ReplayManager.MapWR.RecordPlayerName = this.WR[style].Name; + this.ReplayManager.MapWR.RecordRunTime = this.WR[style].Ticks; + this.ReplayManager.MapWR.Frames = frames; + this.ReplayManager.MapWR.MapTimeID = this.WR[style].ID; + this.ReplayManager.MapWR.MapID = this.ID; + this.ReplayManager.MapWR.Type = 0; + for (int i = 0; i < frames.Count; i++) // Load the situations for the replay + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + this.ReplayManager.MapWR.MapSituations.Add(i); + Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + this.ReplayManager.MapWR.MapSituations.Add(i); + Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + this.ReplayManager.MapWR.StageEnterSituations.Add(i); + Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + this.ReplayManager.MapWR.StageExitSituations.Add(i); + Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + this.ReplayManager.MapWR.CheckpointEnterSituations.Add(i); + Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + this.ReplayManager.MapWR.CheckpointExitSituations.Add(i); + Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_ENTER: + Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + } + } + break; + case 1: // Bonus Replays + // Not loading any Bonus replays yet + break; + case 2: // Stage Replays + // Skip if we the same stage run already exists + if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) + break; + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Adding run {this.StageWR[stage][style].ID} {PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks)} (Ticks = {this.StageWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllStageWR`"); + // Add all stages found to a dictionary with their data + this.ReplayManager.AllStageWR[stage][style].MapID = this.ID; + this.ReplayManager.AllStageWR[stage][style].Frames = frames; + this.ReplayManager.AllStageWR[stage][style].RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.AllStageWR[stage][style].RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.AllStageWR[stage][style].MapTimeID = this.StageWR[stage][style].ID; + this.ReplayManager.AllStageWR[stage][style].Stage = stage; + this.ReplayManager.AllStageWR[stage][style].Type = 2; + this.ReplayManager.AllStageWR[stage][style].RecordRank = 1; + this.ReplayManager.AllStageWR[stage][style].IsPlayable = true; // We set this to `true` else we overwrite it and need to call SetController method again + for (int i = 0; i < frames.Count; i++) + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.STAGE_ZONE_ENTER: + this.ReplayManager.AllStageWR[stage][style].StageEnterSituations.Add(i); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + this.ReplayManager.AllStageWR[stage][style].StageExitSituations.Add(i); + break; + } + } + // Set the stage to replay first + if (this.ReplayManager.StageWR != null && this.ReplayManager.StageWR.MapID == -1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Setting first `ReplayManager.StageWR` to stage {stage}"); + if (this.ReplayManager.StageWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current stage being played? + this.ReplayManager.StageWR.Stop(); + this.ReplayManager.StageWR.MapID = this.ID; + this.ReplayManager.StageWR.Frames = frames; + this.ReplayManager.StageWR.RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.StageWR.RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.StageWR.MapTimeID = this.StageWR[stage][style].ID; + this.ReplayManager.StageWR.Stage = stage; + this.ReplayManager.StageWR.Type = 2; + this.ReplayManager.StageWR.RecordRank = 1; + } + break; + } + + // Start the new map replay if none existed until now + if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); + this.ReplayManager.MapWR.ResetReplay(); + this.ReplayManager.MapWR.Start(); + } + else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.StageWR.ResetReplay(); + this.ReplayManager.StageWR.Start(); + } + } + + public void KickReplayBot(int index) + { + if (!this.ReplayManager.CustomReplays[index].IsPlayable) + return; + + int? id_to_kick = this.ReplayManager.CustomReplays[index].Controller!.UserId; + if (id_to_kick == null) + return; + + this.ReplayManager.CustomReplays.RemoveAt(index); + Server.ExecuteCommand($"kickid {id_to_kick}; bot_quota {this.ReplayManager.CustomReplays.Count}"); + } + + public static bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) + { + if (spawnOrigin.X >= zoneOrigin.X - zoneCollisionRadius && spawnOrigin.X <= zoneOrigin.X + zoneCollisionRadius && + spawnOrigin.Y >= zoneOrigin.Y - zoneCollisionRadius && spawnOrigin.Y <= zoneOrigin.Y + zoneCollisionRadius && + spawnOrigin.Z >= zoneOrigin.Z - zoneCollisionRadius && spawnOrigin.Z <= zoneOrigin.Z + zoneCollisionRadius) + return true; + else + return false; + } } \ No newline at end of file diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 2bcfb83..cbbaed5 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -40,7 +40,7 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen } /// - /// Checks if current player is spcetating player

+ /// Checks if current player is spectating player

///

public bool IsSpectating(CCSPlayerController p) { diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 39ab08e..41a3454 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; @@ -44,10 +44,12 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P { case PlayerTimer.TimeFormatStyle.Compact: return time.TotalMinutes < 1 - ? $"{time.Seconds:D1}.{millis:D3}" - : $"{time.Minutes:D1}:{time.Seconds:D1}.{millis:D3}"; + ? $"{time.Seconds:D2}.{millis:D3}" + : $"{time.Minutes:D1}:{time.Seconds:D2}.{millis:D3}"; case PlayerTimer.TimeFormatStyle.Full: - return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + return time.TotalHours < 1 + ? $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}" + : $"{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: @@ -57,7 +59,7 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P public void Display() { - if(!_player.Controller.IsValid) + if (!_player.Controller.IsValid) return; if (_player.Controller.PawnIsAlive) @@ -73,7 +75,14 @@ public void Display() else timerColor = "#2E9F65"; } - string timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); + + string timerModule; + if (_player.Timer.IsBonusMode) + timerModule = FormatHUDElementHTML("", $"[B{_player.Timer.Bonus}] " + FormatTime(_player.Timer.Ticks), timerColor); + else if (_player.Timer.IsStageMode) + timerModule = FormatHUDElementHTML("", $"[S{_player.Timer.Stage}] " + FormatTime(_player.Timer.Ticks), timerColor); + else + 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 @@ -82,17 +91,42 @@ 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[style].ID != -1 && _player.CurrMap.WR[style].ID != -1) + if (_player.Timer.IsBonusMode) + { + if (_player.Stats.BonusPB[_player.Timer.Bonus][style].ID != -1 && SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.BonusPB[_player.Timer.Bonus][style].Rank}/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); + } + else if (_player.Timer.IsStageMode) { - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); + if (_player.Stats.StagePB[_player.Timer.Stage][style].ID != -1 && SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.StagePB[_player.Timer.Stage][style].Rank}/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); } - else if (_player.CurrMap.WR[style].ID != -1) + else { - rankModule = FormatHUDElementHTML("Rank", $"-/{_player.CurrMap.TotalCompletions}", "#7882dd"); + if (_player.Stats.PB[style].ID != -1 && SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{SurfTimer.CurrentMap.MapCompletions[style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.MapCompletions[style]}", "#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 - 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? + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].Ticks > 0 ? FormatTime(_player.Stats.PB[style].Ticks) : "N/A", "#7882dd"); + string wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.WR[style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.WR[style].Ticks) : "N/A", "#ffc61a"); + + if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values + { + pbModule = FormatHUDElementHTML("PB", _player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks) : "N/A", "#ffc61a"); + } + else if (_player.Timer.IsStageMode) // Show corresponding stage values + { + pbModule = FormatHUDElementHTML("PB", _player.Stats.StagePB[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].Ticks) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks) : "N/A", "#ffc61a"); + } // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; @@ -102,17 +136,22 @@ public void Display() } else if (_player.Controller.Team == CsTeam.Spectator) { - for (int i = 0; i < _player.CurrMap.ReplayBots.Count; i++) - { - if(!_player.CurrMap.ReplayBots[i].IsPlayable || !_player.IsSpectating(_player.CurrMap.ReplayBots[i].Controller!)) - continue; - - string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; + ReplayPlayer? spec_replay; - string nameModule = FormatHUDElementHTML($"{_player.CurrMap.ReplayBots[i].Stat_PlayerName}", $"{FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTime)}", "#ffd500"); + if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.MapWR.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.MapWR; + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.StageWR?.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.StageWR!; + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.BonusWR?.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.BonusWR!; + else + spec_replay = SurfTimer.CurrentMap.ReplayManager.CustomReplays.Find(x => _player.IsSpectating(x.Controller!)); - string elapsed_time = FormatHUDElementHTML("Time", $"{PlayerHUD.FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTick)}", "#7882dd"); - string hud = $"{replayModule}
{elapsed_time}
{nameModule}"; + if (spec_replay != null) + { + string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; + string nameModule = FormatHUDElementHTML($"{spec_replay.RecordPlayerName}", $"{FormatTime(spec_replay.RecordRunTime)}", "#ffd500"); + string hud = $"{replayModule}
{nameModule}"; _player.Controller.PrintToCenterHtml(hud); } @@ -122,14 +161,16 @@ public void Display() /// /// 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 + public void DisplayCheckpointMessages() { int pbTime; int wrTime = -1; float pbSpeed; float wrSpeed = -1.0f; int style = _player.Timer.Style; + int playerCheckpoint = _player.Timer.Checkpoint; + + // _player.Controller.PrintToChat($"{ChatColors.Blue}-> PlayerHUD{ChatColors.Default} => Style {ChatColors.Yellow}{style}{ChatColors.Default} | Checkpoint {playerCheckpoint} | WR Time Ticks {SurfTimer.CurrentMap.WR[style].Ticks} | Player Stage {_player.Timer.Stage} (CP {_player.Timer.Checkpoint}) | Player Ticks {_player.Timer.Ticks}"); int currentTime = _player.Timer.Ticks; float currentSpeed = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X @@ -140,43 +181,43 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi 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 + // We need to try/catch this because the player might not have a PB for this checkpoint 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[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[{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 + pbTime = _player.Stats.PB[style].Checkpoints[playerCheckpoint].Ticks; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX + + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY + + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ); + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint} = {pbTime}]"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint}] = {pbSpeed}"); +#endif } - #if DEBUG +#if DEBUG catch (System.Exception ex) - #else +#else catch (System.Exception) - #endif +#endif { - // Handle the exception gracefully without stopping the application + // Handle the exception gracefully without stopping // We assign default values to pbTime and pbSpeed pbTime = -1; // This determines if we will calculate differences or not!!! pbSpeed = 0.0f; - - #if DEBUG + +#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[{style}].Checkpoint.Count = {_player.Stats.PB[style].Checkpoint.Count}"); - #endif + 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].Checkpoints.Count}"); +#endif } // Calculate differences in PB (PB - Current) if (pbTime != -1) { - #if DEBUG +#if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting PB difference calculation... (pbTime != -1)"); - #endif +#endif // Reset the string strPbDifference = ""; @@ -203,17 +244,17 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi strPbDifference += ChatColors.Default + ")"; } - if (_player.CurrMap.WR[style].Ticks > 0) + if (SurfTimer.CurrentMap.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[{style}].Ticks > 0)"); - #endif - - 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); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)"); +#endif + + wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].Ticks; + wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX + + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY + + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ); // Reset the string strWrDifference = ""; @@ -242,17 +283,17 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Print checkpoint message _player.Controller.PrintToChat( - $"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " + + $"{Config.PluginPrefix} CP [{ChatColors.Yellow}{playerCheckpoint}{ChatColors.Default}]: " + $"{ChatColors.Yellow}{FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + $"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " + $"[PB: {strPbDifference} | " + $"WR: {strWrDifference}]"); - #if DEBUG +#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 +#endif } } diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 3904e35..6f5930a 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -1,23 +1,120 @@ +using CounterStrikeSharp.API.Core; +using Microsoft.VisualBasic; + namespace SurfTimer; internal class PlayerProfile { - public int ID {get; set;} = 0; - public string Name {get; set;} = ""; - public ulong SteamID {get; set;} = 0; - public string Country {get; set;} = ""; - public int JoinDate {get; set;} = 0; - public int LastSeen {get; set;} = 0; - public int Connections {get; set;} = 0; - - public PlayerProfile(int ID, string Name, ulong SteamID, string Country, int JoinDate, int LastSeen, int Connections) + public int ID { get; set; } = 0; + public string Name { get; set; } = ""; + public ulong SteamID { get; set; } = 0; + public string Country { get; set; } = ""; + public int JoinDate { get; set; } = 0; + public int LastSeen { get; set; } = 0; + public int Connections { get; set; } = 0; + + public PlayerProfile(ulong steamId, string name = "", string country = "") + { + this.SteamID = steamId; + this.Name = name; + this.Country = country; + } + + /// + /// Deals with retrieving, creating and updating a Player's information in the database upon joining the server. + /// Automatically detects whether to use API Calls or Queries. + /// + /// Steam ID of the player + /// Name of the player + /// Country of the player + /// PlayerProfile object + public static async Task CreateAsync(ulong steamId, string name = "", string country = "") + { + var profile = new PlayerProfile(steamId, name, country); + await profile.InitializeAsync(); + return profile; + } + + private async Task InitializeAsync() + { + await Get_Player_Profile(); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] We got ProfileID = {this.ID} ({this.Name})"); + } + + /// + /// Retrieves all the data for the player from the database. + /// + public async Task Get_Player_Profile() + { + bool newPlayer = false; + + // Load player profile data from database + using (var playerData = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, this.SteamID))) + { + if (playerData.HasRows && playerData.Read()) + { + // Player exists in database + this.ID = playerData.GetInt32("id"); + this.Name = playerData.GetString("name"); + if (this.Country == "XX" && playerData.GetString("country") != "XX") + this.Country = playerData.GetString("country"); + this.JoinDate = playerData.GetInt32("join_date"); + this.LastSeen = playerData.GetInt32("last_seen"); + this.Connections = playerData.GetInt32("connections"); + } + else + { + newPlayer = true; + } + } + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Returning player {this.Name} ({this.SteamID}) loaded from database with ID {this.ID}"); +#endif + if (newPlayer) + await Insert_Player_Profile(); + } + + /// + /// Insert new player information into the database. + /// Retrieves the ID of the newly created player. + /// + public async Task Insert_Player_Profile() + { + // Player does not exist in database + int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + int lastSeen = joinDate; + int connections = 1; + + // Write new player to database + int newPlayerRows = await SurfTimer.DB.WriteAsync(string.Format( + Config.MySQL.Queries.DB_QUERY_PP_INSERT_PROFILE, + MySqlConnector.MySqlHelper.EscapeString(this.Name), this.SteamID, this.Country, joinDate, lastSeen, connections)); + if (newPlayerRows != 1) + { + Exception ex = new($"Error inserting new player profile for '{this.Name}' ({this.SteamID})"); + throw ex; + } + + await Get_Player_Profile(); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Insert_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] New player {this.Name} ({this.SteamID}) added to database with ID {this.ID}"); +#endif + } + + /// + /// Updates the information in the database for the player. Increments `connections` and changes nickname. + /// + /// Player Name + /// + public async Task Update_Player_Profile(string name) { - this.ID = ID; - this.Name = Name; - this.SteamID = SteamID; - this.Country = Country; - this.JoinDate = JoinDate; - this.LastSeen = LastSeen; - this.Connections = Connections; + int updatePlayerTask = await SurfTimer.DB.WriteAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, this.Country, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.ID, name)); + if (updatePlayerTask != 1) + throw new Exception($"CS2 Surf ERROR >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Failed to update player data in database. Player: {this.Name} ({this.SteamID})"); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Updated player {name} ({this.SteamID}) in database. ID {this.ID}"); +#endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index bf51165..85b6254 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -3,10 +3,10 @@ namespace SurfTimer; internal class Checkpoint : PersonalBest { public int CP { get; set; } - public float EndTouch { get; set; } + public int 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) + public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int 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???? diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index f6fd2d0..52f49fd 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -5,7 +5,7 @@ namespace SurfTimer; /// internal class CurrentRun { - public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public Dictionary Checkpoints { 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 @@ -19,7 +19,7 @@ internal class CurrentRun // Constructor public CurrentRun() { - Checkpoint = new Dictionary(); + Checkpoints = new Dictionary(); Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; @@ -32,7 +32,7 @@ public CurrentRun() public void Reset() { - Checkpoint.Clear(); + Checkpoints.Clear(); Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; @@ -45,46 +45,152 @@ 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 + /// Saves the player's run to the database. /// - public void SaveMapTime(Player player, TimerDatabase DB) + /// Player object + /// Bonus number + /// Stage number + /// Ticks for the run - used for Stage and Bonus entries + public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1) { // Add entry in DB for the run - // To-do: add `type` + // PrintSituations(player); + string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Sending total of {replay_frames.Length} replay frames"); + if (Config.API.GetApiOnly()) + { + return; + } + else + { + await InsertMapTime(player, bonus, stage, run_ticks, replay_frames, true); + + if (stage != 0 || bonus != 0) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Inserted an entry for Stage {stage} - {run_ticks}"); + } + else + { + await SaveCurrentRunCheckpoints(player, true); // Save this run's checkpoints + } + + await player.CurrMap.Get_Map_Record_Runs(); // Reload the times for the Map + } + } + + public void PrintSituations(Player player) + { + Console.WriteLine($"========================== FOUND SITUATIONS =========================="); + for (int i = 0; i < player.ReplayRecorder.Frames.Count; i++) + { + ReplayFrame x = player.ReplayRecorder.Frames[i]; + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_ENTER: + Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + } + } + Console.WriteLine($"========================== =========================="); + } + + /// + /// Saves the CurrentRun of the player to the database. Does NOT support Bonus entries yet. + /// + /// Player object + /// Bonus number + /// Stage number + /// Ticks for the run + /// Replay frames + /// Whether to reload the PersonalBest data for the Player. + /// + public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false) + { + int playerId = player.Profile.ID; + int mapId = player.CurrMap.ID; 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`, `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()}, '{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), 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})"); - updatePlayerRunTask.Dispose(); + int ticks = run_ticks == -1 ? player.Stats.ThisRun.Ticks : run_ticks; + // int ticks = player.Stats.ThisRun.Ticks; + int type = stage != 0 ? 2 : bonus != 0 ? 1 : 0; + float startVelX = player.Stats.ThisRun.StartVelX; + float startVelY = player.Stats.ThisRun.StartVelY; + float startVelZ = player.Stats.ThisRun.StartVelZ; + float endVelX = player.Stats.ThisRun.EndVelX; + float endVelY = player.Stats.ThisRun.EndVelY; + float endVelZ = player.Stats.ThisRun.EndVelZ; + + if (Config.API.GetApiOnly()) // API Calls + { + // API Insert map goes here + } + else // MySQL Queries + { + int updatePlayerRunTask = await SurfTimer.DB.WriteAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, playerId, mapId, style, type, type == 2 ? stage : type == 1 ? bonus : 0, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), replay_frames)); + if (updatePlayerRunTask <= 0) + { + Exception ex = new($"CS2 Surf ERROR >> internal class CurrentRun -> public async Task InsertMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + throw ex; + } - // 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 + if (reloadData && type == 0) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.PB[player.Timer.Style].ID}))"); + await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + } + else if (reloadData && type == 1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.BonusPB[bonus][style].ID}))"); + await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + } + else if (reloadData && type == 2) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.StagePB[stage][style].ID}))"); + await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + } + } } /// /// 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 async Task SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) + /// Player object + /// Whether to reload the PersonalBest Checkpoints data for the Player. + public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false) { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> Will send {player.Stats.ThisRun.Checkpoints.Count} ({this.Checkpoints.Count}) checkpoints to DB...."); int style = player.Timer.Style; + int mapTimeId = player.Stats.PB[style].ID; List commands = new List(); // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Stats.ThisRun.Checkpoint) + // foreach (var item in player.Stats.ThisRun.Checkpoints) + foreach (var item in this.Checkpoints) { int cp = item.Key; int ticks = item.Value!.Ticks; - int runTime = item.Value!.Ticks / 64; // Runtime in decimal + int endTouch = item.Value!.EndTouch; double startVelX = item.Value!.StartVelX; double startVelY = item.Value!.StartVelY; double startVelZ = item.Value!.StartVelZ; @@ -93,35 +199,33 @@ public async Task SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) double endVelZ = item.Value!.EndVelZ; int attempts = item.Value!.Attempts; - #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); +#if DEBUG + Console.WriteLine($"CP: {cp} | MapTime ID: {mapTimeId} | Time: {endTouch} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($@"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> + {string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch)} "); - #endif +#endif // Insert/Update CPs to database // 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) { - string command = $@" - 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); - "; + string command = string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch + ); commands.Add(command); } } - await DB.Transaction(commands); - player.Stats.ThisRun.Checkpoint.Clear(); + await SurfTimer.DB.TransactionAsync(commands); + player.Stats.ThisRun.Checkpoints.Clear(); + + if (reloadData) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveCurrentRunCheckpoints -> Will reload Checkpoints data for {player.Profile.Name} (ID {player.Stats.PB[player.Timer.Style].ID})"); + await player.Stats.PB[player.Timer.Style].PB_LoadCheckpointsData(); // Load the Checkpoints data again + } } } diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 2ee50bc..a689a00 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -1,14 +1,16 @@ namespace SurfTimer; -// To-do: make Style (currently 0) be dynamic -// To-do: add `Type` +/// +/// As the PersonalBest object is being used for each different style, we shouldn't need a separate `Style` variable in here because each style entry will have unique ID in the Database +/// and will therefore be a unique PersonalBest entry. +/// 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 Dictionary Checkpoints { get; set; } + public int Type { get; set; } // Identifies bonus # - 0 for map time -> huh, why o_O? public float StartVelX { get; set; } public float StartVelY { get; set; } public float StartVelZ { get; set; } @@ -22,15 +24,199 @@ internal class PersonalBest // 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; + Ticks = -1; + Checkpoints = new Dictionary(); + Type = -1; StartVelX = -1.0f; StartVelY = -1.0f; StartVelZ = -1.0f; EndVelX = -1.0f; EndVelY = -1.0f; EndVelZ = -1.0f; - RunDate = 0; + RunDate = -1; + } + + /// + /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. + /// Automatically detects whether to use API Calls or MySQL query. + /// Bonus and Stage runs should NOT have any checkpoints. + /// + public async Task PB_LoadCheckpointsData() + { + if (this == null) + { +#if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PersonalBest object is null."); +#endif + return; + } + if (this.Checkpoints == null) + { +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PB Checkpoints list is not initialized."); +#endif + this.Checkpoints = new Dictionary(); // Initialize if null + } + + if (Config.API.GetApiOnly()) // Load with API + { + var checkpoints = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, this.ID)); + if (checkpoints == null || checkpoints.Length == 0) + return; + + foreach (API_Checkpoint checkpoint in checkpoints) + { + Checkpoint cp = new Checkpoint + ( + checkpoint.cp, + checkpoint.run_time, + checkpoint.start_vel_x, + checkpoint.start_vel_y, + checkpoint.start_vel_z, + checkpoint.end_vel_x, + checkpoint.end_vel_y, + checkpoint.end_vel_z, + checkpoint.end_touch, + checkpoint.attempts + ); + cp.ID = checkpoint.cp; + + this.Checkpoints[cp.CP] = cp; + } + } + else // Load with MySQL + { + using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, this.ID))) + { +#if DEBUG + Console.WriteLine($"this.Checkpoint.Count {this.Checkpoints.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 PersonalBest -> public async Task PB_LoadCheckpointsData -> No checkpoints found for this mapTimeId {this.ID}."); +#endif + + return; + } + +#if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Checkpoints found for this mapTimeId"); +#endif + + while (results.Read()) + { +#if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetInt32("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"), + 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("end_touch"), + results.GetInt32("attempts")); + cp.ID = results.GetInt32("cp"); + // To-do: cp.ID = calculate Rank # from DB + + this.Checkpoints[cp.CP] = cp; + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); +#endif + } + } + } + + // #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] {this.Checkpoints.Count} Checkpoints loaded from DB for run ID {this.ID}."); + // #endif + } + + /// + /// Loads specific type/style MapTime data for the player (run without checkpoints) from the database for their personal best runs. + /// Should be used to reload data from a specific `PersonalBest` object + /// + /// Player object + public async Task PB_LoadPlayerSpecificMapTimeData(Player player) + { + // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); + // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) + if (this == null) + { +#if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null."); +#endif + + return; + } + + MySqlConnector.MySqlDataReader? results = null; + + // Console.WriteLine(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + + if (this.ID == -1) + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + else + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, this.ID)); + +#if DEBUG + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.ID {this.ID} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.Ticks {this.Ticks} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.RunDate {this.RunDate} "); +#endif + + if (results == null || !results.HasRows) + { + // #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{player.Profile.Name}' ({player.Profile.ID}). (Null? {results == null})"); + // #endif + + return; + } + +#if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Found a run for '{player.Profile.Name}' ({player.Profile.ID})."); +#endif + + while (results.Read()) + { +#if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetInt32("run_time")} "); + Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); + Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); +#endif + + this.ID = results.GetInt32("id"); + this.Ticks = results.GetInt32("run_time"); + this.Rank = results.GetInt32("rank"); + this.StartVelX = (float)results.GetDouble("start_vel_x"); + this.StartVelY = (float)results.GetDouble("start_vel_y"); + this.StartVelZ = (float)results.GetDouble("start_vel_z"); + this.EndVelX = (float)results.GetDouble("end_vel_x"); + this.EndVelY = (float)results.GetDouble("end_vel_y"); + this.EndVelZ = (float)results.GetDouble("end_vel_z"); + this.RunDate = results.GetInt32("run_date"); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Loaded run (MapId = {this.ID}) by {player.Profile.Name} - {PlayerHUD.FormatTime(this.Ticks)}"); +#endif + } + + // #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> MapTime (Type: {this.Type}) loaded from DB. ID: {this.ID} for {player.Profile.Name}"); + // #endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 48eed71..3e8c662 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -7,144 +7,246 @@ 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 - } + // /// + // /// Stage Personal Best - Refer to as StagePB[style][stage#] + // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE + // /// + // public int[,] StagePB { get; set; } = { { 0, 0 } }; + // /// + // /// Stage Personal Best - Refer to as StageRank[style][stage#] + // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE + // /// + // public int[,] StageRank { get; set; } = { { 0, 0 } }; /// - /// 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 + /// Map Personal Best - Refer to as PB[style] + /// + public Dictionary PB { get; set; } = new Dictionary(); + /// + /// Bonus Personal Best - Refer to as BonusPB[bonus#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of bonuses + /// + public Dictionary[] BonusPB { get; set; } = new Dictionary[32]; + /// + /// Stage Personal Best - Refer to as StagePB[stage#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of stages + /// + public Dictionary[] StagePB { get; set; } = new Dictionary[32]; + /// + /// This object tracks data for the Player's current run. /// - public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) + public CurrentRun ThisRun { get; set; } = new CurrentRun(); + + // Initialize PersonalBest for each `style` (e.g., 0 for normal) + // Here we can loop through all available styles at some point and initialize them + public PlayerStats() { - 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) + // Initialize MapPB for each style + foreach (int style in Config.Styles) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); + PB[style] = new PersonalBest(); + PB[style].Type = 0; } - else + + int initialized = 0; + for (int i = 0; i < 32; i++) { - 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 - } + this.BonusPB[i] = new Dictionary(); + this.BonusPB[i][0] = new PersonalBest(); // To-do: Implement styles + this.BonusPB[i][0].Type = 1; + + this.StagePB[i] = new Dictionary(); + this.StagePB[i][0] = new PersonalBest(); // To-do: Implement styles + this.StagePB[i][0].Type = 2; + initialized++; } - playerStats.Close(); + Console.WriteLine($"====== INITIALIZED {initialized} STAGES AND BONUSES FOR PLAYERSTATS"); } - /// - /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary - /// - public void LoadCheckpointsData(TimerDatabase DB) + // API + public async void LoadMapTime(Player player, int style = 0) { - Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {PB[0].ID};"); - MySqlDataReader results = dbTask.Result; - if (PB[0] == null) + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=0"); + if (player_maptime == null) { - #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class PlayerStats -> LoadCheckpointsData -> PersonalBest object is null."); - #endif - - results.Close(); + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No MapTime data found for Player."); return; } - if (PB[0].Checkpoint == null) + PB[style].ID = player_maptime.id; + PB[style].Ticks = player_maptime.run_time; + PB[style].Type = player_maptime.type; + PB[style].StartVelX = player_maptime.start_vel_x; + PB[style].StartVelY = player_maptime.start_vel_y; + PB[style].StartVelZ = player_maptime.start_vel_z; + PB[style].EndVelX = player_maptime.end_vel_x; + PB[style].EndVelY = player_maptime.end_vel_y; + PB[style].EndVelZ = player_maptime.end_vel_z; + // PB[style].RunDate = player_maptime.run_date ?? 0; + PB[style].RunDate = player_maptime.run_date; + + if (player_maptime.checkpoints == null) { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadCheckpointsData -> PB Checkpoints list is not initialized."); - #endif + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No Checkpoints data found for Player."); + return; + } - PB[0].Checkpoint = new Dictionary(); // Initialize if null + foreach (var cp in player_maptime.checkpoints) + { + PB[style].Checkpoints[cp.cp] = new Checkpoint(cp.cp, cp.run_time, cp.start_vel_x, cp.start_vel_y, cp.start_vel_z, cp.end_vel_x, cp.end_vel_y, cp.end_vel_z, cp.end_touch, cp.attempts); } + } - #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 + // API + public async void LoadStageTime(Player player, int style = 0) + { + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=2"); + if (player_maptime == null) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadStageTime -> No MapTime data found for Player."); + return; + } - if (!results.HasRows) + foreach (API_MapTime mt in player_maptime) { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> No checkpoints found for this mapTimeId {PB[0].ID}."); - #endif + StagePB[mt.stage][style].ID = mt.id; + StagePB[mt.stage][style].Ticks = mt.run_time; + StagePB[mt.stage][style].Type = mt.type; + StagePB[mt.stage][style].StartVelX = mt.start_vel_x; + StagePB[mt.stage][style].StartVelY = mt.start_vel_y; + StagePB[mt.stage][style].StartVelZ = mt.start_vel_z; + StagePB[mt.stage][style].EndVelX = mt.end_vel_x; + StagePB[mt.stage][style].EndVelY = mt.end_vel_y; + StagePB[mt.stage][style].EndVelZ = mt.end_vel_z; + // StagePB[mt.stage][style].RunDate = mt.run_date ?? 0; + StagePB[mt.stage][style].RunDate = mt.run_date; + } + } - results.Close(); + // API + public async void LoadBonusTime(Player player, int style = 0) + { + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=1"); + if (player_maptime == null) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadBonusTime -> No MapTime data found for Player."); return; } - #if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Checkpoints found for this mapTimeId"); - #endif + foreach (API_MapTime mt in player_maptime) + { + BonusPB[mt.stage][style].ID = mt.id; + BonusPB[mt.stage][style].Ticks = mt.run_time; + BonusPB[mt.stage][style].Type = mt.type; + BonusPB[mt.stage][style].StartVelX = mt.start_vel_x; + BonusPB[mt.stage][style].StartVelY = mt.start_vel_y; + BonusPB[mt.stage][style].StartVelZ = mt.start_vel_z; + BonusPB[mt.stage][style].EndVelX = mt.end_vel_x; + BonusPB[mt.stage][style].EndVelY = mt.end_vel_y; + BonusPB[mt.stage][style].EndVelZ = mt.end_vel_z; + // BonusPB[mt.stage][style].RunDate = mt.run_date ?? 0; + BonusPB[mt.stage][style].RunDate = mt.run_date; + } + } + - while (results.Read()) + /// + /// Loads the player's map time data from the database along with their ranks. For all types and styles (may not work correctly for Stages/Bonuses) + /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. + /// This populates all the `style` and `type` stats the player has for the map + /// + public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0) + { + using (var playerStats = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) { - #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 + // int style = player.Timer.Style; + int style; + if (!playerStats.HasRows) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> No MapTimes data found for Player ({player.Profile.ID})."); + return; + } + while (playerStats.Read()) + { + // Load data into each PersonalBest object + if (playerStats.GetInt32("type") == 1) // Bonus time + { +#if DEBUG + System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> BonusPB"); +#endif + int bonusNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + BonusPB[bonusNum][style].ID = playerStats.GetInt32("id"); + BonusPB[bonusNum][style].Ticks = playerStats.GetInt32("run_time"); + BonusPB[bonusNum][style].Type = playerStats.GetInt32("type"); + BonusPB[bonusNum][style].Rank = playerStats.GetInt32("rank"); + BonusPB[bonusNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + BonusPB[bonusNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + BonusPB[bonusNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + BonusPB[bonusNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + BonusPB[bonusNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + BonusPB[bonusNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + BonusPB[bonusNum][style].RunDate = playerStats.GetInt32("run_date"); + } + else if (playerStats.GetInt32("type") == 2) // Stage time + { +#if DEBUG + System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> StagePB"); +#endif + int stageNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + StagePB[stageNum][style].ID = playerStats.GetInt32("id"); + StagePB[stageNum][style].Ticks = playerStats.GetInt32("run_time"); + StagePB[stageNum][style].Type = playerStats.GetInt32("type"); + StagePB[stageNum][style].Rank = playerStats.GetInt32("rank"); + StagePB[stageNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + StagePB[stageNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + StagePB[stageNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + StagePB[stageNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); + Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: + StagePB[{stageNum}][{style}] = + Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}"); + } + else // Map time + { +#if DEBUG + System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> MapPB"); +#endif + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + PB[style].ID = playerStats.GetInt32("id"); + PB[style].Ticks = playerStats.GetInt32("run_time"); + PB[style].Type = playerStats.GetInt32("type"); + PB[style].Rank = playerStats.GetInt32("rank"); + 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].RunDate = playerStats.GetInt32("run_date"); + Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: + PB[{style}] = + ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}"); + await this.PB[style].PB_LoadCheckpointsData(); + } + // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 -> public async Task LoadPlayerMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); +#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 + // // This would have to go inside the `Map Time` else statement in order to load checkpoints for each `style` + // if (PB[player.Timer.Style].ID != -1) + // { + // // await LoadCheckpointsData(player); + // await this.PB[player.Timer.Style].PB_LoadCheckpointsData(); + // } } + } \ No newline at end of file diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 5878205..b87ef7d 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -10,18 +10,19 @@ internal class PlayerTimer // Modes public bool IsPracticeMode { get; set; } = false; // Practice mode toggle public bool IsStageMode { get; set; } = false; // Stage mode toggle + public bool IsBonusMode { get; set; } = false; // Bonus mode toggle // Tracking public int Stage { get; set; } = 0; // Current stage tracker public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public CurrentRun CurrentRunData { get; set; } = new CurrentRun(); // Current RUN data 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 { 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 - // Time Formatting + // Time Formatting - To-do: Move to player settings maybe? public enum TimeFormatStyle { Compact, @@ -39,7 +40,8 @@ public void Reset() this.IsPaused = false; this.IsPracticeMode = false; this.IsStageMode = false; - this.CurrentRunData.Reset(); + this.IsBonusMode = false; + // this.CurrentRunData.Reset(); } public void Pause() diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index f67de67..a5a8b11 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -1,24 +1,45 @@ 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 + + STAGE_ZONE_ENTER, + STAGE_ZONE_EXIT, + + START_ZONE_ENTER, + START_ZONE_EXIT, + + END_ZONE_ENTER, + END_ZONE_EXIT, + + CHECKPOINT_ZONE_ENTER, + CHECKPOINT_ZONE_EXIT, + + // START_RUN, + // END_RUN, + // TOUCH_CHECKPOINT, + // START_STAGE, + // END_STAGE, + // ENTER_STAGE, } [Serializable] -internal class ReplayFrame +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 float[] pos { get; set; } = { 0, 0, 0 }; + public float[] ang { get; set; } = { 0, 0, 0 }; + public ReplayFrameSituation Situation { get; set; } = ReplayFrameSituation.NONE; public uint Flags { get; set; } - public MoveType_t MoveType { get; set; } + + public Vector GetPos() + { + return new Vector(this.pos[0], this.pos[1], this.pos[2]); + } + + public QAngle GetAng() + { + return new QAngle(this.ang[0], this.ang[1], this.ang[2]); + } } diff --git a/src/ST-Player/Replay/ReplayManager.cs b/src/ST-Player/Replay/ReplayManager.cs new file mode 100644 index 0000000..45fef14 --- /dev/null +++ b/src/ST-Player/Replay/ReplayManager.cs @@ -0,0 +1,97 @@ +using System.Text.Json; +using CounterStrikeSharp.API.Core; + +namespace SurfTimer; + +internal class ReplayManager +{ + public ReplayPlayer MapWR { get; set; } + public ReplayPlayer? BonusWR { get; set; } = null; + public ReplayPlayer? StageWR { get; set; } = null; + /// + /// Contains all all Stage records for all styles - Refer to as AllStageWR[stage#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of Stages + /// + public Dictionary[] AllStageWR { get; set; } = new Dictionary[32]; + public List CustomReplays { get; set; } + + /// + /// + /// + /// ID of the map + /// Does the map have Stages + /// Does the map have Bonuses + /// Frames for the replay + /// Run time (Ticks) for the run + /// Name of the player + /// ID of the run + /// Style of the run + /// Stage/Bonus of the run + public ReplayManager(int map_id, bool staged, bool bonused, List frames, int run_time = 0, string playerName = "", int map_time_id = -1, int style = 0, int stage = 0) + { + MapWR = new ReplayPlayer + { + Type = 0, + Stage = 0, + RecordRank = 1, + MapID = map_id, + Frames = frames, + RecordRunTime = run_time, + RecordPlayerName = playerName, + MapTimeID = map_time_id + }; + + if (staged) + { + // Initialize 32 Stages for each style + // TODO: Make the amount of stages and bonuses dynamic + for (int i = 0; i < 32; i++) + // for (int i = 0; i < SurfTimer.CurrentMap.Stages; i++) + { + AllStageWR[i] = new Dictionary(); + foreach (int x in Config.Styles) + { + AllStageWR[i][x] = new ReplayPlayer(); + } + } + StageWR = new ReplayPlayer(); + } + + if (bonused) + { + BonusWR = new ReplayPlayer + { + Type = 1, + Stage = 1, + RecordRank = 1, + MapID = map_id, + Frames = frames, + RecordRunTime = run_time, + RecordPlayerName = playerName, + MapTimeID = map_time_id + }; + } + + CustomReplays = new List(); + } + + public bool IsControllerConnectedToReplayPlayer(CCSPlayerController controller) + { + if (this.MapWR.Controller?.Equals(controller) == true) + return true; + + if (this.StageWR?.Controller?.Equals(controller) == true) + return true; + + if (this.BonusWR?.Controller?.Equals(controller) == true) + return true; + + foreach (var replay in this.CustomReplays) + { + if (replay.Controller?.Equals(controller) == true) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 98a44c4..bc9dc0d 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -1,14 +1,15 @@ -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 { + /// + /// Enable or Disable the replay bots. + /// + public bool IsEnabled { get; set; } = Config.ReplaysEnabled; public bool IsPlaying { get; set; } = false; public bool IsPaused { get; set; } = false; public bool IsPlayable { get; set; } = false; @@ -16,16 +17,33 @@ internal class ReplayPlayer // 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; + public int MapID { get; set; } = -1; + public int MapTimeID { get; set; } = -1; + public int Type { get; set; } = -1; + public int Stage { get; set; } = -1; + + public int RecordRank { get; set; } = -1; // This is used to determine whether replay is for wr or for pb + public string RecordPlayerName { get; set; } = "N/A"; + public int RecordRunTime { get; set; } = -1; + public int ReplayCurrentRunTime { get; set; } = 0; + public bool IsReplayOutsideZone { get; set; } = false; // Tracking public List Frames { get; set; } = new List(); + public List StageEnterSituations { get; set; } = new List(); + public List StageExitSituations { get; set; } = new List(); + public List CheckpointEnterSituations { get; set; } = new List(); + public List CheckpointExitSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List MapSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List BonusSituations { get; set; } = new List(); // Playing public int CurrentFrameTick { get; set; } = 0; @@ -33,18 +51,18 @@ internal class ReplayPlayer public CCSPlayerController? Controller { get; set; } - public void ResetReplay() + public void ResetReplay() { this.CurrentFrameTick = 0; this.FrameTickIncrement = 1; - if(this.RepeatCount > 0) + if (this.RepeatCount > 0) this.RepeatCount--; - this.Stat_IsRunning = false; - this.Stat_RunTick = 0; + this.IsReplayOutsideZone = false; + this.ReplayCurrentRunTime = 0; } - public void Reset() + public void Reset() { this.IsPlaying = false; this.IsPaused = false; @@ -61,35 +79,44 @@ public void Reset() public void SetController(CCSPlayerController c, int repeat_count = -1) { this.Controller = c; - this.RepeatCount = repeat_count; + if (repeat_count != -1) + this.RepeatCount = repeat_count; this.IsPlayable = true; + + // Console.WriteLine($"===== public void SetController -> Set controller for {c.PlayerName}"); } - public void Start() + public void Start() { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; this.IsPlaying = true; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Start() -> Starting replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Stop() + public void Stop() { this.IsPlaying = false; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Stop() -> Stopping replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Pause() + public void Pause() { - if (!this.IsPlaying) + if (!this.IsPlaying || !this.IsEnabled) return; this.IsPaused = !this.IsPaused; - this.Stat_IsRunning = !this.Stat_IsRunning; + this.IsReplayOutsideZone = !this.IsReplayOutsideZone; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Pause() -> Pausing replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Tick() + public void Tick() { - if (!this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) + if (this.MapID == -1 || !this.IsEnabled || !this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) return; ReplayFrame current_frame = this.Frames[this.CurrentFrameTick]; @@ -97,102 +124,105 @@ public void Tick() // SOME BLASHPEMY FOR YOU if (this.FrameTickIncrement >= 0) { - if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + if (current_frame.Situation == ReplayFrameSituation.START_ZONE_EXIT) { - this.Stat_IsRunning = true; - this.Stat_RunTick = 0; + this.IsReplayOutsideZone = true; + this.ReplayCurrentRunTime = 0; } - else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + else if (current_frame.Situation == ReplayFrameSituation.END_ZONE_ENTER) { - this.Stat_IsRunning = false; + this.IsReplayOutsideZone = false; } } else { - if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + if (current_frame.Situation == ReplayFrameSituation.START_ZONE_EXIT) { - this.Stat_IsRunning = false; + this.IsReplayOutsideZone = false; } - else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + else if (current_frame.Situation == ReplayFrameSituation.END_ZONE_ENTER) { - this.Stat_IsRunning = true; - this.Stat_RunTick = this.CurrentFrameTick - (64*2); // (64*2) counts for the 2 seconds before run actually starts + this.IsReplayOutsideZone = true; + this.ReplayCurrentRunTime = 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!; + var current_frame_pos = current_frame.GetPos(); + var current_frame_ang = current_frame.GetAng(); bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; - Vector velocity = (current_frame.Pos - current_pos) * 64; + 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 ((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.Stat_IsRunning) - this.Stat_RunTick = Math.Max(0, this.Stat_RunTick + this.FrameTickIncrement); + if (this.IsReplayOutsideZone) + this.ReplayCurrentRunTime = Math.Max(0, this.ReplayCurrentRunTime + this.FrameTickIncrement); } - if(this.CurrentFrameTick >= this.Frames.Count) + if (this.CurrentFrameTick >= this.Frames.Count) this.ResetReplay(); + // if(RepeatCount != -1) // Spam City + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> Tick -> ====================> {this.RepeatCount} <===================="); } - public void LoadReplayData(TimerDatabase DB) + public void LoadReplayData(int repeat_count = -1) { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; - Task dbTask = DB.Query($@" - 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} - "); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] public void LoadReplayData -> We got MapID = {this.MapID}"); - MySqlDataReader mapTimeReplay = dbTask.Result; - if(!mapTimeReplay.HasRows) + if (this.MapID == -1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerReplay -> Load -> No replay data found for Player."); + Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] No replay data found for Player."); + return; } - 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)!; - this.Stat_RunTime = mapTimeReplay.GetInt32("run_time"); - this.Stat_PlayerName = mapTimeReplay.GetString("name"); - } - FormatBotName(); - } - mapTimeReplay.Close(); - dbTask.Dispose(); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] Loaded replay data for Player '{this.RecordPlayerName}'. MapTime ID: {this.MapTimeID} | Repeat {repeat_count} | Frames {this.Frames.Count} | Ticks {this.RecordRunTime}"); + this.ResetReplay(); + this.RepeatCount = repeat_count; } - private void FormatBotName() + public void FormatBotName() { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; + string prefix; + if (this.RecordRank == 1) + { + prefix = "WR"; + } + else + { + prefix = $"Rank #{this.RecordRank}"; + } + + if (this.Type == 1) + prefix = prefix + $" B{this.Stage}"; + else if (this.Type == 2) + prefix = prefix + $" S{this.Stage}"; + 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}"; + string replay_name = $"[{prefix}] {this.RecordPlayerName} | {PlayerHUD.FormatTime(this.RecordRunTime)}"; + if (this.RecordRunTime <= 0) + replay_name = $"[{prefix}] {this.RecordPlayerName}"; bot_name.Set(replay_name); Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName"); diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index fd20ed5..43dd734 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -1,40 +1,63 @@ using System.Text.Json; using CounterStrikeSharp.API.Modules.Utils; - namespace SurfTimer; internal class ReplayRecorder { public bool IsRecording { get; set; } = false; + public bool IsSaving { get; set; } = false; public ReplayFrameSituation CurrentSituation { get; set; } = ReplayFrameSituation.NONE; public List Frames { get; set; } = new List(); + public List StageEnterSituations { get; set; } = new List(); + public List StageExitSituations { get; set; } = new List(); + public List CheckpointEnterSituations { get; set; } = new List(); + public List CheckpointExitSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List MapSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List BonusSituations { get; set; } = new List(); - public void Reset() + public void Reset() { this.IsRecording = false; this.Frames.Clear(); + this.StageEnterSituations.Clear(); + this.StageExitSituations.Clear(); + this.CheckpointEnterSituations.Clear(); + this.CheckpointExitSituations.Clear(); + this.MapSituations.Clear(); + this.BonusSituations.Clear(); + + Console.WriteLine($"===== ReplayRecorder -> Reset() -> Recording has been reset"); } - public void Start() + public void Start() { this.IsRecording = true; } - public void Stop() + public void Stop() { this.IsRecording = false; } - public void Tick(Player player) + public void Tick(Player player) { if (!this.IsRecording || player == null) return; - // Disabeling Recording if timer disabled - if (!player.Timer.IsEnabled) + // Disabling Recording if timer disabled + if (!player.Timer.IsEnabled) { this.Stop(); this.Reset(); + Console.WriteLine($"===== ReplayRecorder -> Tick() -> Recording has stopped and reset"); return; } @@ -43,15 +66,35 @@ public void Tick(Player player) 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 +/* + switch (this.CurrentSituation) { - 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, + case ReplayFrameSituation.START_ZONE_ENTER: + player.Controller.PrintToChat($"Start Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.Controller.PrintToChat($"Start Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.Controller.PrintToChat($"Stage Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.Controller.PrintToChat($"Stage Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.Controller.PrintToChat($"Checkpoint Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.Controller.PrintToChat($"Checkpoint Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + } +*/ + var frame = new ReplayFrame + { + pos = [player_pos.X, player_pos.Y, player_pos.Z], + ang = [player_angle.X, player_angle.Y, player_angle.Z], + Situation = this.CurrentSituation, Flags = player_flags, - MoveType = player_move_type, }; this.Frames.Add(frame); @@ -62,8 +105,190 @@ public void Tick(Player player) public string SerializeReplay() { - JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; - string replay_frames = JsonSerializer.Serialize(Frames, options); + // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + // string replay_frames = JsonSerializer.Serialize(Frames, options); + string replay_frames = JsonSerializer.Serialize(Frames); return Compressor.Compress(replay_frames); } + + public string SerializeReplayPortion(int start_idx, int end_idx) // Not used anymore + { + // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + // string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx), options); + string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx)); + return Compressor.Compress(replay_frames); + } + + public void SetLastTickSituation(ReplayFrameSituation situation) + { + if (this.Frames.Count == 0) + return; + + this.Frames[this.Frames.Count - 2].Situation = situation; + } + + public string TrimReplay(Player player, int type = 0, bool lastStage = false) + { + this.IsSaving = true; + + List new_frames = new List(); + + if (this.Frames.Count == 0) + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> There are no Frames for trimming"); + throw new Exception("There are no Frames available for trimming"); + } + switch (type) + { + case 0: // Trim Map replays + // Map/Bonus runs + var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Map Run replay. Last start enter {start_enter_index}, last start exit {start_exit_index}, end enter {end_enter_index}"); + + if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (start_exit_index - (Config.ReplaysPre * 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre * 2); + else if (start_exit_index - Config.ReplaysPre >= start_enter_index) + startIndex = start_exit_index - Config.ReplaysPre; + else if (start_exit_index - (Config.ReplaysPre / 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre / 2); + else + startIndex = start_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre * 2); + else if (end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = end_enter_index + Config.ReplaysPre; + else if (end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = end_enter_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } + break; + case 1: // Trim Bonus replays + break; + case 2: // Trim Stage replays + int stage_end_index; + int stage_exit_index; + int stage_enter_index; + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Will trim Stage Run replay. Stage {player.Timer.Stage - 1}, available frames {Frames.Count}"); + + // Stage runs + if (lastStage) // Last stage + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + else if (player.Timer.Stage - 1 > 1) // Not first stage + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + else // First stage is always the start of the map so we are looking for START_ZONE_X + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `START_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Stage Run replay. Stage {player.Timer.Stage - 1} enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}"); + + if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (stage_exit_index - (Config.ReplaysPre * 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre * 2); + else if (stage_exit_index - Config.ReplaysPre >= stage_enter_index) + startIndex = stage_exit_index - Config.ReplaysPre; + else if (stage_exit_index - (Config.ReplaysPre / 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre / 2); + else + startIndex = stage_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (stage_end_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre * 2); + else if (stage_end_index + Config.ReplaysPre < Frames.Count) + endIndex = stage_end_index + Config.ReplaysPre; + else if (stage_end_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = stage_end_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } + break; + } + + this.IsSaving = false; + string trimmed = JsonSerializer.Serialize(new_frames); + return Compressor.Compress(trimmed); + } + + public int LastEnterTick(int start_idx = 0) + { + if (start_idx == 0) + start_idx = this.Frames.Count - 1; + for (int i = start_idx; i > 0; i--) + { + if ( + this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_ENTER + ) + return i; + } + return 0; + } + + public int LastExitTick(int start_idx = 0) + { + if (start_idx == 0) + start_idx = this.Frames.Count - 1; + for (int i = start_idx; i > 0; i--) + { + if ( + this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_EXIT + ) + return i; + } + return 0; + } } \ No newline at end of file diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs new file mode 100644 index 0000000..d972cb3 --- /dev/null +++ b/src/ST-UTILS/Config.cs @@ -0,0 +1,206 @@ +using System.Reflection; +using System.Text.Json; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Utils; + +namespace SurfTimer; + +public static class Config +{ + public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; + public static string PluginPrefix = $"[{ChatColors.DarkBlue}CS2 Surf{ChatColors.Default}]"; // To-do: make configurable + public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; + public static string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; + public static string ApiUrl => API.GetApiUrl(); + public static string DbConnectionString => MySQL.GetConnectionString(); + + /// + /// Placeholder for amount of styles + /// + public static List Styles = new List { 0 }; // Add all supported style IDs + + public static bool ReplaysEnabled => true; + public static int ReplaysPre => 64; + // Helper class/methods for configuration loading + private static class ConfigLoader + { + private static readonly Dictionary _configDocuments = new(); + + public static JsonDocument GetConfigDocument(string configPath) + { + if (!_configDocuments.ContainsKey(configPath)) + { + var fullPath = Server.GameDirectory + configPath; + _configDocuments[configPath] = JsonDocument.Parse(File.ReadAllText(fullPath)); + } + return _configDocuments[configPath]; + } + } + + public static class API + { + private const string API_CONFIG_PATH = "/csgo/cfg/SurfTimer/api_config.json"; + + private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(API_CONFIG_PATH); + + /// + /// Retrieves the `api_url` string from the configuration path + /// + /// A value containing the URL. + public static string GetApiUrl() + { + return ConfigDocument.RootElement.GetProperty("api_url").GetString()!; + } + + /// + /// Retrieves the `api_enabled` value from the configuration path + /// + /// A value for whether the API should be used. + public static bool GetApiOnly() + { + return ConfigDocument.RootElement.GetProperty("api_enabled").GetBoolean(); + } + + /// + /// Contains all the endpoints used by the API for the SurfTimer plugin. + /// + public static class Endpoints + { + // Map.cs related endpoints + public const string ENDPOINT_MAP_GET_INFO = "/surftimer/mapinfo?mapname={0}"; + public const string ENDPOINT_MAP_INSERT_INFO = "/surftimer/insertmap"; + public const string ENDPOINT_MAP_UPDATE_INFO = "/surftimer/updateMap"; + // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maptotals?map_id={0}&style={1}"; + public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?map_id={0}&style={1}&type={2}"; + public const string ENDPOINT_MAP_GET_RUN_CPS = "/surftimer/mapcheckpointsdata?maptime_id={0}"; + + // CurrentRun.cs + public const string ENDPOINT_CR_SAVE_STAGE_TIME = "/surftimer/savestagetime"; + } + } + + public static class MySQL + { + private const string DB_CONFIG_PATH = "/csgo/cfg/SurfTimer/database.json"; + + private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); + + /// + /// Retrieves the connection details for connecting to the MySQL Database + /// + /// A connection + public static string GetConnectionString() + { + string host = ConfigDocument.RootElement.GetProperty("host").GetString()!; + string database = ConfigDocument.RootElement.GetProperty("database").GetString()!; + string user = ConfigDocument.RootElement.GetProperty("user").GetString()!; + string password = ConfigDocument.RootElement.GetProperty("password").GetString()!; + int port = ConfigDocument.RootElement.GetProperty("port").GetInt32()!; + int timeout = ConfigDocument.RootElement.GetProperty("timeout").GetInt32()!; + + string connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; + + // Console.WriteLine($"============= [CS2 Surf] Extracted connection string: {connString}"); + + return connString; + } + + /// + /// Contains all the queries used by MySQL for the SurfTimer plugin. + /// + public static class Queries + { + // Map.cs related queries + public const string DB_QUERY_MAP_GET_RUNS = @" + SELECT MapTimes.*, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.map_id = {0} AND MapTimes.style = {1} AND MapTimes.type = {2} + ORDER BY MapTimes.run_time ASC; + "; // Deprecated + public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; + public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {5})"; // "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()})" + public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2} WHERE id={3};"; + public const string DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT = @" + SELECT + ranked_times.* + FROM ( + SELECT + MapTimes.*, + Player.name, + ROW_NUMBER() OVER ( + PARTITION BY MapTimes.type, MapTimes.stage + ORDER BY MapTimes.run_time ASC + ) AS row_num, + COUNT(*) OVER (PARTITION BY MapTimes.type, MapTimes.stage) AS total_count + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.map_id = {0} + ) AS ranked_times + WHERE ranked_times.row_num = 1;"; + + + // PlayerStats.cs related queries + public const string DB_QUERY_PS_GET_ALL_RUNTIMES = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1}; + "; // Deprecated + + // PersonalBest.cs related queries + public const string DB_QUERY_PB_GET_TYPE_RUNTIME = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1} AND mainquery.`type` = {2} AND mainquery.`style` = {3}; + "; + public const string DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`id` = {0}; + "; + public const string DB_QUERY_PB_GET_CPS = "SELECT * FROM `Checkpoints` WHERE `maptime_id` = {0};"; + + // CurrentRun.cs related queries + public const string DB_QUERY_CR_INSERT_TIME = @" + 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`, `replay_frames`) + VALUES ({0}, {1}, {2}, {3}, {4}, {5}, + {6}, {7}, {8}, {9}, {10}, {11}, {12}, '{13}') + 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), replay_frames=VALUES(replay_frames); + "; + public const string DB_QUERY_CR_INSERT_CP = @" + 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 ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}) + 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); + "; + + // ReplayPlayer.cs related queries + public const string DB_QUERY_RP_LOAD_REPLAY = @" + SELECT MapTimes.replay_frames, MapTimes.run_time, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.id={0}; + "; + + // Players.cs related queries + public const string DB_QUERY_PP_GET_PROFILE = "SELECT * FROM `Player` WHERE `steam_id` = {0} LIMIT 1;"; + public const string DB_QUERY_PP_INSERT_PROFILE = @" + INSERT INTO `Player` (`name`, `steam_id`, `country`, `join_date`, `last_seen`, `connections`) + VALUES ('{0}', {1}, '{2}', {3}, {4}, {5}); + "; + public const string DB_QUERY_PP_UPDATE_PROFILE = @" + UPDATE `Player` SET country = '{0}', + `last_seen` = {1}, `connections` = `connections` + 1, `name` = '{3}' + WHERE `id` = {2} LIMIT 1; + "; + } + } +} \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index b1344df..d0517a9 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -25,13 +25,10 @@ You should have received a copy of the GNU Affero General Public License #define DEBUG -using System.Text.Json; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes.Registration; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; @@ -40,26 +37,34 @@ namespace SurfTimer; public partial class SurfTimer : BasePlugin { // Metadata - public override string ModuleName => "CS2 SurfTimer"; + public override string ModuleName => $"CS2 {Config.PluginName}"; public override string ModuleVersion => "DEV-1"; public override string ModuleDescription => "Official Surf Timer by the CS2 Surf Initiative."; public override string ModuleAuthor => "The CS2 Surf Initiative - github.com/cs2surf"; - public string PluginPrefix => $"[{ChatColors.DarkBlue}CS2 Surf{ChatColors.Default}]"; // To-do: make configurable // Globals private Dictionary playerList = new Dictionary(); // This can probably be done way better, revisit - internal TimerDatabase? DB = new TimerDatabase(); - public string PluginPath = Server.GameDirectory + "/csgo/addons/counterstrikesharp/plugins/SurfTimer/"; - internal Map CurrentMap = null!; + internal static TimerDatabase DB = new TimerDatabase(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string + // internal Map CurrentMap = null!; + internal static Map CurrentMap = null!; /* ========== MAP START HOOKS ========== */ 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 1.0 seconds for now - if ((CurrentMap == null || CurrentMap.Name != mapName) && mapName.Contains("surf_")) + if ((CurrentMap == null || !CurrentMap.Name.Equals(mapName)) && mapName.Contains("surf_")) { - AddTimer(1.0f, () => CurrentMap = new Map(mapName, DB!)); // Was 3 seconds, now 1 second + Server.NextWorldUpdate(() => Console.WriteLine(String.Format(" ____________ ____ ___\n" + + " / ___/ __/_ | / __/_ ______/ _/\n" + + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" + + "\\___/___/____/ /___/\\_,_/_/ /_/\n" + + $"[CS2 Surf] {Config.PluginName} {ModuleVersion} - loading map {mapName}.\n" + + $"[CS2 Surf] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" + + $"[CS2 Surf] ---> Source Code: https://github.com/CS2Surf/Timer\n" + + $"[CS2 Surf] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" + ))); + + Server.NextWorldUpdate(async () => CurrentMap = await Map.CreateAsync(mapName)); // NextWorldUpdate runs even during server hibernation } } @@ -88,30 +93,22 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) /* ========== PLUGIN LOAD ========== */ public override void Load(bool hotReload) { - // Load database config & spawn database object - try + // Check if we have connected to the Database + if (DB != null) { - JsonElement dbConfig = JsonDocument.Parse(File.ReadAllText(Server.GameDirectory + "/csgo/cfg/SurfTimer/database.json")).RootElement; - DB = new TimerDatabase(dbConfig.GetProperty("host").GetString()!, - dbConfig.GetProperty("database").GetString()!, - dbConfig.GetProperty("user").GetString()!, - dbConfig.GetProperty("password").GetString()!, - dbConfig.GetProperty("port").GetInt32(), - dbConfig.GetProperty("timeout").GetInt32()); Console.WriteLine("[CS2 Surf] Database connection established."); } - - catch (Exception e) + else { - Console.WriteLine($"[CS2 Surf] Error loading database config: {e.Message}"); + Console.WriteLine($"[CS2 Surf] Error connecting to the database."); // To-do: Abort plugin loading } Console.WriteLine(String.Format(" ____________ ____ ___\n" + " / ___/ __/_ | / __/_ ______/ _/\n" + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" - + "\\___/___/____/ /___/\\_,_/_/ /_/\n" - + $"[CS2 Surf] SurfTimer plugin loaded. Version: {ModuleVersion}" + + "\\___/___/____/ /___/\\_,_/_/ /_/\n" + + $"[CS2 Surf] {Config.PluginName} plugin loaded. Version: {ModuleVersion}\n" + $"[CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. Source code: https://github.com/CS2Surf/Timer\n" )); @@ -122,10 +119,6 @@ public override void Load(bool hotReload) // Tick listener RegisterListener(OnTick); - // StartTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 - // VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(OnTriggerStartTouch, HookMode.Post); - // EndTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 - // VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(OnTriggerEndTouch, HookMode.Post); HookEntityOutput("trigger_multiple", "OnStartTouch", OnTriggerStartTouch); HookEntityOutput("trigger_multiple", "OnEndTouch", OnTriggerEndTouch); diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 87e253f..82404c6 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable true @@ -13,9 +13,9 @@ - + - + diff --git a/src/bin/MaxMind.Db.dll b/src/bin/MaxMind.Db.dll new file mode 100644 index 0000000000000000000000000000000000000000..dadb1f6bef6c5ebc8a2270f549d66b5ec92ee258 GIT binary patch literal 44544 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 literal 0 HcmV?d00001 diff --git a/src/bin/MaxMind.GeoIP2.dll b/src/bin/MaxMind.GeoIP2.dll new file mode 100644 index 0000000000000000000000000000000000000000..a665688c4e2fad5a10df3ef61f491d319b161be0 GIT binary patch literal 58368 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 literal 0 HcmV?d00001 diff --git a/src/bin/MySqlConnector.dll b/src/bin/MySqlConnector.dll new file mode 100644 index 0000000000000000000000000000000000000000..ea3237d4b05efd88bd8fe7c0f3c829b0afe1f9a4 GIT binary patch literal 838504 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�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�{0)dx8Od6`z!8WxLr#* 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

>ABQ`_ZsaNts%le*c<44rz4NHH(sPl# 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 oG1DSnC@DrEq#=gNLMjb$mTqa_VUjmwiVQ$# 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ΝdJRrU=vK+G9qrCN z?tiTlpQ~j95ZZP*0u@CQY5+nNK)4MM%HgPVui%S~l6Hkpt+mQWmwfxK?S08U*S}Hr z5w;AMD9SJ>d7S|X6 literal 0 HcmV?d00001 From 755921b12d5a9f685499e8943d21708913444774 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:44:06 +0200 Subject: [PATCH 07/52] bonus replays --- .github/workflows/dotnet.yml | 2 +- TODO | 19 ++--- src/ST-Commands/PlayerCommands.cs | 18 ++-- src/ST-Events/Tick.cs | 21 +++-- src/ST-Events/TriggerStartTouch.cs | 102 ++++++++++------------- src/ST-Map/Map.cs | 61 +++++++++++++- src/ST-Player/PlayerStats/CurrentRun.cs | 8 +- src/ST-Player/PlayerStats/PlayerStats.cs | 18 ++-- src/ST-Player/Replay/ReplayManager.cs | 30 ++++--- src/ST-Player/Replay/ReplayPlayer.cs | 4 +- src/ST-Player/Replay/ReplayRecorder.cs | 36 ++++++++ 11 files changed, 201 insertions(+), 118 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bd7d6ed..b9590e3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/TODO b/TODO index 41eedc5..bd60352 100644 --- a/TODO +++ b/TODO @@ -13,20 +13,10 @@ + Checkpoints - Stages - Try and get the Start/End velocity values from the Checkpoint object in Map Run - - Bonuses - - Stage times are being saved with the wrong values for velocities + + Bonuses - Using `!spec` from any start zone and going back to CT starts your timer -+ Trim different type of replays based on situations - + Map Replays - + Stage Replays during Map runs - + Stage Replays in `IsStageMode` - - Bonus Replays - - - -- Replay bots are not being spawned on `Linear` maps # Done + Hibernation fucks up the creation of `CurrentMap`, if nobody has joined the first map it was on and you change to another map, server segfaults. I guess a query is being held?? :think: @@ -65,3 +55,10 @@ + Use seperate method + When loading new stage replays the cycling of them does not work + First ever map run does not save Checkpoints, check if we wait to retrieve the MapTime_ID before trying to insert Checkpoints ++ Trim different type of replays based on situations + + Map Replays + + Stage Replays during Map runs + + Stage Replays in `IsStageMode` + + Bonus Replays ++ Replay bots are not being spawned on `Linear` maps + + Needs the CS2Fixes NAV mesh patch to work with our implementation \ No newline at end of file diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index df2215c..3f6bb58 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -41,21 +41,19 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) return; } - // To-do: players[userid].Timer.Reset() -> teleport player - Player SurfPlayer = playerList[player.UserId ?? 0]; - if (SurfPlayer.Timer.IsBonusMode) + if (oPlayer.Timer.IsBonusMode) { - if (SurfPlayer.Timer.Bonus != 0 && CurrentMap.BonusStartZone[SurfPlayer.Timer.Bonus] != new Vector(0, 0, 0)) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[SurfPlayer.Timer.Bonus], CurrentMap.BonusStartZoneAngles[SurfPlayer.Timer.Bonus], new Vector(0, 0, 0))); + if (oPlayer.Timer.Bonus != 0 && CurrentMap.BonusStartZone[oPlayer.Timer.Bonus] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[oPlayer.Timer.Bonus], CurrentMap.BonusStartZoneAngles[oPlayer.Timer.Bonus], 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))); } else { - 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 (oPlayer.Timer.Stage != 0 && CurrentMap.StageStartZone[oPlayer.Timer.Stage] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[oPlayer.Timer.Stage], CurrentMap.StageStartZoneAngles[oPlayer.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))); } @@ -235,10 +233,14 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordRunTime}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.Frames.Count}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); - player.PrintToChat($"{Config.PluginPrefix} .MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.RecordRunTime}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.Frames.Count}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlaying}"); /* for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 52516fe..2d59bb0 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -18,6 +18,7 @@ public void OnTick() // Need to disable maps from executing their cfgs. Currently idk how (But seriusly it a security issue) ConVar? bot_quota = ConVar.Find("bot_quota"); + // Console.WriteLine($"======== public void OnTick -> bot_quota not null? {bot_quota != null}"); if (bot_quota != null) { int cbq = bot_quota.GetPrimitiveValue(); @@ -31,9 +32,10 @@ public void OnTick() { bot_quota.SetValue(replaybot_count); } + + // Console.WriteLine($"======== public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}"); } - // Console.WriteLine($"======== public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}"); CurrentMap.ReplayManager.MapWR.Tick(); CurrentMap.ReplayManager.StageWR?.Tick(); CurrentMap.ReplayManager.BonusWR?.Tick(); @@ -58,14 +60,19 @@ public void OnTick() if (CurrentMap.ReplayManager.BonusWR?.RepeatCount == 0) { - CurrentMap.ReplayManager.BonusWR.Stage = (CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1; - CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); + int next_bonus; + if (CurrentMap.ReplayManager.AllBonusWR[(CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1][0].MapTimeID == -1) + next_bonus = 1; + else + next_bonus = (CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1; - AddTimer(1.5f, () => CurrentMap.ReplayManager.BonusWR.FormatBotName()); + CurrentMap.ReplayManager.AllBonusWR[next_bonus][0].Controller = CurrentMap.ReplayManager.BonusWR.Controller; - // CurrentMap.ReplayManager.BonusWR.ResetReplay(); - // //CurrentMap.ReplayManager.BonusWR.FormatBotName(); - // CurrentMap.ReplayManager.BonusWR.RepeatCount = 3; + // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for bonus {CurrentMap.ReplayManager.BonusWR.Stage}, changing to bonus {next_bonus}"); + CurrentMap.ReplayManager.BonusWR = CurrentMap.ReplayManager.AllBonusWR[next_bonus][0]; + CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); + CurrentMap.ReplayManager.BonusWR.FormatBotName(); + CurrentMap.ReplayManager.BonusWR.Start(); } for (int i = 0; i < CurrentMap.ReplayManager.CustomReplays.Count; i++) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 111955f..d73d394 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -76,7 +76,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { saveMapTime = true; int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Map{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Map{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)}{ChatColors.Default})"); } else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map { @@ -92,7 +92,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { saveMapTime = true; int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Map{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Map{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); } else // Player did not beat their existing PersonalBest for the map nor the map record { @@ -141,12 +141,8 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn AddTimer(2f, () => { Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); - // CurrentMap.ReplayManager.MapWR.LoadReplayData(); CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again - AddTimer(1.5f, () => - { - CurrentMap.ReplayManager.MapWR.FormatBotName(); - }); + CurrentMap.ReplayManager.MapWR.FormatBotName(); }); } @@ -439,40 +435,33 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // Bonus start zones -- hook into (b)onus#_start else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) - { - // We only want this working if they're in bonus mode, ignore otherwise. - if (player.Timer.IsBonusMode) - { - player.ReplayRecorder.Start(); // Start replay recording - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; - // player.ReplayRecorder.Frames[player.ReplayRecorder.Frames.Count].Situation = (byte)ReplayFrameSituation.START_ZONE_ENTER; - player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + { + int bonus = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + player.Timer.Bonus = bonus; + + player.Timer.Reset(); + player.Timer.IsBonusMode = true; - player.Timer.Reset(); - player.Timer.IsBonusMode = true; - int bonus = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); - player.Timer.Bonus = bonus; - player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); + player.ReplayRecorder.Reset(); + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + + player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); #endif - } } // Bonus end zones -- hook into (b)onus#_end else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success && player.Timer.IsBonusMode && player.Timer.IsRunning) { // To-do: verify the bonus trigger being hit! - int bonus_idx = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; - if (1 != player.Timer.Bonus) // Realy????!! - { - // Exit hook as this end zone is not relevant to the player's current bonus - return HookResult.Continue; - } + int bonus_idx = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); player.Timer.Stop(); player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; @@ -483,36 +472,42 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn player.Stats.ThisRun.EndVelY = velocity_y; // End pre speed for the run player.Stats.ThisRun.EndVelZ = velocity_z; // End pre speed for the run + bool saveBonusTime = false; string PracticeString = ""; if (player.Timer.IsPracticeMode) PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - // To-do: make Style (currently 0) be dynamic - if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PB for the bonus + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR { - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{player.Controller.PlayerName} finished bonus {bonus_idx + 1} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} ({player.Timer.Ticks})!"); + saveBonusTime = true; + int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)}{ChatColors.Default})"); } - else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PB for the bonus + else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map { - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Profile.Name}{ChatColors.Default} beat their bonus {bonus_idx + 1} PB in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default} (Old: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.BonusPB[bonus_idx][pStyle].Ticks)}{ChatColors.Default})!"); + saveBonusTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); } - else // Player did not beat their existing personal best for the bonus + else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished bonus {bonus_idx + 1} in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); - return HookResult.Continue; // Exit here so we don't write to DB + saveBonusTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); } - - if (DB == null) + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus { - Exception exception = new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Bonus end zone) -> DB object is null, this shouldn't happen."); - throw exception; + saveBonusTime = true; + int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); + } + else // Player did not beat their existing personal best for the bonus + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); } - - player.Stats.BonusPB[bonus_idx][pStyle].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query // To-do: save to DB if (!player.Timer.IsPracticeMode) { + /* AddTimer(1.5f, () => { API_CurrentRun bonus_time = new API_CurrentRun @@ -535,18 +530,13 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD }); }); - - if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks || CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) + */ + if (saveBonusTime) { - AddTimer(2f, () => + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => { - CurrentMap.ReplayManager.BonusWR!.Stage = bonus_idx; - CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); - - AddTimer(1.5f, () => - { - CurrentMap.ReplayManager.BonusWR.FormatBotName(); - }); + await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data }); } } @@ -576,7 +566,7 @@ void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR { int timeImprove = CurrentMap.StageWR[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})"); } else if (CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map { @@ -589,7 +579,7 @@ void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time) // Player beating their existing Stage personal best { int timeImprove = player.Stats.StagePB[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Stage {stage}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Stage {stage}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].Ticks)}{ChatColors.Default})"); } player.ReplayRecorder.IsSaving = true; diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 95c8a74..6758c23 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -113,7 +113,7 @@ private async Task InitializeAsync() // Initialize ReplayManager with placeholder values // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Initializing ReplayManager(-1, {this.Stages > 0}, false, null!)"); - this.ReplayManager = new ReplayManager(-1, this.Stages > 0, false, null!); // Adjust values as needed + this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); // Adjust values as needed await Get_Map_Info(); @@ -517,6 +517,14 @@ internal async Task Get_Map_Record_Runs() this.BonusWR[qStage][qStyle].Name = mapWrData.GetString("name"); totalBonusRuns = mapWrData.GetInt32("total_count"); this.BonusCompletions[qStage][qStyle] = totalBonusRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Got Bonus {qStage}"); + + // Populate the ReplayManager for all bonuses found and set the first bonus to replay + if (this.ReplayManager.BonusWR != null) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } break; case 2: // Stage WR data and total completions this.StageWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); @@ -645,10 +653,51 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame } break; case 1: // Bonus Replays - // Not loading any Bonus replays yet + // Skip if the same bonus run already exists + if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].Ticks) + break; + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Adding run {this.BonusWR[stage][style].ID} {PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks)} (Ticks = {this.BonusWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllBonusWR`"); + // Add all stages found to a dictionary with their data + this.ReplayManager.AllBonusWR[stage][style].MapID = this.ID; + this.ReplayManager.AllBonusWR[stage][style].Frames = frames; + this.ReplayManager.AllBonusWR[stage][style].RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.AllBonusWR[stage][style].RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.AllBonusWR[stage][style].MapTimeID = this.BonusWR[stage][style].ID; + this.ReplayManager.AllBonusWR[stage][style].Stage = stage; + this.ReplayManager.AllBonusWR[stage][style].Type = 1; + this.ReplayManager.AllBonusWR[stage][style].RecordRank = 1; + this.ReplayManager.AllBonusWR[stage][style].IsPlayable = true; // We set this to `true` else we overwrite it and need to call SetController method again + for (int i = 0; i < frames.Count; i++) + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); + break; + } + } + // Set the bonus to replay first + if (this.ReplayManager.BonusWR != null && this.ReplayManager.BonusWR.MapID == -1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Setting first `ReplayManager.BonusWR` to bonus {stage}"); + if (this.ReplayManager.BonusWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current bonus being played? + this.ReplayManager.BonusWR.Stop(); + this.ReplayManager.BonusWR.MapID = this.ID; + this.ReplayManager.BonusWR.Frames = frames; + this.ReplayManager.BonusWR.RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.BonusWR.RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.BonusWR.MapTimeID = this.BonusWR[stage][style].ID; + this.ReplayManager.BonusWR.Stage = stage; + this.ReplayManager.BonusWR.Type = 1; + this.ReplayManager.BonusWR.RecordRank = 1; + } break; case 2: // Stage Replays - // Skip if we the same stage run already exists + // Skip if the same stage run already exists if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) break; Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Adding run {this.StageWR[stage][style].ID} {PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks)} (Ticks = {this.StageWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllStageWR`"); @@ -700,6 +749,12 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame this.ReplayManager.MapWR.ResetReplay(); this.ReplayManager.MapWR.Start(); } + else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.BonusWR.ResetReplay(); + this.ReplayManager.BonusWR.Start(); + } else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) { Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 52f49fd..32150a0 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -68,7 +68,7 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r if (stage != 0 || bonus != 0) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Inserted an entry for Stage {stage} - {run_ticks}"); + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Inserted an entry for {(stage != 0 ? "Stage" : "Bonus")} {(stage != 0 ? stage : bonus)} - {run_ticks}"); } else { @@ -158,17 +158,17 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int if (reloadData && type == 0) { Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.PB[player.Timer.Style].ID}))"); - await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) } else if (reloadData && type == 1) { Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.BonusPB[bonus][style].ID}))"); - await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) } else if (reloadData && type == 2) { Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.StagePB[stage][style].ID}))"); - await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) } } } diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 3e8c662..c0586c1 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -53,11 +53,11 @@ public PlayerStats() for (int i = 0; i < 32; i++) { this.BonusPB[i] = new Dictionary(); - this.BonusPB[i][0] = new PersonalBest(); // To-do: Implement styles + this.BonusPB[i][0] = new PersonalBest(); this.BonusPB[i][0].Type = 1; this.StagePB[i] = new Dictionary(); - this.StagePB[i][0] = new PersonalBest(); // To-do: Implement styles + this.StagePB[i][0] = new PersonalBest(); this.StagePB[i][0].Type = 2; initialized++; } @@ -174,7 +174,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma if (playerStats.GetInt32("type") == 1) // Bonus time { #if DEBUG - System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> BonusPB"); + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> BonusPB"); #endif int bonusNum = playerStats.GetInt32("stage"); style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented @@ -193,7 +193,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma else if (playerStats.GetInt32("type") == 2) // Stage time { #if DEBUG - System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> StagePB"); + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> StagePB"); #endif int stageNum = playerStats.GetInt32("stage"); style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented @@ -215,7 +215,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma else // Map time { #if DEBUG - System.Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> MapPB"); + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> MapPB"); #endif style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented PB[style].ID = playerStats.GetInt32("id"); @@ -240,13 +240,5 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma #endif } } - - // // This would have to go inside the `Map Time` else statement in order to load checkpoints for each `style` - // if (PB[player.Timer.Style].ID != -1) - // { - // // await LoadCheckpointsData(player); - // await this.PB[player.Timer.Style].PB_LoadCheckpointsData(); - // } } - } \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayManager.cs b/src/ST-Player/Replay/ReplayManager.cs index 45fef14..7291846 100644 --- a/src/ST-Player/Replay/ReplayManager.cs +++ b/src/ST-Player/Replay/ReplayManager.cs @@ -9,10 +9,15 @@ internal class ReplayManager public ReplayPlayer? BonusWR { get; set; } = null; public ReplayPlayer? StageWR { get; set; } = null; ///

- /// Contains all all Stage records for all styles - Refer to as AllStageWR[stage#][style] + /// Contains all Stage records for all styles - Refer to as AllStageWR[stage#][style] /// Need to figure out a way to NOT hardcode to `32` but to total amount of Stages /// public Dictionary[] AllStageWR { get; set; } = new Dictionary[32]; + /// + /// Contains all Bonus records for all styles - Refer to as AllBonusWR[bonus#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of Bonuses + /// + public Dictionary[] AllBonusWR { get; set; } = new Dictionary[32]; public List CustomReplays { get; set; } /// @@ -44,9 +49,8 @@ public ReplayManager(int map_id, bool staged, bool bonused, List fr if (staged) { // Initialize 32 Stages for each style - // TODO: Make the amount of stages and bonuses dynamic + // TODO: Make the amount of stages dynamic for (int i = 0; i < 32; i++) - // for (int i = 0; i < SurfTimer.CurrentMap.Stages; i++) { AllStageWR[i] = new Dictionary(); foreach (int x in Config.Styles) @@ -59,17 +63,17 @@ public ReplayManager(int map_id, bool staged, bool bonused, List fr if (bonused) { - BonusWR = new ReplayPlayer + // Initialize 32 Stages for each style + // TODO: Make the amount of bonuses dynamic + for (int i = 0; i < 32; i++) { - Type = 1, - Stage = 1, - RecordRank = 1, - MapID = map_id, - Frames = frames, - RecordRunTime = run_time, - RecordPlayerName = playerName, - MapTimeID = map_time_id - }; + AllBonusWR[i] = new Dictionary(); + foreach (int x in Config.Styles) + { + AllBonusWR[i][x] = new ReplayPlayer(); + } + } + BonusWR = new ReplayPlayer(); } CustomReplays = new List(); diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index bc9dc0d..6430f07 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -214,9 +214,9 @@ public void FormatBotName() } if (this.Type == 1) - prefix = prefix + $" B{this.Stage}"; + prefix = prefix + $"B {this.Stage}"; else if (this.Type == 2) - prefix = prefix + $" S{this.Stage}"; + prefix = prefix + $"CP {this.Stage}"; SchemaString bot_name = new SchemaString(this.Controller!, "m_iszPlayerName"); diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 43dd734..61e0a8c 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -179,6 +179,42 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false) } break; case 1: // Trim Bonus replays + // Bonus runs + int bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + int bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + int bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Bonus Run replay. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}"); + + if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (bonus_exit_index - (Config.ReplaysPre * 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre * 2); + else if (bonus_exit_index - Config.ReplaysPre >= bonus_enter_index) + startIndex = bonus_exit_index - Config.ReplaysPre; + else if (bonus_exit_index - (Config.ReplaysPre / 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre / 2); + else + startIndex = bonus_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (bonus_end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre * 2); + else if (bonus_end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = bonus_end_enter_index + Config.ReplaysPre; + else if (bonus_end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = bonus_end_enter_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } break; case 2: // Trim Stage replays int stage_end_index; From 9450674a3e15b662de48277b4caa5402111208eb Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:49:14 +0200 Subject: [PATCH 08/52] readme + todo --- README.md | 8 ++++---- TODO | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3dcf459..685f2b9 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Bold & Italics = being worked on. - [x] Map times - [x] Checkpoint times - [X] Stage times - - [ ] Bonus times + - [X] Bonus times - [X] Practice Mode implementation - [ ] Announce records to Discord - [ ] Stretch goal: sub-tick timing @@ -54,14 +54,14 @@ Bold & Italics = being worked on. - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) -- [x] Replays - Not tracking Stage/Bonus times but Replay functionality for them is there +- [x] Replays - [x] Personal Best - [x] Map Record - [X] Stage Record - - [ ] Bonus Record + - [X] Bonus Record - [x] World Record - [X] Map Record - [X] Stage Record - - [ ] Bonus Record + - [X] Bonus Record - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) diff --git a/TODO b/TODO index bd60352..58070b4 100644 --- a/TODO +++ b/TODO @@ -7,7 +7,6 @@ - Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin - Change `DB_QUERY_PB_GET_RUNTIME` query in API - - Double check the Start/End velocities for End Zone,Checkpoints, Stages, Bonuses + Maps + Checkpoints @@ -15,9 +14,11 @@ - Try and get the Start/End velocity values from the Checkpoint object in Map Run + Bonuses - - Using `!spec` from any start zone and going back to CT starts your timer +- Try and determine Left/Right side for Bonuses (surf_ace) +- Try and determine Left/Right side for Maps +- Try and determine Left/Right side for Stages # Done + Hibernation fucks up the creation of `CurrentMap`, if nobody has joined the first map it was on and you change to another map, server segfaults. I guess a query is being held?? :think: + Solution is to use `Server.NextWorldUpdate` as hibernation stops all `Timer`s :peepoHappy: From c7c985aeafa7b96f4752f8e2c3e08251cc09fafc Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 28 Dec 2024 13:12:13 +0200 Subject: [PATCH 09/52] Update README.md --- README.md | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 685f2b9..fcfade8 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,173 @@ -# PLEASE DO NOT USE THIS, IT IS NOT COMPLETE AND IS AN ACTIVE WORK-IN-PROGRESS. ISSUES HAVE BEEN DISABLED FOR THIS REASON. +# EVERYONE IS WELCOME TO BUILD UP ON THIS PROJECT AND CONTRIBUTE. ISSUES ARE DISABLED FOR THE TIME BEING ## Please join the Discord: https://discord.cs.surf # Timer Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source with the goal of uniting all of CS2 surf towards building the game mode. +
+
+ Center HUD Speedometer +

Different time formatting is available in the code base but not implemented for players to change it themselves. Refer to PlayerTimer.TimeFormatStyle in codebase

+
    +
  • Map +

    Shown while player starts their Map Run from the map start zone (!r)

    +
    + Zone name + Start speed +

    Map Run mode:

    + Map Start Zone + Map Start Zone exit +
    +
  • +
  • Checkpoints +

    Only shown during Map Run after exiting a Checkpoint/Stage zone

    +
    + Zone name + Start speed +

    Checkpoint comparison:

    + Checkpoint Start Zone exit +
    +
  • +
  • Stages +

    Only shown while in Stage Mode, accessed through !s X or !stage X commands

    +
    + Zone name + Start speed +

    Stage Run mode:

    + Stages Start Zone + Stages Start Zone exit +
    +
  • +
  • Bonuses +

    Only shown while in Bonus Mode, accessed through !b X or !bonus X commands

    +
    + Zone name + Start speed +

    Bonus Run mode:

    + Bonuses Start Zone + Bonuses Start Zone exit +
    +
  • +
+
-# Goals +
+ Replays +

Currently only accessible through the !spec command and cycling the players. Different time formatting is available in the code base but not implemented for players to change it themselves. Refer to PlayerTimer.TimeFormatStyle in codebase

+

Replays are saved for all types of runs Map/Stage/Bonus (and future Styles) regardless if they are a World Record or just a Personal Best. No functionality is implemented for replaying PB replays yet, feel free to add and Pull Request it

+
    +
  • Map +
    + Spectating Map Replay +

    Map Run:

    + Map Run Replay +
    +
  • +
  • Stages +
    + Spectating Stage Replay +

    Stage Run:

    + Stages Run Replay +
    +
  • +
  • Bonuses +

    Bonus Replays are also available but no screenshots at the time of writing.

    +
  • +
  • Scoreboard +
    + Currently available replays for the map +

    Scoreboard:

    + Scoreboard showing all available Replays +
    +
  • +
+
+ + +
+ Chat Messages +
    +
  • Map Run +
    + Improving a Record +

    Timer sends a chat message to all players upon a player beating the Map Record. Missing it sends a message only to the player:

    + Beating a Map record +
    +
    + Checkpoint Comparison +

    Timer sends a chat message the player comparing their PB checkpoint times with the current run (value in brackets after the time indicate the Speed):

    + Checkpoints Comparison +
    +
  • +
  • Stage Records +
    + New Stage record and improving Stage record +

    Timer sends a chat message to all players upon a player beating a Stage/Bonus/Map record. Different scenarios for missed/comparing times are also available and shown in chat but only to the player who is doing the run:

    + Stage Records and comparisons +
    +
  • +
  • QOL +
    + Player Connected + Map Info (!mi / !tier) +

    LL is used for Local development and testing:

    + Player Connected + Map Info +
    +
    + Player Rank +

    Displays the rank of the player on the current map:

    + Player Rank +
    +
  • +
+
+ +
+ Player Commands +

We recommend making binds using the Console commands, chat commands may flood the server and not always work.

+
    +
  • Saveloc (Practice Mode) +
    + Save the current location +

    Chat: !saveloc

    +

    Console: css_saveloc

    +
    +
    + Teleport to the last saved location +

    Chat: !tele

    +

    Console: css_tele

    +
    +
    + Teleport to the previous saved location +

    Chat: !teleprev

    +

    Console: css_teleprev

    +
    +
    + Teleport to the next saved location +

    Chat: !telenext

    +

    Console: css_telenext

    +
    +
  • +
+
    +
  • Spectate +
    + Enter Spectator Mode +

    Chat: !spec

    +

    Console: css_spec

    +
    +
    + Exiting Spectator Mode +

    No command currently available to go back to Play Mode (time may NOT be reset and you will loose your progress post entering Spectator Mode)

    +

    Open team choosing menu M and select CT

    +
    +
  • +
+
+
+ +# Main list with tasks (more details can be found [here](https://github.com/tslashd/Timer/blob/dev/TODO)): *Note: This is not definitive/complete and simply serves as a reference for what we should try to achieve. Subject to change.* Bold & Italics = being worked on. - - [ ] Database - - [ ] MySQL database schema ([W.I.P Design Diagram](https://dbdiagram.io/d/CS2Surf-Timer-DB-Schema-6560b76b3be1495787ace4d2)) + - [X] MySQL database schema ([Design Diagram](https://dbdiagram.io/d/CS2Surf-Timer-DB-Schema-6560b76b3be1495787ace4d2)) - [ ] Plugin auto-create tables for easier setup? - [X] Base database class implementation -- [ ] Maps +- [X] Maps - [X] Implement map info object (DB) - [X] Zoning - [X] Hook zones from map triggers @@ -31,12 +186,12 @@ Bold & Italics = being worked on. - [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping - [X] Support for stages (`/rs`, teleporting with `/s`) - [X] Support for bonuses (`/rs`, teleporting with `/b #`) - - [X] _**Start/End touch hooks implemented for all zones**_ + - [X] _Start/End touch hooks implemented for all zones - [ ] Surf configs - [X] Server settings configuration - [ ] Plugin configuration - [X] Database configuration -- [ ] Timing +- [X] Timing - [X] Base timer class implementation - [X] Base timer HUD implementation - [X] Prespeed measurement and display @@ -50,7 +205,7 @@ Bold & Italics = being worked on. - [ ] Stretch goal: sub-tick timing - [ ] Player Data - [X] Base player class - - [ ] **_Player stat classes_** + - [ ] Player stat classes - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) @@ -65,3 +220,4 @@ Bold & Italics = being worked on. - [X] Bonus Record - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) +- [ ] API Integration (Repo can be found [here](https://github.com/CS2Surf/CS2-Surf-API)) From cb93b14e8a0e611679b3e54e296c2f1781e5c2dc Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 28 Dec 2024 13:19:20 +0200 Subject: [PATCH 10/52] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcfade8..b5157d0 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Bold & Italics = being worked on. - [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping - [X] Support for stages (`/rs`, teleporting with `/s`) - [X] Support for bonuses (`/rs`, teleporting with `/b #`) - - [X] _Start/End touch hooks implemented for all zones + - [X] Start/End touch hooks implemented for all zones - [ ] Surf configs - [X] Server settings configuration - [ ] Plugin configuration From b42d46b95387465f5a149ef2ba4b3ee18f8fcabb Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 28 Dec 2024 17:34:46 +0200 Subject: [PATCH 11/52] Update README.md Fix `TODO` link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5157d0..1741b47 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source
-# Main list with tasks (more details can be found [here](https://github.com/tslashd/Timer/blob/dev/TODO)): +# Main list with tasks (more details can be found [here](https://github.com/CS2Surf/Timer/blob/dev/TODO)): *Note: This is not definitive/complete and simply serves as a reference for what we should try to achieve. Subject to change.* Bold & Italics = being worked on. - [ ] Database From 571b455256145aab4f9edaa876c0b6e522df4da7 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sat, 28 Dec 2024 17:43:32 +0200 Subject: [PATCH 12/52] Latest "update" and reworks (#17) * hi Summary of completed items: - Resolved hibernation issues with timers. - Implemented async queries and methods with multiple database connections. - Moved PlayerProfile and Map queries to respective classes. - Added ReplayFrame Situations for situation-specific replay handling. - Corrected Map Replays reload and ReplayFrameSituations usage. - Updated Checkpoints comparison messages for correct display. - Adjusted Stage Replays tracking, saving, and reloading during map runs. - Fixed checkpoint saving on first map run. - Refer to TODO for more information * bonus replays * readme + todo * Update README.md * Update README.md * Update README.md Fix `TODO` link --- .github/workflows/dotnet.yml | 2 +- README.md | 198 +++- TODO | 65 ++ src/ST-API/Api.cs | 41 + src/ST-API/Comms.cs | 85 ++ src/{ST-UTILS => ST-API}/ConVar.cs | 2 +- .../JsonConverters.cs} | 37 +- src/{ST-UTILS => ST-API}/Schema.cs | 0 src/ST-API/Structures.cs | 87 ++ src/ST-Commands/MapCommands.cs | 18 +- src/ST-Commands/PlayerCommands.cs | 459 ++++++++-- src/ST-DB/DB.cs | 163 ++-- src/ST-Events/Players.cs | 280 +++--- src/ST-Events/Tick.cs | 63 +- src/ST-Events/TriggerEndTouch.cs | 209 +++-- src/ST-Events/TriggerStartTouch.cs | 650 ++++++++++--- src/ST-Map/Map.cs | 853 ++++++++++++++---- src/ST-Player/Player.cs | 2 +- src/ST-Player/PlayerHUD.cs | 151 ++-- src/ST-Player/PlayerProfile.cs | 129 ++- src/ST-Player/PlayerStats/Checkpoint.cs | 4 +- src/ST-Player/PlayerStats/CurrentRun.cs | 198 +++- src/ST-Player/PlayerStats/PersonalBest.cs | 202 ++++- src/ST-Player/PlayerStats/PlayerStats.cs | 314 ++++--- src/ST-Player/PlayerTimer.cs | 8 +- src/ST-Player/Replay/ReplayFrame.cs | 45 +- src/ST-Player/Replay/ReplayManager.cs | 101 +++ src/ST-Player/Replay/ReplayPlayer.cs | 176 ++-- src/ST-Player/Replay/ReplayRecorder.cs | 293 +++++- src/ST-UTILS/Config.cs | 206 +++++ src/SurfTimer.cs | 51 +- src/SurfTimer.csproj | 6 +- src/bin/MaxMind.Db.dll | Bin 0 -> 44544 bytes src/bin/MaxMind.GeoIP2.dll | Bin 0 -> 58368 bytes src/bin/MySqlConnector.dll | Bin 0 -> 838504 bytes 35 files changed, 3953 insertions(+), 1145 deletions(-) create mode 100644 TODO create mode 100644 src/ST-API/Api.cs create mode 100644 src/ST-API/Comms.cs rename src/{ST-UTILS => ST-API}/ConVar.cs (91%) rename src/{ST-UTILS/Compression.cs => ST-API/JsonConverters.cs} (83%) rename src/{ST-UTILS => ST-API}/Schema.cs (100%) create mode 100644 src/ST-API/Structures.cs create mode 100644 src/ST-Player/Replay/ReplayManager.cs create mode 100644 src/ST-UTILS/Config.cs create mode 100644 src/bin/MaxMind.Db.dll create mode 100644 src/bin/MaxMind.GeoIP2.dll create mode 100644 src/bin/MySqlConnector.dll diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bd7d6ed..b9590e3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/README.md b/README.md index 4663617..1741b47 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,175 @@ -# PLEASE DO NOT USE THIS, IT IS NOT COMPLETE AND IS AN ACTIVE WORK-IN-PROGRESS. ISSUES HAVE BEEN DISABLED FOR THIS REASON. +# EVERYONE IS WELCOME TO BUILD UP ON THIS PROJECT AND CONTRIBUTE. ISSUES ARE DISABLED FOR THE TIME BEING ## Please join the Discord: https://discord.cs.surf # Timer Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source with the goal of uniting all of CS2 surf towards building the game mode. +
+
+ Center HUD Speedometer +

Different time formatting is available in the code base but not implemented for players to change it themselves. Refer to PlayerTimer.TimeFormatStyle in codebase

+
    +
  • Map +

    Shown while player starts their Map Run from the map start zone (!r)

    +
    + Zone name + Start speed +

    Map Run mode:

    + Map Start Zone + Map Start Zone exit +
    +
  • +
  • Checkpoints +

    Only shown during Map Run after exiting a Checkpoint/Stage zone

    +
    + Zone name + Start speed +

    Checkpoint comparison:

    + Checkpoint Start Zone exit +
    +
  • +
  • Stages +

    Only shown while in Stage Mode, accessed through !s X or !stage X commands

    +
    + Zone name + Start speed +

    Stage Run mode:

    + Stages Start Zone + Stages Start Zone exit +
    +
  • +
  • Bonuses +

    Only shown while in Bonus Mode, accessed through !b X or !bonus X commands

    +
    + Zone name + Start speed +

    Bonus Run mode:

    + Bonuses Start Zone + Bonuses Start Zone exit +
    +
  • +
+
-# Goals +
+ Replays +

Currently only accessible through the !spec command and cycling the players. Different time formatting is available in the code base but not implemented for players to change it themselves. Refer to PlayerTimer.TimeFormatStyle in codebase

+

Replays are saved for all types of runs Map/Stage/Bonus (and future Styles) regardless if they are a World Record or just a Personal Best. No functionality is implemented for replaying PB replays yet, feel free to add and Pull Request it

+
    +
  • Map +
    + Spectating Map Replay +

    Map Run:

    + Map Run Replay +
    +
  • +
  • Stages +
    + Spectating Stage Replay +

    Stage Run:

    + Stages Run Replay +
    +
  • +
  • Bonuses +

    Bonus Replays are also available but no screenshots at the time of writing.

    +
  • +
  • Scoreboard +
    + Currently available replays for the map +

    Scoreboard:

    + Scoreboard showing all available Replays +
    +
  • +
+
+ + +
+ Chat Messages +
    +
  • Map Run +
    + Improving a Record +

    Timer sends a chat message to all players upon a player beating the Map Record. Missing it sends a message only to the player:

    + Beating a Map record +
    +
    + Checkpoint Comparison +

    Timer sends a chat message the player comparing their PB checkpoint times with the current run (value in brackets after the time indicate the Speed):

    + Checkpoints Comparison +
    +
  • +
  • Stage Records +
    + New Stage record and improving Stage record +

    Timer sends a chat message to all players upon a player beating a Stage/Bonus/Map record. Different scenarios for missed/comparing times are also available and shown in chat but only to the player who is doing the run:

    + Stage Records and comparisons +
    +
  • +
  • QOL +
    + Player Connected + Map Info (!mi / !tier) +

    LL is used for Local development and testing:

    + Player Connected + Map Info +
    +
    + Player Rank +

    Displays the rank of the player on the current map:

    + Player Rank +
    +
  • +
+
+ +
+ Player Commands +

We recommend making binds using the Console commands, chat commands may flood the server and not always work.

+
    +
  • Saveloc (Practice Mode) +
    + Save the current location +

    Chat: !saveloc

    +

    Console: css_saveloc

    +
    +
    + Teleport to the last saved location +

    Chat: !tele

    +

    Console: css_tele

    +
    +
    + Teleport to the previous saved location +

    Chat: !teleprev

    +

    Console: css_teleprev

    +
    +
    + Teleport to the next saved location +

    Chat: !telenext

    +

    Console: css_telenext

    +
    +
  • +
+
    +
  • Spectate +
    + Enter Spectator Mode +

    Chat: !spec

    +

    Console: css_spec

    +
    +
    + Exiting Spectator Mode +

    No command currently available to go back to Play Mode (time may NOT be reset and you will loose your progress post entering Spectator Mode)

    +

    Open team choosing menu M and select CT

    +
    +
  • +
+
+
+ +# Main list with tasks (more details can be found [here](https://github.com/CS2Surf/Timer/blob/dev/TODO)): *Note: This is not definitive/complete and simply serves as a reference for what we should try to achieve. Subject to change.* Bold & Italics = being worked on. - - [ ] Database - - [ ] MySQL database schema ([W.I.P Design Diagram](https://dbdiagram.io/d/CS2Surf-Timer-DB-Schema-6560b76b3be1495787ace4d2)) + - [X] MySQL database schema ([Design Diagram](https://dbdiagram.io/d/CS2Surf-Timer-DB-Schema-6560b76b3be1495787ace4d2)) - [ ] Plugin auto-create tables for easier setup? - [X] Base database class implementation -- [ ] Maps +- [X] Maps - [X] Implement map info object (DB) - - [ ] Zoning + - [X] Zoning - [X] Hook zones from map triggers - [X] Map start/end zones - [X] Stage zones @@ -23,45 +178,46 @@ Bold & Italics = being worked on. - [X] Support for stages/checkpoints - [X] Hook to their start/end zones - [X] Save/Compare checkpoint times - - [ ] Save Stage times + - [X] Save Stage times - [X] Support for bonuses - [X] Hook to their start/end zones - - [ ] Save Bonus times + - [X] Save Bonus times - [X] Start/End trigger touch hooks - [X] Load zone information automatically from standardised triggers: https://github.com/CS2Surf/Timer/wiki/CS2-Surf-Mapping - - [X] _**Support for stages (`/rs`, teleporting with `/s`)**_ - - [ ] _**Support for bonuses (`/rs`, teleporting with `/b #`)**_ - - [ ] _**Start/End touch hooks implemented for all zones**_ + - [X] Support for stages (`/rs`, teleporting with `/s`) + - [X] Support for bonuses (`/rs`, teleporting with `/b #`) + - [X] Start/End touch hooks implemented for all zones - [ ] Surf configs - [X] Server settings configuration - [ ] Plugin configuration - [X] Database configuration -- [ ] Timing +- [X] Timing - [X] Base timer class implementation - [X] Base timer HUD implementation - [X] Prespeed measurement and display - - [ ] Save/load times + - [X] Save/load times - [x] Map times - [x] Checkpoint times - - [ ] Stage times - - [ ] Bonus times + - [X] Stage times + - [X] Bonus times - [X] Practice Mode implementation - [ ] Announce records to Discord - [ ] Stretch goal: sub-tick timing - [ ] Player Data - [X] Base player class - - [ ] **_Player stat classes_** + - [ ] Player stat classes - [ ] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) -- [x] Replays - Not tracking Stage/Bonus times but Replay functionality for them is there +- [x] Replays - [x] Personal Best - [x] Map Record - - [ ] Stage Record - - [ ] Bonus Record + - [X] Stage Record + - [X] Bonus Record - [x] World Record - [X] Map Record - - [ ] Stage Record - - [ ] Bonus Record + - [X] Stage Record + - [X] Bonus Record - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) +- [ ] API Integration (Repo can be found [here](https://github.com/CS2Surf/CS2-Surf-API)) diff --git a/TODO b/TODO new file mode 100644 index 0000000..58070b4 --- /dev/null +++ b/TODO @@ -0,0 +1,65 @@ +- Re-add the MySQL queries in code and make it switch between API and DB functions +- Map Time is NOT being saved with API +- Make configs generate themselves inside the `./configs/plugins/...` folder +- Fix loading MapTimes for each type (stage, bonus, maps) + - API + - DB +- Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin +- Change `DB_QUERY_PB_GET_RUNTIME` query in API + +- Double check the Start/End velocities for End Zone,Checkpoints, Stages, Bonuses + + Maps + + Checkpoints + - Stages + - Try and get the Start/End velocity values from the Checkpoint object in Map Run + + Bonuses + +- Using `!spec` from any start zone and going back to CT starts your timer + +- Try and determine Left/Right side for Bonuses (surf_ace) +- Try and determine Left/Right side for Maps +- Try and determine Left/Right side for Stages +# Done ++ Hibernation fucks up the creation of `CurrentMap`, if nobody has joined the first map it was on and you change to another map, server segfaults. I guess a query is being held?? :think: + + Solution is to use `Server.NextWorldUpdate` as hibernation stops all `Timer`s :peepoHappy: ++ Introduce `async` for queries and some methods ++ Make Database spawn a new connection for each query to avoid `Connection in use` error + + Add notes to apply `using` blocks for certain methods for proper operation ++ Move **PlayerProfile** queries to the respective class ++ Move **Map** queries to the respective class ++ Total Completions for a map are wrong in HUD ++ Add `Situation` to each `ReplayFrame` and load it properly + + Fix `STAGE_EXIT_ZONE` situations - not triggered (logic in `TriggerEndTouch` is most likely bad) ++ Fix Map Replays + + Reload after setting a new WR + + Load if no replay existed before a new run + + Use the `ReplayFrameSituation` for determining where to cut replays + + Do not cut replay as soon as we enter end zone (determined by the AddTimer amount for `SaveMapTime`) ++ Double check the Checkpoints comparison messages + + 1st run: [CS2 Surf] CP [1]: 02.468 (1083) [PB: N/A (N/A) | WR: N/A (N/A)] + + 2nd run: [CS2 Surf] CP [1]: 02.421 (1128) [PB: +02.390 (+46) | WR: +02.390 (+46)] ++ Fix Stage replays + + Track the times even during Map Run on staged maps + + Include the pre-strafe + + Load all of them instead of overwriting and cycle (maybe make StageWR a Dictionary) + + Reload them after setting a new Stage WR + + Load if no replay existed before + + Use the `ReplayFrameSituation` for determining where to cut replays + + Save stage replays when `IsStageMode` is enabled + + Use seperate method + + Save the correct amount of `Ticks` for stage runs during map run + + Use seperate method + + Save the last stage time - triggered in `END_ZONE_ENTER` + + `IsStageMode` + + Use seperate method + + Map run + + Use seperate method + + When loading new stage replays the cycling of them does not work ++ First ever map run does not save Checkpoints, check if we wait to retrieve the MapTime_ID before trying to insert Checkpoints ++ Trim different type of replays based on situations + + Map Replays + + Stage Replays during Map runs + + Stage Replays in `IsStageMode` + + Bonus Replays ++ Replay bots are not being spawned on `Linear` maps + + Needs the CS2Fixes NAV mesh patch to work with our implementation \ No newline at end of file diff --git a/src/ST-API/Api.cs b/src/ST-API/Api.cs new file mode 100644 index 0000000..78a9fbe --- /dev/null +++ b/src/ST-API/Api.cs @@ -0,0 +1,41 @@ +using System.Net.Http.Json; + +namespace SurfTimer; + +internal class ApiCall +{ + public static async Task Api_Save_Stage_MapTime(Player player) + { + // This is a trick to record the time before the player exits the start zone + int last_exit_tick = player.ReplayRecorder.LastExitTick(); + int last_enter_tick = player.ReplayRecorder.LastEnterTick(); + + // player.Controller.PrintToChat($"CS2 Surf DEBUG >> OnTriggerStartTouch -> Last Exit Tick: {last_exit_tick} | Current Frame: {player.ReplayRecorder.Frames.Count}"); + + int stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this + int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; + + int tt = -1; + if (last_exit_tick - last_enter_tick > 2 * 64) + tt = last_exit_tick - 2 * 64; + else + tt = last_enter_tick; + + API_CurrentRun stage_time = new() + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = player.Timer.Style, + type = 2, + stage = player.Timer.Stage - 1, + run_time = stage_run_time, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) + + }; + + await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_CR_SAVE_STAGE_TIME, stage_time); + // player.Stats.LoadStageTime(player); + // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD + } +} \ No newline at end of file diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs new file mode 100644 index 0000000..82d3f73 --- /dev/null +++ b/src/ST-API/Comms.cs @@ -0,0 +1,85 @@ +using System.Net.Http.Json; + +namespace SurfTimer; + +internal class ApiMethod +{ + private ApiMethod() { } + + private static readonly HttpClient _client = new(); + private static readonly string base_addr = Config.ApiUrl; + + public static async Task GET(string url) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task GET -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.GetAsync(uri); + + try + { + System.Console.WriteLine($"[API] GET {url} => {response.StatusCode}"); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + Exception exception = new Exception("[API] GET - No data found"); + throw exception; + } + + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } + + public static async Task POST(string url, T body) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task POST -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.PostAsJsonAsync(uri, body); + + try + { + System.Console.WriteLine($"[API] POST {url} => {response.StatusCode}"); + response.EnsureSuccessStatusCode(); // BAD BAD BAD + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } + + public static async Task PUT(string url, T body) + { + var uri = new Uri(base_addr + url); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task PUT -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); +#endif + + using var response = await _client.PutAsJsonAsync(uri, body); + + try + { + System.Console.WriteLine($"[API] PUT {url} => {response.StatusCode}"); + response.EnsureSuccessStatusCode(); // BAD BAD BAD + return await response.Content.ReadFromJsonAsync(); + } + catch + { + Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + return default; + } + } +} \ No newline at end of file diff --git a/src/ST-UTILS/ConVar.cs b/src/ST-API/ConVar.cs similarity index 91% rename from src/ST-UTILS/ConVar.cs rename to src/ST-API/ConVar.cs index 55206ba..c19eefc 100644 --- a/src/ST-UTILS/ConVar.cs +++ b/src/ST-API/ConVar.cs @@ -2,7 +2,7 @@ namespace SurfTimer; -internal class ConVarHelper +internal static class ConVarHelper { public static void RemoveCheatFlagFromConVar(string cv_name) { diff --git a/src/ST-UTILS/Compression.cs b/src/ST-API/JsonConverters.cs similarity index 83% rename from src/ST-UTILS/Compression.cs rename to src/ST-API/JsonConverters.cs index 25ff2b2..0e50238 100644 --- a/src/ST-UTILS/Compression.cs +++ b/src/ST-API/JsonConverters.cs @@ -6,6 +6,28 @@ namespace SurfTimer; +internal static class JsonSelp +{ + public static Dictionary ConstructJsonDictFromString(string str) + { + using (JsonDocument document = JsonDocument.Parse(str)) + { + // Access the root element + JsonElement root = document.RootElement; + + // Create a dictionary to store the parsed JSON data + Dictionary dictionary = new Dictionary(); + + foreach (JsonProperty property in root.EnumerateObject()) + { + dictionary[property.Name] = property.Value.ToString(); + } + + return dictionary; + } + } +} + internal class VectorConverter : JsonConverter { public override Vector Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -102,7 +124,7 @@ public override void Write(Utf8JsonWriter writer, QAngle value, JsonSerializerOp } } -internal class Compressor +internal static class Compressor { public static string Decompress(string input) { @@ -126,22 +148,20 @@ public static byte[] Decompress(byte[] input) source.Read(lengthBytes, 0, 4); var length = BitConverter.ToInt32(lengthBytes, 0); - using (var decompressionStream = new GZipStream(source, - CompressionMode.Decompress)) + 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; + totalRead += bytesRead; } - return result; } } } - public static byte[] Compress(byte[] input) + public static byte[] Compress(byte[] input) { using (var result = new MemoryStream()) { @@ -158,4 +178,9 @@ public static byte[] Compress(byte[] input) return result.ToArray(); } } + + internal static string Decompress(byte v) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/ST-UTILS/Schema.cs b/src/ST-API/Schema.cs similarity index 100% rename from src/ST-UTILS/Schema.cs rename to src/ST-API/Schema.cs diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs new file mode 100644 index 0000000..2b638df --- /dev/null +++ b/src/ST-API/Structures.cs @@ -0,0 +1,87 @@ +namespace SurfTimer; + +// Map Info structure +internal class API_PostResponseData +{ + public int inserted { get; set; } + public float xtime { get; set; } + public int last_id { get; set; } + public int? trx { get; set; } +} + +internal class API_Checkpoint +{ + public int cp { get; set; } + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int end_touch { get; set; } + public int attempts { get; set; } +} + +internal class API_CurrentRun +{ + public int player_id { get; set; } + public int map_id { get; set; } + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public List? checkpoints { get; set; } = null; + public string replay_frames { get; set; } = ""; // This needs to be checked touroughly + public int? run_date { get; set; } = null; +} + +internal class API_MapInfo +{ + public int id { get; set; } = 0; + public string name { get; set; } = "N/A"; + public string author { get; set; } = "Unknown"; + public int tier { get; set; } = 0; + public int stages { get; set; } = 0; + public int bonuses { get; set; } = 0; + public int ranked { get; set; } = 0; + public int? date_added { get; set; } = null; + public int? last_played { get; set; } = null; +} + +internal class API_MapTime +{ + public int id { get; set; } + public int player_id { get; set; } + public int map_id { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int run_date { get; set; } + public string replay_frames { get; set; } = ""; // This needs to be checked touroughly + public List? checkpoints { get; set; } = null; + public string name { get; set; } = "N/A"; +} + +internal class API_PlayerSurfProfile +{ + public string name { get; set; } = "N/A"; + public int steam_id { get; set; } + public string country { get; set; } = "N/A"; + public int join_date { get; set; } + public int last_seen { get; set; } + public int connections { get; set; } +} \ No newline at end of file diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 4bebc09..671a15e 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -19,11 +19,23 @@ public void MapTier(CCSPlayerController? player, CommandInfo command) if (player == null) return; + string msg = $"{Config.PluginPrefix} {CurrentMap.Name} - Tier {ChatColors.Green}{CurrentMap.Tier}{ChatColors.Default} - Author {ChatColors.Yellow}{CurrentMap.Author}{ChatColors.Default} - Added {ChatColors.Yellow}{DateTimeOffset.FromUnixTimeSeconds(CurrentMap.DateAdded).DateTime.ToString("dd.MM.yyyy HH:mm")}{ChatColors.Default}"; + if (CurrentMap.Stages > 1) - player.PrintToChat($"{PluginPrefix} {CurrentMap.Name} - {ChatColors.Green}Tier {CurrentMap.Tier}{ChatColors.Default} - Staged {ChatColors.Yellow}{CurrentMap.Stages} Stages{ChatColors.Default}"); + { + msg = string.Concat(msg, " - ", $"Stages {ChatColors.Yellow}{CurrentMap.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; + { + msg = string.Concat(msg, " - ", $"Linear {ChatColors.Yellow}{CurrentMap.TotalCheckpoints} Checkpoints{ChatColors.Default}"); + } + + if (CurrentMap.Bonuses > 0) + { + msg = string.Concat(msg, " - ", $"Bonuses {ChatColors.Yellow}{CurrentMap.Bonuses}"); + } + + player.PrintToChat(msg); } [ConsoleCommand("css_triggers", "List all valid zone triggers in the map.")] diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 3e51527..3f6bb58 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -1,7 +1,6 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Utils; @@ -13,75 +12,165 @@ public partial class SurfTimer [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerReset(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) return; + Player oPlayer = playerList[player.UserId ?? 0]; + if (oPlayer.ReplayRecorder.IsSaving) + { + player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); + return; + } // 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))); - return; } [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you're in.")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) return; - // 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))); - else // Reset back to map start - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, new QAngle(0, 0, 0), new Vector(0, 0, 0))); - return; + Player oPlayer = playerList[player.UserId ?? 0]; + if (oPlayer.ReplayRecorder.IsSaving) + { + player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); + return; + } + + + if (oPlayer.Timer.IsBonusMode) + { + if (oPlayer.Timer.Bonus != 0 && CurrentMap.BonusStartZone[oPlayer.Timer.Bonus] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[oPlayer.Timer.Bonus], CurrentMap.BonusStartZoneAngles[oPlayer.Timer.Bonus], 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))); + } + + else + { + if (oPlayer.Timer.Stage != 0 && CurrentMap.StageStartZone[oPlayer.Timer.Stage] != new Vector(0, 0, 0)) + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[oPlayer.Timer.Stage], CurrentMap.StageStartZoneAngles[oPlayer.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))); + } } [ConsoleCommand("css_s", "Teleport to a stage")] + [ConsoleCommand("css_stage", "Teleport to a stage")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) return; - int stage = Int32.Parse(command.ArgByIndex(1)) - 1; - if (stage > CurrentMap.Stages - 1 && CurrentMap.Stages > 0) - stage = CurrentMap.Stages - 1; + int stage; + try + { + stage = Int32.Parse(command.ArgByIndex(1)); + } + catch (System.Exception) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + return; + } // Must be 1 argument - if (command.ArgCount < 2 || stage < 0) + if (command.ArgCount < 2 || stage <= 0) { - #if DEBUG - player.PrintToChat($"CS2 Surf DEBUG >> css_s >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); - #endif +#if DEBUG + player.PrintToChat($"CS2 Surf DEBUG >> css_stage >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); +#endif - player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.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."); + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no stages."); + return; + } + + else if (stage > CurrentMap.Stages) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided, this map has {ChatColors.Green}{CurrentMap.Stages} stages."); return; } if (CurrentMap.StageStartZone[stage] != new Vector(0, 0, 0)) { - if (stage == 0) + playerList[player.UserId ?? 0].Timer.Reset(); + + if (stage == 1) + { Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, CurrentMap.StartZoneAngles, new Vector(0, 0, 0))); + } else + { + playerList[player.UserId ?? 0].Timer.Stage = stage; 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; + 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. } else - player.PrintToChat($"{PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); + } + + [ConsoleCommand("css_b", "Teleport to a bonus")] + [ConsoleCommand("css_bonus", "Teleport to a bonus")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) + { + if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + return; + + int bonus; + + // Check for argument count + if (command.ArgCount < 2) + { + if (CurrentMap.Bonuses > 0) + bonus = 1; + else + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!bonus "); + return; + } + } + + else + bonus = Int32.Parse(command.ArgByIndex(1)); + + if (CurrentMap.Bonuses <= 0) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no bonuses."); + return; + } + + else if (bonus > CurrentMap.Bonuses) + { + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided, this map has {ChatColors.Green}{CurrentMap.Bonuses} bonuses."); + return; + } + + if (CurrentMap.BonusStartZone[bonus] != new Vector(0, 0, 0)) + { + playerList[player.UserId ?? 0].Timer.Reset(); + playerList[player.UserId ?? 0].Timer.IsBonusMode = true; + + Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[bonus], CurrentMap.BonusStartZoneAngles[bonus], new Vector(0, 0, 0))); + } + + else + player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided. Usage: {ChatColors.Green}!bonus "); } [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] @@ -93,85 +182,251 @@ public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo comma player.ChangeTeam(CsTeam.Spectator); } + [ConsoleCommand("css_rank", "Show the current rank of the player for the style they are in")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void PlayerRank(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + player.PrintToChat($"{Config.PluginPrefix} Your current rank for {ChatColors.Gold}{CurrentMap.Name}{ChatColors.Default} is {ChatColors.Green}{playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank}{ChatColors.Default} out of {ChatColors.Yellow}{playerList[player.UserId ?? 0].CurrMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]}"); + } + + [ConsoleCommand("css_testx", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void TestCmd(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + // player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Testing 'PB_LoadMapTimeData'"); + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); + player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); + player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); + player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); + player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); + player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); + player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); + player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].Ticks)}"); + // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Orange}====== MAP ======"); + player.PrintToChat($"{Config.PluginPrefix} Map ID: {ChatColors.Green}{CurrentMap.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Map Name: {ChatColors.Green}{CurrentMap.Name}"); + player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); + player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); + player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Purple}====== REPLAYS ======"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.Frames.Count: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.IsRecording: {ChatColors.Green}{oPlayer.ReplayRecorder.IsRecording}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlaying}"); + + /* + for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) + { + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].IsPlayable}"); + } + */ + + /* + for (int i = 0; i < CurrentMap.ReplayManager.MapWR.Frames.Count; i++) + { + ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + player.PrintToChat($"Start Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.PrintToChat($"Start Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.PrintToChat($"Stage Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.PrintToChat($"Stage Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.PrintToChat($"Checkpoint Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.PrintToChat($"Checkpoint Exit: {i} | Situation {x.Situation}"); + break; + } + } + */ + // for (int i = 0; i < CurrentMap.ReplayManager.MapWR.MapSituations.Count; i++) + // { + // ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + // switch (x.Situation) + // { + // case ReplayFrameSituation.START_ZONE_ENTER: + // player.PrintToChat($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.START_ZONE_EXIT: + // player.PrintToChat($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_ENTER: + // player.PrintToChat($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_EXIT: + // player.PrintToChat($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + // player.PrintToChat($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + // player.PrintToChat($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // } + // } + + // player.PrintToChat($"{Config.PluginPrefix} IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} Player.IsSpectating: {ChatColors.Green}{oPlayer.IsSpectating(CurrentMap.ReplayManager.MapWR.Controller!)}"); + // player.PrintToChat($"{Config.PluginPrefix} Name & MapTimeID: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordPlayerName} {CurrentMap.ReplayManager.MapWR.MapTimeID}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayCurrentRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.ReplayCurrentRunTime}"); + // player.PrintToChat($"{Config.PluginPrefix} RepeatCount: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RepeatCount}"); + // player.PrintToChat($"{Config.PluginPrefix} IsReplayOutsideZone: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsReplayOutsideZone}"); + // player.PrintToChat($"{Config.PluginPrefix} CurrentFrameTick: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.CurrentFrameTick}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayRecorder.Frames.Length: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + + // if (CurrentMap.ReplayManager.StageWR != null) + // { + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.MapTimeID - Stage: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.MapTimeID} - {CurrentMap.ReplayManager.StageWR.Stage}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsEnabled: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsEnabled}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPaused: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPaused}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.Controller Null?: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.Controller == null}"); + // } + } + /* ######################### - Reaplay Commands + Replay 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) + if (player == null || player.Team != CsTeam.Spectator) return; - foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + 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) + [ConsoleCommand("css_rbplay", "Start all replays from the start")] + public void PlayReplay(CCSPlayerController? player, CommandInfo command) { - if(player == null || player.Team != CsTeam.Spectator) + if (player == null || player.Team != CsTeam.Spectator) return; - foreach(ReplayPlayer rb in CurrentMap.ReplayBots) + Player oPlayer = playerList[player.UserId ?? 0]; + CurrentMap.ReplayManager.MapWR.ResetReplay(); + CurrentMap.ReplayManager.MapWR.Start(); + + CurrentMap.ReplayManager.StageWR?.ResetReplay(); + CurrentMap.ReplayManager.StageWR?.Start(); + + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - if(!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + if (!rb.IsPlayable || !rb.IsPlaying || !oPlayer.IsSpectating(rb.Controller!)) continue; - - rb.FrameTickIncrement *= -1; + + rb.Start(); } } - [ConsoleCommand("css_pbreplay", "Allows for replay of player's PB")] - public void PbReplay(CCSPlayerController? player, CommandInfo command) + [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) + if (player == null || player.Team != CsTeam.Spectator) return; - int maptime_id = playerList[player!.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].ID; - if (command.ArgCount > 1) + foreach (ReplayPlayer rb in CurrentMap.ReplayManager.CustomReplays) { - try - { - maptime_id = int.Parse(command.ArgByIndex(1)); - } - catch {} - } + if (!rb.IsPlayable || !rb.IsPlaying || !playerList[player.UserId ?? 0].IsSpectating(rb.Controller!)) + continue; - 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; - } + rb.FrameTickIncrement *= -1; } - - CurrentMap.ReplayBots = CurrentMap.ReplayBots.Prepend(new ReplayPlayer() { - Stat_MapTimeID = maptime_id, - Stat_Prefix = "PB" - }).ToList(); - - Server.NextFrame(() => { - Server.ExecuteCommand($"bot_quota {CurrentMap.ReplayBots.Count}"); - }); } - /* + // [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($"{Config.PluginPrefix} {ChatColors.Red}No time was found"); + // return; + // } + + // for(int i = 0; i < CurrentMap.ReplayBots.Count; i++) + // { + // if(CurrentMap.ReplayBots[i].MapTimeID == maptime_id) + // { + // player.PrintToChat($"{Config.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 ######################## @@ -179,13 +434,13 @@ 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)) + 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"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Cannot save location while not in run"); return; } @@ -193,74 +448,80 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) var player_angle = p.Controller.PlayerPawn.Value!.EyeAngles; var player_velocity = p.Controller.PlayerPawn.Value!.AbsVelocity; - p.SavedLocations.Add(new SavelocFrame { + 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.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"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(!p.Timer.IsRunning) + if (!p.Timer.IsRunning) p.Timer.Start(); if (!p.Timer.IsPracticeMode) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Timer now on practice"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Timer now on practice"); p.Timer.IsPracticeMode = true; } - if(command.ArgCount > 1) + if (command.ArgCount > 1) try { int tele_n = int.Parse(command.ArgByIndex(1)); if (tele_n < p.SavedLocations.Count) p.CurrentSavedLocation = tele_n; } - catch { } + catch + { + Exception exception = new("sum ting wong"); + throw exception; + } SavelocFrame location = p.SavedLocations[p.CurrentSavedLocation]; - Server.NextFrame(() => { + 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}"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(p.CurrentSavedLocation == 0) + if (p.CurrentSavedLocation == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at first location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at first location"); } else { @@ -269,26 +530,26 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.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)) + if (player == null || !player.PawnIsAlive || !playerList.ContainsKey(player.UserId ?? 0)) return; Player p = playerList[player.UserId ?? 0]; - if(p.SavedLocations.Count == 0) + if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); return; } - if(p.CurrentSavedLocation == p.SavedLocations.Count-1) + if (p.CurrentSavedLocation == p.SavedLocations.Count - 1) { - p.Controller.PrintToChat($"{PluginPrefix} {ChatColors.Red}Already at last location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at last location"); } else { @@ -297,6 +558,6 @@ public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.PluginPrefix} Teleported #{p.CurrentSavedLocation}"); } } \ No newline at end of file diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs index ca58143..1d6eea7 100644 --- a/src/ST-DB/DB.cs +++ b/src/ST-DB/DB.cs @@ -1,108 +1,123 @@ -namespace SurfTimer; +using MySqlConnector; -using System.Runtime.CompilerServices; -using CounterStrikeSharp.API; -using MySqlConnector; // https://dev.mysql.com/doc/connector-net/en/connector-net-connections-string.html +namespace SurfTimer; -// This will have functions for DB access and query sending internal class TimerDatabase { - private readonly MySqlConnection? _db; - private readonly string _connString = string.Empty; + private readonly string _connString; - public TimerDatabase() + public TimerDatabase(string connectionString) { - // Null'd + _connString = connectionString; } - - public TimerDatabase(string host, string database, string user, string password, int port, int timeout) + public void Dispose() { - this._connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; - this._db = new MySqlConnection(this._connString); - this._db.Open(); + Close(); } public void Close() { - if (this._db != null) - this._db!.Close(); + // Not needed } - public async Task Query(string query) + /// + /// Spawns a new connection to the database. + /// + /// DB Connection + private MySqlConnection GetConnection() { - return await Task.Run(async () => + var connection = new MySqlConnection(_connString); + try { - try - { - if (this._db == null) - { - throw new InvalidOperationException("Database connection is not open."); - } - - MySqlCommand cmd = new(query, this._db); - MySqlDataReader reader = await cmd.ExecuteReaderAsync(); + connection.Open(); + } + catch (MySqlException mysqlEx) // Specifically catch MySQL-related exceptions + { + Console.WriteLine($"[CS2 Surf] MySQL error when connecting: {mysqlEx.Message}"); + throw new InvalidOperationException("Could not establish a connection to the database.", mysqlEx); // Wrap the original exception with additional context + } + catch (System.Exception ex) // Catch all other exceptions + { + Console.WriteLine($"[CS2 Surf] General error when connecting to the database: {ex.Message}"); + throw; // Re-throw the exception without wrapping it + } - return reader; - } - catch (Exception ex) - { - Console.WriteLine($"Error executing query: {ex.Message}"); - throw; - } - }); + return connection; } - public async Task Write(string query) + /// + /// Always encapsulate the block with `using` when calling this method. + /// That way we ensure the proper disposal of the `MySqlDataReader` when we are finished with it. + /// + /// SELECT query to execute + public async Task QueryAsync(string query) { - return await Task.Run(async () => + try { - try - { - if (this._db == null) - { - throw new InvalidOperationException("Database connection is not open."); - } - - MySqlCommand cmd = new(query, this._db); - int rowsAffected = await cmd.ExecuteNonQueryAsync(); - - return rowsAffected; - } - catch (Exception ex) - { - Console.WriteLine($"Error executing write operation: {ex.Message}"); - throw; - } - }); + var connection = GetConnection(); + var cmd = new MySqlCommand(query, connection); + return await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.CloseConnection); + } + catch (Exception ex) + { + Console.WriteLine($"Error executing query {query}: {ex.Message}"); + throw; + } } - public async Task Transaction(List commands) + /// + /// Automatically disposes of the connection and command are disposed of after usage. + /// No need to encapsulate in `using` block. + /// + /// INSERT/UPDATE query to execute + public async Task WriteAsync(string query) { - if (this._db == null) + try { - throw new InvalidOperationException("Database connection is not open."); + using var connection = GetConnection(); + using var cmd = new MySqlCommand(query, connection); + return await cmd.ExecuteNonQueryAsync(); } - - using (var transaction = await this._db.BeginTransactionAsync()) + catch (Exception ex) { - try - { - foreach (var command in commands) - { - using (var cmd = new MySqlCommand(command, this._db, transaction)) - { - await cmd.ExecuteNonQueryAsync(); - } - } + Console.WriteLine($"Error executing write operation {query}: {ex.Message}"); + throw; + } + } - await transaction.CommitAsync(); - } - catch + /// + /// Begins a transaction and executes it on the database. + /// Used for inputting `Checkpoints` data after a run has been finished. + /// No need to encapsulate in a `using` block, method disposes of connection and data itself. + /// + /// INSERT/UPDATE queries to execute + public async Task TransactionAsync(List commands) + { + // Create a new connection and open it + using var connection = GetConnection(); + + // Begin a transaction on the connection + using var transaction = await connection.BeginTransactionAsync(); + + try + { + // Execute each command within the transaction + foreach (var commandText in commands) { - await transaction.RollbackAsync(); - throw; // rethrow the exception to be handled by the caller + using var cmd = new MySqlCommand(commandText, connection, transaction); + await cmd.ExecuteNonQueryAsync(); } + + // Commit the transaction + await transaction.CommitAsync(); + } + catch + { + // Roll back the transaction if an error occurs + await transaction.RollbackAsync(); + throw; } + // The connection and transaction are disposed here } } diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index a4fe6c5..283ce2f 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -13,34 +13,75 @@ public partial class SurfTimer public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) { var controller = @event.Userid; - if(!controller.IsValid || !controller.IsBot) + if (!controller.IsValid || !controller.IsBot || CurrentMap.ReplayManager.IsControllerConnectedToReplayPlayer(controller)) return HookResult.Continue; - for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) + // Set the controller for the MapWR bot + if (!CurrentMap.ReplayManager!.MapWR.IsPlayable) { - if(CurrentMap.ReplayBots[i].IsPlayable) - continue; - - int repeats = -1; - if(CurrentMap.ReplayBots[i].Stat_Prefix == "PB") - repeats = 3; - - CurrentMap.ReplayBots[i].SetController(controller, repeats); - Server.PrintToChatAll($"{PluginPrefix} {ChatColors.Lime}Loading replay data..."); - AddTimer(2f, () => { - if(!CurrentMap.ReplayBots[i].IsPlayable) - return; - - CurrentMap.ReplayBots[i].Controller!.RemoveWeapons(); - - CurrentMap.ReplayBots[i].LoadReplayData(DB!); - - CurrentMap.ReplayBots[i].Start(); + CurrentMap.ReplayManager.MapWR.SetController(controller, -1); + CurrentMap.ReplayManager.MapWR.LoadReplayData(); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.MapWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.MapWR.Start(); + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + + return HookResult.Continue; + } + + // Set the controller for the StageWR bot + if (CurrentMap.ReplayManager.StageWR != null && !CurrentMap.ReplayManager.StageWR.IsPlayable) + { + CurrentMap.ReplayManager.StageWR.SetController(controller, 3); + CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.StageWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.StageWR.Start(); + CurrentMap.ReplayManager.StageWR.FormatBotName(); + }); + + return HookResult.Continue; + } + + // Spawn the BonusWR bot + if (CurrentMap.ReplayManager.BonusWR != null && !CurrentMap.ReplayManager.BonusWR.IsPlayable) + { + CurrentMap.ReplayManager.BonusWR.SetController(controller, 3); + CurrentMap.ReplayManager.BonusWR.LoadReplayData(); + + AddTimer(1.5f, () => + { + CurrentMap.ReplayManager.BonusWR.Controller!.RemoveWeapons(); + CurrentMap.ReplayManager.BonusWR.Start(); + CurrentMap.ReplayManager.BonusWR.FormatBotName(); }); - + return HookResult.Continue; } + // // Spawn the CustomReplays bot (for PB replays?) - T + // CurrentMap.ReplayManager.CustomReplays.ForEach(replay => + // { + // if (!replay.IsPlayable) + // { + // replay.SetController(controller, 3); + // replay.LoadReplayData(); + + // AddTimer(1.5f, () => { + // replay.Controller!.RemoveWeapons(); + // replay.Start(); + // replay.FormatBotName(); + // }); + + // return; + // } + // }); + return HookResult.Continue; } @@ -48,118 +89,46 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) 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) + string name = player.PlayerName; + string country = "XX"; + + // GeoIP + // Check if the IP is private before attempting GeoIP lookup + string ipAddress = player.IpAddress!.Split(":")[0]; + if (!IsPrivateIP(ipAddress)) { - return HookResult.Continue; + DatabaseReader geoipDB = new(Config.PluginPath + "data/GeoIP/GeoLite2-Country.mmdb"); + country = geoipDB.Country(ipAddress).Country.IsoCode ?? "XX"; + geoipDB.Dispose(); } else { - int dbID, joinDate, lastSeen, connections; - string name, country; - - // GeoIP - DatabaseReader geoipDB = new DatabaseReader(PluginPath + "data/GeoIP/GeoLite2-Country.mmdb"); - if (geoipDB.Country(player.IpAddress!.Split(":")[0]).Country.IsoCode is not null) - { - country = geoipDB.Country(player.IpAddress!.Split(":")[0]).Country.IsoCode!; - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> GeoIP -> {player.PlayerName} -> {player.IpAddress!.Split(":")[0]} -> {country}"); - #endif - } - else - country = "XX"; - geoipDB.Dispose(); + country = "LL"; // Handle local IP appropriately + } +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnectFull -> GeoIP -> {name} -> {player.IpAddress!.Split(":")[0]} -> {country}"); +#endif + if (DB == null) + { + Exception ex = new("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldn't happen."); + throw ex; + } - if (DB == null) - throw new Exception("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldnt happen."); + // Create Player object and add to playerList + PlayerProfile Profile = PlayerProfile.CreateAsync(player.SteamID, name, country).GetAwaiter().GetResult(); + playerList[player.UserId ?? 0] = new Player(player, + new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), + Profile, CurrentMap); - // 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()) - { - // Player exists in database - dbID = playerData.GetInt32("id"); - name = playerData.GetString("name"); - if (country == "XX" && playerData.GetString("country") != "XX") - country = playerData.GetString("country"); - joinDate = playerData.GetInt32("join_date"); - lastSeen = playerData.GetInt32("last_seen"); - connections = playerData.GetInt32("connections"); - playerData.Close(); - - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> Returning player {name} ({player.SteamID}) loaded from database with ID {dbID}"); - #endif - } - else - { - playerData.Close(); - // Player does not exist in database - name = player.PlayerName; - joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - lastSeen = joinDate; - 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}); - "); - 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; - if (newPlayerData.HasRows && newPlayerData.Read()) - { - #if DEBUG - // Iterate through data: - for (int i = 0; i < newPlayerData.FieldCount; i++) - { - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnect -> newPlayerData[{i}] = {newPlayerData.GetValue(i)}"); - } - #endif - dbID = newPlayerData.GetInt32("id"); - } - else - throw new Exception($"CS2 Surf ERROR >> OnPlayerConnect -> Failed to get new player's database ID after writing, this shouldnt happen. Player: {name} ({player.SteamID})"); - newPlayerData.Close(); - - #if DEBUG - 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); - playerList[player.UserId ?? 0] = new Player(player, - new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), - Profile, CurrentMap); - - #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], DB); // Will reload PB and Checkpoints for the player for all styles - 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}."); - Console.WriteLine($"[CS2 Surf] {player.PlayerName} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); - return HookResult.Continue; - } + // Load MapTimes for the player's PB and their Checkpoints + playerList[player.UserId ?? 0].Stats.LoadPlayerMapTimesData(playerList[player.UserId ?? 0]).GetAwaiter().GetResult(); // Holds here until result is available + + // Print join messages + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Green}{name}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); + Console.WriteLine($"[CS2 Surf] {name} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); + + return HookResult.Continue; } [GameEventHandler] // Player Disconnect Event @@ -167,15 +136,30 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo { var player = @event.Userid; - for (int i = 0; i < CurrentMap.ReplayBots.Count; i++) - if (CurrentMap.ReplayBots[i].IsPlayable && CurrentMap.ReplayBots[i].Controller!.Equals(player) && CurrentMap.ReplayBots[i].Stat_MapTimeID != -1) - CurrentMap.ReplayBots[i].Reset(); + if (player == null) + { + Console.WriteLine($"CS2 Surf ERROR >> OnPlayerDisconnect -> Null ({player == null})"); + return HookResult.Continue; + } + + if (CurrentMap.ReplayManager.MapWR.Controller != null && CurrentMap.ReplayManager.MapWR.Controller.Equals(player) && CurrentMap.ReplayManager.MapWR.MapID != -1) + CurrentMap.ReplayManager.MapWR.Reset(); + + if (CurrentMap.ReplayManager.StageWR != null && CurrentMap.ReplayManager.StageWR.Controller != null && CurrentMap.ReplayManager.StageWR.Controller.Equals(player) && CurrentMap.ReplayManager.StageWR.MapID != -1) + CurrentMap.ReplayManager.StageWR.Reset(); + + if (CurrentMap.ReplayManager.BonusWR != null && CurrentMap.ReplayManager.BonusWR.Controller != null && CurrentMap.ReplayManager.BonusWR.Controller.Equals(player)) + CurrentMap.ReplayManager.BonusWR!.Reset(); + + for (int i = 0; i < CurrentMap.ReplayManager.CustomReplays.Count; i++) + if (CurrentMap.ReplayManager.CustomReplays[i].Controller != null && CurrentMap.ReplayManager.CustomReplays[i].Controller!.Equals(player)) + CurrentMap.ReplayManager.CustomReplays[i].Reset(); + if (player.IsBot || !player.IsValid) { return HookResult.Continue; } - else { if (DB == null) @@ -188,15 +172,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; - "); - 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(); + playerList[player.UserId ?? 0].Profile.Update_Player_Profile(player.PlayerName).GetAwaiter().GetResult(); // Hold the thread until player data is updated // Remove player data from playerList playerList.Remove(player.UserId ?? 0); @@ -204,4 +180,30 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo return HookResult.Continue; } } + + /// + /// Checks whether an IP is a local one. Allows testing the plugin in a local environment setup for GeoIP + /// + /// IP to check + /// True for Private IP + static bool IsPrivateIP(string ip) + { + var ipParts = ip.Split('.'); + int firstOctet = int.Parse(ipParts[0]); + int secondOctet = int.Parse(ipParts[1]); + + // 10.x.x.x range + if (firstOctet == 10) + return true; + + // 172.16.x.x to 172.31.x.x range + if (firstOctet == 172 && (secondOctet >= 16 && secondOctet <= 31)) + return true; + + // 192.168.x.x range + if (firstOctet == 192 && secondOctet == 168) + return true; + + return false; + } } \ No newline at end of file diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 37703e4..2d59bb0 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -18,20 +18,73 @@ public void OnTick() // Need to disable maps from executing their cfgs. Currently idk how (But seriusly it a security issue) ConVar? bot_quota = ConVar.Find("bot_quota"); + // Console.WriteLine($"======== public void OnTick -> bot_quota not null? {bot_quota != null}"); if (bot_quota != null) { int cbq = bot_quota.GetPrimitiveValue(); - if(cbq != CurrentMap.ReplayBots.Count) + + int replaybot_count = 1 + + (CurrentMap.ReplayManager.StageWR != null ? 1 : 0) + + (CurrentMap.ReplayManager.BonusWR != null ? 1 : 0) + + CurrentMap.ReplayManager.CustomReplays.Count; + + if (cbq != replaybot_count) { - bot_quota.SetValue(CurrentMap.ReplayBots.Count); + bot_quota.SetValue(replaybot_count); } + + // Console.WriteLine($"======== public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}"); + } + + CurrentMap.ReplayManager.MapWR.Tick(); + CurrentMap.ReplayManager.StageWR?.Tick(); + CurrentMap.ReplayManager.BonusWR?.Tick(); + + // Here we will load the NEXT stage replay from AllStageWR + if (CurrentMap.ReplayManager.StageWR?.RepeatCount == 0) + { + int next_stage; + if (CurrentMap.ReplayManager.AllStageWR[(CurrentMap.ReplayManager.StageWR.Stage % CurrentMap.Stages) + 1][0].MapTimeID == -1) + next_stage = 1; + else + next_stage = (CurrentMap.ReplayManager.StageWR.Stage % CurrentMap.Stages) + 1; + + CurrentMap.ReplayManager.AllStageWR[next_stage][0].Controller = CurrentMap.ReplayManager.StageWR.Controller; + + // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for stage {CurrentMap.ReplayManager.StageWR.Stage}, changing to stage {next_stage}"); + CurrentMap.ReplayManager.StageWR = CurrentMap.ReplayManager.AllStageWR[next_stage][0]; + CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); + CurrentMap.ReplayManager.StageWR.FormatBotName(); + CurrentMap.ReplayManager.StageWR.Start(); } - for(int i = 0; i < CurrentMap!.ReplayBots.Count; i++) + if (CurrentMap.ReplayManager.BonusWR?.RepeatCount == 0) { - CurrentMap.ReplayBots[i].Tick(); - if (CurrentMap.ReplayBots[i].RepeatCount == 0) + int next_bonus; + if (CurrentMap.ReplayManager.AllBonusWR[(CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1][0].MapTimeID == -1) + next_bonus = 1; + else + next_bonus = (CurrentMap.ReplayManager.BonusWR.Stage % CurrentMap.Bonuses) + 1; + + CurrentMap.ReplayManager.AllBonusWR[next_bonus][0].Controller = CurrentMap.ReplayManager.BonusWR.Controller; + + // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for bonus {CurrentMap.ReplayManager.BonusWR.Stage}, changing to bonus {next_bonus}"); + CurrentMap.ReplayManager.BonusWR = CurrentMap.ReplayManager.AllBonusWR[next_bonus][0]; + CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); + CurrentMap.ReplayManager.BonusWR.FormatBotName(); + CurrentMap.ReplayManager.BonusWR.Start(); + } + + for (int i = 0; i < CurrentMap.ReplayManager.CustomReplays.Count; i++) + { + if (CurrentMap.ReplayManager.CustomReplays[i].MapID != CurrentMap.ID) + CurrentMap.ReplayManager.CustomReplays[i].MapID = CurrentMap.ID; + + CurrentMap.ReplayManager.CustomReplays[i].Tick(); + if (CurrentMap.ReplayManager.CustomReplays[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 65ddfc4..e685bd7 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -6,27 +6,38 @@ namespace SurfTimer; public partial class SurfTimer { - // Trigger end touch handler - CBaseTrigger_EndTouchFunc - // internal HookResult OnTriggerEndTouch(DynamicHook handler) + /// + /// Handler for trigger end touch hook - CBaseTrigger_EndTouchFunc. + /// + /// Sometimes this gets triggered when a player joins the server (for the 2nd time) so we assign `client` to `null` to bypass the error. + /// - T + /// internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - // CBaseTrigger trigger = handler.GetParam(0); CBaseTrigger trigger = new CBaseTrigger(caller.Handle); - // CBaseEntity entity = handler.GetParam(1); CBaseEntity entity = new CBaseEntity(activator.Handle); - CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); - 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 + CCSPlayerController client = null!; + + try { - return HookResult.Continue; + client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); + } + catch (System.Exception) + { + Console.WriteLine($"===================== [ERROR] OnTriggerEndTouch -> Could not assign `client` (name: {name})"); } + if (client == null || !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; + } else { // Implement Trigger End Touch Here Player player = playerList[client.UserId ?? 0]; - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_EndTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); - #endif +#endif if (trigger.Entity!.Name != null) { @@ -38,119 +49,147 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti 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") || - trigger.Entity.Name.Contains("stage1_start")) + if (trigger.Entity.Name.Contains("map_start") || + 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))); // Todo make a plugin convar for the time saved before start of run - } - // MAP START ZONE - if (!player.Timer.IsStageMode) + if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) { player.Timer.Start(); - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_RUN; - } - - /* Revisit - // 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))); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); } - */ // 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 + player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Map run - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); - #endif +#endif + } + + // Map end zones -- hook into map_end + else if (trigger.Entity.Name == "map_end") + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_EXIT; } // Stage start zones -- hook into (s)tage#_start else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) { - #if DEBUG +#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.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoint.Count}"); - #endif + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.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) + int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + + // Set replay situation + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; + player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); + + // Start the Stage timer + if (player.Timer.IsStageMode && player.Timer.Stage == stage) { - 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}"); - Console.WriteLine($"currentCheckpoint.Attempts {currentCheckpoint.Attempts}"); - #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; - // 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; + player.Timer.Start(); + // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); - // 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"); + // Show Prespeed for Stages - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.ToString("0")} u/s"); } - else + else if (player.Timer.IsRunning) { - // Handle the case where the index is out of bounds +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); + Console.WriteLine($"currentCheckpoint.Attempts {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts}"); +#endif + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; + + // Show Prespeed for Checkpoints - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.ToString("0")} u/s"); } } // 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 +#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 + Console.WriteLine($"===================== player.Timer.Checkpoint {player.Timer.Checkpoint} - player.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.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) + if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoints.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; - // 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; +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); +#endif + + if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_EXIT; + player.ReplayRecorder.CheckpointExitSituations.Add(player.Timer.Ticks); + } + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; // 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"); } - else + } + + // Bonus start zones -- hook into (b)onus#_start + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) + { +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Bonus {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); +#endif + + // 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))); // Todo make a plugin convar for the time saved before start of run + } + + // BONUS START ZONE + if (!player.Timer.IsStageMode && player.Timer.IsBonusMode) { - // Handle the case where the index is out of bounds + player.Timer.Start(); + // Set the CurrentRunData values + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); } + + // Prespeed display + player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); + player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Bonus run } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index c0af249..d73d394 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -7,167 +7,361 @@ namespace SurfTimer; public partial class SurfTimer { - // Trigger start touch handler - CBaseTrigger_StartTouchFunc - // internal HookResult OnTriggerStartTouch(DynamicHook handler) + /// + /// Handler for trigger start touch hook - CBaseTrigger_StartTouchFunc + /// + /// CounterStrikeSharp.API.Core.HookResult + /// internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { - // CBaseTrigger trigger = handler.GetParam(0); CBaseTrigger trigger = new CBaseTrigger(caller.Handle); - // CBaseEntity entity = handler.GetParam(1); CBaseEntity entity = new CBaseEntity(activator.Handle); CCSPlayerController client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); 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; } - 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)) { - // 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)) - { - 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]; - #if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> {trigger.DesignerName} -> {trigger.Entity!.Name}"); - #endif + Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + Exception exception = new($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + throw exception; + } + // 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 + + if (DB == null) + { + Exception exception = new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + throw exception; + } + + 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_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; + int pStyle = player.Timer.Style; - if (trigger.Entity!.Name != null) + // Map end zones -- hook into map_end + if (trigger.Entity.Name == "map_end") { - // 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_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); - int style = player.Timer.Style; - - // 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.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_RUN; + player.Controller.PrintToCenter($"Map End"); - 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 + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run + player.Stats.ThisRun.EndVelX = velocity_x; // End speed for the Map run + player.Stats.ThisRun.EndVelY = velocity_y; // End speed for the Map run + player.Stats.ThisRun.EndVelZ = velocity_z; // End speed for the Map run - // 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} {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} {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} {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 - } - if (DB == null) - throw new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + // MAP END ZONE - Map RUN + if (player.Timer.IsRunning && !player.Timer.IsStageMode) + { + player.Timer.Stop(); + bool saveMapTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - - player.Stats.PB[style].Ticks = player.Timer.Ticks; // Reload the run_time for the HUD and also assign for the DB query + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR + { + saveMapTime = true; + int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Map{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)}{ChatColors.Default})"); + } + else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map + { + saveMapTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Map{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } + else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map + { + saveMapTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Map{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); + } + else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map + { + saveMapTime = true; + int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Map{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); + } + else // Player did not beat their existing PersonalBest for the map nor the map record + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } - #if DEBUG - Console.WriteLine($@"CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> + if (saveMapTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data + }); + } + +#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}, + VALUES ({player.Profile.ID}, {CurrentMap.ID}, {pStyle}, 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 - if(!player.Timer.IsPracticeMode) { - AddTimer(1.5f, async () => { - 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) - await 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 +#endif + + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) + { + // Should we also save a last stage run? + if (CurrentMap.Stages > 0) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); }); + } - // 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) + // This section checks if the PB is better than WR + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => { - 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(); + Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); + CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + } + + } + + // API + /* + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) { + API_CurrentRun? last_stage_time = null; + if (CurrentMap.Stages > 0) + { + int last_exit_tick = player.ReplayRecorder.LastExitTick(); + int last_enter_tick = player.ReplayRecorder.LastEnterTick(); + + int stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this + int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; + + int tt = -1; + if (last_exit_tick - last_enter_tick > 2*64) + tt = last_exit_tick - 2*64; + else + tt = last_enter_tick; + + last_stage_time = new API_CurrentRun + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = style, + type = 2, + stage = CurrentMap.Stages, + run_time = stage_run_time, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) + }; + } + AddTimer(1.5f, () => { + List checkpoints = new List(); + foreach (var cp in player.Stats.ThisRun.Checkpoint) + { + checkpoints.Add(new API_Checkpoint + { + cp = cp.Key, + run_time = cp.Value.Ticks, + start_vel_x = cp.Value.StartVelX, + start_vel_y = cp.Value.StartVelY, + start_vel_z = cp.Value.StartVelZ, + end_vel_x = cp.Value.EndVelX, + end_vel_y = cp.Value.EndVelY, + end_vel_z = cp.Value.EndVelZ, + end_touch = 0, // ????? + attempts = cp.Value.Attempts }); } + + API_CurrentRun map_time = new API_CurrentRun + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = style, + type = 0, + stage = 0, + run_time = player.Stats.ThisRun.Ticks, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + checkpoints = checkpoints, + replay_frames = player.ReplayRecorder.SerializeReplay() + }; + + Task.Run(async () => { + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved map time"); + await ApiCall.POST("/surftimer/savemaptime", map_time); + + if (last_stage_time != null) + { + await ApiCall.POST("/surftimer/savestagetime", last_stage_time); + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved last stage time"); + player.Stats.LoadStageTime(player); + } + + player.Stats.LoadMapTime(player); + await CurrentMap.ApiGetMapRecordAndTotals(); // 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[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => { + System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR PB"); + CurrentMap.ReplayManager.MapWR.LoadReplayData(); + + AddTimer(1.5f, () => { + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + }); } } + */ + } + else if (player.Timer.IsStageMode) + { + player.Timer.Stop(); - #if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); - #endif + if (!player.Timer.IsPracticeMode) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } } - // 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")) +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); +#endif + } + + // 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") + ) + { + // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts + if (!player.ReplayRecorder.IsSaving) { + player.ReplayRecorder.Reset(); // Start replay recording player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); player.Timer.Reset(); - player.Stats.ThisRun.Checkpoint.Clear(); // I have the suspicion that the `Timer.Reset()` does not properly reset this object :thonk: + player.Stats.ThisRun.Checkpoints.Clear(); player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); - #if DEBUG +#if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); - // player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc -> KeyValues: {trigger.Entity.KeyValues3}"); - #endif +#endif } + else + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Yellow}Please try restarting again, replay is still being saved."); + } + } + + // Stage start zones -- hook into (s)tage#_start + 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); + + if (!player.ReplayRecorder.IsRecording) + player.ReplayRecorder.Start(); + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; + player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Stage start zones -- hook into (s)tage#_start - else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) + bool failed_stage = false; + if (player.Timer.Stage == stage) + failed_stage = true; + + // Reset/Stop the Stage timer + // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) + if (player.Timer.IsStageMode) { - int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value) - 1; - player.Timer.Stage = stage; + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks; + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } + player.Timer.Reset(); + player.Timer.IsStageMode = true; + // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{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 + player.Timer.Stage = 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) +#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.Checkpoints.Count <= stage}"); +#endif + + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) + { + // Save Stage MapTime during a Map run + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { - player.Timer.Checkpoint = stage; // Stage = Checkpoint when in a run on a Staged map + int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } - #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[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); - #endif + player.Timer.Checkpoint = stage - 1; // Stage = Checkpoint when in a run on a Staged map - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(PluginPrefix); +#if DEBUG + Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {stage} | player.Timer.Checkpoint: {stage - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - Checkpoint cp2 = new Checkpoint(stage, + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) + { + Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, player.Timer.Ticks, velocity_x, velocity_y, @@ -175,33 +369,46 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn -1.0f, -1.0f, -1.0f, - -1.0f, - 0); - player.Stats.ThisRun.Checkpoint[stage] = cp2; + 0, + 1); + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; } - - #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"); - #endif } - // 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) +#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"); +#endif + } + + // 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); + player.Timer.Checkpoint = checkpoint; + + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < checkpoint) { - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); - player.Timer.Checkpoint = 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)}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif - // 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 (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) { - #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[{style}].Checkpoint.Count = {player.Stats.PB[style].Checkpoint.Count}"); - #endif - - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(PluginPrefix); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; + player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); + } + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) + { // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality Checkpoint cp2 = new Checkpoint(checkpoint, player.Timer.Ticks, @@ -211,18 +418,183 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn -1.0f, -1.0f, -1.0f, - -1.0f, - 0); - player.Stats.ThisRun.Checkpoint[checkpoint] = cp2; + 0, + 1); + player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; } + } + +#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"); +#endif + } + + // Bonus start zones -- hook into (b)onus#_start + 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); + player.Timer.Bonus = bonus; + + player.Timer.Reset(); + player.Timer.IsBonusMode = true; + + + player.ReplayRecorder.Reset(); + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + + player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); +#endif + } + + // Bonus end zones -- hook into (b)onus#_end + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success && player.Timer.IsBonusMode && player.Timer.IsRunning) + { + // To-do: verify the bonus trigger being hit! + int bonus_idx = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + + player.Timer.Stop(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); + + 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 - #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"); - #endif + bool saveBonusTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR + { + saveBonusTime = true; + int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)}{ChatColors.Default})"); + } + else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map + { + saveBonusTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } + else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus + { + saveBonusTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); + } + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus + { + saveBonusTime = true; + int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); + } + else // Player did not beat their existing personal best for the bonus + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + } + + // To-do: save to DB + if (!player.Timer.IsPracticeMode) + { + /* + AddTimer(1.5f, () => + { + API_CurrentRun bonus_time = new API_CurrentRun + { + player_id = player.Profile.ID, + map_id = player.CurrMap.ID, + style = pStyle, + type = 1, + stage = bonus_idx, + run_time = player.Stats.ThisRun.Ticks, + run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + replay_frames = player.ReplayRecorder.SerializeReplay() + }; + + Task.Run(async () => + { + await ApiMethod.POST("/surftimer/savebonustime", bonus_time); + player.Stats.LoadBonusTime(player); + await CurrentMap.Get_Map_Record_Runs(); // Reload the Map record and totals for the HUD + // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD + }); + }); + */ + if (saveBonusTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data + }); + } } } + } + return HookResult.Continue; + } - return HookResult.Continue; + /// + /// Deals with saving a Stage MapTime (Type 2) in the Database. + /// Should deal with `IsStageMode` runs, Stages during Map Runs and also Last Stage. + /// + /// Player object + /// Stage to save + /// Is it the last stage? + /// Run Time (Ticks) for the stage run + void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool saveLastStage = false) + { + // player.Controller.PrintToChat($"{Config.PluginPrefix} SaveStageTime received: {player.Profile.Name}, {stage}, {stage_run_time}, {saveLastStage}"); + int pStyle = player.Timer.Style; + if ( + stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks || + CurrentMap.StageWR[stage][pStyle].ID == -1 || + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time + ) + { + if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR + { + int timeImprove = CurrentMap.StageWR[stage][pStyle].Ticks - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})"); + } + else if (CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map + { + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}!"); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best + { + player.Controller.PrintToChat($"{Config.PluginPrefix} You finished {ChatColors.Yellow}Stage {stage}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, setting your new Personal Best!"); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time) // Player beating their existing Stage personal best + { + int timeImprove = player.Stats.StagePB[stage][pStyle].Ticks - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Stage {stage}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].Ticks)}{ChatColors.Default})"); + } + + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + // Save stage run + Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); + await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data + }); + } + else if (stage_run_time > CurrentMap.StageWR[stage][pStyle].Ticks && player.Timer.IsStageMode) // Player is behind the Stage WR for the map + { + int timeImprove = stage_run_time - CurrentMap.StageWR[stage][pStyle].Ticks; + player.Controller.PrintToChat($"{Config.PluginPrefix} You are behind the {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Grey}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, missing the record by {ChatColors.Red}+{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default} ({ChatColors.Gold}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})!"); } } -} \ No newline at end of file +} + diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index c1fb166..6758c23 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -1,3 +1,5 @@ +using System.Data; +using System.Text.Json; using System.Text.RegularExpressions; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; @@ -6,45 +8,126 @@ namespace SurfTimer; -internal 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) - public string Name {get; set;} = ""; - 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; - public int LastPlayed {get; set;} = 0; - public int TotalCompletions {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; } = string.Empty; + public string Author { get; set; } = ""; + public int Tier { get; set; } = 0; + public int Stages { get; set; } = 0; + public int TotalCheckpoints { 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; + /// + /// Map Completion Count - Refer to as MapCompletions[style] + /// + public Dictionary MapCompletions { get; set; } = new Dictionary(); + /// + /// Bonus Completion Count - Refer to as BonusCompletions[bonus#][style] + /// + public Dictionary[] BonusCompletions { get; set; } = new Dictionary[32]; + /// + /// Stage Completion Count - Refer to as StageCompletions[stage#][style] + /// + public Dictionary[] StageCompletions { get; set; } = new Dictionary[32]; + /// + /// Map World Record - Refer to as WR[style] + /// public Dictionary WR { get; set; } = new Dictionary(); + /// + /// Bonus World Record - Refer to as BonusWR[bonus#][style] + /// + public Dictionary[] BonusWR { get; set; } = new Dictionary[32]; + /// + /// Stage World Record - Refer to as StageWR[stage#][style] + /// + public Dictionary[] StageWR { get; set; } = new Dictionary[32]; + + /// + /// Not sure what this is for. + /// Guessing it's to do with Replays and the ability to play your PB replay. + /// + /// - T + /// public List ConnectedMapTimes { get; set; } = new List(); - public List ReplayBots { get; set; } = new List { new ReplayPlayer() }; // Zone Origin Information - // Map start/end zones - public Vector StartZone {get;} = new Vector(0,0,0); - public QAngle StartZoneAngles {get;} = new QAngle(0,0,0); - public Vector EndZone {get;} = new Vector(0,0,0); - // Map stage zones - public Vector[] StageStartZone {get;} = Enumerable.Repeat(0, 99).Select(x => new Vector(0,0,0)).ToArray(); - public QAngle[] StageStartZoneAngles {get;} = Enumerable.Repeat(0, 99).Select(x => new QAngle(0,0,0)).ToArray(); - // Map bonus zones - 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(); + /* Map Start/End zones */ + public Vector StartZone { get; set; } = new Vector(0, 0, 0); + public QAngle StartZoneAngles { get; set; } = new QAngle(0, 0, 0); + public Vector EndZone { get; set; } = new Vector(0, 0, 0); + /* Map Stage zones */ + public Vector[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector(0, 0, 0)).ToArray(); + public QAngle[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle(0, 0, 0)).ToArray(); + /* Map Bonus zones */ + 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(); + + public ReplayManager ReplayManager { get; set; } = null!; // Constructor - internal Map(string Name, TimerDatabase DB) + internal Map(string name) { // Set map name - this.Name = Name; - this.WR[0] = new PersonalBest(); // To-do: Implement styles + this.Name = name; + + // Initialize WR variables + foreach (int style in Config.Styles) + { + this.WR[style] = new PersonalBest(); + this.MapCompletions[style] = -1; + } + + for (int i = 0; i < 32; i++) + { + this.BonusWR[i] = new Dictionary(); + this.BonusWR[i][0] = new PersonalBest(); + this.BonusWR[i][0].Type = 1; + this.BonusCompletions[i] = new Dictionary(); + + this.StageWR[i] = new Dictionary(); + this.StageWR[i][0] = new PersonalBest(); + this.StageWR[i][0].Type = 2; + this.StageCompletions[i] = new Dictionary(); + } + } + + public static async Task CreateAsync(string name) + { + var map = new Map(name); + await map.InitializeAsync(); + return map; + } + + private async Task InitializeAsync() + { + // Load zones + Map_Load_Zones(); + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Zones have been loaded."); + + // Initialize ReplayManager with placeholder values + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Initializing ReplayManager(-1, {this.Stages > 0}, false, null!)"); + this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); // Adjust values as needed + + await Get_Map_Info(); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> We got MapID = {ID} ({Name})"); + } + + /// + /// Loops through all the hookzones found in the map and loads the respective zones + /// + // To-do: This loops through all the triggers. While that's great and comprehensive, some maps have two triggers with the exact same name, because there are two + // for each side of the course (left and right, for example). We should probably work on automatically catching this. + // Maybe even introduce a new naming convention? + internal void Map_Load_Zones() + { // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); // Gathering info_teleport_destinations from the map @@ -54,17 +137,17 @@ internal Map(string Name, TimerDatabase DB) if (trigger.Entity!.Name != null) { // Map start zone - if (trigger.Entity!.Name.Contains("map_start") || - trigger.Entity!.Name.Contains("stage1_start") || - trigger.Entity!.Name.Contains("s1_start")) + if (trigger.Entity!.Name.Contains("map_start") || + 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 && - (IsInZone(trigger.AbsOrigin!, trigger.Collision.BoundingRadius, teleport.AbsOrigin!) || - teleport.Entity!.Name.Contains("spawn_map_start") || - teleport.Entity!.Name.Contains("spawn_stage1_start") || + 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); @@ -81,25 +164,25 @@ internal Map(string Name, TimerDatabase DB) } // Map end zone - else if (trigger.Entity!.Name.Contains("map_end")) + else if (trigger.Entity!.Name.Contains("map_end")) { this.EndZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } // Stage start zones - else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) + 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 && + 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.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.StageStartZone[stage] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StageStartZoneAngles[stage] = 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; @@ -108,19 +191,20 @@ internal Map(string Name, TimerDatabase DB) if (!foundPlayerSpawn) { - this.StageStartZone[stage - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StageStartZone[stage] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.Stages++; } } // 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) + 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 + this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.TotalCheckpoints++; // Might be useful to have this in DB entry } - + // Bonus start zones - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) + 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); @@ -128,11 +212,11 @@ internal Map(string Name, TimerDatabase DB) bool foundPlayerSpawn = false; // Track whether a player spawn is found foreach (CBaseEntity teleport in teleports) { - if (teleport.Entity!.Name != null && + 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.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.BonusStartZone[bonus] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.BonusStartZoneAngles[bonus] = 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; @@ -141,189 +225,564 @@ internal Map(string Name, TimerDatabase DB) if (!foundPlayerSpawn) { - this.BonusStartZone[bonus - 1] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusStartZone[bonus] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.Bonuses++; } } - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) + else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) { - this.BonusEndZone[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.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } } - if (this.Stages > 0) this.Stages++; // You did not count the stages right :( + + if (this.Stages > 0) // Account for stage 1, not counted above + this.Stages += 1; 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 - Task reader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); - MySqlDataReader mapData = reader.Result; - 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) + /// + /// Inserts a new map entry in the database. + /// Automatically detects whether to use API Calls or MySQL query. + /// + internal async Task Insert_Map_Info() + { + if (Config.API.GetApiOnly()) // API Calls { - this.ID = mapData.GetInt32("id"); - this.Author = mapData.GetString("author") ?? "Unknown"; - this.Tier = mapData.GetInt32("tier"); - 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(); - } + API_MapInfo inserted = new() + { + id = -1, // Shouldn't really use this at all at api side + name = Name, + author = "Unknown", + tier = this.Tier, + stages = this.Stages, + bonuses = this.Bonuses, + ranked = 0, + }; - else - { - mapData.Close(); - 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 shouldn't happen. Map: {Name}"); - writer.Dispose(); + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, inserted); - Task postWriteReader = DB.Query($"SELECT * FROM Maps WHERE name='{MySqlHelper.EscapeString(Name)}'"); - MySqlDataReader postWriteMapData = postWriteReader.Result; - if (postWriteMapData.HasRows && postWriteMapData.Read()) + // Check if the response is not null and get the last_id + if (postResponse != null) { - 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; + Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Insert_Map_Info -> New map '{Name}' inserted, got ID {postResponse.last_id}"); + this.ID = postResponse.last_id; } - postWriteMapData.Close(); return; } + else // MySQL Queries + { + int writerRows = await SurfTimer.DB.WriteAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(Name), "Unknown", this.Stages, this.Bonuses, 0, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds())); + if (writerRows != 1) + { + Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); + throw exception; + } - // Update the map's last played data in the DB - // 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} | was it 'big' update? {updateData}"); - updater.Dispose(); - - // 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(); + await Get_Map_Info(false); + } } - public void KickReplayBot(int index) + /// + /// Updates last played, stages, bonuses for the map in the database. + /// Automatically detects whether to use API Calls or MySQL query. + /// + internal async Task Update_Map_Info() { - if (!this.ReplayBots[index].IsPlayable) - return; + if (Config.API.GetApiOnly()) // API Calls + { + API_MapInfo updated = new() + { + id = this.ID, + name = Name, + author = "Unknown", + tier = this.Tier, + stages = this.Stages, + bonuses = this.Bonuses, + ranked = 0, + last_played = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; - int? id_to_kick = this.ReplayBots[index].Controller!.UserId; - if(id_to_kick == null) - return; + _ = ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, updated).Result; + } + else // MySQL Queries + { + // Update the map's last played data in the DB + string updateQuery = string.Format(Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, + (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.Stages, this.Bonuses, this.ID); + +#if DEBUG + Console.WriteLine($"CS2 Surf >> internal class Map -> internal async Task Update_Map_Info -> Update MapData: {updateQuery}"); +#endif - this.ReplayBots.RemoveAt(index); - Server.ExecuteCommand($"kickid {id_to_kick}; bot_quota {this.ReplayBots.Count}"); + int lastPlayedUpdateRows = await SurfTimer.DB.WriteAsync(updateQuery); + if (lastPlayedUpdateRows != 1) + { + Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}"); + throw exception; + } + } } - public bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) + /// + /// Load map info data using MySQL Queries and update the info as well or create a new entry. + /// Loads the record runs for the map as well. + /// Automatically detects whether to use API Calls or MySQL query. + /// + /// Should we run UPDATE query for the map + internal async Task Get_Map_Info(bool updateData = true) { - if (spawnOrigin.X >= zoneOrigin.X - zoneCollisionRadius && spawnOrigin.X <= zoneOrigin.X + zoneCollisionRadius && - spawnOrigin.Y >= zoneOrigin.Y - zoneCollisionRadius && spawnOrigin.Y <= zoneOrigin.Y + zoneCollisionRadius && - spawnOrigin.Z >= zoneOrigin.Z - zoneCollisionRadius && spawnOrigin.Z <= zoneOrigin.Z + zoneCollisionRadius) - return true; - else - return false; + bool newMap = false; + + if (Config.API.GetApiOnly()) // API Calls + { + // Gather map information OR create entry + var mapinfo = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, Name)); + if (mapinfo != null) + { + this.ID = mapinfo.id; + this.Author = mapinfo.author; + this.Tier = mapinfo.tier; + this.Ranked = mapinfo.ranked == 1; + this.DateAdded = (int)mapinfo.date_added!; + this.LastPlayed = (int)mapinfo.last_played!; + } + else + { + newMap = true; + } + } + else // MySQL queries + { + // Gather map information OR create entry + using (var mapData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(Name)))) + { + 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.Author = mapData.GetString("author") ?? "Unknown"; + this.Tier = mapData.GetInt32("tier"); + this.Ranked = mapData.GetBoolean("ranked"); + this.DateAdded = mapData.GetInt32("date_added"); + this.LastPlayed = mapData.GetInt32("last_played"); + } + else + { + newMap = true; + } + } + } + + // This is a new map + if (newMap) + { + await Insert_Map_Info(); + return; + } + + // this.ReplayManager = new ReplayManager(this.ID, this.Stages > 0, this.Bonuses > 0); + + // Will skip updating the data in the case where we have just inserted a new map with MySQL Queries and called this method again in order to get the Map ID + if (updateData) + await Update_Map_Info(); + + await Get_Map_Record_Runs(); } - // 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 + /// + /// Extracts Map, Bonus, Stage record runs and the total completions for each style. + /// (NOT TESTED WITH MORE THAN 1 STYLE) + /// For the Map WR it also gets the Checkpoints data. + /// Automatically detects whether to use API Calls or MySQL query. + /// TODO: Re-do the API with the new query and fix the API assign of values + /// + internal async Task Get_Map_Record_Runs() { - // Get map world records - 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; - - if (mapWrData.HasRows) - { - // To-do: Implement bonuses WR - // To-do: Implement stages WR - this.ConnectedMapTimes.Clear(); - while (mapWrData.Read()) + int totalMapRuns = 0; + int totalStageRuns = 0; + int totalBonusRuns = 0; + this.ConnectedMapTimes.Clear(); + + int qType; + int qStage; + int qStyle; + + // Replay Stuff + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + + if (Config.API.GetApiOnly()) // Need to update the query in API and re-do the assigning of data + { + // // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, style, type)); + // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, 0, 0)); + // if (maptimes == null) + // { + // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> No map runs found for {this.Name} (MapID {this.ID} | Style {qStyle} | Type {qStyle})"); + // this.MapCompletions[qStyle] = 0; + // return; + // } + + // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> Got {maptimes.Length} map runs for MapID {this.ID} (Style {qStyle} | Type {qStyle})"); + // // To-do: Implement bonuses WR + // // To-do: Implement stages WR + // foreach (var time in maptimes) + // { + // if (totalMapRuns == 0) // First row is always the fastest run for the map, style, type combo + // { + // this.WR[qStyle].ID = time.id; // WR ID for the Map and Style combo + // this.WR[qStyle].Ticks = time.run_time; // Fastest run time (WR) for the Map and Style combo + // this.WR[qStyle].StartVelX = time.start_vel_x; // Fastest run start velocity X for the Map and Style combo + // this.WR[qStyle].StartVelY = time.start_vel_y; // Fastest run start velocity Y for the Map and Style combo + // this.WR[qStyle].StartVelZ = time.start_vel_z; // Fastest run start velocity Z for the Map and Style combo + // this.WR[qStyle].EndVelX = time.end_vel_x; // Fastest run end velocity X for the Map and Style combo + // this.WR[qStyle].EndVelY = time.end_vel_y; // Fastest run end velocity Y for the Map and Style combo + // this.WR[qStyle].EndVelZ = time.end_vel_z; // Fastest run end velocity Z for the Map and Style combo + // this.WR[qStyle].RunDate = time.run_date; // Fastest run date for the Map and Style combo + // this.WR[qStyle].Name = time.name; // Fastest run player name for the Map and Style combo + // } + // this.ConnectedMapTimes.Add(time.id); + // totalMapRuns++; + // } + // // this.ConnectedMapTimes.Remove(this.WR[style].ID); // ?? + // // this.MapCompletions[style] = maptimes.Length; + } + else // MySQL Queries + { + // Get map world record + using (var mapWrData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, this.ID))) { - 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 - this.WR[style].Name = mapWrData.GetString("name"); // Fastest run player name for the Map and Style combo + if (mapWrData.HasRows) + { + while (mapWrData.Read()) + { + qType = mapWrData.GetInt32("type"); + qStage = mapWrData.GetInt32("stage"); + qStyle = mapWrData.GetInt32("style"); + + // Retrieve replay_frames as string from MySQL + string replayFramesBase64; + + // Option A: Try to get the string directly + try + { + replayFramesBase64 = mapWrData.GetString("replay_frames"); + } + catch (InvalidCastException) + { + // Option B: Get the data as byte[] and convert to string + byte[] replayFramesData = mapWrData.GetFieldValue("replay_frames"); + replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); + } + + // Populate parameters for all the MapTime rows found + switch (qType) + { + case 0: // Map WR data and total completions + this.WR[qStyle].ID = mapWrData.GetInt32("id"); + this.WR[qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.WR[qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.WR[qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.WR[qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.WR[qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.WR[qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.WR[qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.WR[qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.WR[qStyle].Name = mapWrData.GetString("name"); + totalMapRuns = mapWrData.GetInt32("total_count"); + this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); + this.MapCompletions[qStyle] = totalMapRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [MapWR] Sending style {qStyle} to `ReplayManager`: Map ID {this.ID} | Stages {this.Stages > 0} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); + + // Populate the ReplayManager for Map WR only if no replay exists or a new WR was set + if (this.ReplayManager.MapWR.MapID == -1 || this.WR[qStyle].Ticks < this.ReplayManager.MapWR.RecordRunTime) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } + break; + case 1: // Bonus WR data and total completions + this.BonusWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); + this.BonusWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.BonusWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.BonusWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.BonusWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.BonusWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.BonusWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.BonusWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.BonusWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.BonusWR[qStage][qStyle].Name = mapWrData.GetString("name"); + totalBonusRuns = mapWrData.GetInt32("total_count"); + this.BonusCompletions[qStage][qStyle] = totalBonusRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Got Bonus {qStage}"); + + // Populate the ReplayManager for all bonuses found and set the first bonus to replay + if (this.ReplayManager.BonusWR != null) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } + break; + case 2: // Stage WR data and total completions + this.StageWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); + this.StageWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); + this.StageWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); + this.StageWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); + this.StageWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); + this.StageWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); + this.StageWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); + this.StageWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); + this.StageWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); + this.StageWR[qStage][qStyle].Name = mapWrData.GetString("name"); + totalStageRuns = mapWrData.GetInt32("total_count"); + this.StageCompletions[qStage][qStyle] = totalStageRuns; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [StageWR] Sending style {qStyle} to `ReplayManager.StageWR`: Map ID {this.ID} | Stages {this.Stages > 0} - {qStage} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); + + // Populate the ReplayManager for all stages found and set the first stage to replay + if (this.ReplayManager.StageWR != null) + { + Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); + } + break; + } + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Map Completions for style {qStyle} {this.MapCompletions[qStyle]}"); + } } - this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); - totalRows++; } } - mapWrData.Close(); - this.TotalCompletions = totalRows; // Total completions for the map and style - this should maybe be added to PersonalBest class - // Get map world record checkpoints - if (totalRows != 0) + // Retrieve the checkpoints for each Style if it has been set. + foreach (int style in Config.Styles) { - Task cpReader = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {this.WR[style].ID};"); - MySqlDataReader cpWrData = cpReader.Result; - while (cpWrData.Read()) + // if (this.MapCompletions[style] > 0 && this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime) // This should also reload Checkpoints if a new MapWR is set + if ( + this.MapCompletions[style] > 0 && this.WR[style].ID != -1 || + this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime + ) // This should also reload Checkpoints if a new MapWR is set { - #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 - - 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 +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal async Task Get_Map_Record_Runs : Map -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Loaded {this.MapCompletions[style]} runs (MapID {this.ID} | Style {style}). WR by {this.WR[style].Name} - {PlayerHUD.FormatTime(this.WR[style].Ticks)}"); +#endif + await Get_Record_Run_Checkpoints(style); } - cpWrData.Close(); } } + + /// + /// Redirects to `PersonalBest.PB_LoadCheckpointsData()`. + /// Extracts all entries from Checkpoints table of the World Record for the given `style` + /// + /// Style to load + internal async Task Get_Record_Run_Checkpoints(int style = 0) + { + await this.WR[style].PB_LoadCheckpointsData(); + } + + /// + /// Sets the data for a replay that has been retrieved from MapTimes data. + /// Also sets the first Stage replay if no replays existed for stages until now. + /// + /// Type - 0 = Map, 1 = Bonus, 2 = Stage + /// Style to add + /// Stage to add + /// Base64 encoded string for the replay_frames + internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64) + { + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + + // Decompress the Base64 string + string json = Compressor.Decompress(replayFramesBase64); + + // Deserialize to List + List frames = JsonSerializer.Deserialize>(json, options)!; + + switch (type) + { + case 0: // Map Replays + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); + if (this.ReplayManager.MapWR.IsPlaying) + this.ReplayManager.MapWR.Stop(); + + this.ReplayManager.MapWR.RecordPlayerName = this.WR[style].Name; + this.ReplayManager.MapWR.RecordRunTime = this.WR[style].Ticks; + this.ReplayManager.MapWR.Frames = frames; + this.ReplayManager.MapWR.MapTimeID = this.WR[style].ID; + this.ReplayManager.MapWR.MapID = this.ID; + this.ReplayManager.MapWR.Type = 0; + for (int i = 0; i < frames.Count; i++) // Load the situations for the replay + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + this.ReplayManager.MapWR.MapSituations.Add(i); + Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + this.ReplayManager.MapWR.MapSituations.Add(i); + Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + this.ReplayManager.MapWR.StageEnterSituations.Add(i); + Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + this.ReplayManager.MapWR.StageExitSituations.Add(i); + Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + this.ReplayManager.MapWR.CheckpointEnterSituations.Add(i); + Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + this.ReplayManager.MapWR.CheckpointExitSituations.Add(i); + Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_ENTER: + Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); + break; + } + } + break; + case 1: // Bonus Replays + // Skip if the same bonus run already exists + if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].Ticks) + break; + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Adding run {this.BonusWR[stage][style].ID} {PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks)} (Ticks = {this.BonusWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllBonusWR`"); + // Add all stages found to a dictionary with their data + this.ReplayManager.AllBonusWR[stage][style].MapID = this.ID; + this.ReplayManager.AllBonusWR[stage][style].Frames = frames; + this.ReplayManager.AllBonusWR[stage][style].RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.AllBonusWR[stage][style].RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.AllBonusWR[stage][style].MapTimeID = this.BonusWR[stage][style].ID; + this.ReplayManager.AllBonusWR[stage][style].Stage = stage; + this.ReplayManager.AllBonusWR[stage][style].Type = 1; + this.ReplayManager.AllBonusWR[stage][style].RecordRank = 1; + this.ReplayManager.AllBonusWR[stage][style].IsPlayable = true; // We set this to `true` else we overwrite it and need to call SetController method again + for (int i = 0; i < frames.Count; i++) + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); + break; + } + } + // Set the bonus to replay first + if (this.ReplayManager.BonusWR != null && this.ReplayManager.BonusWR.MapID == -1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Setting first `ReplayManager.BonusWR` to bonus {stage}"); + if (this.ReplayManager.BonusWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current bonus being played? + this.ReplayManager.BonusWR.Stop(); + this.ReplayManager.BonusWR.MapID = this.ID; + this.ReplayManager.BonusWR.Frames = frames; + this.ReplayManager.BonusWR.RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.BonusWR.RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.BonusWR.MapTimeID = this.BonusWR[stage][style].ID; + this.ReplayManager.BonusWR.Stage = stage; + this.ReplayManager.BonusWR.Type = 1; + this.ReplayManager.BonusWR.RecordRank = 1; + } + break; + case 2: // Stage Replays + // Skip if the same stage run already exists + if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) + break; + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Adding run {this.StageWR[stage][style].ID} {PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks)} (Ticks = {this.StageWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllStageWR`"); + // Add all stages found to a dictionary with their data + this.ReplayManager.AllStageWR[stage][style].MapID = this.ID; + this.ReplayManager.AllStageWR[stage][style].Frames = frames; + this.ReplayManager.AllStageWR[stage][style].RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.AllStageWR[stage][style].RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.AllStageWR[stage][style].MapTimeID = this.StageWR[stage][style].ID; + this.ReplayManager.AllStageWR[stage][style].Stage = stage; + this.ReplayManager.AllStageWR[stage][style].Type = 2; + this.ReplayManager.AllStageWR[stage][style].RecordRank = 1; + this.ReplayManager.AllStageWR[stage][style].IsPlayable = true; // We set this to `true` else we overwrite it and need to call SetController method again + for (int i = 0; i < frames.Count; i++) + { + ReplayFrame f = frames[i]; + switch (f.Situation) + { + case ReplayFrameSituation.STAGE_ZONE_ENTER: + this.ReplayManager.AllStageWR[stage][style].StageEnterSituations.Add(i); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + this.ReplayManager.AllStageWR[stage][style].StageExitSituations.Add(i); + break; + } + } + // Set the stage to replay first + if (this.ReplayManager.StageWR != null && this.ReplayManager.StageWR.MapID == -1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Setting first `ReplayManager.StageWR` to stage {stage}"); + if (this.ReplayManager.StageWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current stage being played? + this.ReplayManager.StageWR.Stop(); + this.ReplayManager.StageWR.MapID = this.ID; + this.ReplayManager.StageWR.Frames = frames; + this.ReplayManager.StageWR.RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.StageWR.RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.StageWR.MapTimeID = this.StageWR[stage][style].ID; + this.ReplayManager.StageWR.Stage = stage; + this.ReplayManager.StageWR.Type = 2; + this.ReplayManager.StageWR.RecordRank = 1; + } + break; + } + + // Start the new map replay if none existed until now + if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); + this.ReplayManager.MapWR.ResetReplay(); + this.ReplayManager.MapWR.Start(); + } + else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.BonusWR.ResetReplay(); + this.ReplayManager.BonusWR.Start(); + } + else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.StageWR.ResetReplay(); + this.ReplayManager.StageWR.Start(); + } + } + + public void KickReplayBot(int index) + { + if (!this.ReplayManager.CustomReplays[index].IsPlayable) + return; + + int? id_to_kick = this.ReplayManager.CustomReplays[index].Controller!.UserId; + if (id_to_kick == null) + return; + + this.ReplayManager.CustomReplays.RemoveAt(index); + Server.ExecuteCommand($"kickid {id_to_kick}; bot_quota {this.ReplayManager.CustomReplays.Count}"); + } + + public static bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector spawnOrigin) + { + if (spawnOrigin.X >= zoneOrigin.X - zoneCollisionRadius && spawnOrigin.X <= zoneOrigin.X + zoneCollisionRadius && + spawnOrigin.Y >= zoneOrigin.Y - zoneCollisionRadius && spawnOrigin.Y <= zoneOrigin.Y + zoneCollisionRadius && + spawnOrigin.Z >= zoneOrigin.Z - zoneCollisionRadius && spawnOrigin.Z <= zoneOrigin.Z + zoneCollisionRadius) + return true; + else + return false; + } } \ No newline at end of file diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 2bcfb83..cbbaed5 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -40,7 +40,7 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen } /// - /// Checks if current player is spcetating player

+ /// Checks if current player is spectating player

///

public bool IsSpectating(CCSPlayerController p) { diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 39ab08e..41a3454 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; @@ -44,10 +44,12 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P { case PlayerTimer.TimeFormatStyle.Compact: return time.TotalMinutes < 1 - ? $"{time.Seconds:D1}.{millis:D3}" - : $"{time.Minutes:D1}:{time.Seconds:D1}.{millis:D3}"; + ? $"{time.Seconds:D2}.{millis:D3}" + : $"{time.Minutes:D1}:{time.Seconds:D2}.{millis:D3}"; case PlayerTimer.TimeFormatStyle.Full: - return $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}"; + return time.TotalHours < 1 + ? $"{time.Minutes:D2}:{time.Seconds:D2}.{millis:D3}" + : $"{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: @@ -57,7 +59,7 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P public void Display() { - if(!_player.Controller.IsValid) + if (!_player.Controller.IsValid) return; if (_player.Controller.PawnIsAlive) @@ -73,7 +75,14 @@ public void Display() else timerColor = "#2E9F65"; } - string timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); + + string timerModule; + if (_player.Timer.IsBonusMode) + timerModule = FormatHUDElementHTML("", $"[B{_player.Timer.Bonus}] " + FormatTime(_player.Timer.Ticks), timerColor); + else if (_player.Timer.IsStageMode) + timerModule = FormatHUDElementHTML("", $"[S{_player.Timer.Stage}] " + FormatTime(_player.Timer.Ticks), timerColor); + else + 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 @@ -82,17 +91,42 @@ 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[style].ID != -1 && _player.CurrMap.WR[style].ID != -1) + if (_player.Timer.IsBonusMode) + { + if (_player.Stats.BonusPB[_player.Timer.Bonus][style].ID != -1 && SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.BonusPB[_player.Timer.Bonus][style].Rank}/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); + } + else if (_player.Timer.IsStageMode) { - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{_player.CurrMap.TotalCompletions}", "#7882dd"); + if (_player.Stats.StagePB[_player.Timer.Stage][style].ID != -1 && SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.StagePB[_player.Timer.Stage][style].Rank}/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); } - else if (_player.CurrMap.WR[style].ID != -1) + else { - rankModule = FormatHUDElementHTML("Rank", $"-/{_player.CurrMap.TotalCompletions}", "#7882dd"); + if (_player.Stats.PB[style].ID != -1 && SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{SurfTimer.CurrentMap.MapCompletions[style]}", "#7882dd"); + else if (SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.MapCompletions[style]}", "#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 - 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? + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].Ticks > 0 ? FormatTime(_player.Stats.PB[style].Ticks) : "N/A", "#7882dd"); + string wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.WR[style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.WR[style].Ticks) : "N/A", "#ffc61a"); + + if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values + { + pbModule = FormatHUDElementHTML("PB", _player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks) : "N/A", "#ffc61a"); + } + else if (_player.Timer.IsStageMode) // Show corresponding stage values + { + pbModule = FormatHUDElementHTML("PB", _player.Stats.StagePB[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].Ticks) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks) : "N/A", "#ffc61a"); + } // Build HUD string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; @@ -102,17 +136,22 @@ public void Display() } else if (_player.Controller.Team == CsTeam.Spectator) { - for (int i = 0; i < _player.CurrMap.ReplayBots.Count; i++) - { - if(!_player.CurrMap.ReplayBots[i].IsPlayable || !_player.IsSpectating(_player.CurrMap.ReplayBots[i].Controller!)) - continue; - - string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; + ReplayPlayer? spec_replay; - string nameModule = FormatHUDElementHTML($"{_player.CurrMap.ReplayBots[i].Stat_PlayerName}", $"{FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTime)}", "#ffd500"); + if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.MapWR.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.MapWR; + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.StageWR?.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.StageWR!; + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.BonusWR?.Controller!)) + spec_replay = SurfTimer.CurrentMap.ReplayManager.BonusWR!; + else + spec_replay = SurfTimer.CurrentMap.ReplayManager.CustomReplays.Find(x => _player.IsSpectating(x.Controller!)); - string elapsed_time = FormatHUDElementHTML("Time", $"{PlayerHUD.FormatTime(_player.CurrMap.ReplayBots[i].Stat_RunTick)}", "#7882dd"); - string hud = $"{replayModule}
{elapsed_time}
{nameModule}"; + if (spec_replay != null) + { + string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; + string nameModule = FormatHUDElementHTML($"{spec_replay.RecordPlayerName}", $"{FormatTime(spec_replay.RecordRunTime)}", "#ffd500"); + string hud = $"{replayModule}
{nameModule}"; _player.Controller.PrintToCenterHtml(hud); } @@ -122,14 +161,16 @@ public void Display() /// /// 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 + public void DisplayCheckpointMessages() { int pbTime; int wrTime = -1; float pbSpeed; float wrSpeed = -1.0f; int style = _player.Timer.Style; + int playerCheckpoint = _player.Timer.Checkpoint; + + // _player.Controller.PrintToChat($"{ChatColors.Blue}-> PlayerHUD{ChatColors.Default} => Style {ChatColors.Yellow}{style}{ChatColors.Default} | Checkpoint {playerCheckpoint} | WR Time Ticks {SurfTimer.CurrentMap.WR[style].Ticks} | Player Stage {_player.Timer.Stage} (CP {_player.Timer.Checkpoint}) | Player Ticks {_player.Timer.Ticks}"); int currentTime = _player.Timer.Ticks; float currentSpeed = (float)Math.Sqrt(_player.Controller.PlayerPawn.Value!.AbsVelocity.X * _player.Controller.PlayerPawn.Value!.AbsVelocity.X @@ -140,43 +181,43 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi 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 + // We need to try/catch this because the player might not have a PB for this checkpoint 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[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[{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 + pbTime = _player.Stats.PB[style].Checkpoints[playerCheckpoint].Ticks; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX + + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY + + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ); + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint} = {pbTime}]"); + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint}] = {pbSpeed}"); +#endif } - #if DEBUG +#if DEBUG catch (System.Exception ex) - #else +#else catch (System.Exception) - #endif +#endif { - // Handle the exception gracefully without stopping the application + // Handle the exception gracefully without stopping // We assign default values to pbTime and pbSpeed pbTime = -1; // This determines if we will calculate differences or not!!! pbSpeed = 0.0f; - - #if DEBUG + +#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[{style}].Checkpoint.Count = {_player.Stats.PB[style].Checkpoint.Count}"); - #endif + 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].Checkpoints.Count}"); +#endif } // Calculate differences in PB (PB - Current) if (pbTime != -1) { - #if DEBUG +#if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting PB difference calculation... (pbTime != -1)"); - #endif +#endif // Reset the string strPbDifference = ""; @@ -203,17 +244,17 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi strPbDifference += ChatColors.Default + ")"; } - if (_player.CurrMap.WR[style].Ticks > 0) + if (SurfTimer.CurrentMap.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[{style}].Ticks > 0)"); - #endif - - 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); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)"); +#endif + + wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].Ticks; + wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX + + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY + + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ); // Reset the string strWrDifference = ""; @@ -242,17 +283,17 @@ public void DisplayCheckpointMessages(string PluginPrefix) // To-do: PluginPrefi // Print checkpoint message _player.Controller.PrintToChat( - $"{PluginPrefix} CP [{ChatColors.Yellow}{_player.Timer.Checkpoint}{ChatColors.Default}]: " + + $"{Config.PluginPrefix} CP [{ChatColors.Yellow}{playerCheckpoint}{ChatColors.Default}]: " + $"{ChatColors.Yellow}{FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + $"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " + $"[PB: {strPbDifference} | " + $"WR: {strWrDifference}]"); - #if DEBUG +#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 +#endif } } diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 3904e35..6f5930a 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -1,23 +1,120 @@ +using CounterStrikeSharp.API.Core; +using Microsoft.VisualBasic; + namespace SurfTimer; internal class PlayerProfile { - public int ID {get; set;} = 0; - public string Name {get; set;} = ""; - public ulong SteamID {get; set;} = 0; - public string Country {get; set;} = ""; - public int JoinDate {get; set;} = 0; - public int LastSeen {get; set;} = 0; - public int Connections {get; set;} = 0; - - public PlayerProfile(int ID, string Name, ulong SteamID, string Country, int JoinDate, int LastSeen, int Connections) + public int ID { get; set; } = 0; + public string Name { get; set; } = ""; + public ulong SteamID { get; set; } = 0; + public string Country { get; set; } = ""; + public int JoinDate { get; set; } = 0; + public int LastSeen { get; set; } = 0; + public int Connections { get; set; } = 0; + + public PlayerProfile(ulong steamId, string name = "", string country = "") + { + this.SteamID = steamId; + this.Name = name; + this.Country = country; + } + + /// + /// Deals with retrieving, creating and updating a Player's information in the database upon joining the server. + /// Automatically detects whether to use API Calls or Queries. + /// + /// Steam ID of the player + /// Name of the player + /// Country of the player + /// PlayerProfile object + public static async Task CreateAsync(ulong steamId, string name = "", string country = "") + { + var profile = new PlayerProfile(steamId, name, country); + await profile.InitializeAsync(); + return profile; + } + + private async Task InitializeAsync() + { + await Get_Player_Profile(); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] We got ProfileID = {this.ID} ({this.Name})"); + } + + /// + /// Retrieves all the data for the player from the database. + /// + public async Task Get_Player_Profile() + { + bool newPlayer = false; + + // Load player profile data from database + using (var playerData = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, this.SteamID))) + { + if (playerData.HasRows && playerData.Read()) + { + // Player exists in database + this.ID = playerData.GetInt32("id"); + this.Name = playerData.GetString("name"); + if (this.Country == "XX" && playerData.GetString("country") != "XX") + this.Country = playerData.GetString("country"); + this.JoinDate = playerData.GetInt32("join_date"); + this.LastSeen = playerData.GetInt32("last_seen"); + this.Connections = playerData.GetInt32("connections"); + } + else + { + newPlayer = true; + } + } + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Returning player {this.Name} ({this.SteamID}) loaded from database with ID {this.ID}"); +#endif + if (newPlayer) + await Insert_Player_Profile(); + } + + /// + /// Insert new player information into the database. + /// Retrieves the ID of the newly created player. + /// + public async Task Insert_Player_Profile() + { + // Player does not exist in database + int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + int lastSeen = joinDate; + int connections = 1; + + // Write new player to database + int newPlayerRows = await SurfTimer.DB.WriteAsync(string.Format( + Config.MySQL.Queries.DB_QUERY_PP_INSERT_PROFILE, + MySqlConnector.MySqlHelper.EscapeString(this.Name), this.SteamID, this.Country, joinDate, lastSeen, connections)); + if (newPlayerRows != 1) + { + Exception ex = new($"Error inserting new player profile for '{this.Name}' ({this.SteamID})"); + throw ex; + } + + await Get_Player_Profile(); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Insert_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] New player {this.Name} ({this.SteamID}) added to database with ID {this.ID}"); +#endif + } + + /// + /// Updates the information in the database for the player. Increments `connections` and changes nickname. + /// + /// Player Name + /// + public async Task Update_Player_Profile(string name) { - this.ID = ID; - this.Name = Name; - this.SteamID = SteamID; - this.Country = Country; - this.JoinDate = JoinDate; - this.LastSeen = LastSeen; - this.Connections = Connections; + int updatePlayerTask = await SurfTimer.DB.WriteAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, this.Country, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.ID, name)); + if (updatePlayerTask != 1) + throw new Exception($"CS2 Surf ERROR >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Failed to update player data in database. Player: {this.Name} ({this.SteamID})"); +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Updated player {name} ({this.SteamID}) in database. ID {this.ID}"); +#endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index bf51165..85b6254 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -3,10 +3,10 @@ namespace SurfTimer; internal class Checkpoint : PersonalBest { public int CP { get; set; } - public float EndTouch { get; set; } + public int 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) + public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int 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???? diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index f6fd2d0..32150a0 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -5,7 +5,7 @@ namespace SurfTimer; ///
internal class CurrentRun { - public Dictionary Checkpoint { get; set; } // Current RUN checkpoints tracker + public Dictionary Checkpoints { 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 @@ -19,7 +19,7 @@ internal class CurrentRun // Constructor public CurrentRun() { - Checkpoint = new Dictionary(); + Checkpoints = new Dictionary(); Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; @@ -32,7 +32,7 @@ public CurrentRun() public void Reset() { - Checkpoint.Clear(); + Checkpoints.Clear(); Ticks = 0; StartVelX = 0.0f; StartVelY = 0.0f; @@ -45,46 +45,152 @@ 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 + /// Saves the player's run to the database. /// - public void SaveMapTime(Player player, TimerDatabase DB) + /// Player object + /// Bonus number + /// Stage number + /// Ticks for the run - used for Stage and Bonus entries + public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1) { // Add entry in DB for the run - // To-do: add `type` + // PrintSituations(player); + string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); + + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Sending total of {replay_frames.Length} replay frames"); + if (Config.API.GetApiOnly()) + { + return; + } + else + { + await InsertMapTime(player, bonus, stage, run_ticks, replay_frames, true); + + if (stage != 0 || bonus != 0) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Inserted an entry for {(stage != 0 ? "Stage" : "Bonus")} {(stage != 0 ? stage : bonus)} - {run_ticks}"); + } + else + { + await SaveCurrentRunCheckpoints(player, true); // Save this run's checkpoints + } + + await player.CurrMap.Get_Map_Record_Runs(); // Reload the times for the Map + } + } + + public void PrintSituations(Player player) + { + Console.WriteLine($"========================== FOUND SITUATIONS =========================="); + for (int i = 0; i < player.ReplayRecorder.Frames.Count; i++) + { + ReplayFrame x = player.ReplayRecorder.Frames[i]; + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_ENTER: + Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + } + } + Console.WriteLine($"========================== =========================="); + } + + /// + /// Saves the CurrentRun of the player to the database. Does NOT support Bonus entries yet. + /// + /// Player object + /// Bonus number + /// Stage number + /// Ticks for the run + /// Replay frames + /// Whether to reload the PersonalBest data for the Player. + /// + public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false) + { + int playerId = player.Profile.ID; + int mapId = player.CurrMap.ID; 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`, `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()}, '{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), 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})"); - updatePlayerRunTask.Dispose(); + int ticks = run_ticks == -1 ? player.Stats.ThisRun.Ticks : run_ticks; + // int ticks = player.Stats.ThisRun.Ticks; + int type = stage != 0 ? 2 : bonus != 0 ? 1 : 0; + float startVelX = player.Stats.ThisRun.StartVelX; + float startVelY = player.Stats.ThisRun.StartVelY; + float startVelZ = player.Stats.ThisRun.StartVelZ; + float endVelX = player.Stats.ThisRun.EndVelX; + float endVelY = player.Stats.ThisRun.EndVelY; + float endVelZ = player.Stats.ThisRun.EndVelZ; + + if (Config.API.GetApiOnly()) // API Calls + { + // API Insert map goes here + } + else // MySQL Queries + { + int updatePlayerRunTask = await SurfTimer.DB.WriteAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, playerId, mapId, style, type, type == 2 ? stage : type == 1 ? bonus : 0, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), replay_frames)); + if (updatePlayerRunTask <= 0) + { + Exception ex = new($"CS2 Surf ERROR >> internal class CurrentRun -> public async Task InsertMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); + throw ex; + } - // 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 + if (reloadData && type == 0) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.PB[player.Timer.Style].ID}))"); + await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + } + else if (reloadData && type == 1) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.BonusPB[bonus][style].ID}))"); + await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) + } + else if (reloadData && type == 2) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.StagePB[stage][style].ID}))"); + await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) + } + } } /// /// 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 async Task SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) + /// Player object + /// Whether to reload the PersonalBest Checkpoints data for the Player. + public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false) { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> Will send {player.Stats.ThisRun.Checkpoints.Count} ({this.Checkpoints.Count}) checkpoints to DB...."); int style = player.Timer.Style; + int mapTimeId = player.Stats.PB[style].ID; List commands = new List(); // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in player.Stats.ThisRun.Checkpoint) + // foreach (var item in player.Stats.ThisRun.Checkpoints) + foreach (var item in this.Checkpoints) { int cp = item.Key; int ticks = item.Value!.Ticks; - int runTime = item.Value!.Ticks / 64; // Runtime in decimal + int endTouch = item.Value!.EndTouch; double startVelX = item.Value!.StartVelX; double startVelY = item.Value!.StartVelY; double startVelZ = item.Value!.StartVelZ; @@ -93,35 +199,33 @@ public async Task SaveCurrentRunCheckpoints(Player player, TimerDatabase DB) double endVelZ = item.Value!.EndVelZ; int attempts = item.Value!.Attempts; - #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); +#if DEBUG + Console.WriteLine($"CP: {cp} | MapTime ID: {mapTimeId} | Time: {endTouch} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); + Console.WriteLine($@"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> + {string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch)} "); - #endif +#endif // Insert/Update CPs to database // 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) { - string command = $@" - 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); - "; + string command = string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch + ); commands.Add(command); } } - await DB.Transaction(commands); - player.Stats.ThisRun.Checkpoint.Clear(); + await SurfTimer.DB.TransactionAsync(commands); + player.Stats.ThisRun.Checkpoints.Clear(); + + if (reloadData) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveCurrentRunCheckpoints -> Will reload Checkpoints data for {player.Profile.Name} (ID {player.Stats.PB[player.Timer.Style].ID})"); + await player.Stats.PB[player.Timer.Style].PB_LoadCheckpointsData(); // Load the Checkpoints data again + } } } diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 2ee50bc..a689a00 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -1,14 +1,16 @@ namespace SurfTimer; -// To-do: make Style (currently 0) be dynamic -// To-do: add `Type` +/// +/// As the PersonalBest object is being used for each different style, we shouldn't need a separate `Style` variable in here because each style entry will have unique ID in the Database +/// and will therefore be a unique PersonalBest entry. +/// 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 Dictionary Checkpoints { get; set; } + public int Type { get; set; } // Identifies bonus # - 0 for map time -> huh, why o_O? public float StartVelX { get; set; } public float StartVelY { get; set; } public float StartVelZ { get; set; } @@ -22,15 +24,199 @@ internal class PersonalBest // 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; + Ticks = -1; + Checkpoints = new Dictionary(); + Type = -1; StartVelX = -1.0f; StartVelY = -1.0f; StartVelZ = -1.0f; EndVelX = -1.0f; EndVelY = -1.0f; EndVelZ = -1.0f; - RunDate = 0; + RunDate = -1; + } + + /// + /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. + /// Automatically detects whether to use API Calls or MySQL query. + /// Bonus and Stage runs should NOT have any checkpoints. + /// + public async Task PB_LoadCheckpointsData() + { + if (this == null) + { +#if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PersonalBest object is null."); +#endif + return; + } + if (this.Checkpoints == null) + { +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PB Checkpoints list is not initialized."); +#endif + this.Checkpoints = new Dictionary(); // Initialize if null + } + + if (Config.API.GetApiOnly()) // Load with API + { + var checkpoints = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, this.ID)); + if (checkpoints == null || checkpoints.Length == 0) + return; + + foreach (API_Checkpoint checkpoint in checkpoints) + { + Checkpoint cp = new Checkpoint + ( + checkpoint.cp, + checkpoint.run_time, + checkpoint.start_vel_x, + checkpoint.start_vel_y, + checkpoint.start_vel_z, + checkpoint.end_vel_x, + checkpoint.end_vel_y, + checkpoint.end_vel_z, + checkpoint.end_touch, + checkpoint.attempts + ); + cp.ID = checkpoint.cp; + + this.Checkpoints[cp.CP] = cp; + } + } + else // Load with MySQL + { + using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, this.ID))) + { +#if DEBUG + Console.WriteLine($"this.Checkpoint.Count {this.Checkpoints.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 PersonalBest -> public async Task PB_LoadCheckpointsData -> No checkpoints found for this mapTimeId {this.ID}."); +#endif + + return; + } + +#if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Checkpoints found for this mapTimeId"); +#endif + + while (results.Read()) + { +#if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetInt32("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"), + 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("end_touch"), + results.GetInt32("attempts")); + cp.ID = results.GetInt32("cp"); + // To-do: cp.ID = calculate Rank # from DB + + this.Checkpoints[cp.CP] = cp; + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); +#endif + } + } + } + + // #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] {this.Checkpoints.Count} Checkpoints loaded from DB for run ID {this.ID}."); + // #endif + } + + /// + /// Loads specific type/style MapTime data for the player (run without checkpoints) from the database for their personal best runs. + /// Should be used to reload data from a specific `PersonalBest` object + /// + /// Player object + public async Task PB_LoadPlayerSpecificMapTimeData(Player player) + { + // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); + // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) + if (this == null) + { +#if DEBUG + Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null."); +#endif + + return; + } + + MySqlConnector.MySqlDataReader? results = null; + + // Console.WriteLine(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + + if (this.ID == -1) + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + else + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, this.ID)); + +#if DEBUG + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.ID {this.ID} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.Ticks {this.Ticks} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.RunDate {this.RunDate} "); +#endif + + if (results == null || !results.HasRows) + { + // #if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{player.Profile.Name}' ({player.Profile.ID}). (Null? {results == null})"); + // #endif + + return; + } + +#if DEBUG + Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Found a run for '{player.Profile.Name}' ({player.Profile.ID})."); +#endif + + while (results.Read()) + { +#if DEBUG + Console.WriteLine($"cp {results.GetInt32("cp")} "); + Console.WriteLine($"run_time {results.GetInt32("run_time")} "); + Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); + Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); +#endif + + this.ID = results.GetInt32("id"); + this.Ticks = results.GetInt32("run_time"); + this.Rank = results.GetInt32("rank"); + this.StartVelX = (float)results.GetDouble("start_vel_x"); + this.StartVelY = (float)results.GetDouble("start_vel_y"); + this.StartVelZ = (float)results.GetDouble("start_vel_z"); + this.EndVelX = (float)results.GetDouble("end_vel_x"); + this.EndVelY = (float)results.GetDouble("end_vel_y"); + this.EndVelZ = (float)results.GetDouble("end_vel_z"); + this.RunDate = results.GetInt32("run_date"); + +#if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Loaded run (MapId = {this.ID}) by {player.Profile.Name} - {PlayerHUD.FormatTime(this.Ticks)}"); +#endif + } + + // #if DEBUG + Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> MapTime (Type: {this.Type}) loaded from DB. ID: {this.ID} for {player.Profile.Name}"); + // #endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 48eed71..c0586c1 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -7,144 +7,238 @@ 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 - } + // /// + // /// Stage Personal Best - Refer to as StagePB[style][stage#] + // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE + // /// + // public int[,] StagePB { get; set; } = { { 0, 0 } }; + // /// + // /// Stage Personal Best - Refer to as StageRank[style][stage#] + // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE + // /// + // public int[,] StageRank { get; set; } = { { 0, 0 } }; /// - /// 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 + /// Map Personal Best - Refer to as PB[style] + /// + public Dictionary PB { get; set; } = new Dictionary(); + /// + /// Bonus Personal Best - Refer to as BonusPB[bonus#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of bonuses + /// + public Dictionary[] BonusPB { get; set; } = new Dictionary[32]; + /// + /// Stage Personal Best - Refer to as StagePB[stage#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of stages + /// + public Dictionary[] StagePB { get; set; } = new Dictionary[32]; + /// + /// This object tracks data for the Player's current run. /// - public void LoadMapTimesData(Player player, TimerDatabase DB, int playerId = 0, int mapId = 0) + public CurrentRun ThisRun { get; set; } = new CurrentRun(); + + // Initialize PersonalBest for each `style` (e.g., 0 for normal) + // Here we can loop through all available styles at some point and initialize them + public PlayerStats() { - 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) + // Initialize MapPB for each style + foreach (int style in Config.Styles) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTimesData -> No MapTimes data found for Player."); + PB[style] = new PersonalBest(); + PB[style].Type = 0; } - else + + int initialized = 0; + for (int i = 0; i < 32; i++) { - 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 - } + this.BonusPB[i] = new Dictionary(); + this.BonusPB[i][0] = new PersonalBest(); + this.BonusPB[i][0].Type = 1; + + this.StagePB[i] = new Dictionary(); + this.StagePB[i][0] = new PersonalBest(); + this.StagePB[i][0].Type = 2; + initialized++; } - playerStats.Close(); + Console.WriteLine($"====== INITIALIZED {initialized} STAGES AND BONUSES FOR PLAYERSTATS"); } - /// - /// Executes the DB query to get all the checkpoints and store them in the Checkpoint dictionary - /// - public void LoadCheckpointsData(TimerDatabase DB) + // API + public async void LoadMapTime(Player player, int style = 0) { - Task dbTask = DB.Query($"SELECT * FROM `Checkpoints` WHERE `maptime_id` = {PB[0].ID};"); - MySqlDataReader results = dbTask.Result; - if (PB[0] == null) + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=0"); + if (player_maptime == null) { - #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class PlayerStats -> LoadCheckpointsData -> PersonalBest object is null."); - #endif - - results.Close(); + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No MapTime data found for Player."); return; } - if (PB[0].Checkpoint == null) + PB[style].ID = player_maptime.id; + PB[style].Ticks = player_maptime.run_time; + PB[style].Type = player_maptime.type; + PB[style].StartVelX = player_maptime.start_vel_x; + PB[style].StartVelY = player_maptime.start_vel_y; + PB[style].StartVelZ = player_maptime.start_vel_z; + PB[style].EndVelX = player_maptime.end_vel_x; + PB[style].EndVelY = player_maptime.end_vel_y; + PB[style].EndVelZ = player_maptime.end_vel_z; + // PB[style].RunDate = player_maptime.run_date ?? 0; + PB[style].RunDate = player_maptime.run_date; + + if (player_maptime.checkpoints == null) { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadCheckpointsData -> PB Checkpoints list is not initialized."); - #endif + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No Checkpoints data found for Player."); + return; + } - PB[0].Checkpoint = new Dictionary(); // Initialize if null + foreach (var cp in player_maptime.checkpoints) + { + PB[style].Checkpoints[cp.cp] = new Checkpoint(cp.cp, cp.run_time, cp.start_vel_x, cp.start_vel_y, cp.start_vel_z, cp.end_vel_x, cp.end_vel_y, cp.end_vel_z, cp.end_touch, cp.attempts); } + } - #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 + // API + public async void LoadStageTime(Player player, int style = 0) + { + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=2"); + if (player_maptime == null) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadStageTime -> No MapTime data found for Player."); + return; + } - if (!results.HasRows) + foreach (API_MapTime mt in player_maptime) { - #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> No checkpoints found for this mapTimeId {PB[0].ID}."); - #endif + StagePB[mt.stage][style].ID = mt.id; + StagePB[mt.stage][style].Ticks = mt.run_time; + StagePB[mt.stage][style].Type = mt.type; + StagePB[mt.stage][style].StartVelX = mt.start_vel_x; + StagePB[mt.stage][style].StartVelY = mt.start_vel_y; + StagePB[mt.stage][style].StartVelZ = mt.start_vel_z; + StagePB[mt.stage][style].EndVelX = mt.end_vel_x; + StagePB[mt.stage][style].EndVelY = mt.end_vel_y; + StagePB[mt.stage][style].EndVelZ = mt.end_vel_z; + // StagePB[mt.stage][style].RunDate = mt.run_date ?? 0; + StagePB[mt.stage][style].RunDate = mt.run_date; + } + } - results.Close(); + // API + public async void LoadBonusTime(Player player, int style = 0) + { + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=1"); + if (player_maptime == null) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadBonusTime -> No MapTime data found for Player."); return; } - #if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Checkpoints found for this mapTimeId"); - #endif - - while (results.Read()) + foreach (API_MapTime mt in player_maptime) { - #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 + BonusPB[mt.stage][style].ID = mt.id; + BonusPB[mt.stage][style].Ticks = mt.run_time; + BonusPB[mt.stage][style].Type = mt.type; + BonusPB[mt.stage][style].StartVelX = mt.start_vel_x; + BonusPB[mt.stage][style].StartVelY = mt.start_vel_y; + BonusPB[mt.stage][style].StartVelZ = mt.start_vel_z; + BonusPB[mt.stage][style].EndVelX = mt.end_vel_x; + BonusPB[mt.stage][style].EndVelY = mt.end_vel_y; + BonusPB[mt.stage][style].EndVelZ = mt.end_vel_z; + // BonusPB[mt.stage][style].RunDate = mt.run_date ?? 0; + BonusPB[mt.stage][style].RunDate = mt.run_date; } - results.Close(); + } - #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class Checkpoint : PersonalBest -> LoadCheckpointsData -> Checkpoints loaded from DB. Count: {PB[0].Checkpoint.Count}"); - #endif + + /// + /// Loads the player's map time data from the database along with their ranks. For all types and styles (may not work correctly for Stages/Bonuses) + /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. + /// This populates all the `style` and `type` stats the player has for the map + /// + public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0) + { + using (var playerStats = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) + { + // int style = player.Timer.Style; + int style; + if (!playerStats.HasRows) + { + Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> No MapTimes data found for Player ({player.Profile.ID})."); + return; + } + while (playerStats.Read()) + { + // Load data into each PersonalBest object + if (playerStats.GetInt32("type") == 1) // Bonus time + { +#if DEBUG + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> BonusPB"); +#endif + int bonusNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + BonusPB[bonusNum][style].ID = playerStats.GetInt32("id"); + BonusPB[bonusNum][style].Ticks = playerStats.GetInt32("run_time"); + BonusPB[bonusNum][style].Type = playerStats.GetInt32("type"); + BonusPB[bonusNum][style].Rank = playerStats.GetInt32("rank"); + BonusPB[bonusNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + BonusPB[bonusNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + BonusPB[bonusNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + BonusPB[bonusNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + BonusPB[bonusNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + BonusPB[bonusNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + BonusPB[bonusNum][style].RunDate = playerStats.GetInt32("run_date"); + } + else if (playerStats.GetInt32("type") == 2) // Stage time + { +#if DEBUG + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> StagePB"); +#endif + int stageNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + StagePB[stageNum][style].ID = playerStats.GetInt32("id"); + StagePB[stageNum][style].Ticks = playerStats.GetInt32("run_time"); + StagePB[stageNum][style].Type = playerStats.GetInt32("type"); + StagePB[stageNum][style].Rank = playerStats.GetInt32("rank"); + StagePB[stageNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + StagePB[stageNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + StagePB[stageNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + StagePB[stageNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); + Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: + StagePB[{stageNum}][{style}] = + Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}"); + } + else // Map time + { +#if DEBUG + Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> MapPB"); +#endif + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + PB[style].ID = playerStats.GetInt32("id"); + PB[style].Ticks = playerStats.GetInt32("run_time"); + PB[style].Type = playerStats.GetInt32("type"); + PB[style].Rank = playerStats.GetInt32("rank"); + 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].RunDate = playerStats.GetInt32("run_date"); + Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: + PB[{style}] = + ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}"); + await this.PB[style].PB_LoadCheckpointsData(); + } + // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 -> public async Task LoadPlayerMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); +#endif + } + } } } \ No newline at end of file diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 5878205..b87ef7d 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -10,18 +10,19 @@ internal class PlayerTimer // Modes public bool IsPracticeMode { get; set; } = false; // Practice mode toggle public bool IsStageMode { get; set; } = false; // Stage mode toggle + public bool IsBonusMode { get; set; } = false; // Bonus mode toggle // Tracking public int Stage { get; set; } = 0; // Current stage tracker public int Checkpoint {get; set;} = 0; // Current checkpoint tracker - public CurrentRun CurrentRunData { get; set; } = new CurrentRun(); // Current RUN data 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 { 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 - // Time Formatting + // Time Formatting - To-do: Move to player settings maybe? public enum TimeFormatStyle { Compact, @@ -39,7 +40,8 @@ public void Reset() this.IsPaused = false; this.IsPracticeMode = false; this.IsStageMode = false; - this.CurrentRunData.Reset(); + this.IsBonusMode = false; + // this.CurrentRunData.Reset(); } public void Pause() diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index f67de67..a5a8b11 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -1,24 +1,45 @@ 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 + + STAGE_ZONE_ENTER, + STAGE_ZONE_EXIT, + + START_ZONE_ENTER, + START_ZONE_EXIT, + + END_ZONE_ENTER, + END_ZONE_EXIT, + + CHECKPOINT_ZONE_ENTER, + CHECKPOINT_ZONE_EXIT, + + // START_RUN, + // END_RUN, + // TOUCH_CHECKPOINT, + // START_STAGE, + // END_STAGE, + // ENTER_STAGE, } [Serializable] -internal class ReplayFrame +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 float[] pos { get; set; } = { 0, 0, 0 }; + public float[] ang { get; set; } = { 0, 0, 0 }; + public ReplayFrameSituation Situation { get; set; } = ReplayFrameSituation.NONE; public uint Flags { get; set; } - public MoveType_t MoveType { get; set; } + + public Vector GetPos() + { + return new Vector(this.pos[0], this.pos[1], this.pos[2]); + } + + public QAngle GetAng() + { + return new QAngle(this.ang[0], this.ang[1], this.ang[2]); + } } diff --git a/src/ST-Player/Replay/ReplayManager.cs b/src/ST-Player/Replay/ReplayManager.cs new file mode 100644 index 0000000..7291846 --- /dev/null +++ b/src/ST-Player/Replay/ReplayManager.cs @@ -0,0 +1,101 @@ +using System.Text.Json; +using CounterStrikeSharp.API.Core; + +namespace SurfTimer; + +internal class ReplayManager +{ + public ReplayPlayer MapWR { get; set; } + public ReplayPlayer? BonusWR { get; set; } = null; + public ReplayPlayer? StageWR { get; set; } = null; + /// + /// Contains all Stage records for all styles - Refer to as AllStageWR[stage#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of Stages + /// + public Dictionary[] AllStageWR { get; set; } = new Dictionary[32]; + /// + /// Contains all Bonus records for all styles - Refer to as AllBonusWR[bonus#][style] + /// Need to figure out a way to NOT hardcode to `32` but to total amount of Bonuses + /// + public Dictionary[] AllBonusWR { get; set; } = new Dictionary[32]; + public List CustomReplays { get; set; } + + /// + /// + /// + /// ID of the map + /// Does the map have Stages + /// Does the map have Bonuses + /// Frames for the replay + /// Run time (Ticks) for the run + /// Name of the player + /// ID of the run + /// Style of the run + /// Stage/Bonus of the run + public ReplayManager(int map_id, bool staged, bool bonused, List frames, int run_time = 0, string playerName = "", int map_time_id = -1, int style = 0, int stage = 0) + { + MapWR = new ReplayPlayer + { + Type = 0, + Stage = 0, + RecordRank = 1, + MapID = map_id, + Frames = frames, + RecordRunTime = run_time, + RecordPlayerName = playerName, + MapTimeID = map_time_id + }; + + if (staged) + { + // Initialize 32 Stages for each style + // TODO: Make the amount of stages dynamic + for (int i = 0; i < 32; i++) + { + AllStageWR[i] = new Dictionary(); + foreach (int x in Config.Styles) + { + AllStageWR[i][x] = new ReplayPlayer(); + } + } + StageWR = new ReplayPlayer(); + } + + if (bonused) + { + // Initialize 32 Stages for each style + // TODO: Make the amount of bonuses dynamic + for (int i = 0; i < 32; i++) + { + AllBonusWR[i] = new Dictionary(); + foreach (int x in Config.Styles) + { + AllBonusWR[i][x] = new ReplayPlayer(); + } + } + BonusWR = new ReplayPlayer(); + } + + CustomReplays = new List(); + } + + public bool IsControllerConnectedToReplayPlayer(CCSPlayerController controller) + { + if (this.MapWR.Controller?.Equals(controller) == true) + return true; + + if (this.StageWR?.Controller?.Equals(controller) == true) + return true; + + if (this.BonusWR?.Controller?.Equals(controller) == true) + return true; + + foreach (var replay in this.CustomReplays) + { + if (replay.Controller?.Equals(controller) == true) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 98a44c4..6430f07 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -1,14 +1,15 @@ -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 { + /// + /// Enable or Disable the replay bots. + /// + public bool IsEnabled { get; set; } = Config.ReplaysEnabled; public bool IsPlaying { get; set; } = false; public bool IsPaused { get; set; } = false; public bool IsPlayable { get; set; } = false; @@ -16,16 +17,33 @@ internal class ReplayPlayer // 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; + public int MapID { get; set; } = -1; + public int MapTimeID { get; set; } = -1; + public int Type { get; set; } = -1; + public int Stage { get; set; } = -1; + + public int RecordRank { get; set; } = -1; // This is used to determine whether replay is for wr or for pb + public string RecordPlayerName { get; set; } = "N/A"; + public int RecordRunTime { get; set; } = -1; + public int ReplayCurrentRunTime { get; set; } = 0; + public bool IsReplayOutsideZone { get; set; } = false; // Tracking public List Frames { get; set; } = new List(); + public List StageEnterSituations { get; set; } = new List(); + public List StageExitSituations { get; set; } = new List(); + public List CheckpointEnterSituations { get; set; } = new List(); + public List CheckpointExitSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List MapSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List BonusSituations { get; set; } = new List(); // Playing public int CurrentFrameTick { get; set; } = 0; @@ -33,18 +51,18 @@ internal class ReplayPlayer public CCSPlayerController? Controller { get; set; } - public void ResetReplay() + public void ResetReplay() { this.CurrentFrameTick = 0; this.FrameTickIncrement = 1; - if(this.RepeatCount > 0) + if (this.RepeatCount > 0) this.RepeatCount--; - this.Stat_IsRunning = false; - this.Stat_RunTick = 0; + this.IsReplayOutsideZone = false; + this.ReplayCurrentRunTime = 0; } - public void Reset() + public void Reset() { this.IsPlaying = false; this.IsPaused = false; @@ -61,35 +79,44 @@ public void Reset() public void SetController(CCSPlayerController c, int repeat_count = -1) { this.Controller = c; - this.RepeatCount = repeat_count; + if (repeat_count != -1) + this.RepeatCount = repeat_count; this.IsPlayable = true; + + // Console.WriteLine($"===== public void SetController -> Set controller for {c.PlayerName}"); } - public void Start() + public void Start() { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; this.IsPlaying = true; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Start() -> Starting replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Stop() + public void Stop() { this.IsPlaying = false; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Stop() -> Stopping replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Pause() + public void Pause() { - if (!this.IsPlaying) + if (!this.IsPlaying || !this.IsEnabled) return; this.IsPaused = !this.IsPaused; - this.Stat_IsRunning = !this.Stat_IsRunning; + this.IsReplayOutsideZone = !this.IsReplayOutsideZone; + + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Pause() -> Pausing replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); } - public void Tick() + public void Tick() { - if (!this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) + if (this.MapID == -1 || !this.IsEnabled || !this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) return; ReplayFrame current_frame = this.Frames[this.CurrentFrameTick]; @@ -97,102 +124,105 @@ public void Tick() // SOME BLASHPEMY FOR YOU if (this.FrameTickIncrement >= 0) { - if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + if (current_frame.Situation == ReplayFrameSituation.START_ZONE_EXIT) { - this.Stat_IsRunning = true; - this.Stat_RunTick = 0; + this.IsReplayOutsideZone = true; + this.ReplayCurrentRunTime = 0; } - else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + else if (current_frame.Situation == ReplayFrameSituation.END_ZONE_ENTER) { - this.Stat_IsRunning = false; + this.IsReplayOutsideZone = false; } } else { - if (current_frame.Situation == (uint)ReplayFrameSituation.START_RUN) + if (current_frame.Situation == ReplayFrameSituation.START_ZONE_EXIT) { - this.Stat_IsRunning = false; + this.IsReplayOutsideZone = false; } - else if (current_frame.Situation == (uint)ReplayFrameSituation.END_RUN) + else if (current_frame.Situation == ReplayFrameSituation.END_ZONE_ENTER) { - this.Stat_IsRunning = true; - this.Stat_RunTick = this.CurrentFrameTick - (64*2); // (64*2) counts for the 2 seconds before run actually starts + this.IsReplayOutsideZone = true; + this.ReplayCurrentRunTime = 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!; + var current_frame_pos = current_frame.GetPos(); + var current_frame_ang = current_frame.GetAng(); bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; - Vector velocity = (current_frame.Pos - current_pos) * 64; + 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 ((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.Stat_IsRunning) - this.Stat_RunTick = Math.Max(0, this.Stat_RunTick + this.FrameTickIncrement); + if (this.IsReplayOutsideZone) + this.ReplayCurrentRunTime = Math.Max(0, this.ReplayCurrentRunTime + this.FrameTickIncrement); } - if(this.CurrentFrameTick >= this.Frames.Count) + if (this.CurrentFrameTick >= this.Frames.Count) this.ResetReplay(); + // if(RepeatCount != -1) // Spam City + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> Tick -> ====================> {this.RepeatCount} <===================="); } - public void LoadReplayData(TimerDatabase DB) + public void LoadReplayData(int repeat_count = -1) { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; - Task dbTask = DB.Query($@" - 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} - "); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] public void LoadReplayData -> We got MapID = {this.MapID}"); - MySqlDataReader mapTimeReplay = dbTask.Result; - if(!mapTimeReplay.HasRows) + if (this.MapID == -1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerReplay -> Load -> No replay data found for Player."); + Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] No replay data found for Player."); + return; } - 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)!; - this.Stat_RunTime = mapTimeReplay.GetInt32("run_time"); - this.Stat_PlayerName = mapTimeReplay.GetString("name"); - } - FormatBotName(); - } - mapTimeReplay.Close(); - dbTask.Dispose(); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] Loaded replay data for Player '{this.RecordPlayerName}'. MapTime ID: {this.MapTimeID} | Repeat {repeat_count} | Frames {this.Frames.Count} | Ticks {this.RecordRunTime}"); + this.ResetReplay(); + this.RepeatCount = repeat_count; } - private void FormatBotName() + public void FormatBotName() { - if (!this.IsPlayable) + if (!this.IsPlayable || !this.IsEnabled) return; + string prefix; + if (this.RecordRank == 1) + { + prefix = "WR"; + } + else + { + prefix = $"Rank #{this.RecordRank}"; + } + + if (this.Type == 1) + prefix = prefix + $"B {this.Stage}"; + else if (this.Type == 2) + prefix = prefix + $"CP {this.Stage}"; + 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}"; + string replay_name = $"[{prefix}] {this.RecordPlayerName} | {PlayerHUD.FormatTime(this.RecordRunTime)}"; + if (this.RecordRunTime <= 0) + replay_name = $"[{prefix}] {this.RecordPlayerName}"; bot_name.Set(replay_name); Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName"); diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index fd20ed5..61e0a8c 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -1,40 +1,63 @@ using System.Text.Json; using CounterStrikeSharp.API.Modules.Utils; - namespace SurfTimer; internal class ReplayRecorder { public bool IsRecording { get; set; } = false; + public bool IsSaving { get; set; } = false; public ReplayFrameSituation CurrentSituation { get; set; } = ReplayFrameSituation.NONE; public List Frames { get; set; } = new List(); + public List StageEnterSituations { get; set; } = new List(); + public List StageExitSituations { get; set; } = new List(); + public List CheckpointEnterSituations { get; set; } = new List(); + public List CheckpointExitSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List MapSituations { get; set; } = new List(); + /// + /// Indexes should always follow this pattern: START_ZONE_ENTER > START_ZONE_EXIT > END_ZONE_ENTER > END_ZONE_EXIT + /// Where END_ZONE_EXIT is not guaranteed + /// + public List BonusSituations { get; set; } = new List(); - public void Reset() + public void Reset() { this.IsRecording = false; this.Frames.Clear(); + this.StageEnterSituations.Clear(); + this.StageExitSituations.Clear(); + this.CheckpointEnterSituations.Clear(); + this.CheckpointExitSituations.Clear(); + this.MapSituations.Clear(); + this.BonusSituations.Clear(); + + Console.WriteLine($"===== ReplayRecorder -> Reset() -> Recording has been reset"); } - public void Start() + public void Start() { this.IsRecording = true; } - public void Stop() + public void Stop() { this.IsRecording = false; } - public void Tick(Player player) + public void Tick(Player player) { if (!this.IsRecording || player == null) return; - // Disabeling Recording if timer disabled - if (!player.Timer.IsEnabled) + // Disabling Recording if timer disabled + if (!player.Timer.IsEnabled) { this.Stop(); this.Reset(); + Console.WriteLine($"===== ReplayRecorder -> Tick() -> Recording has stopped and reset"); return; } @@ -43,15 +66,35 @@ public void Tick(Player player) 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 +/* + switch (this.CurrentSituation) { - 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, + case ReplayFrameSituation.START_ZONE_ENTER: + player.Controller.PrintToChat($"Start Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.Controller.PrintToChat($"Start Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.Controller.PrintToChat($"Stage Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.Controller.PrintToChat($"Stage Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.Controller.PrintToChat($"Checkpoint Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.Controller.PrintToChat($"Checkpoint Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + } +*/ + var frame = new ReplayFrame + { + pos = [player_pos.X, player_pos.Y, player_pos.Z], + ang = [player_angle.X, player_angle.Y, player_angle.Z], + Situation = this.CurrentSituation, Flags = player_flags, - MoveType = player_move_type, }; this.Frames.Add(frame); @@ -62,8 +105,226 @@ public void Tick(Player player) public string SerializeReplay() { - JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; - string replay_frames = JsonSerializer.Serialize(Frames, options); + // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + // string replay_frames = JsonSerializer.Serialize(Frames, options); + string replay_frames = JsonSerializer.Serialize(Frames); + return Compressor.Compress(replay_frames); + } + + public string SerializeReplayPortion(int start_idx, int end_idx) // Not used anymore + { + // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; + // string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx), options); + string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx)); return Compressor.Compress(replay_frames); } + + public void SetLastTickSituation(ReplayFrameSituation situation) + { + if (this.Frames.Count == 0) + return; + + this.Frames[this.Frames.Count - 2].Situation = situation; + } + + public string TrimReplay(Player player, int type = 0, bool lastStage = false) + { + this.IsSaving = true; + + List new_frames = new List(); + + if (this.Frames.Count == 0) + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> There are no Frames for trimming"); + throw new Exception("There are no Frames available for trimming"); + } + switch (type) + { + case 0: // Trim Map replays + // Map/Bonus runs + var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Map Run replay. Last start enter {start_enter_index}, last start exit {start_exit_index}, end enter {end_enter_index}"); + + if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (start_exit_index - (Config.ReplaysPre * 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre * 2); + else if (start_exit_index - Config.ReplaysPre >= start_enter_index) + startIndex = start_exit_index - Config.ReplaysPre; + else if (start_exit_index - (Config.ReplaysPre / 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre / 2); + else + startIndex = start_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre * 2); + else if (end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = end_enter_index + Config.ReplaysPre; + else if (end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = end_enter_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } + break; + case 1: // Trim Bonus replays + // Bonus runs + int bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + int bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + int bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Bonus Run replay. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}"); + + if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (bonus_exit_index - (Config.ReplaysPre * 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre * 2); + else if (bonus_exit_index - Config.ReplaysPre >= bonus_enter_index) + startIndex = bonus_exit_index - Config.ReplaysPre; + else if (bonus_exit_index - (Config.ReplaysPre / 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre / 2); + else + startIndex = bonus_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (bonus_end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre * 2); + else if (bonus_end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = bonus_end_enter_index + Config.ReplaysPre; + else if (bonus_end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = bonus_end_enter_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } + break; + case 2: // Trim Stage replays + int stage_end_index; + int stage_exit_index; + int stage_enter_index; + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Will trim Stage Run replay. Stage {player.Timer.Stage - 1}, available frames {Frames.Count}"); + + // Stage runs + if (lastStage) // Last stage + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + else if (player.Timer.Stage - 1 > 1) // Not first stage + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + else // First stage is always the start of the map so we are looking for START_ZONE_X + { + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `START_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); + } + + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Stage Run replay. Stage {player.Timer.Stage - 1} enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}"); + + if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (stage_exit_index - (Config.ReplaysPre * 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre * 2); + else if (stage_exit_index - Config.ReplaysPre >= stage_enter_index) + startIndex = stage_exit_index - Config.ReplaysPre; + else if (stage_exit_index - (Config.ReplaysPre / 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre / 2); + else + startIndex = stage_enter_index; // fallback to minimum allowed + + // Try different buffer sizes for end index + int endIndex; + if (stage_end_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre * 2); + else if (stage_end_index + Config.ReplaysPre < Frames.Count) + endIndex = stage_end_index + Config.ReplaysPre; + else if (stage_end_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = stage_end_index; // fallback to maximum allowed + + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); + } + break; + } + + this.IsSaving = false; + string trimmed = JsonSerializer.Serialize(new_frames); + return Compressor.Compress(trimmed); + } + + public int LastEnterTick(int start_idx = 0) + { + if (start_idx == 0) + start_idx = this.Frames.Count - 1; + for (int i = start_idx; i > 0; i--) + { + if ( + this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_ENTER || + this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_ENTER + ) + return i; + } + return 0; + } + + public int LastExitTick(int start_idx = 0) + { + if (start_idx == 0) + start_idx = this.Frames.Count - 1; + for (int i = start_idx; i > 0; i--) + { + if ( + this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_EXIT || + this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_EXIT + ) + return i; + } + return 0; + } } \ No newline at end of file diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs new file mode 100644 index 0000000..d972cb3 --- /dev/null +++ b/src/ST-UTILS/Config.cs @@ -0,0 +1,206 @@ +using System.Reflection; +using System.Text.Json; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Utils; + +namespace SurfTimer; + +public static class Config +{ + public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; + public static string PluginPrefix = $"[{ChatColors.DarkBlue}CS2 Surf{ChatColors.Default}]"; // To-do: make configurable + public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; + public static string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; + public static string ApiUrl => API.GetApiUrl(); + public static string DbConnectionString => MySQL.GetConnectionString(); + + /// + /// Placeholder for amount of styles + /// + public static List Styles = new List { 0 }; // Add all supported style IDs + + public static bool ReplaysEnabled => true; + public static int ReplaysPre => 64; + // Helper class/methods for configuration loading + private static class ConfigLoader + { + private static readonly Dictionary _configDocuments = new(); + + public static JsonDocument GetConfigDocument(string configPath) + { + if (!_configDocuments.ContainsKey(configPath)) + { + var fullPath = Server.GameDirectory + configPath; + _configDocuments[configPath] = JsonDocument.Parse(File.ReadAllText(fullPath)); + } + return _configDocuments[configPath]; + } + } + + public static class API + { + private const string API_CONFIG_PATH = "/csgo/cfg/SurfTimer/api_config.json"; + + private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(API_CONFIG_PATH); + + /// + /// Retrieves the `api_url` string from the configuration path + /// + /// A value containing the URL. + public static string GetApiUrl() + { + return ConfigDocument.RootElement.GetProperty("api_url").GetString()!; + } + + /// + /// Retrieves the `api_enabled` value from the configuration path + /// + /// A value for whether the API should be used. + public static bool GetApiOnly() + { + return ConfigDocument.RootElement.GetProperty("api_enabled").GetBoolean(); + } + + /// + /// Contains all the endpoints used by the API for the SurfTimer plugin. + /// + public static class Endpoints + { + // Map.cs related endpoints + public const string ENDPOINT_MAP_GET_INFO = "/surftimer/mapinfo?mapname={0}"; + public const string ENDPOINT_MAP_INSERT_INFO = "/surftimer/insertmap"; + public const string ENDPOINT_MAP_UPDATE_INFO = "/surftimer/updateMap"; + // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maptotals?map_id={0}&style={1}"; + public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?map_id={0}&style={1}&type={2}"; + public const string ENDPOINT_MAP_GET_RUN_CPS = "/surftimer/mapcheckpointsdata?maptime_id={0}"; + + // CurrentRun.cs + public const string ENDPOINT_CR_SAVE_STAGE_TIME = "/surftimer/savestagetime"; + } + } + + public static class MySQL + { + private const string DB_CONFIG_PATH = "/csgo/cfg/SurfTimer/database.json"; + + private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); + + /// + /// Retrieves the connection details for connecting to the MySQL Database + /// + /// A connection + public static string GetConnectionString() + { + string host = ConfigDocument.RootElement.GetProperty("host").GetString()!; + string database = ConfigDocument.RootElement.GetProperty("database").GetString()!; + string user = ConfigDocument.RootElement.GetProperty("user").GetString()!; + string password = ConfigDocument.RootElement.GetProperty("password").GetString()!; + int port = ConfigDocument.RootElement.GetProperty("port").GetInt32()!; + int timeout = ConfigDocument.RootElement.GetProperty("timeout").GetInt32()!; + + string connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; + + // Console.WriteLine($"============= [CS2 Surf] Extracted connection string: {connString}"); + + return connString; + } + + /// + /// Contains all the queries used by MySQL for the SurfTimer plugin. + /// + public static class Queries + { + // Map.cs related queries + public const string DB_QUERY_MAP_GET_RUNS = @" + SELECT MapTimes.*, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.map_id = {0} AND MapTimes.style = {1} AND MapTimes.type = {2} + ORDER BY MapTimes.run_time ASC; + "; // Deprecated + public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; + public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {5})"; // "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()})" + public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2} WHERE id={3};"; + public const string DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT = @" + SELECT + ranked_times.* + FROM ( + SELECT + MapTimes.*, + Player.name, + ROW_NUMBER() OVER ( + PARTITION BY MapTimes.type, MapTimes.stage + ORDER BY MapTimes.run_time ASC + ) AS row_num, + COUNT(*) OVER (PARTITION BY MapTimes.type, MapTimes.stage) AS total_count + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.map_id = {0} + ) AS ranked_times + WHERE ranked_times.row_num = 1;"; + + + // PlayerStats.cs related queries + public const string DB_QUERY_PS_GET_ALL_RUNTIMES = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1}; + "; // Deprecated + + // PersonalBest.cs related queries + public const string DB_QUERY_PB_GET_TYPE_RUNTIME = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1} AND mainquery.`type` = {2} AND mainquery.`style` = {3}; + "; + public const string DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA = @" + 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery + WHERE mainquery.`id` = {0}; + "; + public const string DB_QUERY_PB_GET_CPS = "SELECT * FROM `Checkpoints` WHERE `maptime_id` = {0};"; + + // CurrentRun.cs related queries + public const string DB_QUERY_CR_INSERT_TIME = @" + 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`, `replay_frames`) + VALUES ({0}, {1}, {2}, {3}, {4}, {5}, + {6}, {7}, {8}, {9}, {10}, {11}, {12}, '{13}') + 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), replay_frames=VALUES(replay_frames); + "; + public const string DB_QUERY_CR_INSERT_CP = @" + 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 ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}) + 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); + "; + + // ReplayPlayer.cs related queries + public const string DB_QUERY_RP_LOAD_REPLAY = @" + SELECT MapTimes.replay_frames, MapTimes.run_time, Player.name + FROM MapTimes + JOIN Player ON MapTimes.player_id = Player.id + WHERE MapTimes.id={0}; + "; + + // Players.cs related queries + public const string DB_QUERY_PP_GET_PROFILE = "SELECT * FROM `Player` WHERE `steam_id` = {0} LIMIT 1;"; + public const string DB_QUERY_PP_INSERT_PROFILE = @" + INSERT INTO `Player` (`name`, `steam_id`, `country`, `join_date`, `last_seen`, `connections`) + VALUES ('{0}', {1}, '{2}', {3}, {4}, {5}); + "; + public const string DB_QUERY_PP_UPDATE_PROFILE = @" + UPDATE `Player` SET country = '{0}', + `last_seen` = {1}, `connections` = `connections` + 1, `name` = '{3}' + WHERE `id` = {2} LIMIT 1; + "; + } + } +} \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index b1344df..d0517a9 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -25,13 +25,10 @@ You should have received a copy of the GNU Affero General Public License #define DEBUG -using System.Text.Json; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes.Registration; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; @@ -40,26 +37,34 @@ namespace SurfTimer; public partial class SurfTimer : BasePlugin { // Metadata - public override string ModuleName => "CS2 SurfTimer"; + public override string ModuleName => $"CS2 {Config.PluginName}"; public override string ModuleVersion => "DEV-1"; public override string ModuleDescription => "Official Surf Timer by the CS2 Surf Initiative."; public override string ModuleAuthor => "The CS2 Surf Initiative - github.com/cs2surf"; - public string PluginPrefix => $"[{ChatColors.DarkBlue}CS2 Surf{ChatColors.Default}]"; // To-do: make configurable // Globals private Dictionary playerList = new Dictionary(); // This can probably be done way better, revisit - internal TimerDatabase? DB = new TimerDatabase(); - public string PluginPath = Server.GameDirectory + "/csgo/addons/counterstrikesharp/plugins/SurfTimer/"; - internal Map CurrentMap = null!; + internal static TimerDatabase DB = new TimerDatabase(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string + // internal Map CurrentMap = null!; + internal static Map CurrentMap = null!; /* ========== MAP START HOOKS ========== */ 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 1.0 seconds for now - if ((CurrentMap == null || CurrentMap.Name != mapName) && mapName.Contains("surf_")) + if ((CurrentMap == null || !CurrentMap.Name.Equals(mapName)) && mapName.Contains("surf_")) { - AddTimer(1.0f, () => CurrentMap = new Map(mapName, DB!)); // Was 3 seconds, now 1 second + Server.NextWorldUpdate(() => Console.WriteLine(String.Format(" ____________ ____ ___\n" + + " / ___/ __/_ | / __/_ ______/ _/\n" + + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" + + "\\___/___/____/ /___/\\_,_/_/ /_/\n" + + $"[CS2 Surf] {Config.PluginName} {ModuleVersion} - loading map {mapName}.\n" + + $"[CS2 Surf] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" + + $"[CS2 Surf] ---> Source Code: https://github.com/CS2Surf/Timer\n" + + $"[CS2 Surf] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" + ))); + + Server.NextWorldUpdate(async () => CurrentMap = await Map.CreateAsync(mapName)); // NextWorldUpdate runs even during server hibernation } } @@ -88,30 +93,22 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) /* ========== PLUGIN LOAD ========== */ public override void Load(bool hotReload) { - // Load database config & spawn database object - try + // Check if we have connected to the Database + if (DB != null) { - JsonElement dbConfig = JsonDocument.Parse(File.ReadAllText(Server.GameDirectory + "/csgo/cfg/SurfTimer/database.json")).RootElement; - DB = new TimerDatabase(dbConfig.GetProperty("host").GetString()!, - dbConfig.GetProperty("database").GetString()!, - dbConfig.GetProperty("user").GetString()!, - dbConfig.GetProperty("password").GetString()!, - dbConfig.GetProperty("port").GetInt32(), - dbConfig.GetProperty("timeout").GetInt32()); Console.WriteLine("[CS2 Surf] Database connection established."); } - - catch (Exception e) + else { - Console.WriteLine($"[CS2 Surf] Error loading database config: {e.Message}"); + Console.WriteLine($"[CS2 Surf] Error connecting to the database."); // To-do: Abort plugin loading } Console.WriteLine(String.Format(" ____________ ____ ___\n" + " / ___/ __/_ | / __/_ ______/ _/\n" + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" - + "\\___/___/____/ /___/\\_,_/_/ /_/\n" - + $"[CS2 Surf] SurfTimer plugin loaded. Version: {ModuleVersion}" + + "\\___/___/____/ /___/\\_,_/_/ /_/\n" + + $"[CS2 Surf] {Config.PluginName} plugin loaded. Version: {ModuleVersion}\n" + $"[CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. Source code: https://github.com/CS2Surf/Timer\n" )); @@ -122,10 +119,6 @@ public override void Load(bool hotReload) // Tick listener RegisterListener(OnTick); - // StartTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 - // VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(OnTriggerStartTouch, HookMode.Post); - // EndTouch Hook -- DEPRECATE BROKEN CS2 7TH FEB 2024 - // VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(OnTriggerEndTouch, HookMode.Post); HookEntityOutput("trigger_multiple", "OnStartTouch", OnTriggerStartTouch); HookEntityOutput("trigger_multiple", "OnEndTouch", OnTriggerEndTouch); diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 87e253f..82404c6 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable true @@ -13,9 +13,9 @@ - + - + diff --git a/src/bin/MaxMind.Db.dll b/src/bin/MaxMind.Db.dll new file mode 100644 index 0000000000000000000000000000000000000000..dadb1f6bef6c5ebc8a2270f549d66b5ec92ee258 GIT binary patch literal 44544 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 literal 0 HcmV?d00001 diff --git a/src/bin/MaxMind.GeoIP2.dll b/src/bin/MaxMind.GeoIP2.dll new file mode 100644 index 0000000000000000000000000000000000000000..a665688c4e2fad5a10df3ef61f491d319b161be0 GIT binary patch literal 58368 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 literal 0 HcmV?d00001 diff --git a/src/bin/MySqlConnector.dll b/src/bin/MySqlConnector.dll new file mode 100644 index 0000000000000000000000000000000000000000..ea3237d4b05efd88bd8fe7c0f3c829b0afe1f9a4 GIT binary patch literal 838504 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�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�{0)dx8Od6`z!8WxLr#* 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

>ABQ`_ZsaNts%le*c<44rz4NHH(sPl# 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 oG1DSnC@DrEq#=gNLMjb$mTqa_VUjmwiVQ$# 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ΝdJRrU=vK+G9qrCN z?tiTlpQ~j95ZZP*0u@CQY5+nNK)4MM%HgPVui%S~l6Hkpt+mQWmwfxK?S08U*S}Hr z5w;AMD9SJ>d7S|X6 literal 0 HcmV?d00001 From 7817c8d3c8893c9d5d5c7bd6b5df7a8351658657 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 27 May 2025 18:48:32 +0300 Subject: [PATCH 13/52] Add Logging --- src/ST-Events/Players.cs | 25 +- src/ST-Map/Map.cs | 134 ++++-- src/ST-Player/PlayerProfile.cs | 38 +- src/ST-Player/PlayerStats/CurrentRun.cs | 85 +++- src/ST-Player/PlayerStats/PersonalBest.cs | 77 ++-- src/ST-Player/PlayerStats/PlayerStats.cs | 72 +++- src/ST-Player/Replay/ReplayPlayer.cs | 58 ++- src/ST-Player/Replay/ReplayRecorder.cs | 481 ++++++++++++++-------- src/ST-UTILS/Injection.cs | 71 ++++ src/SurfTimer.cs | 49 ++- src/SurfTimer.csproj | 4 + 11 files changed, 759 insertions(+), 335 deletions(-) create mode 100644 src/ST-UTILS/Injection.cs diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 283ce2f..a554522 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -2,8 +2,8 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Utils; -using MySqlConnector; using MaxMind.GeoIP2; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -106,11 +106,12 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn { country = "LL"; // Handle local IP appropriately } -#if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnectFull -> GeoIP -> {name} -> {player.IpAddress!.Split(":")[0]} -> {country}"); -#endif + // #if DEBUG + // Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnectFull -> GeoIP -> {name} -> {player.IpAddress!.Split(":")[0]} -> {country}"); + // #endif if (DB == null) { + _logger.LogCritical("OnPlayerConnect -> DB object is null, this shouldn't happen."); Exception ex = new("CS2 Surf ERROR >> OnPlayerConnect -> DB object is null, this shouldn't happen."); throw ex; } @@ -126,8 +127,9 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn // Print join messages Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Green}{name}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); - Console.WriteLine($"[CS2 Surf] {name} has connected from {playerList[player.UserId ?? 0].Profile.Country}."); - + _logger.LogTrace("[{Prefix}] {PlayerName} has connected from {Country}.", + Config.PluginName, name, playerList[player.UserId ?? 0].Profile.Country + ); return HookResult.Continue; } @@ -138,7 +140,9 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo if (player == null) { - Console.WriteLine($"CS2 Surf ERROR >> OnPlayerDisconnect -> Null ({player == null})"); + _logger.LogError("OnPlayerDisconnect -> 'player' is NULL ({IsNull})", + player == null + ); return HookResult.Continue; } @@ -163,11 +167,16 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { if (DB == null) + { + _logger.LogCritical("OnPlayerDisconnect -> DB object is null, this shouldnt happen."); throw new Exception("CS2 Surf ERROR >> OnPlayerDisconnect -> DB object is null, this shouldnt happen."); + } 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})"); + _logger.LogError("OnPlayerDisconnect -> playerList does NOT contain player.UserId, this shouldn't happen. Player: {PlayerName} ({UserId})", + player.PlayerName, player.UserId + ); } else { diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 6758c23..1f169c8 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -1,9 +1,13 @@ using System.Data; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.RegularExpressions; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using MySqlConnector; namespace SurfTimer; @@ -71,9 +75,14 @@ internal class Map public ReplayManager ReplayManager { get; set; } = null!; + private readonly ILogger _logger; + // Constructor internal Map(string name) { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + // Set map name this.Name = name; @@ -105,19 +114,26 @@ public static async Task CreateAsync(string name) return map; } - private async Task InitializeAsync() + private async Task InitializeAsync([CallerMemberName] string methodName = "") { // Load zones Map_Load_Zones(); - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Zones have been loaded."); + _logger.LogInformation("[{ClassName}] {MethodName} -> Zones have been loaded. | Bonuses: {Bonuses} | Stages: {Stages} | Checkpoints: {Checkpoints}", + nameof(Map), methodName, this.Bonuses, this.Stages, this.TotalCheckpoints + ); // Initialize ReplayManager with placeholder values // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Initializing ReplayManager(-1, {this.Stages > 0}, false, null!)"); this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); // Adjust values as needed + // Start timing + var stopwatch = Stopwatch.StartNew(); await Get_Map_Info(); + stopwatch.Stop(); - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> We got MapID = {ID} ({Name})"); + _logger.LogInformation("[{ClassName}] {MethodName} -> We got MapID = {ID} ({Name}) in {ElapsedMilliseconds}ms | API = {API}", + nameof(Map), methodName, ID, Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } ///

@@ -126,7 +142,7 @@ private async Task InitializeAsync() // To-do: This loops through all the triggers. While that's great and comprehensive, some maps have two triggers with the exact same name, because there are two // for each side of the course (left and right, for example). We should probably work on automatically catching this. // Maybe even introduce a new naming convention? - internal void Map_Load_Zones() + internal void Map_Load_Zones([CallerMemberName] string methodName = "") { // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); @@ -239,14 +255,19 @@ internal void Map_Load_Zones() if (this.Stages > 0) // Account for stage 1, not counted above this.Stages += 1; - 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}"); + + _logger.LogTrace("[{ClassName}] {MethodName} -> Start zone: {StartZoneX},{StartZoneY},{StartZoneZ} | End zone: {EndZoneX},{EndZoneY},{EndZoneZ}", + nameof(Map), methodName, this.StartZone.X, this.StartZone.Y, this.StartZone.Z, this.EndZone.X, this.EndZone.Y, this.EndZone.Z + ); + + KillServerCommandEnts(); } /// /// Inserts a new map entry in the database. /// Automatically detects whether to use API Calls or MySQL query. /// - internal async Task Insert_Map_Info() + internal async Task Insert_Map_Info([CallerMemberName] string methodName = "") { if (Config.API.GetApiOnly()) // API Calls { @@ -266,7 +287,9 @@ internal async Task Insert_Map_Info() // Check if the response is not null and get the last_id if (postResponse != null) { - Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Insert_Map_Info -> New map '{Name}' inserted, got ID {postResponse.last_id}"); + _logger.LogInformation("[{ClassName}] {MethodName} -> Insert_Map_Info -> New map '{Name}' inserted, got ID {MapID}", + nameof(Map), methodName, Name, postResponse.last_id + ); this.ID = postResponse.last_id; } @@ -278,6 +301,9 @@ internal async Task Insert_Map_Info() string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(Name), "Unknown", this.Stages, this.Bonuses, 0, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds())); if (writerRows != 1) { + _logger.LogCritical("[{ClassName}] {MethodName} -> Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}", + nameof(Map), methodName, Name + ); Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); throw exception; } @@ -290,7 +316,7 @@ internal async Task Insert_Map_Info() /// Updates last played, stages, bonuses for the map in the database. /// Automatically detects whether to use API Calls or MySQL query. /// - internal async Task Update_Map_Info() + internal async Task Update_Map_Info([CallerMemberName] string methodName = "") { if (Config.API.GetApiOnly()) // API Calls { @@ -315,12 +341,17 @@ internal async Task Update_Map_Info() (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.Stages, this.Bonuses, this.ID); #if DEBUG - Console.WriteLine($"CS2 Surf >> internal class Map -> internal async Task Update_Map_Info -> Update MapData: {updateQuery}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> Update MapData: {Query}", + nameof(Map), methodName, updateQuery + ); #endif int lastPlayedUpdateRows = await SurfTimer.DB.WriteAsync(updateQuery); if (lastPlayedUpdateRows != 1) { + _logger.LogCritical("[{ClassName}] {MethodName} -> Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}", + nameof(Map), methodName, Name + ); Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}"); throw exception; } @@ -333,7 +364,7 @@ internal async Task Update_Map_Info() /// Automatically detects whether to use API Calls or MySQL query. /// /// Should we run UPDATE query for the map - internal async Task Get_Map_Info(bool updateData = true) + internal async Task Get_Map_Info(bool updateData = true, [CallerMemberName] string methodName = "") { bool newMap = false; @@ -390,7 +421,14 @@ internal async Task Get_Map_Info(bool updateData = true) if (updateData) await Update_Map_Info(); + var stopwatch = Stopwatch.StartNew(); await Get_Map_Record_Runs(); + stopwatch.Stop(); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Finished Get_Map_Record_Runs in {ElapsedMilliseconds}ms | API = {API}", + nameof(Map), methodName, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); +#endif } /// @@ -400,7 +438,7 @@ internal async Task Get_Map_Info(bool updateData = true) /// Automatically detects whether to use API Calls or MySQL query. /// TODO: Re-do the API with the new query and fix the API assign of values /// - internal async Task Get_Map_Record_Runs() + internal async Task Get_Map_Record_Runs([CallerMemberName] string methodName = "") { int totalMapRuns = 0; int totalStageRuns = 0; @@ -566,9 +604,18 @@ internal async Task Get_Map_Record_Runs() ) // This should also reload Checkpoints if a new MapWR is set { #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal async Task Get_Map_Record_Runs : Map -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Loaded {this.MapCompletions[style]} runs (MapID {this.ID} | Style {style}). WR by {this.WR[style].Name} - {PlayerHUD.FormatTime(this.WR[style].Ticks)}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> Get_Map_Record_Runs : Map -> [{DBorAPI}] Loaded {MapCompletions} runs (MapID {MapID} | Style {Style}). WR by {PlayerName} - {Time}", + nameof(Map), methodName, (Config.API.GetApiOnly() ? "API" : "DB"), this.MapCompletions[style], this.ID, style, this.WR[style].Name, PlayerHUD.FormatTime(this.WR[style].Ticks) + ); #endif + + var stopwatch = Stopwatch.StartNew(); await Get_Record_Run_Checkpoints(style); + stopwatch.Stop(); + + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished Get_Record_Run_Checkpoints({Style}) in {ElapsedMilliseconds}ms | API = {API}", + nameof(Map), methodName, style, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } } } @@ -591,7 +638,7 @@ internal async Task Get_Record_Run_Checkpoints(int style = 0) /// Style to add /// Stage to add /// Base64 encoded string for the replay_frames - internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64) + internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64, [CallerMemberName] string methodName = "") { JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; @@ -621,33 +668,33 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame { case ReplayFrameSituation.START_ZONE_ENTER: this.ReplayManager.MapWR.MapSituations.Add(i); - Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); + // Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.START_ZONE_EXIT: this.ReplayManager.MapWR.MapSituations.Add(i); - Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); + // Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.STAGE_ZONE_ENTER: this.ReplayManager.MapWR.StageEnterSituations.Add(i); - Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); + // Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.STAGE_ZONE_EXIT: this.ReplayManager.MapWR.StageExitSituations.Add(i); - Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); + // Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: this.ReplayManager.MapWR.CheckpointEnterSituations.Add(i); - Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); + // Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: this.ReplayManager.MapWR.CheckpointExitSituations.Add(i); - Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); + // Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.END_ZONE_ENTER: - Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); + // Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.END_ZONE_EXIT: - Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); + // Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); break; } } @@ -656,7 +703,12 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Skip if the same bonus run already exists if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].Ticks) break; - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Adding run {this.BonusWR[stage][style].ID} {PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks)} (Ticks = {this.BonusWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllBonusWR`"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Set_Replay_Data -> [BonusWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllBonusWR`", + nameof(Map), methodName, this.BonusWR[stage][style].ID, PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks), this.BonusWR[stage][style].Ticks, frames.Count + ); +#endif + // Add all stages found to a dictionary with their data this.ReplayManager.AllBonusWR[stage][style].MapID = this.ID; this.ReplayManager.AllBonusWR[stage][style].Frames = frames; @@ -683,7 +735,11 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Set the bonus to replay first if (this.ReplayManager.BonusWR != null && this.ReplayManager.BonusWR.MapID == -1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] Setting first `ReplayManager.BonusWR` to bonus {stage}"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> [BonusWR] Setting first `ReplayManager.BonusWR` to bonus {stage}", + nameof(Map), methodName, stage + ); +#endif if (this.ReplayManager.BonusWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current bonus being played? this.ReplayManager.BonusWR.Stop(); this.ReplayManager.BonusWR.MapID = this.ID; @@ -700,7 +756,12 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Skip if the same stage run already exists if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) break; - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Adding run {this.StageWR[stage][style].ID} {PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks)} (Ticks = {this.StageWR[stage][style].Ticks}; Frames = {frames.Count}) to `ReplayManager.AllStageWR`"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Set_Replay_Data -> [StageWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllStageWR`", + nameof(Map), methodName, this.StageWR[stage][style].ID, PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks), this.StageWR[stage][style].Ticks, frames.Count + ); +#endif + // Add all stages found to a dictionary with their data this.ReplayManager.AllStageWR[stage][style].MapID = this.ID; this.ReplayManager.AllStageWR[stage][style].Frames = frames; @@ -727,7 +788,12 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Set the stage to replay first if (this.ReplayManager.StageWR != null && this.ReplayManager.StageWR.MapID == -1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] Setting first `ReplayManager.StageWR` to stage {stage}"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> [StageWR] Setting first `ReplayManager.StageWR` to stage {stage}", + nameof(Map), methodName, stage + ); +#endif + if (this.ReplayManager.StageWR.IsPlaying) // Maybe only stop the replay if we are overwriting the current stage being played? this.ReplayManager.StageWR.Stop(); this.ReplayManager.StageWR.MapID = this.ID; @@ -745,19 +811,19 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Start the new map replay if none existed until now if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); this.ReplayManager.MapWR.ResetReplay(); this.ReplayManager.MapWR.Start(); } else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); this.ReplayManager.BonusWR.ResetReplay(); this.ReplayManager.BonusWR.Start(); } else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); this.ReplayManager.StageWR.ResetReplay(); this.ReplayManager.StageWR.Start(); } @@ -785,4 +851,16 @@ public static bool IsInZone(Vector zoneOrigin, float zoneCollisionRadius, Vector else return false; } + + private void KillServerCommandEnts([CallerMemberName] string methodName = "") + { + var pointServerCommands = Utilities.FindAllEntitiesByDesignerName("point_servercommand"); + + foreach (var servercmd in pointServerCommands) + { + if (servercmd == null) continue; + _logger.LogTrace("[{ClassName}] {MethodName} -> Killed point_servercommand ent: {ServerCMD}", nameof(Map), methodName, servercmd.Handle); + servercmd.Remove(); + } + } } \ No newline at end of file diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 6f5930a..790fb64 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -1,5 +1,6 @@ -using CounterStrikeSharp.API.Core; -using Microsoft.VisualBasic; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -12,9 +13,13 @@ internal class PlayerProfile public int JoinDate { get; set; } = 0; public int LastSeen { get; set; } = 0; public int Connections { get; set; } = 0; + private readonly ILogger _logger; public PlayerProfile(ulong steamId, string name = "", string country = "") { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + this.SteamID = steamId; this.Name = name; this.Country = country; @@ -35,17 +40,19 @@ public static async Task CreateAsync(ulong steamId, string name = return profile; } - private async Task InitializeAsync() + private async Task InitializeAsync([CallerMemberName] string methodName = "") { await Get_Player_Profile(); - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] We got ProfileID = {this.ID} ({this.Name})"); + _logger.LogTrace("[{ClassName}] {MethodName} -> InitializeAsync -> [{ConnType}] We got ProfileID {ProfileID} ({PlayerName})", + nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.ID, this.Name + ); } /// /// Retrieves all the data for the player from the database. /// - public async Task Get_Player_Profile() + public async Task Get_Player_Profile([CallerMemberName] string methodName = "") { bool newPlayer = false; @@ -70,7 +77,9 @@ public async Task Get_Player_Profile() } #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> InitializeAsync -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Returning player {this.Name} ({this.SteamID}) loaded from database with ID {this.ID}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> Get_Player_Profile -> [{ConnType}] Returning player {PlayerName} ({SteamID}) loaded with ID {ProfileID}.", + nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID + ); #endif if (newPlayer) await Insert_Player_Profile(); @@ -80,7 +89,7 @@ public async Task Get_Player_Profile() /// Insert new player information into the database. /// Retrieves the ID of the newly created player. /// - public async Task Insert_Player_Profile() + public async Task Insert_Player_Profile([CallerMemberName] string methodName = "") { // Player does not exist in database int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -99,7 +108,9 @@ public async Task Insert_Player_Profile() await Get_Player_Profile(); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Insert_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] New player {this.Name} ({this.SteamID}) added to database with ID {this.ID}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> Insert_Player_Profile -> [{ConnType}] New player {PlayerName} ({SteamID}) added with ID {ProfileID}.", + nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID + ); #endif } @@ -108,13 +119,20 @@ public async Task Insert_Player_Profile() /// /// Player Name /// - public async Task Update_Player_Profile(string name) + public async Task Update_Player_Profile(string name, [CallerMemberName] string methodName = "") { int updatePlayerTask = await SurfTimer.DB.WriteAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, this.Country, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.ID, name)); if (updatePlayerTask != 1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Update_Player_Profile -> [{ConnType}] Failed to update data in database. Player {PlayerName} ({SteamID})", + nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID + ); throw new Exception($"CS2 Surf ERROR >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Failed to update player data in database. Player: {this.Name} ({this.SteamID})"); + } #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Updated player {name} ({this.SteamID}) in database. ID {this.ID}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> Update_Player_Profile -> [{ConnType}] Updated player {PlayerName} ({SteamID}) in database with ID {ProfileID}.", + nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID + ); #endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 32150a0..1489be5 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -1,3 +1,8 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + namespace SurfTimer; /// @@ -14,11 +19,15 @@ internal class CurrentRun 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; } + private readonly ILogger _logger; // Add other properties as needed // Constructor public CurrentRun() { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + Checkpoints = new Dictionary(); Ticks = 0; StartVelX = 0.0f; @@ -51,13 +60,18 @@ public void Reset() /// Bonus number /// Stage number /// Ticks for the run - used for Stage and Bonus entries - public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1) + public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") { // Add entry in DB for the run // PrintSituations(player); string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Sending total of {replay_frames.Length} replay frames"); + _logger.LogTrace("[{ClassName}] {MethodName} -> SaveMapTime -> Sending total of {ReplayFramesTotal} replay frames.", + nameof(CurrentRun), methodName, replay_frames.Length + ); + + var stopwatch = Stopwatch.StartNew(); + if (Config.API.GetApiOnly()) { return; @@ -68,7 +82,9 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r if (stage != 0 || bonus != 0) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveMapTime -> Inserted an entry for {(stage != 0 ? "Stage" : "Bonus")} {(stage != 0 ? stage : bonus)} - {run_ticks}"); + _logger.LogTrace("[{ClassName}] {MethodName} -> Inserted an entry for {Type} {Number} - {Ticks}", + nameof(CurrentRun), methodName, (stage != 0 ? "Stage" : "Bonus"), (stage != 0 ? stage : bonus), run_ticks + ); } else { @@ -77,6 +93,11 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r await player.CurrMap.Get_Map_Record_Runs(); // Reload the times for the Map } + + stopwatch.Stop(); + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", + nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } public void PrintSituations(Player player) @@ -113,7 +134,8 @@ public void PrintSituations(Player player) break; } } - Console.WriteLine($"========================== =========================="); + Console.WriteLine("========================== MapSituations: {MapSituations} | START_ZONE_ENTER = {START_ZONE_ENTER} | START_ZONE_EXIT = {START_ZONE_EXIT} | END_ZONE_ENTER = {END_ZONE_ENTER} ==========================", player.ReplayRecorder.MapSituations.Count, player.ReplayRecorder.MapSituations[0], player.ReplayRecorder.MapSituations[1], player.ReplayRecorder.MapSituations[2]); + Console.WriteLine("========================== Total Frames: {TotalFrames} ==========================", player.ReplayRecorder.Frames.Count); } /// @@ -126,7 +148,7 @@ public void PrintSituations(Player player) /// Replay frames /// Whether to reload the PersonalBest data for the Player. /// - public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false) + public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false, [CallerMemberName] string methodName = "") { int playerId = player.Profile.ID; int mapId = player.CurrMap.ID; @@ -141,6 +163,8 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int float endVelY = player.Stats.ThisRun.EndVelY; float endVelZ = player.Stats.ThisRun.EndVelZ; + var stopwatch = Stopwatch.StartNew(); + if (Config.API.GetApiOnly()) // API Calls { // API Insert map goes here @@ -151,26 +175,40 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, playerId, mapId, style, type, type == 2 ? stage : type == 1 ? bonus : 0, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), replay_frames)); if (updatePlayerRunTask <= 0) { + _logger.LogError("[{ClassName}] {MethodName} -> InsertMapTime -> Failed to insert/update player run in database. Player: {Name} ({SteamID})", + nameof(CurrentRun), methodName, player.Profile.Name, player.Profile.SteamID + ); Exception ex = new($"CS2 Surf ERROR >> internal class CurrentRun -> public async Task InsertMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); throw ex; } if (reloadData && type == 0) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.PB[player.Timer.Style].ID}))"); + _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", + nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID + ); await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) } else if (reloadData && type == 1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.BonusPB[bonus][style].ID}))"); + _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", + nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.BonusPB[bonus][style].ID + ); await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) } else if (reloadData && type == 2) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{player.Profile.Name}' (ID {player.Stats.StagePB[stage][style].ID}))"); + _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", + nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.StagePB[stage][style].ID + ); await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) } } + + stopwatch.Stop(); + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished InsertMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", + nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } /// @@ -178,14 +216,17 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int /// /// Player object /// Whether to reload the PersonalBest Checkpoints data for the Player. - public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false) + public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false, [CallerMemberName] string methodName = "") { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> Will send {player.Stats.ThisRun.Checkpoints.Count} ({this.Checkpoints.Count}) checkpoints to DB...."); + _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will send {ThisRunCheckpoints} ({CheckpointsCount}) checkpoints to DB....", + nameof(CurrentRun), methodName, player.Stats.ThisRun.Checkpoints.Count, this.Checkpoints.Count + ); + var stopwatch = Stopwatch.StartNew(); + int style = player.Timer.Style; int mapTimeId = player.Stats.PB[style].ID; List commands = new List(); // Loop through the checkpoints and insert/update them in the database for the run - // foreach (var item in player.Stats.ThisRun.Checkpoints) foreach (var item in this.Checkpoints) { int cp = item.Key; @@ -200,12 +241,13 @@ public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = fal int attempts = item.Value!.Attempts; #if DEBUG - Console.WriteLine($"CP: {cp} | MapTime ID: {mapTimeId} | Time: {endTouch} | Ticks: {ticks} | startVelX: {startVelX} | startVelY: {startVelY} | startVelZ: {startVelZ} | endVelX: {endVelX} | endVelY: {endVelY} | endVelZ: {endVelZ}"); - Console.WriteLine($@"CS2 Surf DEBUG >> internal class CurrentRun -> SaveCurrentRunCheckpoints -> - {string.Format( - Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, - mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch)} - "); + _logger.LogDebug("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> CP: {Checkpoint} | MapTime ID: {MapTimeID} | Time: {Time} | Ticks: {Ticks} | SVX {StartVelX} | SVY {StartVelY} | SVZ {StartVelZ} | EVX {EndVelX} | EVY {EndVelY} | EVZ {EndVelZ}", + nameof(CurrentRun), methodName, cp, mapTimeId, endTouch, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ + ); + _logger.LogDebug("Query to send:\n{Query}", + string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch) + ); #endif // Insert/Update CPs to database @@ -224,8 +266,15 @@ public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = fal if (reloadData) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class CurrentRun -> public async Task SaveCurrentRunCheckpoints -> Will reload Checkpoints data for {player.Profile.Name} (ID {player.Stats.PB[player.Timer.Style].ID})"); + _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will reload Checkpoints data for '{Name}' (ID {MapTimeID})", + nameof(CurrentRun), methodName, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID + ); await player.Stats.PB[player.Timer.Style].PB_LoadCheckpointsData(); // Load the Checkpoints data again } + + stopwatch.Stop(); + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveCurrentRunCheckpoints(reloadData = {reloadData}) for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", + nameof(CurrentRun), methodName, reloadData, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } } diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index a689a00..cfd77fb 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -1,3 +1,7 @@ +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + namespace SurfTimer; /// @@ -19,11 +23,15 @@ internal class PersonalBest public float EndVelZ { get; set; } public int RunDate { get; set; } public string Name { get; set; } = ""; // This is used only for WRs + private readonly ILogger _logger; // Add other properties as needed // Constructor public PersonalBest() { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + Ticks = -1; Checkpoints = new Dictionary(); Type = -1; @@ -41,19 +49,23 @@ public PersonalBest() /// Automatically detects whether to use API Calls or MySQL query. /// Bonus and Stage runs should NOT have any checkpoints. /// - public async Task PB_LoadCheckpointsData() + public async Task PB_LoadCheckpointsData([CallerMemberName] string methodName = "") { if (this == null) { #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PersonalBest object is null."); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> PersonalBest object is null.", + nameof(PersonalBest), methodName + ); #endif return; } if (this.Checkpoints == null) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> PB Checkpoints list is not initialized."); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> PB Checkpoints list is not initialized.", + nameof(PersonalBest), methodName + ); #endif this.Checkpoints = new Dictionary(); // Initialize if null } @@ -89,32 +101,28 @@ public async Task PB_LoadCheckpointsData() using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, this.ID))) { #if DEBUG - Console.WriteLine($"this.Checkpoint.Count {this.Checkpoints.Count} "); - Console.WriteLine($"this.ID {this.ID} "); - Console.WriteLine($"this.Ticks {this.Ticks} "); - Console.WriteLine($"this.RunDate {this.RunDate} "); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading from DB: this.Checkpoint.Count {RunCheckpointsCount} | this.ID {RunID} | this.Ticks {RunTicks} | this.RunDate {RunDate}", + nameof(PersonalBest), methodName, this.Checkpoints.Count, this.ID, this.Ticks, this.RunDate + ); #endif if (!results.HasRows) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> No checkpoints found for this mapTimeId {this.ID}."); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> No checkpoints found for this mapTimeId {RunID}.", + nameof(PersonalBest), methodName, this.ID + ); #endif return; } -#if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Checkpoints found for this mapTimeId"); -#endif - while (results.Read()) { #if DEBUG - Console.WriteLine($"cp {results.GetInt32("cp")} "); - Console.WriteLine($"run_time {results.GetInt32("run_time")} "); - Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); - Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading Checkpoint: Checkpoint {Checkpoint} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}", + nameof(PersonalBest), methodName, results.GetInt32("cp"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") + ); #endif Checkpoint cp = new(results.GetInt32("cp"), @@ -131,16 +139,14 @@ public async Task PB_LoadCheckpointsData() // To-do: cp.ID = calculate Rank # from DB this.Checkpoints[cp.CP] = cp; - -#if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> Loaded CP {cp.CP} with RunTime {cp.Ticks}."); -#endif } } } // #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadCheckpointsData -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] {this.Checkpoints.Count} Checkpoints loaded from DB for run ID {this.ID}."); + _logger.LogInformation("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading Checkpoint: [{Type}] {TotalCheckpoints} Checkpoints loaded for run ID {RunID}.", + nameof(PersonalBest), methodName, (Config.API.GetApiOnly() ? "API" : "DB"), this.Checkpoints.Count, this.ID + ); // #endif } @@ -149,14 +155,16 @@ public async Task PB_LoadCheckpointsData() /// Should be used to reload data from a specific `PersonalBest` object /// /// Player object - public async Task PB_LoadPlayerSpecificMapTimeData(Player player) + public async Task PB_LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") { // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) if (this == null) { #if DEBUG - Console.WriteLine("CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null."); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null.", + nameof(PersonalBest), methodName + ); #endif return; @@ -180,23 +188,20 @@ public async Task PB_LoadPlayerSpecificMapTimeData(Player player) if (results == null || !results.HasRows) { // #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{player.Profile.Name}' ({player.Profile.ID}). (Null? {results == null})"); + _logger.LogTrace("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{playerName}' ({playerID}). (Results Null? {IsNull})", + nameof(PersonalBest), methodName, player.Profile.Name, player.Profile.ID, results == null + ); // #endif return; } -#if DEBUG - Console.WriteLine($"======== CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Found a run for '{player.Profile.Name}' ({player.Profile.ID})."); -#endif - while (results.Read()) { #if DEBUG - Console.WriteLine($"cp {results.GetInt32("cp")} "); - Console.WriteLine($"run_time {results.GetInt32("run_time")} "); - Console.WriteLine($"sVelX {results.GetFloat("start_vel_x")} "); - Console.WriteLine($"sVelY {results.GetFloat("start_vel_y")} "); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> Loading MapTime Run: RunID {RunID} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}.", + nameof(PersonalBest), methodName, results.GetInt32("id"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") + ); #endif this.ID = results.GetInt32("id"); @@ -209,14 +214,12 @@ public async Task PB_LoadPlayerSpecificMapTimeData(Player player) this.EndVelY = (float)results.GetDouble("end_vel_y"); this.EndVelZ = (float)results.GetDouble("end_vel_z"); this.RunDate = results.GetInt32("run_date"); - -#if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> Loaded run (MapId = {this.ID}) by {player.Profile.Name} - {PlayerHUD.FormatTime(this.Ticks)}"); -#endif } // #if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> MapTime (Type: {this.Type}) loaded from DB. ID: {this.ID} for {player.Profile.Name}"); + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> MapTime ID {ID} (Type: {Type}) loaded for '{PlayerName}' with time {RunTime}", + nameof(PersonalBest), methodName, this.ID, this.Type, player.Profile.Name, PlayerHUD.FormatTime(this.Ticks) + ); // #endif } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index c0586c1..ae9c647 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -1,4 +1,6 @@ -using MySqlConnector; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -37,11 +39,15 @@ internal class PlayerStats /// This object tracks data for the Player's current run. /// public CurrentRun ThisRun { get; set; } = new CurrentRun(); + private readonly ILogger _logger; // Initialize PersonalBest for each `style` (e.g., 0 for normal) // Here we can loop through all available styles at some point and initialize them - public PlayerStats() + public PlayerStats([CallerMemberName] string methodName = "") { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + // Initialize MapPB for each style foreach (int style in Config.Styles) { @@ -61,16 +67,20 @@ public PlayerStats() this.StagePB[i][0].Type = 2; initialized++; } - Console.WriteLine($"====== INITIALIZED {initialized} STAGES AND BONUSES FOR PLAYERSTATS"); + _logger.LogTrace("[{ClassName}] {MethodName} -> PlayerStats -> Initialized {initialized} Stages and Bonuses", + nameof(PlayerStats), methodName, initialized + ); } // API - public async void LoadMapTime(Player player, int style = 0) + public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=0"); if (player_maptime == null) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No MapTime data found for Player."); + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadMapTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID + ); return; } @@ -88,7 +98,9 @@ public async void LoadMapTime(Player player, int style = 0) if (player_maptime.checkpoints == null) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadMapTime -> No Checkpoints data found for Player."); + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadMapTime -> No Checkpoints data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID + ); return; } @@ -99,12 +111,14 @@ public async void LoadMapTime(Player player, int style = 0) } // API - public async void LoadStageTime(Player player, int style = 0) + public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=2"); if (player_maptime == null) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadStageTime -> No MapTime data found for Player."); + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadStageTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID + ); return; } @@ -125,12 +139,14 @@ public async void LoadStageTime(Player player, int style = 0) } // API - public async void LoadBonusTime(Player player, int style = 0) + public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=1"); if (player_maptime == null) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> LoadBonusTime -> No MapTime data found for Player."); + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadBonusTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID + ); return; } @@ -156,7 +172,7 @@ public async void LoadBonusTime(Player player, int style = 0) /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. /// This populates all the `style` and `type` stats the player has for the map /// - public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0) + public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") { using (var playerStats = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) @@ -165,7 +181,9 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma int style; if (!playerStats.HasRows) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> No MapTimes data found for Player ({player.Profile.ID})."); + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> No MapTimes data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID + ); return; } while (playerStats.Read()) @@ -174,7 +192,9 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma if (playerStats.GetInt32("type") == 1) // Bonus time { #if DEBUG - Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> BonusPB"); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", + nameof(PlayerStats), methodName + ); #endif int bonusNum = playerStats.GetInt32("stage"); style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented @@ -193,7 +213,9 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma else if (playerStats.GetInt32("type") == 2) // Stage time { #if DEBUG - Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> StagePB"); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", + nameof(PlayerStats), methodName + ); #endif int stageNum = playerStats.GetInt32("stage"); style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented @@ -208,14 +230,17 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); - Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: - StagePB[{stageNum}][{style}] = - Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}"); + // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: + // StagePB[{stageNum}][{style}] = + // Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}" + // ); } else // Map time { #if DEBUG - Console.WriteLine("DEBUG >> (func) LoadPlayerMapTimesData >> MapPB"); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", + nameof(PlayerStats), methodName + ); #endif style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented PB[style].ID = playerStats.GetInt32("id"); @@ -229,14 +254,17 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma PB[style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); PB[style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); PB[style].RunDate = playerStats.GetInt32("run_date"); - Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: - PB[{style}] = - ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}"); + // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: + // PB[{style}] = + // ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}" + // ); await this.PB[style].PB_LoadCheckpointsData(); } // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 -> public async Task LoadPlayerMapTimesData -> PlayerStats.PB (ID {PB[style].ID}) loaded from DB."); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> Loaded PB[{Style}] run {RunID} (Rank {Rank}) for '{PlayerName}' (ID {PlayerID}).", + nameof(PlayerStats), methodName, style, PB[style].ID, PB[style].Rank, player.Profile.Name, player.Profile.ID + ); #endif } } diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 6430f07..b7eba1e 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -1,6 +1,9 @@ +using System.Runtime.CompilerServices; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -51,6 +54,15 @@ internal class ReplayPlayer public CCSPlayerController? Controller { get; set; } + private readonly ILogger _logger; + + // Constructor + internal ReplayPlayer() + { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + } + public void ResetReplay() { this.CurrentFrameTick = 0; @@ -76,42 +88,54 @@ public void Reset() this.Controller = null; } - public void SetController(CCSPlayerController c, int repeat_count = -1) + public void SetController(CCSPlayerController c, int repeat_count = -1, [CallerMemberName] string methodName = "") { this.Controller = c; if (repeat_count != -1) this.RepeatCount = repeat_count; this.IsPlayable = true; - // Console.WriteLine($"===== public void SetController -> Set controller for {c.PlayerName}"); + _logger.LogTrace("[{ClassName}] {MethodName} -> Set controller for {PlayerName}", + nameof(ReplayPlayer), methodName, c.PlayerName + ); } - public void Start() + public void Start([CallerMemberName] string methodName = "") { if (!this.IsPlayable || !this.IsEnabled) return; + this.FormatBotName(); this.IsPlaying = true; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Start() -> Starting replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Starting replay for run {MapTimeID} (Map ID {MapID}) - {RecordPlayerName} (Stage {Stage})", + nameof(ReplayPlayer), methodName, this.MapTimeID, this.MapID, this.RecordPlayerName, this.Stage + ); +#endif } - public void Stop() + public void Stop([CallerMemberName] string methodName = "") { this.IsPlaying = false; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Stop() -> Stopping replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Stopping replay for run {MapTimeID} (Map ID {MapID}) - {RecordPlayerName} (Stage {Stage})", + nameof(ReplayPlayer), methodName, this.MapTimeID, this.MapID, this.RecordPlayerName, this.Stage + ); +#endif } - public void Pause() + public void Pause([CallerMemberName] string methodName = "") { if (!this.IsPlaying || !this.IsEnabled) return; this.IsPaused = !this.IsPaused; this.IsReplayOutsideZone = !this.IsReplayOutsideZone; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void Pause() -> Pausing replay for run {this.MapTimeID} (Map ID {this.MapID}) - {this.RecordPlayerName} (Stage {this.Stage})"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Pausing replay for run {MapTimeID} (Map ID {MapID}) - {RecordPlayerName} (Stage {Stage})", + nameof(ReplayPlayer), methodName, this.MapTimeID, this.MapID, this.RecordPlayerName, this.Stage + ); +#endif } public void Tick() @@ -121,6 +145,8 @@ public void Tick() ReplayFrame current_frame = this.Frames[this.CurrentFrameTick]; + this.FormatBotName(); + // SOME BLASHPEMY FOR YOU if (this.FrameTickIncrement >= 0) { @@ -180,7 +206,7 @@ public void Tick() // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> Tick -> ====================> {this.RepeatCount} <===================="); } - public void LoadReplayData(int repeat_count = -1) + public void LoadReplayData(int repeat_count = -1, [CallerMemberName] string methodName = "") { if (!this.IsPlayable || !this.IsEnabled) return; @@ -189,7 +215,9 @@ public void LoadReplayData(int repeat_count = -1) if (this.MapID == -1) { - Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] No replay data found for Player."); + _logger.LogTrace("[{ClassName}] {MethodName} -> [{Type}] No replay data found for Player.", + nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay") + ); return; } @@ -214,9 +242,9 @@ public void FormatBotName() } if (this.Type == 1) - prefix = prefix + $"B {this.Stage}"; + prefix += $"B {this.Stage}"; else if (this.Type == 2) - prefix = prefix + $"CP {this.Stage}"; + prefix += $"CP {this.Stage}"; SchemaString bot_name = new SchemaString(this.Controller!, "m_iszPlayerName"); diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 61e0a8c..8fbf2c3 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -1,9 +1,20 @@ +using System.Runtime.CompilerServices; using System.Text.Json; -using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + namespace SurfTimer; internal class ReplayRecorder { + private readonly ILogger _logger; + + public ReplayRecorder() + { + // Resolve the logger instance from the DI container + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + } + public bool IsRecording { get; set; } = false; public bool IsSaving { get; set; } = false; public ReplayFrameSituation CurrentSituation { get; set; } = ReplayFrameSituation.NONE; @@ -23,7 +34,7 @@ internal class ReplayRecorder /// public List BonusSituations { get; set; } = new List(); - public void Reset() + public void Reset([CallerMemberName] string methodName = "") { this.IsRecording = false; this.Frames.Clear(); @@ -34,30 +45,49 @@ public void Reset() this.MapSituations.Clear(); this.BonusSituations.Clear(); - Console.WriteLine($"===== ReplayRecorder -> Reset() -> Recording has been reset"); +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Recording has been reset", + nameof(ReplayRecorder), methodName + ); +#endif } - public void Start() + public void Start([CallerMemberName] string methodName = "") { this.IsRecording = true; + +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Recording has been started", + nameof(ReplayRecorder), methodName + ); +#endif } - public void Stop() + public void Stop([CallerMemberName] string methodName = "") { this.IsRecording = false; + +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> Recording has been stopped", + nameof(ReplayRecorder), methodName + ); +#endif } - public void Tick(Player player) + public void Tick(Player player, [CallerMemberName] string methodName = "") { if (!this.IsRecording || player == null) return; // Disabling Recording if timer disabled - if (!player.Timer.IsEnabled) + // if (!player.Timer.IsEnabled) + if (!player.Timer.IsEnabled && !player.ReplayRecorder.IsSaving) { this.Stop(); this.Reset(); - Console.WriteLine($"===== ReplayRecorder -> Tick() -> Recording has stopped and reset"); + _logger.LogTrace("[{ClassName}] {MethodName} -> Recording has stopped and reset for player {Name}", + nameof(ReplayRecorder), methodName, player.Profile.Name + ); return; } @@ -66,29 +96,29 @@ public void Tick(Player player) 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; -/* - switch (this.CurrentSituation) - { - case ReplayFrameSituation.START_ZONE_ENTER: - player.Controller.PrintToChat($"Start Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.START_ZONE_EXIT: - player.Controller.PrintToChat($"Start Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: - player.Controller.PrintToChat($"Stage Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - player.Controller.PrintToChat($"Stage Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - player.Controller.PrintToChat($"Checkpoint Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - player.Controller.PrintToChat($"Checkpoint Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - } -*/ + /* + switch (this.CurrentSituation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + player.Controller.PrintToChat($"Start Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.Controller.PrintToChat($"Start Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.Controller.PrintToChat($"Stage Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.Controller.PrintToChat($"Stage Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.Controller.PrintToChat($"Checkpoint Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.Controller.PrintToChat($"Checkpoint Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); + break; + } + */ var frame = new ReplayFrame { pos = [player_pos.X, player_pos.Y, player_pos.Z], @@ -127,167 +157,252 @@ public void SetLastTickSituation(ReplayFrameSituation situation) this.Frames[this.Frames.Count - 2].Situation = situation; } - public string TrimReplay(Player player, int type = 0, bool lastStage = false) + public string TrimReplay(Player player, int type = 0, bool lastStage = false, [CallerMemberName] string methodName = "") { this.IsSaving = true; List new_frames = new List(); + _logger.LogTrace(">>> [{ClassName}] {MethodName} -> Trimming replay for '{PlayerName}' | type = {type} | lastStage = {lastStage} ", + nameof(ReplayRecorder), methodName, player.Profile.Name, type, lastStage + ); + if (this.Frames.Count == 0) { - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> There are no Frames for trimming"); + _logger.LogError("[{ClassName}] {MethodName} -> There are no Frames available for trimming for player {Name}", + nameof(ReplayRecorder), methodName, player.Profile.Name + ); throw new Exception("There are no Frames available for trimming"); } - switch (type) - { - case 0: // Trim Map replays - // Map/Bonus runs - var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Map Run replay. Last start enter {start_enter_index}, last start exit {start_exit_index}, end enter {end_enter_index}"); - - if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) - { - // Try different buffer sizes for start index - int startIndex; - if (start_exit_index - (Config.ReplaysPre * 2) >= start_enter_index) - startIndex = start_exit_index - (Config.ReplaysPre * 2); - else if (start_exit_index - Config.ReplaysPre >= start_enter_index) - startIndex = start_exit_index - Config.ReplaysPre; - else if (start_exit_index - (Config.ReplaysPre / 2) >= start_enter_index) - startIndex = start_exit_index - (Config.ReplaysPre / 2); - else - startIndex = start_enter_index; // fallback to minimum allowed - - // Try different buffer sizes for end index - int endIndex; - if (end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = end_enter_index + (Config.ReplaysPre * 2); - else if (end_enter_index + Config.ReplaysPre < Frames.Count) - endIndex = end_enter_index + Config.ReplaysPre; - else if (end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = end_enter_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = end_enter_index; // fallback to maximum allowed - - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); - } - break; - case 1: // Trim Bonus replays - // Bonus runs - int bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - int bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - int bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Bonus Run replay. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}"); - - if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) - { - // Try different buffer sizes for start index - int startIndex; - if (bonus_exit_index - (Config.ReplaysPre * 2) >= bonus_enter_index) - startIndex = bonus_exit_index - (Config.ReplaysPre * 2); - else if (bonus_exit_index - Config.ReplaysPre >= bonus_enter_index) - startIndex = bonus_exit_index - Config.ReplaysPre; - else if (bonus_exit_index - (Config.ReplaysPre / 2) >= bonus_enter_index) - startIndex = bonus_exit_index - (Config.ReplaysPre / 2); - else - startIndex = bonus_enter_index; // fallback to minimum allowed - - // Try different buffer sizes for end index - int endIndex; - if (bonus_end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = bonus_end_enter_index + (Config.ReplaysPre * 2); - else if (bonus_end_enter_index + Config.ReplaysPre < Frames.Count) - endIndex = bonus_end_enter_index + Config.ReplaysPre; - else if (bonus_end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = bonus_end_enter_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = bonus_end_enter_index; // fallback to maximum allowed - - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); - } - break; - case 2: // Trim Stage replays - int stage_end_index; - int stage_exit_index; - int stage_enter_index; - - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Will trim Stage Run replay. Stage {player.Timer.Stage - 1}, available frames {Frames.Count}"); - - // Stage runs - if (lastStage) // Last stage - { - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); // Last stage enter (finishing the stage) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); - } - else if (player.Timer.Stage - 1 > 1) // Not first stage - { - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `STAGE_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); - } - else // First stage is always the start of the map so we are looking for START_ZONE_X - { - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Stage replay trimming will use `START_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_end_index = {stage_end_index}"); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_exit_index = {stage_exit_index}"); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> stage_enter_index = {stage_enter_index}"); - } - - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimming Stage Run replay. Stage {player.Timer.Stage - 1} enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}"); - - if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) - { - // Try different buffer sizes for start index - int startIndex; - if (stage_exit_index - (Config.ReplaysPre * 2) >= stage_enter_index) - startIndex = stage_exit_index - (Config.ReplaysPre * 2); - else if (stage_exit_index - Config.ReplaysPre >= stage_enter_index) - startIndex = stage_exit_index - Config.ReplaysPre; - else if (stage_exit_index - (Config.ReplaysPre / 2) >= stage_enter_index) - startIndex = stage_exit_index - (Config.ReplaysPre / 2); - else - startIndex = stage_enter_index; // fallback to minimum allowed - - // Try different buffer sizes for end index - int endIndex; - if (stage_end_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = stage_end_index + (Config.ReplaysPre * 2); - else if (stage_end_index + Config.ReplaysPre < Frames.Count) - endIndex = stage_end_index + Config.ReplaysPre; - else if (stage_end_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = stage_end_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = stage_end_index; // fallback to maximum allowed - - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); - Console.WriteLine($"======== internal class ReplayRecorder -> public string TrimReplay -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames.Count}) - from total {this.Frames.Count}"); - } - break; - } + switch (type) + { + case 0: // Trim Map replays + // Map/Bonus runs + var start_enter_index = MapSituations[0]; + var start_exit_index = MapSituations[1]; + var end_enter_index = MapSituations[2]; + // var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // This comes out as `-1` somehow, need to edit the IF statement below to use the `start_exit_index` in that case and not care about this variable + // var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + // var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", + nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index + ); + + if (start_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for start_enter_index during Map replay trimming. Setting 'start_enter_index' to '0' | IsStageMode = {StageMode} | IsBonusMode = {BonusMode}", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode + ); + start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; + // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; + } + + if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (start_exit_index - (Config.ReplaysPre * 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre * 2); + else if (start_exit_index - Config.ReplaysPre >= start_enter_index) + startIndex = start_exit_index - Config.ReplaysPre; + else if (start_exit_index - (Config.ReplaysPre / 2) >= start_enter_index) + startIndex = start_exit_index - (Config.ReplaysPre / 2); + else + startIndex = start_enter_index; // fallback to minimum allowed + // Try different buffer sizes for end index + int endIndex; + if (end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre * 2); + else if (end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = end_enter_index + Config.ReplaysPre; + else if (end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = end_enter_index; // fallback to maximum allowed + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", + nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index + ); + } + break; + case 1: // Trim Bonus replays + // Bonus runs + int bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + int bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + int bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index + ); + + if (bonus_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus + ); + bonus_enter_index = 0; + } + + if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (bonus_exit_index - (Config.ReplaysPre * 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre * 2); + else if (bonus_exit_index - Config.ReplaysPre >= bonus_enter_index) + startIndex = bonus_exit_index - Config.ReplaysPre; + else if (bonus_exit_index - (Config.ReplaysPre / 2) >= bonus_enter_index) + startIndex = bonus_exit_index - (Config.ReplaysPre / 2); + else + startIndex = bonus_enter_index; // fallback to minimum allowed + // Try different buffer sizes for end index + int endIndex; + if (bonus_end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre * 2); + else if (bonus_end_enter_index + Config.ReplaysPre < Frames.Count) + endIndex = bonus_end_enter_index + Config.ReplaysPre; + else if (bonus_end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = bonus_end_enter_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = bonus_end_enter_index; // fallback to maximum allowed + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index + ); + } + break; + case 2: // Trim Stage replays + int stage_end_index; + int stage_exit_index; + int stage_enter_index; + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Stage Run replay trim indexes. Stage {Stage}, available frames {TotalFrames}", + nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, Frames.Count + ); + // Stage runs + if (lastStage) // Last stage + { + _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); // Last stage enter (finishing the stage) + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } + else if (player.Timer.Stage - 1 > 1) // Not first stage + { + _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } + else if (player.Timer.Stage - 1 == -1) // Don't crash...? + { + _logger.LogError(">>>> Stage replay trimming will abort! <<<<"); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> this.IsSaving = {IsSaving}", this.IsSaving); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsRunning = {IsRunning}", player.Timer.IsRunning); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsEnabled = {IsEnabled}", player.Timer.IsEnabled); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Stage = {Stage}", player.Timer.Stage); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Ticks = {Ticks}", player.Timer.Ticks); + stage_enter_index = -1; + stage_end_index = -1; + stage_exit_index = -1; + } + else // First stage is always the start of the map so we are looking for START_ZONE_X + { + _logger.LogInformation("Stage replay trimming will use `START_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", + nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index + ); + + if (stage_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for stage_enter_index during Stage ({StageNumber}) replay trimming. Setting 'stage_enter_index' to '0'", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Stage + ); + stage_enter_index = 0; + } + + if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) + { + // Try different buffer sizes for start index + int startIndex; + if (stage_exit_index - (Config.ReplaysPre * 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre * 2); + else if (stage_exit_index - Config.ReplaysPre >= stage_enter_index) + startIndex = stage_exit_index - Config.ReplaysPre; + else if (stage_exit_index - (Config.ReplaysPre / 2) >= stage_enter_index) + startIndex = stage_exit_index - (Config.ReplaysPre / 2); + else + startIndex = stage_enter_index; // fallback to minimum allowed + // Try different buffer sizes for end index + int endIndex; + if (stage_end_index + (Config.ReplaysPre * 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre * 2); + else if (stage_end_index + Config.ReplaysPre < Frames.Count) + endIndex = stage_end_index + Config.ReplaysPre; + else if (stage_end_index + (Config.ReplaysPre / 2) < Frames.Count) + endIndex = stage_end_index + (Config.ReplaysPre / 2); + else + // endIndex = Frames.Count - 1; // fallback to maximum allowed + endIndex = stage_end_index; // fallback to maximum allowed + // Get the range of frames + new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Stage ({StageNumber}) replay for '{Name}'. stage_enter_index = {stage_enter_index} | stage_exit_index = {stage_exit_index} | stage_end_index = {stage_end_index}", + nameof(ReplayRecorder), methodName, player.Timer.Stage, player.Profile.Name, stage_enter_index, stage_exit_index, stage_end_index + ); + } + break; + } this.IsSaving = false; string trimmed = JsonSerializer.Serialize(new_frames); diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs new file mode 100644 index 0000000..94ff0bb --- /dev/null +++ b/src/ST-UTILS/Injection.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using CounterStrikeSharp.API.Core; +using Serilog; +using Serilog.Events; +using CounterStrikeSharp.API; + +namespace SurfTimer; + +public class Injection : IPluginServiceCollection +{ + private static readonly string LogDirectory = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/logs"; + + public void ConfigureServices(IServiceCollection services) + { + var fileName = $"log-SurfTimer-.txt"; // Date seems to be automatically appended so we leave it out + var filePath = Path.Combine(LogDirectory, fileName); + + // Configure Serilog + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Console() + .WriteTo.File( + path: filePath, + restrictedToMinimumLevel: LogEventLevel.Verbose, + rollingInterval: RollingInterval.Day + ) + .CreateLogger(); + + // Show the full path to the log file + Console.WriteLine($"[SurfTimer] Logging to file: {filePath}"); + Log.Information("[SurfTimer] Logging to file: {LogFile}", filePath); + + // Register Serilog as a logging provider for Microsoft.Extensions.Logging + services.AddLogging(builder => + { + builder.ClearProviders(); + builder.AddSerilog(dispose: true); + }); + + // Register Dependencies + services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players + services.AddSingleton(); // Single instance for 1 Map object + } +} + +/* +public class Injection : IPluginServiceCollection +{ + public void ConfigureServices(IServiceCollection serviceCollection) + { + // Register Logging + serviceCollection.AddLogging(builder => + { + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Debug); + }); + + + // Register Dependencies + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddSingleton(); // Single instance for 1 Map object + } +} +*/ \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index d0517a9..9a42bef 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -29,6 +29,7 @@ You should have received a copy of the GNU Affero General Public License using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes.Registration; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -36,6 +37,16 @@ namespace SurfTimer; [MinimumApiVersion(120)] public partial class SurfTimer : BasePlugin { + private readonly ILogger _logger; + public static IServiceProvider ServiceProvider { get; private set; } = null!; + + // Inject ILogger and store IServiceProvider globally + public SurfTimer(ILogger logger, IServiceProvider serviceProvider) + { + _logger = logger; + ServiceProvider = serviceProvider; + } + // Metadata public override string ModuleName => $"CS2 {Config.PluginName}"; public override string ModuleVersion => "DEV-1"; @@ -58,10 +69,10 @@ public void OnMapStart(string mapName) + " / ___/ __/_ | / __/_ ______/ _/\n" + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" + "\\___/___/____/ /___/\\_,_/_/ /_/\n" - + $"[CS2 Surf] {Config.PluginName} {ModuleVersion} - loading map {mapName}.\n" - + $"[CS2 Surf] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" - + $"[CS2 Surf] ---> Source Code: https://github.com/CS2Surf/Timer\n" - + $"[CS2 Surf] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" + + $"[{Config.PluginName}] v.{ModuleVersion} - loading map {mapName}.\n" + + $"[{Config.PluginName}] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" + + $"[{Config.PluginName}] ---> Source Code: https://github.com/CS2Surf/Timer\n" + + $"[{Config.PluginName}] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" ))); Server.NextWorldUpdate(async () => CurrentMap = await Map.CreateAsync(mapName)); // NextWorldUpdate runs even during server hibernation @@ -86,7 +97,9 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) ConVarHelper.RemoveCheatFlagFromConVar("bot_zombie"); Server.ExecuteCommand("execifexists SurfTimer/server_settings.cfg"); - Console.WriteLine("[CS2 Surf] Executed configuration: server_settings.cfg"); + _logger.LogTrace("[{Prefix}] Executed configuration: server_settings.cfg", + Config.PluginName + ); return HookResult.Continue; } @@ -96,21 +109,29 @@ public override void Load(bool hotReload) // Check if we have connected to the Database if (DB != null) { - Console.WriteLine("[CS2 Surf] Database connection established."); + _logger.LogInformation("[{Prefix}] Database connection established.", + Config.PluginName + ); } else { - Console.WriteLine($"[CS2 Surf] Error connecting to the database."); + _logger.LogCritical("[{Prefix}] Error connecting to the database.", + Config.PluginName + ); // To-do: Abort plugin loading } - Console.WriteLine(String.Format(" ____________ ____ ___\n" - + " / ___/ __/_ | / __/_ ______/ _/\n" - + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" - + "\\___/___/____/ /___/\\_,_/_/ /_/\n" - + $"[CS2 Surf] {Config.PluginName} plugin loaded. Version: {ModuleVersion}\n" - + $"[CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. Source code: https://github.com/CS2Surf/Timer\n" - )); + _logger.LogInformation(""" + + ____________ ____ ___ + / ___/ __/_ | / __/_ ______/ _/ + / /___\ \/ __/ _\ \/ // / __/ _/ + \___/___/____/ /___/\_,_/_/ /_/ + [CS2 Surf] {PluginName} plugin loaded. Version: {ModuleVersion} + [CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. + Source code: https://github.com/CS2Surf/Timer + """, Config.PluginName, ModuleVersion + ); // Map Start Hook RegisterListener(OnMapStart); diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 82404c6..35c78ef 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -16,6 +16,10 @@ + + + + From 2ed468c257ae30c2f5b5db205512c956e74f91e2 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 28 May 2025 22:36:45 +0300 Subject: [PATCH 14/52] Add Localization; Move chat messages to lang file --- lang/en.json | 38 ++++++++++++- src/ST-Commands/PlayerCommands.cs | 87 ++++++++++++++++++++++-------- src/ST-Events/Players.cs | 4 +- src/ST-Events/TriggerStartTouch.cs | 67 ++++++++++++++++------- src/ST-Player/PlayerHUD.cs | 9 ++-- src/ST-UTILS/Config.cs | 64 ++++++++++++++++++++-- src/ST-UTILS/Injection.cs | 13 +++++ src/SurfTimer.cs | 4 +- 8 files changed, 232 insertions(+), 54 deletions(-) diff --git a/lang/en.json b/lang/en.json index 544b7b4..8d07d50 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,3 +1,39 @@ { - + "prefix": "[{white}asd{green}asd{red}asd{default}]", + + "player_connected": "{green}{0}{default} has connected from {lime}{1}{default}.", + + /* PlayerCommands */ + "reset_delay": "Please wait for your run to be saved before resetting.", + "invalid_usage": "{red}Invalid arguments. {default}Usage: {green}{0}", + "invalid_stage_value": "{red}Invalid stage provided. {default}this map has {green}{0}{default} stages.", + "not_staged": "{red}This map has no stages.", + "invalid_bonus_value": "{red}Invalid bonus provided. {default}this map has {green}{0}{default} bonuses.", + "not_bonused": "{red}This map has no bonuses.", + "rank":"Your current rank for {gold}{0}{default} is {green}{1}{default} out of {yellow}{2}", + "saveloc_not_in_run": "{red}Cannot save location while not in run.", + "saveloc_saved": "{green}Saved location! {default}Use {green}!tele {0}{default} to teleport to this location.", + "saveloc_no_locations": "{red}No saved locations.", + "saveloc_practice": "{red}Timer now in Practice.", + "saveloc_teleported": "Teleported to #{green}{0}", + "saveloc_first": "{red}Already at first location", + "saveloc_last": "{red}Already at last location", + + /* TriggerStartTouch */ + "stagewr_set": "{lime}{0}{default} set the first {yellow}Stage {1}{default} record at {gold}{2}{default}!", + "stagewr_improved": "{lime}{0}{default} has set a new {yellow}Stage {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "stagewr_missed": "You are behind the {yellow}Stage {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", + "stagepb_set": "You finished {yellow}Stage {0}{default} in {gold}{1}{ChatColors.Default}, setting your new Personal Best!", + "stagepb_improved": "{lime}{0}{default} beat their {yellow}Stage {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "mapwr_set": "{lime}{0}{default} set the first {yellow}Map{default} record at {gold}{1}{default}!", + "mapwr_improved": "{lime}{0}{default} has set a new {yellow}Map{default} record with a time of {gold}{1}{default}, beating the old record by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", + "mappb_set": "You finished the {yellow}Map{default} in {gold}{0}{default}, setting your new Personal Best!", + "mappb_improved": "{lime}{0}{default} beat their {yellow}Map{default} Personal Best with a time of {gold}{1}{default}, improving by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", + "mappb_missed": "You finished the map in {yellow}{0}{default}!", + "bonuswr_set": "{lime}{0}{default} set the first {yellow}Bonus {1}{default} record at {gold}{2}{default}!", + "bonuswr_improved": "{lime}{0}{default} has set a new {yellow}Bonus {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "bonuspb_set": "You finished {yellow}Bonus {0}{default} in {gold}{1}{ChatColors.Default}, setting your new Personal Best!", + "bonuspb_improved": "{lime}{0}{default} beat their {yellow}Bonus {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "bonuspb_missed": "You are behind the {yellow}Bonus {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", + "checkpoint_message": "CP [{yellow}{0}{default}]: {yellow}{1}{default} {yellow}({2}){default} [PB: {3} | WR: {4}]", } \ No newline at end of file diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 3f6bb58..b739fb5 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -18,9 +18,11 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) Player oPlayer = playerList[player.UserId ?? 0]; if (oPlayer.ReplayRecorder.IsSaving) { - player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); return; } + + // oPlayer.ReplayRecorder.Reset(); // To-do: players[userid].Timer.Reset() -> teleport player playerList[player.UserId ?? 0].Timer.Reset(); if (CurrentMap.StartZone != new Vector(0, 0, 0)) @@ -37,7 +39,7 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) Player oPlayer = playerList[player.UserId ?? 0]; if (oPlayer.ReplayRecorder.IsSaving) { - player.PrintToChat($"{Config.PluginPrefix} Please wait for your run to be saved before resetting."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); return; } @@ -74,7 +76,9 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) } catch (System.Exception) { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!s "]}" + ); return; } @@ -85,19 +89,23 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"CS2 Surf DEBUG >> css_stage >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); #endif - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!s "]}" + ); return; } else if (CurrentMap.Stages <= 0) { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no stages."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["not_staged"]}"); return; } else if (stage > CurrentMap.Stages) { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided, this map has {ChatColors.Green}{CurrentMap.Stages} stages."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_stage_value", + CurrentMap.Stages]}" + ); return; } @@ -121,7 +129,9 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) } else - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid stage provided. Usage: {ChatColors.Green}!s "); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!s "]}" + ); } [ConsoleCommand("css_b", "Teleport to a bonus")] @@ -141,7 +151,9 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) bonus = 1; else { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid arguments. Usage: {ChatColors.Green}!bonus "); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!b "]}" + ); return; } } @@ -151,13 +163,15 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) if (CurrentMap.Bonuses <= 0) { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}This map has no bonuses."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["not_bonused"]}"); return; } else if (bonus > CurrentMap.Bonuses) { - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided, this map has {ChatColors.Green}{CurrentMap.Bonuses} bonuses."); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_bonus_value", + CurrentMap.Bonuses]}" + ); return; } @@ -170,7 +184,9 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) } else - player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Invalid bonus provided. Usage: {ChatColors.Green}!bonus "); + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!b "]}" + ); } [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] @@ -189,7 +205,24 @@ public void PlayerRank(CCSPlayerController? player, CommandInfo command) if (player == null) return; - player.PrintToChat($"{Config.PluginPrefix} Your current rank for {ChatColors.Gold}{CurrentMap.Name}{ChatColors.Default} is {ChatColors.Green}{playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank}{ChatColors.Default} out of {ChatColors.Yellow}{playerList[player.UserId ?? 0].CurrMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]}"); + int pRank = playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank; + int tRank = playerList[player.UserId ?? 0].CurrMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]; + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["rank", + CurrentMap.Name, pRank, tRank]}" + ); + } + + [ConsoleCommand("css_rx", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + oPlayer.Stats.ThisRun.PrintSituations(oPlayer); } [ConsoleCommand("css_testx", "x")] @@ -440,7 +473,7 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) Player p = playerList[player.UserId ?? 0]; if (!p.Timer.IsRunning) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Cannot save location while not in run"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_not_in_run"]}"); return; } @@ -457,7 +490,9 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) }); p.CurrentSavedLocation = p.SavedLocations.Count - 1; - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Green}Saved location! {ChatColors.Default} use !tele {p.SavedLocations.Count - 1} to teleport to this location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_saved", + p.SavedLocations.Count - 1]}" + ); } [ConsoleCommand("css_tele", "Teleport player to current saved location")] @@ -470,7 +505,7 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_no_locations"]}"); return; } @@ -479,7 +514,7 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm if (!p.Timer.IsPracticeMode) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Timer now on practice"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_practice"]}"); p.Timer.IsPracticeMode = true; } @@ -502,7 +537,9 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm p.Timer.Ticks = location.Tick; }); - p.Controller.PrintToChat($"{Config.PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_teleported", + p.CurrentSavedLocation]}" + ); } [ConsoleCommand("css_teleprev", "Teleport player to previous saved location")] @@ -515,13 +552,13 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_no_locations"]}"); return; } if (p.CurrentSavedLocation == 0) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at first location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_first"]}"); } else { @@ -530,7 +567,9 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{Config.PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_teleported", + p.CurrentSavedLocation]}" + ); } [ConsoleCommand("css_telenext", "Teleport player to next saved location")] @@ -543,13 +582,13 @@ public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo if (p.SavedLocations.Count == 0) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}No saved locations"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_no_locations"]}"); return; } if (p.CurrentSavedLocation == p.SavedLocations.Count - 1) { - p.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Already at last location"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_last"]}"); } else { @@ -558,6 +597,8 @@ public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo TeleportPlayerLocation(player, command); - p.Controller.PrintToChat($"{Config.PluginPrefix} Teleported #{p.CurrentSavedLocation}"); + p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_teleported", + p.CurrentSavedLocation]}" + ); } } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index a554522..13c016e 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -126,7 +126,9 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn playerList[player.UserId ?? 0].Stats.LoadPlayerMapTimesData(playerList[player.UserId ?? 0]).GetAwaiter().GetResult(); // Holds here until result is available // Print join messages - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Green}{name}{ChatColors.Default} has connected from {ChatColors.Lime}{playerList[player.UserId ?? 0].Profile.Country}{ChatColors.Default}."); + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["player_connected", + name, country]}" + ); _logger.LogTrace("[{Prefix}] {PlayerName} has connected from {Country}.", Config.PluginName, name, playerList[player.UserId ?? 0].Profile.Country ); diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index d73d394..e90722f 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -76,27 +76,37 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { saveMapTime = true; int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Map{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" + ); } else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map { saveMapTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Map{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map { saveMapTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Map{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map { saveMapTime = true; int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Map{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); } else // Player did not beat their existing PersonalBest for the map nor the map record { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the map in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } if (saveMapTime) @@ -278,7 +288,8 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); player.Timer.Reset(); player.Stats.ThisRun.Checkpoints.Clear(); player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); @@ -289,7 +300,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn } else { - player.Controller.PrintToChat($"{Config.PluginPrefix} {ChatColors.Yellow}Please try restarting again, replay is still being saved."); + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); } } @@ -435,7 +446,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // Bonus start zones -- hook into (b)onus#_start 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); player.Timer.Bonus = bonus; @@ -481,27 +492,37 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { saveBonusTime = true; int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" + ); } else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map { saveBonusTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus { saveBonusTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished the {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, setting your new Personal Best!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus { saveBonusTime = true; int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); } else // Player did not beat their existing personal best for the bonus { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}You finished {ChatColors.Yellow}Bonus {bonus_idx}{ChatColors.Default} in {ChatColors.Yellow}{PlayerHUD.FormatTime(player.Timer.Ticks)}{ChatColors.Default}!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); } // To-do: save to DB @@ -566,20 +587,28 @@ void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR { int timeImprove = CurrentMap.StageWR[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} has set a new {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, beating the old record by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_improved", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)]}" + ); } else if (CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map { - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} set the first {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record at {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}!"); + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_set", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time)]}" + ); } else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best { - player.Controller.PrintToChat($"{Config.PluginPrefix} You finished {ChatColors.Yellow}Stage {stage}{ChatColors.Default} in {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, setting your new Personal Best!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_set", + stage, PlayerHUD.FormatTime(stage_run_time)]}" + ); } else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time) // Player beating their existing Stage personal best { int timeImprove = player.Stats.StagePB[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {ChatColors.Lime}{player.Controller.PlayerName}{ChatColors.Default} beat their {ChatColors.Yellow}Stage {stage}{ChatColors.Default} Personal Best with a time of {ChatColors.Gold}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, improving by {ChatColors.Green}-{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default}! (Previous: {ChatColors.BlueGrey}{PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].Ticks)}{ChatColors.Default})"); + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_improved", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].Ticks)]}" + ); } player.ReplayRecorder.IsSaving = true; @@ -593,7 +622,9 @@ void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool else if (stage_run_time > CurrentMap.StageWR[stage][pStyle].Ticks && player.Timer.IsStageMode) // Player is behind the Stage WR for the map { int timeImprove = stage_run_time - CurrentMap.StageWR[stage][pStyle].Ticks; - player.Controller.PrintToChat($"{Config.PluginPrefix} You are behind the {ChatColors.Yellow}Stage {stage}{ChatColors.Default} record with a time of {ChatColors.Grey}{PlayerHUD.FormatTime(stage_run_time)}{ChatColors.Default}, missing the record by {ChatColors.Red}+{PlayerHUD.FormatTime(timeImprove)}{ChatColors.Default} ({ChatColors.Gold}{PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)}{ChatColors.Default})!"); + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_missed", + stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)]}" + ); } } } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 41a3454..d204154 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -282,12 +282,9 @@ public void DisplayCheckpointMessages() } // Print checkpoint message - _player.Controller.PrintToChat( - $"{Config.PluginPrefix} CP [{ChatColors.Yellow}{playerCheckpoint}{ChatColors.Default}]: " + - $"{ChatColors.Yellow}{FormatTime(_player.Timer.Ticks)}{ChatColors.Default} " + - $"{ChatColors.Yellow}({currentSpeed.ToString("0")}){ChatColors.Default} " + - $"[PB: {strPbDifference} | " + - $"WR: {strWrDifference}]"); + _player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["checkpoint_message", + playerCheckpoint, FormatTime(_player.Timer.Ticks), currentSpeed.ToString("0"), strPbDifference, strWrDifference]}" + ); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] PB: {pbTime} - CURR: {currentTime} = pbTime: {pbTime - currentTime}"); diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index d972cb3..4018d8e 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -8,7 +8,7 @@ namespace SurfTimer; public static class Config { public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; - public static string PluginPrefix = $"[{ChatColors.DarkBlue}CS2 Surf{ChatColors.Default}]"; // To-do: make configurable + public static string PluginPrefix = LocalizationService.LocalizerNonNull["prefix"]; public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; public static string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; public static string ApiUrl => API.GetApiUrl(); @@ -21,6 +21,7 @@ public static class Config public static bool ReplaysEnabled => true; public static int ReplaysPre => 64; + // Helper class/methods for configuration loading private static class ConfigLoader { @@ -37,10 +38,23 @@ public static JsonDocument GetConfigDocument(string configPath) } } + /// + /// Values from `timer_settings.json` + /// + private static class TimerSettings + { + private const string TIMER_CONFIG_PATH = "/csgo/cfg/SurfTimer/timer_settings.json"; + private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(TIMER_CONFIG_PATH); + + public static string GetPrefix() + { + return ConfigDocument.RootElement.GetProperty("prefix").GetString()!; + } + } + public static class API { private const string API_CONFIG_PATH = "/csgo/cfg/SurfTimer/api_config.json"; - private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(API_CONFIG_PATH); /// @@ -82,7 +96,6 @@ public static class Endpoints public static class MySQL { private const string DB_CONFIG_PATH = "/csgo/cfg/SurfTimer/database.json"; - private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); /// @@ -203,4 +216,47 @@ FROM MapTimes "; } } -} \ No newline at end of file +} + + + +/* + /// + /// Replaces color codes from strings to CS# ChatColors. + /// {white} -> {ChatColors.White} + /// + /// String to replace colors + /// with 'ChatColors' + private static string ReplaceColors(string message) + { + var replacements = new Dictionary + { + { "{default}", $"{ChatColors.Default}" }, + { "{red}", $"{ChatColors.Red}" }, + { "{white}", $"{ChatColors.White}" }, + { "{darkred}", $"{ChatColors.DarkRed}" }, + { "{green}", $"{ChatColors.Green}" }, + { "{lightyellow}", $"{ChatColors.LightYellow}" }, + { "{lightblue}", $"{ChatColors.LightBlue}" }, + { "{olive}", $"{ChatColors.Olive}" }, + { "{lime}", $"{ChatColors.Lime}" }, + { "{lightpurple}", $"{ChatColors.LightPurple}" }, + { "{purple}", $"{ChatColors.Purple}" }, + { "{grey}", $"{ChatColors.Grey}" }, + { "{yellow}", $"{ChatColors.Yellow}" }, + { "{gold}", $"{ChatColors.Gold}" }, + { "{silver}", $"{ChatColors.Silver}" }, + { "{blue}", $"{ChatColors.Blue}" }, + { "{darkblue}", $"{ChatColors.DarkBlue}" }, + { "{bluegrey}", $"{ChatColors.BlueGrey}" }, + { "{magenta}", $"{ChatColors.Magenta}" }, + { "{lightred}", $"{ChatColors.LightRed}" }, + { "{orange}", $"{ChatColors.Orange}" } + }; + + foreach (var replacement in replacements) + message = message.Replace(replacement.Key, replacement.Value); + + return message; + } +*/ \ No newline at end of file diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index 94ff0bb..afbb72f 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Localization; using CounterStrikeSharp.API.Core; using Serilog; using Serilog.Events; @@ -49,6 +50,18 @@ public void ConfigureServices(IServiceCollection services) } } +public class LocalizationService +{ + // Localizer as a Singleton + public static IStringLocalizer? Localizer { get; private set; } + public static IStringLocalizer LocalizerNonNull + => Localizer!; + public static void Init(IStringLocalizer localizer) + { + Localizer = localizer; + } +} + /* public class Injection : IPluginServiceCollection { diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 9a42bef..ec7ae41 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -34,7 +34,7 @@ You should have received a copy of the GNU Affero General Public License namespace SurfTimer; // Gameplan: https://github.com/CS2Surf/Timer/tree/dev/README.md -[MinimumApiVersion(120)] +[MinimumApiVersion(318)] public partial class SurfTimer : BasePlugin { private readonly ILogger _logger; @@ -106,6 +106,8 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) /* ========== PLUGIN LOAD ========== */ public override void Load(bool hotReload) { + LocalizationService.Init(Localizer); + // Check if we have connected to the Database if (DB != null) { From 259e74e2a76a73c9012b6d7cf1ec7f73ca29b2d2 Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:46:05 -0400 Subject: [PATCH 15/52] Implemented Fix Vector Leak Replaces base CSSharp vector class with one that doesn't have a memory leak!!!!! --- src/ST-API/JsonConverters.cs | 16 ++-- src/ST-Commands/MapCommands.cs | 4 +- src/ST-Commands/PlayerCommands.cs | 34 +++---- src/ST-Events/TriggerEndTouch.cs | 51 +++++------ src/ST-Events/TriggerStartTouch.cs | 30 +++---- src/ST-Map/Map.cs | 80 ++++++++--------- src/ST-Player/Replay/ReplayFrame.cs | 15 ++-- src/ST-Player/Replay/ReplayPlayer.cs | 8 +- src/ST-Player/Saveloc/SavelocFrame.cs | 6 +- src/ST-UTILS/Extensions.cs | 46 ++++++++++ src/ST-UTILS/Structs/QAngle_t.cs | 123 +++++++++++++++++++++++++ src/ST-UTILS/Structs/Vector_t.cs | 124 ++++++++++++++++++++++++++ 12 files changed, 414 insertions(+), 123 deletions(-) create mode 100644 src/ST-UTILS/Extensions.cs create mode 100644 src/ST-UTILS/Structs/QAngle_t.cs create mode 100644 src/ST-UTILS/Structs/Vector_t.cs diff --git a/src/ST-API/JsonConverters.cs b/src/ST-API/JsonConverters.cs index 0e50238..377aacd 100644 --- a/src/ST-API/JsonConverters.cs +++ b/src/ST-API/JsonConverters.cs @@ -28,9 +28,9 @@ public static Dictionary ConstructJsonDictFromString(string str) } } -internal class VectorConverter : JsonConverter +internal class Vector_tConverter : JsonConverter { - public override Vector Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override Vector_t 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) @@ -63,10 +63,10 @@ public override Vector Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS } } - return new Vector { X = x, Y = y, Z = z }; + return new Vector_t { X = x, Y = y, Z = z }; } - public override void Write(Utf8JsonWriter writer, Vector value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, Vector_t value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("X", value.X); @@ -76,9 +76,9 @@ public override void Write(Utf8JsonWriter writer, Vector value, JsonSerializerOp } } -internal class QAngleConverter : JsonConverter +internal class QAngle_tConverter : JsonConverter { - public override QAngle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override QAngle_t 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) @@ -111,10 +111,10 @@ public override QAngle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS } } - return new QAngle { X = X, Y = Y, Z = Z }; + return new QAngle_t { X = X, Y = Y, Z = Z }; } - public override void Write(Utf8JsonWriter writer, QAngle value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, QAngle_t value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("X", value.X); diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 671a15e..14714d7 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -59,7 +59,7 @@ public void Triggers(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"Hooked Trigger -> Start -> {CurrentMap.StartZone} -> Angles {CurrentMap.StartZoneAngles}"); player.PrintToChat($"Hooked Trigger -> End -> {CurrentMap.EndZone}"); int i = 1; - foreach (Vector stage in CurrentMap.StageStartZone) + foreach (Vector_t stage in CurrentMap.StageStartZone) { if (stage.X == 0 && stage.Y == 0 && stage.Z == 0) continue; @@ -71,7 +71,7 @@ public void Triggers(CCSPlayerController? player, CommandInfo command) } i = 1; - foreach (Vector bonus in CurrentMap.BonusStartZone) + foreach (Vector_t bonus in CurrentMap.BonusStartZone) { if (bonus.X == 0 && bonus.Y == 0 && bonus.Z == 0) continue; diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index b739fb5..23dca72 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -25,8 +25,8 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) // oPlayer.ReplayRecorder.Reset(); // 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.IsZero()) + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); } [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you're in.")] @@ -46,18 +46,18 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) if (oPlayer.Timer.IsBonusMode) { - if (oPlayer.Timer.Bonus != 0 && CurrentMap.BonusStartZone[oPlayer.Timer.Bonus] != new Vector(0, 0, 0)) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[oPlayer.Timer.Bonus], CurrentMap.BonusStartZoneAngles[oPlayer.Timer.Bonus], new Vector(0, 0, 0))); + if (oPlayer.Timer.Bonus != 0 && !CurrentMap.BonusStartZone[oPlayer.Timer.Bonus].IsZero()) + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value! , CurrentMap.BonusStartZone[oPlayer.Timer.Bonus])); 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(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); } else { - if (oPlayer.Timer.Stage != 0 && CurrentMap.StageStartZone[oPlayer.Timer.Stage] != new Vector(0, 0, 0)) - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[oPlayer.Timer.Stage], CurrentMap.StageStartZoneAngles[oPlayer.Timer.Stage], new Vector(0, 0, 0))); + if (oPlayer.Timer.Stage != 0 && !CurrentMap.StageStartZone[oPlayer.Timer.Stage].IsZero()) + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StageStartZone[oPlayer.Timer.Stage])); 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(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); } } @@ -109,18 +109,18 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) return; } - if (CurrentMap.StageStartZone[stage] != new Vector(0, 0, 0)) + if (!CurrentMap.StageStartZone[stage].IsZero()) { playerList[player.UserId ?? 0].Timer.Reset(); if (stage == 1) { - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StartZone, CurrentMap.StartZoneAngles, new Vector(0, 0, 0))); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); } else { playerList[player.UserId ?? 0].Timer.Stage = stage; - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.StageStartZone[stage], CurrentMap.StageStartZoneAngles[stage], new Vector(0, 0, 0))); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StageStartZone[stage])); playerList[player.UserId ?? 0].Timer.IsStageMode = true; } @@ -175,12 +175,12 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) return; } - if (CurrentMap.BonusStartZone[bonus] != new Vector(0, 0, 0)) + if (!CurrentMap.BonusStartZone[bonus].IsZero()) { playerList[player.UserId ?? 0].Timer.Reset(); playerList[player.UserId ?? 0].Timer.IsBonusMode = true; - Server.NextFrame(() => player.PlayerPawn.Value!.Teleport(CurrentMap.BonusStartZone[bonus], CurrentMap.BonusStartZoneAngles[bonus], new Vector(0, 0, 0))); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.BonusStartZone[bonus])); } else @@ -483,9 +483,9 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) 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), + Pos = new Vector_t(player_pos.X, player_pos.Y, player_pos.Z), + Ang = new QAngle_t(player_angle.X, player_angle.Y, player_angle.Z), + Vel = new Vector_t(player_velocity.X, player_velocity.Y, player_velocity.Z), Tick = p.Timer.Ticks }); p.CurrentSavedLocation = p.SavedLocations.Count - 1; @@ -533,7 +533,7 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm SavelocFrame location = p.SavedLocations[p.CurrentSavedLocation]; Server.NextFrame(() => { - p.Controller.PlayerPawn.Value!.Teleport(location.Pos, location.Ang, location.Vel); + Extensions.Teleport(p.Controller.PlayerPawn.Value!, location.Pos, location.Ang, location.Vel); p.Timer.Ticks = location.Tick; }); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index e685bd7..c7ea964 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -43,10 +43,7 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti { // 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_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); + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); // Map start zones -- hook into map_start, (s)tage1_start if (trigger.Entity.Name.Contains("map_start") || @@ -64,10 +61,10 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti } // Prespeed display - player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); - player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Map run - player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Map run - player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Map run + player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0} u/s"); + player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Map run #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); @@ -103,25 +100,25 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); // Show Prespeed for Stages - will be enabled/disabled by the user? - player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.ToString("0")} u/s"); + player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.velMag().ToString("0")} u/s"); } else if (player.Timer.IsRunning) { #if DEBUG - Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); - Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); - Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); Console.WriteLine($"currentCheckpoint.Attempts {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts}"); #endif // Update the Checkpoint object values - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; // Show Prespeed for Checkpoints - will be enabled/disabled by the user? - player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.ToString("0")} u/s"); + player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.velMag():0} u/s"); } } @@ -137,9 +134,9 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti if (player.Timer.Checkpoint != 0 && player.Timer.Checkpoint <= player.Stats.ThisRun.Checkpoints.Count) { #if DEBUG - Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity_x {velocity_x}"); - Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity_y {velocity_y}"); - Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity_z {velocity_z}"); + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); #endif if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) @@ -149,13 +146,13 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti } // Update the Checkpoint object values - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity_x; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity_y; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity_z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; // 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"); + player.Controller.PrintToCenter($"Checkpoint {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} - Prespeed: {velocity.velMag():0} u/s"); } } @@ -186,10 +183,10 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti } // Prespeed display - player.Controller.PrintToCenter($"Prespeed: {velocity.ToString("0")} u/s"); - player.Stats.ThisRun.StartVelX = velocity_x; // Start pre speed for the Bonus run - player.Stats.ThisRun.StartVelY = velocity_y; // Start pre speed for the Bonus run - player.Stats.ThisRun.StartVelZ = velocity_z; // Start pre speed for the Bonus run + player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0)} u/s"); + player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Bonus run } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index e90722f..6c837b9 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -44,9 +44,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { // 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_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; + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); int pStyle = player.Timer.Style; // Map end zones -- hook into map_end @@ -58,9 +56,9 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run - player.Stats.ThisRun.EndVelX = velocity_x; // End speed for the Map run - player.Stats.ThisRun.EndVelY = velocity_y; // End speed for the Map run - player.Stats.ThisRun.EndVelZ = velocity_z; // End speed for the Map run + player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run + player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run + player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run // MAP END ZONE - Map RUN @@ -123,7 +121,7 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn ============== 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}, {pStyle}, 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()}) + {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); "); @@ -374,9 +372,9 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, player.Timer.Ticks, - velocity_x, - velocity_y, - velocity_z, + velocity.X, + velocity.Y, + velocity.Z, -1.0f, -1.0f, -1.0f, @@ -423,9 +421,9 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality Checkpoint cp2 = new Checkpoint(checkpoint, player.Timer.Ticks, - velocity_x, - velocity_y, - velocity_z, + velocity.X, + velocity.Y, + velocity.Z, -1.0f, -1.0f, -1.0f, @@ -479,9 +477,9 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); 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 + player.Stats.ThisRun.EndVelX = velocity.X; // End pre speed for the run + player.Stats.ThisRun.EndVelY = velocity.Z; // End pre speed for the run + player.Stats.ThisRun.EndVelZ = velocity.Y; // End pre speed for the run bool saveBonusTime = false; string PracticeString = ""; diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 1f169c8..44e33fa 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -60,18 +60,18 @@ internal class Map // Zone Origin Information /* Map Start/End zones */ - public Vector StartZone { get; set; } = new Vector(0, 0, 0); - public QAngle StartZoneAngles { get; set; } = new QAngle(0, 0, 0); - public Vector EndZone { get; set; } = new Vector(0, 0, 0); + public Vector_t StartZone { get; set; } = new Vector_t(0, 0, 0); + public QAngle_t StartZoneAngles { get; set; } = new QAngle_t(0, 0, 0); + public Vector_t EndZone { get; set; } = new Vector_t(0, 0, 0); /* Map Stage zones */ - public Vector[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector(0, 0, 0)).ToArray(); - public QAngle[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle(0, 0, 0)).ToArray(); + public Vector_t[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); + public QAngle_t[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle_t(0, 0, 0)).ToArray(); /* Map Bonus zones */ - 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 + public Vector_t[] BonusStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); // To-do: Implement bonuses + public QAngle_t[] BonusStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle_t(0, 0, 0)).ToArray(); // To-do: Implement bonuses + public Vector_t[] BonusEndZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(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(); + public Vector_t[] CheckpointStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); public ReplayManager ReplayManager { get; set; } = null!; @@ -166,8 +166,8 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") 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); + this.StartZone = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StartZoneAngles = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); foundPlayerSpawn = true; break; } @@ -175,14 +175,14 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.StartZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StartZone = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } // Map end zone else if (trigger.Entity!.Name.Contains("map_end")) { - this.EndZone = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.EndZone = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } // Stage start zones @@ -197,8 +197,8 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") 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.StageStartZone[stage] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - this.StageStartZoneAngles[stage] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.StageStartZone[stage] = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StageStartZoneAngles[stage] = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Stages++; // Count stage zones for the map to populate DB foundPlayerSpawn = true; break; @@ -207,7 +207,7 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.StageStartZone[stage] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StageStartZone[stage] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.Stages++; } } @@ -215,7 +215,7 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") // 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)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.TotalCheckpoints++; // Might be useful to have this in DB entry } @@ -231,8 +231,8 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") 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.BonusStartZone[bonus] = new Vector(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - this.BonusStartZoneAngles[bonus] = new QAngle(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.BonusStartZone[bonus] = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.BonusStartZoneAngles[bonus] = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Bonuses++; // Count bonus zones for the map to populate DB foundPlayerSpawn = true; break; @@ -241,14 +241,14 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.BonusStartZone[bonus] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusStartZone[bonus] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.Bonuses++; } } else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) { - this.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } } @@ -450,7 +450,7 @@ internal async Task Get_Map_Record_Runs([CallerMemberName] string methodName = " int qStyle; // Replay Stuff - JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; if (Config.API.GetApiOnly()) // Need to update the query in API and re-do the assigning of data { @@ -640,7 +640,7 @@ internal async Task Get_Record_Run_Checkpoints(int style = 0) /// Base64 encoded string for the replay_frames internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64, [CallerMemberName] string methodName = "") { - JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; // Decompress the Base64 string string json = Compressor.Decompress(replayFramesBase64); @@ -809,24 +809,24 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame } // Start the new map replay if none existed until now - if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) + Server.NextFrame(() => { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); - this.ReplayManager.MapWR.ResetReplay(); - this.ReplayManager.MapWR.Start(); - } - else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) - { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); - this.ReplayManager.BonusWR.ResetReplay(); - this.ReplayManager.BonusWR.Start(); - } - else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) - { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); - this.ReplayManager.StageWR.ResetReplay(); - this.ReplayManager.StageWR.Start(); - } + if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) + { + this.ReplayManager.MapWR.ResetReplay(); + this.ReplayManager.MapWR.Start(); + } + else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) + { + this.ReplayManager.BonusWR.ResetReplay(); + this.ReplayManager.BonusWR.Start(); + } + else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) + { + this.ReplayManager.StageWR.ResetReplay(); + this.ReplayManager.StageWR.Start(); + } + }); } public void KickReplayBot(int index) diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index a5a8b11..2bf493e 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -1,4 +1,8 @@ namespace SurfTimer; + +using System; +using System.Numerics; +using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; internal enum ReplayFrameSituation @@ -31,15 +35,14 @@ internal class ReplayFrame public float[] pos { get; set; } = { 0, 0, 0 }; public float[] ang { get; set; } = { 0, 0, 0 }; public ReplayFrameSituation Situation { get; set; } = ReplayFrameSituation.NONE; - public uint Flags { get; set; } + public uint Flags { get; set; } - public Vector GetPos() + public Vector_t GetPos() { - return new Vector(this.pos[0], this.pos[1], this.pos[2]); + return new Vector_t(this.pos[0], this.pos[1], this.pos[2]); } - - public QAngle GetAng() + public QAngle_t GetAng() { - return new QAngle(this.ang[0], this.ang[1], this.ang[2]); + return new QAngle_t(this.ang[0], this.ang[1], this.ang[2]); } } diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index b7eba1e..e203e12 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -174,13 +174,13 @@ public void Tick() } // END OF BLASPHEMY - var current_pos = this.Controller!.PlayerPawn.Value!.AbsOrigin!; + var current_pos = Controller!.PlayerPawn.Value!.AbsOrigin!.ToVector_t(); var current_frame_pos = current_frame.GetPos(); var current_frame_ang = current_frame.GetAng(); bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; - Vector velocity = (current_frame_pos - current_pos) * 64; + Vector_t velocity = (current_frame_pos - current_pos) * 64; if (is_on_ground) this.Controller.PlayerPawn.Value.MoveType = MoveType_t.MOVETYPE_WALK; @@ -188,9 +188,9 @@ public void Tick() 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)); + Extensions.Teleport(Controller.PlayerPawn.Value, current_frame_pos, current_frame_ang, null); else - this.Controller.PlayerPawn.Value.Teleport(new Vector(nint.Zero), current_frame_ang, velocity); + Extensions.Teleport(Controller.PlayerPawn.Value, null , current_frame_ang, velocity); if (!this.IsPaused) diff --git a/src/ST-Player/Saveloc/SavelocFrame.cs b/src/ST-Player/Saveloc/SavelocFrame.cs index a6bcd4c..7fd9db6 100644 --- a/src/ST-Player/Saveloc/SavelocFrame.cs +++ b/src/ST-Player/Saveloc/SavelocFrame.cs @@ -4,8 +4,8 @@ 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 Vector_t Pos { get; set; } = new Vector_t(0, 0, 0); + public QAngle_t Ang { get; set; } = new QAngle_t(0, 0, 0); + public Vector_t Vel { get; set; } = new Vector_t(0, 0, 0); public int Tick { get; set; } = 0; } diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs new file mode 100644 index 0000000..c422d5c --- /dev/null +++ b/src/ST-UTILS/Extensions.cs @@ -0,0 +1,46 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Utils; + + +namespace SurfTimer; + +unsafe static class Extensions +{ + public static void Teleport(this CBaseEntity entity, Vector_t? position = null, QAngle_t? angles = null, Vector_t? velocity = null) + { + Guard.IsValidEntity(entity); + + void* pPos = null, pAng = null, pVel = null; + + // Structs are stored on the stack, GC should not break pointers. + + if (position.HasValue) + { + var pos = position.Value; // Remove nullable wrapper + pPos = &pos; + } + + if (angles.HasValue) + { + var ang = angles.Value; + pAng = ∠ + } + + if (velocity.HasValue) + { + var vel = velocity.Value; + pVel = &vel; + } + + VirtualFunction.CreateVoid(entity.Handle, GameData.GetOffset("CBaseEntity_Teleport"))(entity.Handle, (nint)pPos, + (nint)pAng, (nint)pVel); + } + + public static (Vector_t fwd, Vector_t right, Vector_t up) AngleVectors(this QAngle vec) => vec.ToQAngle_t().AngleVectors(); + public static void AngleVectors(this QAngle vec, out Vector_t fwd, out Vector_t right, out Vector_t up) => vec.ToQAngle_t().AngleVectors(out fwd, out right, out up); + + public static Vector_t ToVector_t(this Vector vec) => new(vec.Handle); + public static QAngle_t ToQAngle_t(this QAngle vec) => new(vec.Handle); +} \ No newline at end of file diff --git a/src/ST-UTILS/Structs/QAngle_t.cs b/src/ST-UTILS/Structs/QAngle_t.cs new file mode 100644 index 0000000..f00c804 --- /dev/null +++ b/src/ST-UTILS/Structs/QAngle_t.cs @@ -0,0 +1,123 @@ +using CounterStrikeSharp.API.Core; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SurfTimer; + +struct QAngle_t : IAdditionOperators, + ISubtractionOperators, + IMultiplyOperators, + IDivisionOperators +{ + public float X, Y, Z; + + public const int SIZE = 3; + + public unsafe float this[int i] + { + readonly get + { + if (i < 0 || i > SIZE) + { + throw new IndexOutOfRangeException(); + } + + fixed (void* ptr = &this) + { + return Unsafe.Read(Unsafe.Add(ptr, i)); + } + } + set + { + if (i < 0 || i > SIZE) + { + throw new IndexOutOfRangeException(); + } + + fixed (void* ptr = &this) + { + Unsafe.Write(Unsafe.Add(ptr, i), value); + } + } + } + + public QAngle_t() + { + } + + public unsafe QAngle_t(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) + { + } + + public QAngle_t(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public QAngle_t(ReadOnlySpan values) + { + if (values.Length < SIZE) + { + throw new ArgumentOutOfRangeException(nameof(values)); + } + + this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + public unsafe (Vector_t fwd, Vector_t right, Vector_t up) AngleVectors() + { + Vector_t fwd = default, right = default, up = default; + + nint pFwd = (nint)Unsafe.AsPointer(ref fwd); + nint pRight = (nint)Unsafe.AsPointer(ref right); + nint pUp = (nint)Unsafe.AsPointer(ref up); + + fixed (void* ptr = &this) + { + NativeAPI.AngleVectors((nint)ptr, pFwd, pRight, pUp); + } + + return ( fwd, right, up ); + } + + public unsafe void AngleVectors(out Vector_t fwd, out Vector_t right, out Vector_t up) + { + fixed (void* ptr = &this, pFwd = &fwd, pRight = &right, pUp = &up) + { + NativeAPI.AngleVectors((nint)ptr, (nint)pFwd, (nint)pRight, (nint)pUp); + } + } + + public readonly override string ToString() + { + return $"{X:n2} {Y:n2} {Z:n2}"; + } + + public static QAngle_t operator +(QAngle_t a, QAngle_t b) + { + return new QAngle_t(a.X + b.X, a.Y + b.Y, a.Z + b.Z); + } + + public static QAngle_t operator -(QAngle_t a, QAngle_t b) + { + return new QAngle_t(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + public static QAngle_t operator -(QAngle_t a) + { + return new QAngle_t(-a.X, -a.Y, -a.Z); + } + + public static QAngle_t operator *(QAngle_t a, float b) + { + return new QAngle_t(a.X * b, a.Y * b, a.Z * b); + } + + public static QAngle_t operator /(QAngle_t a, float b) + { + return new QAngle_t(a.X / b, a.Y / b, a.Z / b); + } +} \ No newline at end of file diff --git a/src/ST-UTILS/Structs/Vector_t.cs b/src/ST-UTILS/Structs/Vector_t.cs new file mode 100644 index 0000000..da8606a --- /dev/null +++ b/src/ST-UTILS/Structs/Vector_t.cs @@ -0,0 +1,124 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SurfTimer; + +struct Vector_t : IAdditionOperators, + ISubtractionOperators, + IMultiplyOperators, + IDivisionOperators +{ + public float X, Y, Z; + + public const int SIZE = 3; + + public unsafe float this[int i] + { + readonly get + { + if (i < 0 || i > SIZE) + { + throw new IndexOutOfRangeException(); + } + + fixed (void* ptr = &this) + { + return Unsafe.Read(Unsafe.Add(ptr, i)); + } + } + set + { + if (i < 0 || i > SIZE) + { + throw new IndexOutOfRangeException(); + } + + fixed (void* ptr = &this) + { + Unsafe.Write(Unsafe.Add(ptr, i), value); + } + } + } + + public Vector_t() + { + } + + public unsafe Vector_t(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) + { + } + + public Vector_t(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public Vector_t(ReadOnlySpan values) + { + if (values.Length < SIZE) + { + throw new ArgumentOutOfRangeException(nameof(values)); + } + + this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + public readonly float Length() + { + return (float)Math.Sqrt(X * X + Y * Y + Z * Z); + } + + public readonly float Length2D() + { + return (float)Math.Sqrt(X * X + Y * Y); + } + + public readonly float velMag() + { + return (float)Math.Sqrt(X * X + Y * Y + Z + Z ); + } + + public readonly bool IsZero(float tolerance = 0.0001f) + { + return Math.Abs(X) <= tolerance && Math.Abs(Y) <= tolerance && Math.Abs(Z) <= tolerance; + } + public void Scale(float scale) + { + X *= scale; + Y *= scale; + Z *= scale; + } + + public readonly override string ToString() + { + return $"{X:n2} {Y:n2} {Z:n2}"; + } + + public static Vector_t operator +(Vector_t a, Vector_t b) + { + return new Vector_t(a.X + b.X, a.Y + b.Y, a.Z + b.Z); + } + + public static Vector_t operator -(Vector_t a, Vector_t b) + { + return new Vector_t(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + public static Vector_t operator -(Vector_t a) + { + return new Vector_t(-a.X, -a.Y, -a.Z); + } + + public static Vector_t operator *(Vector_t a, float b) + { + return new Vector_t(a.X * b, a.Y * b, a.Z * b); + } + + public static Vector_t operator /(Vector_t a, float b) + { + return new Vector_t(a.X / b, a.Y / b, a.Z / b); + } +} \ No newline at end of file From e4c0d7f3035decc5368277997cd492eb21a20416 Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:48:40 -0400 Subject: [PATCH 16/52] Players in spec can use restart and teleport commands --- src/ST-Commands/PlayerCommands.cs | 56 ++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 23dca72..9d25030 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -12,8 +12,13 @@ public partial class SurfTimer [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerReset(CCSPlayerController? player, CommandInfo command) { - if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player oPlayer = playerList[player.UserId ?? 0]; if (oPlayer.ReplayRecorder.IsSaving) @@ -33,8 +38,13 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) { - if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player oPlayer = playerList[player.UserId ?? 0]; if (oPlayer.ReplayRecorder.IsSaving) @@ -66,8 +76,13 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { - if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } int stage; try @@ -139,8 +154,13 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) { - if (player == null || player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } int bonus; @@ -467,8 +487,13 @@ 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)) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player p = playerList[player.UserId ?? 0]; if (!p.Timer.IsRunning) @@ -498,8 +523,13 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) [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)) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player p = playerList[player.UserId ?? 0]; @@ -545,8 +575,13 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm [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)) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player p = playerList[player.UserId ?? 0]; @@ -575,8 +610,13 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo [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)) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } Player p = playerList[player.UserId ?? 0]; From da6622f61b1610a7f357375acf0b9e7210272f29 Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:51:53 -0400 Subject: [PATCH 17/52] Cleanup for replay indexing; Fixed threading error with FormatBotName() When ReplayPlayer() was being called bot naming would cause a threading error since FormatBotName() was being called on a background thread, but needed to be moved to the main thread. (thanks chatgpt!) --- src/ST-Player/Replay/ReplayPlayer.cs | 4 + src/ST-Player/Replay/ReplayRecorder.cs | 202 +++++++++++-------------- 2 files changed, 91 insertions(+), 115 deletions(-) diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index e203e12..6293fca 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -105,13 +105,17 @@ public void Start([CallerMemberName] string methodName = "") if (!this.IsPlayable || !this.IsEnabled) return; + Server.NextFrame(() => + { this.FormatBotName(); this.IsPlaying = true; + #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> Starting replay for run {MapTimeID} (Map ID {MapID}) - {RecordPlayerName} (Stage {Stage})", nameof(ReplayPlayer), methodName, this.MapTimeID, this.MapID, this.RecordPlayerName, this.Stage ); #endif + }); } public void Stop([CallerMemberName] string methodName = "") diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 8fbf2c3..61bacee 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -170,23 +170,20 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (this.Frames.Count == 0) { _logger.LogError("[{ClassName}] {MethodName} -> There are no Frames available for trimming for player {Name}", - nameof(ReplayRecorder), methodName, player.Profile.Name - ); + nameof(ReplayRecorder), methodName, player.Profile.Name + ); throw new Exception("There are no Frames available for trimming"); } switch (type) { - case 0: // Trim Map replays - // Map/Bonus runs - var start_enter_index = MapSituations[0]; - var start_exit_index = MapSituations[1]; - var end_enter_index = MapSituations[2]; - // var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // This comes out as `-1` somehow, need to edit the IF statement below to use the `start_exit_index` in that case and not care about this variable - // var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - // var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + case 0: // Map Run + { + var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", - nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index - ); + nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); if (start_enter_index == -1) { @@ -199,32 +196,12 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) { - // Try different buffer sizes for start index - int startIndex; - if (start_exit_index - (Config.ReplaysPre * 2) >= start_enter_index) - startIndex = start_exit_index - (Config.ReplaysPre * 2); - else if (start_exit_index - Config.ReplaysPre >= start_enter_index) - startIndex = start_exit_index - Config.ReplaysPre; - else if (start_exit_index - (Config.ReplaysPre / 2) >= start_enter_index) - startIndex = start_exit_index - (Config.ReplaysPre / 2); - else - startIndex = start_enter_index; // fallback to minimum allowed - // Try different buffer sizes for end index - int endIndex; - if (end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = end_enter_index + (Config.ReplaysPre * 2); - else if (end_enter_index + Config.ReplaysPre < Frames.Count) - endIndex = end_enter_index + Config.ReplaysPre; - else if (end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = end_enter_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = end_enter_index; // fallback to maximum allowed - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + int startIndex = CalculateStartIndex(start_enter_index, start_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count - ); + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); } else { @@ -233,11 +210,12 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C ); } break; - case 1: // Trim Bonus replays - // Bonus runs - int bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - int bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - int bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + } + case 1: // Bonus Run + { + var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index ); @@ -252,29 +230,10 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) { - // Try different buffer sizes for start index - int startIndex; - if (bonus_exit_index - (Config.ReplaysPre * 2) >= bonus_enter_index) - startIndex = bonus_exit_index - (Config.ReplaysPre * 2); - else if (bonus_exit_index - Config.ReplaysPre >= bonus_enter_index) - startIndex = bonus_exit_index - Config.ReplaysPre; - else if (bonus_exit_index - (Config.ReplaysPre / 2) >= bonus_enter_index) - startIndex = bonus_exit_index - (Config.ReplaysPre / 2); - else - startIndex = bonus_enter_index; // fallback to minimum allowed - // Try different buffer sizes for end index - int endIndex; - if (bonus_end_enter_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = bonus_end_enter_index + (Config.ReplaysPre * 2); - else if (bonus_end_enter_index + Config.ReplaysPre < Frames.Count) - endIndex = bonus_end_enter_index + Config.ReplaysPre; - else if (bonus_end_enter_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = bonus_end_enter_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = bonus_end_enter_index; // fallback to maximum allowed - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + int startIndex = CalculateStartIndex(bonus_enter_index, bonus_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count ); @@ -286,44 +245,47 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C ); } break; - case 2: // Trim Stage replays + } + case 2: // Stage Run + { int stage_end_index; int stage_exit_index; int stage_enter_index; + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Stage Run replay trim indexes. Stage {Stage}, available frames {TotalFrames}", nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, Frames.Count ); - // Stage runs - if (lastStage) // Last stage + + if (lastStage) { _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); // Last stage enter (finishing the stage) + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index + stage_end_index ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index + stage_exit_index ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index + stage_enter_index ); } - else if (player.Timer.Stage - 1 > 1) // Not first stage + else if (player.Timer.Stage - 1 > 1) { _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index + stage_end_index ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index + stage_exit_index ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index + stage_enter_index ); } else if (player.Timer.Stage - 1 == -1) // Don't crash...? @@ -338,26 +300,26 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C stage_end_index = -1; stage_exit_index = -1; } - else // First stage is always the start of the map so we are looking for START_ZONE_X + else { _logger.LogInformation("Stage replay trimming will use `START_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); // Last stage enter (finishing the stage) + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index + stage_end_index ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); // Exiting the previous stage zone (what we are looking for start of the stage run) + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index + stage_exit_index ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); // Entering the previous stage zone (what we are looking for pre-speed trim) + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index + stage_enter_index ); } - _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", - nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", + nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index ); - + if (stage_enter_index == -1) { _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for stage_enter_index during Stage ({StageNumber}) replay trimming. Setting 'stage_enter_index' to '0'", @@ -368,29 +330,10 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) { - // Try different buffer sizes for start index - int startIndex; - if (stage_exit_index - (Config.ReplaysPre * 2) >= stage_enter_index) - startIndex = stage_exit_index - (Config.ReplaysPre * 2); - else if (stage_exit_index - Config.ReplaysPre >= stage_enter_index) - startIndex = stage_exit_index - Config.ReplaysPre; - else if (stage_exit_index - (Config.ReplaysPre / 2) >= stage_enter_index) - startIndex = stage_exit_index - (Config.ReplaysPre / 2); - else - startIndex = stage_enter_index; // fallback to minimum allowed - // Try different buffer sizes for end index - int endIndex; - if (stage_end_index + (Config.ReplaysPre * 2) < Frames.Count) - endIndex = stage_end_index + (Config.ReplaysPre * 2); - else if (stage_end_index + Config.ReplaysPre < Frames.Count) - endIndex = stage_end_index + Config.ReplaysPre; - else if (stage_end_index + (Config.ReplaysPre / 2) < Frames.Count) - endIndex = stage_end_index + (Config.ReplaysPre / 2); - else - // endIndex = Frames.Count - 1; // fallback to maximum allowed - endIndex = stage_end_index; // fallback to maximum allowed - // Get the range of frames - new_frames = Frames.GetRange(startIndex, endIndex - startIndex + 1); + int startIndex = CalculateStartIndex(stage_enter_index, stage_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(stage_end_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count ); @@ -402,7 +345,8 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C ); } break; - } + } + } this.IsSaving = false; string trimmed = JsonSerializer.Serialize(new_frames); @@ -442,4 +386,32 @@ public int LastExitTick(int start_idx = 0) } return 0; } + private int CalculateStartIndex(int start_enter, int start_exit, int buffer) + { + if (start_exit - (buffer * 2) >= start_enter) + return start_exit - (buffer * 2); + else if (start_exit - buffer >= start_enter) + return start_exit - buffer; + else if (start_exit - (buffer / 2) >= start_enter) + return start_exit - (buffer / 2); + else + return start_enter; + } + + private int CalculateEndIndex(int end_enter, int totalFrames, int buffer) + { + if (end_enter + (buffer * 2) < totalFrames) + return end_enter + (buffer * 2); + else if (end_enter + buffer < totalFrames) + return end_enter + buffer; + else if (end_enter + (buffer / 2) < totalFrames) + return end_enter + (buffer / 2); + else + return end_enter; + } + + private List GetTrimmedFrames(int startIndex, int endIndex) + { + return Frames.GetRange(startIndex, endIndex - startIndex + 1); + } } \ No newline at end of file From 98ac825f04a55b21d9fdac1925edc473846675ea Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:22:09 +0000 Subject: [PATCH 18/52] feat: set player collision to debris for no block --- src/ST-Events/Tick.cs | 4 ++++ src/ST-UTILS/Extensions.cs | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 2d59bb0..4003900 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -1,4 +1,5 @@ using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Entities.Constants; namespace SurfTimer; @@ -11,6 +12,9 @@ public void OnTick() player.Timer.Tick(); player.ReplayRecorder.Tick(player); player.HUD.Display(); + if (player.Controller.Collision == null) continue; + if ((CollisionGroup)player.Controller.Collision.CollisionGroup == CollisionGroup.COLLISION_GROUP_DEBRIS) continue; + player.Controller.SetCollisionGroup(CollisionGroup.COLLISION_GROUP_DEBRIS); } if (CurrentMap == null) diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index c422d5c..493b3b5 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -1,5 +1,6 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; @@ -43,4 +44,14 @@ public static void Teleport(this CBaseEntity entity, Vector_t? position = null, public static Vector_t ToVector_t(this Vector vec) => new(vec.Handle); public static QAngle_t ToQAngle_t(this QAngle vec) => new(vec.Handle); + + public static void SetCollisionGroup(this CCSPlayerController controller, CollisionGroup collisionGroup) + { + if (!controller.IsValid || controller.Collision==null) return; + controller.Collision.CollisionAttribute.CollisionGroup = (byte)collisionGroup; + controller.Collision.CollisionGroup = (byte)collisionGroup; + + Utilities.SetStateChanged(controller, "CColisionProperity", "m_collisionGroup"); + Utilities.SetStateChanged(controller, "CCollisionProperty", "m_collisionAttribute"); + } } \ No newline at end of file From 51db553e463f857a7966eb0d4fed3987dd472a1e Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:23:42 +0000 Subject: [PATCH 19/52] chore(player.cs): removed unecessary namespaec ref --- src/ST-Events/Players.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 13c016e..3c4b713 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -1,7 +1,6 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; -using CounterStrikeSharp.API.Modules.Utils; using MaxMind.GeoIP2; using Microsoft.Extensions.Logging; From cec70845915017c9dd78709c8209584edc1bc214 Mon Sep 17 00:00:00 2001 From: Slumey <100669917+Slumey@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:30:59 +0000 Subject: [PATCH 20/52] fix(TriggerStartTouch..cs): swap Y and Z velocity components --- 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 6c837b9..69c115c 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -478,8 +478,8 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn 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.Z; // End pre speed for the run - player.Stats.ThisRun.EndVelZ = velocity.Y; // 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 bool saveBonusTime = false; string PracticeString = ""; From c4bb65db7fed6d2ca86d9ca17c2cca8d1175f076 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:24:22 +0300 Subject: [PATCH 21/52] RunStats, IDataAccessService, Models, API --- TODO | 10 +- src/ST-API/Comms.cs | 56 ++- src/ST-API/Structures.cs | 47 ++- src/ST-Commands/PlayerCommands.cs | 325 ++++++++------- src/ST-DB/DB.cs | 7 +- src/ST-Events/Players.cs | 12 +- src/ST-Events/Tick.cs | 11 +- src/ST-Events/TriggerEndTouch.cs | 1 + src/ST-Map/Map.cs | 419 +++++++------------- src/ST-Player/PlayerHUD.cs | 3 + src/ST-Player/PlayerProfile.cs | 87 ++-- src/ST-Player/PlayerStats/Checkpoint.cs | 18 +- src/ST-Player/PlayerStats/CurrentRun.cs | 333 ++++++++++------ src/ST-Player/PlayerStats/PersonalBest.cs | 286 ++++++------- src/ST-Player/PlayerStats/PlayerStats.cs | 256 ++++++++---- src/ST-Player/PlayerStats/RunStats.cs | 38 ++ src/ST-Player/Replay/ReplayPlayer.cs | 19 +- src/ST-UTILS/Config.cs | 28 +- src/ST-UTILS/Data/ApiDataAccessService.cs | 393 ++++++++++++++++++ src/ST-UTILS/Data/IDataAccessService.cs | 78 ++++ src/ST-UTILS/Data/Models.cs | 66 +++ src/ST-UTILS/Data/MySqlDataAccessService.cs | 378 ++++++++++++++++++ src/ST-UTILS/Injection.cs | 8 + 23 files changed, 1986 insertions(+), 893 deletions(-) create mode 100644 src/ST-Player/PlayerStats/RunStats.cs create mode 100644 src/ST-UTILS/Data/ApiDataAccessService.cs create mode 100644 src/ST-UTILS/Data/IDataAccessService.cs create mode 100644 src/ST-UTILS/Data/Models.cs create mode 100644 src/ST-UTILS/Data/MySqlDataAccessService.cs diff --git a/TODO b/TODO index 58070b4..5675419 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,13 @@ -- Re-add the MySQL queries in code and make it switch between API and DB functions +- Replay Bot Scoreboard names NOT changing when setting new recs + ++ Re-add the MySQL queries in code and make it switch between API and DB functions - Map Time is NOT being saved with API - Make configs generate themselves inside the `./configs/plugins/...` folder - Fix loading MapTimes for each type (stage, bonus, maps) - API - DB -- Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin -- Change `DB_QUERY_PB_GET_RUNTIME` query in API ++ Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin ++ Change `DB_QUERY_PB_GET_RUNTIME` query in API - Double check the Start/End velocities for End Zone,Checkpoints, Stages, Bonuses + Maps @@ -14,7 +16,7 @@ - Try and get the Start/End velocity values from the Checkpoint object in Map Run + Bonuses -- Using `!spec` from any start zone and going back to CT starts your timer +- Using `!spec` from any start zone and going back to CT starts your timer - Will be in a PR - Try and determine Left/Right side for Bonuses (surf_ace) - Try and determine Left/Right side for Maps diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index 82d3f73..b61469a 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -1,4 +1,7 @@ using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -9,9 +12,10 @@ private ApiMethod() { } private static readonly HttpClient _client = new(); private static readonly string base_addr = Config.ApiUrl; - public static async Task GET(string url) + public static async Task GET(string url, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); + var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); #if DEBUG Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task GET -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); @@ -21,25 +25,37 @@ private ApiMethod() { } try { - System.Console.WriteLine($"[API] GET {url} => {response.StatusCode}"); - if (response.StatusCode != System.Net.HttpStatusCode.OK) + _logger.LogInformation("[{ClassName}] {MethodName} -> GET {URL} => {StatusCode}", + nameof(ApiMethod), methodName, url, response.StatusCode + ); + + if (response.StatusCode == System.Net.HttpStatusCode.NoContent) + { + _logger.LogWarning("[{ClassName}] {MethodName} -> No data found {StatusCode}", + nameof(ApiMethod), methodName, response.StatusCode + ); + + return default; + } + else if (response.StatusCode != System.Net.HttpStatusCode.OK) { - Exception exception = new Exception("[API] GET - No data found"); + Exception exception = new Exception($"[{nameof(ApiMethod)}] {methodName} -> Unexpected status code {response.StatusCode}"); throw exception; } return await response.Content.ReadFromJsonAsync(); } - catch + catch (Exception ex) { - Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", nameof(ApiMethod), methodName); return default; } } - public static async Task POST(string url, T body) + public static async Task POST(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); + var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); #if DEBUG Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task POST -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); @@ -49,20 +65,26 @@ private ApiMethod() { } try { - System.Console.WriteLine($"[API] POST {url} => {response.StatusCode}"); + _logger.LogInformation("[{ClassName}] {MethodName} -> POST {URL} => {StatusCode}", + nameof(ApiMethod), methodName, url, response.StatusCode + ); + response.EnsureSuccessStatusCode(); // BAD BAD BAD return await response.Content.ReadFromJsonAsync(); } - catch + catch (Exception ex) { - Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", + nameof(ApiMethod), methodName + ); return default; } } - public static async Task PUT(string url, T body) + public static async Task PUT(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); + var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); #if DEBUG Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task PUT -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); @@ -72,13 +94,19 @@ private ApiMethod() { } try { - System.Console.WriteLine($"[API] PUT {url} => {response.StatusCode}"); + _logger.LogInformation("[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode}", + nameof(ApiMethod), methodName, url, response.StatusCode + ); + response.EnsureSuccessStatusCode(); // BAD BAD BAD return await response.Content.ReadFromJsonAsync(); } - catch + catch (Exception ex) { - Console.WriteLine("HTTP Response was invalid or could not be deserialised."); + _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", + nameof(ApiMethod), methodName + ); + return default; } } diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs index 2b638df..a4722d3 100644 --- a/src/ST-API/Structures.cs +++ b/src/ST-API/Structures.cs @@ -74,14 +74,57 @@ internal class API_MapTime public string replay_frames { get; set; } = ""; // This needs to be checked touroughly public List? checkpoints { get; set; } = null; public string name { get; set; } = "N/A"; + public int total_count { get; set; } } internal class API_PlayerSurfProfile { + public int id { get; set; } public string name { get; set; } = "N/A"; - public int steam_id { get; set; } + public ulong steam_id { get; set; } public string country { get; set; } = "N/A"; public int join_date { get; set; } public int last_seen { get; set; } public int connections { get; set; } -} \ No newline at end of file +} + +internal class API_PersonalBest +{ + public int id { get; set; } + public int player_id { get; set; } + public int map_id { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int run_date { get; set; } + public string replay_frames { get; set; } = ""; // This needs to be checked touroughly + public List? checkpoints { get; set; } = null; + public string name { get; set; } = "N/A"; + public int rank { get; set; } +} + +internal class API_SaveMapTime +{ + public int player_id { get; set; } + public int map_id { get; set; } + public int run_time { get; set; } + public float start_vel_x { get; set; } + public float start_vel_y { get; set; } + public float start_vel_z { get; set; } + public float end_vel_x { get; set; } + public float end_vel_y { get; set; } + public float end_vel_z { get; set; } + public int style { get; set; } = 0; + public int type { get; set; } = 0; + public int stage { get; set; } = 0; + public List? checkpoints { get; set; } = null; + public string replay_frames { get; set; } = ""; + public int? run_date { get; set; } = null; +} diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index b739fb5..e1b1812 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -212,152 +212,6 @@ public void PlayerRank(CCSPlayerController? player, CommandInfo command) ); } - [ConsoleCommand("css_rx", "x")] - [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) - { - if (player == null) - return; - - Player oPlayer = playerList[player.UserId ?? 0]; - int style = oPlayer.Timer.Style; - - oPlayer.Stats.ThisRun.PrintSituations(oPlayer); - } - - [ConsoleCommand("css_testx", "x")] - [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - public void TestCmd(CCSPlayerController? player, CommandInfo command) - { - if (player == null) - return; - - Player oPlayer = playerList[player.UserId ?? 0]; - int style = oPlayer.Timer.Style; - - // player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Testing 'PB_LoadMapTimeData'"); - player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); - player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); - player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); - player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].Ticks)}"); - player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); - player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); - player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); - player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); - player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); - player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); - player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].Ticks)}"); - // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); - - - player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Orange}====== MAP ======"); - player.PrintToChat($"{Config.PluginPrefix} Map ID: {ChatColors.Green}{CurrentMap.ID}"); - player.PrintToChat($"{Config.PluginPrefix} Map Name: {ChatColors.Green}{CurrentMap.Name}"); - player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); - player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); - player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); - player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].Ticks}"); - player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); - - - player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Purple}====== REPLAYS ======"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.Frames.Count: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.IsRecording: {ChatColors.Green}{oPlayer.ReplayRecorder.IsRecording}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordRunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.RecordRunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.IsPlayable}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.RecordRunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlayable}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlaying}"); - - /* - for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) - { - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].RecordRunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].IsPlayable}"); - } - */ - - /* - for (int i = 0; i < CurrentMap.ReplayManager.MapWR.Frames.Count; i++) - { - ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; - - switch (x.Situation) - { - case ReplayFrameSituation.START_ZONE_ENTER: - player.PrintToChat($"Start Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.START_ZONE_EXIT: - player.PrintToChat($"Start Exit: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: - player.PrintToChat($"Stage Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - player.PrintToChat($"Stage Exit: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - player.PrintToChat($"Checkpoint Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - player.PrintToChat($"Checkpoint Exit: {i} | Situation {x.Situation}"); - break; - } - } - */ - // for (int i = 0; i < CurrentMap.ReplayManager.MapWR.MapSituations.Count; i++) - // { - // ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; - // switch (x.Situation) - // { - // case ReplayFrameSituation.START_ZONE_ENTER: - // player.PrintToChat($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.START_ZONE_EXIT: - // player.PrintToChat($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.STAGE_ZONE_ENTER: - // player.PrintToChat($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.STAGE_ZONE_EXIT: - // player.PrintToChat($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - // player.PrintToChat($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - // player.PrintToChat($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // } - // } - - // player.PrintToChat($"{Config.PluginPrefix} IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); - // player.PrintToChat($"{Config.PluginPrefix} IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlaying}"); - // player.PrintToChat($"{Config.PluginPrefix} Player.IsSpectating: {ChatColors.Green}{oPlayer.IsSpectating(CurrentMap.ReplayManager.MapWR.Controller!)}"); - // player.PrintToChat($"{Config.PluginPrefix} Name & MapTimeID: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordPlayerName} {CurrentMap.ReplayManager.MapWR.MapTimeID}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayCurrentRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.ReplayCurrentRunTime}"); - // player.PrintToChat($"{Config.PluginPrefix} RepeatCount: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RepeatCount}"); - // player.PrintToChat($"{Config.PluginPrefix} IsReplayOutsideZone: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsReplayOutsideZone}"); - // player.PrintToChat($"{Config.PluginPrefix} CurrentFrameTick: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.CurrentFrameTick}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayRecorder.Frames.Length: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); - - // if (CurrentMap.ReplayManager.StageWR != null) - // { - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.MapTimeID - Stage: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.MapTimeID} - {CurrentMap.ReplayManager.StageWR.Stage}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlayable}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsEnabled: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsEnabled}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPaused: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPaused}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlaying}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.Controller Null?: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.Controller == null}"); - // } - } /* ######################### @@ -601,4 +455,183 @@ public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo p.CurrentSavedLocation]}" ); } + + + + + /* + ######################## + TEST CMDS + ######################## + */ + [ConsoleCommand("css_rx", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + oPlayer.Stats.ThisRun.PrintSituations(oPlayer); + } + + + // [ConsoleCommand("css_setpb", "xxxxxx")] + // [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + // public void TestSetPb(CCSPlayerController? player, CommandInfo command) + // { + // var _ = await TestSetPbAsync(player, command); + // } + + [ConsoleCommand("css_setpb", "xxxxx")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public async void TestSetPb(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + await oPlayer.Stats.ThisRun.SaveMapTime(oPlayer, 0, 0, 6666, "TestSetPb"); + // oPlayer.Stats.ThisRun.PrintSituations(oPlayer); + } + + [ConsoleCommand("css_testx", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void TestCmd(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + Player oPlayer = playerList[player.UserId ?? 0]; + int style = oPlayer.Timer.Style; + + // player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Testing 'PB_LoadMapTimeData'"); + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); + player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); + player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); + player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); + player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); + player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); + player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); + player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].Ticks)}"); + // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Orange}====== MAP ======"); + player.PrintToChat($"{Config.PluginPrefix} Map ID: {ChatColors.Green}{CurrentMap.ID}"); + player.PrintToChat($"{Config.PluginPrefix} Map Name: {ChatColors.Green}{CurrentMap.Name}"); + player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); + player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); + player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); + + + player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Purple}====== REPLAYS ======"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.Frames.Count: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayRecorder.IsRecording: {ChatColors.Green}{oPlayer.ReplayRecorder.IsRecording}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.MapWR.MapSituations.Count: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.MapSituations.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlayable}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlaying}"); + + /* + for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) + { + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].RecordRunTime}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].Frames.Count}"); + player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].IsPlayable}"); + } + */ + + /* + for (int i = 0; i < CurrentMap.ReplayManager.MapWR.Frames.Count; i++) + { + ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + player.PrintToChat($"Start Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + player.PrintToChat($"Start Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + player.PrintToChat($"Stage Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + player.PrintToChat($"Stage Exit: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + player.PrintToChat($"Checkpoint Enter: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + player.PrintToChat($"Checkpoint Exit: {i} | Situation {x.Situation}"); + break; + } + } + */ + // for (int i = 0; i < CurrentMap.ReplayManager.MapWR.MapSituations.Count; i++) + // { + // ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; + // switch (x.Situation) + // { + // case ReplayFrameSituation.START_ZONE_ENTER: + // player.PrintToChat($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.START_ZONE_EXIT: + // player.PrintToChat($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_ENTER: + // player.PrintToChat($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.STAGE_ZONE_EXIT: + // player.PrintToChat($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + // player.PrintToChat($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + // break; + // case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + // player.PrintToChat($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + // break; + // } + // } + + // player.PrintToChat($"{Config.PluginPrefix} IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} Player.IsSpectating: {ChatColors.Green}{oPlayer.IsSpectating(CurrentMap.ReplayManager.MapWR.Controller!)}"); + // player.PrintToChat($"{Config.PluginPrefix} Name & MapTimeID: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordPlayerName} {CurrentMap.ReplayManager.MapWR.MapTimeID}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayCurrentRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.ReplayCurrentRunTime}"); + // player.PrintToChat($"{Config.PluginPrefix} RepeatCount: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RepeatCount}"); + // player.PrintToChat($"{Config.PluginPrefix} IsReplayOutsideZone: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsReplayOutsideZone}"); + // player.PrintToChat($"{Config.PluginPrefix} CurrentFrameTick: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.CurrentFrameTick}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayRecorder.Frames.Length: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); + + // if (CurrentMap.ReplayManager.StageWR != null) + // { + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.MapTimeID - Stage: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.MapTimeID} - {CurrentMap.ReplayManager.StageWR.Stage}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlayable}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsEnabled: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsEnabled}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPaused: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPaused}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlaying}"); + // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.Controller Null?: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.Controller == null}"); + // } + } + + } \ No newline at end of file diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs index 1d6eea7..879853d 100644 --- a/src/ST-DB/DB.cs +++ b/src/ST-DB/DB.cs @@ -71,13 +71,16 @@ public async Task QueryAsync(string query) /// No need to encapsulate in `using` block. /// /// INSERT/UPDATE query to execute - public async Task WriteAsync(string query) + /// rowsInserted, lastInsertedId + public async Task<(int rowsInserted, long lastInsertedId)> WriteAsync(string query) { try { using var connection = GetConnection(); using var cmd = new MySqlCommand(query, connection); - return await cmd.ExecuteNonQueryAsync(); + var rows = await cmd.ExecuteNonQueryAsync(); + long lastId = cmd.LastInsertedId; // Retrieve the last ID inserted + return (rows, lastId); } catch (Exception ex) { diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 13c016e..3e7d055 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -16,8 +16,12 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) if (!controller.IsValid || !controller.IsBot || CurrentMap.ReplayManager.IsControllerConnectedToReplayPlayer(controller)) return HookResult.Continue; + _logger.LogTrace("OnPlayerSpawn -> Player {Name} spawned.", + controller.PlayerName + ); + // Set the controller for the MapWR bot - if (!CurrentMap.ReplayManager!.MapWR.IsPlayable) + if (!CurrentMap.ReplayManager!.MapWR.IsPlayable && controller.IsBot) { CurrentMap.ReplayManager.MapWR.SetController(controller, -1); CurrentMap.ReplayManager.MapWR.LoadReplayData(); @@ -33,7 +37,7 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) } // Set the controller for the StageWR bot - if (CurrentMap.ReplayManager.StageWR != null && !CurrentMap.ReplayManager.StageWR.IsPlayable) + if (CurrentMap.ReplayManager.StageWR != null && !CurrentMap.ReplayManager.StageWR.IsPlayable && controller.IsBot) { CurrentMap.ReplayManager.StageWR.SetController(controller, 3); CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); @@ -49,7 +53,7 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) } // Spawn the BonusWR bot - if (CurrentMap.ReplayManager.BonusWR != null && !CurrentMap.ReplayManager.BonusWR.IsPlayable) + if (CurrentMap.ReplayManager.BonusWR != null && !CurrentMap.ReplayManager.BonusWR.IsPlayable && controller.IsBot) { CurrentMap.ReplayManager.BonusWR.SetController(controller, 3); CurrentMap.ReplayManager.BonusWR.LoadReplayData(); @@ -183,7 +187,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo else { // Update data in Player DB table - playerList[player.UserId ?? 0].Profile.Update_Player_Profile(player.PlayerName).GetAwaiter().GetResult(); // Hold the thread until player data is updated + playerList[player.UserId ?? 0].Profile.UpdatePlayerProfile(player.PlayerName).GetAwaiter().GetResult(); // Hold the thread until player data is updated // Remove player data from playerList playerList.Remove(player.UserId ?? 0); diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 2d59bb0..0f8570f 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -33,13 +33,18 @@ public void OnTick() bot_quota.SetValue(replaybot_count); } - // Console.WriteLine($"======== public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}"); + // _logger.LogInformation("public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}", cbq, replaybot_count); } CurrentMap.ReplayManager.MapWR.Tick(); CurrentMap.ReplayManager.StageWR?.Tick(); CurrentMap.ReplayManager.BonusWR?.Tick(); + if (CurrentMap.ReplayManager.MapWR.MapTimeID != -1) + { + CurrentMap.ReplayManager.MapWR.FormatBotName(); + } + // Here we will load the NEXT stage replay from AllStageWR if (CurrentMap.ReplayManager.StageWR?.RepeatCount == 0) { @@ -51,7 +56,7 @@ public void OnTick() CurrentMap.ReplayManager.AllStageWR[next_stage][0].Controller = CurrentMap.ReplayManager.StageWR.Controller; - // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for stage {CurrentMap.ReplayManager.StageWR.Stage}, changing to stage {next_stage}"); + // _logger.LogInformation("public void OnTick() -> Finished replay cycle for stage {Stage}, changing to stage {next_stage}", CurrentMap.ReplayManager.StageWR.Stage, next_stage); CurrentMap.ReplayManager.StageWR = CurrentMap.ReplayManager.AllStageWR[next_stage][0]; CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); CurrentMap.ReplayManager.StageWR.FormatBotName(); @@ -68,7 +73,7 @@ public void OnTick() CurrentMap.ReplayManager.AllBonusWR[next_bonus][0].Controller = CurrentMap.ReplayManager.BonusWR.Controller; - // Console.WriteLine($"======== public void OnTick() -> Finished replay cycle for bonus {CurrentMap.ReplayManager.BonusWR.Stage}, changing to bonus {next_bonus}"); + // _logger.LogInformation("public void OnTick() -> Finished replay cycle for bonus {Bonus}, changing to bonus {next_bonus}", CurrentMap.ReplayManager.BonusWR.Stage, next_bonus); CurrentMap.ReplayManager.BonusWR = CurrentMap.ReplayManager.AllBonusWR[next_bonus][0]; CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); CurrentMap.ReplayManager.BonusWR.FormatBotName(); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index e685bd7..59fdedd 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -60,6 +60,7 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti player.Stats.ThisRun.Ticks = player.Timer.Ticks; player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); } diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 1f169c8..bfc37dd 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -8,7 +8,7 @@ using CounterStrikeSharp.API.Modules.Utils; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using MySqlConnector; +using SurfTimer.Data; namespace SurfTimer; @@ -76,12 +76,14 @@ internal class Map public ReplayManager ReplayManager { get; set; } = null!; private readonly ILogger _logger; + private readonly IDataAccessService _dataService; // Constructor internal Map(string name) { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + _dataService = SurfTimer.ServiceProvider.GetRequiredService(); // Set map name this.Name = name; @@ -128,7 +130,7 @@ private async Task InitializeAsync([CallerMemberName] string methodName = "") // Start timing var stopwatch = Stopwatch.StartNew(); - await Get_Map_Info(); + await LoadMapInfo(); stopwatch.Stop(); _logger.LogInformation("[{ClassName}] {MethodName} -> We got MapID = {ID} ({Name}) in {ElapsedMilliseconds}ms | API = {API}", @@ -267,48 +269,33 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") /// Inserts a new map entry in the database. /// Automatically detects whether to use API Calls or MySQL query. /// - internal async Task Insert_Map_Info([CallerMemberName] string methodName = "") + internal async Task InsertMapInfo([CallerMemberName] string methodName = "") { - if (Config.API.GetApiOnly()) // API Calls + var mapInfo = new MapInfoDataModel { - API_MapInfo inserted = new() - { - id = -1, // Shouldn't really use this at all at api side - name = Name, - author = "Unknown", - tier = this.Tier, - stages = this.Stages, - bonuses = this.Bonuses, - ranked = 0, - }; - - var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, inserted); - - // Check if the response is not null and get the last_id - if (postResponse != null) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> Insert_Map_Info -> New map '{Name}' inserted, got ID {MapID}", - nameof(Map), methodName, Name, postResponse.last_id - ); - this.ID = postResponse.last_id; - } - - return; + Name = this.Name, + Author = "Unknown", // Or set appropriately + Tier = this.Tier, + Stages = this.Stages, + Bonuses = this.Bonuses, + Ranked = false + }; + + try + { + // this.ID = await _dataService.InsertMapInfoAsync(mapInfo); + int mapId = await _dataService.InsertMapInfoAsync(mapInfo); + this.ID = mapId; + _logger.LogInformation("[{ClassName}] {MethodName} -> Map '{Map}' inserted successfully with ID {ID}.", + nameof(Map), methodName, this.Name, this.ID + ); } - else // MySQL Queries + catch (Exception ex) { - int writerRows = await SurfTimer.DB.WriteAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(Name), "Unknown", this.Stages, this.Bonuses, 0, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds())); - if (writerRows != 1) - { - _logger.LogCritical("[{ClassName}] {MethodName} -> Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}", - nameof(Map), methodName, Name - ); - Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Insert_Map_Info -> Failed to write new map to database, this shouldn't happen. Map: {Name}"); - throw exception; - } - - await Get_Map_Info(false); + _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to insert map '{Map}'.", + nameof(Map), methodName, this.Name + ); + throw; } } @@ -316,45 +303,36 @@ internal async Task Insert_Map_Info([CallerMemberName] string methodName = "") /// Updates last played, stages, bonuses for the map in the database. /// Automatically detects whether to use API Calls or MySQL query. /// - internal async Task Update_Map_Info([CallerMemberName] string methodName = "") + internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") { - if (Config.API.GetApiOnly()) // API Calls + var mapInfo = new MapInfoDataModel { - API_MapInfo updated = new() - { - id = this.ID, - name = Name, - author = "Unknown", - tier = this.Tier, - stages = this.Stages, - bonuses = this.Bonuses, - ranked = 0, - last_played = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() - }; - - _ = ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, updated).Result; - } - else // MySQL Queries + ID = this.ID, + Name = this.Name, + Author = "Unknown", // adjust as necessary + Tier = this.Tier, + Stages = this.Stages, + Bonuses = this.Bonuses, + Ranked = false, + LastPlayed = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + + try { - // Update the map's last played data in the DB - string updateQuery = string.Format(Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, - (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.Stages, this.Bonuses, this.ID); + await _dataService.UpdateMapInfoAsync(mapInfo); #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Update MapData: {Query}", - nameof(Map), methodName, updateQuery + _logger.LogDebug("[{ClassName}] {MethodName} -> Updated map '{Map}' (ID: {ID}).", + nameof(Map), methodName, this.Name, this.ID ); #endif - - int lastPlayedUpdateRows = await SurfTimer.DB.WriteAsync(updateQuery); - if (lastPlayedUpdateRows != 1) - { - _logger.LogCritical("[{ClassName}] {MethodName} -> Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}", - nameof(Map), methodName, Name - ); - Exception exception = new($"CS2 Surf ERROR >> internal class Map -> internal async Task Update_Map_Info -> Failed to update map in database, this shouldn't happen. Map: {Name}"); - throw exception; - } + } + catch (Exception ex) + { + _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to update map '{Map}'.", + nameof(Map), methodName, this.Name + ); + throw; } } @@ -364,70 +342,42 @@ internal async Task Update_Map_Info([CallerMemberName] string methodName = "") /// Automatically detects whether to use API Calls or MySQL query. /// /// Should we run UPDATE query for the map - internal async Task Get_Map_Info(bool updateData = true, [CallerMemberName] string methodName = "") + internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] string methodName = "") { bool newMap = false; - if (Config.API.GetApiOnly()) // API Calls + var mapInfo = await _dataService.GetMapInfoAsync(this.Name); + + if (mapInfo != null) { - // Gather map information OR create entry - var mapinfo = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, Name)); - if (mapinfo != null) - { - this.ID = mapinfo.id; - this.Author = mapinfo.author; - this.Tier = mapinfo.tier; - this.Ranked = mapinfo.ranked == 1; - this.DateAdded = (int)mapinfo.date_added!; - this.LastPlayed = (int)mapinfo.last_played!; - } - else - { - newMap = true; - } + ID = mapInfo.ID; + Author = mapInfo.Author; + Tier = mapInfo.Tier; + Ranked = mapInfo.Ranked; + DateAdded = mapInfo.DateAdded; + LastPlayed = mapInfo.LastPlayed; } - else // MySQL queries + else { - // Gather map information OR create entry - using (var mapData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(Name)))) - { - 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.Author = mapData.GetString("author") ?? "Unknown"; - this.Tier = mapData.GetInt32("tier"); - this.Ranked = mapData.GetBoolean("ranked"); - this.DateAdded = mapData.GetInt32("date_added"); - this.LastPlayed = mapData.GetInt32("last_played"); - } - else - { - newMap = true; - } - } + newMap = true; } - // This is a new map if (newMap) { - await Insert_Map_Info(); + await InsertMapInfo(); return; } - // this.ReplayManager = new ReplayManager(this.ID, this.Stages > 0, this.Bonuses > 0); - - // Will skip updating the data in the case where we have just inserted a new map with MySQL Queries and called this method again in order to get the Map ID if (updateData) - await Update_Map_Info(); + await UpdateMapInfo(); var stopwatch = Stopwatch.StartNew(); - await Get_Map_Record_Runs(); + await LoadMapRecordRuns(); stopwatch.Stop(); + #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Finished Get_Map_Record_Runs in {ElapsedMilliseconds}ms | API = {API}", - nameof(Map), methodName, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() - ); + _logger.LogDebug("[{ClassName}] {MethodName} -> Finished LoadMapRecordRuns in {Elapsed}ms | API = {API}", + nameof(Map), methodName, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly()); #endif } @@ -438,182 +388,86 @@ internal async Task Get_Map_Info(bool updateData = true, [CallerMemberName] stri /// Automatically detects whether to use API Calls or MySQL query. /// TODO: Re-do the API with the new query and fix the API assign of values /// - internal async Task Get_Map_Record_Runs([CallerMemberName] string methodName = "") + internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { - int totalMapRuns = 0; - int totalStageRuns = 0; - int totalBonusRuns = 0; + // int totalMapRuns = 0; + // int totalStageRuns = 0; + // int totalBonusRuns = 0; this.ConnectedMapTimes.Clear(); - int qType; - int qStage; - int qStyle; - - // Replay Stuff - JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; + var runs = await _dataService.GetMapRecordRunsAsync(this.ID); - if (Config.API.GetApiOnly()) // Need to update the query in API and re-do the assigning of data + foreach (var run in runs) { - // // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, style, type)); - // var maptimes = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, this.ID, 0, 0)); - // if (maptimes == null) - // { - // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> No map runs found for {this.Name} (MapID {this.ID} | Style {qStyle} | Type {qStyle})"); - // this.MapCompletions[qStyle] = 0; - // return; - // } - - // Console.WriteLine($"======= CS2 Surf DEBUG API >> public async Task Get_Map_Record_Runs -> Got {maptimes.Length} map runs for MapID {this.ID} (Style {qStyle} | Type {qStyle})"); - // // To-do: Implement bonuses WR - // // To-do: Implement stages WR - // foreach (var time in maptimes) - // { - // if (totalMapRuns == 0) // First row is always the fastest run for the map, style, type combo - // { - // this.WR[qStyle].ID = time.id; // WR ID for the Map and Style combo - // this.WR[qStyle].Ticks = time.run_time; // Fastest run time (WR) for the Map and Style combo - // this.WR[qStyle].StartVelX = time.start_vel_x; // Fastest run start velocity X for the Map and Style combo - // this.WR[qStyle].StartVelY = time.start_vel_y; // Fastest run start velocity Y for the Map and Style combo - // this.WR[qStyle].StartVelZ = time.start_vel_z; // Fastest run start velocity Z for the Map and Style combo - // this.WR[qStyle].EndVelX = time.end_vel_x; // Fastest run end velocity X for the Map and Style combo - // this.WR[qStyle].EndVelY = time.end_vel_y; // Fastest run end velocity Y for the Map and Style combo - // this.WR[qStyle].EndVelZ = time.end_vel_z; // Fastest run end velocity Z for the Map and Style combo - // this.WR[qStyle].RunDate = time.run_date; // Fastest run date for the Map and Style combo - // this.WR[qStyle].Name = time.name; // Fastest run player name for the Map and Style combo - // } - // this.ConnectedMapTimes.Add(time.id); - // totalMapRuns++; - // } - // // this.ConnectedMapTimes.Remove(this.WR[style].ID); // ?? - // // this.MapCompletions[style] = maptimes.Length; - } - else // MySQL Queries - { - // Get map world record - using (var mapWrData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, this.ID))) + switch (run.Type) { - if (mapWrData.HasRows) - { - while (mapWrData.Read()) - { - qType = mapWrData.GetInt32("type"); - qStage = mapWrData.GetInt32("stage"); - qStyle = mapWrData.GetInt32("style"); - - // Retrieve replay_frames as string from MySQL - string replayFramesBase64; - - // Option A: Try to get the string directly - try - { - replayFramesBase64 = mapWrData.GetString("replay_frames"); - } - catch (InvalidCastException) - { - // Option B: Get the data as byte[] and convert to string - byte[] replayFramesData = mapWrData.GetFieldValue("replay_frames"); - replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); - } + case 0: // Map WR data and total completions + WR[run.Style].ID = run.ID; + WR[run.Style].Ticks = run.RunTime; + WR[run.Style].StartVelX = run.StartVelX; + WR[run.Style].StartVelY = run.StartVelY; + WR[run.Style].StartVelZ = run.StartVelZ; + WR[run.Style].EndVelX = run.EndVelX; + WR[run.Style].EndVelY = run.EndVelY; + WR[run.Style].EndVelZ = run.EndVelZ; + WR[run.Style].RunDate = run.RunDate; + WR[run.Style].Name = run.Name; + // totalMapRuns = run.TotalCount; + ConnectedMapTimes.Add(run.ID); + MapCompletions[run.Style] = run.TotalCount; + + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + break; - // Populate parameters for all the MapTime rows found - switch (qType) - { - case 0: // Map WR data and total completions - this.WR[qStyle].ID = mapWrData.GetInt32("id"); - this.WR[qStyle].Ticks = mapWrData.GetInt32("run_time"); - this.WR[qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); - this.WR[qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); - this.WR[qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); - this.WR[qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); - this.WR[qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); - this.WR[qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); - this.WR[qStyle].RunDate = mapWrData.GetInt32("run_date"); - this.WR[qStyle].Name = mapWrData.GetString("name"); - totalMapRuns = mapWrData.GetInt32("total_count"); - this.ConnectedMapTimes.Add(mapWrData.GetInt32("id")); - this.MapCompletions[qStyle] = totalMapRuns; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [MapWR] Sending style {qStyle} to `ReplayManager`: Map ID {this.ID} | Stages {this.Stages > 0} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); - - // Populate the ReplayManager for Map WR only if no replay exists or a new WR was set - if (this.ReplayManager.MapWR.MapID == -1 || this.WR[qStyle].Ticks < this.ReplayManager.MapWR.RecordRunTime) - { - Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); - } - break; - case 1: // Bonus WR data and total completions - this.BonusWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); - this.BonusWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); - this.BonusWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); - this.BonusWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); - this.BonusWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); - this.BonusWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); - this.BonusWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); - this.BonusWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); - this.BonusWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); - this.BonusWR[qStage][qStyle].Name = mapWrData.GetString("name"); - totalBonusRuns = mapWrData.GetInt32("total_count"); - this.BonusCompletions[qStage][qStyle] = totalBonusRuns; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Got Bonus {qStage}"); - - // Populate the ReplayManager for all bonuses found and set the first bonus to replay - if (this.ReplayManager.BonusWR != null) - { - Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); - } - break; - case 2: // Stage WR data and total completions - this.StageWR[qStage][qStyle].ID = mapWrData.GetInt32("id"); - this.StageWR[qStage][qStyle].Ticks = mapWrData.GetInt32("run_time"); - this.StageWR[qStage][qStyle].StartVelX = mapWrData.GetFloat("start_vel_x"); - this.StageWR[qStage][qStyle].StartVelY = mapWrData.GetFloat("start_vel_y"); - this.StageWR[qStage][qStyle].StartVelZ = mapWrData.GetFloat("start_vel_z"); - this.StageWR[qStage][qStyle].EndVelX = mapWrData.GetFloat("end_vel_x"); - this.StageWR[qStage][qStyle].EndVelY = mapWrData.GetFloat("end_vel_y"); - this.StageWR[qStage][qStyle].EndVelZ = mapWrData.GetFloat("end_vel_z"); - this.StageWR[qStage][qStyle].RunDate = mapWrData.GetInt32("run_date"); - this.StageWR[qStage][qStyle].Name = mapWrData.GetString("name"); - totalStageRuns = mapWrData.GetInt32("total_count"); - this.StageCompletions[qStage][qStyle] = totalStageRuns; - - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> [StageWR] Sending style {qStyle} to `ReplayManager.StageWR`: Map ID {this.ID} | Stages {this.Stages > 0} - {qStage} | Bonuses {this.Bonuses > 0} | Run Time {this.WR[qStyle].Ticks} | Name {this.WR[qStyle].Name} | MapTime ID {this.WR[qStyle].ID}"); - - // Populate the ReplayManager for all stages found and set the first stage to replay - if (this.ReplayManager.StageWR != null) - { - Set_Replay_Data(qType, qStyle, qStage, replayFramesBase64); - } - break; - } + case 1: // Bonus WR data and total completions + BonusWR[run.Stage][run.Style].ID = run.ID; + BonusWR[run.Stage][run.Style].Ticks = run.RunTime; + BonusWR[run.Stage][run.Style].StartVelX = run.StartVelX; + BonusWR[run.Stage][run.Style].StartVelY = run.StartVelY; + BonusWR[run.Stage][run.Style].StartVelZ = run.StartVelZ; + BonusWR[run.Stage][run.Style].EndVelX = run.EndVelX; + BonusWR[run.Stage][run.Style].EndVelY = run.EndVelY; + BonusWR[run.Stage][run.Style].EndVelZ = run.EndVelZ; + BonusWR[run.Stage][run.Style].RunDate = run.RunDate; + BonusWR[run.Stage][run.Style].Name = run.Name; + BonusCompletions[run.Stage][run.Style] = run.TotalCount; + + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + break; - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal async Task Get_Map_Record_Runs -> Map Completions for style {qStyle} {this.MapCompletions[qStyle]}"); - } - } + case 2: // Stage WR data and total completions + StageWR[run.Stage][run.Style].ID = run.ID; + StageWR[run.Stage][run.Style].Ticks = run.RunTime; + StageWR[run.Stage][run.Style].StartVelX = run.StartVelX; + StageWR[run.Stage][run.Style].StartVelY = run.StartVelY; + StageWR[run.Stage][run.Style].StartVelZ = run.StartVelZ; + StageWR[run.Stage][run.Style].EndVelX = run.EndVelX; + StageWR[run.Stage][run.Style].EndVelY = run.EndVelY; + StageWR[run.Stage][run.Style].EndVelZ = run.EndVelZ; + StageWR[run.Stage][run.Style].RunDate = run.RunDate; + StageWR[run.Stage][run.Style].Name = run.Name; + StageCompletions[run.Stage][run.Style] = run.TotalCount; + + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + break; } } - // Retrieve the checkpoints for each Style if it has been set. foreach (int style in Config.Styles) { - // if (this.MapCompletions[style] > 0 && this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime) // This should also reload Checkpoints if a new MapWR is set - if ( - this.MapCompletions[style] > 0 && this.WR[style].ID != -1 || - this.WR[style].ID != -1 && this.WR[style].Ticks < this.ReplayManager.MapWR.RecordRunTime - ) // This should also reload Checkpoints if a new MapWR is set + if (MapCompletions[style] > 0 && WR[style].ID != -1) { #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Get_Map_Record_Runs : Map -> [{DBorAPI}] Loaded {MapCompletions} runs (MapID {MapID} | Style {Style}). WR by {PlayerName} - {Time}", + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadMapRecordRuns : Map -> [{DBorAPI}] Loaded {MapCompletions} runs (MapID {MapID} | Style {Style}). WR by {PlayerName} - {Time}", nameof(Map), methodName, (Config.API.GetApiOnly() ? "API" : "DB"), this.MapCompletions[style], this.ID, style, this.WR[style].Name, PlayerHUD.FormatTime(this.WR[style].Ticks) ); #endif var stopwatch = Stopwatch.StartNew(); - await Get_Record_Run_Checkpoints(style); + await this.WR[style].LoadCheckpoints(); // Load the checkpoints for the WR and Style combo stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished Get_Record_Run_Checkpoints({Style}) in {ElapsedMilliseconds}ms | API = {API}", + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished WR.[{Style}].LoadCheckpoints() in {ElapsedMilliseconds}ms | API = {API}", nameof(Map), methodName, style, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() ); } @@ -621,13 +475,13 @@ internal async Task Get_Map_Record_Runs([CallerMemberName] string methodName = " } /// - /// Redirects to `PersonalBest.PB_LoadCheckpointsData()`. + /// Redirects to `PersonalBest.LoadCheckpoints()`. /// Extracts all entries from Checkpoints table of the World Record for the given `style` /// /// Style to load internal async Task Get_Record_Run_Checkpoints(int style = 0) { - await this.WR[style].PB_LoadCheckpointsData(); + await this.WR[style].LoadCheckpoints(); } /// @@ -638,7 +492,7 @@ internal async Task Get_Record_Run_Checkpoints(int style = 0) /// Style to add /// Stage to add /// Base64 encoded string for the replay_frames - internal void Set_Replay_Data(int type, int style, int stage, string replayFramesBase64, [CallerMemberName] string methodName = "") + internal void SetReplayData(int type, int style, int stage, string replayFramesBase64, [CallerMemberName] string methodName = "") { JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() } }; @@ -651,7 +505,10 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame switch (type) { case 0: // Map Replays - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); + _logger.LogTrace("[{ClassName}] {MethodName} -> SetReplayData -> [MapWR] Setting run {RunID} {RunTime} (Ticks = {RunTicks}; Frames = {TotalFrames})", + nameof(Map), methodName, this.WR[style].ID, PlayerHUD.FormatTime(this.WR[style].Ticks), this.WR[style].Ticks, frames.Count + ); if (this.ReplayManager.MapWR.IsPlaying) this.ReplayManager.MapWR.Stop(); @@ -704,7 +561,7 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].Ticks) break; #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Set_Replay_Data -> [BonusWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllBonusWR`", + _logger.LogDebug("[{ClassName}] {MethodName} -> SetReplayData -> [BonusWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllBonusWR`", nameof(Map), methodName, this.BonusWR[stage][style].ID, PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks), this.BonusWR[stage][style].Ticks, frames.Count ); #endif @@ -757,7 +614,7 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) break; #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Set_Replay_Data -> [StageWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllStageWR`", + _logger.LogDebug("[{ClassName}] {MethodName} -> SetReplayData -> [StageWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllStageWR`", nameof(Map), methodName, this.StageWR[stage][style].ID, PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks), this.StageWR[stage][style].Ticks, frames.Count ); #endif @@ -811,19 +668,19 @@ internal void Set_Replay_Data(int type, int style, int stage, string replayFrame // Start the new map replay if none existed until now if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [MapWR] ResetReplay() and Start()"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] ResetReplay() and Start()"); this.ReplayManager.MapWR.ResetReplay(); this.ReplayManager.MapWR.Start(); } else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [BonusWR] ResetReplay() and Start() {stage}"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [BonusWR] ResetReplay() and Start() {stage}"); this.ReplayManager.BonusWR.ResetReplay(); this.ReplayManager.BonusWR.Start(); } else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void Set_Replay_Data -> [StageWR] ResetReplay() and Start() {stage}"); + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [StageWR] ResetReplay() and Start() {stage}"); this.ReplayManager.StageWR.ResetReplay(); this.ReplayManager.StageWR.Start(); } diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index d204154..7f28516 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -57,6 +57,9 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P } } + /// + /// Displays the Center HUD for the client + /// public void Display() { if (!_player.Controller.IsValid) diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 790fb64..7f9124b 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Data; namespace SurfTimer; @@ -14,11 +15,14 @@ internal class PlayerProfile public int LastSeen { get; set; } = 0; public int Connections { get; set; } = 0; private readonly ILogger _logger; + private readonly IDataAccessService _dataService; public PlayerProfile(ulong steamId, string name = "", string country = "") { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + _dataService = SurfTimer.ServiceProvider.GetRequiredService(); + this.SteamID = steamId; this.Name = name; @@ -42,7 +46,7 @@ public static async Task CreateAsync(ulong steamId, string name = private async Task InitializeAsync([CallerMemberName] string methodName = "") { - await Get_Player_Profile(); + await GetPlayerProfile(); _logger.LogTrace("[{ClassName}] {MethodName} -> InitializeAsync -> [{ConnType}] We got ProfileID {ProfileID} ({PlayerName})", nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.ID, this.Name @@ -52,63 +56,49 @@ private async Task InitializeAsync([CallerMemberName] string methodName = "") /// /// Retrieves all the data for the player from the database. /// - public async Task Get_Player_Profile([CallerMemberName] string methodName = "") + public async Task GetPlayerProfile([CallerMemberName] string methodName = "") { - bool newPlayer = false; + var profile = await _dataService.GetPlayerProfileAsync(this.SteamID); - // Load player profile data from database - using (var playerData = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, this.SteamID))) + if (profile != null) + { + this.ID = profile.ID; + this.Name = profile.Name; + if (this.Country == "XX" && profile.Country != "XX") + this.Country = profile.Country; + this.JoinDate = profile.JoinDate; + this.LastSeen = profile.LastSeen; + this.Connections = profile.Connections; + } + else { - if (playerData.HasRows && playerData.Read()) - { - // Player exists in database - this.ID = playerData.GetInt32("id"); - this.Name = playerData.GetString("name"); - if (this.Country == "XX" && playerData.GetString("country") != "XX") - this.Country = playerData.GetString("country"); - this.JoinDate = playerData.GetInt32("join_date"); - this.LastSeen = playerData.GetInt32("last_seen"); - this.Connections = playerData.GetInt32("connections"); - } - else - { - newPlayer = true; - } + await InsertPlayerProfile(); } #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Get_Player_Profile -> [{ConnType}] Returning player {PlayerName} ({SteamID}) loaded with ID {ProfileID}.", + _logger.LogDebug("[{ClassName}] {MethodName} -> GetPlayerProfile -> [{ConnType}] Loaded player {PlayerName} ({SteamID}) with ID {ProfileID}.", nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID ); #endif - if (newPlayer) - await Insert_Player_Profile(); } /// /// Insert new player information into the database. /// Retrieves the ID of the newly created player. /// - public async Task Insert_Player_Profile([CallerMemberName] string methodName = "") + public async Task InsertPlayerProfile([CallerMemberName] string methodName = "") { - // Player does not exist in database - int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - int lastSeen = joinDate; - int connections = 1; - - // Write new player to database - int newPlayerRows = await SurfTimer.DB.WriteAsync(string.Format( - Config.MySQL.Queries.DB_QUERY_PP_INSERT_PROFILE, - MySqlConnector.MySqlHelper.EscapeString(this.Name), this.SteamID, this.Country, joinDate, lastSeen, connections)); - if (newPlayerRows != 1) + var profile = new PlayerProfileDataModel { - Exception ex = new($"Error inserting new player profile for '{this.Name}' ({this.SteamID})"); - throw ex; - } + SteamID = this.SteamID, + Name = this.Name, + Country = this.Country + }; + + this.ID = await _dataService.InsertPlayerProfileAsync(profile); - await Get_Player_Profile(); #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Insert_Player_Profile -> [{ConnType}] New player {PlayerName} ({SteamID}) added with ID {ProfileID}.", + _logger.LogDebug("[{ClassName}] {MethodName} -> InsertPlayerProfile -> [{ConnType}] New player {PlayerName} ({SteamID}) added with ID {ProfileID}.", nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID ); #endif @@ -119,18 +109,19 @@ public async Task Insert_Player_Profile([CallerMemberName] string methodName = " /// /// Player Name /// - public async Task Update_Player_Profile(string name, [CallerMemberName] string methodName = "") + public async Task UpdatePlayerProfile(string name, [CallerMemberName] string methodName = "") { - int updatePlayerTask = await SurfTimer.DB.WriteAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, this.Country, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), this.ID, name)); - if (updatePlayerTask != 1) + this.Name = name; + await _dataService.UpdatePlayerProfileAsync(new PlayerProfileDataModel { - _logger.LogError("[{ClassName}] {MethodName} -> Update_Player_Profile -> [{ConnType}] Failed to update data in database. Player {PlayerName} ({SteamID})", - nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID - ); - throw new Exception($"CS2 Surf ERROR >> internal class PlayerProfile -> Update_Player_Profile -> [{(Config.API.GetApiOnly() ? "API" : "DB")}] Failed to update player data in database. Player: {this.Name} ({this.SteamID})"); - } + ID = this.ID, + SteamID = this.SteamID, + Name = this.Name, + Country = this.Country + }); + #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> Update_Player_Profile -> [{ConnType}] Updated player {PlayerName} ({SteamID}) in database with ID {ProfileID}.", + _logger.LogDebug("[{ClassName}] {MethodName} -> UpdatePlayerProfile -> [{ConnType}] Updated player {PlayerName} ({SteamID}) with ID {ProfileID}.", nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.Name, this.SteamID, this.ID ); #endif diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index 85b6254..6470561 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -1,15 +1,23 @@ -namespace SurfTimer; - -internal class Checkpoint : PersonalBest +public class Checkpoint { public int CP { get; set; } + public int Ticks { 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 EndTouch { get; set; } public int Attempts { get; set; } + public int ID { get; set; } + + public Checkpoint() { } public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int 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???? + Ticks = ticks; StartVelX = startVelX; StartVelY = startVelY; StartVelZ = startVelZ; @@ -19,4 +27,4 @@ public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float sta 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 index 1489be5..02fb74e 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -2,55 +2,29 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Data; namespace SurfTimer; /// /// This class stores data for the current run. /// -internal class CurrentRun +internal class CurrentRun : RunStats { - public Dictionary Checkpoints { 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; } private readonly ILogger _logger; - // Add other properties as needed + private readonly IDataAccessService _dataService; - // Constructor - public CurrentRun() + + public CurrentRun() : base() { - // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); - - Checkpoints = new Dictionary(); - Ticks = 0; - StartVelX = 0.0f; - StartVelY = 0.0f; - StartVelZ = 0.0f; - EndVelX = 0.0f; - EndVelY = 0.0f; - EndVelZ = 0.0f; - RunDate = 0; + _dataService = SurfTimer.ServiceProvider.GetRequiredService(); } - public void Reset() + public override void Reset() { - Checkpoints.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 + base.Reset(); + // Reset other properties as needed (specific for this class) } /// @@ -62,116 +36,161 @@ public void Reset() /// Ticks for the run - used for Stage and Bonus entries public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") { - // Add entry in DB for the run - // PrintSituations(player); - string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); + string replay_frames = ""; + + /* Test Time Saving */ + if (methodName != "TestSetPb") + replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); - _logger.LogTrace("[{ClassName}] {MethodName} -> SaveMapTime -> Sending total of {ReplayFramesTotal} replay frames.", - nameof(CurrentRun), methodName, replay_frames.Length - ); + + _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} replay frames.", + nameof(CurrentRun), methodName, replay_frames.Length); var stopwatch = Stopwatch.StartNew(); + int recType = stage != 0 ? 2 : bonus != 0 ? 1 : 0; - if (Config.API.GetApiOnly()) - { - return; - } - else + var mapTime = new MapTimeDataModel { - await InsertMapTime(player, bonus, stage, run_ticks, replay_frames, true); + PlayerId = player.Profile.ID, + MapId = player.CurrMap.ID, + Style = player.Timer.Style, + Type = recType, + Stage = stage != 0 ? stage : bonus, + Ticks = run_ticks == -1 ? this.Ticks : run_ticks, + StartVelX = this.StartVelX, + StartVelY = this.StartVelY, + StartVelZ = this.StartVelZ, + EndVelX = this.EndVelX, + EndVelY = this.EndVelY, + EndVelZ = this.EndVelZ, + ReplayFramesBase64 = replay_frames, + Checkpoints = this.Checkpoints // Test out + }; - if (stage != 0 || bonus != 0) + /* Test Time Saving */ + if (methodName == "TestSetPb") + { + // 1. Dummy Checkpoint + var dummyCheckpoint = new Checkpoint { - _logger.LogTrace("[{ClassName}] {MethodName} -> Inserted an entry for {Type} {Number} - {Ticks}", - nameof(CurrentRun), methodName, (stage != 0 ? "Stage" : "Bonus"), (stage != 0 ? stage : bonus), run_ticks - ); - } - else + CP = 1, + Ticks = 1234, + EndTouch = 1, + StartVelX = 111.1f, + StartVelY = 222.2f, + StartVelZ = 333.3f, + EndVelX = 444.4f, + EndVelY = 555.5f, + EndVelZ = 666.6f, + Attempts = 2 + }; + + // 2. Dummy Dictionary за mapTime.Checkpoints + var dummyCheckpointsDict = new Dictionary { - await SaveCurrentRunCheckpoints(player, true); // Save this run's checkpoints - } + { dummyCheckpoint.CP, dummyCheckpoint } + }; - await player.CurrMap.Get_Map_Record_Runs(); // Reload the times for the Map + mapTime = new MapTimeDataModel + { + PlayerId = player.Profile.ID, + MapId = player.CurrMap.ID, + Style = player.Timer.Style, + Type = 0, + Stage = stage != 0 ? stage : bonus, + Ticks = 666, + StartVelX = this.StartVelX, + StartVelY = this.StartVelY, + StartVelZ = this.StartVelZ, + EndVelX = this.EndVelX, + EndVelY = this.EndVelY, + EndVelZ = this.EndVelZ, + ReplayFramesBase64 = replay_frames, + Checkpoints = dummyCheckpointsDict + }; } + /* END Test Time Saving */ + + await _dataService.InsertMapTimeAsync(mapTime); + + if (recType == 0 && !Config.API.GetApiOnly()) + await SaveCurrentRunCheckpoints(player, true); + + await player.CurrMap.LoadMapRecordRuns(); stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", + _logger.LogInformation("[{Class}] {Method} -> Finished SaveMapTime for '{Name}' in {Elapsed}ms | API = {API}", nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() ); } - public void PrintSituations(Player player) - { - Console.WriteLine($"========================== FOUND SITUATIONS =========================="); - for (int i = 0; i < player.ReplayRecorder.Frames.Count; i++) + /* + public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") { - ReplayFrame x = player.ReplayRecorder.Frames[i]; - switch (x.Situation) + string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); + + _logger.LogTrace("[{ClassName}] {MethodName} -> SaveMapTime -> Sending total of {ReplayFramesTotal} replay frames.", + nameof(CurrentRun), methodName, replay_frames.Length + ); + + var stopwatch = Stopwatch.StartNew(); + + if (Config.API.GetApiOnly()) { - case ReplayFrameSituation.START_ZONE_ENTER: - Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.START_ZONE_EXIT: - Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: - Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.END_ZONE_ENTER: - Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.END_ZONE_EXIT: - Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {x.Situation}"); - break; + return; } + else + { + await InsertMapTime(player, bonus, stage, run_ticks, replay_frames, true); + + if (stage != 0 || bonus != 0) + { + _logger.LogTrace("[{ClassName}] {MethodName} -> Inserted an entry for {Type} {Number} - {Ticks}", + nameof(CurrentRun), methodName, (stage != 0 ? "Stage" : "Bonus"), (stage != 0 ? stage : bonus), run_ticks + ); + } + else + { + await SaveCurrentRunCheckpoints(player, true); // Save this run's checkpoints + } + + await player.CurrMap.LoadMapRecordRuns(); // Reload the times for the Map + } + + stopwatch.Stop(); + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", + nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + ); } - Console.WriteLine("========================== MapSituations: {MapSituations} | START_ZONE_ENTER = {START_ZONE_ENTER} | START_ZONE_EXIT = {START_ZONE_EXIT} | END_ZONE_ENTER = {END_ZONE_ENTER} ==========================", player.ReplayRecorder.MapSituations.Count, player.ReplayRecorder.MapSituations[0], player.ReplayRecorder.MapSituations[1], player.ReplayRecorder.MapSituations[2]); - Console.WriteLine("========================== Total Frames: {TotalFrames} ==========================", player.ReplayRecorder.Frames.Count); - } + */ /// /// Saves the CurrentRun of the player to the database. Does NOT support Bonus entries yet. /// - /// Player object - /// Bonus number - /// Stage number - /// Ticks for the run - /// Replay frames - /// Whether to reload the PersonalBest data for the Player. - /// public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false, [CallerMemberName] string methodName = "") { int playerId = player.Profile.ID; int mapId = player.CurrMap.ID; int style = player.Timer.Style; - int ticks = run_ticks == -1 ? player.Stats.ThisRun.Ticks : run_ticks; - // int ticks = player.Stats.ThisRun.Ticks; + int ticks = run_ticks == -1 ? this.Ticks : run_ticks; int type = stage != 0 ? 2 : bonus != 0 ? 1 : 0; - float startVelX = player.Stats.ThisRun.StartVelX; - float startVelY = player.Stats.ThisRun.StartVelY; - float startVelZ = player.Stats.ThisRun.StartVelZ; - float endVelX = player.Stats.ThisRun.EndVelX; - float endVelY = player.Stats.ThisRun.EndVelY; - float endVelZ = player.Stats.ThisRun.EndVelZ; + float startVelX = this.StartVelX; + float startVelY = this.StartVelY; + float startVelZ = this.StartVelZ; + float endVelX = this.EndVelX; + float endVelY = this.EndVelY; + float endVelZ = this.EndVelZ; var stopwatch = Stopwatch.StartNew(); - if (Config.API.GetApiOnly()) // API Calls + if (Config.API.GetApiOnly()) { // API Insert map goes here } - else // MySQL Queries + else { - int updatePlayerRunTask = await SurfTimer.DB.WriteAsync( + // int updatePlayerRunTask = await SurfTimer.DB.WriteAsync( + var (updatePlayerRunTask, lastId) = await SurfTimer.DB.WriteAsync( string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, playerId, mapId, style, type, type == 2 ? stage : type == 1 ? bonus : 0, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), replay_frames)); if (updatePlayerRunTask <= 0) { @@ -187,21 +206,21 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID ); - await player.Stats.PB[style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) + await player.Stats.PB[style].LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) } else if (reloadData && type == 1) { _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.BonusPB[bonus][style].ID ); - await player.Stats.BonusPB[bonus][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) + await player.Stats.BonusPB[bonus][style].LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) } else if (reloadData && type == 2) { _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.StagePB[stage][style].ID ); - await player.Stats.StagePB[stage][style].PB_LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) + await player.Stats.StagePB[stage][style].LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) } } @@ -213,13 +232,54 @@ public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int /// /// Saves the `CurrentRunCheckpoints` dictionary to the database + /// API deals with this when sending a SaveMapTime of type 0, so we do not have an endpoint for it /// /// Player object /// Whether to reload the PersonalBest Checkpoints data for the Player. public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false, [CallerMemberName] string methodName = "") + { + _logger.LogInformation("[{ClassName}] {MethodName} -> Saving {Count} checkpoints...", + nameof(CurrentRun), methodName, this.Checkpoints.Count); + + var stopwatch = Stopwatch.StartNew(); + + var checkpoints = this.Checkpoints.Select(cp => new Checkpoint + { + CP = cp.Key, + Ticks = cp.Value.Ticks, + EndTouch = cp.Value.EndTouch, + StartVelX = cp.Value.StartVelX, + StartVelY = cp.Value.StartVelY, + StartVelZ = cp.Value.StartVelZ, + EndVelX = cp.Value.EndVelX, + EndVelY = cp.Value.EndVelY, + EndVelZ = cp.Value.EndVelZ, + Attempts = cp.Value.Attempts + }); + + int mapTimeId = player.Stats.PB[player.Timer.Style].ID; + + // await _dataService.SaveRunCheckpointsAsync(mapTimeId, checkpoints); + + this.Checkpoints.Clear(); + + if (reloadData) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> Reloading Checkpoints data for '{Name}' (ID {MapTimeID})", + nameof(CurrentRun), methodName, player.Profile.Name, mapTimeId); + await player.Stats.PB[player.Timer.Style].LoadCheckpoints(); + } + + stopwatch.Stop(); + _logger.LogInformation("[{ClassName}] {MethodName} -> Finished saving checkpoints for '{Name}' in {Elapsed}ms | API = {API}", + nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly()); + } + + /* Delete after testing it using the ApiDataAccessService + public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will send {ThisRunCheckpoints} ({CheckpointsCount}) checkpoints to DB....", - nameof(CurrentRun), methodName, player.Stats.ThisRun.Checkpoints.Count, this.Checkpoints.Count + nameof(CurrentRun), methodName, this.Checkpoints.Count, this.Checkpoints.Count ); var stopwatch = Stopwatch.StartNew(); @@ -250,8 +310,6 @@ public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = fal ); #endif - // Insert/Update CPs to database - // 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) { string command = string.Format( @@ -262,14 +320,14 @@ public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = fal } } await SurfTimer.DB.TransactionAsync(commands); - player.Stats.ThisRun.Checkpoints.Clear(); + this.Checkpoints.Clear(); if (reloadData) { _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will reload Checkpoints data for '{Name}' (ID {MapTimeID})", nameof(CurrentRun), methodName, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID ); - await player.Stats.PB[player.Timer.Style].PB_LoadCheckpointsData(); // Load the Checkpoints data again + await player.Stats.PB[player.Timer.Style].LoadCheckpoints(); // Load the Checkpoints data again } stopwatch.Stop(); @@ -277,4 +335,47 @@ public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = fal nameof(CurrentRun), methodName, reloadData, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() ); } + +*/ + public void PrintSituations(Player player) + { + Console.WriteLine($"========================== FOUND SITUATIONS =========================="); + for (int i = 0; i < player.ReplayRecorder.Frames.Count; i++) + { + ReplayFrame x = player.ReplayRecorder.Frames[i]; + switch (x.Situation) + { + case ReplayFrameSituation.START_ZONE_ENTER: + Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.START_ZONE_EXIT: + Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_ENTER: + Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.STAGE_ZONE_EXIT: + Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: + Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_ENTER: + Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {x.Situation}"); + break; + case ReplayFrameSituation.END_ZONE_EXIT: + Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {x.Situation}"); + break; + } + } + Console.WriteLine("========================== MapSituations: {0} | START_ZONE_ENTER = {1} | START_ZONE_EXIT = {2} | END_ZONE_ENTER = {3} ==========================", + player.ReplayRecorder.MapSituations.Count, + player.ReplayRecorder.MapSituations[0], player.ReplayRecorder.MapSituations[1], player.ReplayRecorder.MapSituations[2]); + Console.WriteLine("========================== Total Frames: {0} ==========================", player.ReplayRecorder.Frames.Count); + } + } + diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index cfd77fb..b2155a3 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Data; namespace SurfTimer; @@ -8,218 +9,169 @@ namespace SurfTimer; /// As the PersonalBest object is being used for each different style, we shouldn't need a separate `Style` variable in here because each style entry will have unique ID in the Database /// and will therefore be a unique PersonalBest entry. /// -internal class PersonalBest +internal class PersonalBest : RunStats { 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 Checkpoints { get; set; } - public int Type { get; set; } // Identifies bonus # - 0 for map time -> huh, why o_O? - 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; } + public int Type { get; set; } = -1; // Identifies bonus # - 0 for map time -> huh, why o_O? public string Name { get; set; } = ""; // This is used only for WRs private readonly ILogger _logger; + private readonly IDataAccessService _dataService; // Add other properties as needed // Constructor - public PersonalBest() + public PersonalBest() : base() { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); - - Ticks = -1; - Checkpoints = new Dictionary(); - Type = -1; - StartVelX = -1.0f; - StartVelY = -1.0f; - StartVelZ = -1.0f; - EndVelX = -1.0f; - EndVelY = -1.0f; - EndVelZ = -1.0f; - RunDate = -1; + _dataService = SurfTimer.ServiceProvider.GetRequiredService(); } /// /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. - /// Automatically detects whether to use API Calls or MySQL query. /// Bonus and Stage runs should NOT have any checkpoints. /// - public async Task PB_LoadCheckpointsData([CallerMemberName] string methodName = "") + public async Task LoadCheckpoints([CallerMemberName] string methodName = "") { - if (this == null) + // 1) ask the data service for your checkpoints + var cps = await _dataService.LoadCheckpointsAsync(this.ID); + + // 2) if none, just return + if (cps == null || cps.Count == 0) { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> PersonalBest object is null.", - nameof(PersonalBest), methodName + _logger.LogInformation( + "[{Class}] {Method} -> No checkpoints found for run {RunId}.", + nameof(PersonalBest), methodName, this.ID ); -#endif return; } - if (this.Checkpoints == null) - { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> PB Checkpoints list is not initialized.", - nameof(PersonalBest), methodName - ); -#endif - this.Checkpoints = new Dictionary(); // Initialize if null - } - if (Config.API.GetApiOnly()) // Load with API - { - var checkpoints = await ApiMethod.GET(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, this.ID)); - if (checkpoints == null || checkpoints.Length == 0) - return; - - foreach (API_Checkpoint checkpoint in checkpoints) - { - Checkpoint cp = new Checkpoint - ( - checkpoint.cp, - checkpoint.run_time, - checkpoint.start_vel_x, - checkpoint.start_vel_y, - checkpoint.start_vel_z, - checkpoint.end_vel_x, - checkpoint.end_vel_y, - checkpoint.end_vel_z, - checkpoint.end_touch, - checkpoint.attempts - ); - cp.ID = checkpoint.cp; - - this.Checkpoints[cp.CP] = cp; - } - } - else // Load with MySQL - { - using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, this.ID))) - { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading from DB: this.Checkpoint.Count {RunCheckpointsCount} | this.ID {RunID} | this.Ticks {RunTicks} | this.RunDate {RunDate}", - nameof(PersonalBest), methodName, this.Checkpoints.Count, this.ID, this.Ticks, this.RunDate - ); -#endif - - if (!results.HasRows) - { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> No checkpoints found for this mapTimeId {RunID}.", - nameof(PersonalBest), methodName, this.ID - ); -#endif - - return; - } - - while (results.Read()) - { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading Checkpoint: Checkpoint {Checkpoint} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}", - nameof(PersonalBest), methodName, results.GetInt32("cp"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") - ); -#endif - - Checkpoint cp = new(results.GetInt32("cp"), - results.GetInt32("run_time"), - 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("end_touch"), - results.GetInt32("attempts")); - cp.ID = results.GetInt32("cp"); - // To-do: cp.ID = calculate Rank # from DB - - this.Checkpoints[cp.CP] = cp; - } - } - } + // 3) otherwise assign + this.Checkpoints = cps; - // #if DEBUG - _logger.LogInformation("[{ClassName}] {MethodName} -> PB_LoadCheckpointsData -> Loading Checkpoint: [{Type}] {TotalCheckpoints} Checkpoints loaded for run ID {RunID}.", - nameof(PersonalBest), methodName, (Config.API.GetApiOnly() ? "API" : "DB"), this.Checkpoints.Count, this.ID + // 4) log how many you got + _logger.LogInformation( + "[{ClassName}] {MethodName} -> Loaded {Count} checkpoints for run {RunId}.", + nameof(PersonalBest), methodName, cps.Count, this.ID ); - // #endif } + /// /// Loads specific type/style MapTime data for the player (run without checkpoints) from the database for their personal best runs. /// Should be used to reload data from a specific `PersonalBest` object /// /// Player object - public async Task PB_LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") + public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") { - // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); - // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) - if (this == null) + // 1) call the data service, passing only the primitives: + var model = await _dataService.LoadPersonalBestRunAsync( + pbId: this.ID == -1 ? (int?)null : this.ID, + playerId: player.Profile.ID, + mapId: SurfTimer.CurrentMap.ID, + type: this.Type, + style: player.Timer.Style + ); + + // 2) if nothing found, log & return + if (model == null) { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null.", - nameof(PersonalBest), methodName + _logger.LogTrace( + "[{ClassName}] {MethodName} -> No personal best found for player {Player} (ID={Id}).", + nameof(PersonalBest), methodName, + player.Profile.Name, player.Profile.ID ); -#endif - return; } - MySqlConnector.MySqlDataReader? results = null; + // 3) map back into your instance + this.ID = model.ID; + this.Ticks = model.Ticks; + this.Rank = model.Rank; + this.StartVelX = model.StartVelX; + this.StartVelY = model.StartVelY; + this.StartVelZ = model.StartVelZ; + this.EndVelX = model.EndVelX; + this.EndVelY = model.EndVelY; + this.EndVelZ = model.EndVelZ; + this.RunDate = model.RunDate; + // this.ReplayFrames = model.ReplayFrames; // Won't work with MySQL load? + + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Loaded PB run {RunId} for {Player}.", + nameof(PersonalBest), methodName, + this.ID, player.Profile.Name + ); + } - // Console.WriteLine(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + /* Delete after testing with API? + public async Task PB_LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") + { + // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); + // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) + if (this == null) + { + #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null.", + nameof(PersonalBest), methodName + ); + #endif - if (this.ID == -1) - results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); - else - results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, this.ID)); + return; + } -#if DEBUG - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.ID {this.ID} "); - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.Ticks {this.Ticks} "); - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.RunDate {this.RunDate} "); -#endif + MySqlConnector.MySqlDataReader? results = null; - if (results == null || !results.HasRows) - { - // #if DEBUG - _logger.LogTrace("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{playerName}' ({playerID}). (Results Null? {IsNull})", - nameof(PersonalBest), methodName, player.Profile.Name, player.Profile.ID, results == null - ); - // #endif + // Console.WriteLine(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); - return; - } + if (this.ID == -1) + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); + else + results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, this.ID)); - while (results.Read()) - { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> Loading MapTime Run: RunID {RunID} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}.", - nameof(PersonalBest), methodName, results.GetInt32("id"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") + #if DEBUG + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.ID {this.ID} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.Ticks {this.Ticks} "); + Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.RunDate {this.RunDate} "); + #endif + + if (results == null || !results.HasRows) + { + // #if DEBUG + _logger.LogTrace("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{playerName}' ({playerID}). (Results Null? {IsNull})", + nameof(PersonalBest), methodName, player.Profile.Name, player.Profile.ID, results == null + ); + // #endif + + return; + } + + while (results.Read()) + { + #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> Loading MapTime Run: RunID {RunID} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}.", + nameof(PersonalBest), methodName, results.GetInt32("id"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") + ); + #endif + + this.ID = results.GetInt32("id"); + this.Ticks = results.GetInt32("run_time"); + this.Rank = results.GetInt32("rank"); + this.StartVelX = (float)results.GetDouble("start_vel_x"); + this.StartVelY = (float)results.GetDouble("start_vel_y"); + this.StartVelZ = (float)results.GetDouble("start_vel_z"); + this.EndVelX = (float)results.GetDouble("end_vel_x"); + this.EndVelY = (float)results.GetDouble("end_vel_y"); + this.EndVelZ = (float)results.GetDouble("end_vel_z"); + this.RunDate = results.GetInt32("run_date"); + } + + // #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> MapTime ID {ID} (Type: {Type}) loaded for '{PlayerName}' with time {RunTime}", + nameof(PersonalBest), methodName, this.ID, this.Type, player.Profile.Name, PlayerHUD.FormatTime(this.Ticks) ); -#endif - - this.ID = results.GetInt32("id"); - this.Ticks = results.GetInt32("run_time"); - this.Rank = results.GetInt32("rank"); - this.StartVelX = (float)results.GetDouble("start_vel_x"); - this.StartVelY = (float)results.GetDouble("start_vel_y"); - this.StartVelZ = (float)results.GetDouble("start_vel_z"); - this.EndVelX = (float)results.GetDouble("end_vel_x"); - this.EndVelY = (float)results.GetDouble("end_vel_y"); - this.EndVelZ = (float)results.GetDouble("end_vel_z"); - this.RunDate = results.GetInt32("run_date"); + // #endif } + */ - // #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> MapTime ID {ID} (Type: {Type}) loaded for '{PlayerName}' with time {RunTime}", - nameof(PersonalBest), methodName, this.ID, this.Type, player.Profile.Name, PlayerHUD.FormatTime(this.Ticks) - ); - // #endif - } } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index ae9c647..aeefda4 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Data; namespace SurfTimer; @@ -40,6 +41,9 @@ internal class PlayerStats /// public CurrentRun ThisRun { get; set; } = new CurrentRun(); private readonly ILogger _logger; + private readonly IDataAccessService _dataService; + + // Initialize PersonalBest for each `style` (e.g., 0 for normal) // Here we can loop through all available styles at some point and initialize them @@ -47,6 +51,7 @@ public PlayerStats([CallerMemberName] string methodName = "") { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + _dataService = SurfTimer.ServiceProvider.GetRequiredService(); // Initialize MapPB for each style foreach (int style in Config.Styles) @@ -72,7 +77,7 @@ public PlayerStats([CallerMemberName] string methodName = "") ); } - // API + // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=0"); @@ -110,7 +115,7 @@ public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] s } } - // API + // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=2"); @@ -138,7 +143,7 @@ public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] } } - // API + // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=1"); @@ -174,99 +179,180 @@ public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] /// public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") { - using (var playerStats = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) + var playerMapTimes = await _dataService.GetPlayerMapTimesAsync(player.Profile.ID, SurfTimer.CurrentMap.ID); + + if (!playerMapTimes.Any()) { - // int style = player.Timer.Style; - int style; - if (!playerStats.HasRows) + _logger.LogTrace("[{ClassName}] {MethodName} -> No MapTimes data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID); + return; + } + + foreach (var mapTime in playerMapTimes) + { + int style = mapTime.Style; + switch (mapTime.Type) { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> No MapTimes data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; + case 1: // Bonus time +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", nameof(PlayerStats), methodName); +#endif + BonusPB[mapTime.Stage][style].ID = mapTime.ID; + BonusPB[mapTime.Stage][style].Ticks = mapTime.RunTime; + BonusPB[mapTime.Stage][style].Type = mapTime.Type; + BonusPB[mapTime.Stage][style].Rank = mapTime.Rank; + BonusPB[mapTime.Stage][style].StartVelX = mapTime.StartVelX; + BonusPB[mapTime.Stage][style].StartVelY = mapTime.StartVelY; + BonusPB[mapTime.Stage][style].StartVelZ = mapTime.StartVelZ; + BonusPB[mapTime.Stage][style].EndVelX = mapTime.EndVelX; + BonusPB[mapTime.Stage][style].EndVelY = mapTime.EndVelY; + BonusPB[mapTime.Stage][style].EndVelZ = mapTime.EndVelZ; + BonusPB[mapTime.Stage][style].RunDate = mapTime.RunDate; + break; + + case 2: // Stage time +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", nameof(PlayerStats), methodName); +#endif + StagePB[mapTime.Stage][style].ID = mapTime.ID; + StagePB[mapTime.Stage][style].Ticks = mapTime.RunTime; + StagePB[mapTime.Stage][style].Type = mapTime.Type; + StagePB[mapTime.Stage][style].Rank = mapTime.Rank; + StagePB[mapTime.Stage][style].StartVelX = mapTime.StartVelX; + StagePB[mapTime.Stage][style].StartVelY = mapTime.StartVelY; + StagePB[mapTime.Stage][style].StartVelZ = mapTime.StartVelZ; + StagePB[mapTime.Stage][style].EndVelX = mapTime.EndVelX; + StagePB[mapTime.Stage][style].EndVelY = mapTime.EndVelY; + StagePB[mapTime.Stage][style].EndVelZ = mapTime.EndVelZ; + StagePB[mapTime.Stage][style].RunDate = mapTime.RunDate; + break; + + default: // Map time +#if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", nameof(PlayerStats), methodName); +#endif + PB[style].ID = mapTime.ID; + PB[style].Ticks = mapTime.RunTime; + PB[style].Type = mapTime.Type; + PB[style].Rank = mapTime.Rank; + PB[style].StartVelX = mapTime.StartVelX; + PB[style].StartVelY = mapTime.StartVelY; + PB[style].StartVelZ = mapTime.StartVelZ; + PB[style].EndVelX = mapTime.EndVelX; + PB[style].EndVelY = mapTime.EndVelY; + PB[style].EndVelZ = mapTime.EndVelZ; + PB[style].RunDate = mapTime.RunDate; + + await PB[style].LoadCheckpoints(); + break; } - while (playerStats.Read()) - { - // Load data into each PersonalBest object - if (playerStats.GetInt32("type") == 1) // Bonus time - { + #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", - nameof(PlayerStats), methodName - ); + _logger.LogDebug("[{ClassName}] {MethodName} -> Loaded PB[{Style}] run {RunID} (Rank {Rank}) for '{PlayerName}' (ID {PlayerID}).", + nameof(PlayerStats), methodName, style, mapTime.ID, mapTime.Rank, player.Profile.Name, player.Profile.ID); #endif - int bonusNum = playerStats.GetInt32("stage"); - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - BonusPB[bonusNum][style].ID = playerStats.GetInt32("id"); - BonusPB[bonusNum][style].Ticks = playerStats.GetInt32("run_time"); - BonusPB[bonusNum][style].Type = playerStats.GetInt32("type"); - BonusPB[bonusNum][style].Rank = playerStats.GetInt32("rank"); - BonusPB[bonusNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - BonusPB[bonusNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - BonusPB[bonusNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - BonusPB[bonusNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - BonusPB[bonusNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - BonusPB[bonusNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - BonusPB[bonusNum][style].RunDate = playerStats.GetInt32("run_date"); - } - else if (playerStats.GetInt32("type") == 2) // Stage time + } + } + + + /* + public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") + { + using (var playerStats = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) + { + // int style = player.Timer.Style; + int style; + if (!playerStats.HasRows) { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", - nameof(PlayerStats), methodName + _logger.LogTrace("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> No MapTimes data found for Player {PlayerName} (ID {PlayerID}).", + nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID ); -#endif - int stageNum = playerStats.GetInt32("stage"); - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - StagePB[stageNum][style].ID = playerStats.GetInt32("id"); - StagePB[stageNum][style].Ticks = playerStats.GetInt32("run_time"); - StagePB[stageNum][style].Type = playerStats.GetInt32("type"); - StagePB[stageNum][style].Rank = playerStats.GetInt32("rank"); - StagePB[stageNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - StagePB[stageNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - StagePB[stageNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - StagePB[stageNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); - // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: - // StagePB[{stageNum}][{style}] = - // Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}" - // ); + return; } - else // Map time + while (playerStats.Read()) { -#if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", - nameof(PlayerStats), methodName + // Load data into each PersonalBest object + if (playerStats.GetInt32("type") == 1) // Bonus time + { + #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", + nameof(PlayerStats), methodName + ); + #endif + int bonusNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + BonusPB[bonusNum][style].ID = playerStats.GetInt32("id"); + BonusPB[bonusNum][style].Ticks = playerStats.GetInt32("run_time"); + BonusPB[bonusNum][style].Type = playerStats.GetInt32("type"); + BonusPB[bonusNum][style].Rank = playerStats.GetInt32("rank"); + BonusPB[bonusNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + BonusPB[bonusNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + BonusPB[bonusNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + BonusPB[bonusNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + BonusPB[bonusNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + BonusPB[bonusNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + BonusPB[bonusNum][style].RunDate = playerStats.GetInt32("run_date"); + } + else if (playerStats.GetInt32("type") == 2) // Stage time + { + #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", + nameof(PlayerStats), methodName + ); + #endif + int stageNum = playerStats.GetInt32("stage"); + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + StagePB[stageNum][style].ID = playerStats.GetInt32("id"); + StagePB[stageNum][style].Ticks = playerStats.GetInt32("run_time"); + StagePB[stageNum][style].Type = playerStats.GetInt32("type"); + StagePB[stageNum][style].Rank = playerStats.GetInt32("rank"); + StagePB[stageNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); + StagePB[stageNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); + StagePB[stageNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); + StagePB[stageNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); + StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); + StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); + StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); + // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: + // StagePB[{stageNum}][{style}] = + // Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}" + // ); + } + else // Map time + { + #if DEBUG + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", + nameof(PlayerStats), methodName + ); + #endif + style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented + PB[style].ID = playerStats.GetInt32("id"); + PB[style].Ticks = playerStats.GetInt32("run_time"); + PB[style].Type = playerStats.GetInt32("type"); + PB[style].Rank = playerStats.GetInt32("rank"); + 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].RunDate = playerStats.GetInt32("run_date"); + // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: + // PB[{style}] = + // ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}" + // ); + await this.PB[style].LoadCheckpoints(); + } + // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> Loaded PB[{Style}] run {RunID} (Rank {Rank}) for '{PlayerName}' (ID {PlayerID}).", + nameof(PlayerStats), methodName, style, PB[style].ID, PB[style].Rank, player.Profile.Name, player.Profile.ID ); -#endif - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - PB[style].ID = playerStats.GetInt32("id"); - PB[style].Ticks = playerStats.GetInt32("run_time"); - PB[style].Type = playerStats.GetInt32("type"); - PB[style].Rank = playerStats.GetInt32("rank"); - 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].RunDate = playerStats.GetInt32("run_date"); - // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: - // PB[{style}] = - // ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}" - // ); - await this.PB[style].PB_LoadCheckpointsData(); + #endif } - // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> Loaded PB[{Style}] run {RunID} (Rank {Rank}) for '{PlayerName}' (ID {PlayerID}).", - nameof(PlayerStats), methodName, style, PB[style].ID, PB[style].Rank, player.Profile.Name, player.Profile.ID - ); -#endif } } - } + */ + } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/RunStats.cs b/src/ST-Player/PlayerStats/RunStats.cs new file mode 100644 index 0000000..c60fce2 --- /dev/null +++ b/src/ST-Player/PlayerStats/RunStats.cs @@ -0,0 +1,38 @@ +namespace SurfTimer; + +public abstract class RunStats +{ + public Dictionary Checkpoints { get; set; } + public int Ticks { 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; } + public string ReplayFramesBase64 { get; set; } = ""; + + protected RunStats() + { + Checkpoints = new Dictionary(); + Ticks = 0; + StartVelX = 0.0f; + StartVelY = 0.0f; + StartVelZ = 0.0f; + EndVelX = 0.0f; + EndVelY = 0.0f; + EndVelZ = 0.0f; + RunDate = 0; + } + + // Shared Method + public virtual void Reset() + { + Checkpoints.Clear(); + Ticks = 0; + StartVelX = StartVelY = StartVelZ = 0.0f; + EndVelX = EndVelY = EndVelZ = 0.0f; + RunDate = 0; + } +} diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index b7eba1e..fa098da 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -211,24 +211,25 @@ public void LoadReplayData(int repeat_count = -1, [CallerMemberName] string meth if (!this.IsPlayable || !this.IsEnabled) return; - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] public void LoadReplayData -> We got MapID = {this.MapID}"); - if (this.MapID == -1) { - _logger.LogTrace("[{ClassName}] {MethodName} -> [{Type}] No replay data found for Player.", - nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay") + _logger.LogWarning("[{ClassName}] {MethodName} -> [{Type}] No replay data found for Player. MapID {MapID} | MapTimeID {MapTimeID} | RecordPlayerName {RecordPlayerName}", + nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : this.Type == 0 ? "Map Replay" : "Unknown Type"), this.MapID, this.MapTimeID, RecordPlayerName ); return; } - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> public void LoadReplayData -> [{(this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : "Map Replay")}] Loaded replay data for Player '{this.RecordPlayerName}'. MapTime ID: {this.MapTimeID} | Repeat {repeat_count} | Frames {this.Frames.Count} | Ticks {this.RecordRunTime}"); + _logger.LogTrace("[{ClassName}] {MethodName} -> [{Type}] Loaded replay data for Player '{RecordPlayerName}' | MapTime ID: {MapTimeID} | Repeat {Repeat} | Frames {TotalFrames} | Ticks {RecordTicks}", + nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : this.Type == 0 ? "Map Replay" : "Unknown Type"), this.RecordPlayerName, this.MapTimeID, repeat_count, this.Frames.Count, this.RecordRunTime + ); + this.ResetReplay(); this.RepeatCount = repeat_count; } - public void FormatBotName() + public void FormatBotName([CallerMemberName] string methodName = "") { - if (!this.IsPlayable || !this.IsEnabled) + if (!this.IsPlayable || !this.IsEnabled || this.MapID == -1) return; string prefix; @@ -254,5 +255,9 @@ public void FormatBotName() bot_name.Set(replay_name); Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName"); + + // _logger.LogTrace("[{ClassName}] {MethodName} -> Changed replay bot name from '{OldName}' to '{NewName}'", + // nameof(ReplayPlayer), methodName, bot_name, replay_name + // ); } } \ No newline at end of file diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 4018d8e..0d5067c 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -85,11 +85,28 @@ public static class Endpoints public const string ENDPOINT_MAP_INSERT_INFO = "/surftimer/insertmap"; public const string ENDPOINT_MAP_UPDATE_INFO = "/surftimer/updateMap"; // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maptotals?map_id={0}&style={1}"; - public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?map_id={0}&style={1}&type={2}"; + // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?map_id={0}&style={1}&type={2}"; + public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?id={0}"; public const string ENDPOINT_MAP_GET_RUN_CPS = "/surftimer/mapcheckpointsdata?maptime_id={0}"; // CurrentRun.cs + public const string ENDPOINT_CR_SAVE_MAP_TIME = "/surftimer/savemaptime"; public const string ENDPOINT_CR_SAVE_STAGE_TIME = "/surftimer/savestagetime"; + + + // PersonalBest.cs + public const string ENDPOINT_MAP_GET_PB_BY_PLAYER = "/surftimer/runbyplayer?player_id={0}&map_id={1}&type={2}&style={3}"; + public const string ENDPOINT_MAP_GET_PB_BY_ID = "/surftimer/runbyid?run_id={0}"; + + + // PlayerProfile.cs + public const string ENDPOINT_PP_GET_PROFILE = "/surftimer/playersurfprofile/{0}"; + public const string ENDPOINT_PP_INSERT_PROFILE = "/surftimer/insertplayer"; + public const string ENDPOINT_PP_UPDATE_PROFILE = "/surftimer/updateplayerprofile"; + + // PlayerStats.cs + public const string ENDPOINT_PS_GET_PLAYER_MAP_DATA = "/surftimer/playermapdata?player_id={0}&map_id={1}"; + } } @@ -124,15 +141,8 @@ public static string GetConnectionString() public static class Queries { // Map.cs related queries - public const string DB_QUERY_MAP_GET_RUNS = @" - SELECT MapTimes.*, Player.name - FROM MapTimes - JOIN Player ON MapTimes.player_id = Player.id - WHERE MapTimes.map_id = {0} AND MapTimes.style = {1} AND MapTimes.type = {2} - ORDER BY MapTimes.run_time ASC; - "; // Deprecated public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; - public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {5})"; // "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()})" + public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, bonuses, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {6}, {6})"; // "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()})" public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2} WHERE id={3};"; public const string DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT = @" SELECT diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs new file mode 100644 index 0000000..7995b67 --- /dev/null +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -0,0 +1,393 @@ +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace SurfTimer.Data +{ + public class ApiDataAccessService : IDataAccessService + { + private readonly ILogger _logger; + + public ApiDataAccessService() + { + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + } + + /* PersonalBest.cs */ + /// + /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. + /// Bonus and Stage runs should NOT have any checkpoints. + /// + public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + { + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using API data access service.", + nameof(ApiDataAccessService), methodName + ); + + var checkpoints = await ApiMethod + .GET( + string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) + ); + if (checkpoints == null || checkpoints.Length == 0) + return new Dictionary(); + + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints.", + nameof(ApiDataAccessService), methodName, checkpoints.Length + ); + + return checkpoints + .Select(cp => + { + var c = new Checkpoint( + cp.cp, + cp.run_time, + cp.start_vel_x, + cp.start_vel_y, + cp.start_vel_z, + cp.end_vel_x, + cp.end_vel_y, + cp.end_vel_z, + cp.end_touch, + cp.attempts + ); + c.ID = cp.cp; + return c; + }) + .ToDictionary(c => c.CP, c => c); + } + + public async Task LoadPersonalBestRunAsync( + int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "" + ) + { + string url = pbId == null || pbId == -1 + ? string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, + playerId, mapId, type, style) + : string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, + pbId.Value); + + var apiResult = await ApiMethod.GET(url); + if (apiResult == null) + return null; + + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Personal Best data", + nameof(ApiDataAccessService), methodName + ); + + return new PersonalBestDataModel + { + ID = apiResult.id, + Ticks = apiResult.run_time, + Rank = apiResult.rank, + StartVelX = apiResult.start_vel_x, + StartVelY = apiResult.start_vel_y, + StartVelZ = apiResult.start_vel_z, + EndVelX = apiResult.end_vel_x, + EndVelY = apiResult.end_vel_y, + EndVelZ = apiResult.end_vel_z, + RunDate = apiResult.run_date, + ReplayFramesBase64 = apiResult.replay_frames + }; + } + + + /* Map.cs */ + public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + { + var mapInfo = await ApiMethod.GET( + string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); + + if (mapInfo != null) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data", + nameof(ApiDataAccessService), methodName + ); + + return new MapInfoDataModel + { + ID = mapInfo.id, + Name = mapInfo.name, + Author = mapInfo.author, + Tier = mapInfo.tier, + Stages = mapInfo.stages, + Bonuses = mapInfo.bonuses, + Ranked = mapInfo.ranked == 1, + DateAdded = mapInfo.date_added ?? 0, + LastPlayed = mapInfo.last_played ?? 0 + }; + } + + return null; + } + + public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + { + var apiMapInfo = new API_MapInfo + { + id = -1, // API-side will ignore or auto-increment + name = mapInfo.Name, + author = mapInfo.Author, + tier = mapInfo.Tier, + stages = mapInfo.Stages, + bonuses = mapInfo.Bonuses, + ranked = mapInfo.Ranked ? 1 : 0, + }; + + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, apiMapInfo); + + if (postResponse == null || postResponse.last_id <= 0) + { + throw new Exception($"API failed to insert map '{mapInfo.Name}'."); + } + + return postResponse.last_id; + } + + public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + { + var apiMapInfo = new API_MapInfo + { + id = mapInfo.ID, + name = mapInfo.Name, + author = mapInfo.Author, + tier = mapInfo.Tier, + stages = mapInfo.Stages, + bonuses = mapInfo.Bonuses, + ranked = mapInfo.Ranked ? 1 : 0, + // last_played = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + + var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, apiMapInfo); + if (response == null) + { + throw new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapInfo.ID})."); + } + } + + /// + /// Gets and loads all the record times for a given map ID + /// + /// ID of the map in DB + /// + public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + { + // TODO: Re-do the API with the new query and fix the API assign of values + var apiRuns = await ApiMethod.GET( + string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId)); + + var runs = new List(); + + if (apiRuns != null) + { + foreach (var time in apiRuns) + { + runs.Add(new MapRecordRunDataModel + { + ID = time.id, + RunTime = time.run_time, + Type = time.type, // API currently returns only map times, needs rework + Stage = time.stage, + Style = time.style, // Fix this when updating API + Name = time.name, + StartVelX = (float)time.start_vel_x, + StartVelY = (float)time.start_vel_y, + StartVelZ = (float)time.start_vel_z, + EndVelX = (float)time.end_vel_x, + EndVelY = (float)time.end_vel_y, + EndVelZ = (float)time.end_vel_z, + RunDate = time.run_date, + TotalCount = time.total_count, // API should return total count, fix this as well + ReplayFramesBase64 = time.replay_frames // API should return this + }); + } + } + + return runs; + } + + + /* PlayerProfile.cs */ + public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + { + // TODO: Implement API logic + // throw new NotImplementedException(); + + var player = await ApiMethod.GET( + string.Format(Config.API.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); + + if (player != null) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data", + nameof(ApiDataAccessService), methodName + ); + return new PlayerProfileDataModel + { + ID = player.id, + // SteamID = steamId, + Name = player.name, + Country = player.country, + JoinDate = player.join_date, + LastSeen = player.last_seen, + Connections = player.connections + }; + } + + _logger.LogWarning("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> No PlayerProfile data found for {SteamID}", + nameof(ApiDataAccessService), methodName, steamId + ); + return null; + } + + public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + { + // TODO: Implement API logic + // throw new NotImplementedException(); + int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + var apiPlayerProfileInfo = new API_PlayerSurfProfile + { + steam_id = profile.SteamID, + name = profile.Name, + country = profile.Country, + join_date = joinDate, + last_seen = joinDate, + connections = 1 + }; + + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, apiPlayerProfileInfo); + + if (postResponse == null || postResponse.last_id <= 0) + { + throw new Exception($"API failed to insert Player Profile for '{profile.Name}'."); + } + + return postResponse.last_id; + } + + public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + { + // TODO: Implement API logic + // throw new NotImplementedException(); + int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + var apiPlayerProfileInfo = new API_PlayerSurfProfile + { + id = profile.ID, + steam_id = profile.SteamID, + name = profile.Name, + country = profile.Country, + join_date = profile.JoinDate, + last_seen = lastSeen, + connections = 1 + }; + + var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, apiPlayerProfileInfo); + if (response == null) + { + throw new Exception($"API failed to update Player Profile for '{apiPlayerProfileInfo.name}' (ID {apiPlayerProfileInfo.id})."); + } + } + + + /* PlayerStats.cs */ + public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + { + // TODO: Implement API logic + var mapTimes = new List(); + + var apiResponse = await ApiMethod.GET( + string.Format(Config.API.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) + ); + + if (apiResponse != null) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", + nameof(ApiDataAccessService), methodName, playerId, mapId + ); + + foreach (var time in apiResponse) + { + mapTimes.Add(new PlayerMapTimeDataModel + { + ID = time.id, + RunTime = time.run_time, + Type = time.type, + Stage = time.stage, + Style = time.style, + Rank = time.rank, + StartVelX = (float)time.start_vel_x, + StartVelY = (float)time.start_vel_y, + StartVelZ = (float)time.start_vel_z, + EndVelX = (float)time.end_vel_x, + EndVelY = (float)time.end_vel_y, + EndVelZ = (float)time.end_vel_z, + RunDate = time.run_date, + ReplayFramesBase64 = time.replay_frames + }); + } + } + + return mapTimes; + } + + + + /* CurrentRun.cs */ + public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") + { + // Convert the Checkpoint object to the API_Checkpoint one + var runCheckpoints = mapTime.Checkpoints.Select(cp => new API_Checkpoint + { + cp = cp.Key, + run_time = cp.Value.Ticks, + end_touch = cp.Value.EndTouch, + start_vel_x = cp.Value.StartVelX, + start_vel_y = cp.Value.StartVelY, + start_vel_z = cp.Value.StartVelZ, + end_vel_x = cp.Value.EndVelX, + end_vel_y = cp.Value.EndVelY, + end_vel_z = cp.Value.EndVelZ, + attempts = cp.Value.Attempts + }).ToList(); + + var apiSaveMapTime = new API_SaveMapTime + { + player_id = mapTime.PlayerId, + map_id = mapTime.MapId, + run_time = mapTime.Ticks, + start_vel_x = mapTime.StartVelX, + start_vel_y = mapTime.StartVelY, + start_vel_z = mapTime.StartVelZ, + end_vel_x = mapTime.EndVelX, + end_vel_y = mapTime.EndVelY, + end_vel_z = mapTime.EndVelZ, + style = mapTime.Style, + type = mapTime.Type, + stage = mapTime.Stage, + replay_frames = mapTime.ReplayFramesBase64, + run_date = mapTime.RunDate, + checkpoints = runCheckpoints.ToList() + }; + + var postResponse = await ApiMethod.POST( + Config.API.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, + apiSaveMapTime + ); + + if (postResponse == null || postResponse.last_id <= 0) + { + throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); + } + + return postResponse.last_id; + } + + // public async Task SaveRunCheckpointsAsync(int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "") + // { + // // TODO: Implement API logic + // // throw new NotImplementedException(); + + // } + + } +} diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs new file mode 100644 index 0000000..0efc60a --- /dev/null +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -0,0 +1,78 @@ +using System.Runtime.CompilerServices; +using CounterStrikeSharp.API.Modules.Entities; + +namespace SurfTimer.Data +{ + public interface IDataAccessService + { + /* PersonalBest.cs */ + /// + /// Load all checkpoints for a given run ID (map time). + /// Returns an empty dictionary if none found. + /// + Task> LoadCheckpointsAsync( + int runId, + [CallerMemberName] string methodName = "" + ); + + /// + /// Load a personal-best run for a given player. + /// - If pbId is null or -1, load by playerId/mapId/type/style. + /// - If pbId has a value, load that specific run. + /// Returns null if not found. + /// + Task LoadPersonalBestRunAsync( + int? pbId, + int playerId, + int mapId, + int type, + int style, + [CallerMemberName] string methodName = "" + ); + + + /* Map.cs */ + Task GetMapInfoAsync( + string mapName, [CallerMemberName] string methodName = "" + ); + Task InsertMapInfoAsync( + MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" + ); + Task UpdateMapInfoAsync( + MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" + ); + Task> GetMapRecordRunsAsync( + int mapId, [CallerMemberName] string methodName = "" + ); + + + /* PlayerProfile.cs */ + Task GetPlayerProfileAsync( + ulong steamId, [CallerMemberName] string methodName = "" + ); + Task InsertPlayerProfileAsync( + PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" + ); + Task UpdatePlayerProfileAsync( + PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" + ); + + + /* PlayerStats.cs */ + Task> GetPlayerMapTimesAsync( + int playerId, int mapId, [CallerMemberName] string methodName = "" + ); + + + /* CurrentRun.cs */ + Task InsertMapTimeAsync( + // Task InsertMapTimeAsync( + MapTimeDataModel mapTime, [CallerMemberName] string methodName = "" + ); + /* Merged with InsertMapTimeAsync */ + // Task SaveRunCheckpointsAsync( + // int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "" + // ); + + } +} diff --git a/src/ST-UTILS/Data/Models.cs b/src/ST-UTILS/Data/Models.cs new file mode 100644 index 0000000..9456f19 --- /dev/null +++ b/src/ST-UTILS/Data/Models.cs @@ -0,0 +1,66 @@ +// File: Data/PersonalBestDataModel.cs +namespace SurfTimer.Data +{ + public class PersonalBestDataModel : RunStats + { + public int ID { get; set; } + public int Rank { get; set; } + } + + public class MapInfoDataModel + { + public int ID { get; set; } + public string Name { get; set; } = "N/A"; + public string Author { get; set; } = "Unknown"; + public int Tier { get; set; } + public int Stages { get; set; } + public int Bonuses { get; set; } + public bool Ranked { get; set; } + public int DateAdded { get; set; } + public int LastPlayed { get; set; } + } + + + public class MapRecordRunDataModel : RunStats + { + public int ID { get; set; } + public int RunTime { get; set; } + public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage + public int Stage { get; set; } + public int Style { get; set; } + public string Name { get; set; } = ""; + public int TotalCount { get; set; } + // public string ReplayFramesBase64 { get; set; } = ""; + } + + + public class PlayerProfileDataModel + { + public int ID { get; set; } = 0; + public string Name { get; set; } = ""; + public ulong SteamID { get; set; } = 0; + public string Country { get; set; } = ""; + public int JoinDate { get; set; } = 0; + public int LastSeen { get; set; } = 0; + public int Connections { get; set; } = 0; + } + + public class PlayerMapTimeDataModel : RunStats + { + public int ID { get; set; } + public int RunTime { get; set; } + public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage + public int Stage { get; set; } + public int Style { get; set; } + public int Rank { get; set; } + } + + public class MapTimeDataModel : RunStats + { + public int PlayerId { get; set; } + public int MapId { get; set; } + public int Style { get; set; } + public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage + public int Stage { get; set; } + } +} diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs new file mode 100644 index 0000000..eb85e88 --- /dev/null +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -0,0 +1,378 @@ +using MySqlConnector; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using System.Runtime.CompilerServices; +using System.Data; + +namespace SurfTimer.Data +{ + public class MySqlDataAccessService : IDataAccessService + { + private readonly ILogger _logger; + + public MySqlDataAccessService() + { + _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + } + + /* PersonalBest.cs */ + /// + /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. + /// Bonus and Stage runs should NOT have any checkpoints. + /// + public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + { + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using MySQL data access service.", + nameof(MySqlDataAccessService), methodName + ); + + var dict = new Dictionary(); + + using (var results = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, runId))) + { + if (results == null || !results.HasRows) + return dict; + + while (results.Read()) + { + var cp = new Checkpoint( + results.GetInt32("cp"), + results.GetInt32("run_time"), + 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("end_touch"), + results.GetInt32("attempts") + ); + cp.ID = results.GetInt32("cp"); + dict[cp.CP] = cp; + } + } + + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints.", + nameof(MySqlDataAccessService), methodName, dict.Count + ); + + return dict; + } + + public async Task LoadPersonalBestRunAsync( + int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "" + ) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Using MySQL data access service.", + nameof(MySqlDataAccessService), methodName + ); + + string sql = pbId == null || pbId == -1 + ? string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, + playerId, mapId, type, style) + : string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, + pbId.Value); + + using var results = await SurfTimer.DB.QueryAsync(sql); + if (results == null || !results.HasRows) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> No data found. PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", + nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style + ); + return null; + } + + // read the first (and only) row + await results.ReadAsync(); + + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Found data for PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", + nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style + ); + + return new PersonalBestDataModel + { + ID = results.GetInt32("id"), + Ticks = results.GetInt32("run_time"), + Rank = results.GetInt32("rank"), + StartVelX = (float)results.GetDouble("start_vel_x"), + StartVelY = (float)results.GetDouble("start_vel_y"), + StartVelZ = (float)results.GetDouble("start_vel_z"), + EndVelX = (float)results.GetDouble("end_vel_x"), + EndVelY = (float)results.GetDouble("end_vel_y"), + EndVelZ = (float)results.GetDouble("end_vel_z"), + RunDate = results.GetInt32("run_date") + }; + } + + + /* Map.cs */ + public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + { + using var mapData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(mapName))); + + if (mapData.HasRows && mapData.Read()) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data", + nameof(MySqlDataAccessService), methodName + ); + + return new MapInfoDataModel + { + ID = mapData.GetInt32("id"), + Name = mapName, + Author = mapData.GetString("author") ?? "Unknown", + Tier = mapData.GetInt32("tier"), + Ranked = mapData.GetBoolean("ranked"), + DateAdded = mapData.GetInt32("date_added"), + LastPlayed = mapData.GetInt32("last_played"), + }; + } + + return null; + } + + public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + { + // int rowsWritten = await SurfTimer.DB.WriteAsync( + var (rowsWritten, lastId) = await SurfTimer.DB.WriteAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, + MySqlHelper.EscapeString(mapInfo.Name), + MySqlHelper.EscapeString(mapInfo.Author), + mapInfo.Tier, + mapInfo.Stages, + mapInfo.Bonuses, + mapInfo.Ranked ? 1 : 0, + (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + ); + + if (rowsWritten != 1) + { + throw new Exception($"Failed to insert new map '{mapInfo.Name}' into database."); + } + + return (int)lastId; + } + + public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + { + string updateQuery = string.Format( + Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, + (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + mapInfo.Stages, + mapInfo.Bonuses, + mapInfo.ID + ); + + var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(updateQuery); + if (rowsUpdated != 1) + { + throw new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapInfo.ID}) in database."); + } + } + + public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + { + var runs = new List(); + + using var results = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, mapId)); + + if (results.HasRows) + { + while (results.Read()) + { + string replayFramesBase64; + + try + { + replayFramesBase64 = results.GetString("replay_frames"); + } + catch (InvalidCastException) + { + byte[] replayFramesData = results.GetFieldValue("replay_frames"); + replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); + } + + runs.Add(new MapRecordRunDataModel + { + ID = results.GetInt32("id"), + RunTime = results.GetInt32("run_time"), + Type = results.GetInt32("type"), + Stage = results.GetInt32("stage"), + Style = results.GetInt32("style"), + Name = results.GetString("name"), + StartVelX = results.GetFloat("start_vel_x"), + StartVelY = results.GetFloat("start_vel_y"), + StartVelZ = results.GetFloat("start_vel_z"), + EndVelX = results.GetFloat("end_vel_x"), + EndVelY = results.GetFloat("end_vel_y"), + EndVelZ = results.GetFloat("end_vel_z"), + RunDate = results.GetInt32("run_date"), + TotalCount = results.GetInt32("total_count"), + ReplayFramesBase64 = replayFramesBase64 + }); + } + } + + return runs; + } + + + + /* PlayerProfile.cs */ + public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + { + using var playerData = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, steamId)); + + if (playerData.HasRows && playerData.Read()) + { + return new PlayerProfileDataModel + { + ID = playerData.GetInt32("id"), + SteamID = steamId, + Name = playerData.GetString("name"), + Country = playerData.GetString("country"), + JoinDate = playerData.GetInt32("join_date"), + LastSeen = playerData.GetInt32("last_seen"), + Connections = playerData.GetInt32("connections") + }; + } + + return null; + } + + public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + { + int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync(string.Format( + Config.MySQL.Queries.DB_QUERY_PP_INSERT_PROFILE, + MySqlConnector.MySqlHelper.EscapeString(profile.Name), + profile.SteamID, + profile.Country, + joinDate, + joinDate, + 1)); + + if (rowsInserted != 1) + throw new Exception($"Failed to insert new player '{profile.Name}' ({profile.SteamID})."); + + return (int)lastId; + } + + public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + { + int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(string.Format( + Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, + profile.Country, + lastSeen, + profile.ID, + MySqlConnector.MySqlHelper.EscapeString(profile.Name))); + + if (rowsUpdated != 1) + throw new Exception($"Failed to update player '{profile.Name}' ({profile.SteamID})."); + } + + + + /* PlayerStats.cs */ + public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + { + var mapTimes = new List(); + + using var results = await SurfTimer.DB.QueryAsync( + string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, playerId, mapId)); + + if (results.HasRows) + { + while (results.Read()) + { + mapTimes.Add(new PlayerMapTimeDataModel + { + ID = results.GetInt32("id"), + RunTime = results.GetInt32("run_time"), + Type = results.GetInt32("type"), + Stage = results.GetInt32("stage"), + Style = results.GetInt32("style"), + Rank = results.GetInt32("rank"), + StartVelX = (float)results.GetDouble("start_vel_x"), + StartVelY = (float)results.GetDouble("start_vel_y"), + StartVelZ = (float)results.GetDouble("start_vel_z"), + EndVelX = (float)results.GetDouble("end_vel_x"), + EndVelY = (float)results.GetDouble("end_vel_y"), + EndVelZ = (float)results.GetDouble("end_vel_z"), + RunDate = results.GetInt32("run_date") + }); + } + } + + return mapTimes; + } + + + + /* CurrentRun.cs */ + public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") + { + var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync( + string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, + mapTime.PlayerId, + mapTime.MapId, + mapTime.Style, + mapTime.Type, + mapTime.Stage, + mapTime.Ticks, + mapTime.StartVelX, + mapTime.StartVelY, + mapTime.StartVelZ, + mapTime.EndVelX, + mapTime.EndVelY, + mapTime.EndVelZ, + mapTime.RunDate, + mapTime.ReplayFramesBase64) + ); + + if (rowsInserted <= 0) + { + throw new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerId}."); + } + + // Write the checkpoints after we have the `lastId` + if (mapTime.Checkpoints != null && mapTime.Checkpoints.Count > 0) + { + var commands = new List(); + foreach (var cp in mapTime.Checkpoints.Values) + { + commands.Add(string.Format( + Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + lastId, cp.CP, cp.Ticks, cp.StartVelX, cp.StartVelY, cp.StartVelZ, + cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); + } + await SurfTimer.DB.TransactionAsync(commands); + } + + + return (int)lastId; + } + + // public async Task SaveRunCheckpointsAsync(int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "") + // { + // var commands = new List(); + + // foreach (var cp in checkpoints) + // { + // commands.Add(string.Format( + // Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + // mapTimeId, cp.CP, cp.Ticks, cp.StartVelX, cp.StartVelY, cp.StartVelZ, + // cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); + // } + + // await SurfTimer.DB.TransactionAsync(commands); + // } + + } +} diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index afbb72f..7a370bf 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -5,6 +5,7 @@ using Serilog; using Serilog.Events; using CounterStrikeSharp.API; +using SurfTimer.Data; namespace SurfTimer; @@ -46,7 +47,14 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); // Multiple instances for different players services.AddScoped(); // Multiple instances for different players services.AddScoped(); // Multiple instances for different players + services.AddScoped(); // Multiple instances for different players services.AddSingleton(); // Single instance for 1 Map object + + services.AddScoped(provider => + Config.API.GetApiOnly() + ? new ApiDataAccessService() + : new MySqlDataAccessService() + ); } } From b5e00872527fdb67744e721b3126675149798435 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 7 Jul 2025 18:49:45 +0300 Subject: [PATCH 22/52] Fix POST MapTime for Map runs and Stage PBs; Change POST and PUT methods --- lang/en.json | 4 +- src/ST-API/Comms.cs | 71 ++++++++++++++++------- src/ST-API/Structures.cs | 2 +- src/ST-Commands/PlayerCommands.cs | 53 ++++++++++++++--- src/ST-Events/TriggerStartTouch.cs | 3 +- src/ST-Player/PlayerStats/CurrentRun.cs | 70 +++++++++------------- src/ST-Player/PlayerStats/PlayerStats.cs | 2 +- src/ST-UTILS/Data/ApiDataAccessService.cs | 31 +++++++++- 8 files changed, 157 insertions(+), 79 deletions(-) diff --git a/lang/en.json b/lang/en.json index 8d07d50..feac428 100644 --- a/lang/en.json +++ b/lang/en.json @@ -23,7 +23,7 @@ "stagewr_set": "{lime}{0}{default} set the first {yellow}Stage {1}{default} record at {gold}{2}{default}!", "stagewr_improved": "{lime}{0}{default} has set a new {yellow}Stage {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", "stagewr_missed": "You are behind the {yellow}Stage {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", - "stagepb_set": "You finished {yellow}Stage {0}{default} in {gold}{1}{ChatColors.Default}, setting your new Personal Best!", + "stagepb_set": "You finished {yellow}Stage {0}{default} in {gold}{1}{default}, setting your new Personal Best!", "stagepb_improved": "{lime}{0}{default} beat their {yellow}Stage {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", "mapwr_set": "{lime}{0}{default} set the first {yellow}Map{default} record at {gold}{1}{default}!", "mapwr_improved": "{lime}{0}{default} has set a new {yellow}Map{default} record with a time of {gold}{1}{default}, beating the old record by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", @@ -32,7 +32,7 @@ "mappb_missed": "You finished the map in {yellow}{0}{default}!", "bonuswr_set": "{lime}{0}{default} set the first {yellow}Bonus {1}{default} record at {gold}{2}{default}!", "bonuswr_improved": "{lime}{0}{default} has set a new {yellow}Bonus {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", - "bonuspb_set": "You finished {yellow}Bonus {0}{default} in {gold}{1}{ChatColors.Default}, setting your new Personal Best!", + "bonuspb_set": "You finished {yellow}Bonus {0}{default} in {gold}{1}{default}, setting your new Personal Best!", "bonuspb_improved": "{lime}{0}{default} beat their {yellow}Bonus {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", "bonuspb_missed": "You are behind the {yellow}Bonus {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", "checkpoint_message": "CP [{yellow}{0}{default}]: {yellow}{1}{default} {yellow}({2}){default} [PB: {3} | WR: {4}]", diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index b61469a..1a99a9f 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -57,26 +57,40 @@ private ApiMethod() { } var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); -#if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task POST -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); -#endif - - using var response = await _client.PostAsJsonAsync(uri, body); - try { - _logger.LogInformation("[{ClassName}] {MethodName} -> POST {URL} => {StatusCode}", + using var response = await _client.PostAsJsonAsync(uri, body); + + _logger.LogInformation( + "[{ClassName}] {MethodName} -> POST {URL} => {StatusCode}", nameof(ApiMethod), methodName, url, response.StatusCode ); - response.EnsureSuccessStatusCode(); // BAD BAD BAD - return await response.Content.ReadFromJsonAsync(); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + else + { + // Read response body to log what went wrong + var errorContent = await response.Content.ReadAsStringAsync(); + + _logger.LogWarning( + "[{ClassName}] {MethodName} -> POST {URL} failed with status {StatusCode}. Response body: {ResponseBody}", + nameof(ApiMethod), methodName, url, response.StatusCode, errorContent + ); + + return default; + } } catch (Exception ex) { - _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", - nameof(ApiMethod), methodName + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> Exception during POST {URL}", + nameof(ApiMethod), methodName, url ); + return default; } } @@ -86,28 +100,41 @@ private ApiMethod() { } var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); -#if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task PUT -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); -#endif - - using var response = await _client.PutAsJsonAsync(uri, body); - try { - _logger.LogInformation("[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode}", + using var response = await _client.PutAsJsonAsync(uri, body); + + _logger.LogInformation( + "[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode}", nameof(ApiMethod), methodName, url, response.StatusCode ); - response.EnsureSuccessStatusCode(); // BAD BAD BAD - return await response.Content.ReadFromJsonAsync(); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + + _logger.LogWarning( + "[{ClassName}] {MethodName} -> PUT {URL} failed with status {StatusCode}. Response body: {ResponseBody}", + nameof(ApiMethod), methodName, url, response.StatusCode, errorContent + ); + + return default; + } } catch (Exception ex) { - _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", - nameof(ApiMethod), methodName + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> Exception during PUT {URL}", + nameof(ApiMethod), methodName, url ); return default; } } + } \ No newline at end of file diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs index a4722d3..79a4e04 100644 --- a/src/ST-API/Structures.cs +++ b/src/ST-API/Structures.cs @@ -6,7 +6,7 @@ internal class API_PostResponseData public int inserted { get; set; } public float xtime { get; set; } public int last_id { get; set; } - public int? trx { get; set; } + public List? trx { get; set; } } internal class API_Checkpoint diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index e1b1812..221c24a 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -477,14 +477,6 @@ public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) oPlayer.Stats.ThisRun.PrintSituations(oPlayer); } - - // [ConsoleCommand("css_setpb", "xxxxxx")] - // [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - // public void TestSetPb(CCSPlayerController? player, CommandInfo command) - // { - // var _ = await TestSetPbAsync(player, command); - // } - [ConsoleCommand("css_setpb", "xxxxx")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public async void TestSetPb(CCSPlayerController? player, CommandInfo command) @@ -497,6 +489,51 @@ public async void TestSetPb(CCSPlayerController? player, CommandInfo command) await oPlayer.Stats.ThisRun.SaveMapTime(oPlayer, 0, 0, 6666, "TestSetPb"); // oPlayer.Stats.ThisRun.PrintSituations(oPlayer); + + /* Test Time Saving *//* + if (methodName == "TestSetPb") + { + // 1. Dummy Checkpoint + var dummyCheckpoint = new Checkpoint + { + CP = 1, + Ticks = 1234, + EndTouch = 1, + StartVelX = 111.1f, + StartVelY = 222.2f, + StartVelZ = 333.3f, + EndVelX = 444.4f, + EndVelY = 555.5f, + EndVelZ = 666.6f, + Attempts = 2 + }; + + // 2. Dummy Dictionary за mapTime.Checkpoints + var dummyCheckpointsDict = new Dictionary + { + { dummyCheckpoint.CP, dummyCheckpoint } + }; + + mapTime = new MapTimeDataModel + { + PlayerId = player.Profile.ID, + MapId = player.CurrMap.ID, + Style = player.Timer.Style, + Type = 0, + Stage = stage != 0 ? stage : bonus, + Ticks = 666, + StartVelX = this.StartVelX, + StartVelY = this.StartVelY, + StartVelZ = this.StartVelZ, + EndVelX = this.EndVelX, + EndVelY = this.EndVelY, + EndVelZ = this.EndVelZ, + ReplayFramesBase64 = replay_frames, + Checkpoints = dummyCheckpointsDict + }; + } + /* END Test Time Saving */ + } [ConsoleCommand("css_testx", "x")] diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index e90722f..192fbb1 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -581,7 +581,8 @@ void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool if ( stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks || CurrentMap.StageWR[stage][pStyle].ID == -1 || - player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time || + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1 ) { if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 02fb74e..8ebd888 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -67,49 +67,32 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r Checkpoints = this.Checkpoints // Test out }; - /* Test Time Saving */ - if (methodName == "TestSetPb") - { - // 1. Dummy Checkpoint - var dummyCheckpoint = new Checkpoint - { - CP = 1, - Ticks = 1234, - EndTouch = 1, - StartVelX = 111.1f, - StartVelY = 222.2f, - StartVelZ = 333.3f, - EndVelX = 444.4f, - EndVelY = 555.5f, - EndVelZ = 666.6f, - Attempts = 2 - }; - - // 2. Dummy Dictionary за mapTime.Checkpoints - var dummyCheckpointsDict = new Dictionary - { - { dummyCheckpoint.CP, dummyCheckpoint } - }; - - mapTime = new MapTimeDataModel - { - PlayerId = player.Profile.ID, - MapId = player.CurrMap.ID, - Style = player.Timer.Style, - Type = 0, - Stage = stage != 0 ? stage : bonus, - Ticks = 666, - StartVelX = this.StartVelX, - StartVelY = this.StartVelY, - StartVelZ = this.StartVelZ, - EndVelX = this.EndVelX, - EndVelY = this.EndVelY, - EndVelZ = this.EndVelZ, - ReplayFramesBase64 = replay_frames, - Checkpoints = dummyCheckpointsDict - }; - } - /* END Test Time Saving */ + /* + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Sending data:\n" + + " PlayerId: {PlayerId}\n" + + " MapId: {MapId}\n" + + " Style: {Style}\n" + + " Type: {Type}\n" + + " Stage: {Stage}\n" + + " Ticks: {Ticks}\n" + + " StartVel: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + + " EndVel: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + + " ReplayFramesBase64: {ReplayFrames}\n" + + " Checkpoints: {CheckpointsCount}", + nameof(CurrentRun), methodName, + mapTime.PlayerId, + mapTime.MapId, + mapTime.Style, + mapTime.Type, + mapTime.Stage, + mapTime.Ticks, + mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, + mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, + mapTime.ReplayFramesBase64?.Length ?? 0, // log length to avoid dumping huge string + mapTime.Checkpoints?.Count ?? 0 + ); + */ await _dataService.InsertMapTimeAsync(mapTime); @@ -117,6 +100,7 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r await SaveCurrentRunCheckpoints(player, true); await player.CurrMap.LoadMapRecordRuns(); + await player.Stats.LoadPlayerMapTimesData(player); stopwatch.Stop(); _logger.LogInformation("[{Class}] {Method} -> Finished SaveMapTime for '{Name}' in {Elapsed}ms | API = {API}", diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index aeefda4..3158cdc 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -174,7 +174,7 @@ public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] /// /// Loads the player's map time data from the database along with their ranks. For all types and styles (may not work correctly for Stages/Bonuses) - /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. + /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. (This method calls it if needed) /// This populates all the `style` and `type` stats the player has for the map /// public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 7995b67..5d5bd6f 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -366,9 +366,38 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe stage = mapTime.Stage, replay_frames = mapTime.ReplayFramesBase64, run_date = mapTime.RunDate, - checkpoints = runCheckpoints.ToList() + checkpoints = runCheckpoints }; + /* + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Converted and sending API_SaveMapTime:\n" + + " player_id: {PlayerId}\n" + + " map_id: {MapId}\n" + + " run_time: {RunTime}\n" + + " style: {Style}\n" + + " type: {Type}\n" + + " stage: {Stage}\n" + + " start_vel: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + + " end_vel: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + + " replay_frames: {ReplayFramesLength}\n" + + " checkpoints: {CheckpointsCount}\n" + + " run_date: {RunDate}", + nameof(CurrentRun), methodName, + apiSaveMapTime.player_id, + apiSaveMapTime.map_id, + apiSaveMapTime.run_time, + apiSaveMapTime.style, + apiSaveMapTime.type, + apiSaveMapTime.stage, + apiSaveMapTime.start_vel_x, apiSaveMapTime.start_vel_y, apiSaveMapTime.start_vel_z, + apiSaveMapTime.end_vel_x, apiSaveMapTime.end_vel_y, apiSaveMapTime.end_vel_z, + apiSaveMapTime.replay_frames?.Length ?? 0, + apiSaveMapTime.checkpoints?.Count ?? 0, + apiSaveMapTime.run_date ?? 0 + ); + */ + var postResponse = await ApiMethod.POST( Config.API.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, apiSaveMapTime From 7c2033bb28711e5f55f23c4b0462a541ce5179e9 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 7 Jul 2025 22:37:11 +0300 Subject: [PATCH 23/52] small fixes to !spec PR --- .gitignore | 4 ++- TODO | 8 ++--- src/ST-Commands/PlayerCommands.cs | 51 +++++++++++++++++----------- src/ST-Map/Map.cs | 38 ++++++++++----------- src/ST-Player/Replay/ReplayPlayer.cs | 6 ++-- 5 files changed, 61 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 3485604..35e2280 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ src/bin/Debug/* src/bin/Release/* src/obj/* -src/SurfTimer.csproj \ No newline at end of file +src/SurfTimer.csproj +*.puml +out/uml/include/full.png \ No newline at end of file diff --git a/TODO b/TODO index 5675419..46f582e 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,11 @@ - Replay Bot Scoreboard names NOT changing when setting new recs + Re-add the MySQL queries in code and make it switch between API and DB functions -- Map Time is NOT being saved with API ++ Map Time is NOT being saved with API - Make configs generate themselves inside the `./configs/plugins/...` folder -- Fix loading MapTimes for each type (stage, bonus, maps) - - API - - DB ++ Fix loading MapTimes for each type (stage, bonus, maps) + + API + + DB + Change `DB_QUERY_MAP_GET_RUNS` query with `DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT` in API and edit code in plugin + Change `DB_QUERY_PB_GET_RUNTIME` query in API diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index cb790ff..6679032 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -14,10 +14,15 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) { if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); + Server.NextWorldUpdate(() => // Does NOT trigger a Joined X team message + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + ); } Player oPlayer = playerList[player.UserId ?? 0]; @@ -31,15 +36,16 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) // To-do: players[userid].Timer.Reset() -> teleport player playerList[player.UserId ?? 0].Timer.Reset(); if (!CurrentMap.StartZone.IsZero()) - Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); } - [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you're in.")] + [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you were in.")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) { - if (player == null) + if (player == null) return; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { player.ChangeTeam(CsTeam.CounterTerrorist); @@ -57,9 +63,9 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) if (oPlayer.Timer.IsBonusMode) { if (oPlayer.Timer.Bonus != 0 && !CurrentMap.BonusStartZone[oPlayer.Timer.Bonus].IsZero()) - Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value! , CurrentMap.BonusStartZone[oPlayer.Timer.Bonus])); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.BonusStartZone[oPlayer.Timer.Bonus])); else // Reset back to map start - Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); } else @@ -67,7 +73,7 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) if (oPlayer.Timer.Stage != 0 && !CurrentMap.StageStartZone[oPlayer.Timer.Stage].IsZero()) Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StageStartZone[oPlayer.Timer.Stage])); else // Reset back to map start - Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!,CurrentMap.StartZone)); + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); } } @@ -78,11 +84,6 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { if (player == null) return; - if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) - { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); - } int stage; try @@ -128,6 +129,12 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { playerList[player.UserId ?? 0].Timer.Reset(); + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + if (stage == 1) { Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); @@ -156,11 +163,6 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) { if (player == null) return; - if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) - { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); - } int bonus; @@ -200,6 +202,12 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) playerList[player.UserId ?? 0].Timer.Reset(); playerList[player.UserId ?? 0].Timer.IsBonusMode = true; + if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.BonusStartZone[bonus])); } @@ -212,10 +220,13 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo 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) + // if (player == null || player.Team == CsTeam.Spectator) + if (player == null) return; - player.ChangeTeam(CsTeam.Spectator); + Server.NextFrame(() => + player.ChangeTeam(CsTeam.Spectator) + ); } [ConsoleCommand("css_rank", "Show the current rank of the player for the style they are in")] diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 40e621e..7fc85ba 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -395,10 +395,6 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") // int totalBonusRuns = 0; this.ConnectedMapTimes.Clear(); - int qType; - int qStage; - int qStyle; - // Replay Stuff JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; @@ -675,22 +671,26 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB // Start the new map replay if none existed until now Server.NextFrame(() => { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] ResetReplay() and Start()"); - this.ReplayManager.MapWR.ResetReplay(); - this.ReplayManager.MapWR.Start(); - } - else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) - { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [BonusWR] ResetReplay() and Start() {stage}"); - this.ReplayManager.BonusWR.ResetReplay(); - this.ReplayManager.BonusWR.Start(); - } - else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) - { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [StageWR] ResetReplay() and Start() {stage}"); - this.ReplayManager.StageWR.ResetReplay(); - this.ReplayManager.StageWR.Start(); + if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) + { + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] ResetReplay() and Start()"); + this.ReplayManager.MapWR.ResetReplay(); + this.ReplayManager.MapWR.Start(); + } + else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) + { + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [BonusWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.BonusWR.ResetReplay(); + this.ReplayManager.BonusWR.Start(); + } + else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) + { + // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [StageWR] ResetReplay() and Start() {stage}"); + this.ReplayManager.StageWR.ResetReplay(); + this.ReplayManager.StageWR.Start(); + } } + ); } public void KickReplayBot(int index) diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 962c3ac..f67a6d0 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -194,7 +194,7 @@ public void Tick() if ((current_pos - current_frame_pos).Length() > 200) Extensions.Teleport(Controller.PlayerPawn.Value, current_frame_pos, current_frame_ang, null); else - Extensions.Teleport(Controller.PlayerPawn.Value, null , current_frame_ang, velocity); + Extensions.Teleport(Controller.PlayerPawn.Value, null, current_frame_ang, velocity); if (!this.IsPaused) @@ -258,7 +258,9 @@ public void FormatBotName([CallerMemberName] string methodName = "") replay_name = $"[{prefix}] {this.RecordPlayerName}"; bot_name.Set(replay_name); - Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName"); + Server.NextFrame(() => + Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName") + ); // _logger.LogTrace("[{ClassName}] {MethodName} -> Changed replay bot name from '{OldName}' to '{NewName}'", // nameof(ReplayPlayer), methodName, bot_name, replay_name From 88b417170cba182e39c20fc1ccf746db737cf44d Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:49:23 +0300 Subject: [PATCH 24/52] fix respawn from spec; minArgs --- src/ST-Commands/PlayerCommands.cs | 120 ++++++++++++++++++------------ 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 6679032..00757f7 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -17,10 +17,15 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { - Server.NextWorldUpdate(() => // Does NOT trigger a Joined X team message + Server.NextFrame(() => // Weird CS2 bug that requires doing this twice to show the Joined X team in chat and not stay in limbo { player.ChangeTeam(CsTeam.CounterTerrorist); player.Respawn(); + + player.ChangeTeam(CsTeam.Spectator); + + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); } ); } @@ -33,10 +38,13 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) } // oPlayer.ReplayRecorder.Reset(); - // To-do: players[userid].Timer.Reset() -> teleport player playerList[player.UserId ?? 0].Timer.Reset(); if (!CurrentMap.StartZone.IsZero()) - Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); + Server.NextFrame(() => + { + Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone); + } + ); } [ConsoleCommand("css_rs", "Reset back to the start of the stage or bonus you were in.")] @@ -48,8 +56,17 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); + Server.NextFrame(() => // Weird CS2 bug that requires doing this twice to show the Joined X team in chat and not stay in limbo + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + + player.ChangeTeam(CsTeam.Spectator); + + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + ); } Player oPlayer = playerList[player.UserId ?? 0]; @@ -67,7 +84,6 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) else // Reset back to map start Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.StartZone)); } - else { if (oPlayer.Timer.Stage != 0 && !CurrentMap.StageStartZone[oPlayer.Timer.Stage].IsZero()) @@ -79,7 +95,7 @@ public void PlayerResetStage(CCSPlayerController? player, CommandInfo command) [ConsoleCommand("css_s", "Teleport to a stage")] [ConsoleCommand("css_stage", "Teleport to a stage")] - [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [CommandHelper(minArgs: 1, usage: " [1/2/3]", whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -98,25 +114,11 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) return; } - // Must be 1 argument - if (command.ArgCount < 2 || stage <= 0) - { -#if DEBUG - player.PrintToChat($"CS2 Surf DEBUG >> css_stage >> Arg#: {command.ArgCount} >> Args: {Int32.Parse(command.ArgByIndex(1))}"); -#endif - - player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", - "!s "]}" - ); - return; - } - - else if (CurrentMap.Stages <= 0) + if (CurrentMap.Stages <= 0) { player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["not_staged"]}"); return; } - else if (stage > CurrentMap.Stages) { player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_stage_value", @@ -131,8 +133,17 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); + Server.NextFrame(() => // Weird CS2 bug that requires doing this twice to show the Joined X team in chat and not stay in limbo + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + + player.ChangeTeam(CsTeam.Spectator); + + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + ); } if (stage == 1) @@ -149,7 +160,6 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) // 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. } - else player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", "!s "]}" @@ -158,7 +168,7 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) [ConsoleCommand("css_b", "Teleport to a bonus")] [ConsoleCommand("css_bonus", "Teleport to a bonus")] - [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [CommandHelper(minArgs: 1, usage: " [1/2/3]", whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -166,29 +176,23 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) int bonus; - // Check for argument count - if (command.ArgCount < 2) + try { - if (CurrentMap.Bonuses > 0) - bonus = 1; - else - { - player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", - "!b "]}" - ); - return; - } - } - - else bonus = Int32.Parse(command.ArgByIndex(1)); + } + catch (System.Exception) + { + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!b "]}" + ); + return; + } if (CurrentMap.Bonuses <= 0) { player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["not_bonused"]}"); return; } - else if (bonus > CurrentMap.Bonuses) { player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_bonus_value", @@ -204,13 +208,21 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) { - player.ChangeTeam(CsTeam.CounterTerrorist); - player.Respawn(); + Server.NextFrame(() => // Weird CS2 bug that requires doing this twice to show the Joined X team in chat and not stay in limbo + { + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + + player.ChangeTeam(CsTeam.Spectator); + + player.ChangeTeam(CsTeam.CounterTerrorist); + player.Respawn(); + } + ); } Server.NextFrame(() => Extensions.Teleport(player.PlayerPawn.Value!, CurrentMap.BonusStartZone[bonus])); } - else player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", "!b "]}" @@ -218,9 +230,9 @@ public void PlayerGoToBonus(CCSPlayerController? player, CommandInfo command) } [ConsoleCommand("css_spec", "Moves a player automaticlly into spectator mode")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void MovePlayerToSpectator(CCSPlayerController? player, CommandInfo command) { - // if (player == null || player.Team == CsTeam.Spectator) if (player == null) return; @@ -251,6 +263,7 @@ Replay Commands */ [ConsoleCommand("css_replaybotpause", "Pause the replay bot playback")] [ConsoleCommand("css_rbpause", "Pause the replay bot playback")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PauseReplay(CCSPlayerController? player, CommandInfo command) { if (player == null || player.Team != CsTeam.Spectator) @@ -266,6 +279,7 @@ public void PauseReplay(CCSPlayerController? player, CommandInfo command) } [ConsoleCommand("css_rbplay", "Start all replays from the start")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void PlayReplay(CCSPlayerController? player, CommandInfo command) { if (player == null || player.Team != CsTeam.Spectator) @@ -289,6 +303,7 @@ public void PlayReplay(CCSPlayerController? player, CommandInfo command) [ConsoleCommand("css_replaybotflip", "Flips the replay bot between Forward/Backward playback")] [ConsoleCommand("css_rbflip", "Flips the replay bot between Forward/Backward playback")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void ReverseReplay(CCSPlayerController? player, CommandInfo command) { if (player == null || player.Team != CsTeam.Spectator) @@ -350,6 +365,7 @@ Saveloc Commands ######################## */ [ConsoleCommand("css_saveloc", "Save current player location to be practiced")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -386,6 +402,7 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) } [ConsoleCommand("css_tele", "Teleport player to current saved location")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -414,6 +431,7 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm } if (command.ArgCount > 1) + { try { int tele_n = int.Parse(command.ArgByIndex(1)); @@ -425,12 +443,14 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm Exception exception = new("sum ting wong"); throw exception; } + } SavelocFrame location = p.SavedLocations[p.CurrentSavedLocation]; Server.NextFrame(() => - { - Extensions.Teleport(p.Controller.PlayerPawn.Value!, location.Pos, location.Ang, location.Vel); - p.Timer.Ticks = location.Tick; - }); + { + Extensions.Teleport(p.Controller.PlayerPawn.Value!, location.Pos, location.Ang, location.Vel); + p.Timer.Ticks = location.Tick; + } + ); p.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["saveloc_teleported", p.CurrentSavedLocation]}" @@ -438,6 +458,7 @@ public void TeleportPlayerLocation(CCSPlayerController? player, CommandInfo comm } [ConsoleCommand("css_teleprev", "Teleport player to previous saved location")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -473,6 +494,7 @@ public void TeleportPlayerLocationPrev(CCSPlayerController? player, CommandInfo } [ConsoleCommand("css_telenext", "Teleport player to next saved location")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] public void TeleportPlayerLocationNext(CCSPlayerController? player, CommandInfo command) { if (player == null) From 950897451f004e92c7ced6a6066d027b58b94ef1 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:01:39 +0300 Subject: [PATCH 25/52] constructors for models;formatting;credits --- src/ST-API/Structures.cs | 100 ++++++- src/ST-Player/PlayerStats/CurrentRun.cs | 63 ++-- src/ST-Player/PlayerStats/PersonalBest.cs | 6 +- src/ST-Player/Replay/ReplayRecorder.cs | 315 ++++++++++---------- src/ST-UTILS/Data/ApiDataAccessService.cs | 177 ++--------- src/ST-UTILS/Data/Models.cs | 205 ++++++++++++- src/ST-UTILS/Data/MySqlDataAccessService.cs | 88 +----- src/ST-UTILS/Extensions.cs | 2 +- src/SurfTimer.cs | 17 +- 9 files changed, 519 insertions(+), 454 deletions(-) diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs index 79a4e04..94f7a48 100644 --- a/src/ST-API/Structures.cs +++ b/src/ST-API/Structures.cs @@ -1,7 +1,10 @@ +using System.Text.Json.Serialization; +using SurfTimer.Data; + namespace SurfTimer; // Map Info structure -internal class API_PostResponseData +public class API_PostResponseData { public int inserted { get; set; } public float xtime { get; set; } @@ -9,7 +12,7 @@ internal class API_PostResponseData public List? trx { get; set; } } -internal class API_Checkpoint +public class API_Checkpoint { public int cp { get; set; } public int run_time { get; set; } @@ -23,7 +26,7 @@ internal class API_Checkpoint public int attempts { get; set; } } -internal class API_CurrentRun +public class API_CurrentRun { public int player_id { get; set; } public int map_id { get; set; } @@ -42,7 +45,7 @@ internal class API_CurrentRun public int? run_date { get; set; } = null; } -internal class API_MapInfo +public class API_MapInfo { public int id { get; set; } = 0; public string name { get; set; } = "N/A"; @@ -53,9 +56,31 @@ internal class API_MapInfo public int ranked { get; set; } = 0; public int? date_added { get; set; } = null; public int? last_played { get; set; } = null; + + [JsonConstructor] + /// + /// Parameterless constructor for manual assigning of data + /// + public API_MapInfo() { } + + /// + /// Assigns values to the needed data model for an API request + /// + public API_MapInfo(MapInfoDataModel data) + { + id = data.ID; + name = data.Name; + author = data.Author; + tier = data.Tier; + stages = data.Stages; + bonuses = data.Bonuses; + ranked = data.Ranked ? 1 : 0; + date_added = data.DateAdded; + last_played = data.LastPlayed; + } } -internal class API_MapTime +public class API_MapTime { public int id { get; set; } public int player_id { get; set; } @@ -77,7 +102,7 @@ internal class API_MapTime public int total_count { get; set; } } -internal class API_PlayerSurfProfile +public class API_PlayerSurfProfile { public int id { get; set; } public string name { get; set; } = "N/A"; @@ -86,9 +111,29 @@ internal class API_PlayerSurfProfile public int join_date { get; set; } public int last_seen { get; set; } public int connections { get; set; } + + [JsonConstructor] + /// + /// Parameterless constructor for manual assigning of data + /// + public API_PlayerSurfProfile() { } + + /// + /// Assigns values to the needed data model for an API request + /// + public API_PlayerSurfProfile(PlayerProfileDataModel data) + { + id = data.ID; + name = data.Name; + steam_id = data.SteamID; + country = data.Country; + join_date = data.JoinDate; + last_seen = data.LastSeen; + connections = data.Connections == 0 ? 1 : data.Connections; + } } -internal class API_PersonalBest +public class API_PersonalBest { public int id { get; set; } public int player_id { get; set; } @@ -108,9 +153,12 @@ internal class API_PersonalBest public List? checkpoints { get; set; } = null; public string name { get; set; } = "N/A"; public int rank { get; set; } + + [JsonConstructor] + public API_PersonalBest() { } // Parameterless constructor used by GET method } -internal class API_SaveMapTime +public class API_SaveMapTime { public int player_id { get; set; } public int map_id { get; set; } @@ -127,4 +175,40 @@ internal class API_SaveMapTime public List? checkpoints { get; set; } = null; public string replay_frames { get; set; } = ""; public int? run_date { get; set; } = null; + + /// + /// Assigns values to the needed data model for an API request + /// + internal API_SaveMapTime(MapTimeDataModel data) + { + player_id = data.PlayerId; + map_id = data.MapId; + run_time = data.Ticks; + start_vel_x = data.StartVelX; + start_vel_y = data.StartVelY; + start_vel_z = data.StartVelZ; + end_vel_x = data.EndVelX; + end_vel_y = data.EndVelY; + end_vel_z = data.EndVelZ; + style = data.Style; + type = data.Type; + stage = data.Stage; + replay_frames = data.ReplayFramesBase64; + run_date = data.RunDate; + + // Convert Checkpoints + checkpoints = data.Checkpoints.Select(cp => new API_Checkpoint + { + cp = cp.Key, + run_time = cp.Value.Ticks, + end_touch = cp.Value.EndTouch, + start_vel_x = cp.Value.StartVelX, + start_vel_y = cp.Value.StartVelY, + start_vel_z = cp.Value.StartVelZ, + end_vel_x = cp.Value.EndVelX, + end_vel_y = cp.Value.EndVelY, + end_vel_z = cp.Value.EndVelZ, + attempts = cp.Value.Attempts + }).ToList(); + } } diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 8ebd888..d99b4c5 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -38,10 +38,9 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r { string replay_frames = ""; - /* Test Time Saving */ - if (methodName != "TestSetPb") - replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); - + // /* Test Time Saving */ + // if (methodName != "TestSetPb") + replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} replay frames.", nameof(CurrentRun), methodName, replay_frames.Length); @@ -67,44 +66,38 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r Checkpoints = this.Checkpoints // Test out }; - /* - _logger.LogDebug( - "[{ClassName}] {MethodName} -> Sending data:\n" + - " PlayerId: {PlayerId}\n" + - " MapId: {MapId}\n" + - " Style: {Style}\n" + - " Type: {Type}\n" + - " Stage: {Stage}\n" + - " Ticks: {Ticks}\n" + - " StartVel: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + - " EndVel: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + - " ReplayFramesBase64: {ReplayFrames}\n" + - " Checkpoints: {CheckpointsCount}", - nameof(CurrentRun), methodName, - mapTime.PlayerId, - mapTime.MapId, - mapTime.Style, - mapTime.Type, - mapTime.Stage, - mapTime.Ticks, - mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, - mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, - mapTime.ReplayFramesBase64?.Length ?? 0, // log length to avoid dumping huge string - mapTime.Checkpoints?.Count ?? 0 - ); - */ - - await _dataService.InsertMapTimeAsync(mapTime); + int mapTimeId = await _dataService.InsertMapTimeAsync(mapTime); if (recType == 0 && !Config.API.GetApiOnly()) await SaveCurrentRunCheckpoints(player, true); + // Reload the times for the map await player.CurrMap.LoadMapRecordRuns(); - await player.Stats.LoadPlayerMapTimesData(player); + + _logger.LogTrace("[{ClassName}] {MethodName} -> Loading data for run {ID} with type {Type}.", + nameof(CurrentRun), methodName, mapTimeId, recType + ); + + // Reload the player PB time (could possibly be skipped as we have mapTimeId after inserting) + switch (recType) + { + case 0: + player.Stats.PB[player.Timer.Style].ID = mapTimeId; + await player.Stats.PB[player.Timer.Style].LoadPlayerSpecificMapTimeData(player); + break; + case 1: + player.Stats.BonusPB[bonus][player.Timer.Style].ID = mapTimeId; + await player.Stats.BonusPB[bonus][player.Timer.Style].LoadPlayerSpecificMapTimeData(player); + break; + case 2: + player.Stats.StagePB[stage][player.Timer.Style].ID = mapTimeId; + await player.Stats.StagePB[stage][player.Timer.Style].LoadPlayerSpecificMapTimeData(player); + break; + } stopwatch.Stop(); - _logger.LogInformation("[{Class}] {Method} -> Finished SaveMapTime for '{Name}' in {Elapsed}ms | API = {API}", - nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + _logger.LogInformation("[{Class}] {Method} -> Finished SaveMapTime for '{Name}' (ID {ID}) in {Elapsed}ms | API = {API}", + nameof(CurrentRun), methodName, player.Profile.Name, mapTimeId, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() ); } diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index b2155a3..074d744 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -77,9 +77,9 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName if (model == null) { _logger.LogTrace( - "[{ClassName}] {MethodName} -> No personal best found for player {Player} (ID={Id}).", + "[{ClassName}] {MethodName} -> No personal best found for player {Player} (ID={Id} ; Type={Type}).", nameof(PersonalBest), methodName, - player.Profile.Name, player.Profile.ID + player.Profile.Name, player.Profile.ID, this.Type ); return; } @@ -95,7 +95,7 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName this.EndVelY = model.EndVelY; this.EndVelZ = model.EndVelZ; this.RunDate = model.RunDate; - // this.ReplayFrames = model.ReplayFrames; // Won't work with MySQL load? + this.ReplayFramesBase64 = model.ReplayFramesBase64; // Won't work with MySQL load? - Not tested _logger.LogDebug( "[{ClassName}] {MethodName} -> Loaded PB run {RunId} for {Player}.", diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 61bacee..115f34a 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -176,177 +176,177 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C } switch (type) { - case 0: // Map Run - { - var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - - _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", - nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); - - if (start_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for start_enter_index during Map replay trimming. Setting 'start_enter_index' to '0' | IsStageMode = {StageMode} | IsBonusMode = {BonusMode}", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode - ); - start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; - // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; - } - - if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + case 0: // Map Run { - int startIndex = CalculateStartIndex(start_enter_index, start_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); - - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); + var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", + nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); + + if (start_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for start_enter_index during Map replay trimming. Setting 'start_enter_index' to '0' | IsStageMode = {StageMode} | IsBonusMode = {BonusMode}", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode + ); + start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; + // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; + } + + if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + { + int startIndex = CalculateStartIndex(start_enter_index, start_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", + nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index + ); + } + break; } - else + case 1: // Bonus Run { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", - nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index + var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index ); - } - break; - } - case 1: // Bonus Run - { - var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", - nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index - ); - if (bonus_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus - ); - bonus_enter_index = 0; + if (bonus_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus + ); + bonus_enter_index = 0; + } + + if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + { + int startIndex = CalculateStartIndex(bonus_enter_index, bonus_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index + ); + } + break; } - - if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + case 2: // Stage Run { - int startIndex = CalculateStartIndex(bonus_enter_index, bonus_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); + int stage_end_index; + int stage_exit_index; + int stage_enter_index; - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Stage Run replay trim indexes. Stage {Stage}, available frames {TotalFrames}", + nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, Frames.Count ); - } - else - { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", - nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index - ); - } - break; - } - case 2: // Stage Run - { - int stage_end_index; - int stage_exit_index; - int stage_enter_index; - - _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Stage Run replay trim indexes. Stage {Stage}, available frames {TotalFrames}", - nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, Frames.Count - ); - if (lastStage) - { - _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); - } - else if (player.Timer.Stage - 1 > 1) - { - _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); - } - else if (player.Timer.Stage - 1 == -1) // Don't crash...? - { - _logger.LogError(">>>> Stage replay trimming will abort! <<<<"); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> this.IsSaving = {IsSaving}", this.IsSaving); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsRunning = {IsRunning}", player.Timer.IsRunning); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsEnabled = {IsEnabled}", player.Timer.IsEnabled); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Stage = {Stage}", player.Timer.Stage); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Ticks = {Ticks}", player.Timer.Ticks); - stage_enter_index = -1; - stage_end_index = -1; - stage_exit_index = -1; - } - else - { - _logger.LogInformation("Stage replay trimming will use `START_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); - } + if (lastStage) + { + _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } + else if (player.Timer.Stage - 1 > 1) + { + _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } + else if (player.Timer.Stage - 1 == -1) // Don't crash...? + { + _logger.LogError(">>>> Stage replay trimming will abort! <<<<"); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> this.IsSaving = {IsSaving}", this.IsSaving); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsRunning = {IsRunning}", player.Timer.IsRunning); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsEnabled = {IsEnabled}", player.Timer.IsEnabled); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Stage = {Stage}", player.Timer.Stage); + _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Ticks = {Ticks}", player.Timer.Ticks); + stage_enter_index = -1; + stage_end_index = -1; + stage_exit_index = -1; + } + else + { + _logger.LogInformation("Stage replay trimming will use `START_ZONE_X`"); + stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); + _logger.LogTrace("stage_end_index = {stage_end_index}", + stage_end_index + ); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + _logger.LogTrace("stage_exit_index = {stage_exit_index}", + stage_exit_index + ); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + _logger.LogTrace("stage_enter_index = {stage_enter_index}", + stage_enter_index + ); + } _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index ); - - if (stage_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for stage_enter_index during Stage ({StageNumber}) replay trimming. Setting 'stage_enter_index' to '0'", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Stage - ); - stage_enter_index = 0; - } - if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) - { - int startIndex = CalculateStartIndex(stage_enter_index, stage_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(stage_end_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); - - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count - ); + if (stage_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for stage_enter_index during Stage ({StageNumber}) replay trimming. Setting 'stage_enter_index' to '0'", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Stage + ); + stage_enter_index = 0; + } + + if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) + { + int startIndex = CalculateStartIndex(stage_enter_index, stage_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(stage_end_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Stage ({StageNumber}) replay for '{Name}'. stage_enter_index = {stage_enter_index} | stage_exit_index = {stage_exit_index} | stage_end_index = {stage_end_index}", + nameof(ReplayRecorder), methodName, player.Timer.Stage, player.Profile.Name, stage_enter_index, stage_exit_index, stage_end_index + ); + } + break; } - else - { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Stage ({StageNumber}) replay for '{Name}'. stage_enter_index = {stage_enter_index} | stage_exit_index = {stage_exit_index} | stage_end_index = {stage_end_index}", - nameof(ReplayRecorder), methodName, player.Timer.Stage, player.Profile.Name, stage_enter_index, stage_exit_index, stage_end_index - ); - } - break; - } - } + } this.IsSaving = false; string trimmed = JsonSerializer.Serialize(new_frames); @@ -386,6 +386,7 @@ public int LastExitTick(int start_idx = 0) } return 0; } + private int CalculateStartIndex(int start_enter, int start_exit, int buffer) { if (start_exit - (buffer * 2) >= start_enter) @@ -412,6 +413,6 @@ private int CalculateEndIndex(int end_enter, int totalFrames, int buffer) private List GetTrimmedFrames(int startIndex, int endIndex) { - return Frames.GetRange(startIndex, endIndex - startIndex + 1); + return Frames.GetRange(startIndex, endIndex - startIndex + 1); } } \ No newline at end of file diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 5d5bd6f..ec7d48a 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -56,9 +56,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ .ToDictionary(c => c.CP, c => c); } - public async Task LoadPersonalBestRunAsync( - int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "" - ) + public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") { string url = pbId == null || pbId == -1 ? string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, @@ -74,20 +72,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(ApiDataAccessService), methodName ); - return new PersonalBestDataModel - { - ID = apiResult.id, - Ticks = apiResult.run_time, - Rank = apiResult.rank, - StartVelX = apiResult.start_vel_x, - StartVelY = apiResult.start_vel_y, - StartVelZ = apiResult.start_vel_z, - EndVelX = apiResult.end_vel_x, - EndVelY = apiResult.end_vel_y, - EndVelZ = apiResult.end_vel_z, - RunDate = apiResult.run_date, - ReplayFramesBase64 = apiResult.replay_frames - }; + return new PersonalBestDataModel(apiResult); } @@ -103,18 +88,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(ApiDataAccessService), methodName ); - return new MapInfoDataModel - { - ID = mapInfo.id, - Name = mapInfo.name, - Author = mapInfo.author, - Tier = mapInfo.tier, - Stages = mapInfo.stages, - Bonuses = mapInfo.bonuses, - Ranked = mapInfo.ranked == 1, - DateAdded = mapInfo.date_added ?? 0, - LastPlayed = mapInfo.last_played ?? 0 - }; + return new MapInfoDataModel(mapInfo); } return null; @@ -122,16 +96,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") { - var apiMapInfo = new API_MapInfo - { - id = -1, // API-side will ignore or auto-increment - name = mapInfo.Name, - author = mapInfo.Author, - tier = mapInfo.Tier, - stages = mapInfo.Stages, - bonuses = mapInfo.Bonuses, - ranked = mapInfo.Ranked ? 1 : 0, - }; + var apiMapInfo = new API_MapInfo(mapInfo); var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, apiMapInfo); @@ -145,17 +110,7 @@ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMembe public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") { - var apiMapInfo = new API_MapInfo - { - id = mapInfo.ID, - name = mapInfo.Name, - author = mapInfo.Author, - tier = mapInfo.Tier, - stages = mapInfo.Stages, - bonuses = mapInfo.Bonuses, - ranked = mapInfo.Ranked ? 1 : 0, - // last_played = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() - }; + var apiMapInfo = new API_MapInfo(mapInfo); var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, apiMapInfo); if (response == null) @@ -171,7 +126,6 @@ public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName /// public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { - // TODO: Re-do the API with the new query and fix the API assign of values var apiRuns = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId)); @@ -181,24 +135,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, { foreach (var time in apiRuns) { - runs.Add(new MapRecordRunDataModel - { - ID = time.id, - RunTime = time.run_time, - Type = time.type, // API currently returns only map times, needs rework - Stage = time.stage, - Style = time.style, // Fix this when updating API - Name = time.name, - StartVelX = (float)time.start_vel_x, - StartVelY = (float)time.start_vel_y, - StartVelZ = (float)time.start_vel_z, - EndVelX = (float)time.end_vel_x, - EndVelY = (float)time.end_vel_y, - EndVelZ = (float)time.end_vel_z, - RunDate = time.run_date, - TotalCount = time.total_count, // API should return total count, fix this as well - ReplayFramesBase64 = time.replay_frames // API should return this - }); + runs.Add(new MapRecordRunDataModel(time)); } } @@ -209,9 +146,6 @@ public async Task> GetMapRecordRunsAsync(int mapId, /* PlayerProfile.cs */ public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { - // TODO: Implement API logic - // throw new NotImplementedException(); - var player = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); @@ -220,16 +154,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data", nameof(ApiDataAccessService), methodName ); - return new PlayerProfileDataModel - { - ID = player.id, - // SteamID = steamId, - Name = player.name, - Country = player.country, - JoinDate = player.join_date, - LastSeen = player.last_seen, - Connections = player.connections - }; + return new PlayerProfileDataModel(player); } _logger.LogWarning("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> No PlayerProfile data found for {SteamID}", @@ -240,19 +165,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") { - // TODO: Implement API logic - // throw new NotImplementedException(); - int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - - var apiPlayerProfileInfo = new API_PlayerSurfProfile - { - steam_id = profile.SteamID, - name = profile.Name, - country = profile.Country, - join_date = joinDate, - last_seen = joinDate, - connections = 1 - }; + var apiPlayerProfileInfo = new API_PlayerSurfProfile(profile); var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, apiPlayerProfileInfo); @@ -266,20 +179,7 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") { - // TODO: Implement API logic - // throw new NotImplementedException(); - int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - - var apiPlayerProfileInfo = new API_PlayerSurfProfile - { - id = profile.ID, - steam_id = profile.SteamID, - name = profile.Name, - country = profile.Country, - join_date = profile.JoinDate, - last_seen = lastSeen, - connections = 1 - }; + var apiPlayerProfileInfo = new API_PlayerSurfProfile(profile); var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, apiPlayerProfileInfo); if (response == null) @@ -292,7 +192,6 @@ public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [Call /* PlayerStats.cs */ public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { - // TODO: Implement API logic var mapTimes = new List(); var apiResponse = await ApiMethod.GET( @@ -307,23 +206,7 @@ public async Task> GetPlayerMapTimesAsync(int playe foreach (var time in apiResponse) { - mapTimes.Add(new PlayerMapTimeDataModel - { - ID = time.id, - RunTime = time.run_time, - Type = time.type, - Stage = time.stage, - Style = time.style, - Rank = time.rank, - StartVelX = (float)time.start_vel_x, - StartVelY = (float)time.start_vel_y, - StartVelZ = (float)time.start_vel_z, - EndVelX = (float)time.end_vel_x, - EndVelY = (float)time.end_vel_y, - EndVelZ = (float)time.end_vel_z, - RunDate = time.run_date, - ReplayFramesBase64 = time.replay_frames - }); + mapTimes.Add(new PlayerMapTimeDataModel(time)); } } @@ -335,39 +218,8 @@ public async Task> GetPlayerMapTimesAsync(int playe /* CurrentRun.cs */ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") { - // Convert the Checkpoint object to the API_Checkpoint one - var runCheckpoints = mapTime.Checkpoints.Select(cp => new API_Checkpoint - { - cp = cp.Key, - run_time = cp.Value.Ticks, - end_touch = cp.Value.EndTouch, - start_vel_x = cp.Value.StartVelX, - start_vel_y = cp.Value.StartVelY, - start_vel_z = cp.Value.StartVelZ, - end_vel_x = cp.Value.EndVelX, - end_vel_y = cp.Value.EndVelY, - end_vel_z = cp.Value.EndVelZ, - attempts = cp.Value.Attempts - }).ToList(); - - var apiSaveMapTime = new API_SaveMapTime - { - player_id = mapTime.PlayerId, - map_id = mapTime.MapId, - run_time = mapTime.Ticks, - start_vel_x = mapTime.StartVelX, - start_vel_y = mapTime.StartVelY, - start_vel_z = mapTime.StartVelZ, - end_vel_x = mapTime.EndVelX, - end_vel_y = mapTime.EndVelY, - end_vel_z = mapTime.EndVelZ, - style = mapTime.Style, - type = mapTime.Type, - stage = mapTime.Stage, - replay_frames = mapTime.ReplayFramesBase64, - run_date = mapTime.RunDate, - checkpoints = runCheckpoints - }; + // Initialize the API structure for POST request + var apiSaveMapTime = new API_SaveMapTime(mapTime); /* _logger.LogDebug( @@ -408,6 +260,11 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); } + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Successfully inserted entry with id {ID} with type {Type}", + nameof(CurrentRun), methodName, postResponse.last_id, mapTime.Type + ); + return postResponse.last_id; } diff --git a/src/ST-UTILS/Data/Models.cs b/src/ST-UTILS/Data/Models.cs index 9456f19..6871138 100644 --- a/src/ST-UTILS/Data/Models.cs +++ b/src/ST-UTILS/Data/Models.cs @@ -1,10 +1,48 @@ // File: Data/PersonalBestDataModel.cs +using System.Data; +using MySqlConnector; + namespace SurfTimer.Data { public class PersonalBestDataModel : RunStats { public int ID { get; set; } public int Rank { get; set; } + + /// + /// Assigns data from API response to the needed data model + /// + public PersonalBestDataModel(API_PersonalBest data) + { + ID = data.id; + Ticks = data.run_time; + Rank = data.rank; + StartVelX = data.start_vel_x; + StartVelY = data.start_vel_y; + StartVelZ = data.start_vel_z; + EndVelX = data.end_vel_x; + EndVelY = data.end_vel_y; + EndVelZ = data.end_vel_z; + RunDate = data.run_date; + ReplayFramesBase64 = data.replay_frames; + } + + /// + /// Assigns data from MySqlDataReader (MySQL query) to the needed data model + /// + public PersonalBestDataModel(MySqlDataReader data) + { + ID = data.GetInt32("id"); + Ticks = data.GetInt32("run_time"); + Rank = data.GetInt32("rank"); + StartVelX = (float)data.GetDouble("start_vel_x"); + StartVelY = (float)data.GetDouble("start_vel_y"); + StartVelZ = (float)data.GetDouble("start_vel_z"); + EndVelX = (float)data.GetDouble("end_vel_x"); + EndVelY = (float)data.GetDouble("end_vel_y"); + EndVelZ = (float)data.GetDouble("end_vel_z"); + RunDate = data.GetInt32("run_date"); + } } public class MapInfoDataModel @@ -18,8 +56,42 @@ public class MapInfoDataModel public bool Ranked { get; set; } public int DateAdded { get; set; } public int LastPlayed { get; set; } - } + /// + /// Parameterless constructor for manual assigning of data + /// + public MapInfoDataModel() { } + + /// + /// Assigns data from API response to the needed data model + /// + public MapInfoDataModel(API_MapInfo data) + { + ID = data.id; + Name = data.name; + Author = data.author; + Tier = data.tier; + Stages = data.stages; + Bonuses = data.bonuses; + Ranked = data.ranked == 1; + DateAdded = data.date_added ?? 0; + LastPlayed = data.last_played ?? 0; + } + + /// + /// Assigns data from MySqlDataReader (MySQL query) to the needed data model + /// + public MapInfoDataModel(MySqlDataReader data) + { + ID = data.GetInt32("id"); + Name = data.GetString("name"); + Author = data.GetString("author") ?? "Unknown"; + Tier = data.GetInt32("tier"); + Ranked = data.GetBoolean("ranked"); + DateAdded = data.GetInt32("date_added"); + LastPlayed = data.GetInt32("last_played"); + } + } public class MapRecordRunDataModel : RunStats { @@ -30,9 +102,63 @@ public class MapRecordRunDataModel : RunStats public int Style { get; set; } public string Name { get; set; } = ""; public int TotalCount { get; set; } - // public string ReplayFramesBase64 { get; set; } = ""; - } + /// + /// Assigns data from API response to the needed data model + /// + public MapRecordRunDataModel(API_MapTime data) + { + ID = data.id; + RunTime = data.run_time; + Type = data.type; + Stage = data.stage; + Style = data.style; + Name = data.name; + StartVelX = (float)data.start_vel_x; + StartVelY = (float)data.start_vel_y; + StartVelZ = (float)data.start_vel_z; + EndVelX = (float)data.end_vel_x; + EndVelY = (float)data.end_vel_y; + EndVelZ = (float)data.end_vel_z; + RunDate = data.run_date; + TotalCount = data.total_count; + ReplayFramesBase64 = data.replay_frames; + } + + /// + /// Assigns data from MySqlDataReader (MySQL query) to the needed data model + /// + public MapRecordRunDataModel(MySqlDataReader data) + { + string replayFramesBase64; + + try + { + replayFramesBase64 = data.GetString("replay_frames"); + } + catch (InvalidCastException) + { + byte[] replayFramesData = data.GetFieldValue("replay_frames"); + replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); + } + + ID = data.GetInt32("id"); + RunTime = data.GetInt32("run_time"); + Type = data.GetInt32("type"); + Stage = data.GetInt32("stage"); + Style = data.GetInt32("style"); + Name = data.GetString("name"); + StartVelX = data.GetFloat("start_vel_x"); + StartVelY = data.GetFloat("start_vel_y"); + StartVelZ = data.GetFloat("start_vel_z"); + EndVelX = data.GetFloat("end_vel_x"); + EndVelY = data.GetFloat("end_vel_y"); + EndVelZ = data.GetFloat("end_vel_z"); + RunDate = data.GetInt32("run_date"); + TotalCount = data.GetInt32("total_count"); + ReplayFramesBase64 = replayFramesBase64; + } + } public class PlayerProfileDataModel { @@ -43,6 +169,39 @@ public class PlayerProfileDataModel public int JoinDate { get; set; } = 0; public int LastSeen { get; set; } = 0; public int Connections { get; set; } = 0; + + /// + /// Parameterless constructor for manual assigning of data + /// + public PlayerProfileDataModel() { } + + /// + /// Assigns data from API response to the needed data model + /// + public PlayerProfileDataModel(API_PlayerSurfProfile data) + { + ID = data.id; + // SteamID = steamId; + Name = data.name; + Country = data.country; + JoinDate = data.join_date; + LastSeen = data.last_seen; + Connections = data.connections; + } + + /// + /// Assigns data from MySqlDataReader (MySQL query) to the needed data model + /// + public PlayerProfileDataModel(MySqlDataReader data) + { + ID = data.GetInt32("id"); + // SteamID = data.GetString("steam_id"); + Name = data.GetString("name"); + Country = data.GetString("country"); + JoinDate = data.GetInt32("join_date"); + LastSeen = data.GetInt32("last_seen"); + Connections = data.GetInt32("connections"); + } } public class PlayerMapTimeDataModel : RunStats @@ -53,6 +212,46 @@ public class PlayerMapTimeDataModel : RunStats public int Stage { get; set; } public int Style { get; set; } public int Rank { get; set; } + + /// + /// Assigns data from API response to the needed data model + /// + public PlayerMapTimeDataModel(API_PersonalBest data) + { + RunTime = data.run_time; + Type = data.type; + Stage = data.stage; + Style = data.style; + Rank = data.rank; + StartVelX = (float)data.start_vel_x; + StartVelY = (float)data.start_vel_y; + StartVelZ = (float)data.start_vel_z; + EndVelX = (float)data.end_vel_x; + EndVelY = (float)data.end_vel_y; + EndVelZ = (float)data.end_vel_z; + RunDate = data.run_date; + ReplayFramesBase64 = data.replay_frames; + } + + /// + /// Assigns data from MySqlDataReader (MySQL query) to the needed data model + /// + public PlayerMapTimeDataModel(MySqlDataReader data) + { + ID = data.GetInt32("id"); + RunTime = data.GetInt32("run_time"); + Type = data.GetInt32("type"); + Stage = data.GetInt32("stage"); + Style = data.GetInt32("style"); + Rank = data.GetInt32("rank"); + StartVelX = (float)data.GetDouble("start_vel_x"); + StartVelY = (float)data.GetDouble("start_vel_y"); + StartVelZ = (float)data.GetDouble("start_vel_z"); + EndVelX = (float)data.GetDouble("end_vel_x"); + EndVelY = (float)data.GetDouble("end_vel_y"); + EndVelZ = (float)data.GetDouble("end_vel_z"); + RunDate = data.GetInt32("run_date"); + } } public class MapTimeDataModel : RunStats diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index eb85e88..f310f29 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -60,9 +60,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ return dict; } - public async Task LoadPersonalBestRunAsync( - int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "" - ) + public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Using MySQL data access service.", nameof(MySqlDataAccessService), methodName @@ -90,19 +88,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style ); - return new PersonalBestDataModel - { - ID = results.GetInt32("id"), - Ticks = results.GetInt32("run_time"), - Rank = results.GetInt32("rank"), - StartVelX = (float)results.GetDouble("start_vel_x"), - StartVelY = (float)results.GetDouble("start_vel_y"), - StartVelZ = (float)results.GetDouble("start_vel_z"), - EndVelX = (float)results.GetDouble("end_vel_x"), - EndVelY = (float)results.GetDouble("end_vel_y"), - EndVelZ = (float)results.GetDouble("end_vel_z"), - RunDate = results.GetInt32("run_date") - }; + return new PersonalBestDataModel(results); } @@ -118,16 +104,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(MySqlDataAccessService), methodName ); - return new MapInfoDataModel - { - ID = mapData.GetInt32("id"), - Name = mapName, - Author = mapData.GetString("author") ?? "Unknown", - Tier = mapData.GetInt32("tier"), - Ranked = mapData.GetBoolean("ranked"), - DateAdded = mapData.GetInt32("date_added"), - LastPlayed = mapData.GetInt32("last_played"), - }; + return new MapInfoDataModel(mapData); } return null; @@ -183,36 +160,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, { while (results.Read()) { - string replayFramesBase64; - - try - { - replayFramesBase64 = results.GetString("replay_frames"); - } - catch (InvalidCastException) - { - byte[] replayFramesData = results.GetFieldValue("replay_frames"); - replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); - } - - runs.Add(new MapRecordRunDataModel - { - ID = results.GetInt32("id"), - RunTime = results.GetInt32("run_time"), - Type = results.GetInt32("type"), - Stage = results.GetInt32("stage"), - Style = results.GetInt32("style"), - Name = results.GetString("name"), - StartVelX = results.GetFloat("start_vel_x"), - StartVelY = results.GetFloat("start_vel_y"), - StartVelZ = results.GetFloat("start_vel_z"), - EndVelX = results.GetFloat("end_vel_x"), - EndVelY = results.GetFloat("end_vel_y"), - EndVelZ = results.GetFloat("end_vel_z"), - RunDate = results.GetInt32("run_date"), - TotalCount = results.GetInt32("total_count"), - ReplayFramesBase64 = replayFramesBase64 - }); + runs.Add(new MapRecordRunDataModel(results)); } } @@ -229,16 +177,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, if (playerData.HasRows && playerData.Read()) { - return new PlayerProfileDataModel - { - ID = playerData.GetInt32("id"), - SteamID = steamId, - Name = playerData.GetString("name"), - Country = playerData.GetString("country"), - JoinDate = playerData.GetInt32("join_date"), - LastSeen = playerData.GetInt32("last_seen"), - Connections = playerData.GetInt32("connections") - }; + return new PlayerProfileDataModel(playerData); } return null; @@ -290,22 +229,7 @@ public async Task> GetPlayerMapTimesAsync(int playe { while (results.Read()) { - mapTimes.Add(new PlayerMapTimeDataModel - { - ID = results.GetInt32("id"), - RunTime = results.GetInt32("run_time"), - Type = results.GetInt32("type"), - Stage = results.GetInt32("stage"), - Style = results.GetInt32("style"), - Rank = results.GetInt32("rank"), - StartVelX = (float)results.GetDouble("start_vel_x"), - StartVelY = (float)results.GetDouble("start_vel_y"), - StartVelZ = (float)results.GetDouble("start_vel_z"), - EndVelX = (float)results.GetDouble("end_vel_x"), - EndVelY = (float)results.GetDouble("end_vel_y"), - EndVelZ = (float)results.GetDouble("end_vel_z"), - RunDate = results.GetInt32("run_date") - }); + mapTimes.Add(new PlayerMapTimeDataModel(results)); } } diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 493b3b5..d3b4fa1 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -47,7 +47,7 @@ public static void Teleport(this CBaseEntity entity, Vector_t? position = null, public static void SetCollisionGroup(this CCSPlayerController controller, CollisionGroup collisionGroup) { - if (!controller.IsValid || controller.Collision==null) return; + if (!controller.IsValid || controller.Collision == null) return; controller.Collision.CollisionAttribute.CollisionGroup = (byte)collisionGroup; controller.Collision.CollisionGroup = (byte)collisionGroup; diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index ec7ae41..6aa1d44 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -1,11 +1,19 @@ /* - ___ _____ _________ ___ - ___ / _/ |/ / __/ _ \/ _ | - ___ _/ // / _// , _/ __ | - ___ /___/_/|_/_/ /_/|_/_/ |_| + ___ _____ ____________ ___ + ___ / _/ | / / ____/ __ \/ | + ___ / // |/ / /_ / /_/ / /| | + ___ _/ // /| / __/ / _, _/ ___ | + ___ /___/_/ |_/_/ /_/ |_/_/ |_| + + ___ ___________ __ ___ _____ __ ______ + ___ /_ __/ ___// / / | / ___// / / / __ \ + ___ / / \__ \/ / / /| | \__ \/ /_/ / / / / + ___ / / ___/ / /___/ ___ |___/ / __ / /_/ / + ___ /_/ /____/_____/_/ |_/____/_/ /_/_____/ Official Timer plugin for the CS2 Surf Initiative. Copyright (C) 2024 Liam C. (Infra) + Copyright (C) 2025 tslashd This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -56,7 +64,6 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) // Globals private Dictionary playerList = new Dictionary(); // This can probably be done way better, revisit internal static TimerDatabase DB = new TimerDatabase(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string - // internal Map CurrentMap = null!; internal static Map CurrentMap = null!; /* ========== MAP START HOOKS ========== */ From 65a416e3c25c0f0e084ed067d9d1f5c98b768de7 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:52:05 +0300 Subject: [PATCH 26/52] chore: summaries, cleanup --- src/ST-API/Comms.cs | 20 ++ src/ST-API/JsonConverters.cs | 1 - src/ST-Events/Players.cs | 2 +- src/ST-Map/Map.cs | 33 +-- src/ST-Player/Player.cs | 2 +- src/ST-Player/PlayerHUD.cs | 1 + src/ST-Player/PlayerProfile.cs | 8 +- src/ST-Player/PlayerStats/CurrentRun.cs | 219 +------------------- src/ST-Player/PlayerStats/PersonalBest.cs | 80 +------ src/ST-Player/PlayerStats/PlayerStats.cs | 111 +--------- src/ST-Player/PlayerTimer.cs | 3 + src/ST-UTILS/Config.cs | 7 +- src/ST-UTILS/Data/ApiDataAccessService.cs | 21 +- src/ST-UTILS/Data/IDataAccessService.cs | 73 +++++-- src/ST-UTILS/Data/MySqlDataAccessService.cs | 31 +-- src/ST-UTILS/Injection.cs | 26 +-- 16 files changed, 123 insertions(+), 515 deletions(-) diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index 1a99a9f..e3b5161 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -12,6 +12,12 @@ private ApiMethod() { } private static readonly HttpClient _client = new(); private static readonly string base_addr = Config.ApiUrl; + /// + /// Executes a GET request to the specified URL and deserializes the response to type T. + /// + /// Type to deserialize response into + /// Relative URL to call + /// Deserialized T or null public static async Task GET(string url, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); @@ -52,6 +58,13 @@ private ApiMethod() { } } } + /// + /// Executes a POST request to the specified URL with the given body and returns the response. + /// + /// Type of the request body + /// Relative URL to call + /// Request body to send + /// API_PostResponseData or null public static async Task POST(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); @@ -95,6 +108,13 @@ private ApiMethod() { } } } + /// + /// Executes a PUT request to the specified URL with the given body and returns the response. + /// + /// Type of the request body + /// Relative URL to call + /// Request body to send + /// API_PostResponseData or null public static async Task PUT(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); diff --git a/src/ST-API/JsonConverters.cs b/src/ST-API/JsonConverters.cs index 377aacd..89e85bd 100644 --- a/src/ST-API/JsonConverters.cs +++ b/src/ST-API/JsonConverters.cs @@ -2,7 +2,6 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 08306d2..7247580 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -199,7 +199,7 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo /// Checks whether an IP is a local one. Allows testing the plugin in a local environment setup for GeoIP /// /// IP to check - /// True for Private IP + /// True for Private IP static bool IsPrivateIP(string ip) { var ipParts = ip.Split('.'); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 7fc85ba..56728f8 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -119,7 +119,7 @@ public static async Task CreateAsync(string name) private async Task InitializeAsync([CallerMemberName] string methodName = "") { // Load zones - Map_Load_Zones(); + MapLoadZones(); _logger.LogInformation("[{ClassName}] {MethodName} -> Zones have been loaded. | Bonuses: {Bonuses} | Stages: {Stages} | Checkpoints: {Checkpoints}", nameof(Map), methodName, this.Bonuses, this.Stages, this.TotalCheckpoints ); @@ -144,7 +144,7 @@ private async Task InitializeAsync([CallerMemberName] string methodName = "") // To-do: This loops through all the triggers. While that's great and comprehensive, some maps have two triggers with the exact same name, because there are two // for each side of the course (left and right, for example). We should probably work on automatically catching this. // Maybe even introduce a new naming convention? - internal void Map_Load_Zones([CallerMemberName] string methodName = "") + internal void MapLoadZones([CallerMemberName] string methodName = "") { // Gathering zones from the map IEnumerable triggers = Utilities.FindAllEntitiesByDesignerName("trigger_multiple"); @@ -267,7 +267,6 @@ internal void Map_Load_Zones([CallerMemberName] string methodName = "") /// /// Inserts a new map entry in the database. - /// Automatically detects whether to use API Calls or MySQL query. /// internal async Task InsertMapInfo([CallerMemberName] string methodName = "") { @@ -283,9 +282,8 @@ internal async Task InsertMapInfo([CallerMemberName] string methodName = "") try { - // this.ID = await _dataService.InsertMapInfoAsync(mapInfo); - int mapId = await _dataService.InsertMapInfoAsync(mapInfo); - this.ID = mapId; + this.ID = await _dataService.InsertMapInfoAsync(mapInfo); + _logger.LogInformation("[{ClassName}] {MethodName} -> Map '{Map}' inserted successfully with ID {ID}.", nameof(Map), methodName, this.Name, this.ID ); @@ -301,7 +299,6 @@ internal async Task InsertMapInfo([CallerMemberName] string methodName = "") /// /// Updates last played, stages, bonuses for the map in the database. - /// Automatically detects whether to use API Calls or MySQL query. /// internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") { @@ -337,11 +334,10 @@ internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") } /// - /// Load map info data using MySQL Queries and update the info as well or create a new entry. + /// Load/update/create Map table entry. /// Loads the record runs for the map as well. - /// Automatically detects whether to use API Calls or MySQL query. /// - /// Should we run UPDATE query for the map + /// Should we run UPDATE query for the map internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] string methodName = "") { bool newMap = false; @@ -385,14 +381,10 @@ internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] strin /// Extracts Map, Bonus, Stage record runs and the total completions for each style. /// (NOT TESTED WITH MORE THAN 1 STYLE) /// For the Map WR it also gets the Checkpoints data. - /// Automatically detects whether to use API Calls or MySQL query. - /// TODO: Re-do the API with the new query and fix the API assign of values + /// TODO?: Re-do the API with the new query and fix the API assign of values /// internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { - // int totalMapRuns = 0; - // int totalStageRuns = 0; - // int totalBonusRuns = 0; this.ConnectedMapTimes.Clear(); // Replay Stuff @@ -415,7 +407,6 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") WR[run.Style].EndVelZ = run.EndVelZ; WR[run.Style].RunDate = run.RunDate; WR[run.Style].Name = run.Name; - // totalMapRuns = run.TotalCount; ConnectedMapTimes.Add(run.ID); MapCompletions[run.Style] = run.TotalCount; @@ -477,16 +468,6 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") } } - /// - /// Redirects to `PersonalBest.LoadCheckpoints()`. - /// Extracts all entries from Checkpoints table of the World Record for the given `style` - /// - /// Style to load - internal async Task Get_Record_Run_Checkpoints(int style = 0) - { - await this.WR[style].LoadCheckpoints(); - } - /// /// Sets the data for a replay that has been retrieved from MapTimes data. /// Also sets the first Stage replay if no replays existed for stages until now. diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index cbbaed5..07fc0d6 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -40,7 +40,7 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen } /// - /// Checks if current player is spectating player

+ /// Checks if current player is spectating player ///

public bool IsSpectating(CCSPlayerController p) { diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 7f28516..79729a6 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -162,6 +162,7 @@ public void Display() } /// + /// Displays checkpoints comparison messages in player chat. /// Only calculates if the player has a PB, otherwise it will display N/A /// public void DisplayCheckpointMessages() diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 7f9124b..f360660 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -31,7 +31,6 @@ public PlayerProfile(ulong steamId, string name = "", string country = "") /// /// Deals with retrieving, creating and updating a Player's information in the database upon joining the server. - /// Automatically detects whether to use API Calls or Queries. /// /// Steam ID of the player /// Name of the player @@ -54,7 +53,7 @@ private async Task InitializeAsync([CallerMemberName] string methodName = "") } /// - /// Retrieves all the data for the player from the database. + /// Retrieves all the data for the player profile from the database. /// public async Task GetPlayerProfile([CallerMemberName] string methodName = "") { @@ -83,7 +82,7 @@ public async Task GetPlayerProfile([CallerMemberName] string methodName = "") } /// - /// Insert new player information into the database. + /// Insert new player profile information into the database. /// Retrieves the ID of the newly created player. /// public async Task InsertPlayerProfile([CallerMemberName] string methodName = "") @@ -105,10 +104,9 @@ public async Task InsertPlayerProfile([CallerMemberName] string methodName = "") } /// - /// Updates the information in the database for the player. Increments `connections` and changes nickname. + /// Updates the information in the database for the player profile. Increments `connections` and changes nickname. /// /// Player Name - /// public async Task UpdatePlayerProfile(string name, [CallerMemberName] string methodName = "") { this.Name = name; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index d99b4c5..998c8d7 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -28,7 +28,8 @@ public override void Reset() } /// - /// Saves the player's run to the database. + /// Saves the player's run to the database. + /// Supports all types of runs Map/Bonus/Stage. /// /// Player object /// Bonus number @@ -68,9 +69,6 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r int mapTimeId = await _dataService.InsertMapTimeAsync(mapTime); - if (recType == 0 && !Config.API.GetApiOnly()) - await SaveCurrentRunCheckpoints(player, true); - // Reload the times for the map await player.CurrMap.LoadMapRecordRuns(); @@ -101,219 +99,6 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r ); } - /* - public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") - { - string replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); - - _logger.LogTrace("[{ClassName}] {MethodName} -> SaveMapTime -> Sending total of {ReplayFramesTotal} replay frames.", - nameof(CurrentRun), methodName, replay_frames.Length - ); - - var stopwatch = Stopwatch.StartNew(); - - if (Config.API.GetApiOnly()) - { - return; - } - else - { - await InsertMapTime(player, bonus, stage, run_ticks, replay_frames, true); - - if (stage != 0 || bonus != 0) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> Inserted an entry for {Type} {Number} - {Ticks}", - nameof(CurrentRun), methodName, (stage != 0 ? "Stage" : "Bonus"), (stage != 0 ? stage : bonus), run_ticks - ); - } - else - { - await SaveCurrentRunCheckpoints(player, true); // Save this run's checkpoints - } - - await player.CurrMap.LoadMapRecordRuns(); // Reload the times for the Map - } - - stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", - nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() - ); - } - */ - - /// - /// Saves the CurrentRun of the player to the database. Does NOT support Bonus entries yet. - /// - public async Task InsertMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, string replay_frames = "", bool reloadData = false, [CallerMemberName] string methodName = "") - { - int playerId = player.Profile.ID; - int mapId = player.CurrMap.ID; - int style = player.Timer.Style; - int ticks = run_ticks == -1 ? this.Ticks : run_ticks; - int type = stage != 0 ? 2 : bonus != 0 ? 1 : 0; - float startVelX = this.StartVelX; - float startVelY = this.StartVelY; - float startVelZ = this.StartVelZ; - float endVelX = this.EndVelX; - float endVelY = this.EndVelY; - float endVelZ = this.EndVelZ; - - var stopwatch = Stopwatch.StartNew(); - - if (Config.API.GetApiOnly()) - { - // API Insert map goes here - } - else - { - // int updatePlayerRunTask = await SurfTimer.DB.WriteAsync( - var (updatePlayerRunTask, lastId) = await SurfTimer.DB.WriteAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, playerId, mapId, style, type, type == 2 ? stage : type == 1 ? bonus : 0, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), replay_frames)); - if (updatePlayerRunTask <= 0) - { - _logger.LogError("[{ClassName}] {MethodName} -> InsertMapTime -> Failed to insert/update player run in database. Player: {Name} ({SteamID})", - nameof(CurrentRun), methodName, player.Profile.Name, player.Profile.SteamID - ); - Exception ex = new($"CS2 Surf ERROR >> internal class CurrentRun -> public async Task InsertMapTime -> Failed to insert/update player run in database. Player: {player.Profile.Name} ({player.Profile.SteamID})"); - throw ex; - } - - if (reloadData && type == 0) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", - nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID - ); - await player.Stats.PB[style].LoadPlayerSpecificMapTimeData(player); // Load the Map MapTime PB data again (will refresh the MapTime ID for the Checkpoints query) - } - else if (reloadData && type == 1) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Bonus MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", - nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.BonusPB[bonus][style].ID - ); - await player.Stats.BonusPB[bonus][style].LoadPlayerSpecificMapTimeData(player); // Load the Bonus MapTime PB data again (will refresh the MapTime ID) - } - else if (reloadData && type == 2) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> InsertMapTime -> Will reload Stage MapTime (Type {type}) data for '{Name}' (ID {MapTimeID}))", - nameof(CurrentRun), methodName, type, player.Profile.Name, player.Stats.StagePB[stage][style].ID - ); - await player.Stats.StagePB[stage][style].LoadPlayerSpecificMapTimeData(player); // Load the Stage MapTime PB data again (will refresh the MapTime ID) - } - } - - stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished InsertMapTime for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", - nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() - ); - } - - /// - /// Saves the `CurrentRunCheckpoints` dictionary to the database - /// API deals with this when sending a SaveMapTime of type 0, so we do not have an endpoint for it - /// - /// Player object - /// Whether to reload the PersonalBest Checkpoints data for the Player. - public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false, [CallerMemberName] string methodName = "") - { - _logger.LogInformation("[{ClassName}] {MethodName} -> Saving {Count} checkpoints...", - nameof(CurrentRun), methodName, this.Checkpoints.Count); - - var stopwatch = Stopwatch.StartNew(); - - var checkpoints = this.Checkpoints.Select(cp => new Checkpoint - { - CP = cp.Key, - Ticks = cp.Value.Ticks, - EndTouch = cp.Value.EndTouch, - StartVelX = cp.Value.StartVelX, - StartVelY = cp.Value.StartVelY, - StartVelZ = cp.Value.StartVelZ, - EndVelX = cp.Value.EndVelX, - EndVelY = cp.Value.EndVelY, - EndVelZ = cp.Value.EndVelZ, - Attempts = cp.Value.Attempts - }); - - int mapTimeId = player.Stats.PB[player.Timer.Style].ID; - - // await _dataService.SaveRunCheckpointsAsync(mapTimeId, checkpoints); - - this.Checkpoints.Clear(); - - if (reloadData) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> Reloading Checkpoints data for '{Name}' (ID {MapTimeID})", - nameof(CurrentRun), methodName, player.Profile.Name, mapTimeId); - await player.Stats.PB[player.Timer.Style].LoadCheckpoints(); - } - - stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished saving checkpoints for '{Name}' in {Elapsed}ms | API = {API}", - nameof(CurrentRun), methodName, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly()); - } - - /* Delete after testing it using the ApiDataAccessService - public async Task SaveCurrentRunCheckpoints(Player player, bool reloadData = false, [CallerMemberName] string methodName = "") - { - _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will send {ThisRunCheckpoints} ({CheckpointsCount}) checkpoints to DB....", - nameof(CurrentRun), methodName, this.Checkpoints.Count, this.Checkpoints.Count - ); - var stopwatch = Stopwatch.StartNew(); - - int style = player.Timer.Style; - int mapTimeId = player.Stats.PB[style].ID; - List commands = new List(); - // Loop through the checkpoints and insert/update them in the database for the run - foreach (var item in this.Checkpoints) - { - int cp = item.Key; - int ticks = item.Value!.Ticks; - int endTouch = item.Value!.EndTouch; - 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 - _logger.LogDebug("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> CP: {Checkpoint} | MapTime ID: {MapTimeID} | Time: {Time} | Ticks: {Ticks} | SVX {StartVelX} | SVY {StartVelY} | SVZ {StartVelZ} | EVX {EndVelX} | EVY {EndVelY} | EVZ {EndVelZ}", - nameof(CurrentRun), methodName, cp, mapTimeId, endTouch, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ - ); - _logger.LogDebug("Query to send:\n{Query}", - string.Format(Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, - mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch) - ); -#endif - - if (item.Value != null && player.Controller.PlayerPawn.Value != null) - { - string command = string.Format( - Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, - mapTimeId, cp, ticks, startVelX, startVelY, startVelZ, endVelX, endVelY, endVelZ, attempts, endTouch - ); - commands.Add(command); - } - } - await SurfTimer.DB.TransactionAsync(commands); - this.Checkpoints.Clear(); - - if (reloadData) - { - _logger.LogInformation("[{ClassName}] {MethodName} -> SaveCurrentRunCheckpoints -> Will reload Checkpoints data for '{Name}' (ID {MapTimeID})", - nameof(CurrentRun), methodName, player.Profile.Name, player.Stats.PB[player.Timer.Style].ID - ); - await player.Stats.PB[player.Timer.Style].LoadCheckpoints(); // Load the Checkpoints data again - } - - stopwatch.Stop(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Finished SaveCurrentRunCheckpoints(reloadData = {reloadData}) for player '{Name}' in {ElapsedMilliseconds}ms | API = {API}", - nameof(CurrentRun), methodName, reloadData, player.Profile.Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() - ); - } - -*/ public void PrintSituations(Player player) { Console.WriteLine($"========================== FOUND SITUATIONS =========================="); diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 074d744..7707f55 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -33,10 +33,9 @@ public PersonalBest() : base() ///
public async Task LoadCheckpoints([CallerMemberName] string methodName = "") { - // 1) ask the data service for your checkpoints var cps = await _dataService.LoadCheckpointsAsync(this.ID); - // 2) if none, just return + // If nothing found, log and return if (cps == null || cps.Count == 0) { _logger.LogInformation( @@ -46,10 +45,8 @@ public async Task LoadCheckpoints([CallerMemberName] string methodName = "") return; } - // 3) otherwise assign this.Checkpoints = cps; - // 4) log how many you got _logger.LogInformation( "[{ClassName}] {MethodName} -> Loaded {Count} checkpoints for run {RunId}.", nameof(PersonalBest), methodName, cps.Count, this.ID @@ -64,7 +61,6 @@ public async Task LoadCheckpoints([CallerMemberName] string methodName = "") /// Player object public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") { - // 1) call the data service, passing only the primitives: var model = await _dataService.LoadPersonalBestRunAsync( pbId: this.ID == -1 ? (int?)null : this.ID, playerId: player.Profile.ID, @@ -73,7 +69,7 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName style: player.Timer.Style ); - // 2) if nothing found, log & return + // If nothing found, log and return if (model == null) { _logger.LogTrace( @@ -84,7 +80,6 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName return; } - // 3) map back into your instance this.ID = model.ID; this.Ticks = model.Ticks; this.Rank = model.Rank; @@ -103,75 +98,4 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName this.ID, player.Profile.Name ); } - - /* Delete after testing with API? - public async Task PB_LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") - { - // Console.WriteLine($"CS2 Surf ERROR >> internal class PersonalBest -> public async Task PB_LoadPlayerSpecificMapTimeData -> QUERY:\n{string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style)}"); - // using (var results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_RUNTIME, player.Profile.ID, player.CurrMap.ID, 0, player.Timer.Style))) - if (this == null) - { - #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> PersonalBest object is null.", - nameof(PersonalBest), methodName - ); - #endif - - return; - } - - MySqlConnector.MySqlDataReader? results = null; - - // Console.WriteLine(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); - - if (this.ID == -1) - results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, player.Profile.ID, SurfTimer.CurrentMap.ID, this.Type, player.Timer.Style)); - else - results = await SurfTimer.DB.QueryAsync(string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, this.ID)); - - #if DEBUG - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.ID {this.ID} "); - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.Ticks {this.Ticks} "); - Console.WriteLine($"----> public async Task PB_LoadPlayerSpecificMapTimeData -> this.RunDate {this.RunDate} "); - #endif - - if (results == null || !results.HasRows) - { - // #if DEBUG - _logger.LogTrace("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> No MapTime data found for '{playerName}' ({playerID}). (Results Null? {IsNull})", - nameof(PersonalBest), methodName, player.Profile.Name, player.Profile.ID, results == null - ); - // #endif - - return; - } - - while (results.Read()) - { - #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> Loading MapTime Run: RunID {RunID} | RunTicks {RunTicks} | StartVelX {StartVelX} | StartVelY {StartVelY}.", - nameof(PersonalBest), methodName, results.GetInt32("id"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y") - ); - #endif - - this.ID = results.GetInt32("id"); - this.Ticks = results.GetInt32("run_time"); - this.Rank = results.GetInt32("rank"); - this.StartVelX = (float)results.GetDouble("start_vel_x"); - this.StartVelY = (float)results.GetDouble("start_vel_y"); - this.StartVelZ = (float)results.GetDouble("start_vel_z"); - this.EndVelX = (float)results.GetDouble("end_vel_x"); - this.EndVelY = (float)results.GetDouble("end_vel_y"); - this.EndVelZ = (float)results.GetDouble("end_vel_z"); - this.RunDate = results.GetInt32("run_date"); - } - - // #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> PB_LoadPlayerSpecificMapTimeData -> MapTime ID {ID} (Type: {Type}) loaded for '{PlayerName}' with time {RunTime}", - nameof(PersonalBest), methodName, this.ID, this.Type, player.Profile.Name, PlayerHUD.FormatTime(this.Ticks) - ); - // #endif - } - */ - } \ No newline at end of file diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 3158cdc..58bff1f 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -77,6 +77,9 @@ public PlayerStats([CallerMemberName] string methodName = "") ); } + /// + /// Not used + /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { @@ -115,6 +118,9 @@ public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] s } } + /// + /// Not used + /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { @@ -143,6 +149,9 @@ public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] } } + /// + /// Not used + /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { @@ -253,106 +262,4 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma #endif } } - - - /* - public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") - { - using (var playerStats = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, player.Profile.ID, SurfTimer.CurrentMap.ID))) - { - // int style = player.Timer.Style; - int style; - if (!playerStats.HasRows) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> No MapTimes data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; - } - while (playerStats.Read()) - { - // Load data into each PersonalBest object - if (playerStats.GetInt32("type") == 1) // Bonus time - { - #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", - nameof(PlayerStats), methodName - ); - #endif - int bonusNum = playerStats.GetInt32("stage"); - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - BonusPB[bonusNum][style].ID = playerStats.GetInt32("id"); - BonusPB[bonusNum][style].Ticks = playerStats.GetInt32("run_time"); - BonusPB[bonusNum][style].Type = playerStats.GetInt32("type"); - BonusPB[bonusNum][style].Rank = playerStats.GetInt32("rank"); - BonusPB[bonusNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - BonusPB[bonusNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - BonusPB[bonusNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - BonusPB[bonusNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - BonusPB[bonusNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - BonusPB[bonusNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - BonusPB[bonusNum][style].RunDate = playerStats.GetInt32("run_date"); - } - else if (playerStats.GetInt32("type") == 2) // Stage time - { - #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", - nameof(PlayerStats), methodName - ); - #endif - int stageNum = playerStats.GetInt32("stage"); - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - StagePB[stageNum][style].ID = playerStats.GetInt32("id"); - StagePB[stageNum][style].Ticks = playerStats.GetInt32("run_time"); - StagePB[stageNum][style].Type = playerStats.GetInt32("type"); - StagePB[stageNum][style].Rank = playerStats.GetInt32("rank"); - StagePB[stageNum][style].StartVelX = (float)playerStats.GetDouble("start_vel_x"); - StagePB[stageNum][style].StartVelY = (float)playerStats.GetDouble("start_vel_y"); - StagePB[stageNum][style].StartVelZ = (float)playerStats.GetDouble("start_vel_z"); - StagePB[stageNum][style].EndVelX = (float)playerStats.GetDouble("end_vel_x"); - StagePB[stageNum][style].EndVelY = (float)playerStats.GetDouble("end_vel_y"); - StagePB[stageNum][style].EndVelZ = (float)playerStats.GetDouble("end_vel_z"); - StagePB[stageNum][style].RunDate = playerStats.GetInt32("run_date"); - // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> StagePB Loaded: - // StagePB[{stageNum}][{style}] = - // Stage: {stageNum} | ID: {StagePB[stageNum][style].ID} | Ticks: {StagePB[stageNum][style].Ticks} | Rank: {StagePB[stageNum][style].Rank} | Type: {StagePB[stageNum][style].Type}" - // ); - } - else // Map time - { - #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", - nameof(PlayerStats), methodName - ); - #endif - style = playerStats.GetInt32("style"); // To-do: Uncomment when style is implemented - PB[style].ID = playerStats.GetInt32("id"); - PB[style].Ticks = playerStats.GetInt32("run_time"); - PB[style].Type = playerStats.GetInt32("type"); - PB[style].Rank = playerStats.GetInt32("rank"); - 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].RunDate = playerStats.GetInt32("run_date"); - // Console.WriteLine(@$"DEBUG >> (func) LoadPlayerMapTimesData >> PB Loaded: - // PB[{style}] = - // ID: {PB[style].ID} | Ticks: {PB[style].Ticks} | Rank: {PB[style].Rank} | Type: {PB[style].Type}" - // ); - await this.PB[style].LoadCheckpoints(); - } - // Console.WriteLine($"============== CS2 Surf DEBUG >> internal class PlayerStats -> public async Task LoadPlayerMapTimesData -> 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 - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData -> Loaded PB[{Style}] run {RunID} (Rank {Rank}) for '{PlayerName}' (ID {PlayerID}).", - nameof(PlayerStats), methodName, style, PB[style].ID, PB[style].Rank, player.Profile.Name, player.Profile.ID - ); - #endif - } - } - } - */ - } \ No newline at end of file diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index b87ef7d..1e4ddc4 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -22,6 +22,9 @@ internal class PlayerTimer // 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 + /// + /// Different types of time formatting for chat and HUD + /// // Time Formatting - To-do: Move to player settings maybe? public enum TimeFormatStyle { diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 0d5067c..9f5824d 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -1,7 +1,6 @@ using System.Reflection; using System.Text.Json; using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; @@ -116,9 +115,9 @@ public static class MySQL private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); /// - /// Retrieves the connection details for connecting to the MySQL Database + /// Retrieves the connection details for connecting to the MySQL Database /// - /// A connection + /// A connection string public static string GetConnectionString() { string host = ConfigDocument.RootElement.GetProperty("host").GetString()!; @@ -136,7 +135,7 @@ public static string GetConnectionString() } /// - /// Contains all the queries used by MySQL for the SurfTimer plugin. + /// Contains all the queries used by MySQL for the SurfTimer plugin. /// public static class Queries { diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index ec7d48a..68876f4 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -8,16 +8,15 @@ public class ApiDataAccessService : IDataAccessService { private readonly ILogger _logger; + /// + /// Add/load data using API calls. + /// public ApiDataAccessService() { _logger = SurfTimer.ServiceProvider.GetRequiredService>(); } /* PersonalBest.cs */ - /// - /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. - /// Bonus and Stage runs should NOT have any checkpoints. - /// public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using API data access service.", @@ -119,11 +118,6 @@ public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName } } - /// - /// Gets and loads all the record times for a given map ID - /// - /// ID of the map in DB - /// public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { var apiRuns = await ApiMethod.GET( @@ -218,7 +212,6 @@ public async Task> GetPlayerMapTimesAsync(int playe /* CurrentRun.cs */ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") { - // Initialize the API structure for POST request var apiSaveMapTime = new API_SaveMapTime(mapTime); /* @@ -267,13 +260,5 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe return postResponse.last_id; } - - // public async Task SaveRunCheckpointsAsync(int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "") - // { - // // TODO: Implement API logic - // // throw new NotImplementedException(); - - // } - } } diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs index 0efc60a..fdc5866 100644 --- a/src/ST-UTILS/Data/IDataAccessService.cs +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -1,26 +1,35 @@ using System.Runtime.CompilerServices; -using CounterStrikeSharp.API.Modules.Entities; namespace SurfTimer.Data { + /// + /// Contains all methods for data retrieval or insertion by all access services (API, MySQL) + /// public interface IDataAccessService { /* PersonalBest.cs */ /// - /// Load all checkpoints for a given run ID (map time). - /// Returns an empty dictionary if none found. + /// Retrieve Checkpoints table entries for a given run ID (map time). + /// Bonus and Stage runs should NOT have any checkpoints. /// + /// ID of the run from DB + /// Dictionary[int, Checkpoint] data or NULL if none found Task> LoadCheckpointsAsync( int runId, [CallerMemberName] string methodName = "" ); /// - /// Load a personal-best run for a given player. - /// - If pbId is null or -1, load by playerId/mapId/type/style. - /// - If pbId has a value, load that specific run. - /// Returns null if not found. + /// Load a personal-best run for a given player from MapTime table through API or MySQL. + /// If pbId is null or -1, load by playerId/mapId/type/style. + /// If pbId has a value, load that specific run. /// + /// [Optional] ID of the run from DB. If present other arguments will be ignored + /// ID of the player from DB. If pbId is null or -1 + /// ID of the map from DB. If pbId is null or -1 + /// Run Type (0 = Map ; 1 = Bonus ; 2 = Stage). If pbId is null or -1 + /// If pbId is null or -1 + /// PersonalBestDataModel data or null if not found Task LoadPersonalBestRunAsync( int? pbId, int playerId, @@ -32,47 +41,85 @@ Task> LoadCheckpointsAsync( /* Map.cs */ + /// + /// Retrieves Map table entry for map through API or MySQL. + /// + /// Name of map + /// MapInfoDataModel data Task GetMapInfoAsync( string mapName, [CallerMemberName] string methodName = "" ); + /// + /// Adds Map table entry for map through API or MySQL. + /// + /// Data to add in table + /// int mapId Task InsertMapInfoAsync( MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" ); + /// + /// Updates Map table entry for map through API or MySQL. + /// + /// Data to update in table Task UpdateMapInfoAsync( MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" ); + /// + /// Retrieves MapTime table record runs for given mapId through API or MySQL. + /// + /// ID from DB + /// List[MapRecordRunDataModel] data Task> GetMapRecordRunsAsync( int mapId, [CallerMemberName] string methodName = "" ); /* PlayerProfile.cs */ + /// + /// Retrieve Player table entry for the player through API or MySQL. + /// + /// SteamID for the player + /// PlayerProfileDataModel data Task GetPlayerProfileAsync( ulong steamId, [CallerMemberName] string methodName = "" ); + /// + /// Adds Player table entry for the player through API or MySQL. + /// + /// Data to add in table + /// int playerId given by DB Task InsertPlayerProfileAsync( PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" ); + /// + /// Updates Player table entry for the player through API or MySQL. + /// + /// Data to update in table Task UpdatePlayerProfileAsync( PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" ); /* PlayerStats.cs */ + /// + /// Retrieves ALL MapTime table entries for playerId and mapId combo through API or MySQL. + /// + /// ID from DB + /// ID from DB + /// List[PlayerMapTimeDataModel] data Task> GetPlayerMapTimesAsync( int playerId, int mapId, [CallerMemberName] string methodName = "" ); /* CurrentRun.cs */ + /// + /// Adds/updates a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 + /// + /// Data to insert/update in table + /// int mapTimeId given by DB Task InsertMapTimeAsync( - // Task InsertMapTimeAsync( MapTimeDataModel mapTime, [CallerMemberName] string methodName = "" ); - /* Merged with InsertMapTimeAsync */ - // Task SaveRunCheckpointsAsync( - // int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "" - // ); - } } diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index f310f29..15bb61c 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using System.Runtime.CompilerServices; -using System.Data; namespace SurfTimer.Data { @@ -10,16 +9,15 @@ public class MySqlDataAccessService : IDataAccessService { private readonly ILogger _logger; + /// + /// Add/load data using MySQL connection and queries. + /// public MySqlDataAccessService() { _logger = SurfTimer.ServiceProvider.GetRequiredService>(); } /* PersonalBest.cs */ - /// - /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. - /// Bonus and Stage runs should NOT have any checkpoints. - /// public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using MySQL data access service.", @@ -112,7 +110,6 @@ public async Task> LoadCheckpointsAsync(int runId, [ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") { - // int rowsWritten = await SurfTimer.DB.WriteAsync( var (rowsWritten, lastId) = await SurfTimer.DB.WriteAsync( string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(mapInfo.Name), @@ -168,7 +165,6 @@ public async Task> GetMapRecordRunsAsync(int mapId, } - /* PlayerProfile.cs */ public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { @@ -216,7 +212,6 @@ public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [Call } - /* PlayerStats.cs */ public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { @@ -237,7 +232,6 @@ public async Task> GetPlayerMapTimesAsync(int playe } - /* CurrentRun.cs */ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") { @@ -266,7 +260,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe } // Write the checkpoints after we have the `lastId` - if (mapTime.Checkpoints != null && mapTime.Checkpoints.Count > 0) + if (mapTime.Checkpoints != null && mapTime.Checkpoints.Count > 0 && mapTime.Type == 0) { var commands = new List(); foreach (var cp in mapTime.Checkpoints.Values) @@ -279,24 +273,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe await SurfTimer.DB.TransactionAsync(commands); } - return (int)lastId; } - - // public async Task SaveRunCheckpointsAsync(int mapTimeId, IEnumerable checkpoints, [CallerMemberName] string methodName = "") - // { - // var commands = new List(); - - // foreach (var cp in checkpoints) - // { - // commands.Add(string.Format( - // Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, - // mapTimeId, cp.CP, cp.Ticks, cp.StartVelX, cp.StartVelY, cp.StartVelZ, - // cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); - // } - - // await SurfTimer.DB.TransactionAsync(commands); - // } - } } diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index 7a370bf..f5fdf95 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -58,6 +58,9 @@ public void ConfigureServices(IServiceCollection services) } } +/// +/// Handles translation files +/// public class LocalizationService { // Localizer as a Singleton @@ -68,25 +71,4 @@ public static void Init(IStringLocalizer localizer) { Localizer = localizer; } -} - -/* -public class Injection : IPluginServiceCollection -{ - public void ConfigureServices(IServiceCollection serviceCollection) - { - // Register Logging - serviceCollection.AddLogging(builder => - { - builder.AddConsole(); - builder.SetMinimumLevel(LogLevel.Debug); - }); - - - // Register Dependencies - serviceCollection.AddScoped(); // Multiple instances for different players - serviceCollection.AddScoped(); // Multiple instances for different players - serviceCollection.AddSingleton(); // Single instance for 1 Map object - } -} -*/ \ No newline at end of file +} \ No newline at end of file From 64ddd08755db8082931071e6c9660593641d7381 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:03:29 +0300 Subject: [PATCH 27/52] dev: TriggerStartTouch separate zone handlers, missing values in Models --- src/ST-Events/TriggerStartTouch.cs | 862 +++++++++++------------ src/ST-Player/PlayerStats/PlayerStats.cs | 6 +- src/ST-UTILS/Data/Models.cs | 14 + 3 files changed, 425 insertions(+), 457 deletions(-) diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 7e3083d..981b45d 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -2,17 +2,53 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API; +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; namespace SurfTimer; public partial class SurfTimer { + internal enum ZoneType + { + MapEnd, + MapStart, + StageStart, + Checkpoint, + BonusStart, + BonusEnd, + Unknown + } + + /// + /// Determines the zone type based on the entity name. + /// + /// Name of the entity. + /// ZoneType data + private ZoneType GetZoneType(string entityName) + { + if (entityName == "map_end") + return ZoneType.MapEnd; + else if (entityName.Contains("map_start") || entityName.Contains("s1_start") || entityName.Contains("stage1_start")) + return ZoneType.MapStart; + else if (Regex.IsMatch(entityName, @"^s([1-9][0-9]?|tage[1-9][0-9]?)_start$")) + return ZoneType.StageStart; + else if (Regex.IsMatch(entityName, @"^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$")) + return ZoneType.Checkpoint; + else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_start$")) + return ZoneType.BonusStart; + else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_end$")) + return ZoneType.BonusEnd; + + return ZoneType.Unknown; + } + /// /// Handler for trigger start touch hook - CBaseTrigger_StartTouchFunc /// /// CounterStrikeSharp.API.Core.HookResult /// - internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) + internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay, [CallerMemberName] string methodName = "") { CBaseTrigger trigger = new CBaseTrigger(caller.Handle); CBaseEntity entity = new CBaseEntity(activator.Handle); @@ -24,544 +60,462 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // 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)) { - Console.WriteLine($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); - Exception exception = new($"CS2 Surf ERROR >> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + _logger.LogCritical("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {PlayerName} ({UserId})", + nameof(SurfTimer), methodName, client.PlayerName, client.UserId + ); + + Exception exception = new($"[{nameof(SurfTimer)}] {methodName} -> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); throw exception; } // 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 if (DB == null) { - Exception exception = new Exception("CS2 Surf ERROR >> OnTriggerStartTouch (Map end zone) -> DB object is null, this shouldn't happen."); + _logger.LogCritical("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> DB object is null, this shouldn't happen.", + nameof(SurfTimer), methodName + ); + + Exception exception = new Exception($"[{nameof(SurfTimer)}] {methodName} -> OnTriggerStartTouch -> DB object is null, this shouldn't happen."); throw exception; } 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 - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; + ZoneType currentZone = GetZoneType(trigger.Entity.Name); - // Map end zones -- hook into map_end - if (trigger.Entity.Name == "map_end") + switch (currentZone) { - player.Controller.PrintToCenter($"Map End"); + // Map end zones -- hook into map_end + case ZoneType.MapEnd: + HandleMapEndZone(player); + break; + // Map start zones -- hook into map_start, (s)tage1_start + case ZoneType.MapStart: + HandleMapStartZone(player, trigger); + break; + // Stage start zones -- hook into (s)tage#_start + case ZoneType.StageStart: + HandleStageStartZone(player, trigger); + break; + // Map checkpoint zones -- hook into map_(c)heck(p)oint# + case ZoneType.Checkpoint: + HandleCheckpointZone(player, trigger); + break; + // Bonus start zones -- hook into (b)onus#_start + case ZoneType.BonusStart: + HandleBonusStartZone(player, trigger); + break; + // Bonus end zones -- hook into (b)onus#_end + case ZoneType.BonusEnd: + HandleBonusEndZone(player, trigger); + break; + + default: + _logger.LogError("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> Unknown MapZone detected in OnTriggerStartTouch. Name: {ZoneName}", + nameof(SurfTimer), methodName, trigger.Entity.Name + ); + break; + } + } + return HookResult.Continue; + } - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; - player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); + private void HandleMapEndZone(Player player, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; - player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run - player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run - player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run - player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run + player.Controller.PrintToCenter($"Map End"); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); - // MAP END ZONE - Map RUN - if (player.Timer.IsRunning && !player.Timer.IsStageMode) - { - player.Timer.Stop(); - bool saveMapTime = false; - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run + player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run + player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run + player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR - { - saveMapTime = true; - int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" - ); - } - else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map - { - saveMapTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map - { - saveMapTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map - { - saveMapTime = true; - int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" - ); - } - else // Player did not beat their existing PersonalBest for the map nor the map record - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - if (saveMapTime) - { - player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => - { - await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data - }); - } + // MAP END ZONE - Map RUN + if (player.Timer.IsRunning && !player.Timer.IsStageMode) + { + player.Timer.Stop(); + bool saveMapTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; -#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}, {pStyle}, 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 + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR + { + saveMapTime = true; + int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" + ); + } + else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map + { + saveMapTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map + { + saveMapTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map + { + saveMapTime = true; + int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); + } + else // Player did not beat their existing PersonalBest for the map nor the map record + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } - // Add entry in DB for the run - if (!player.Timer.IsPracticeMode) + if (saveMapTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data + }); + } + + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) + { + // Should we also save a last stage run? + if (CurrentMap.Stages > 0) + { + AddTimer(0.1f, () => { - // Should we also save a last stage run? - if (CurrentMap.Stages > 0) - { - AddTimer(0.1f, () => - { - // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object - int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); - }); - } - - // This section checks if the PB is better than WR - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) - { - AddTimer(2f, () => - { - Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); - CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again - CurrentMap.ReplayManager.MapWR.FormatBotName(); - }); - } - - } - - // API - /* - // Add entry in DB for the run - if (!player.Timer.IsPracticeMode) { - API_CurrentRun? last_stage_time = null; - if (CurrentMap.Stages > 0) - { - int last_exit_tick = player.ReplayRecorder.LastExitTick(); - int last_enter_tick = player.ReplayRecorder.LastEnterTick(); - - int stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this - int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; - - int tt = -1; - if (last_exit_tick - last_enter_tick > 2*64) - tt = last_exit_tick - 2*64; - else - tt = last_enter_tick; - - last_stage_time = new API_CurrentRun - { - player_id = player.Profile.ID, - map_id = player.CurrMap.ID, - style = style, - type = 2, - stage = CurrentMap.Stages, - run_time = stage_run_time, - run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) - }; - } - AddTimer(1.5f, () => { - List checkpoints = new List(); - foreach (var cp in player.Stats.ThisRun.Checkpoint) - { - checkpoints.Add(new API_Checkpoint - { - cp = cp.Key, - run_time = cp.Value.Ticks, - start_vel_x = cp.Value.StartVelX, - start_vel_y = cp.Value.StartVelY, - start_vel_z = cp.Value.StartVelZ, - end_vel_x = cp.Value.EndVelX, - end_vel_y = cp.Value.EndVelY, - end_vel_z = cp.Value.EndVelZ, - end_touch = 0, // ????? - attempts = cp.Value.Attempts - }); - } - - API_CurrentRun map_time = new API_CurrentRun - { - player_id = player.Profile.ID, - map_id = player.CurrMap.ID, - style = style, - type = 0, - stage = 0, - run_time = player.Stats.ThisRun.Ticks, - run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - checkpoints = checkpoints, - replay_frames = player.ReplayRecorder.SerializeReplay() - }; - - Task.Run(async () => { - System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved map time"); - await ApiCall.POST("/surftimer/savemaptime", map_time); - - if (last_stage_time != null) - { - await ApiCall.POST("/surftimer/savestagetime", last_stage_time); - System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> Saved last stage time"); - player.Stats.LoadStageTime(player); - } - - player.Stats.LoadMapTime(player); - await CurrentMap.ApiGetMapRecordAndTotals(); // 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[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) - { - AddTimer(2f, () => { - System.Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR PB"); - CurrentMap.ReplayManager.MapWR.LoadReplayData(); - - AddTimer(1.5f, () => { - CurrentMap.ReplayManager.MapWR.FormatBotName(); - }); - }); - } - } - */ + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); } - else if (player.Timer.IsStageMode) - { - player.Timer.Stop(); - if (!player.Timer.IsPracticeMode) + // This section checks if the PB is better than WR + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => { - AddTimer(0.1f, () => - { - // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object - int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); - }); - } + Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); + CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); } -#if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); -#endif } + } + // MAP END ZONE - Stage RUN + else if (player.Timer.IsStageMode) + { + player.Timer.Stop(); - // 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") - ) + if (!player.Timer.IsPracticeMode) { - // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts - if (!player.ReplayRecorder.IsSaving) + AddTimer(0.1f, () => { - player.ReplayRecorder.Reset(); // Start replay recording - player.ReplayRecorder.Start(); // Start replay recording - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - player.Timer.Reset(); - player.Stats.ThisRun.Checkpoints.Clear(); - player.Controller.PrintToCenter($"Map Start ({trigger.Entity.Name})"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } + } #if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); #endif - } - else - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); - } - } + } - // Stage start zones -- hook into (s)tage#_start - 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); + private static void HandleMapStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts + if (!player.ReplayRecorder.IsSaving) + { + player.ReplayRecorder.Reset(); // Start replay recording + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + player.Timer.Reset(); + player.Stats.ThisRun.Checkpoints.Clear(); + player.Controller.PrintToCenter($"Map Start ({trigger.Entity!.Name})"); - if (!player.ReplayRecorder.IsRecording) - player.ReplayRecorder.Start(); +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); +#endif + } + else + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); + } + } - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; - player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); + private void HandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); - bool failed_stage = false; - if (player.Timer.Stage == stage) - failed_stage = true; + if (!player.ReplayRecorder.IsRecording) + player.ReplayRecorder.Start(); - // Reset/Stop the Stage timer - // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) - if (player.Timer.IsStageMode) - { - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); - if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) - { - int stage_run_time = player.Timer.Ticks; - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); - } - player.Timer.Reset(); - player.Timer.IsStageMode = true; - // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); - } + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; + player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); - player.Timer.Stage = stage; + bool failed_stage = false; + if (player.Timer.Stage == stage) + failed_stage = true; + + // Reset/Stop the Stage timer + // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) + if (player.Timer.IsStageMode) + { + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks; + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } + player.Timer.Reset(); + player.Timer.IsStageMode = true; + // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); + } + + 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.Checkpoints.Count <= stage}"); + 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.Checkpoints.Count <= stage}"); #endif - // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) - { - // Save Stage MapTime during a Map run - if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) - { - int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); - } + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) + { + // Save Stage MapTime during a Map run + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } - player.Timer.Checkpoint = stage - 1; // Stage = Checkpoint when in a run on a Staged map + player.Timer.Checkpoint = stage - 1; // 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`: {stage} | player.Timer.Checkpoint: {stage - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); + Console.WriteLine($"============== Initial entity value: {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} | Assigned to `stage`: {stage} | player.Timer.Checkpoint: {stage - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); #endif - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(); + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) - { - Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, - player.Timer.Ticks, - velocity.X, - velocity.Y, - velocity.Z, - -1.0f, - -1.0f, - -1.0f, - 0, - 1); - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; - } - else - { - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; - } - } + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) + { + Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, + player.Timer.Ticks, + velocity.X, + velocity.Y, + velocity.Z, + -1.0f, + -1.0f, + -1.0f, + 0, + 1); + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; + } + } #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"); + 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"); #endif - } + } - // 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); - player.Timer.Checkpoint = checkpoint; + private static void HandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + 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* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < checkpoint) - { + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < checkpoint* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.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)}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); + 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)}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); #endif - if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) - { - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; - player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); - } + if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; + player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); + } - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(); + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); - if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) - { - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - Checkpoint cp2 = new Checkpoint(checkpoint, - player.Timer.Ticks, - velocity.X, - velocity.Y, - velocity.Z, - -1.0f, - -1.0f, - -1.0f, - 0, - 1); - player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; - } - else - { - player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; - } - } + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) + { + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + Checkpoint cp2 = new Checkpoint(checkpoint, + player.Timer.Ticks, + velocity.X, + velocity.Y, + velocity.Z, + -1.0f, + -1.0f, + -1.0f, + 0, + 1); + player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; + } + } #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"); + 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"); #endif - } + } - // Bonus start zones -- hook into (b)onus#_start - 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); - player.Timer.Bonus = bonus; + private static void HandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + int bonus = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + player.Timer.Bonus = bonus; - player.Timer.Reset(); - player.Timer.IsBonusMode = true; + player.Timer.Reset(); + player.Timer.IsBonusMode = true; - player.ReplayRecorder.Reset(); - player.ReplayRecorder.Start(); // Start replay recording - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; - player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + player.ReplayRecorder.Reset(); + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); - player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); + player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); #endif - } + } - // Bonus end zones -- hook into (b)onus#_end - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success && player.Timer.IsBonusMode && player.Timer.IsRunning) - { - // To-do: verify the bonus trigger being hit! - int bonus_idx = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); + private void HandleBonusEndZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + // To-do: verify the bonus trigger being hit! + int bonus_idx = int.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); - player.Timer.Stop(); - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; - player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); + player.Timer.Stop(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); - 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 + 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 - bool saveBonusTime = false; - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + bool saveBonusTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR - { - saveBonusTime = true; - int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" - ); - } - else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map - { - saveBonusTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus - { - saveBonusTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus - { - saveBonusTime = true; - int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" - ); - } - else // Player did not beat their existing personal best for the bonus - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR + { + saveBonusTime = true; + int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" + ); + } + else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map + { + saveBonusTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus + { + saveBonusTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus + { + saveBonusTime = true; + int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); + } + else // Player did not beat their existing personal best for the bonus + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } - // To-do: save to DB - if (!player.Timer.IsPracticeMode) + if (!player.Timer.IsPracticeMode) + { + if (saveBonusTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => { - /* - AddTimer(1.5f, () => - { - API_CurrentRun bonus_time = new API_CurrentRun - { - player_id = player.Profile.ID, - map_id = player.CurrMap.ID, - style = pStyle, - type = 1, - stage = bonus_idx, - run_time = player.Stats.ThisRun.Ticks, - run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - replay_frames = player.ReplayRecorder.SerializeReplay() - }; - - Task.Run(async () => - { - await ApiMethod.POST("/surftimer/savebonustime", bonus_time); - player.Stats.LoadBonusTime(player); - await CurrentMap.Get_Map_Record_Runs(); // Reload the Map record and totals for the HUD - // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD - }); - }); - */ - if (saveBonusTime) - { - player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => - { - await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data - }); - } - } + await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data + }); } } - return HookResult.Continue; } /// diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 58bff1f..10861a1 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -204,7 +204,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma { case 1: // Bonus time #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB", nameof(PlayerStats), methodName); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif BonusPB[mapTime.Stage][style].ID = mapTime.ID; BonusPB[mapTime.Stage][style].Ticks = mapTime.RunTime; @@ -221,7 +221,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma case 2: // Stage time #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB", nameof(PlayerStats), methodName); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif StagePB[mapTime.Stage][style].ID = mapTime.ID; StagePB[mapTime.Stage][style].Ticks = mapTime.RunTime; @@ -238,7 +238,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma default: // Map time #if DEBUG - _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB", nameof(PlayerStats), methodName); + _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif PB[style].ID = mapTime.ID; PB[style].Ticks = mapTime.RunTime; diff --git a/src/ST-UTILS/Data/Models.cs b/src/ST-UTILS/Data/Models.cs index 6871138..34c7eda 100644 --- a/src/ST-UTILS/Data/Models.cs +++ b/src/ST-UTILS/Data/Models.cs @@ -218,6 +218,7 @@ public class PlayerMapTimeDataModel : RunStats /// public PlayerMapTimeDataModel(API_PersonalBest data) { + ID = data.id; RunTime = data.run_time; Type = data.type; Stage = data.stage; @@ -238,6 +239,18 @@ public PlayerMapTimeDataModel(API_PersonalBest data) /// public PlayerMapTimeDataModel(MySqlDataReader data) { + string replayFramesBase64; + + try + { + replayFramesBase64 = data.GetString("replay_frames"); + } + catch (InvalidCastException) + { + byte[] replayFramesData = data.GetFieldValue("replay_frames"); + replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); + } + ID = data.GetInt32("id"); RunTime = data.GetInt32("run_time"); Type = data.GetInt32("type"); @@ -251,6 +264,7 @@ public PlayerMapTimeDataModel(MySqlDataReader data) EndVelY = (float)data.GetDouble("end_vel_y"); EndVelZ = (float)data.GetDouble("end_vel_z"); RunDate = data.GetInt32("run_date"); + ReplayFramesBase64 = replayFramesBase64; } } From 90d97cc45bc49a36afe4b65a8cd88843aab02490 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Thu, 10 Jul 2025 22:39:27 +0300 Subject: [PATCH 28/52] dev: ZoneEventHandlers.cs, CS# version bump, disable some logs --- src/ST-Events/Players.cs | 6 +- src/ST-Events/TriggerEndTouch.cs | 184 ++------ src/ST-Events/TriggerStartTouch.cs | 460 +------------------ src/ST-Events/ZoneEventHandlers.cs | 587 +++++++++++++++++++++++++ src/ST-Player/Replay/ReplayRecorder.cs | 64 +-- src/SurfTimer.csproj | 2 +- 6 files changed, 674 insertions(+), 629 deletions(-) create mode 100644 src/ST-Events/ZoneEventHandlers.cs diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 7247580..03a4f27 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -12,7 +12,7 @@ public partial class SurfTimer public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) { var controller = @event.Userid; - if (!controller.IsValid || !controller.IsBot || CurrentMap.ReplayManager.IsControllerConnectedToReplayPlayer(controller)) + if (!controller!.IsValid || !controller.IsBot || CurrentMap.ReplayManager.IsControllerConnectedToReplayPlayer(controller)) return HookResult.Continue; _logger.LogTrace("OnPlayerSpawn -> Player {Name} spawned.", @@ -93,8 +93,8 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn { var player = @event.Userid; - string name = player.PlayerName; - string country = "XX"; + string name = player!.PlayerName; + string country; // GeoIP // Check if the IP is private before attempting GeoIP lookup diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 73da580..5dfd90a 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -1,6 +1,5 @@ -using System.Text.RegularExpressions; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; namespace SurfTimer; @@ -24,7 +23,9 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti } catch (System.Exception) { - Console.WriteLine($"===================== [ERROR] OnTriggerEndTouch -> Could not assign `client` (name: {name})"); + _logger.LogError("[{ClassName}] OnTriggerEndTouch -> Could not assign `client` (name: {Name})", + nameof(SurfTimer), name + ); } if (client == null || !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 @@ -41,153 +42,40 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti 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 - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + ZoneType currentZone = GetZoneType(trigger.Entity.Name); - // Map start zones -- hook into map_start, (s)tage1_start - if (trigger.Entity.Name.Contains("map_start") || - trigger.Entity.Name.Contains("s1_start") || - trigger.Entity.Name.Contains("stage1_start")) + switch (currentZone) { - // MAP START ZONE - if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) - { - player.Timer.Start(); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; - player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - // player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - } - - // Prespeed display - player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0} u/s"); - player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Map run - player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Map run - player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Map run - -#if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); -#endif - } - - // Map end zones -- hook into map_end - else if (trigger.Entity.Name == "map_end") - { - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_EXIT; - } - - // Stage start zones -- hook into (s)tage#_start - else if (Regex.Match(trigger.Entity.Name, "^s([1-9][0-9]?|tage[1-9][0-9]?)_start$").Success) - { -#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.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.Count}"); -#endif - - int stage = Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value); - - // Set replay situation - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; - player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; - // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); - - // Start the Stage timer - if (player.Timer.IsStageMode && player.Timer.Stage == stage) - { - player.Timer.Start(); - // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); - - // Show Prespeed for Stages - will be enabled/disabled by the user? - player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.velMag().ToString("0")} u/s"); - } - else if (player.Timer.IsRunning) - { -#if DEBUG - Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); - Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); - Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); - Console.WriteLine($"currentCheckpoint.Attempts {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts}"); -#endif - - // Update the Checkpoint object values - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; - - // Show Prespeed for Checkpoints - will be enabled/disabled by the user? - player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.velMag():0} u/s"); - } - } - - // 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.Checkpoints.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.Checkpoints.Count) - { -#if DEBUG - Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); - Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); - Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); -#endif - - if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) - { - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_EXIT; - player.ReplayRecorder.CheckpointExitSituations.Add(player.Timer.Ticks); - } - - // Update the Checkpoint object values - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; - - // 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.velMag():0} u/s"); - } - } - - // Bonus start zones -- hook into (b)onus#_start - else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_start$").Success) - { -#if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Bonus {Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value} Start Zone"); -#endif - - // 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))); // Todo make a plugin convar for the time saved before start of run - } - - // BONUS START ZONE - if (!player.Timer.IsStageMode && player.Timer.IsBonusMode) - { - player.Timer.Start(); - // Set the CurrentRunData values - player.Stats.ThisRun.Ticks = player.Timer.Ticks; - - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; - player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); - } - - // Prespeed display - player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0)} u/s"); - player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Bonus run - player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Bonus run - player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Bonus run + // Map end zones -- hook into map_end + case ZoneType.MapEnd: + EndTouchHandleMapEndZone(player); + break; + // Map start zones -- hook into map_start, (s)tage1_start + case ZoneType.MapStart: + EndTouchHandleMapStartZone(player); + break; + // Stage start zones -- hook into (s)tage#_start + case ZoneType.StageStart: + EndTouchHandleStageStartZone(player, trigger); + break; + // Map checkpoint zones -- hook into map_(c)heck(p)oint# + case ZoneType.Checkpoint: + EndTouchHandleCheckpointZone(player, trigger); + break; + // Bonus start zones -- hook into (b)onus#_start + case ZoneType.BonusStart: + EndTouchHandleBonusStartZone(player, trigger); + break; + // Bonus end zones -- hook into (b)onus#_end + case ZoneType.BonusEnd: + EndTouchHandleBonusEndZone(player); + break; + + default: + _logger.LogError("[{ClassName}] OnTriggerStartTouch -> Unknown MapZone detected in OnTriggerStartTouch. Name: {ZoneName}", + nameof(SurfTimer), trigger.Entity.Name + ); + break; } } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 981b45d..9bae755 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -1,54 +1,17 @@ -using System.Text.RegularExpressions; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API; using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; namespace SurfTimer; public partial class SurfTimer { - internal enum ZoneType - { - MapEnd, - MapStart, - StageStart, - Checkpoint, - BonusStart, - BonusEnd, - Unknown - } - - /// - /// Determines the zone type based on the entity name. - /// - /// Name of the entity. - /// ZoneType data - private ZoneType GetZoneType(string entityName) - { - if (entityName == "map_end") - return ZoneType.MapEnd; - else if (entityName.Contains("map_start") || entityName.Contains("s1_start") || entityName.Contains("stage1_start")) - return ZoneType.MapStart; - else if (Regex.IsMatch(entityName, @"^s([1-9][0-9]?|tage[1-9][0-9]?)_start$")) - return ZoneType.StageStart; - else if (Regex.IsMatch(entityName, @"^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$")) - return ZoneType.Checkpoint; - else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_start$")) - return ZoneType.BonusStart; - else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_end$")) - return ZoneType.BonusEnd; - - return ZoneType.Unknown; - } - /// /// Handler for trigger start touch hook - CBaseTrigger_StartTouchFunc /// /// CounterStrikeSharp.API.Core.HookResult /// - internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay, [CallerMemberName] string methodName = "") + internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { CBaseTrigger trigger = new CBaseTrigger(caller.Handle); CBaseEntity entity = new CBaseEntity(activator.Handle); @@ -60,11 +23,11 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn // 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)) { - _logger.LogCritical("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {PlayerName} ({UserId})", - nameof(SurfTimer), methodName, client.PlayerName, client.UserId + _logger.LogCritical("[{ClassName}] OnTriggerStartTouch -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {PlayerName} ({UserId})", + nameof(SurfTimer), client.PlayerName, client.UserId ); - Exception exception = new($"[{nameof(SurfTimer)}] {methodName} -> OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); + Exception exception = new($"[{nameof(SurfTimer)}] OnTriggerStartTouch -> Init -> Player playerList does NOT contain client.UserId, this shouldn't happen. Player: {client.PlayerName} ({client.UserId})"); throw exception; } // Implement Trigger Start Touch Here @@ -76,11 +39,11 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn if (DB == null) { - _logger.LogCritical("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> DB object is null, this shouldn't happen.", - nameof(SurfTimer), methodName + _logger.LogCritical("[{ClassName}] OnTriggerStartTouch -> DB object is null, this shouldn't happen.", + nameof(SurfTimer) ); - Exception exception = new Exception($"[{nameof(SurfTimer)}] {methodName} -> OnTriggerStartTouch -> DB object is null, this shouldn't happen."); + Exception exception = new Exception($"[{nameof(SurfTimer)}] OnTriggerStartTouch -> DB object is null, this shouldn't happen."); throw exception; } @@ -92,32 +55,32 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn { // Map end zones -- hook into map_end case ZoneType.MapEnd: - HandleMapEndZone(player); + StartTouchHandleMapEndZone(player); break; // Map start zones -- hook into map_start, (s)tage1_start case ZoneType.MapStart: - HandleMapStartZone(player, trigger); + StartTouchHandleMapStartZone(player, trigger); break; // Stage start zones -- hook into (s)tage#_start case ZoneType.StageStart: - HandleStageStartZone(player, trigger); + StartTouchHandleStageStartZone(player, trigger); break; // Map checkpoint zones -- hook into map_(c)heck(p)oint# case ZoneType.Checkpoint: - HandleCheckpointZone(player, trigger); + StartTouchHandleCheckpointZone(player, trigger); break; // Bonus start zones -- hook into (b)onus#_start case ZoneType.BonusStart: - HandleBonusStartZone(player, trigger); + StartTouchHandleBonusStartZone(player, trigger); break; // Bonus end zones -- hook into (b)onus#_end case ZoneType.BonusEnd: - HandleBonusEndZone(player, trigger); + StartTouchHandleBonusEndZone(player, trigger); break; default: - _logger.LogError("[{ClassName}] {MethodName} -> OnTriggerStartTouch -> Unknown MapZone detected in OnTriggerStartTouch. Name: {ZoneName}", - nameof(SurfTimer), methodName, trigger.Entity.Name + _logger.LogError("[{ClassName}] OnTriggerStartTouch -> Unknown MapZone detected in OnTriggerStartTouch. Name: {ZoneName}", + nameof(SurfTimer), trigger.Entity.Name ); break; } @@ -125,399 +88,6 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn return HookResult.Continue; } - private void HandleMapEndZone(Player player, [CallerMemberName] string methodName = "") - { - // Get velocities for DB queries - // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - - player.Controller.PrintToCenter($"Map End"); - - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; - player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); - - player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run - player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run - player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run - player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run - - - // MAP END ZONE - Map RUN - if (player.Timer.IsRunning && !player.Timer.IsStageMode) - { - player.Timer.Stop(); - bool saveMapTime = false; - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR - { - saveMapTime = true; - int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" - ); - } - else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map - { - saveMapTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map - { - saveMapTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map - { - saveMapTime = true; - int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" - ); - } - else // Player did not beat their existing PersonalBest for the map nor the map record - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - - if (saveMapTime) - { - player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => - { - await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data - }); - } - - // Add entry in DB for the run - if (!player.Timer.IsPracticeMode) - { - // Should we also save a last stage run? - if (CurrentMap.Stages > 0) - { - AddTimer(0.1f, () => - { - // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object - int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); - }); - } - - // This section checks if the PB is better than WR - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) - { - AddTimer(2f, () => - { - Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); - CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again - CurrentMap.ReplayManager.MapWR.FormatBotName(); - }); - } - - } - } - // MAP END ZONE - Stage RUN - else if (player.Timer.IsStageMode) - { - player.Timer.Stop(); - - if (!player.Timer.IsPracticeMode) - { - AddTimer(0.1f, () => - { - // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object - int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); - }); - } - } - -#if DEBUG - player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); -#endif - } - - private static void HandleMapStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") - { - // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts - if (!player.ReplayRecorder.IsSaving) - { - player.ReplayRecorder.Reset(); // Start replay recording - player.ReplayRecorder.Start(); // Start replay recording - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; - - player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - player.Timer.Reset(); - player.Stats.ThisRun.Checkpoints.Clear(); - 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"); -#endif - } - else - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); - } - } - - private void HandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") - { - // Get velocities for DB queries - // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); - - if (!player.ReplayRecorder.IsRecording) - player.ReplayRecorder.Start(); - - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; - player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); - - bool failed_stage = false; - if (player.Timer.Stage == stage) - failed_stage = true; - - // Reset/Stop the Stage timer - // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) - if (player.Timer.IsStageMode) - { - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); - if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) - { - int stage_run_time = player.Timer.Ticks; - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); - } - player.Timer.Reset(); - player.Timer.IsStageMode = true; - // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); - } - - 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.Checkpoints.Count <= stage}"); -#endif - - // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) - { - // Save Stage MapTime during a Map run - if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) - { - int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); - } - - player.Timer.Checkpoint = stage - 1; // 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`: {stage} | player.Timer.Checkpoint: {stage - 1}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); -#endif - - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(); - - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) - { - Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, - player.Timer.Ticks, - velocity.X, - velocity.Y, - velocity.Z, - -1.0f, - -1.0f, - -1.0f, - 0, - 1); - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; - } - else - { - player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; - } - } - -#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"); -#endif - } - - private static void HandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") - { - // Get velocities for DB queries - // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - 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* - if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.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)}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); -#endif - - if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) - { - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; - player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); - } - - // Print checkpoint message - player.HUD.DisplayCheckpointMessages(); - - if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) - { - // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - Checkpoint cp2 = new Checkpoint(checkpoint, - player.Timer.Ticks, - velocity.X, - velocity.Y, - velocity.Z, - -1.0f, - -1.0f, - -1.0f, - 0, - 1); - player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; - } - else - { - player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; - } - } - -#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"); -#endif - } - - private static void HandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") - { - int bonus = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); - player.Timer.Bonus = bonus; - - player.Timer.Reset(); - player.Timer.IsBonusMode = true; - - - player.ReplayRecorder.Reset(); - player.ReplayRecorder.Start(); // Start replay recording - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; - player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); - - player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); - -#if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); - Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); -#endif - } - - private void HandleBonusEndZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") - { - // Get velocities for DB queries - // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - // To-do: verify the bonus trigger being hit! - int bonus_idx = int.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); - - player.Timer.Stop(); - player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; - player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); - - 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 - - bool saveBonusTime = false; - string PracticeString = ""; - if (player.Timer.IsPracticeMode) - PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - - if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR - { - saveBonusTime = true; - int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" - ); - } - else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map - { - saveBonusTime = true; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus - { - saveBonusTime = true; - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus - { - saveBonusTime = true; - int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; - Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" - ); - } - else // Player did not beat their existing personal best for the bonus - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" - ); - } - - if (!player.Timer.IsPracticeMode) - { - if (saveBonusTime) - { - player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => - { - await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data - }); - } - } - } - /// /// Deals with saving a Stage MapTime (Type 2) in the Database. /// Should deal with `IsStageMode` runs, Stages during Map Runs and also Last Stage. diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs new file mode 100644 index 0000000..4910c09 --- /dev/null +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -0,0 +1,587 @@ +using System.Text.RegularExpressions; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using CounterStrikeSharp.API; +using System.Runtime.CompilerServices; + +namespace SurfTimer; + +public partial class SurfTimer +{ + internal enum ZoneType + { + MapEnd, + MapStart, + StageStart, + Checkpoint, + BonusStart, + BonusEnd, + Unknown + } + + /// + /// Determines the zone type based on the entity name. + /// + /// Name of the entity. + /// ZoneType data + private static ZoneType GetZoneType(string entityName) + { + if (entityName == "map_end") + return ZoneType.MapEnd; + else if (entityName.Contains("map_start") || entityName.Contains("s1_start") || entityName.Contains("stage1_start")) + return ZoneType.MapStart; + else if (Regex.IsMatch(entityName, @"^s([1-9][0-9]?|tage[1-9][0-9]?)_start$")) + return ZoneType.StageStart; + else if (Regex.IsMatch(entityName, @"^map_c(p[1-9][0-9]?|heckpoint[1-9][0-9]?)$")) + return ZoneType.Checkpoint; + else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_start$")) + return ZoneType.BonusStart; + else if (Regex.IsMatch(entityName, @"^b([1-9][0-9]?|onus[1-9][0-9]?)_end$")) + return ZoneType.BonusEnd; + + return ZoneType.Unknown; + } + + /* StartTouch */ + private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + + player.Controller.PrintToCenter($"Map End"); + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); + + player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run + player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run + player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run + player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run + + + // MAP END ZONE - Map RUN + if (player.Timer.IsRunning && !player.Timer.IsStageMode) + { + player.Timer.Stop(); + bool saveMapTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR + { + saveMapTime = true; + int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" + ); + } + else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map + { + saveMapTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map + { + saveMapTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map + { + saveMapTime = true; + int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); + } + else // Player did not beat their existing PersonalBest for the map nor the map record + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", + PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + + if (saveMapTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data + }); + } + + // Add entry in DB for the run + if (!player.Timer.IsPracticeMode) + { + // Should we also save a last stage run? + if (CurrentMap.Stages > 0) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } + + // This section checks if the PB is better than WR + if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + { + AddTimer(2f, () => + { + Console.WriteLine("CS2 Surf DEBUG >> OnTriggerStartTouch (Map end zone) -> WR/PB"); + CurrentMap.ReplayManager.MapWR.Start(); // Start the replay again + CurrentMap.ReplayManager.MapWR.FormatBotName(); + }); + } + + } + } + // MAP END ZONE - Stage RUN + else if (player.Timer.IsStageMode) + { + player.Timer.Stop(); + + if (!player.Timer.IsPracticeMode) + { + AddTimer(0.1f, () => + { + // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object + int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); + + // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); + SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + }); + } + } + +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.Lime}StartTouchFunc{ChatColors.Default} -> {ChatColors.Red}Map Stop Zone"); +#endif + } + + private static void StartTouchHandleMapStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // We shouldn't start timer and reset data until MapTime has been saved - mostly concerns the Replays and trimming the correct parts + if (!player.ReplayRecorder.IsSaving) + { + player.ReplayRecorder.Reset(); // Start replay recording + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + player.Timer.Reset(); + player.Stats.ThisRun.Checkpoints.Clear(); + 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"); +#endif + } + else + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["reset_delay"]}"); + } + } + + private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + + if (!player.ReplayRecorder.IsRecording) + player.ReplayRecorder.Start(); + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; + player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); + + bool failed_stage = false; + if (player.Timer.Stage == stage) + failed_stage = true; + + // Reset/Stop the Stage timer + // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) + if (player.Timer.IsStageMode) + { + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); + // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks; + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } + player.Timer.Reset(); + player.Timer.IsStageMode = true; + // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); + } + + 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.Checkpoints.Count <= stage}"); +#endif + + // This should patch up re-triggering *player.Stats.ThisRun.Checkpoint.Count < stage* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < stage) + { + // Save Stage MapTime during a Map run + if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) + { + int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + SaveStageTime(player, stage - 1, stage_run_time); + } + + player.Timer.Checkpoint = stage - 1; // 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`: {stage} | player.Timer.Checkpoint: {stage - 1}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Stage start zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif + + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) + { + Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, + player.Timer.Ticks, + velocity.X, + velocity.Y, + velocity.Z, + -1.0f, + -1.0f, + -1.0f, + 0, + 1); + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts++; + } + } + +#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"); +#endif + } + + private static void StartTouchHandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + 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* + if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.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)}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); +#endif + + if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_ENTER; + player.ReplayRecorder.CheckpointEnterSituations.Add(player.Timer.Ticks); + } + + // Print checkpoint message + player.HUD.DisplayCheckpointMessages(); + + if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) + { + // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality + Checkpoint cp2 = new Checkpoint(checkpoint, + player.Timer.Ticks, + velocity.X, + velocity.Y, + velocity.Z, + -1.0f, + -1.0f, + -1.0f, + 0, + 1); + player.Stats.ThisRun.Checkpoints[checkpoint] = cp2; + } + else + { + player.Stats.ThisRun.Checkpoints[checkpoint].Attempts++; + } + } + +#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"); +#endif + } + + private static void StartTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + int bonus = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + player.Timer.Bonus = bonus; + + player.Timer.Reset(); + player.Timer.IsBonusMode = true; + + + player.ReplayRecorder.Reset(); + player.ReplayRecorder.Start(); // Start replay recording + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + + player.Controller.PrintToCenter($"Bonus Start ({trigger.Entity.Name})"); + +#if DEBUG + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> player.Timer.IsRunning: {player.Timer.IsRunning}"); + Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Bonus start zones) -> !player.Timer.IsBonusMode: {!player.Timer.IsBonusMode}"); +#endif + } + + private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { + // Get velocities for DB queries + // Get the velocity of the player - we will be using this values to compare and write to DB + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int pStyle = player.Timer.Style; + // To-do: verify the bonus trigger being hit! + int bonus_idx = int.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + + player.Timer.Stop(); + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; + player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); + + 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 + + bool saveBonusTime = false; + string PracticeString = ""; + if (player.Timer.IsPracticeMode) + PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; + + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR + { + saveBonusTime = true; + int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" + ); + } + else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map + { + saveBonusTime = true; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus + { + saveBonusTime = true; + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus + { + saveBonusTime = true; + int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + ); + } + else // Player did not beat their existing personal best for the bonus + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", + bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + ); + } + + if (!player.Timer.IsPracticeMode) + { + if (saveBonusTime) + { + player.ReplayRecorder.IsSaving = true; + AddTimer(1.0f, async () => + { + await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data + }); + } + } + } + + + /* EndTouch */ + private void EndTouchHandleMapEndZone(Player player, [CallerMemberName] string methodName = "") + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_EXIT; + } + + private void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string methodName = "") + { + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + + // MAP START ZONE + if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) + { + player.Timer.Start(); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); + // player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + } + + // Prespeed display + player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0} u/s"); + player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Map run + player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Map run + +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Green}Map Start Zone"); +#endif + } + + private void EndTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { +#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.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.Count}"); +#endif + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + + // Set replay situation + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; + player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); + + // Start the Stage timer + if (player.Timer.IsStageMode && player.Timer.Stage == stage) + { + player.Timer.Start(); + // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); + + // Show Prespeed for Stages - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.velMag().ToString("0")} u/s"); + } + else if (player.Timer.IsRunning) + { +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); + Console.WriteLine($"currentCheckpoint.Attempts {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].Attempts}"); +#endif + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; + + // Show Prespeed for Checkpoints - will be enabled/disabled by the user? + player.Controller.PrintToCenter($"Checkpoint {player.Timer.Checkpoint} - Prespeed: {velocity.velMag():0} u/s"); + } + } + + private void EndTouchHandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { +#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.Checkpoints.Count}"); +#endif + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + + // 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.Checkpoints.Count) + { +#if DEBUG + Console.WriteLine($"currentCheckpoint.EndVelX {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX} - velocity.X {velocity.X}"); + Console.WriteLine($"currentCheckpoint.EndVelY {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY} - velocity.Y {velocity.Y}"); + Console.WriteLine($"currentCheckpoint.EndVelZ {player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ} - velocity.Z {velocity.Z}"); +#endif + + if (player.Timer.IsRunning && player.ReplayRecorder.IsRecording) + { + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.CHECKPOINT_ZONE_EXIT; + player.ReplayRecorder.CheckpointExitSituations.Add(player.Timer.Ticks); + } + + // Update the Checkpoint object values + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelX = velocity.X; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelY = velocity.Y; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndVelZ = velocity.Z; + player.Stats.ThisRun.Checkpoints[player.Timer.Checkpoint].EndTouch = player.Timer.Ticks; + + // 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.velMag():0} u/s"); + } + } + + private void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + { +#if DEBUG + player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Bonus {Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value} Start Zone"); +#endif + Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + + // 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))); // Todo make a plugin convar for the time saved before start of run + } + + // BONUS START ZONE + if (!player.Timer.IsStageMode && player.Timer.IsBonusMode) + { + player.Timer.Start(); + // Set the CurrentRunData values + player.Stats.ThisRun.Ticks = player.Timer.Ticks; + + player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; + player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); + Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); + } + + // Prespeed display + player.Controller.PrintToCenter($"Prespeed: {velocity.velMag():0)} u/s"); + player.Stats.ThisRun.StartVelX = velocity.X; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelY = velocity.Y; // Start pre speed for the Bonus run + player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Bonus run + } + + private void EndTouchHandleBonusEndZone(Player player, [CallerMemberName] string methodName = "") + { + + } +} \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 115f34a..39f996e 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -200,7 +200,7 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); new_frames = GetTrimmedFrames(startIndex, endIndex); - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", + _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); } else @@ -234,7 +234,7 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); new_frames = GetTrimmedFrames(startIndex, endIndex); - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count ); } @@ -258,35 +258,35 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (lastStage) { - _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); + _logger.LogDebug("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); + // _logger.LogDebug("stage_end_index = {stage_end_index}", + // stage_end_index + // ); stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); + // _logger.LogDebug("stage_exit_index = {stage_exit_index}", + // stage_exit_index + // ); stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); + // _logger.LogDebug("stage_enter_index = {stage_enter_index}", + // stage_enter_index + // ); } else if (player.Timer.Stage - 1 > 1) { - _logger.LogTrace("Stage replay trimming will use `STAGE_ZONE_X`"); + _logger.LogDebug("Stage replay trimming will use `STAGE_ZONE_X`"); stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); + // _logger.LogDebug("stage_end_index = {stage_end_index}", + // stage_end_index + // ); stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); + // _logger.LogDebug("stage_exit_index = {stage_exit_index}", + // stage_exit_index + // ); stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); + // _logger.LogDebug("stage_enter_index = {stage_enter_index}", + // stage_enter_index + // ); } else if (player.Timer.Stage - 1 == -1) // Don't crash...? { @@ -302,19 +302,19 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C } else { - _logger.LogInformation("Stage replay trimming will use `START_ZONE_X`"); + _logger.LogDebug("Stage replay trimming will use `START_ZONE_X`"); stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - _logger.LogTrace("stage_end_index = {stage_end_index}", - stage_end_index - ); + // _logger.LogDebug("stage_end_index = {stage_end_index}", + // stage_end_index + // ); stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - _logger.LogTrace("stage_exit_index = {stage_exit_index}", - stage_exit_index - ); + // _logger.LogDebug("stage_exit_index = {stage_exit_index}", + // stage_exit_index + // ); stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - _logger.LogTrace("stage_enter_index = {stage_enter_index}", - stage_enter_index - ); + // _logger.LogDebug("stage_enter_index = {stage_enter_index}", + // stage_enter_index + // ); } _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 35c78ef..84fa216 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -13,7 +13,7 @@ - + From 04234aacc417dd8bc4a9acbb0332361056f5df9c Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:09:34 +0300 Subject: [PATCH 29/52] feat(commands): amt, amn, amr, tier colors --- lang/en.json | 8 +- src/ST-Commands/MapCommands.cs | 156 +++++++++++++++++++- src/ST-Map/Map.cs | 6 +- src/ST-UTILS/Config.cs | 2 +- src/ST-UTILS/Data/MySqlDataAccessService.cs | 3 + src/ST-UTILS/Extensions.cs | 21 +++ src/SurfTimer.cs | 4 + 7 files changed, 190 insertions(+), 10 deletions(-) diff --git a/lang/en.json b/lang/en.json index feac428..966395e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,8 +1,14 @@ { - "prefix": "[{white}asd{green}asd{red}asd{default}]", + "prefix": "[{blue}Surf{bluegrey}Timer{default}]", "player_connected": "{green}{0}{default} has connected from {lime}{1}{default}.", + /* MapCommands */ + "map_info": "{yellow}{0}{default} | Tier - {1}{default} | Author - {yellow}{2}{default} | Ranked - {3}{default} | Added - {yellow}{4}{default}", + "map_info_linear": " | Linear - {yellow}{0} Checkpoints{default}", + "map_info_stages": " | Stages - {yellow}{0}{default}", + "map_info_bonuses": " | Bonuses - {yellow}{0}{default}", + /* PlayerCommands */ "reset_delay": "Please wait for your run to be saved before resetting.", "invalid_usage": "{red}Invalid arguments. {default}Usage: {green}{0}", diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 14714d7..39177b7 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -4,6 +4,8 @@ using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Admin; +using SurfTimer.Data; +using System.Text.RegularExpressions; namespace SurfTimer; @@ -13,28 +15,172 @@ public partial class SurfTimer [ConsoleCommand("css_tier", "Display the current map tier.")] [ConsoleCommand("css_mapinfo", "Display the current map tier.")] [ConsoleCommand("css_mi", "Display the current map tier.")] + [ConsoleCommand("css_difficulty", "Display the current map tier.")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - public void MapTier(CCSPlayerController? player, CommandInfo command) + public static void MapTier(CCSPlayerController? player, CommandInfo command) { if (player == null) return; - string msg = $"{Config.PluginPrefix} {CurrentMap.Name} - Tier {ChatColors.Green}{CurrentMap.Tier}{ChatColors.Default} - Author {ChatColors.Yellow}{CurrentMap.Author}{ChatColors.Default} - Added {ChatColors.Yellow}{DateTimeOffset.FromUnixTimeSeconds(CurrentMap.DateAdded).DateTime.ToString("dd.MM.yyyy HH:mm")}{ChatColors.Default}"; + char rankedColor = CurrentMap.Ranked ? ChatColors.Green : ChatColors.Red; + string rankedStatus = CurrentMap.Ranked ? "Yes" : "No"; + + string msg = $"{Config.PluginPrefix} " + LocalizationService.LocalizerNonNull["map_info", + CurrentMap.Name, + $"{Extensions.GetTierColor(CurrentMap.Tier)}{CurrentMap.Tier}", + CurrentMap.Author, + $"{rankedColor}{rankedStatus}", + DateTimeOffset.FromUnixTimeSeconds(CurrentMap.DateAdded).DateTime.ToString("dd.MM.yyyy HH:mm") + ]; if (CurrentMap.Stages > 1) { - msg = string.Concat(msg, " - ", $"Stages {ChatColors.Yellow}{CurrentMap.Stages}{ChatColors.Default}"); + msg += LocalizationService.LocalizerNonNull["map_info_stages", CurrentMap.Stages]; } else { - msg = string.Concat(msg, " - ", $"Linear {ChatColors.Yellow}{CurrentMap.TotalCheckpoints} Checkpoints{ChatColors.Default}"); + msg += LocalizationService.LocalizerNonNull["map_info_linear", CurrentMap.TotalCheckpoints]; } if (CurrentMap.Bonuses > 0) { - msg = string.Concat(msg, " - ", $"Bonuses {ChatColors.Yellow}{CurrentMap.Bonuses}"); + msg += LocalizationService.LocalizerNonNull["map_info_bonuses", CurrentMap.Bonuses]; + } + + player.PrintToChat(msg); + } + + [ConsoleCommand("css_amt", "Set the Tier of the map.")] + [ConsoleCommand("css_addmaptier", "Set the Tier of the map.")] + [RequiresPermissions("@css/root")] + [CommandHelper(minArgs: 1, usage: " [1-8]", whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void AddMapTier(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + int tier; + try + { + tier = int.Parse(command.ArgByIndex(1)); + } + catch (System.Exception) + { + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!amt [1-8]"]}" + ); + return; + } + + if (tier > 8) + { + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!amt [1-8]"]}" + ); + return; + } + + var mapInfo = new MapInfoDataModel + { + ID = CurrentMap.ID, + Name = CurrentMap.Name, + Author = CurrentMap.Author, + Tier = tier, + Stages = CurrentMap.Stages, + Bonuses = CurrentMap.Bonuses, + Ranked = CurrentMap.Ranked, + LastPlayed = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + + CurrentMap.Tier = tier; + + Task.Run(async () => + { + await _dataService!.UpdateMapInfoAsync(mapInfo); + }); + + string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Tier to {Extensions.GetTierColor(CurrentMap.Tier)}{CurrentMap.Tier}{ChatColors.Default}."; + + player.PrintToChat(msg); + } + + [ConsoleCommand("css_amn", "Set the Name of the map author.")] + [ConsoleCommand("css_addmappername", "Set the Name of the map author.")] + [RequiresPermissions("@css/root")] + [CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void AddMapAuthor(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + string author = command.ArgString.Trim(); + + // Validate: letters, numbers, intervals, dashes and up to 50 symbols + if (string.IsNullOrWhiteSpace(author) || author.Length > 50 || !Regex.IsMatch(author, @"^[\w\s\-\.]+$")) + { + player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["invalid_usage", + "!amn "]}" + ); + return; } + var mapInfo = new MapInfoDataModel + { + ID = CurrentMap.ID, + Name = CurrentMap.Name, + Author = author, + Tier = CurrentMap.Tier, + Stages = CurrentMap.Stages, + Bonuses = CurrentMap.Bonuses, + Ranked = CurrentMap.Ranked, + LastPlayed = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + + CurrentMap.Author = author; + + Task.Run(async () => + { + await _dataService!.UpdateMapInfoAsync(mapInfo); + }); + + string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Author to {ChatColors.Green}{CurrentMap.Author}{ChatColors.Default}."; + + player.PrintToChat(msg); + } + + [ConsoleCommand("css_amr", "Set the Ranked option of the map.")] + [ConsoleCommand("css_addmapranked", "Set the Ranked option of the map.")] + [RequiresPermissions("@css/root")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + public void AddMapRanked(CCSPlayerController? player, CommandInfo command) + { + if (player == null) + return; + + if (CurrentMap.Ranked) + CurrentMap.Ranked = false; + else + CurrentMap.Ranked = true; + + var mapInfo = new MapInfoDataModel + { + ID = CurrentMap.ID, + Name = CurrentMap.Name, + Author = CurrentMap.Author, + Tier = CurrentMap.Tier, + Stages = CurrentMap.Stages, + Bonuses = CurrentMap.Bonuses, + Ranked = CurrentMap.Ranked, + LastPlayed = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + + Task.Run(async () => + { + await _dataService!.UpdateMapInfoAsync(mapInfo); + }); + + string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Ranked to {(CurrentMap.Ranked ? ChatColors.Green : ChatColors.Red)}{CurrentMap.Ranked}{ChatColors.Default}."; + player.PrintToChat(msg); } diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 56728f8..cd38268 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -22,7 +22,7 @@ internal class Map public int Stages { get; set; } = 0; public int TotalCheckpoints { get; set; } = 0; public int Bonuses { get; set; } = 0; - public bool Ranked { get; set; } = false; + public bool Ranked { get; set; } = false; // Not decided on what to use this for public int DateAdded { get; set; } = 0; public int LastPlayed { get; set; } = 0; /// @@ -306,11 +306,11 @@ internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") { ID = this.ID, Name = this.Name, - Author = "Unknown", // adjust as necessary + Author = this.Author, Tier = this.Tier, Stages = this.Stages, Bonuses = this.Bonuses, - Ranked = false, + Ranked = this.Ranked, LastPlayed = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() }; diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 9f5824d..1d00302 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -142,7 +142,7 @@ public static class Queries // Map.cs related queries public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, bonuses, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {6}, {6})"; // "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()})" - public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2} WHERE id={3};"; + public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2}, author='{3}', tier={4}, ranked={5} WHERE id={6};"; public const string DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT = @" SELECT ranked_times.* diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index 15bb61c..7f54667 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -136,6 +136,9 @@ public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), mapInfo.Stages, mapInfo.Bonuses, + mapInfo.Author, + mapInfo.Tier, + mapInfo.Ranked ? 1 : 0, // Table is using TINYINT so we need to send 0 or 1 instead of Boolean mapInfo.ID ); diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index d3b4fa1..6195228 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -54,4 +54,25 @@ public static void SetCollisionGroup(this CCSPlayerController controller, Collis Utilities.SetStateChanged(controller, "CColisionProperity", "m_collisionGroup"); Utilities.SetStateChanged(controller, "CCollisionProperty", "m_collisionAttribute"); } + + /// + /// Asssigns a ChatColor to the given Tier value + /// + /// Map Tier up to 8 + /// Appropriate ChatColor value for the Tier + public static char GetTierColor(int tier) + { + return tier switch + { + 1 => ChatColors.Green, + 2 => ChatColors.Lime, + 3 => ChatColors.Yellow, + 4 => ChatColors.Orange, + 5 => ChatColors.LightRed, + 6 => ChatColors.DarkRed, + 7 => ChatColors.LightPurple, + 8 => ChatColors.Purple, + _ => ChatColors.White + }; + } } \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 6aa1d44..6ca8ba8 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -37,7 +37,9 @@ You should have received a copy of the GNU Affero General Public License using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes.Registration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Data; namespace SurfTimer; @@ -47,12 +49,14 @@ public partial class SurfTimer : BasePlugin { private readonly ILogger _logger; public static IServiceProvider ServiceProvider { get; private set; } = null!; + private readonly IDataAccessService? _dataService; // Inject ILogger and store IServiceProvider globally public SurfTimer(ILogger logger, IServiceProvider serviceProvider) { _logger = logger; ServiceProvider = serviceProvider; + _dataService = ServiceProvider.GetRequiredService(); } // Metadata From 63bdbd7282e4186aa01de02bf06ff6b3723bc698 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 28 Jul 2025 18:09:29 +0300 Subject: [PATCH 30/52] fix: invalid signature for MapTier command --- src/ST-Commands/MapCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 39177b7..0853cdb 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -17,7 +17,7 @@ public partial class SurfTimer [ConsoleCommand("css_mi", "Display the current map tier.")] [ConsoleCommand("css_difficulty", "Display the current map tier.")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - public static void MapTier(CCSPlayerController? player, CommandInfo command) + public void MapTier(CCSPlayerController? player, CommandInfo command) { if (player == null) return; From 598bbcdb936a6586d26fa6d7e898f7e3206bb405 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:27:09 +0300 Subject: [PATCH 31/52] feat: PingAccessService --- src/ST-UTILS/Config.cs | 4 +++ src/ST-UTILS/Data/ApiDataAccessService.cs | 30 +++++++++++++++++++++ src/ST-UTILS/Data/IDataAccessService.cs | 6 +++++ src/ST-UTILS/Data/MySqlDataAccessService.cs | 24 +++++++++++++++++ src/SurfTimer.cs | 26 +++++++++++++----- 5 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 1d00302..a9f2f47 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -79,6 +79,8 @@ public static bool GetApiOnly() /// public static class Endpoints { + public const string ENDPOINT_PING = "/ping?client_unix={0}"; + // Map.cs related endpoints public const string ENDPOINT_MAP_GET_INFO = "/surftimer/mapinfo?mapname={0}"; public const string ENDPOINT_MAP_INSERT_INFO = "/surftimer/insertmap"; @@ -139,6 +141,8 @@ public static string GetConnectionString() /// public static class Queries { + public const string DB_QUERY_PING = "SELECT 1;"; + // Map.cs related queries public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, bonuses, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {6}, {6})"; // "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()})" diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 68876f4..9599122 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -16,6 +16,36 @@ public ApiDataAccessService() _logger = SurfTimer.ServiceProvider.GetRequiredService>(); } + public async Task PingAccessService([CallerMemberName] string methodName = "") + { + try + { + var response = await ApiMethod.GET>( + string.Format(Config.API.Endpoints.ENDPOINT_PING, DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + ); + + if (response != null && response.ContainsKey("client_unix")) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> Success -> Client: {ClientUnix} | Server: {ServerUnix} | Latency: {LatencyS}s | Latency: {LatencyMS}ms", + nameof(ApiDataAccessService), methodName, response["client_unix"], response["server_unix"], response["latency_seconds"], response["latency_ms"] + ); + return true; + } + + _logger.LogWarning("[{ClassName}] {MethodName} -> Unexpected response structure.", + nameof(ApiDataAccessService), methodName + ); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "[{ClassName}] {MethodName} -> Failed to reach API.", + nameof(ApiDataAccessService), methodName + ); + return false; + } + } + /* PersonalBest.cs */ public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs index fdc5866..45c3e88 100644 --- a/src/ST-UTILS/Data/IDataAccessService.cs +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -7,6 +7,12 @@ namespace SurfTimer.Data /// public interface IDataAccessService { + /// + /// Ping the Data Access Service. + /// + /// True for successful connection, False otherwise + Task PingAccessService([CallerMemberName] string methodName = ""); + /* PersonalBest.cs */ /// /// Retrieve Checkpoints table entries for a given run ID (map time). diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index 7f54667..c226a7a 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -17,6 +17,30 @@ public MySqlDataAccessService() _logger = SurfTimer.ServiceProvider.GetRequiredService>(); } + public async Task PingAccessService([CallerMemberName] string methodName = "") + { + try + { + using var result = await SurfTimer.DB.QueryAsync(Config.MySQL.Queries.DB_QUERY_PING); + + if (result.HasRows) + { + _logger.LogInformation("[{ClassName}] {MethodName} -> PingAccessService -> MySQL is reachable", + nameof(MySqlDataAccessService), methodName + ); + return true; + } + } + catch (Exception ex) + { + _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> PingAccessService -> MySQL is unreachable", + nameof(MySqlDataAccessService), methodName + ); + } + + return false; + } + /* PersonalBest.cs */ public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 6ca8ba8..8433126 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -119,19 +119,31 @@ public override void Load(bool hotReload) { LocalizationService.Init(Localizer); - // Check if we have connected to the Database - if (DB != null) + bool accessService = false; + + try + { + accessService = Task.Run(() => _dataService!.PingAccessService()).GetAwaiter().GetResult(); + } + catch (Exception ex) { - _logger.LogInformation("[{Prefix}] Database connection established.", - Config.PluginName + _logger.LogError(ex, "[{Prefix}] PingAccessService threw an exception.", Config.PluginName); + } + + if (accessService) + { + _logger.LogInformation("[{Prefix}] {AccessService} connection established.", + Config.PluginName, Config.API.GetApiOnly() ? "API" : "DB" ); } else { - _logger.LogCritical("[{Prefix}] Error connecting to the database.", - Config.PluginName + _logger.LogCritical("[{Prefix}] Error connecting to the {AccessService}.", + Config.PluginName, Config.API.GetApiOnly() ? "API" : "DB" ); - // To-do: Abort plugin loading + + Exception exception = new Exception($"[{Config.PluginName}] Error connecting to the {(Config.API.GetApiOnly() ? "API" : "DB")}"); + throw exception; } _logger.LogInformation(""" From c16119cd8230189262ead47c59021fec07084ad6 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:33:55 +0300 Subject: [PATCH 32/52] dev: switch to .net api;cs# version bump;public classes;prop rename;separate replay type trimming;updatemaptime; --- src/ST-API/Api.cs | 2 +- src/ST-API/Comms.cs | 27 +- src/ST-API/Structures.cs | 13 +- src/ST-Commands/PlayerCommands.cs | 44 ++- src/ST-Events/Players.cs | 47 +-- src/ST-Events/TriggerStartTouch.cs | 63 ---- src/ST-Events/ZoneEventHandlers.cs | 67 ++-- src/ST-Map/Map.cs | 49 +-- src/ST-Player/Player.cs | 8 +- src/ST-Player/PlayerHUD.cs | 26 +- src/ST-Player/PlayerProfile.cs | 14 +- src/ST-Player/PlayerStats/Checkpoint.cs | 4 +- src/ST-Player/PlayerStats/CurrentRun.cs | 112 +++++- src/ST-Player/PlayerStats/PersonalBest.cs | 14 +- src/ST-Player/PlayerStats/PlayerStats.cs | 41 +-- src/ST-Player/PlayerStats/RunStats.cs | 9 +- src/ST-Player/PlayerTimer.cs | 2 +- src/ST-Player/Replay/ReplayFrame.cs | 9 +- src/ST-Player/Replay/ReplayManager.cs | 5 +- src/ST-Player/Replay/ReplayPlayer.cs | 20 +- src/ST-Player/Replay/ReplayRecorder.cs | 359 ++++++++++---------- src/ST-Player/Saveloc/SavelocFrame.cs | 4 +- src/ST-UTILS/Config.cs | 80 +---- src/ST-UTILS/Data/ApiDataAccessService.cs | 259 ++++++++------ src/ST-UTILS/Data/IDataAccessService.cs | 10 +- src/ST-UTILS/Data/Models.cs | 37 +- src/ST-UTILS/Data/MySqlDataAccessService.cs | 11 +- src/ST-UTILS/Structs/QAngle_t.cs | 2 +- src/ST-UTILS/Structs/Vector_t.cs | 2 +- src/SurfTimer.cs | 7 +- src/SurfTimer.csproj | 2 +- 31 files changed, 726 insertions(+), 623 deletions(-) diff --git a/src/ST-API/Api.cs b/src/ST-API/Api.cs index 78a9fbe..e399373 100644 --- a/src/ST-API/Api.cs +++ b/src/ST-API/Api.cs @@ -24,7 +24,7 @@ public static async Task Api_Save_Stage_MapTime(Player player) API_CurrentRun stage_time = new() { player_id = player.Profile.ID, - map_id = player.CurrMap.ID, + map_id = SurfTimer.CurrentMap.ID, style = player.Timer.Style, type = 2, stage = player.Timer.Stage - 1, diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index e3b5161..b22dd8d 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -31,8 +31,13 @@ private ApiMethod() { } try { - _logger.LogInformation("[{ClassName}] {MethodName} -> GET {URL} => {StatusCode}", - nameof(ApiMethod), methodName, url, response.StatusCode + var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && + !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; + + _logger.LogInformation("[{ClassName}] {MethodName} -> GET {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", + nameof(ApiMethod), methodName, url, response.StatusCode, responseTime ); if (response.StatusCode == System.Net.HttpStatusCode.NoContent) @@ -74,9 +79,14 @@ private ApiMethod() { } { using var response = await _client.PostAsJsonAsync(uri, body); + var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && + !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; + _logger.LogInformation( - "[{ClassName}] {MethodName} -> POST {URL} => {StatusCode}", - nameof(ApiMethod), methodName, url, response.StatusCode + "[{ClassName}] {MethodName} -> POST {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", + nameof(ApiMethod), methodName, url, response.StatusCode, responseTime ); if (response.IsSuccessStatusCode) @@ -124,9 +134,14 @@ private ApiMethod() { } { using var response = await _client.PutAsJsonAsync(uri, body); + var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && + !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; + _logger.LogInformation( - "[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode}", - nameof(ApiMethod), methodName, url, response.StatusCode + "[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", + nameof(ApiMethod), methodName, url, response.StatusCode, responseTime ); if (response.IsSuccessStatusCode) diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs index 94f7a48..9901a66 100644 --- a/src/ST-API/Structures.cs +++ b/src/ST-API/Structures.cs @@ -6,10 +6,9 @@ namespace SurfTimer; // Map Info structure public class API_PostResponseData { - public int inserted { get; set; } - public float xtime { get; set; } - public int last_id { get; set; } - public List? trx { get; set; } + public int Id { get; set; } + public int Inserted { get; set; } + public bool Trx { get; set; } } public class API_Checkpoint @@ -183,7 +182,7 @@ internal API_SaveMapTime(MapTimeDataModel data) { player_id = data.PlayerId; map_id = data.MapId; - run_time = data.Ticks; + run_time = data.RunTime; start_vel_x = data.StartVelX; start_vel_y = data.StartVelY; start_vel_z = data.StartVelZ; @@ -193,14 +192,14 @@ internal API_SaveMapTime(MapTimeDataModel data) style = data.Style; type = data.Type; stage = data.Stage; - replay_frames = data.ReplayFramesBase64; + replay_frames = data.ReplayFrames; run_date = data.RunDate; // Convert Checkpoints checkpoints = data.Checkpoints.Select(cp => new API_Checkpoint { cp = cp.Key, - run_time = cp.Value.Ticks, + run_time = cp.Value.RunTime, end_touch = cp.Value.EndTouch, start_vel_x = cp.Value.StartVelX, start_vel_y = cp.Value.StartVelY, diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 00757f7..6628823 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -3,6 +3,7 @@ using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Utils; +using CounterStrikeSharp.API.Modules.Admin; namespace SurfTimer; @@ -249,7 +250,7 @@ public void PlayerRank(CCSPlayerController? player, CommandInfo command) return; int pRank = playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank; - int tRank = playerList[player.UserId ?? 0].CurrMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]; + int tRank = CurrentMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]; player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["rank", CurrentMap.Name, pRank, tRank]}" ); @@ -539,6 +540,7 @@ TEST CMDS */ [ConsoleCommand("css_rx", "x")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/root")] public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -552,6 +554,7 @@ public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) [ConsoleCommand("css_setpb", "xxxxx")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/root")] public async void TestSetPb(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -611,6 +614,7 @@ public async void TestSetPb(CCSPlayerController? player, CommandInfo command) [ConsoleCommand("css_testx", "x")] [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] + [RequiresPermissions("@css/root")] public void TestCmd(CCSPlayerController? player, CommandInfo command) { if (player == null) @@ -623,14 +627,14 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); - player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].RunTime)}"); player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); - player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].RunTime)}"); // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); @@ -640,7 +644,7 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); - player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].Ticks}"); + player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].RunTime}"); player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); @@ -743,5 +747,37 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) // } } + [ConsoleCommand("css_ctest", "x")] + [CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) + { + Console.WriteLine($"Map ID: {CurrentMap.ID}"); + Console.WriteLine($"Map Name: {CurrentMap.Name}"); + Console.WriteLine($"Map Author: {CurrentMap.Author}"); + Console.WriteLine($"Map Tier: {CurrentMap.Tier}"); + Console.WriteLine($"Map Stages: {CurrentMap.Stages}"); + Console.WriteLine($"Map Bonuses: {CurrentMap.Bonuses}"); + Console.WriteLine($"Map Completions: {CurrentMap.MapCompletions[0]}"); + + Console.WriteLine($"Map WR ID: {CurrentMap.WR[0].ID}"); + Console.WriteLine($"Map WR Name: {CurrentMap.WR[0].Name}"); + Console.WriteLine($"Map WR Type: {CurrentMap.WR[0].Type}"); + Console.WriteLine($"Map WR Rank: {CurrentMap.WR[0].Rank}"); + Console.WriteLine($"Map WR Checkpoints.Count: {CurrentMap.WR[0].Checkpoints.Count}"); + Console.WriteLine($"Map WR ReplayFramesBase64.Length: {CurrentMap.WR[0].ReplayFrames.Length}"); + Console.WriteLine($"Map WR ReplayFrames.Length: {CurrentMap.WR[0].ReplayFrames.Length}"); + + Console.WriteLine($"Map Stage Completions: {CurrentMap.StageCompletions.Length}"); + Console.WriteLine($"Map StageWR ID: {CurrentMap.StageWR[1][0].ID}"); + Console.WriteLine($"Map StageWR Name: {CurrentMap.StageWR[1][0].Name}"); + Console.WriteLine($"Map StageWR Type: {CurrentMap.StageWR[1][0].Type}"); + Console.WriteLine($"Map StageWR Rank: {CurrentMap.StageWR[1][0].Rank}"); + Console.WriteLine($"Map StageWR ReplayFramesBase64.Length: {CurrentMap.StageWR[1][0].ReplayFrames.Length}"); + Console.WriteLine($"Map StageWR ReplayFrames.Length: {CurrentMap.StageWR[1][0].ReplayFrames.Length}"); + + Console.WriteLine($"Map Bonus Completions: {CurrentMap.BonusCompletions.Length}"); + } + } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 03a4f27..8ea19a9 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -109,9 +109,7 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn { country = "LL"; // Handle local IP appropriately } - // #if DEBUG - // Console.WriteLine($"CS2 Surf DEBUG >> OnPlayerConnectFull -> GeoIP -> {name} -> {player.IpAddress!.Split(":")[0]} -> {country}"); - // #endif + if (DB == null) { _logger.LogCritical("OnPlayerConnect -> DB object is null, this shouldn't happen."); @@ -119,22 +117,27 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn throw ex; } - // Create Player object and add to playerList - PlayerProfile Profile = PlayerProfile.CreateAsync(player.SteamID, name, country).GetAwaiter().GetResult(); - playerList[player.UserId ?? 0] = new Player(player, - new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle), - Profile, CurrentMap); + var profile = PlayerProfile.CreateAsync(player.SteamID, name, country).GetAwaiter().GetResult(); + var movement = new CCSPlayer_MovementServices(player.PlayerPawn.Value!.MovementServices!.Handle); - // Load MapTimes for the player's PB and their Checkpoints - playerList[player.UserId ?? 0].Stats.LoadPlayerMapTimesData(playerList[player.UserId ?? 0]).GetAwaiter().GetResult(); // Holds here until result is available + var p = new Player(player, movement, profile); - // Print join messages - Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["player_connected", - name, country]}" - ); - _logger.LogTrace("[{Prefix}] {PlayerName} has connected from {Country}.", - Config.PluginName, name, playerList[player.UserId ?? 0].Profile.Country - ); + // No lock - we use thread-safe method AddOrUpdate + playerList.AddOrUpdate(player.UserId ?? 0, p, (_, _) => p); + + _ = p.Stats.LoadPlayerMapTimesData(p); + + // Go back to the Main Thread for chat message + Server.NextFrame(() => + { + // Print join messages + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["player_connected", + name, country]}" + ); + _logger.LogTrace("[{Prefix}] {PlayerName} has connected from {Country}.", + Config.PluginName, name, playerList[player.UserId ?? 0].Profile.Country + ); + }); return HookResult.Continue; } @@ -185,11 +188,13 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo } else { - // Update data in Player DB table - playerList[player.UserId ?? 0].Profile.UpdatePlayerProfile(player.PlayerName).GetAwaiter().GetResult(); // Hold the thread until player data is updated + int userId = player.UserId ?? 0; - // Remove player data from playerList - playerList.Remove(player.UserId ?? 0); + if (playerList.TryGetValue(userId, out var playerData)) + { + _ = playerData.Profile.UpdatePlayerProfile(player.PlayerName); + playerList.TryRemove(userId, out _); + } } return HookResult.Continue; } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 9bae755..4fd781c 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -87,68 +87,5 @@ internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEn } return HookResult.Continue; } - - /// - /// Deals with saving a Stage MapTime (Type 2) in the Database. - /// Should deal with `IsStageMode` runs, Stages during Map Runs and also Last Stage. - /// - /// Player object - /// Stage to save - /// Is it the last stage? - /// Run Time (Ticks) for the stage run - void SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool saveLastStage = false) - { - // player.Controller.PrintToChat($"{Config.PluginPrefix} SaveStageTime received: {player.Profile.Name}, {stage}, {stage_run_time}, {saveLastStage}"); - int pStyle = player.Timer.Style; - if ( - stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks || - CurrentMap.StageWR[stage][pStyle].ID == -1 || - player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time || - player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1 - ) - { - if (stage_run_time < CurrentMap.StageWR[stage][pStyle].Ticks) // Player beat the Stage WR - { - int timeImprove = CurrentMap.StageWR[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_improved", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)]}" - ); - } - else if (CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map - { - Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_set", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time)]}" - ); - } - else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best - { - player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_set", - stage, PlayerHUD.FormatTime(stage_run_time)]}" - ); - } - else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].Ticks > stage_run_time) // Player beating their existing Stage personal best - { - int timeImprove = player.Stats.StagePB[stage][pStyle].Ticks - stage_run_time; - Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_improved", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].Ticks)]}" - ); - } - - player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => - { - // Save stage run - Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); - await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data - }); - } - else if (stage_run_time > CurrentMap.StageWR[stage][pStyle].Ticks && player.Timer.IsStageMode) // Player is behind the Stage WR for the map - { - int timeImprove = stage_run_time - CurrentMap.StageWR[stage][pStyle].Ticks; - player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_missed", - stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.StageWR[stage][pStyle].Ticks)]}" - ); - } - } } diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index 4910c09..d78a89c 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -55,7 +55,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; player.ReplayRecorder.MapSituations.Add(player.Timer.Ticks); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the Map run + player.Stats.ThisRun.RunTime = player.Timer.Ticks; // End time for the Map run player.Stats.ThisRun.EndVelX = velocity.X; // End speed for the Map run player.Stats.ThisRun.EndVelY = velocity.Y; // End speed for the Map run player.Stats.ThisRun.EndVelZ = velocity.Z; // End speed for the Map run @@ -70,12 +70,12 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string if (player.Timer.IsPracticeMode) PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks) // Player beat the Map WR + if (player.Timer.Ticks < CurrentMap.WR[pStyle].RunTime) // Player beat the Map WR { saveMapTime = true; - int timeImprove = CurrentMap.WR[pStyle].Ticks - player.Timer.Ticks; + int timeImprove = CurrentMap.WR[pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].Ticks)]}" + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].RunTime)]}" ); } else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map @@ -85,19 +85,19 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" ); } - else if (player.Stats.PB[pStyle].Ticks <= 0) // Player first ever PersonalBest for the map + else if (player.Stats.PB[pStyle].RunTime <= 0) // Player first ever PersonalBest for the map { saveMapTime = true; player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", PlayerHUD.FormatTime(player.Timer.Ticks)]}" ); } - else if (player.Timer.Ticks < player.Stats.PB[pStyle].Ticks) // Player beating their existing PersonalBest for the map + else if (player.Timer.Ticks < player.Stats.PB[pStyle].RunTime) // Player beating their existing PersonalBest for the map { saveMapTime = true; - int timeImprove = player.Stats.PB[pStyle].Ticks - player.Timer.Ticks; + int timeImprove = player.Stats.PB[pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].RunTime)]}" ); } else // Player did not beat their existing PersonalBest for the map nor the map record @@ -110,7 +110,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string if (saveMapTime) { player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { await player.Stats.ThisRun.SaveMapTime(player); // Save the MapTime PB data }); @@ -122,18 +122,18 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string // Should we also save a last stage run? if (CurrentMap.Stages > 0) { - AddTimer(0.1f, () => + AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone { // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + await player.Stats.ThisRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); }); } // This section checks if the PB is better than WR - if (player.Timer.Ticks < CurrentMap.WR[pStyle].Ticks || CurrentMap.WR[pStyle].ID == -1) + if (player.Timer.Ticks < CurrentMap.WR[pStyle].RunTime || CurrentMap.WR[pStyle].ID == -1) { AddTimer(2f, () => { @@ -152,13 +152,13 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string if (!player.Timer.IsPracticeMode) { - AddTimer(0.1f, () => + AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone { // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + await player.Stats.ThisRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); }); } } @@ -225,8 +225,11 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { int stage_run_time = player.Timer.Ticks; + AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + { + await player.Stats.ThisRun.SaveStageTime(player, stage - 1, stage_run_time); + }); // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); } player.Timer.Reset(); player.Timer.IsStageMode = true; @@ -247,9 +250,13 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, // Save Stage MapTime during a Map run if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { - int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.Ticks; // player.Stats.ThisRun.Ticks should be the Tick we left the previous Stage zone - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - SaveStageTime(player, stage - 1, stage_run_time); + int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.RunTime; // player.Stats.ThisRun.RunTime should be the Tick we left the previous Stage zone + // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + { + await player.Stats.ThisRun.SaveStageTime(player, stage - 1, stage_run_time); + }); + } player.Timer.Checkpoint = stage - 1; // Stage = Checkpoint when in a run on a Staged map @@ -376,7 +383,7 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; player.ReplayRecorder.BonusSituations.Add(player.Timer.Ticks); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; // End time for the run + player.Stats.ThisRun.RunTime = 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 @@ -386,12 +393,12 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ if (player.Timer.IsPracticeMode) PracticeString = $"({ChatColors.Grey}Practice{ChatColors.Default}) "; - if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].Ticks) // Player beat the Bonus WR + if (player.Timer.Ticks < CurrentMap.BonusWR[bonus_idx][pStyle].RunTime) // Player beat the Bonus WR { saveBonusTime = true; - int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].Ticks)]}" + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].RunTime)]}" ); } else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map @@ -401,19 +408,19 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" ); } - else if (player.Stats.BonusPB[bonus_idx][pStyle].Ticks <= 0) // Player first ever PersonalBest for the bonus + else if (player.Stats.BonusPB[bonus_idx][pStyle].RunTime <= 0) // Player first ever PersonalBest for the bonus { saveBonusTime = true; player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" ); } - else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].Ticks) // Player beating their existing PersonalBest for the bonus + else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].RunTime) // Player beating their existing PersonalBest for the bonus { saveBonusTime = true; - int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].Ticks - player.Timer.Ticks; + int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].Ticks)]}" + player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].RunTime)]}" ); } else // Player did not beat their existing personal best for the bonus @@ -428,7 +435,7 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ if (saveBonusTime) { player.ReplayRecorder.IsSaving = true; - AddTimer(1.0f, async () => + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { await player.Stats.ThisRun.SaveMapTime(player, bonus: bonus_idx); // Save the Bonus MapTime data }); @@ -451,7 +458,7 @@ private void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) { player.Timer.Start(); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.Stats.ThisRun.RunTime = player.Timer.Ticks; player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); // player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); @@ -481,7 +488,7 @@ private void EndTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [ // Set replay situation player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); - player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.Stats.ThisRun.RunTime = player.Timer.Ticks; // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); // Start the Stage timer @@ -566,7 +573,7 @@ private void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [ { player.Timer.Start(); // Set the CurrentRunData values - player.Stats.ThisRun.Ticks = player.Timer.Ticks; + player.Stats.ThisRun.RunTime = player.Timer.Ticks; player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index cd38268..4486ac6 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -12,7 +12,7 @@ namespace SurfTimer; -internal class Map +public 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) @@ -109,14 +109,14 @@ internal Map(string name) } } - public static async Task CreateAsync(string name) + internal static async Task CreateAsync(string name) { var map = new Map(name); await map.InitializeAsync(); return map; } - private async Task InitializeAsync([CallerMemberName] string methodName = "") + internal async Task InitializeAsync([CallerMemberName] string methodName = "") { // Load zones MapLoadZones(); @@ -258,7 +258,7 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") if (this.Stages > 0) // Account for stage 1, not counted above this.Stages += 1; - _logger.LogTrace("[{ClassName}] {MethodName} -> Start zone: {StartZoneX},{StartZoneY},{StartZoneZ} | End zone: {EndZoneX},{EndZoneY},{EndZoneZ}", + _logger.LogTrace("[{ClassName}] {MethodName} -> Start zone: {StartZoneX}, {StartZoneY}, {StartZoneZ} | End zone: {EndZoneX}, {EndZoneY}, {EndZoneZ}", nameof(Map), methodName, this.StartZone.X, this.StartZone.Y, this.StartZone.Z, this.EndZone.X, this.EndZone.Y, this.EndZone.Z ); @@ -387,18 +387,19 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { this.ConnectedMapTimes.Clear(); - // Replay Stuff - JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; - var runs = await _dataService.GetMapRecordRunsAsync(this.ID); + _logger.LogInformation("[{ClassName}] {MethodName} -> Received {Length} of runs from `GetMapRecordRunsAsync`", + nameof(Map), methodName, runs.Count + ); + foreach (var run in runs) { switch (run.Type) { case 0: // Map WR data and total completions WR[run.Style].ID = run.ID; - WR[run.Style].Ticks = run.RunTime; + WR[run.Style].RunTime = run.RunTime; WR[run.Style].StartVelX = run.StartVelX; WR[run.Style].StartVelY = run.StartVelY; WR[run.Style].StartVelZ = run.StartVelZ; @@ -410,12 +411,12 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") ConnectedMapTimes.Add(run.ID); MapCompletions[run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); break; case 1: // Bonus WR data and total completions BonusWR[run.Stage][run.Style].ID = run.ID; - BonusWR[run.Stage][run.Style].Ticks = run.RunTime; + BonusWR[run.Stage][run.Style].RunTime = run.RunTime; BonusWR[run.Stage][run.Style].StartVelX = run.StartVelX; BonusWR[run.Stage][run.Style].StartVelY = run.StartVelY; BonusWR[run.Stage][run.Style].StartVelZ = run.StartVelZ; @@ -426,12 +427,12 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") BonusWR[run.Stage][run.Style].Name = run.Name; BonusCompletions[run.Stage][run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); break; case 2: // Stage WR data and total completions StageWR[run.Stage][run.Style].ID = run.ID; - StageWR[run.Stage][run.Style].Ticks = run.RunTime; + StageWR[run.Stage][run.Style].RunTime = run.RunTime; StageWR[run.Stage][run.Style].StartVelX = run.StartVelX; StageWR[run.Stage][run.Style].StartVelY = run.StartVelY; StageWR[run.Stage][run.Style].StartVelZ = run.StartVelZ; @@ -442,7 +443,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") StageWR[run.Stage][run.Style].Name = run.Name; StageCompletions[run.Stage][run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFramesBase64); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); break; } } @@ -453,7 +454,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> LoadMapRecordRuns : Map -> [{DBorAPI}] Loaded {MapCompletions} runs (MapID {MapID} | Style {Style}). WR by {PlayerName} - {Time}", - nameof(Map), methodName, (Config.API.GetApiOnly() ? "API" : "DB"), this.MapCompletions[style], this.ID, style, this.WR[style].Name, PlayerHUD.FormatTime(this.WR[style].Ticks) + nameof(Map), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.MapCompletions[style], this.ID, style, this.WR[style].Name, PlayerHUD.FormatTime(this.WR[style].RunTime) ); #endif @@ -491,13 +492,13 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB case 0: // Map Replays // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); _logger.LogTrace("[{ClassName}] {MethodName} -> SetReplayData -> [MapWR] Setting run {RunID} {RunTime} (Ticks = {RunTicks}; Frames = {TotalFrames})", - nameof(Map), methodName, this.WR[style].ID, PlayerHUD.FormatTime(this.WR[style].Ticks), this.WR[style].Ticks, frames.Count + nameof(Map), methodName, this.WR[style].ID, PlayerHUD.FormatTime(this.WR[style].RunTime), this.WR[style].RunTime, frames.Count ); if (this.ReplayManager.MapWR.IsPlaying) this.ReplayManager.MapWR.Stop(); this.ReplayManager.MapWR.RecordPlayerName = this.WR[style].Name; - this.ReplayManager.MapWR.RecordRunTime = this.WR[style].Ticks; + this.ReplayManager.MapWR.RecordRunTime = this.WR[style].RunTime; this.ReplayManager.MapWR.Frames = frames; this.ReplayManager.MapWR.MapTimeID = this.WR[style].ID; this.ReplayManager.MapWR.MapID = this.ID; @@ -542,18 +543,18 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB break; case 1: // Bonus Replays // Skip if the same bonus run already exists - if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].Ticks) + if (this.ReplayManager.AllBonusWR[stage][style].RecordRunTime == this.BonusWR[stage][style].RunTime) break; #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> SetReplayData -> [BonusWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllBonusWR`", - nameof(Map), methodName, this.BonusWR[stage][style].ID, PlayerHUD.FormatTime(this.BonusWR[stage][style].Ticks), this.BonusWR[stage][style].Ticks, frames.Count + nameof(Map), methodName, this.BonusWR[stage][style].ID, PlayerHUD.FormatTime(this.BonusWR[stage][style].RunTime), this.BonusWR[stage][style].RunTime, frames.Count ); #endif // Add all stages found to a dictionary with their data this.ReplayManager.AllBonusWR[stage][style].MapID = this.ID; this.ReplayManager.AllBonusWR[stage][style].Frames = frames; - this.ReplayManager.AllBonusWR[stage][style].RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.AllBonusWR[stage][style].RecordRunTime = this.BonusWR[stage][style].RunTime; this.ReplayManager.AllBonusWR[stage][style].RecordPlayerName = this.BonusWR[stage][style].Name; this.ReplayManager.AllBonusWR[stage][style].MapTimeID = this.BonusWR[stage][style].ID; this.ReplayManager.AllBonusWR[stage][style].Stage = stage; @@ -585,7 +586,7 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB this.ReplayManager.BonusWR.Stop(); this.ReplayManager.BonusWR.MapID = this.ID; this.ReplayManager.BonusWR.Frames = frames; - this.ReplayManager.BonusWR.RecordRunTime = this.BonusWR[stage][style].Ticks; + this.ReplayManager.BonusWR.RecordRunTime = this.BonusWR[stage][style].RunTime; this.ReplayManager.BonusWR.RecordPlayerName = this.BonusWR[stage][style].Name; this.ReplayManager.BonusWR.MapTimeID = this.BonusWR[stage][style].ID; this.ReplayManager.BonusWR.Stage = stage; @@ -595,18 +596,18 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB break; case 2: // Stage Replays // Skip if the same stage run already exists - if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].Ticks) + if (this.ReplayManager.AllStageWR[stage][style].RecordRunTime == this.StageWR[stage][style].RunTime) break; #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> SetReplayData -> [StageWR] Adding run {ID} {Time} (Ticks = {Ticks}; Frames = {Frames}) to `ReplayManager.AllStageWR`", - nameof(Map), methodName, this.StageWR[stage][style].ID, PlayerHUD.FormatTime(this.StageWR[stage][style].Ticks), this.StageWR[stage][style].Ticks, frames.Count + nameof(Map), methodName, this.StageWR[stage][style].ID, PlayerHUD.FormatTime(this.StageWR[stage][style].RunTime), this.StageWR[stage][style].RunTime, frames.Count ); #endif // Add all stages found to a dictionary with their data this.ReplayManager.AllStageWR[stage][style].MapID = this.ID; this.ReplayManager.AllStageWR[stage][style].Frames = frames; - this.ReplayManager.AllStageWR[stage][style].RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.AllStageWR[stage][style].RecordRunTime = this.StageWR[stage][style].RunTime; this.ReplayManager.AllStageWR[stage][style].RecordPlayerName = this.StageWR[stage][style].Name; this.ReplayManager.AllStageWR[stage][style].MapTimeID = this.StageWR[stage][style].ID; this.ReplayManager.AllStageWR[stage][style].Stage = stage; @@ -639,7 +640,7 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB this.ReplayManager.StageWR.Stop(); this.ReplayManager.StageWR.MapID = this.ID; this.ReplayManager.StageWR.Frames = frames; - this.ReplayManager.StageWR.RecordRunTime = this.StageWR[stage][style].Ticks; + this.ReplayManager.StageWR.RecordRunTime = this.StageWR[stage][style].RunTime; this.ReplayManager.StageWR.RecordPlayerName = this.StageWR[stage][style].Name; this.ReplayManager.StageWR.MapTimeID = this.StageWR[stage][style].ID; this.ReplayManager.StageWR.Stage = stage; diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 07fc0d6..5b384bb 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -1,7 +1,7 @@ namespace SurfTimer; using CounterStrikeSharp.API.Core; -internal class Player +public class Player { // CCS requirements public CCSPlayerController Controller {get;} @@ -18,11 +18,8 @@ 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, Map CurrMap) + internal Player(CCSPlayerController Controller, CCSPlayer_MovementServices MovementServices, PlayerProfile Profile) { this.Controller = Controller; this.MovementServices = MovementServices; @@ -36,7 +33,6 @@ public Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movemen CurrentSavedLocation = 0; this.HUD = new PlayerHUD(this); - this.CurrMap = CurrMap; } /// diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 79729a6..e927ab2 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -2,11 +2,11 @@ namespace SurfTimer; -internal class PlayerHUD +public class PlayerHUD { private Player _player; - public PlayerHUD(Player Player) + internal PlayerHUD(Player Player) { _player = Player; } @@ -60,7 +60,7 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P /// /// Displays the Center HUD for the client /// - public void Display() + internal void Display() { if (!_player.Controller.IsValid) return; @@ -117,18 +117,18 @@ public void Display() } // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].Ticks > 0 ? FormatTime(_player.Stats.PB[style].Ticks) : "N/A", "#7882dd"); - string wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.WR[style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.WR[style].Ticks) : "N/A", "#ffc61a"); + string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].RunTime > 0 ? FormatTime(_player.Stats.PB[style].RunTime) : "N/A", "#7882dd"); + string wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.WR[style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.WR[style].RunTime) : "N/A", "#ffc61a"); if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values { - pbModule = FormatHUDElementHTML("PB", _player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].Ticks) : "N/A", "#7882dd"); - wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].Ticks) : "N/A", "#ffc61a"); + pbModule = FormatHUDElementHTML("PB", _player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime > 0 ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime) : "N/A", "#ffc61a"); } else if (_player.Timer.IsStageMode) // Show corresponding stage values { - pbModule = FormatHUDElementHTML("PB", _player.Stats.StagePB[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].Ticks) : "N/A", "#7882dd"); - wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks > 0 ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].Ticks) : "N/A", "#ffc61a"); + pbModule = FormatHUDElementHTML("PB", _player.Stats.StagePB[_player.Timer.Stage][style].RunTime > 0 ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].RunTime) : "N/A", "#7882dd"); + wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime) : "N/A", "#ffc61a"); } // Build HUD @@ -165,7 +165,7 @@ public void Display() /// Displays checkpoints comparison messages in player chat. /// Only calculates if the player has a PB, otherwise it will display N/A /// - public void DisplayCheckpointMessages() + internal void DisplayCheckpointMessages() { int pbTime; int wrTime = -1; @@ -189,7 +189,7 @@ public void DisplayCheckpointMessages() // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[style].Checkpoints[playerCheckpoint].Ticks; + pbTime = _player.Stats.PB[style].Checkpoints[playerCheckpoint].RunTime; pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ); @@ -248,14 +248,14 @@ public void DisplayCheckpointMessages() strPbDifference += ChatColors.Default + ")"; } - if (SurfTimer.CurrentMap.WR[style].Ticks > 0) + if (SurfTimer.CurrentMap.WR[style].RunTime > 0) { // Calculate differences in WR (WR - Current) #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)"); #endif - wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].Ticks; + wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].RunTime; wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ); diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index f360660..35456d6 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -5,7 +5,7 @@ namespace SurfTimer; -internal class PlayerProfile +public class PlayerProfile { public int ID { get; set; } = 0; public string Name { get; set; } = ""; @@ -17,7 +17,7 @@ internal class PlayerProfile private readonly ILogger _logger; private readonly IDataAccessService _dataService; - public PlayerProfile(ulong steamId, string name = "", string country = "") + internal PlayerProfile(ulong steamId, string name = "", string country = "") { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -36,14 +36,14 @@ public PlayerProfile(ulong steamId, string name = "", string country = "") /// Name of the player /// Country of the player /// PlayerProfile object - public static async Task CreateAsync(ulong steamId, string name = "", string country = "") + internal static async Task CreateAsync(ulong steamId, string name = "", string country = "") { var profile = new PlayerProfile(steamId, name, country); await profile.InitializeAsync(); return profile; } - private async Task InitializeAsync([CallerMemberName] string methodName = "") + internal async Task InitializeAsync([CallerMemberName] string methodName = "") { await GetPlayerProfile(); @@ -55,7 +55,7 @@ private async Task InitializeAsync([CallerMemberName] string methodName = "") /// /// Retrieves all the data for the player profile from the database. /// - public async Task GetPlayerProfile([CallerMemberName] string methodName = "") + internal async Task GetPlayerProfile([CallerMemberName] string methodName = "") { var profile = await _dataService.GetPlayerProfileAsync(this.SteamID); @@ -85,7 +85,7 @@ public async Task GetPlayerProfile([CallerMemberName] string methodName = "") /// Insert new player profile information into the database. /// Retrieves the ID of the newly created player. /// - public async Task InsertPlayerProfile([CallerMemberName] string methodName = "") + internal async Task InsertPlayerProfile([CallerMemberName] string methodName = "") { var profile = new PlayerProfileDataModel { @@ -107,7 +107,7 @@ public async Task InsertPlayerProfile([CallerMemberName] string methodName = "") /// Updates the information in the database for the player profile. Increments `connections` and changes nickname. /// /// Player Name - public async Task UpdatePlayerProfile(string name, [CallerMemberName] string methodName = "") + internal async Task UpdatePlayerProfile(string name, [CallerMemberName] string methodName = "") { this.Name = name; await _dataService.UpdatePlayerProfileAsync(new PlayerProfileDataModel diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs index 6470561..0a765e2 100644 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ b/src/ST-Player/PlayerStats/Checkpoint.cs @@ -1,7 +1,7 @@ public class Checkpoint { public int CP { get; set; } - public int Ticks { get; set; } + public int RunTime { get; set; } public float StartVelX { get; set; } public float StartVelY { get; set; } public float StartVelZ { get; set; } @@ -17,7 +17,7 @@ public Checkpoint() { } public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int endTouch, int attempts) { CP = cp; - Ticks = ticks; + RunTime = ticks; StartVelX = startVelX; StartVelY = startVelY; StartVelZ = startVelZ; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 998c8d7..3c5b91a 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using CounterStrikeSharp.API; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; @@ -9,13 +10,13 @@ namespace SurfTimer; /// /// This class stores data for the current run. /// -internal class CurrentRun : RunStats +public class CurrentRun : RunStats { private readonly ILogger _logger; private readonly IDataAccessService _dataService; - public CurrentRun() : base() + internal CurrentRun() : base() { _logger = SurfTimer.ServiceProvider.GetRequiredService>(); _dataService = SurfTimer.ServiceProvider.GetRequiredService(); @@ -35,7 +36,7 @@ public override void Reset() /// Bonus number /// Stage number /// Ticks for the run - used for Stage and Bonus entries - public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") + internal async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") { string replay_frames = ""; @@ -43,34 +44,54 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r // if (methodName != "TestSetPb") replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); - _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} replay frames.", - nameof(CurrentRun), methodName, replay_frames.Length); + _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} serialized and compressed replay frames.", + nameof(CurrentRun), methodName, replay_frames.Length + ); var stopwatch = Stopwatch.StartNew(); int recType = stage != 0 ? 2 : bonus != 0 ? 1 : 0; + int style = player.Timer.Style; + int mapTimeId = 0; var mapTime = new MapTimeDataModel { PlayerId = player.Profile.ID, - MapId = player.CurrMap.ID, + MapId = SurfTimer.CurrentMap.ID, Style = player.Timer.Style, Type = recType, Stage = stage != 0 ? stage : bonus, - Ticks = run_ticks == -1 ? this.Ticks : run_ticks, + RunTime = run_ticks == -1 ? this.RunTime : run_ticks, StartVelX = this.StartVelX, StartVelY = this.StartVelY, StartVelZ = this.StartVelZ, EndVelX = this.EndVelX, EndVelY = this.EndVelY, EndVelZ = this.EndVelZ, - ReplayFramesBase64 = replay_frames, - Checkpoints = this.Checkpoints // Test out + ReplayFrames = replay_frames, + Checkpoints = this.Checkpoints }; - int mapTimeId = await _dataService.InsertMapTimeAsync(mapTime); + switch (recType) + { + case 0: + mapTimeId = player.Stats.PB[style].ID; + break; + case 1: + mapTimeId = player.Stats.BonusPB[bonus][style].ID; + break; + case 2: + mapTimeId = player.Stats.StagePB[stage][style].ID; + break; + } + + if (mapTimeId <= 0) + mapTimeId = await _dataService.InsertMapTimeAsync(mapTime); + else + mapTimeId = await _dataService.UpdateMapTimeAsync(mapTime, mapTimeId); + // Reload the times for the map - await player.CurrMap.LoadMapRecordRuns(); + await SurfTimer.CurrentMap.LoadMapRecordRuns(); _logger.LogTrace("[{ClassName}] {MethodName} -> Loading data for run {ID} with type {Type}.", nameof(CurrentRun), methodName, mapTimeId, recType @@ -99,6 +120,75 @@ public async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int r ); } + /// + /// Deals with saving a Stage MapTime (Type 2) in the Database. + /// Should deal with `IsStageMode` runs, Stages during Map Runs and also Last Stage. + /// + /// Player object + /// Stage to save + /// Is it the last stage? + /// Run Time (Ticks) for the stage run + internal async Task SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool saveLastStage = false) + { + // player.Controller.PrintToChat($"{Config.PluginPrefix} SaveStageTime received: {player.Profile.Name}, {stage}, {stage_run_time}, {saveLastStage}"); + int pStyle = player.Timer.Style; + if ( + stage_run_time < SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime || + SurfTimer.CurrentMap.StageWR[stage][pStyle].ID == -1 || + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].RunTime > stage_run_time || + player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1 + ) + { + if (stage_run_time < SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime) // Player beat the Stage WR + { + int timeImprove = SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_improved", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" + ); + } + else if (SurfTimer.CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map + { + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_set", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time)]}" + ); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best + { + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_set", + stage, PlayerHUD.FormatTime(stage_run_time)]}" + ); + } + else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].RunTime > stage_run_time) // Player beating their existing Stage personal best + { + int timeImprove = player.Stats.StagePB[stage][pStyle].RunTime - stage_run_time; + Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_improved", + player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].RunTime)]}" + ); + } + + player.ReplayRecorder.IsSaving = true; + + // Save stage run + Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); + await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data + + // AddTimer(1.0f, async () => + // { + // // Save stage run + // Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); + // await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data + // }); + } + else if (stage_run_time > SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime && player.Timer.IsStageMode) // Player is behind the Stage WR for the map + { + int timeImprove = stage_run_time - SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime; + player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_missed", + stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" + ); + } + } + + public void PrintSituations(Player player) { Console.WriteLine($"========================== FOUND SITUATIONS =========================="); diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 7707f55..4b8a72b 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -9,9 +9,9 @@ namespace SurfTimer; /// As the PersonalBest object is being used for each different style, we shouldn't need a separate `Style` variable in here because each style entry will have unique ID in the Database /// and will therefore be a unique PersonalBest entry. /// -internal class PersonalBest : RunStats +public class PersonalBest : RunStats { - public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving + // public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving public int Type { get; set; } = -1; // Identifies bonus # - 0 for map time -> huh, why o_O? public string Name { get; set; } = ""; // This is used only for WRs @@ -20,7 +20,7 @@ internal class PersonalBest : RunStats // Add other properties as needed // Constructor - public PersonalBest() : base() + internal PersonalBest() : base() { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -31,7 +31,7 @@ public PersonalBest() : base() /// Loads the Checkpoint data for the given MapTime_ID. Used for loading player's personal bests and Map's world records. /// Bonus and Stage runs should NOT have any checkpoints. /// - public async Task LoadCheckpoints([CallerMemberName] string methodName = "") + internal async Task LoadCheckpoints([CallerMemberName] string methodName = "") { var cps = await _dataService.LoadCheckpointsAsync(this.ID); @@ -59,7 +59,7 @@ public async Task LoadCheckpoints([CallerMemberName] string methodName = "") /// Should be used to reload data from a specific `PersonalBest` object /// /// Player object - public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") + internal async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName] string methodName = "") { var model = await _dataService.LoadPersonalBestRunAsync( pbId: this.ID == -1 ? (int?)null : this.ID, @@ -81,7 +81,7 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName } this.ID = model.ID; - this.Ticks = model.Ticks; + this.RunTime = model.RunTime; this.Rank = model.Rank; this.StartVelX = model.StartVelX; this.StartVelY = model.StartVelY; @@ -90,7 +90,7 @@ public async Task LoadPlayerSpecificMapTimeData(Player player, [CallerMemberName this.EndVelY = model.EndVelY; this.EndVelZ = model.EndVelZ; this.RunDate = model.RunDate; - this.ReplayFramesBase64 = model.ReplayFramesBase64; // Won't work with MySQL load? - Not tested + this.ReplayFrames = model.ReplayFrames; // Won't work with MySQL load? - Not tested _logger.LogDebug( "[{ClassName}] {MethodName} -> Loaded PB run {RunId} for {Player}.", diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 10861a1..b1ecd39 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -5,23 +5,12 @@ namespace SurfTimer; -internal class PlayerStats +public 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. - // /// - // /// Stage Personal Best - Refer to as StagePB[style][stage#] - // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE - // /// - // public int[,] StagePB { get; set; } = { { 0, 0 } }; - // /// - // /// Stage Personal Best - Refer to as StageRank[style][stage#] - // /// To-do: DEPRECATE THIS WHEN IMPLEMENTING STAGES, FOLLOW NEW PB STRUCTURE - // /// - // public int[,] StageRank { get; set; } = { { 0, 0 } }; - /// /// Map Personal Best - Refer to as PB[style] /// @@ -47,7 +36,7 @@ internal class PlayerStats // Initialize PersonalBest for each `style` (e.g., 0 for normal) // Here we can loop through all available styles at some point and initialize them - public PlayerStats([CallerMemberName] string methodName = "") + internal PlayerStats([CallerMemberName] string methodName = "") { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -81,9 +70,9 @@ public PlayerStats([CallerMemberName] string methodName = "") /// Not used /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") + internal async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=0"); + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=0"); if (player_maptime == null) { _logger.LogTrace("[{ClassName}] {MethodName} -> LoadMapTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", @@ -93,7 +82,7 @@ public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] s } PB[style].ID = player_maptime.id; - PB[style].Ticks = player_maptime.run_time; + PB[style].RunTime = player_maptime.run_time; PB[style].Type = player_maptime.type; PB[style].StartVelX = player_maptime.start_vel_x; PB[style].StartVelY = player_maptime.start_vel_y; @@ -122,9 +111,9 @@ public async void LoadMapTime(Player player, int style = 0, [CallerMemberName] s /// Not used /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") + internal async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=2"); + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=2"); if (player_maptime == null) { _logger.LogTrace("[{ClassName}] {MethodName} -> LoadStageTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", @@ -136,7 +125,7 @@ public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] foreach (API_MapTime mt in player_maptime) { StagePB[mt.stage][style].ID = mt.id; - StagePB[mt.stage][style].Ticks = mt.run_time; + StagePB[mt.stage][style].RunTime = mt.run_time; StagePB[mt.stage][style].Type = mt.type; StagePB[mt.stage][style].StartVelX = mt.start_vel_x; StagePB[mt.stage][style].StartVelY = mt.start_vel_y; @@ -153,9 +142,9 @@ public async void LoadStageTime(Player player, int style = 0, [CallerMemberName] /// Not used /// // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") + internal async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={player.CurrMap.ID}&style={style}&type=1"); + var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=1"); if (player_maptime == null) { _logger.LogTrace("[{ClassName}] {MethodName} -> LoadBonusTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", @@ -167,7 +156,7 @@ public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] foreach (API_MapTime mt in player_maptime) { BonusPB[mt.stage][style].ID = mt.id; - BonusPB[mt.stage][style].Ticks = mt.run_time; + BonusPB[mt.stage][style].RunTime = mt.run_time; BonusPB[mt.stage][style].Type = mt.type; BonusPB[mt.stage][style].StartVelX = mt.start_vel_x; BonusPB[mt.stage][style].StartVelY = mt.start_vel_y; @@ -186,7 +175,7 @@ public async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. (This method calls it if needed) /// This populates all the `style` and `type` stats the player has for the map /// - public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") + internal async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int mapId = 0, [CallerMemberName] string methodName = "") { var playerMapTimes = await _dataService.GetPlayerMapTimesAsync(player.Profile.ID, SurfTimer.CurrentMap.ID); @@ -207,7 +196,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> BonusPB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif BonusPB[mapTime.Stage][style].ID = mapTime.ID; - BonusPB[mapTime.Stage][style].Ticks = mapTime.RunTime; + BonusPB[mapTime.Stage][style].RunTime = mapTime.RunTime; BonusPB[mapTime.Stage][style].Type = mapTime.Type; BonusPB[mapTime.Stage][style].Rank = mapTime.Rank; BonusPB[mapTime.Stage][style].StartVelX = mapTime.StartVelX; @@ -224,7 +213,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> StagePB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif StagePB[mapTime.Stage][style].ID = mapTime.ID; - StagePB[mapTime.Stage][style].Ticks = mapTime.RunTime; + StagePB[mapTime.Stage][style].RunTime = mapTime.RunTime; StagePB[mapTime.Stage][style].Type = mapTime.Type; StagePB[mapTime.Stage][style].Rank = mapTime.Rank; StagePB[mapTime.Stage][style].StartVelX = mapTime.StartVelX; @@ -241,7 +230,7 @@ public async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int ma _logger.LogDebug("[{ClassName}] {MethodName} -> LoadPlayerMapTimesData >> MapPB with ID {ID}", nameof(PlayerStats), methodName, mapTime.ID); #endif PB[style].ID = mapTime.ID; - PB[style].Ticks = mapTime.RunTime; + PB[style].RunTime = mapTime.RunTime; PB[style].Type = mapTime.Type; PB[style].Rank = mapTime.Rank; PB[style].StartVelX = mapTime.StartVelX; diff --git a/src/ST-Player/PlayerStats/RunStats.cs b/src/ST-Player/PlayerStats/RunStats.cs index c60fce2..2f92d16 100644 --- a/src/ST-Player/PlayerStats/RunStats.cs +++ b/src/ST-Player/PlayerStats/RunStats.cs @@ -3,7 +3,8 @@ namespace SurfTimer; public abstract class RunStats { public Dictionary Checkpoints { get; set; } - public int Ticks { get; set; } + public int ID { get; set; } + public int RunTime { get; set; } public float StartVelX { get; set; } public float StartVelY { get; set; } public float StartVelZ { get; set; } @@ -11,12 +12,12 @@ public abstract class RunStats public float EndVelY { get; set; } public float EndVelZ { get; set; } public int RunDate { get; set; } - public string ReplayFramesBase64 { get; set; } = ""; + public string ReplayFrames { get; set; } = ""; protected RunStats() { Checkpoints = new Dictionary(); - Ticks = 0; + RunTime = 0; StartVelX = 0.0f; StartVelY = 0.0f; StartVelZ = 0.0f; @@ -30,7 +31,7 @@ protected RunStats() public virtual void Reset() { Checkpoints.Clear(); - Ticks = 0; + RunTime = 0; StartVelX = StartVelY = StartVelZ = 0.0f; EndVelX = EndVelY = EndVelZ = 0.0f; RunDate = 0; diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 1e4ddc4..5211f35 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -1,6 +1,6 @@ namespace SurfTimer; -internal class PlayerTimer +public class PlayerTimer { // Status public bool IsEnabled { get; set; } = true; // Enable toggle for entire timer diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index 2bf493e..ede7faa 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -1,11 +1,8 @@ namespace SurfTimer; using System; -using System.Numerics; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; -internal enum ReplayFrameSituation +public enum ReplayFrameSituation { NONE, @@ -30,12 +27,12 @@ internal enum ReplayFrameSituation } [Serializable] -internal class ReplayFrame +public class ReplayFrame { public float[] pos { get; set; } = { 0, 0, 0 }; public float[] ang { get; set; } = { 0, 0, 0 }; public ReplayFrameSituation Situation { get; set; } = ReplayFrameSituation.NONE; - public uint Flags { get; set; } + public uint Flags { get; set; } public Vector_t GetPos() { diff --git a/src/ST-Player/Replay/ReplayManager.cs b/src/ST-Player/Replay/ReplayManager.cs index 7291846..b06730b 100644 --- a/src/ST-Player/Replay/ReplayManager.cs +++ b/src/ST-Player/Replay/ReplayManager.cs @@ -1,9 +1,8 @@ -using System.Text.Json; using CounterStrikeSharp.API.Core; namespace SurfTimer; -internal class ReplayManager +public class ReplayManager { public ReplayPlayer MapWR { get; set; } public ReplayPlayer? BonusWR { get; set; } = null; @@ -32,7 +31,7 @@ internal class ReplayManager /// ID of the run /// Style of the run /// Stage/Bonus of the run - public ReplayManager(int map_id, bool staged, bool bonused, List frames, int run_time = 0, string playerName = "", int map_time_id = -1, int style = 0, int stage = 0) + internal ReplayManager(int map_id, bool staged, bool bonused, List frames, int run_time = 0, string playerName = "", int map_time_id = -1, int style = 0, int stage = 0) { MapWR = new ReplayPlayer { diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index f67a6d0..92319e4 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -7,7 +7,7 @@ namespace SurfTimer; -internal class ReplayPlayer +public class ReplayPlayer { /// /// Enable or Disable the replay bots. @@ -63,7 +63,7 @@ internal ReplayPlayer() _logger = SurfTimer.ServiceProvider.GetRequiredService>(); } - public void ResetReplay() + internal void ResetReplay() { this.CurrentFrameTick = 0; this.FrameTickIncrement = 1; @@ -74,7 +74,7 @@ public void ResetReplay() this.ReplayCurrentRunTime = 0; } - public void Reset() + internal void Reset() { this.IsPlaying = false; this.IsPaused = false; @@ -88,7 +88,7 @@ public void Reset() this.Controller = null; } - public void SetController(CCSPlayerController c, int repeat_count = -1, [CallerMemberName] string methodName = "") + internal void SetController(CCSPlayerController c, int repeat_count = -1, [CallerMemberName] string methodName = "") { this.Controller = c; if (repeat_count != -1) @@ -100,7 +100,7 @@ public void SetController(CCSPlayerController c, int repeat_count = -1, [CallerM ); } - public void Start([CallerMemberName] string methodName = "") + internal void Start([CallerMemberName] string methodName = "") { if (!this.IsPlayable || !this.IsEnabled) return; @@ -118,7 +118,7 @@ public void Start([CallerMemberName] string methodName = "") }); } - public void Stop([CallerMemberName] string methodName = "") + internal void Stop([CallerMemberName] string methodName = "") { this.IsPlaying = false; #if DEBUG @@ -128,7 +128,7 @@ public void Stop([CallerMemberName] string methodName = "") #endif } - public void Pause([CallerMemberName] string methodName = "") + internal void Pause([CallerMemberName] string methodName = "") { if (!this.IsPlaying || !this.IsEnabled) return; @@ -142,7 +142,7 @@ public void Pause([CallerMemberName] string methodName = "") #endif } - public void Tick() + internal void Tick() { if (this.MapID == -1 || !this.IsEnabled || !this.IsPlaying || !this.IsPlayable || this.Frames.Count == 0) return; @@ -210,7 +210,7 @@ public void Tick() // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> Tick -> ====================> {this.RepeatCount} <===================="); } - public void LoadReplayData(int repeat_count = -1, [CallerMemberName] string methodName = "") + internal void LoadReplayData(int repeat_count = -1, [CallerMemberName] string methodName = "") { if (!this.IsPlayable || !this.IsEnabled) return; @@ -231,7 +231,7 @@ public void LoadReplayData(int repeat_count = -1, [CallerMemberName] string meth this.RepeatCount = repeat_count; } - public void FormatBotName([CallerMemberName] string methodName = "") + internal void FormatBotName([CallerMemberName] string methodName = "") { if (!this.IsPlayable || !this.IsEnabled || this.MapID == -1) return; diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 39f996e..35f5b66 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -5,11 +5,11 @@ namespace SurfTimer; -internal class ReplayRecorder +public class ReplayRecorder { private readonly ILogger _logger; - public ReplayRecorder() + internal ReplayRecorder() { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -34,7 +34,7 @@ public ReplayRecorder() /// public List BonusSituations { get; set; } = new List(); - public void Reset([CallerMemberName] string methodName = "") + internal void Reset([CallerMemberName] string methodName = "") { this.IsRecording = false; this.Frames.Clear(); @@ -52,7 +52,7 @@ public void Reset([CallerMemberName] string methodName = "") #endif } - public void Start([CallerMemberName] string methodName = "") + internal void Start([CallerMemberName] string methodName = "") { this.IsRecording = true; @@ -63,7 +63,7 @@ public void Start([CallerMemberName] string methodName = "") #endif } - public void Stop([CallerMemberName] string methodName = "") + internal void Stop([CallerMemberName] string methodName = "") { this.IsRecording = false; @@ -74,7 +74,7 @@ public void Stop([CallerMemberName] string methodName = "") #endif } - public void Tick(Player player, [CallerMemberName] string methodName = "") + internal void Tick(Player player, [CallerMemberName] string methodName = "") { if (!this.IsRecording || player == null) return; @@ -133,7 +133,7 @@ public void Tick(Player player, [CallerMemberName] string methodName = "") this.CurrentSituation = ReplayFrameSituation.NONE; } - public string SerializeReplay() + internal string SerializeReplay() { // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; // string replay_frames = JsonSerializer.Serialize(Frames, options); @@ -141,7 +141,7 @@ public string SerializeReplay() return Compressor.Compress(replay_frames); } - public string SerializeReplayPortion(int start_idx, int end_idx) // Not used anymore + internal string SerializeReplayPortion(int start_idx, int end_idx) // Not used anymore { // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; // string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx), options); @@ -149,7 +149,7 @@ public string SerializeReplayPortion(int start_idx, int end_idx) // Not used any return Compressor.Compress(replay_frames); } - public void SetLastTickSituation(ReplayFrameSituation situation) + internal void SetLastTickSituation(ReplayFrameSituation situation) { if (this.Frames.Count == 0) return; @@ -157,11 +157,11 @@ public void SetLastTickSituation(ReplayFrameSituation situation) this.Frames[this.Frames.Count - 2].Situation = situation; } - public string TrimReplay(Player player, int type = 0, bool lastStage = false, [CallerMemberName] string methodName = "") + internal string TrimReplay(Player player, int type = 0, bool lastStage = false, [CallerMemberName] string methodName = "") { this.IsSaving = true; - List new_frames = new List(); + List? trimmed_frames = new List(); _logger.LogTrace(">>> [{ClassName}] {MethodName} -> Trimming replay for '{PlayerName}' | type = {type} | lastStage = {lastStage} ", nameof(ReplayRecorder), methodName, player.Profile.Name, type, lastStage @@ -169,7 +169,7 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C if (this.Frames.Count == 0) { - _logger.LogError("[{ClassName}] {MethodName} -> There are no Frames available for trimming for player {Name}", + _logger.LogError("[{ClassName}] {MethodName} -> There are no Frames available for replay trimming for player {Name}", nameof(ReplayRecorder), methodName, player.Profile.Name ); throw new Exception("There are no Frames available for trimming"); @@ -178,182 +178,183 @@ public string TrimReplay(Player player, int type = 0, bool lastStage = false, [C { case 0: // Map Run { - var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - - _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", - nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); - - if (start_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for start_enter_index during Map replay trimming. Setting 'start_enter_index' to '0' | IsStageMode = {StageMode} | IsBonusMode = {BonusMode}", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode - ); - start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; - // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; - } - - if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) - { - int startIndex = CalculateStartIndex(start_enter_index, start_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); - - _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); - } - else - { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", - nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index - ); - } + trimmed_frames = TrimMapRun(player); break; } case 1: // Bonus Run { - var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", - nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index - ); - - if (bonus_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus - ); - bonus_enter_index = 0; - } - - if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) - { - int startIndex = CalculateStartIndex(bonus_enter_index, bonus_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); - - _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count - ); - } - else - { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", - nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index - ); - } + trimmed_frames = TrimBonusRun(player); break; } case 2: // Stage Run { - int stage_end_index; - int stage_exit_index; - int stage_enter_index; - - _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Stage Run replay trim indexes. Stage {Stage}, available frames {TotalFrames}", - nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, Frames.Count - ); - - if (lastStage) - { - _logger.LogDebug("Stage replay trimming will use `STAGE_ZONE_X` + `END_ZONE_ENTER`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - // _logger.LogDebug("stage_end_index = {stage_end_index}", - // stage_end_index - // ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - // _logger.LogDebug("stage_exit_index = {stage_exit_index}", - // stage_exit_index - // ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - // _logger.LogDebug("stage_enter_index = {stage_enter_index}", - // stage_enter_index - // ); - } - else if (player.Timer.Stage - 1 > 1) - { - _logger.LogDebug("Stage replay trimming will use `STAGE_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - // _logger.LogDebug("stage_end_index = {stage_end_index}", - // stage_end_index - // ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - // _logger.LogDebug("stage_exit_index = {stage_exit_index}", - // stage_exit_index - // ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - // _logger.LogDebug("stage_enter_index = {stage_enter_index}", - // stage_enter_index - // ); - } - else if (player.Timer.Stage - 1 == -1) // Don't crash...? - { - _logger.LogError(">>>> Stage replay trimming will abort! <<<<"); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> this.IsSaving = {IsSaving}", this.IsSaving); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsRunning = {IsRunning}", player.Timer.IsRunning); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.IsEnabled = {IsEnabled}", player.Timer.IsEnabled); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Stage = {Stage}", player.Timer.Stage); - _logger.LogError("======== internal class ReplayRecorder -> public string TrimReplay -> player.Timer.Ticks = {Ticks}", player.Timer.Ticks); - stage_enter_index = -1; - stage_end_index = -1; - stage_exit_index = -1; - } - else - { - _logger.LogDebug("Stage replay trimming will use `START_ZONE_X`"); - stage_end_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER); - // _logger.LogDebug("stage_end_index = {stage_end_index}", - // stage_end_index - // ); - stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); - // _logger.LogDebug("stage_exit_index = {stage_exit_index}", - // stage_exit_index - // ); - stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); - // _logger.LogDebug("stage_enter_index = {stage_enter_index}", - // stage_enter_index - // ); - } - _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {stage_enter_index}, stage exit {stage_exit_index}, stage end {stage_end_index}", - nameof(ReplayRecorder), methodName, player.Timer.Stage - 1, stage_enter_index, stage_exit_index, stage_end_index - ); - - if (stage_enter_index == -1) - { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for stage_enter_index during Stage ({StageNumber}) replay trimming. Setting 'stage_enter_index' to '0'", - nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Stage - ); - stage_enter_index = 0; - } - - if (stage_enter_index != -1 && stage_exit_index != -1 && stage_end_index != -1) - { - int startIndex = CalculateStartIndex(stage_enter_index, stage_exit_index, Config.ReplaysPre); - int endIndex = CalculateEndIndex(stage_end_index, Frames.Count, Config.ReplaysPre); - new_frames = GetTrimmedFrames(startIndex, endIndex); - - _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", - nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count - ); - } - else - { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Stage ({StageNumber}) replay for '{Name}'. stage_enter_index = {stage_enter_index} | stage_exit_index = {stage_exit_index} | stage_end_index = {stage_end_index}", - nameof(ReplayRecorder), methodName, player.Timer.Stage, player.Profile.Name, stage_enter_index, stage_exit_index, stage_end_index - ); - } + trimmed_frames = TrimStageRun(player, lastStage); break; } } this.IsSaving = false; - string trimmed = JsonSerializer.Serialize(new_frames); + _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} replay frames.", + nameof(CurrentRun), methodName, trimmed_frames?.Count + ); + var trimmed = JsonSerializer.Serialize(trimmed_frames); return Compressor.Compress(trimmed); } - public int LastEnterTick(int start_idx = 0) + internal List? TrimMapRun(Player player, [CallerMemberName] string methodName = "") + { + List? new_frames = new List(); + + var start_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", + nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); + + if (start_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for start_enter_index during Map replay trimming. Setting 'start_enter_index' to '0' | IsStageMode = {StageMode} | IsBonusMode = {BonusMode}", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode + ); + start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; + // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; + } + + if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) + { + int startIndex = CalculateStartIndex(start_enter_index, start_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed from {StartIndex} to {EndIndex} (new_frames = {NewFramesCount}) - from total {TotalFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count); + + return new_frames; + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", + nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index + ); + + return new_frames; + } + } + + internal List? TrimBonusRun(Player player, [CallerMemberName] string methodName = "") + { + List? new_frames = new List(); + + var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); + var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); + var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index + ); + + if (bonus_enter_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", + nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus + ); + bonus_enter_index = 0; + } + + if (bonus_enter_index != -1 && bonus_exit_index != -1 && bonus_end_enter_index != -1) + { + int startIndex = CalculateStartIndex(bonus_enter_index, bonus_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count + ); + + return new_frames; + } + else + { + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", + nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index + ); + + return new_frames; + } + } + + internal List? TrimStageRun(Player player, bool lastStage = false, [CallerMemberName] string methodName = "") + { + List? new_frames = new List(); + + int stage_end_index; + int stage_exit_index; + int stage_enter_index; + + int stage = player.Timer.Stage - 1; + + ReplayFrameSituation enterZone; + ReplayFrameSituation exitZone; + ReplayFrameSituation endZone; + + // Select the correct enums for trimming + if (stage == 1) + { + _logger.LogDebug("Stage replay trimming will use START_ZONE_*"); + enterZone = ReplayFrameSituation.START_ZONE_ENTER; + exitZone = ReplayFrameSituation.START_ZONE_EXIT; + endZone = ReplayFrameSituation.STAGE_ZONE_ENTER; + } + else + { + _logger.LogDebug("Stage replay trimming will use STAGE_ZONE_*"); + enterZone = ReplayFrameSituation.STAGE_ZONE_ENTER; + exitZone = ReplayFrameSituation.STAGE_ZONE_EXIT; + endZone = ReplayFrameSituation.STAGE_ZONE_ENTER; + + // If it's the last stage we need to use END_ZONE_ENTER for trimming + if (lastStage) + { + _logger.LogDebug("This is the last stage, will end on END_ZONE_ENTER."); + endZone = ReplayFrameSituation.END_ZONE_ENTER; + stage += 1; + } + } + + _logger.LogInformation("[{ClassName}] {MethodName} -> Player is on Stage {Stage} and we are trimming replay for Stage {TrimmingStage}", + nameof(ReplayRecorder), methodName, player.Timer.Stage, stage + ); + + stage_end_index = Frames.FindLastIndex(f => f.Situation == endZone); + stage_exit_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == exitZone); + stage_enter_index = Frames.FindLastIndex(stage_end_index - 1, f => f.Situation == enterZone); + + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Stage Run replay. Stage {Stage}, enter {EnterIndex}, exit {ExitIndex}, end {EndIndex}", + nameof(ReplayRecorder), methodName, stage, stage_enter_index, stage_exit_index, stage_end_index + ); + + if (stage_enter_index == -1 || stage_exit_index == -1 || stage_end_index == -1) + { + _logger.LogError("[{ClassName}] {MethodName} -> Could not find necessary frame indexes for trimming Stage {Stage} replay for player '{Name}'. ENTER: {Enter}, EXIT: {Exit}, END: {End}", + nameof(ReplayRecorder), methodName, stage, player.Profile.Name, + stage_enter_index, stage_exit_index, stage_end_index + ); + return new_frames; + } + + int startIndex = CalculateStartIndex(stage_enter_index, stage_exit_index, Config.ReplaysPre); + int endIndex = CalculateEndIndex(stage_end_index, Frames.Count, Config.ReplaysPre); + + new_frames = GetTrimmedFrames(startIndex, endIndex); + + _logger.LogInformation("<<< [{ClassName}] {MethodName} -> Trimmed Stage {Stage} replay from {Start} to {End} (Total Frames: {NewFrames})", + nameof(ReplayRecorder), methodName, stage, startIndex, endIndex, new_frames.Count + ); + + return new_frames; + } + + internal int LastEnterTick(int start_idx = 0) { if (start_idx == 0) start_idx = this.Frames.Count - 1; @@ -370,7 +371,7 @@ public int LastEnterTick(int start_idx = 0) return 0; } - public int LastExitTick(int start_idx = 0) + internal int LastExitTick(int start_idx = 0) { if (start_idx == 0) start_idx = this.Frames.Count - 1; @@ -402,13 +403,25 @@ private int CalculateStartIndex(int start_enter, int start_exit, int buffer) private int CalculateEndIndex(int end_enter, int totalFrames, int buffer) { if (end_enter + (buffer * 2) < totalFrames) + { + // _logger.LogTrace("CalculateEndIndex 1st -> end_enter + (buffer * 2) < totalFrames ({Logic}) -> {Value}", end_enter + (buffer * 2) < totalFrames, end_enter + (buffer * 2)); return end_enter + (buffer * 2); + } else if (end_enter + buffer < totalFrames) + { + // _logger.LogTrace("CalculateEndIndex 2nd -> end_enter + buffer < totalFrames ({Logic}) -> {Value}", end_enter + buffer < totalFrames, end_enter + buffer); return end_enter + buffer; + } else if (end_enter + (buffer / 2) < totalFrames) + { + // _logger.LogTrace("CalculateEndIndex 3rd -> end_enter + (buffer / 2) < totalFrames ({Logic}) -> {Value}", end_enter + (buffer / 2) < totalFrames, end_enter + (buffer / 2)); return end_enter + (buffer / 2); + } else + { + // _logger.LogTrace("CalculateEndIndex 4th -> Last -> {Value}", end_enter); return end_enter; + } } private List GetTrimmedFrames(int startIndex, int endIndex) diff --git a/src/ST-Player/Saveloc/SavelocFrame.cs b/src/ST-Player/Saveloc/SavelocFrame.cs index 7fd9db6..1862cf4 100644 --- a/src/ST-Player/Saveloc/SavelocFrame.cs +++ b/src/ST-Player/Saveloc/SavelocFrame.cs @@ -1,8 +1,6 @@ -using CounterStrikeSharp.API.Modules.Utils; - namespace SurfTimer; -internal class SavelocFrame +public class SavelocFrame { public Vector_t Pos { get; set; } = new Vector_t(0, 0, 0); public QAngle_t Ang { get; set; } = new QAngle_t(0, 0, 0); diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index a9f2f47..6140ee1 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -79,34 +79,31 @@ public static bool GetApiOnly() /// public static class Endpoints { - public const string ENDPOINT_PING = "/ping?client_unix={0}"; - + public const string ENDPOINT_PING = "/api/Utilities/ping/clientUnix={0}"; + // Map.cs related endpoints - public const string ENDPOINT_MAP_GET_INFO = "/surftimer/mapinfo?mapname={0}"; - public const string ENDPOINT_MAP_INSERT_INFO = "/surftimer/insertmap"; - public const string ENDPOINT_MAP_UPDATE_INFO = "/surftimer/updateMap"; - // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maptotals?map_id={0}&style={1}"; - // public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?map_id={0}&style={1}&type={2}"; - public const string ENDPOINT_MAP_GET_RUNS = "/surftimer/maprunsdata?id={0}"; - public const string ENDPOINT_MAP_GET_RUN_CPS = "/surftimer/mapcheckpointsdata?maptime_id={0}"; + public const string ENDPOINT_MAP_GET_INFO = "/api/Map/mapName={0}"; + public const string ENDPOINT_MAP_INSERT_INFO = "/api/Map"; + public const string ENDPOINT_MAP_UPDATE_INFO = "/api/Map/mapId={0}"; + public const string ENDPOINT_MAP_GET_RUNS = "/api/Map/mapId={0}"; + public const string ENDPOINT_MAP_GET_RUN_CPS = "/api/PersonalBest/checkpoints/mapTimeId={0}"; // CurrentRun.cs - public const string ENDPOINT_CR_SAVE_MAP_TIME = "/surftimer/savemaptime"; + public const string ENDPOINT_CR_SAVE_MAP_TIME = "/api/CurrentRun/saveMapTime"; + public const string ENDPOINT_CR_UPDATE_MAP_TIME = "/api/CurrentRun/updateMapTime/mapTimeId={0}"; public const string ENDPOINT_CR_SAVE_STAGE_TIME = "/surftimer/savestagetime"; - // PersonalBest.cs - public const string ENDPOINT_MAP_GET_PB_BY_PLAYER = "/surftimer/runbyplayer?player_id={0}&map_id={1}&type={2}&style={3}"; - public const string ENDPOINT_MAP_GET_PB_BY_ID = "/surftimer/runbyid?run_id={0}"; - + public const string ENDPOINT_MAP_GET_PB_BY_PLAYER = "/api/PersonalBest/playerId={0}&mapId={1}&type={2}&style={3}"; + public const string ENDPOINT_MAP_GET_PB_BY_ID = "/api/PersonalBest/runById/mapTimeId={0}"; // PlayerProfile.cs - public const string ENDPOINT_PP_GET_PROFILE = "/surftimer/playersurfprofile/{0}"; - public const string ENDPOINT_PP_INSERT_PROFILE = "/surftimer/insertplayer"; - public const string ENDPOINT_PP_UPDATE_PROFILE = "/surftimer/updateplayerprofile"; + public const string ENDPOINT_PP_GET_PROFILE = "/api/PlayerProfile/steamId={0}"; + public const string ENDPOINT_PP_INSERT_PROFILE = "/api/PlayerProfile"; + public const string ENDPOINT_PP_UPDATE_PROFILE = "/api/PlayerProfile/playerId={0}"; // PlayerStats.cs - public const string ENDPOINT_PS_GET_PLAYER_MAP_DATA = "/surftimer/playermapdata?player_id={0}&map_id={1}"; + public const string ENDPOINT_PS_GET_PLAYER_MAP_DATA = "/api/PlayerStats/playerId={0}&mapId={1}"; } } @@ -172,7 +169,7 @@ FROM MapTimes WHERE subquery.`map_id` = mainquery.`map_id` AND subquery.`style` = mainquery.`style` AND subquery.`run_time` <= mainquery.`run_time` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1}; - "; // Deprecated + "; // PersonalBest.cs related queries public const string DB_QUERY_PB_GET_TYPE_RUNTIME = @" @@ -229,47 +226,4 @@ FROM MapTimes "; } } -} - - - -/* - /// - /// Replaces color codes from strings to CS# ChatColors. - /// {white} -> {ChatColors.White} - /// - /// String to replace colors - /// with 'ChatColors' - private static string ReplaceColors(string message) - { - var replacements = new Dictionary - { - { "{default}", $"{ChatColors.Default}" }, - { "{red}", $"{ChatColors.Red}" }, - { "{white}", $"{ChatColors.White}" }, - { "{darkred}", $"{ChatColors.DarkRed}" }, - { "{green}", $"{ChatColors.Green}" }, - { "{lightyellow}", $"{ChatColors.LightYellow}" }, - { "{lightblue}", $"{ChatColors.LightBlue}" }, - { "{olive}", $"{ChatColors.Olive}" }, - { "{lime}", $"{ChatColors.Lime}" }, - { "{lightpurple}", $"{ChatColors.LightPurple}" }, - { "{purple}", $"{ChatColors.Purple}" }, - { "{grey}", $"{ChatColors.Grey}" }, - { "{yellow}", $"{ChatColors.Yellow}" }, - { "{gold}", $"{ChatColors.Gold}" }, - { "{silver}", $"{ChatColors.Silver}" }, - { "{blue}", $"{ChatColors.Blue}" }, - { "{darkblue}", $"{ChatColors.DarkBlue}" }, - { "{bluegrey}", $"{ChatColors.BlueGrey}" }, - { "{magenta}", $"{ChatColors.Magenta}" }, - { "{lightred}", $"{ChatColors.LightRed}" }, - { "{orange}", $"{ChatColors.Orange}" } - }; - - foreach (var replacement in replacements) - message = message.Replace(replacement.Key, replacement.Value); - - return message; - } -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 9599122..6a4fb36 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -20,14 +20,14 @@ public async Task PingAccessService([CallerMemberName] string methodName = { try { - var response = await ApiMethod.GET>( - string.Format(Config.API.Endpoints.ENDPOINT_PING, DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + var response = await ApiMethod.GET>( + string.Format(Config.API.Endpoints.ENDPOINT_PING, (double)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) ); - if (response != null && response.ContainsKey("client_unix")) + if (response != null && response.ContainsKey("clientUnix")) { _logger.LogInformation("[{ClassName}] {MethodName} -> Success -> Client: {ClientUnix} | Server: {ServerUnix} | Latency: {LatencyS}s | Latency: {LatencyMS}ms", - nameof(ApiDataAccessService), methodName, response["client_unix"], response["server_unix"], response["latency_seconds"], response["latency_ms"] + nameof(ApiDataAccessService), methodName, response["clientUnix"], response["serverUnix"], response["latencySeconds"], response["latencyMs"] ); return true; } @@ -54,35 +54,17 @@ public async Task> LoadCheckpointsAsync(int runId, [ ); var checkpoints = await ApiMethod - .GET( + .GET>( string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) ); - if (checkpoints == null || checkpoints.Length == 0) + if (checkpoints == null || checkpoints.Count == 0) return new Dictionary(); - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints.", - nameof(ApiDataAccessService), methodName, checkpoints.Length + _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints for MapTimeId {MapTimeId}.", + nameof(ApiDataAccessService), methodName, checkpoints.Count, runId ); - return checkpoints - .Select(cp => - { - var c = new Checkpoint( - cp.cp, - cp.run_time, - cp.start_vel_x, - cp.start_vel_y, - cp.start_vel_z, - cp.end_vel_x, - cp.end_vel_y, - cp.end_vel_z, - cp.end_touch, - cp.attempts - ); - c.ID = cp.cp; - return c; - }) - .ToDictionary(c => c.CP, c => c); + return checkpoints; } public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") @@ -93,7 +75,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ : string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, pbId.Value); - var apiResult = await ApiMethod.GET(url); + var apiResult = await ApiMethod.GET(url); if (apiResult == null) return null; @@ -101,14 +83,14 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(ApiDataAccessService), methodName ); - return new PersonalBestDataModel(apiResult); + return apiResult; } /* Map.cs */ public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") { - var mapInfo = await ApiMethod.GET( + var mapInfo = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); if (mapInfo != null) @@ -117,7 +99,11 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(ApiDataAccessService), methodName ); - return new MapInfoDataModel(mapInfo); + _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> MapID {MapId}", + nameof(ApiDataAccessService), methodName, mapInfo.ID + ); + + return mapInfo; } return null; @@ -125,23 +111,21 @@ public async Task> LoadCheckpointsAsync(int runId, [ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") { - var apiMapInfo = new API_MapInfo(mapInfo); - - var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, apiMapInfo); + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, mapInfo); - if (postResponse == null || postResponse.last_id <= 0) + if (postResponse == null || postResponse.Id <= 0) { throw new Exception($"API failed to insert map '{mapInfo.Name}'."); } - return postResponse.last_id; + return postResponse.Id; } public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") { - var apiMapInfo = new API_MapInfo(mapInfo); - - var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, apiMapInfo); + var response = await ApiMethod.PUT( + string.Format(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapInfo.ID), mapInfo + ); if (response == null) { throw new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapInfo.ID})."); @@ -150,35 +134,26 @@ public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { - var apiRuns = await ApiMethod.GET( - string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId)); - - var runs = new List(); + var apiRuns = await ApiMethod.GET>( + string.Format(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId))); - if (apiRuns != null) - { - foreach (var time in apiRuns) - { - runs.Add(new MapRecordRunDataModel(time)); - } - } - - return runs; + return apiRuns!; } /* PlayerProfile.cs */ public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { - var player = await ApiMethod.GET( + var player = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); if (player != null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data", - nameof(ApiDataAccessService), methodName + _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data for ProfileID = {ProfileID}", + nameof(ApiDataAccessService), methodName, player.ID ); - return new PlayerProfileDataModel(player); + + return player; } _logger.LogWarning("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> No PlayerProfile data found for {SteamID}", @@ -189,26 +164,25 @@ public async Task> GetMapRecordRunsAsync(int mapId, public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") { - var apiPlayerProfileInfo = new API_PlayerSurfProfile(profile); - - var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, apiPlayerProfileInfo); + var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, profile); - if (postResponse == null || postResponse.last_id <= 0) + if (postResponse == null || postResponse.Id <= 0) { throw new Exception($"API failed to insert Player Profile for '{profile.Name}'."); } - return postResponse.last_id; + return postResponse.Id; } public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") { - var apiPlayerProfileInfo = new API_PlayerSurfProfile(profile); + var response = await ApiMethod.PUT( + string.Format(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, profile.ID), profile + ); - var response = await ApiMethod.PUT(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, apiPlayerProfileInfo); if (response == null) { - throw new Exception($"API failed to update Player Profile for '{apiPlayerProfileInfo.name}' (ID {apiPlayerProfileInfo.id})."); + throw new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {profile.ID})."); } } @@ -216,79 +190,148 @@ public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [Call /* PlayerStats.cs */ public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { - var mapTimes = new List(); - - var apiResponse = await ApiMethod.GET( + var apiResponse = await ApiMethod.GET>( string.Format(Config.API.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) ); - if (apiResponse != null) + if (apiResponse == null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", - nameof(ApiDataAccessService), methodName, playerId, mapId - ); - - foreach (var time in apiResponse) - { - mapTimes.Add(new PlayerMapTimeDataModel(time)); - } + throw new Exception($"API failed to GET MapTime entries for PlayerID = {playerId} and MapID = {mapId}."); } - return mapTimes; - } + _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", + nameof(ApiDataAccessService), methodName, playerId, mapId + ); + return apiResponse; + } /* CurrentRun.cs */ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") { - var apiSaveMapTime = new API_SaveMapTime(mapTime); - /* _logger.LogDebug( - "[{ClassName}] {MethodName} -> Converted and sending API_SaveMapTime:\n" + - " player_id: {PlayerId}\n" + - " map_id: {MapId}\n" + - " run_time: {RunTime}\n" + - " style: {Style}\n" + - " type: {Type}\n" + - " stage: {Stage}\n" + - " start_vel: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + - " end_vel: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + - " replay_frames: {ReplayFramesLength}\n" + - " checkpoints: {CheckpointsCount}\n" + - " run_date: {RunDate}", - nameof(CurrentRun), methodName, - apiSaveMapTime.player_id, - apiSaveMapTime.map_id, - apiSaveMapTime.run_time, - apiSaveMapTime.style, - apiSaveMapTime.type, - apiSaveMapTime.stage, - apiSaveMapTime.start_vel_x, apiSaveMapTime.start_vel_y, apiSaveMapTime.start_vel_z, - apiSaveMapTime.end_vel_x, apiSaveMapTime.end_vel_y, apiSaveMapTime.end_vel_z, - apiSaveMapTime.replay_frames?.Length ?? 0, - apiSaveMapTime.checkpoints?.Count ?? 0, - apiSaveMapTime.run_date ?? 0 + "[{ClassName}] {MethodName} -> Sending MapTimeDataModel:\n" + + " PlayerId: {PlayerId}\n" + + " MapId: {MapId}\n" + + " RunTime: {RunTime}\n" + + " Style: {Style}\n" + + " Type: {Type}\n" + + " Stage: {Stage}\n" + + " Start Velocities: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + + " End Velocities: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + + " ReplayFramesLength: {ReplayFramesLength}\n" + + " ReplayFramesBase64Length: {ReplayFramesLength}\n" + + " CheckpointsCount: {CheckpointsCount}\n", + nameof(ApiDataAccessService), methodName, + mapTime.PlayerId, + mapTime.MapId, + mapTime.RunTime, + mapTime.Style, + mapTime.Type, + mapTime.Stage, + mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, + mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, + mapTime.ReplayFrames?.Length ?? 0, + mapTime.ReplayFrames?.Length ?? 0, + mapTime.Checkpoints?.Count ?? 0 ); + + if (mapTime.Type == 0) + { + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Sending MapTimeDataModel Checkpoint 1:\n" + + " Checkpoints?[1].CP: {CP}\n" + + " Checkpoints?[1].RunTime: {RunTime}\n" + + " Checkpoints?[1].Attempts: {Attempts}\n", + nameof(ApiDataAccessService), methodName, + mapTime.Checkpoints?[1].CP, + mapTime.Checkpoints?[1].RunTime, + mapTime.Checkpoints?[1].Attempts + ); + } */ var postResponse = await ApiMethod.POST( Config.API.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, - apiSaveMapTime + mapTime ); - if (postResponse == null || postResponse.last_id <= 0) + if (postResponse == null || postResponse.Inserted <= 0) { throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); } _logger.LogDebug( "[{ClassName}] {MethodName} -> Successfully inserted entry with id {ID} with type {Type}", - nameof(CurrentRun), methodName, postResponse.last_id, mapTime.Type + nameof(ApiDataAccessService), methodName, postResponse.Id, mapTime.Type + ); + + return postResponse.Id; + } + + public async Task UpdateMapTimeAsync(MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "") + { + /* + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Sending MapTimeDataModel:\n" + + " PlayerId: {PlayerId}\n" + + " MapId: {MapId}\n" + + " RunTime: {RunTime}\n" + + " Style: {Style}\n" + + " Type: {Type}\n" + + " Stage: {Stage}\n" + + " Start Velocities: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + + " End Velocities: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + + " ReplayFramesLength: {ReplayFramesLength}\n" + + " ReplayFramesBase64Length: {ReplayFramesLength}\n" + + " CheckpointsCount: {CheckpointsCount}\n", + nameof(ApiDataAccessService), methodName, + mapTime.PlayerId, + mapTime.MapId, + mapTime.RunTime, + mapTime.Style, + mapTime.Type, + mapTime.Stage, + mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, + mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, + mapTime.ReplayFrames?.Length ?? 0, + mapTime.ReplayFrames?.Length ?? 0, + mapTime.Checkpoints?.Count ?? 0 + ); + + if (mapTime.Type == 0) + { + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Sending MapTimeDataModel Checkpoint 1:\n" + + " Checkpoints?[1].CP: {CP}\n" + + " Checkpoints?[1].RunTime: {RunTime}\n" + + " Checkpoints?[1].Attempts: {Attempts}\n", + nameof(ApiDataAccessService), methodName, + mapTime.Checkpoints?[1].CP, + mapTime.Checkpoints?[1].RunTime, + mapTime.Checkpoints?[1].Attempts + ); + } + */ + + var postResponse = await ApiMethod.PUT( + string.Format(Config.API.Endpoints.ENDPOINT_CR_UPDATE_MAP_TIME, mapTimeId), + mapTime + ); + + if (postResponse == null || postResponse.Inserted <= 0) + { + throw new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); + } + + _logger.LogDebug( + "[{ClassName}] {MethodName} -> Successfully updated MapTime entry {ID} with type {Type}", + nameof(ApiDataAccessService), methodName, mapTimeId, mapTime.Type ); - return postResponse.last_id; + return postResponse.Id; } } } diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs index 45c3e88..157961b 100644 --- a/src/ST-UTILS/Data/IDataAccessService.cs +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -120,12 +120,20 @@ Task> GetPlayerMapTimesAsync( /* CurrentRun.cs */ /// - /// Adds/updates a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 + /// Adds a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 /// /// Data to insert/update in table /// int mapTimeId given by DB Task InsertMapTimeAsync( MapTimeDataModel mapTime, [CallerMemberName] string methodName = "" ); + /// + /// Updates a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 + /// + /// Data to update in table + /// int mapTimeId that was updated + Task UpdateMapTimeAsync( + MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "" + ); } } diff --git a/src/ST-UTILS/Data/Models.cs b/src/ST-UTILS/Data/Models.cs index 34c7eda..db38c73 100644 --- a/src/ST-UTILS/Data/Models.cs +++ b/src/ST-UTILS/Data/Models.cs @@ -1,4 +1,3 @@ -// File: Data/PersonalBestDataModel.cs using System.Data; using MySqlConnector; @@ -6,16 +5,20 @@ namespace SurfTimer.Data { public class PersonalBestDataModel : RunStats { - public int ID { get; set; } + public int PlayerID { get; set; } + public int MapID { get; set; } + // public int ID { get; set; } public int Rank { get; set; } + public PersonalBestDataModel() { } + /// /// Assigns data from API response to the needed data model /// public PersonalBestDataModel(API_PersonalBest data) { ID = data.id; - Ticks = data.run_time; + RunTime = data.run_time; Rank = data.rank; StartVelX = data.start_vel_x; StartVelY = data.start_vel_y; @@ -24,7 +27,7 @@ public PersonalBestDataModel(API_PersonalBest data) EndVelY = data.end_vel_y; EndVelZ = data.end_vel_z; RunDate = data.run_date; - ReplayFramesBase64 = data.replay_frames; + ReplayFrames = data.replay_frames; } /// @@ -33,7 +36,7 @@ public PersonalBestDataModel(API_PersonalBest data) public PersonalBestDataModel(MySqlDataReader data) { ID = data.GetInt32("id"); - Ticks = data.GetInt32("run_time"); + RunTime = data.GetInt32("run_time"); Rank = data.GetInt32("rank"); StartVelX = (float)data.GetDouble("start_vel_x"); StartVelY = (float)data.GetDouble("start_vel_y"); @@ -95,13 +98,17 @@ public MapInfoDataModel(MySqlDataReader data) public class MapRecordRunDataModel : RunStats { - public int ID { get; set; } - public int RunTime { get; set; } + public int PlayerID { get; set; } + public int MapID { get; set; } + public int TotalCount { get; set; } + // public int ID { get; set; } + // public int RunTime { get; set; } public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage public int Stage { get; set; } public int Style { get; set; } public string Name { get; set; } = ""; - public int TotalCount { get; set; } + + public MapRecordRunDataModel() { } /// /// Assigns data from API response to the needed data model @@ -122,7 +129,7 @@ public MapRecordRunDataModel(API_MapTime data) EndVelZ = (float)data.end_vel_z; RunDate = data.run_date; TotalCount = data.total_count; - ReplayFramesBase64 = data.replay_frames; + ReplayFrames = data.replay_frames; } /// @@ -156,7 +163,7 @@ public MapRecordRunDataModel(MySqlDataReader data) EndVelZ = data.GetFloat("end_vel_z"); RunDate = data.GetInt32("run_date"); TotalCount = data.GetInt32("total_count"); - ReplayFramesBase64 = replayFramesBase64; + ReplayFrames = replayFramesBase64; } } @@ -206,13 +213,15 @@ public PlayerProfileDataModel(MySqlDataReader data) public class PlayerMapTimeDataModel : RunStats { - public int ID { get; set; } - public int RunTime { get; set; } + // public int ID { get; set; } + // public int RunTime { get; set; } public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage public int Stage { get; set; } public int Style { get; set; } public int Rank { get; set; } + public PlayerMapTimeDataModel() { } + /// /// Assigns data from API response to the needed data model /// @@ -231,7 +240,7 @@ public PlayerMapTimeDataModel(API_PersonalBest data) EndVelY = (float)data.end_vel_y; EndVelZ = (float)data.end_vel_z; RunDate = data.run_date; - ReplayFramesBase64 = data.replay_frames; + ReplayFrames = data.replay_frames; } /// @@ -264,7 +273,7 @@ public PlayerMapTimeDataModel(MySqlDataReader data) EndVelY = (float)data.GetDouble("end_vel_y"); EndVelZ = (float)data.GetDouble("end_vel_z"); RunDate = data.GetInt32("run_date"); - ReplayFramesBase64 = replayFramesBase64; + ReplayFrames = replayFramesBase64; } } diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index c226a7a..39592bc 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -270,7 +270,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe mapTime.Style, mapTime.Type, mapTime.Stage, - mapTime.Ticks, + mapTime.RunTime, mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, @@ -278,7 +278,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe mapTime.EndVelY, mapTime.EndVelZ, mapTime.RunDate, - mapTime.ReplayFramesBase64) + mapTime.ReplayFrames) ); if (rowsInserted <= 0) @@ -294,7 +294,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe { commands.Add(string.Format( Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, - lastId, cp.CP, cp.Ticks, cp.StartVelX, cp.StartVelY, cp.StartVelZ, + lastId, cp.CP, cp.RunTime, cp.StartVelX, cp.StartVelY, cp.StartVelZ, cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); } await SurfTimer.DB.TransactionAsync(commands); @@ -302,5 +302,10 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe return (int)lastId; } + + public async Task UpdateMapTimeAsync(MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "") + { + throw new NotImplementedException(); + } } } diff --git a/src/ST-UTILS/Structs/QAngle_t.cs b/src/ST-UTILS/Structs/QAngle_t.cs index f00c804..8804022 100644 --- a/src/ST-UTILS/Structs/QAngle_t.cs +++ b/src/ST-UTILS/Structs/QAngle_t.cs @@ -5,7 +5,7 @@ namespace SurfTimer; -struct QAngle_t : IAdditionOperators, +public struct QAngle_t : IAdditionOperators, ISubtractionOperators, IMultiplyOperators, IDivisionOperators diff --git a/src/ST-UTILS/Structs/Vector_t.cs b/src/ST-UTILS/Structs/Vector_t.cs index da8606a..af852d5 100644 --- a/src/ST-UTILS/Structs/Vector_t.cs +++ b/src/ST-UTILS/Structs/Vector_t.cs @@ -4,7 +4,7 @@ namespace SurfTimer; -struct Vector_t : IAdditionOperators, +public struct Vector_t : IAdditionOperators, ISubtractionOperators, IMultiplyOperators, IDivisionOperators diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 8433126..e4c1e87 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -33,6 +33,7 @@ You should have received a copy of the GNU Affero General Public License #define DEBUG +using System.Collections.Concurrent; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; @@ -44,7 +45,7 @@ You should have received a copy of the GNU Affero General Public License namespace SurfTimer; // Gameplan: https://github.com/CS2Surf/Timer/tree/dev/README.md -[MinimumApiVersion(318)] +[MinimumApiVersion(333)] public partial class SurfTimer : BasePlugin { private readonly ILogger _logger; @@ -66,8 +67,8 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) public override string ModuleAuthor => "The CS2 Surf Initiative - github.com/cs2surf"; // Globals - private Dictionary playerList = new Dictionary(); // This can probably be done way better, revisit - internal static TimerDatabase DB = new TimerDatabase(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string + private ConcurrentDictionary playerList = new(); + internal static TimerDatabase DB = new(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string internal static Map CurrentMap = null!; /* ========== MAP START HOOKS ========== */ diff --git a/src/SurfTimer.csproj b/src/SurfTimer.csproj index 84fa216..574b908 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.csproj @@ -13,7 +13,7 @@ - + From f63a9d104d1588a821b9a38a642fcbbfeb820188 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:57:32 +0300 Subject: [PATCH 33/52] Update project structure and build configurations - Replace `SurfTimer.csproj` with `SurfTimer.Plugin.csproj` in `tasks.json` for build configurations. - Add new project entries for `SurfTimer.Plugin`, `SurfTimer.Api`, and `SurfTimer.Shared` in the solution file (`CS2SurfTimer.sln`). - Add a project reference to `SurfTimer.Shared` in `SurfTimer.Plugin.csproj`. --- .vscode/tasks.json | 4 ++-- CS2SurfTimer.sln | 14 +++++++++++++- src/{SurfTimer.csproj => SurfTimer.Plugin.csproj} | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) rename src/{SurfTimer.csproj => SurfTimer.Plugin.csproj} (92%) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 68c000f..c0ede26 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/SurfTimer.csproj", + "${workspaceFolder}/src/SurfTimer.Plugin.csproj", "/property:Configuration=Debug" ], "problemMatcher": "$msCompile" @@ -18,7 +18,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/SurfTimer.csproj", + "${workspaceFolder}/src/SurfTimer.Plugin.csproj", "/property:Configuration=Release" ], "problemMatcher": "$msCompile" diff --git a/CS2SurfTimer.sln b/CS2SurfTimer.sln index ab56b21..55b45d5 100644 --- a/CS2SurfTimer.sln +++ b/CS2SurfTimer.sln @@ -5,7 +5,11 @@ VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A9962DB7-AE8A-4370-B381-19529A91B7EC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SurfTimer", "src\SurfTimer.csproj", "{98841535-B479-49B7-8D35-03786D4C31B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SurfTimer.Plugin", "src\SurfTimer.Plugin.csproj", "{98841535-B479-49B7-8D35-03786D4C31B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurfTimer.Api", "..\SurfTimer.Api\SurfTimer.Api.csproj", "{F53C6067-0A4E-18EF-53CB-69AB97901241}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurfTimer.Shared", "..\SurfTimer.Shared\SurfTimer.Shared.csproj", "{6FA80551-EAFE-9030-69AA-1CEE7BEC44FB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,6 +21,14 @@ Global {98841535-B479-49B7-8D35-03786D4C31B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {98841535-B479-49B7-8D35-03786D4C31B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {98841535-B479-49B7-8D35-03786D4C31B9}.Release|Any CPU.Build.0 = Release|Any CPU + {F53C6067-0A4E-18EF-53CB-69AB97901241}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F53C6067-0A4E-18EF-53CB-69AB97901241}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F53C6067-0A4E-18EF-53CB-69AB97901241}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F53C6067-0A4E-18EF-53CB-69AB97901241}.Release|Any CPU.Build.0 = Release|Any CPU + {6FA80551-EAFE-9030-69AA-1CEE7BEC44FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FA80551-EAFE-9030-69AA-1CEE7BEC44FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FA80551-EAFE-9030-69AA-1CEE7BEC44FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FA80551-EAFE-9030-69AA-1CEE7BEC44FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/SurfTimer.csproj b/src/SurfTimer.Plugin.csproj similarity index 92% rename from src/SurfTimer.csproj rename to src/SurfTimer.Plugin.csproj index 574b908..e6cbeda 100644 --- a/src/SurfTimer.csproj +++ b/src/SurfTimer.Plugin.csproj @@ -20,6 +20,7 @@ + From 0a1e34b1f1fa21be674f472e1057ded82a01b3af Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Sun, 10 Aug 2025 23:37:39 +0300 Subject: [PATCH 34/52] Refactor data models and update API interactions - Introduce new data models from `SurfTimer.Shared` for consistency across `Plugin` and `API` - Remove overlapping models - Update `AccessService`s to match new structures (MySQL **NOT** tested) - `ApiMethod` includes custom JSON serialization option for `ReplayFramesStringConverter` to match with `API` --- src/ST-API/Api.cs | 4 +- src/ST-API/Comms.cs | 31 ++- src/ST-API/Structures.cs | 14 +- src/ST-Commands/MapCommands.cs | 33 +-- src/ST-Commands/PlayerCommands.cs | 10 +- src/ST-Events/ZoneEventHandlers.cs | 9 +- src/ST-Map/Map.cs | 72 ++--- src/ST-Player/PlayerProfile.cs | 28 +- src/ST-Player/PlayerStats/Checkpoint.cs | 30 -- src/ST-Player/PlayerStats/CurrentRun.cs | 13 +- src/ST-Player/PlayerStats/PersonalBest.cs | 12 +- src/ST-Player/PlayerStats/PlayerStats.cs | 5 +- src/ST-Player/PlayerStats/RunStats.cs | 6 +- src/ST-Player/Replay/ReplayRecorder.cs | 4 +- src/ST-UTILS/Data/ApiDataAccessService.cs | 58 ++-- src/ST-UTILS/Data/IDataAccessService.cs | 38 +-- src/ST-UTILS/Data/Models.cs | 288 -------------------- src/ST-UTILS/Data/MySqlDataAccessService.cs | 64 ++--- src/ST-UTILS/Extensions.cs | 2 +- src/SurfTimer.Plugin.csproj | 42 +-- src/SurfTimer.cs | 4 +- 21 files changed, 228 insertions(+), 539 deletions(-) delete mode 100644 src/ST-Player/PlayerStats/Checkpoint.cs delete mode 100644 src/ST-UTILS/Data/Models.cs diff --git a/src/ST-API/Api.cs b/src/ST-API/Api.cs index e399373..b28eb56 100644 --- a/src/ST-API/Api.cs +++ b/src/ST-API/Api.cs @@ -1,3 +1,4 @@ +/* using System.Net.Http.Json; namespace SurfTimer; @@ -38,4 +39,5 @@ public static async Task Api_Save_Stage_MapTime(Player player) // player.Stats.LoadStageTime(player); // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index b22dd8d..d5449e8 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -1,17 +1,29 @@ -using System.Net.Http.Json; -using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Shared.Entities; +using SurfTimer.Shared.JsonConverters; +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using System.Text.Json; namespace SurfTimer; internal class ApiMethod { - private ApiMethod() { } - private static readonly HttpClient _client = new(); private static readonly string base_addr = Config.ApiUrl; + // Custom Converter for ReplayFramesString + private static readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + static ApiMethod() + { + _jsonOptions.Converters.Add(new ReplayFramesStringConverter()); + } + /// /// Executes a GET request to the specified URL and deserializes the response to type T. /// @@ -54,7 +66,8 @@ private ApiMethod() { } throw exception; } - return await response.Content.ReadFromJsonAsync(); + // Input the custom JsonSerializerOptions to handle ReplayFramesString conversion + return await response.Content.ReadFromJsonAsync(_jsonOptions); } catch (Exception ex) { @@ -70,7 +83,7 @@ private ApiMethod() { } /// Relative URL to call /// Request body to send /// API_PostResponseData or null - public static async Task POST(string url, T body, [CallerMemberName] string methodName = "") + public static async Task POST(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -91,7 +104,7 @@ private ApiMethod() { } if (response.IsSuccessStatusCode) { - return await response.Content.ReadFromJsonAsync(); + return await response.Content.ReadFromJsonAsync(); } else { @@ -125,7 +138,7 @@ private ApiMethod() { } /// Relative URL to call /// Request body to send /// API_PostResponseData or null - public static async Task PUT(string url, T body, [CallerMemberName] string methodName = "") + public static async Task PUT(string url, T body, [CallerMemberName] string methodName = "") { var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -146,7 +159,7 @@ private ApiMethod() { } if (response.IsSuccessStatusCode) { - return await response.Content.ReadFromJsonAsync(); + return await response.Content.ReadFromJsonAsync(); } else { diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs index 9901a66..edab792 100644 --- a/src/ST-API/Structures.cs +++ b/src/ST-API/Structures.cs @@ -3,13 +3,14 @@ namespace SurfTimer; -// Map Info structure +/* public class API_PostResponseData { public int Id { get; set; } public int Inserted { get; set; } public bool Trx { get; set; } } +*/ public class API_Checkpoint { @@ -25,6 +26,7 @@ public class API_Checkpoint public int attempts { get; set; } } +/* public class API_CurrentRun { public int player_id { get; set; } @@ -43,7 +45,9 @@ public class API_CurrentRun public string replay_frames { get; set; } = ""; // This needs to be checked touroughly public int? run_date { get; set; } = null; } +*/ +/* public class API_MapInfo { public int id { get; set; } = 0; @@ -78,7 +82,7 @@ public API_MapInfo(MapInfoDataModel data) last_played = data.LastPlayed; } } - +*/ public class API_MapTime { public int id { get; set; } @@ -101,6 +105,7 @@ public class API_MapTime public int total_count { get; set; } } +/* public class API_PlayerSurfProfile { public int id { get; set; } @@ -131,7 +136,9 @@ public API_PlayerSurfProfile(PlayerProfileDataModel data) connections = data.Connections == 0 ? 1 : data.Connections; } } +*/ +/* public class API_PersonalBest { public int id { get; set; } @@ -156,7 +163,9 @@ public class API_PersonalBest [JsonConstructor] public API_PersonalBest() { } // Parameterless constructor used by GET method } +*/ +/* public class API_SaveMapTime { public int player_id { get; set; } @@ -211,3 +220,4 @@ internal API_SaveMapTime(MapTimeDataModel data) }).ToList(); } } +*/ \ No newline at end of file diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 0853cdb..75786a9 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -1,10 +1,10 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Utils; -using CounterStrikeSharp.API.Modules.Admin; -using SurfTimer.Data; +using SurfTimer.Shared.DTO; using System.Text.RegularExpressions; namespace SurfTimer; @@ -59,10 +59,10 @@ public void AddMapTier(CCSPlayerController? player, CommandInfo command) if (player == null) return; - int tier; + short tier; try { - tier = int.Parse(command.ArgByIndex(1)); + tier = short.Parse(command.ArgByIndex(1)); } catch (System.Exception) { @@ -80,11 +80,10 @@ public void AddMapTier(CCSPlayerController? player, CommandInfo command) return; } - var mapInfo = new MapInfoDataModel + var mapInfo = new MapDto { - ID = CurrentMap.ID, - Name = CurrentMap.Name, - Author = CurrentMap.Author, + Name = CurrentMap.Name!, + Author = CurrentMap.Author!, Tier = tier, Stages = CurrentMap.Stages, Bonuses = CurrentMap.Bonuses, @@ -96,7 +95,7 @@ public void AddMapTier(CCSPlayerController? player, CommandInfo command) Task.Run(async () => { - await _dataService!.UpdateMapInfoAsync(mapInfo); + await _dataService!.UpdateMapInfoAsync(mapInfo, CurrentMap.ID); }); string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Tier to {Extensions.GetTierColor(CurrentMap.Tier)}{CurrentMap.Tier}{ChatColors.Default}."; @@ -124,10 +123,9 @@ public void AddMapAuthor(CCSPlayerController? player, CommandInfo command) return; } - var mapInfo = new MapInfoDataModel + var mapInfo = new MapDto { - ID = CurrentMap.ID, - Name = CurrentMap.Name, + Name = CurrentMap.Name!, Author = author, Tier = CurrentMap.Tier, Stages = CurrentMap.Stages, @@ -140,7 +138,7 @@ public void AddMapAuthor(CCSPlayerController? player, CommandInfo command) Task.Run(async () => { - await _dataService!.UpdateMapInfoAsync(mapInfo); + await _dataService!.UpdateMapInfoAsync(mapInfo, CurrentMap.ID); }); string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Author to {ChatColors.Green}{CurrentMap.Author}{ChatColors.Default}."; @@ -162,11 +160,10 @@ public void AddMapRanked(CCSPlayerController? player, CommandInfo command) else CurrentMap.Ranked = true; - var mapInfo = new MapInfoDataModel + var mapInfo = new MapDto { - ID = CurrentMap.ID, - Name = CurrentMap.Name, - Author = CurrentMap.Author, + Name = CurrentMap.Name!, + Author = CurrentMap.Author!, Tier = CurrentMap.Tier, Stages = CurrentMap.Stages, Bonuses = CurrentMap.Bonuses, @@ -176,7 +173,7 @@ public void AddMapRanked(CCSPlayerController? player, CommandInfo command) Task.Run(async () => { - await _dataService!.UpdateMapInfoAsync(mapInfo); + await _dataService!.UpdateMapInfoAsync(mapInfo, CurrentMap.ID); }); string msg = $"{Config.PluginPrefix} {ChatColors.Yellow}{CurrentMap.Name}{ChatColors.Default} - Set Ranked to {(CurrentMap.Ranked ? ChatColors.Green : ChatColors.Red)}{CurrentMap.Ranked}{ChatColors.Default}."; diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 6628823..8873f88 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -764,17 +764,17 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map WR Name: {CurrentMap.WR[0].Name}"); Console.WriteLine($"Map WR Type: {CurrentMap.WR[0].Type}"); Console.WriteLine($"Map WR Rank: {CurrentMap.WR[0].Rank}"); - Console.WriteLine($"Map WR Checkpoints.Count: {CurrentMap.WR[0].Checkpoints.Count}"); - Console.WriteLine($"Map WR ReplayFramesBase64.Length: {CurrentMap.WR[0].ReplayFrames.Length}"); - Console.WriteLine($"Map WR ReplayFrames.Length: {CurrentMap.WR[0].ReplayFrames.Length}"); + Console.WriteLine($"Map WR Checkpoints.Count: {CurrentMap.WR[0].Checkpoints?.Count}"); + Console.WriteLine($"Map WR ReplayFramesBase64.Length: {CurrentMap.WR[0].ReplayFrames?.ToString().Length}"); + Console.WriteLine($"Map WR ReplayFrames.Length: {CurrentMap.WR[0].ReplayFrames?.ToString().Length}"); Console.WriteLine($"Map Stage Completions: {CurrentMap.StageCompletions.Length}"); Console.WriteLine($"Map StageWR ID: {CurrentMap.StageWR[1][0].ID}"); Console.WriteLine($"Map StageWR Name: {CurrentMap.StageWR[1][0].Name}"); Console.WriteLine($"Map StageWR Type: {CurrentMap.StageWR[1][0].Type}"); Console.WriteLine($"Map StageWR Rank: {CurrentMap.StageWR[1][0].Rank}"); - Console.WriteLine($"Map StageWR ReplayFramesBase64.Length: {CurrentMap.StageWR[1][0].ReplayFrames.Length}"); - Console.WriteLine($"Map StageWR ReplayFrames.Length: {CurrentMap.StageWR[1][0].ReplayFrames.Length}"); + Console.WriteLine($"Map StageWR ReplayFramesBase64.Length: {CurrentMap.StageWR[1][0].ReplayFrames?.ToString().Length}"); + Console.WriteLine($"Map StageWR ReplayFrames.Length: {CurrentMap.StageWR[1][0].ReplayFrames?.ToString().Length}"); Console.WriteLine($"Map Bonus Completions: {CurrentMap.BonusCompletions.Length}"); } diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index d78a89c..2724b27 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -1,8 +1,9 @@ -using System.Text.RegularExpressions; +using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; -using CounterStrikeSharp.API; +using SurfTimer.Shared.Entities; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; namespace SurfTimer; @@ -272,7 +273,7 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality if (!player.Stats.ThisRun.Checkpoints.ContainsKey(player.Timer.Checkpoint)) { - Checkpoint cp2 = new Checkpoint(player.Timer.Checkpoint, + var cp2 = new CheckpointEntity(player.Timer.Checkpoint, player.Timer.Ticks, velocity.X, velocity.Y, @@ -324,7 +325,7 @@ private static void StartTouchHandleCheckpointZone(Player player, CBaseTrigger t if (!player.Stats.ThisRun.Checkpoints.ContainsKey(checkpoint)) { // store the checkpoint in the player's current run checkpoints used for Checkpoint functionality - Checkpoint cp2 = new Checkpoint(checkpoint, + var cp2 = new CheckpointEntity(checkpoint, player.Timer.Ticks, velocity.X, velocity.Y, diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 4486ac6..38f9473 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -1,30 +1,23 @@ -using System.Data; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text.Json; -using System.Text.RegularExpressions; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; +using SurfTimer.Shared.Types; +using System.Data; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.RegularExpressions; namespace SurfTimer; -public class Map +public class Map : MapEntity { - // 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) - public string Name { get; set; } = string.Empty; - public string Author { get; set; } = ""; - public int Tier { get; set; } = 0; - public int Stages { get; set; } = 0; public int TotalCheckpoints { get; set; } = 0; - public int Bonuses { get; set; } = 0; - public bool Ranked { get; set; } = false; // Not decided on what to use this for - public int DateAdded { get; set; } = 0; - public int LastPlayed { get; set; } = 0; /// /// Map Completion Count - Refer to as MapCompletions[style] /// @@ -125,7 +118,6 @@ internal async Task InitializeAsync([CallerMemberName] string methodName = "") ); // Initialize ReplayManager with placeholder values - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> InitializeAsync -> Initializing ReplayManager(-1, {this.Stages > 0}, false, null!)"); this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); // Adjust values as needed // Start timing @@ -270,9 +262,9 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") /// internal async Task InsertMapInfo([CallerMemberName] string methodName = "") { - var mapInfo = new MapInfoDataModel + var mapInfo = new MapDto { - Name = this.Name, + Name = this.Name!, Author = "Unknown", // Or set appropriately Tier = this.Tier, Stages = this.Stages, @@ -302,11 +294,10 @@ internal async Task InsertMapInfo([CallerMemberName] string methodName = "") /// internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") { - var mapInfo = new MapInfoDataModel + var mapInfo = new MapDto { - ID = this.ID, - Name = this.Name, - Author = this.Author, + Name = this.Name!, + Author = this.Author!, Tier = this.Tier, Stages = this.Stages, Bonuses = this.Bonuses, @@ -316,7 +307,7 @@ internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") try { - await _dataService.UpdateMapInfoAsync(mapInfo); + await _dataService.UpdateMapInfoAsync(mapInfo, this.ID); #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> Updated map '{Map}' (ID: {ID}).", @@ -342,7 +333,7 @@ internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] strin { bool newMap = false; - var mapInfo = await _dataService.GetMapInfoAsync(this.Name); + var mapInfo = await _dataService.GetMapInfoAsync(this.Name!); if (mapInfo != null) { @@ -381,7 +372,6 @@ internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] strin /// Extracts Map, Bonus, Stage record runs and the total completions for each style. /// (NOT TESTED WITH MORE THAN 1 STYLE) /// For the Map WR it also gets the Checkpoints data. - /// TODO?: Re-do the API with the new query and fix the API assign of values /// internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { @@ -411,7 +401,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") ConnectedMapTimes.Add(run.ID); MapCompletions[run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames!); break; case 1: // Bonus WR data and total completions @@ -427,7 +417,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") BonusWR[run.Stage][run.Style].Name = run.Name; BonusCompletions[run.Stage][run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames!); break; case 2: // Stage WR data and total completions @@ -443,7 +433,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") StageWR[run.Stage][run.Style].Name = run.Name; StageCompletions[run.Stage][run.Style] = run.TotalCount; - SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames); + SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames!); break; } } @@ -477,12 +467,12 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") /// Style to add /// Stage to add /// Base64 encoded string for the replay_frames - internal void SetReplayData(int type, int style, int stage, string replayFramesBase64, [CallerMemberName] string methodName = "") + internal void SetReplayData(int type, int style, int stage, ReplayFramesString replayFramesBase64, [CallerMemberName] string methodName = "") { JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; // Decompress the Base64 string - string json = Compressor.Decompress(replayFramesBase64); + string json = Compressor.Decompress(replayFramesBase64.ToString()); // Deserialize to List List frames = JsonSerializer.Deserialize>(json, options)!; @@ -490,7 +480,6 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB switch (type) { case 0: // Map Replays - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] Setting run {this.WR[style].ID} {PlayerHUD.FormatTime(this.WR[style].Ticks)} (Ticks = {this.WR[style].Ticks}; Frames = {frames.Count}) to `ReplayManager.MapWR`"); _logger.LogTrace("[{ClassName}] {MethodName} -> SetReplayData -> [MapWR] Setting run {RunID} {RunTime} (Ticks = {RunTicks}; Frames = {TotalFrames})", nameof(Map), methodName, this.WR[style].ID, PlayerHUD.FormatTime(this.WR[style].RunTime), this.WR[style].RunTime, frames.Count ); @@ -510,33 +499,33 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB { case ReplayFrameSituation.START_ZONE_ENTER: this.ReplayManager.MapWR.MapSituations.Add(i); - // Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.START_ZONE_EXIT: this.ReplayManager.MapWR.MapSituations.Add(i); - // Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.STAGE_ZONE_ENTER: this.ReplayManager.MapWR.StageEnterSituations.Add(i); - // Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.STAGE_ZONE_EXIT: this.ReplayManager.MapWR.StageExitSituations.Add(i); - // Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: this.ReplayManager.MapWR.CheckpointEnterSituations.Add(i); - // Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: this.ReplayManager.MapWR.CheckpointExitSituations.Add(i); - // Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.END_ZONE_ENTER: - // Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); break; case ReplayFrameSituation.END_ZONE_EXIT: - // Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); + /// Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); break; } } @@ -655,19 +644,16 @@ internal void SetReplayData(int type, int style, int stage, string replayFramesB { if (type == 0 && this.ReplayManager.MapWR != null && !this.ReplayManager.MapWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [MapWR] ResetReplay() and Start()"); this.ReplayManager.MapWR.ResetReplay(); this.ReplayManager.MapWR.Start(); } else if (type == 1 && this.ReplayManager.BonusWR != null && !this.ReplayManager.BonusWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [BonusWR] ResetReplay() and Start() {stage}"); this.ReplayManager.BonusWR.ResetReplay(); this.ReplayManager.BonusWR.Start(); } else if (type == 2 && this.ReplayManager.StageWR != null && !this.ReplayManager.StageWR.IsPlaying) { - // Console.WriteLine($"CS2 Surf DEBUG >> internal class Map -> internal void SetReplayData -> [StageWR] ResetReplay() and Start() {stage}"); this.ReplayManager.StageWR.ResetReplay(); this.ReplayManager.StageWR.Start(); } diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index 35456d6..c905ad8 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -1,19 +1,14 @@ -using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; +using System.Runtime.CompilerServices; namespace SurfTimer; -public class PlayerProfile +public class PlayerProfile : PlayerProfileEntity { - public int ID { get; set; } = 0; - public string Name { get; set; } = ""; - public ulong SteamID { get; set; } = 0; - public string Country { get; set; } = ""; - public int JoinDate { get; set; } = 0; - public int LastSeen { get; set; } = 0; - public int Connections { get; set; } = 0; private readonly ILogger _logger; private readonly IDataAccessService _dataService; @@ -87,11 +82,11 @@ internal async Task GetPlayerProfile([CallerMemberName] string methodName = "") /// internal async Task InsertPlayerProfile([CallerMemberName] string methodName = "") { - var profile = new PlayerProfileDataModel + var profile = new PlayerProfileDto { SteamID = this.SteamID, - Name = this.Name, - Country = this.Country + Name = this.Name!, + Country = this.Country! }; this.ID = await _dataService.InsertPlayerProfileAsync(profile); @@ -110,13 +105,14 @@ internal async Task InsertPlayerProfile([CallerMemberName] string methodName = " internal async Task UpdatePlayerProfile(string name, [CallerMemberName] string methodName = "") { this.Name = name; - await _dataService.UpdatePlayerProfileAsync(new PlayerProfileDataModel + var dto = new PlayerProfileDto { - ID = this.ID, SteamID = this.SteamID, Name = this.Name, - Country = this.Country - }); + Country = this.Country! + }; + + await _dataService.UpdatePlayerProfileAsync(dto, this.ID); #if DEBUG _logger.LogDebug("[{ClassName}] {MethodName} -> UpdatePlayerProfile -> [{ConnType}] Updated player {PlayerName} ({SteamID}) with ID {ProfileID}.", diff --git a/src/ST-Player/PlayerStats/Checkpoint.cs b/src/ST-Player/PlayerStats/Checkpoint.cs deleted file mode 100644 index 0a765e2..0000000 --- a/src/ST-Player/PlayerStats/Checkpoint.cs +++ /dev/null @@ -1,30 +0,0 @@ -public class Checkpoint -{ - public int CP { get; set; } - public int RunTime { 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 EndTouch { get; set; } - public int Attempts { get; set; } - public int ID { get; set; } - - public Checkpoint() { } - - public Checkpoint(int cp, int ticks, float startVelX, float startVelY, float startVelZ, float endVelX, float endVelY, float endVelZ, int endTouch, int attempts) - { - CP = cp; - RunTime = ticks; - StartVelX = startVelX; - StartVelY = startVelY; - StartVelZ = startVelZ; - EndVelX = endVelX; - EndVelY = endVelY; - EndVelZ = endVelZ; - EndTouch = endTouch; - Attempts = attempts; - } -} diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 3c5b91a..10b5691 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -1,16 +1,17 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; using CounterStrikeSharp.API; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.DTO; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace SurfTimer; /// /// This class stores data for the current run. /// -public class CurrentRun : RunStats +public class CurrentRun : RunStats // TODO: Inherit from RunStatsEntity instead of RunStats { private readonly ILogger _logger; private readonly IDataAccessService _dataService; @@ -53,10 +54,10 @@ internal async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int int style = player.Timer.Style; int mapTimeId = 0; - var mapTime = new MapTimeDataModel + var mapTime = new MapTimeRunDataDto { - PlayerId = player.Profile.ID, - MapId = SurfTimer.CurrentMap.ID, + PlayerID = player.Profile.ID, + MapID = SurfTimer.CurrentMap.ID, Style = player.Timer.Style, Type = recType, Stage = stage != 0 ? stage : bonus, diff --git a/src/ST-Player/PlayerStats/PersonalBest.cs b/src/ST-Player/PlayerStats/PersonalBest.cs index 4b8a72b..66be387 100644 --- a/src/ST-Player/PlayerStats/PersonalBest.cs +++ b/src/ST-Player/PlayerStats/PersonalBest.cs @@ -1,7 +1,8 @@ -using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.Entities; +using System.Runtime.CompilerServices; namespace SurfTimer; @@ -9,17 +10,12 @@ namespace SurfTimer; /// As the PersonalBest object is being used for each different style, we shouldn't need a separate `Style` variable in here because each style entry will have unique ID in the Database /// and will therefore be a unique PersonalBest entry. /// -public class PersonalBest : RunStats +public class PersonalBest : MapTimeRunDataEntity { - // public int ID { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public int Rank { get; set; } = -1; // Exclude from constructor, retrieve from Database when loading/saving - public int Type { get; set; } = -1; // Identifies bonus # - 0 for map time -> huh, why o_O? - public string Name { get; set; } = ""; // This is used only for WRs + public Dictionary? Checkpoints { get; set; } private readonly ILogger _logger; private readonly IDataAccessService _dataService; - // Add other properties as needed - // Constructor internal PersonalBest() : base() { // Resolve the logger instance from the DI container diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index b1ecd39..01c3db2 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -1,7 +1,8 @@ -using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.Entities; +using System.Runtime.CompilerServices; namespace SurfTimer; @@ -103,7 +104,7 @@ internal async void LoadMapTime(Player player, int style = 0, [CallerMemberName] foreach (var cp in player_maptime.checkpoints) { - PB[style].Checkpoints[cp.cp] = new Checkpoint(cp.cp, cp.run_time, cp.start_vel_x, cp.start_vel_y, cp.start_vel_z, cp.end_vel_x, cp.end_vel_y, cp.end_vel_z, cp.end_touch, cp.attempts); + PB[style].Checkpoints[cp.cp] = new CheckpointEntity(cp.cp, cp.run_time, cp.start_vel_x, cp.start_vel_y, cp.start_vel_z, cp.end_vel_x, cp.end_vel_y, cp.end_vel_z, cp.end_touch, cp.attempts); } } diff --git a/src/ST-Player/PlayerStats/RunStats.cs b/src/ST-Player/PlayerStats/RunStats.cs index 2f92d16..cfd4067 100644 --- a/src/ST-Player/PlayerStats/RunStats.cs +++ b/src/ST-Player/PlayerStats/RunStats.cs @@ -1,8 +1,10 @@ +using SurfTimer.Shared.Entities; + namespace SurfTimer; public abstract class RunStats { - public Dictionary Checkpoints { get; set; } + public Dictionary Checkpoints { get; set; } public int ID { get; set; } public int RunTime { get; set; } public float StartVelX { get; set; } @@ -16,7 +18,7 @@ public abstract class RunStats protected RunStats() { - Checkpoints = new Dictionary(); + Checkpoints = new Dictionary(); RunTime = 0; StartVelX = 0.0f; StartVelY = 0.0f; diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 35f5b66..311842b 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -1,7 +1,7 @@ -using System.Runtime.CompilerServices; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; +using System.Text.Json; namespace SurfTimer; diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 6a4fb36..d9c509e 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -1,6 +1,8 @@ -using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; +using System.Runtime.CompilerServices; namespace SurfTimer.Data { @@ -47,18 +49,14 @@ public async Task PingAccessService([CallerMemberName] string methodName = } /* PersonalBest.cs */ - public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using API data access service.", - nameof(ApiDataAccessService), methodName - ); - var checkpoints = await ApiMethod - .GET>( + .GET>( string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) ); if (checkpoints == null || checkpoints.Count == 0) - return new Dictionary(); + return new Dictionary(); _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints for MapTimeId {MapTimeId}.", nameof(ApiDataAccessService), methodName, checkpoints.Count, runId @@ -67,7 +65,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ return checkpoints; } - public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") + public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") { string url = pbId == null || pbId == -1 ? string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, @@ -75,7 +73,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ : string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, pbId.Value); - var apiResult = await ApiMethod.GET(url); + var apiResult = await ApiMethod.GET(url); if (apiResult == null) return null; @@ -88,9 +86,9 @@ public async Task> LoadCheckpointsAsync(int runId, [ /* Map.cs */ - public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") { - var mapInfo = await ApiMethod.GET( + var mapInfo = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); if (mapInfo != null) @@ -109,7 +107,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ return null; } - public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") { var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, mapInfo); @@ -121,20 +119,20 @@ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMembe return postResponse.Id; } - public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") { var response = await ApiMethod.PUT( - string.Format(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapInfo.ID), mapInfo + string.Format(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapId), mapInfo ); if (response == null) { - throw new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapInfo.ID})."); + throw new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapId})."); } } - public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { - var apiRuns = await ApiMethod.GET>( + var apiRuns = await ApiMethod.GET>( string.Format(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId))); return apiRuns!; @@ -142,9 +140,9 @@ public async Task> GetMapRecordRunsAsync(int mapId, /* PlayerProfile.cs */ - public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { - var player = await ApiMethod.GET( + var player = await ApiMethod.GET( string.Format(Config.API.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); if (player != null) @@ -162,7 +160,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, return null; } - public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [CallerMemberName] string methodName = "") { var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, profile); @@ -174,23 +172,23 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, return postResponse.Id; } - public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") { var response = await ApiMethod.PUT( - string.Format(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, profile.ID), profile + string.Format(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, playerId), profile ); if (response == null) { - throw new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {profile.ID})."); + throw new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {playerId})."); } } /* PlayerStats.cs */ - public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { - var apiResponse = await ApiMethod.GET>( + var apiResponse = await ApiMethod.GET>( string.Format(Config.API.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) ); @@ -208,7 +206,7 @@ public async Task> GetPlayerMapTimesAsync(int playe /* CurrentRun.cs */ - public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") + public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "") { /* _logger.LogDebug( @@ -260,7 +258,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe if (postResponse == null || postResponse.Inserted <= 0) { - throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); + throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}."); } _logger.LogDebug( @@ -271,7 +269,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe return postResponse.Id; } - public async Task UpdateMapTimeAsync(MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "") + public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "") { /* _logger.LogDebug( @@ -323,7 +321,7 @@ public async Task UpdateMapTimeAsync(MapTimeDataModel mapTime, int mapTimeI if (postResponse == null || postResponse.Inserted <= 0) { - throw new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerId}' on Map ID '{mapTime.MapId}'."); + throw new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); } _logger.LogDebug( diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs index 157961b..35d1681 100644 --- a/src/ST-UTILS/Data/IDataAccessService.cs +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -1,3 +1,5 @@ +using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; using System.Runtime.CompilerServices; namespace SurfTimer.Data @@ -19,8 +21,8 @@ public interface IDataAccessService /// Bonus and Stage runs should NOT have any checkpoints. /// /// ID of the run from DB - /// Dictionary[int, Checkpoint] data or NULL if none found - Task> LoadCheckpointsAsync( + /// Dictionary[int, CheckpointEntity] data or NULL if none found + Task> LoadCheckpointsAsync( int runId, [CallerMemberName] string methodName = "" ); @@ -35,8 +37,8 @@ Task> LoadCheckpointsAsync( /// ID of the map from DB. If pbId is null or -1 /// Run Type (0 = Map ; 1 = Bonus ; 2 = Stage). If pbId is null or -1 /// If pbId is null or -1 - /// PersonalBestDataModel data or null if not found - Task LoadPersonalBestRunAsync( + /// MapTimeRunDataEntity data or null if not found + Task LoadPersonalBestRunAsync( int? pbId, int playerId, int mapId, @@ -51,8 +53,8 @@ Task> LoadCheckpointsAsync( /// Retrieves Map table entry for map through API or MySQL. /// /// Name of map - /// MapInfoDataModel data - Task GetMapInfoAsync( + /// MapEntity data + Task GetMapInfoAsync( string mapName, [CallerMemberName] string methodName = "" ); /// @@ -61,21 +63,21 @@ Task> LoadCheckpointsAsync( /// Data to add in table /// int mapId Task InsertMapInfoAsync( - MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" + MapDto mapInfo, [CallerMemberName] string methodName = "" ); /// /// Updates Map table entry for map through API or MySQL. /// /// Data to update in table Task UpdateMapInfoAsync( - MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "" + MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "" ); /// /// Retrieves MapTime table record runs for given mapId through API or MySQL. /// /// ID from DB - /// List[MapRecordRunDataModel] data - Task> GetMapRecordRunsAsync( + /// List[MapTimeRunDataEntity] data + Task> GetMapRecordRunsAsync( int mapId, [CallerMemberName] string methodName = "" ); @@ -85,8 +87,8 @@ Task> GetMapRecordRunsAsync( /// Retrieve Player table entry for the player through API or MySQL. /// /// SteamID for the player - /// PlayerProfileDataModel data - Task GetPlayerProfileAsync( + /// PlayerProfileEntity data + Task GetPlayerProfileAsync( ulong steamId, [CallerMemberName] string methodName = "" ); /// @@ -95,14 +97,14 @@ Task> GetMapRecordRunsAsync( /// Data to add in table /// int playerId given by DB Task InsertPlayerProfileAsync( - PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" + PlayerProfileDto profile, [CallerMemberName] string methodName = "" ); /// /// Updates Player table entry for the player through API or MySQL. /// /// Data to update in table Task UpdatePlayerProfileAsync( - PlayerProfileDataModel profile, [CallerMemberName] string methodName = "" + PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "" ); @@ -112,8 +114,8 @@ Task UpdatePlayerProfileAsync( /// /// ID from DB /// ID from DB - /// List[PlayerMapTimeDataModel] data - Task> GetPlayerMapTimesAsync( + /// List[MapTimeRunDataEntity] data + Task> GetPlayerMapTimesAsync( int playerId, int mapId, [CallerMemberName] string methodName = "" ); @@ -125,7 +127,7 @@ Task> GetPlayerMapTimesAsync( /// Data to insert/update in table /// int mapTimeId given by DB Task InsertMapTimeAsync( - MapTimeDataModel mapTime, [CallerMemberName] string methodName = "" + MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "" ); /// /// Updates a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 @@ -133,7 +135,7 @@ Task InsertMapTimeAsync( /// Data to update in table /// int mapTimeId that was updated Task UpdateMapTimeAsync( - MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "" + MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "" ); } } diff --git a/src/ST-UTILS/Data/Models.cs b/src/ST-UTILS/Data/Models.cs deleted file mode 100644 index db38c73..0000000 --- a/src/ST-UTILS/Data/Models.cs +++ /dev/null @@ -1,288 +0,0 @@ -using System.Data; -using MySqlConnector; - -namespace SurfTimer.Data -{ - public class PersonalBestDataModel : RunStats - { - public int PlayerID { get; set; } - public int MapID { get; set; } - // public int ID { get; set; } - public int Rank { get; set; } - - public PersonalBestDataModel() { } - - /// - /// Assigns data from API response to the needed data model - /// - public PersonalBestDataModel(API_PersonalBest data) - { - ID = data.id; - RunTime = data.run_time; - Rank = data.rank; - StartVelX = data.start_vel_x; - StartVelY = data.start_vel_y; - StartVelZ = data.start_vel_z; - EndVelX = data.end_vel_x; - EndVelY = data.end_vel_y; - EndVelZ = data.end_vel_z; - RunDate = data.run_date; - ReplayFrames = data.replay_frames; - } - - /// - /// Assigns data from MySqlDataReader (MySQL query) to the needed data model - /// - public PersonalBestDataModel(MySqlDataReader data) - { - ID = data.GetInt32("id"); - RunTime = data.GetInt32("run_time"); - Rank = data.GetInt32("rank"); - StartVelX = (float)data.GetDouble("start_vel_x"); - StartVelY = (float)data.GetDouble("start_vel_y"); - StartVelZ = (float)data.GetDouble("start_vel_z"); - EndVelX = (float)data.GetDouble("end_vel_x"); - EndVelY = (float)data.GetDouble("end_vel_y"); - EndVelZ = (float)data.GetDouble("end_vel_z"); - RunDate = data.GetInt32("run_date"); - } - } - - public class MapInfoDataModel - { - public int ID { get; set; } - public string Name { get; set; } = "N/A"; - public string Author { get; set; } = "Unknown"; - public int Tier { get; set; } - public int Stages { get; set; } - public int Bonuses { get; set; } - public bool Ranked { get; set; } - public int DateAdded { get; set; } - public int LastPlayed { get; set; } - - /// - /// Parameterless constructor for manual assigning of data - /// - public MapInfoDataModel() { } - - /// - /// Assigns data from API response to the needed data model - /// - public MapInfoDataModel(API_MapInfo data) - { - ID = data.id; - Name = data.name; - Author = data.author; - Tier = data.tier; - Stages = data.stages; - Bonuses = data.bonuses; - Ranked = data.ranked == 1; - DateAdded = data.date_added ?? 0; - LastPlayed = data.last_played ?? 0; - } - - /// - /// Assigns data from MySqlDataReader (MySQL query) to the needed data model - /// - public MapInfoDataModel(MySqlDataReader data) - { - ID = data.GetInt32("id"); - Name = data.GetString("name"); - Author = data.GetString("author") ?? "Unknown"; - Tier = data.GetInt32("tier"); - Ranked = data.GetBoolean("ranked"); - DateAdded = data.GetInt32("date_added"); - LastPlayed = data.GetInt32("last_played"); - } - } - - public class MapRecordRunDataModel : RunStats - { - public int PlayerID { get; set; } - public int MapID { get; set; } - public int TotalCount { get; set; } - // public int ID { get; set; } - // public int RunTime { get; set; } - public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage - public int Stage { get; set; } - public int Style { get; set; } - public string Name { get; set; } = ""; - - public MapRecordRunDataModel() { } - - /// - /// Assigns data from API response to the needed data model - /// - public MapRecordRunDataModel(API_MapTime data) - { - ID = data.id; - RunTime = data.run_time; - Type = data.type; - Stage = data.stage; - Style = data.style; - Name = data.name; - StartVelX = (float)data.start_vel_x; - StartVelY = (float)data.start_vel_y; - StartVelZ = (float)data.start_vel_z; - EndVelX = (float)data.end_vel_x; - EndVelY = (float)data.end_vel_y; - EndVelZ = (float)data.end_vel_z; - RunDate = data.run_date; - TotalCount = data.total_count; - ReplayFrames = data.replay_frames; - } - - /// - /// Assigns data from MySqlDataReader (MySQL query) to the needed data model - /// - public MapRecordRunDataModel(MySqlDataReader data) - { - string replayFramesBase64; - - try - { - replayFramesBase64 = data.GetString("replay_frames"); - } - catch (InvalidCastException) - { - byte[] replayFramesData = data.GetFieldValue("replay_frames"); - replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); - } - - ID = data.GetInt32("id"); - RunTime = data.GetInt32("run_time"); - Type = data.GetInt32("type"); - Stage = data.GetInt32("stage"); - Style = data.GetInt32("style"); - Name = data.GetString("name"); - StartVelX = data.GetFloat("start_vel_x"); - StartVelY = data.GetFloat("start_vel_y"); - StartVelZ = data.GetFloat("start_vel_z"); - EndVelX = data.GetFloat("end_vel_x"); - EndVelY = data.GetFloat("end_vel_y"); - EndVelZ = data.GetFloat("end_vel_z"); - RunDate = data.GetInt32("run_date"); - TotalCount = data.GetInt32("total_count"); - ReplayFrames = replayFramesBase64; - } - } - - public class PlayerProfileDataModel - { - public int ID { get; set; } = 0; - public string Name { get; set; } = ""; - public ulong SteamID { get; set; } = 0; - public string Country { get; set; } = ""; - public int JoinDate { get; set; } = 0; - public int LastSeen { get; set; } = 0; - public int Connections { get; set; } = 0; - - /// - /// Parameterless constructor for manual assigning of data - /// - public PlayerProfileDataModel() { } - - /// - /// Assigns data from API response to the needed data model - /// - public PlayerProfileDataModel(API_PlayerSurfProfile data) - { - ID = data.id; - // SteamID = steamId; - Name = data.name; - Country = data.country; - JoinDate = data.join_date; - LastSeen = data.last_seen; - Connections = data.connections; - } - - /// - /// Assigns data from MySqlDataReader (MySQL query) to the needed data model - /// - public PlayerProfileDataModel(MySqlDataReader data) - { - ID = data.GetInt32("id"); - // SteamID = data.GetString("steam_id"); - Name = data.GetString("name"); - Country = data.GetString("country"); - JoinDate = data.GetInt32("join_date"); - LastSeen = data.GetInt32("last_seen"); - Connections = data.GetInt32("connections"); - } - } - - public class PlayerMapTimeDataModel : RunStats - { - // public int ID { get; set; } - // public int RunTime { get; set; } - public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage - public int Stage { get; set; } - public int Style { get; set; } - public int Rank { get; set; } - - public PlayerMapTimeDataModel() { } - - /// - /// Assigns data from API response to the needed data model - /// - public PlayerMapTimeDataModel(API_PersonalBest data) - { - ID = data.id; - RunTime = data.run_time; - Type = data.type; - Stage = data.stage; - Style = data.style; - Rank = data.rank; - StartVelX = (float)data.start_vel_x; - StartVelY = (float)data.start_vel_y; - StartVelZ = (float)data.start_vel_z; - EndVelX = (float)data.end_vel_x; - EndVelY = (float)data.end_vel_y; - EndVelZ = (float)data.end_vel_z; - RunDate = data.run_date; - ReplayFrames = data.replay_frames; - } - - /// - /// Assigns data from MySqlDataReader (MySQL query) to the needed data model - /// - public PlayerMapTimeDataModel(MySqlDataReader data) - { - string replayFramesBase64; - - try - { - replayFramesBase64 = data.GetString("replay_frames"); - } - catch (InvalidCastException) - { - byte[] replayFramesData = data.GetFieldValue("replay_frames"); - replayFramesBase64 = System.Text.Encoding.UTF8.GetString(replayFramesData); - } - - ID = data.GetInt32("id"); - RunTime = data.GetInt32("run_time"); - Type = data.GetInt32("type"); - Stage = data.GetInt32("stage"); - Style = data.GetInt32("style"); - Rank = data.GetInt32("rank"); - StartVelX = (float)data.GetDouble("start_vel_x"); - StartVelY = (float)data.GetDouble("start_vel_y"); - StartVelZ = (float)data.GetDouble("start_vel_z"); - EndVelX = (float)data.GetDouble("end_vel_x"); - EndVelY = (float)data.GetDouble("end_vel_y"); - EndVelZ = (float)data.GetDouble("end_vel_z"); - RunDate = data.GetInt32("run_date"); - ReplayFrames = replayFramesBase64; - } - } - - public class MapTimeDataModel : RunStats - { - public int PlayerId { get; set; } - public int MapId { get; set; } - public int Style { get; set; } - public int Type { get; set; } // 0 = Map, 1 = Bonus, 2 = Stage - public int Stage { get; set; } - } -} diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index 39592bc..3ee5247 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -1,6 +1,8 @@ -using MySqlConnector; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MySqlConnector; +using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; using System.Runtime.CompilerServices; namespace SurfTimer.Data @@ -42,13 +44,13 @@ public async Task PingAccessService([CallerMemberName] string methodName = } /* PersonalBest.cs */ - public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using MySQL data access service.", nameof(MySqlDataAccessService), methodName ); - var dict = new Dictionary(); + var dict = new Dictionary(); using (var results = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, runId))) @@ -56,9 +58,9 @@ public async Task> LoadCheckpointsAsync(int runId, [ if (results == null || !results.HasRows) return dict; - while (results.Read()) + while (await results.ReadAsync()) { - var cp = new Checkpoint( + var cp = new CheckpointEntity( results.GetInt32("cp"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), @@ -70,7 +72,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ results.GetInt32("end_touch"), results.GetInt32("attempts") ); - cp.ID = results.GetInt32("cp"); + cp.MapTimeID = results.GetInt32("maptime_id"); dict[cp.CP] = cp; } } @@ -82,7 +84,7 @@ public async Task> LoadCheckpointsAsync(int runId, [ return dict; } - public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") + public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") { _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Using MySQL data access service.", nameof(MySqlDataAccessService), methodName @@ -110,12 +112,12 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style ); - return new PersonalBestDataModel(results); + return new MapTimeRunDataEntity(results); } /* Map.cs */ - public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") { using var mapData = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(mapName))); @@ -126,13 +128,13 @@ public async Task> LoadCheckpointsAsync(int runId, [ nameof(MySqlDataAccessService), methodName ); - return new MapInfoDataModel(mapData); + return new MapEntity(mapData); } return null; } - public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") { var (rowsWritten, lastId) = await SurfTimer.DB.WriteAsync( string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, @@ -153,7 +155,7 @@ public async Task InsertMapInfoAsync(MapInfoDataModel mapInfo, [CallerMembe return (int)lastId; } - public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName] string methodName = "") + public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") { string updateQuery = string.Format( Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, @@ -163,19 +165,19 @@ public async Task UpdateMapInfoAsync(MapInfoDataModel mapInfo, [CallerMemberName mapInfo.Author, mapInfo.Tier, mapInfo.Ranked ? 1 : 0, // Table is using TINYINT so we need to send 0 or 1 instead of Boolean - mapInfo.ID + mapId ); var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(updateQuery); if (rowsUpdated != 1) { - throw new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapInfo.ID}) in database."); + throw new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapId}) in database."); } } - public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { - var runs = new List(); + var runs = new List(); using var results = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, mapId)); @@ -184,7 +186,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, { while (results.Read()) { - runs.Add(new MapRecordRunDataModel(results)); + runs.Add(new MapTimeRunDataEntity(results)); } } @@ -193,20 +195,20 @@ public async Task> GetMapRecordRunsAsync(int mapId, /* PlayerProfile.cs */ - public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { using var playerData = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, steamId)); if (playerData.HasRows && playerData.Read()) { - return new PlayerProfileDataModel(playerData); + return new PlayerProfileEntity(playerData); } return null; } - public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [CallerMemberName] string methodName = "") { int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync(string.Format( @@ -224,14 +226,14 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDataModel profile, return (int)lastId; } - public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [CallerMemberName] string methodName = "") + public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") { int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(string.Format( Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, profile.Country, lastSeen, - profile.ID, + playerId, MySqlConnector.MySqlHelper.EscapeString(profile.Name))); if (rowsUpdated != 1) @@ -240,9 +242,9 @@ public async Task UpdatePlayerProfileAsync(PlayerProfileDataModel profile, [Call /* PlayerStats.cs */ - public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { - var mapTimes = new List(); + var mapTimes = new List(); using var results = await SurfTimer.DB.QueryAsync( string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, playerId, mapId)); @@ -251,7 +253,7 @@ public async Task> GetPlayerMapTimesAsync(int playe { while (results.Read()) { - mapTimes.Add(new PlayerMapTimeDataModel(results)); + mapTimes.Add(new MapTimeRunDataEntity(results)); } } @@ -260,13 +262,13 @@ public async Task> GetPlayerMapTimesAsync(int playe /* CurrentRun.cs */ - public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMemberName] string methodName = "") + public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "") { var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync( string.Format( Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, - mapTime.PlayerId, - mapTime.MapId, + mapTime.PlayerID, + mapTime.MapID, mapTime.Style, mapTime.Type, mapTime.Stage, @@ -283,7 +285,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe if (rowsInserted <= 0) { - throw new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerId}."); + throw new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerID}."); } // Write the checkpoints after we have the `lastId` @@ -303,7 +305,7 @@ public async Task InsertMapTimeAsync(MapTimeDataModel mapTime, [CallerMembe return (int)lastId; } - public async Task UpdateMapTimeAsync(MapTimeDataModel mapTime, int mapTimeId, [CallerMemberName] string methodName = "") + public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "") { throw new NotImplementedException(); } diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 6195228..0567739 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -60,7 +60,7 @@ public static void SetCollisionGroup(this CCSPlayerController controller, Collis /// /// Map Tier up to 8 /// Appropriate ChatColor value for the Tier - public static char GetTierColor(int tier) + public static char GetTierColor(short tier) { return tier switch { diff --git a/src/SurfTimer.Plugin.csproj b/src/SurfTimer.Plugin.csproj index e6cbeda..fc8784f 100644 --- a/src/SurfTimer.Plugin.csproj +++ b/src/SurfTimer.Plugin.csproj @@ -1,26 +1,26 @@ - + - - net8.0 - enable - enable - true - + + net8.0 + enable + enable + true + - - - DEBUG - + + + DEBUG + - - - - - - - - - - + + + + + + + + + + diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index e4c1e87..6c15758 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -33,7 +33,6 @@ You should have received a copy of the GNU Affero General Public License #define DEBUG -using System.Collections.Concurrent; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; @@ -41,6 +40,7 @@ You should have received a copy of the GNU Affero General Public License using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using System.Collections.Concurrent; namespace SurfTimer; @@ -75,7 +75,7 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) public void OnMapStart(string mapName) { // Initialise Map Object - if ((CurrentMap == null || !CurrentMap.Name.Equals(mapName)) && mapName.Contains("surf_")) + if ((CurrentMap == null || CurrentMap.Name.Equals(mapName)) && mapName.Contains("surf_")) { Server.NextWorldUpdate(() => Console.WriteLine(String.Format(" ____________ ____ ___\n" + " / ___/ __/_ | / __/_ ______/ _/\n" From 99b7802918e0a82e3a851e213ce2f37db3f75231 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:39:58 +0300 Subject: [PATCH 35/52] Cleanup, error handling refactoring, naming convention. - Fully use SurfTimer.Shared entities - Delete unused files and methods - Get rid of some comments and messages - Rename classes to follow PascalCase - Better(?) error handling - Change types to match with DB - Start using `timer_settings.json` (does NOT stop replays atm) --- cfg/SurfTimer/timer_settings.json | 5 +- src/ST-API/Api.cs | 43 ---- src/ST-API/JsonConverters.cs | 69 +++--- src/ST-API/Schema.cs | 1 - src/ST-API/Structures.cs | 223 -------------------- src/ST-Commands/MapCommands.cs | 12 +- src/ST-Commands/PlayerCommands.cs | 174 ++------------- src/ST-DB/DB.cs | 14 +- src/ST-Events/Players.cs | 10 +- src/ST-Events/Tick.cs | 6 +- src/ST-Events/TriggerEndTouch.cs | 6 +- src/ST-Events/TriggerStartTouch.cs | 1 - src/ST-Events/ZoneEventHandlers.cs | 97 ++++----- src/ST-Map/Map.cs | 100 ++++----- src/ST-Player/Player.cs | 4 +- src/ST-Player/PlayerHUD.cs | 26 ++- src/ST-Player/PlayerProfile.cs | 2 +- src/ST-Player/PlayerStats/CurrentRun.cs | 72 ++++--- src/ST-Player/PlayerStats/PlayerStats.cs | 107 +--------- src/ST-Player/PlayerStats/RunStats.cs | 41 ---- src/ST-Player/PlayerTimer.cs | 10 +- src/ST-Player/Replay/ReplayFrame.cs | 8 +- src/ST-Player/Replay/ReplayPlayer.cs | 32 ++- src/ST-Player/Replay/ReplayRecorder.cs | 113 ++-------- src/ST-Player/Saveloc/SavelocFrame.cs | 8 +- src/{ST-API => ST-UTILS}/ConVar.cs | 0 src/ST-UTILS/Config.cs | 31 +-- src/ST-UTILS/Data/ApiDataAccessService.cs | 49 +++-- src/ST-UTILS/Data/MySqlDataAccessService.cs | 62 +++--- src/ST-UTILS/Extensions.cs | 10 +- src/ST-UTILS/Injection.cs | 8 +- src/ST-UTILS/Structs/QAngleT.cs | 143 +++++++++++++ src/ST-UTILS/Structs/QAngle_t.cs | 123 ----------- src/ST-UTILS/Structs/VectorT.cs | 144 +++++++++++++ src/ST-UTILS/Structs/Vector_t.cs | 124 ----------- src/SurfTimer.cs | 10 +- src/bin/MySqlConnector.dll | Bin 838504 -> 857448 bytes 37 files changed, 643 insertions(+), 1245 deletions(-) delete mode 100644 src/ST-API/Api.cs delete mode 100644 src/ST-API/Structures.cs delete mode 100644 src/ST-Player/PlayerStats/RunStats.cs rename src/{ST-API => ST-UTILS}/ConVar.cs (100%) create mode 100644 src/ST-UTILS/Structs/QAngleT.cs delete mode 100644 src/ST-UTILS/Structs/QAngle_t.cs create mode 100644 src/ST-UTILS/Structs/VectorT.cs delete mode 100644 src/ST-UTILS/Structs/Vector_t.cs diff --git a/cfg/SurfTimer/timer_settings.json b/cfg/SurfTimer/timer_settings.json index 9e26dfe..77f73cb 100644 --- a/cfg/SurfTimer/timer_settings.json +++ b/cfg/SurfTimer/timer_settings.json @@ -1 +1,4 @@ -{} \ No newline at end of file +{ + "replays_enabled": true, + "replays_pre": 64 +} \ No newline at end of file diff --git a/src/ST-API/Api.cs b/src/ST-API/Api.cs deleted file mode 100644 index b28eb56..0000000 --- a/src/ST-API/Api.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -using System.Net.Http.Json; - -namespace SurfTimer; - -internal class ApiCall -{ - public static async Task Api_Save_Stage_MapTime(Player player) - { - // This is a trick to record the time before the player exits the start zone - int last_exit_tick = player.ReplayRecorder.LastExitTick(); - int last_enter_tick = player.ReplayRecorder.LastEnterTick(); - - // player.Controller.PrintToChat($"CS2 Surf DEBUG >> OnTriggerStartTouch -> Last Exit Tick: {last_exit_tick} | Current Frame: {player.ReplayRecorder.Frames.Count}"); - - int stage_run_time = player.ReplayRecorder.Frames.Count - 1 - last_exit_tick; // Would like some check on this - int time_since_last_enter = player.ReplayRecorder.Frames.Count - 1 - last_enter_tick; - - int tt = -1; - if (last_exit_tick - last_enter_tick > 2 * 64) - tt = last_exit_tick - 2 * 64; - else - tt = last_enter_tick; - - API_CurrentRun stage_time = new() - { - player_id = player.Profile.ID, - map_id = SurfTimer.CurrentMap.ID, - style = player.Timer.Style, - type = 2, - stage = player.Timer.Stage - 1, - run_time = stage_run_time, - run_date = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - replay_frames = player.ReplayRecorder.SerializeReplayPortion(tt, time_since_last_enter) - - }; - - await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_CR_SAVE_STAGE_TIME, stage_time); - // player.Stats.LoadStageTime(player); - // await CurrentMap.ApiGetMapRecordAndTotals(); // Reload the Map record and totals for the HUD - } -} -*/ \ No newline at end of file diff --git a/src/ST-API/JsonConverters.cs b/src/ST-API/JsonConverters.cs index 89e85bd..475a5bd 100644 --- a/src/ST-API/JsonConverters.cs +++ b/src/ST-API/JsonConverters.cs @@ -27,9 +27,9 @@ public static Dictionary ConstructJsonDictFromString(string str) } } -internal class Vector_tConverter : JsonConverter +internal class VectorTConverter : JsonConverter { - public override Vector_t Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override VectorT 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) @@ -62,10 +62,10 @@ public override Vector_t Read(ref Utf8JsonReader reader, Type typeToConvert, Jso } } - return new Vector_t { X = x, Y = y, Z = z }; + return new VectorT { X = x, Y = y, Z = z }; } - public override void Write(Utf8JsonWriter writer, Vector_t value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, VectorT value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("X", value.X); @@ -75,9 +75,9 @@ public override void Write(Utf8JsonWriter writer, Vector_t value, JsonSerializer } } -internal class QAngle_tConverter : JsonConverter +internal class QAngleTConverter : JsonConverter { - public override QAngle_t Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override QAngleT 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) @@ -110,10 +110,10 @@ public override QAngle_t Read(ref Utf8JsonReader reader, Type typeToConvert, Jso } } - return new QAngle_t { X = X, Y = Y, Z = Z }; + return new QAngleT { X = X, Y = Y, Z = Z }; } - public override void Write(Utf8JsonWriter writer, QAngle_t value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, QAngleT value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("X", value.X); @@ -125,11 +125,22 @@ public override void Write(Utf8JsonWriter writer, QAngle_t value, JsonSerializer internal static class Compressor { - public static string Decompress(string input) + public static byte[] Compress(byte[] input) { - byte[] compressed = Convert.FromBase64String(input); - byte[] decompressed = Decompress(compressed); - return Encoding.UTF8.GetString(decompressed); + 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(); + } } public static string Compress(string input) @@ -144,42 +155,28 @@ public static byte[] Decompress(byte[] input) using (var source = new MemoryStream(input)) { byte[] lengthBytes = new byte[4]; - source.Read(lengthBytes, 0, 4); + int bytesRead = source.Read(lengthBytes, 0, 4); + if (bytesRead != 4) + throw new InvalidDataException("Failed to read the expected length prefix."); 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) + int totalRead = 0, read; + while ((read = decompressionStream.Read(result, totalRead, length - totalRead)) > 0) { - totalRead += bytesRead; + totalRead += read; } 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(); - } - } - - internal static string Decompress(byte v) + public static string Decompress(string input) { - throw new NotImplementedException(); + byte[] compressed = Convert.FromBase64String(input); + byte[] decompressed = Decompress(compressed); + return Encoding.UTF8.GetString(decompressed); } } \ No newline at end of file diff --git a/src/ST-API/Schema.cs b/src/ST-API/Schema.cs index ba324df..3b79a01 100644 --- a/src/ST-API/Schema.cs +++ b/src/ST-API/Schema.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Memory; - using System.Runtime.CompilerServices; using System.Text; diff --git a/src/ST-API/Structures.cs b/src/ST-API/Structures.cs deleted file mode 100644 index edab792..0000000 --- a/src/ST-API/Structures.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System.Text.Json.Serialization; -using SurfTimer.Data; - -namespace SurfTimer; - -/* -public class API_PostResponseData -{ - public int Id { get; set; } - public int Inserted { get; set; } - public bool Trx { get; set; } -} -*/ - -public class API_Checkpoint -{ - public int cp { get; set; } - public int run_time { get; set; } - public float start_vel_x { get; set; } - public float start_vel_y { get; set; } - public float start_vel_z { get; set; } - public float end_vel_x { get; set; } - public float end_vel_y { get; set; } - public float end_vel_z { get; set; } - public int end_touch { get; set; } - public int attempts { get; set; } -} - -/* -public class API_CurrentRun -{ - public int player_id { get; set; } - public int map_id { get; set; } - public int run_time { get; set; } - public float start_vel_x { get; set; } - public float start_vel_y { get; set; } - public float start_vel_z { get; set; } - public float end_vel_x { get; set; } - public float end_vel_y { get; set; } - public float end_vel_z { get; set; } - public int style { get; set; } = 0; - public int type { get; set; } = 0; - public int stage { get; set; } = 0; - public List? checkpoints { get; set; } = null; - public string replay_frames { get; set; } = ""; // This needs to be checked touroughly - public int? run_date { get; set; } = null; -} -*/ - -/* -public class API_MapInfo -{ - public int id { get; set; } = 0; - public string name { get; set; } = "N/A"; - public string author { get; set; } = "Unknown"; - public int tier { get; set; } = 0; - public int stages { get; set; } = 0; - public int bonuses { get; set; } = 0; - public int ranked { get; set; } = 0; - public int? date_added { get; set; } = null; - public int? last_played { get; set; } = null; - - [JsonConstructor] - /// - /// Parameterless constructor for manual assigning of data - /// - public API_MapInfo() { } - - /// - /// Assigns values to the needed data model for an API request - /// - public API_MapInfo(MapInfoDataModel data) - { - id = data.ID; - name = data.Name; - author = data.Author; - tier = data.Tier; - stages = data.Stages; - bonuses = data.Bonuses; - ranked = data.Ranked ? 1 : 0; - date_added = data.DateAdded; - last_played = data.LastPlayed; - } -} -*/ -public class API_MapTime -{ - public int id { get; set; } - public int player_id { get; set; } - public int map_id { get; set; } - public int style { get; set; } = 0; - public int type { get; set; } = 0; - public int stage { get; set; } = 0; - public int run_time { get; set; } - public float start_vel_x { get; set; } - public float start_vel_y { get; set; } - public float start_vel_z { get; set; } - public float end_vel_x { get; set; } - public float end_vel_y { get; set; } - public float end_vel_z { get; set; } - public int run_date { get; set; } - public string replay_frames { get; set; } = ""; // This needs to be checked touroughly - public List? checkpoints { get; set; } = null; - public string name { get; set; } = "N/A"; - public int total_count { get; set; } -} - -/* -public class API_PlayerSurfProfile -{ - public int id { get; set; } - public string name { get; set; } = "N/A"; - public ulong steam_id { get; set; } - public string country { get; set; } = "N/A"; - public int join_date { get; set; } - public int last_seen { get; set; } - public int connections { get; set; } - - [JsonConstructor] - /// - /// Parameterless constructor for manual assigning of data - /// - public API_PlayerSurfProfile() { } - - /// - /// Assigns values to the needed data model for an API request - /// - public API_PlayerSurfProfile(PlayerProfileDataModel data) - { - id = data.ID; - name = data.Name; - steam_id = data.SteamID; - country = data.Country; - join_date = data.JoinDate; - last_seen = data.LastSeen; - connections = data.Connections == 0 ? 1 : data.Connections; - } -} -*/ - -/* -public class API_PersonalBest -{ - public int id { get; set; } - public int player_id { get; set; } - public int map_id { get; set; } - public int style { get; set; } = 0; - public int type { get; set; } = 0; - public int stage { get; set; } = 0; - public int run_time { get; set; } - public float start_vel_x { get; set; } - public float start_vel_y { get; set; } - public float start_vel_z { get; set; } - public float end_vel_x { get; set; } - public float end_vel_y { get; set; } - public float end_vel_z { get; set; } - public int run_date { get; set; } - public string replay_frames { get; set; } = ""; // This needs to be checked touroughly - public List? checkpoints { get; set; } = null; - public string name { get; set; } = "N/A"; - public int rank { get; set; } - - [JsonConstructor] - public API_PersonalBest() { } // Parameterless constructor used by GET method -} -*/ - -/* -public class API_SaveMapTime -{ - public int player_id { get; set; } - public int map_id { get; set; } - public int run_time { get; set; } - public float start_vel_x { get; set; } - public float start_vel_y { get; set; } - public float start_vel_z { get; set; } - public float end_vel_x { get; set; } - public float end_vel_y { get; set; } - public float end_vel_z { get; set; } - public int style { get; set; } = 0; - public int type { get; set; } = 0; - public int stage { get; set; } = 0; - public List? checkpoints { get; set; } = null; - public string replay_frames { get; set; } = ""; - public int? run_date { get; set; } = null; - - /// - /// Assigns values to the needed data model for an API request - /// - internal API_SaveMapTime(MapTimeDataModel data) - { - player_id = data.PlayerId; - map_id = data.MapId; - run_time = data.RunTime; - start_vel_x = data.StartVelX; - start_vel_y = data.StartVelY; - start_vel_z = data.StartVelZ; - end_vel_x = data.EndVelX; - end_vel_y = data.EndVelY; - end_vel_z = data.EndVelZ; - style = data.Style; - type = data.Type; - stage = data.Stage; - replay_frames = data.ReplayFrames; - run_date = data.RunDate; - - // Convert Checkpoints - checkpoints = data.Checkpoints.Select(cp => new API_Checkpoint - { - cp = cp.Key, - run_time = cp.Value.RunTime, - end_touch = cp.Value.EndTouch, - start_vel_x = cp.Value.StartVelX, - start_vel_y = cp.Value.StartVelY, - start_vel_z = cp.Value.StartVelZ, - end_vel_x = cp.Value.EndVelX, - end_vel_y = cp.Value.EndVelY, - end_vel_z = cp.Value.EndVelZ, - attempts = cp.Value.Attempts - }).ToList(); - } -} -*/ \ No newline at end of file diff --git a/src/ST-Commands/MapCommands.cs b/src/ST-Commands/MapCommands.cs index 75786a9..e2ac1a0 100644 --- a/src/ST-Commands/MapCommands.cs +++ b/src/ST-Commands/MapCommands.cs @@ -26,9 +26,9 @@ public void MapTier(CCSPlayerController? player, CommandInfo command) string rankedStatus = CurrentMap.Ranked ? "Yes" : "No"; string msg = $"{Config.PluginPrefix} " + LocalizationService.LocalizerNonNull["map_info", - CurrentMap.Name, + CurrentMap.Name!, $"{Extensions.GetTierColor(CurrentMap.Tier)}{CurrentMap.Tier}", - CurrentMap.Author, + CurrentMap.Author!, $"{rankedColor}{rankedStatus}", DateTimeOffset.FromUnixTimeSeconds(CurrentMap.DateAdded).DateTime.ToString("dd.MM.yyyy HH:mm") ]; @@ -202,9 +202,9 @@ public void Triggers(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"Hooked Trigger -> Start -> {CurrentMap.StartZone} -> Angles {CurrentMap.StartZoneAngles}"); player.PrintToChat($"Hooked Trigger -> End -> {CurrentMap.EndZone}"); int i = 1; - foreach (Vector_t stage in CurrentMap.StageStartZone) + foreach (VectorT stage in CurrentMap.StageStartZone) { - if (stage.X == 0 && stage.Y == 0 && stage.Z == 0) + if (stage.IsZero()) continue; else { @@ -214,9 +214,9 @@ public void Triggers(CCSPlayerController? player, CommandInfo command) } i = 1; - foreach (Vector_t bonus in CurrentMap.BonusStartZone) + foreach (VectorT bonus in CurrentMap.BonusStartZone) { - if (bonus.X == 0 && bonus.Y == 0 && bonus.Z == 0) + if (bonus.IsZero()) continue; else { diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 8873f88..0e775f7 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -38,7 +38,6 @@ public void PlayerReset(CCSPlayerController? player, CommandInfo command) return; } - // oPlayer.ReplayRecorder.Reset(); playerList[player.UserId ?? 0].Timer.Reset(); if (!CurrentMap.StartZone.IsZero()) Server.NextFrame(() => @@ -102,10 +101,10 @@ public void PlayerGoToStage(CCSPlayerController? player, CommandInfo command) if (player == null) return; - int stage; + short stage; try { - stage = Int32.Parse(command.ArgByIndex(1)); + stage = short.Parse(command.ArgByIndex(1)); } catch (System.Exception) { @@ -252,7 +251,7 @@ public void PlayerRank(CCSPlayerController? player, CommandInfo command) int pRank = playerList[player.UserId ?? 0].Stats.PB[playerList[player.UserId ?? 0].Timer.Style].Rank; int tRank = CurrentMap.MapCompletions[playerList[player.UserId ?? 0].Timer.Style]; player.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["rank", - CurrentMap.Name, pRank, tRank]}" + CurrentMap.Name!, pRank, tRank]}" ); } @@ -390,9 +389,9 @@ public void SavePlayerLocation(CCSPlayerController? player, CommandInfo command) p.SavedLocations.Add(new SavelocFrame { - Pos = new Vector_t(player_pos.X, player_pos.Y, player_pos.Z), - Ang = new QAngle_t(player_angle.X, player_angle.Y, player_angle.Z), - Vel = new Vector_t(player_velocity.X, player_velocity.Y, player_velocity.Z), + Pos = new VectorT(player_pos.X, player_pos.Y, player_pos.Z), + Ang = new QAngleT(player_angle.X, player_angle.Y, player_angle.Z), + Vel = new VectorT(player_velocity.X, player_velocity.Y, player_velocity.Z), Tick = p.Timer.Ticks }); p.CurrentSavedLocation = p.SavedLocations.Count - 1; @@ -547,69 +546,8 @@ public void TestSituationCmd(CCSPlayerController? player, CommandInfo command) return; Player oPlayer = playerList[player.UserId ?? 0]; - int style = oPlayer.Timer.Style; - - oPlayer.Stats.ThisRun.PrintSituations(oPlayer); - } - - [ConsoleCommand("css_setpb", "xxxxx")] - [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] - [RequiresPermissions("@css/root")] - public async void TestSetPb(CCSPlayerController? player, CommandInfo command) - { - if (player == null) - return; - - Player oPlayer = playerList[player.UserId ?? 0]; - int style = oPlayer.Timer.Style; - - await oPlayer.Stats.ThisRun.SaveMapTime(oPlayer, 0, 0, 6666, "TestSetPb"); - // oPlayer.Stats.ThisRun.PrintSituations(oPlayer); - - /* Test Time Saving *//* - if (methodName == "TestSetPb") - { - // 1. Dummy Checkpoint - var dummyCheckpoint = new Checkpoint - { - CP = 1, - Ticks = 1234, - EndTouch = 1, - StartVelX = 111.1f, - StartVelY = 222.2f, - StartVelZ = 333.3f, - EndVelX = 444.4f, - EndVelY = 555.5f, - EndVelZ = 666.6f, - Attempts = 2 - }; - - // 2. Dummy Dictionary за mapTime.Checkpoints - var dummyCheckpointsDict = new Dictionary - { - { dummyCheckpoint.CP, dummyCheckpoint } - }; - - mapTime = new MapTimeDataModel - { - PlayerId = player.Profile.ID, - MapId = player.CurrMap.ID, - Style = player.Timer.Style, - Type = 0, - Stage = stage != 0 ? stage : bonus, - Ticks = 666, - StartVelX = this.StartVelX, - StartVelY = this.StartVelY, - StartVelZ = this.StartVelZ, - EndVelX = this.EndVelX, - EndVelY = this.EndVelY, - EndVelZ = this.EndVelZ, - ReplayFramesBase64 = replay_frames, - Checkpoints = dummyCheckpointsDict - }; - } - /* END Test Time Saving */ + CurrentRun.PrintSituations(oPlayer); } [ConsoleCommand("css_testx", "x")] @@ -623,19 +561,17 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) Player oPlayer = playerList[player.UserId ?? 0]; int style = oPlayer.Timer.Style; - // player.PrintToChat($"{Config.PluginPrefix} {ChatColors.Red}Testing 'PB_LoadMapTimeData'"); player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Lime}====== PLAYER ======"); player.PrintToChat($"{Config.PluginPrefix} Profile ID: {ChatColors.Green}{oPlayer.Profile.ID}"); player.PrintToChat($"{Config.PluginPrefix} Steam ID: {ChatColors.Green}{oPlayer.Profile.SteamID}"); - player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.PB[style].RunTime)}"); + player.PrintToChat($"{Config.PluginPrefix} MapTime ID: {ChatColors.Green}{oPlayer.Stats.PB[style].ID} - {PlayerHud.FormatTime(oPlayer.Stats.PB[style].RunTime)}"); player.PrintToChat($"{Config.PluginPrefix} Stage: {ChatColors.Green}{oPlayer.Timer.Stage}"); player.PrintToChat($"{Config.PluginPrefix} IsStageMode: {ChatColors.Green}{oPlayer.Timer.IsStageMode}"); player.PrintToChat($"{Config.PluginPrefix} IsRunning: {ChatColors.Green}{oPlayer.Timer.IsRunning}"); player.PrintToChat($"{Config.PluginPrefix} Checkpoint: {ChatColors.Green}{oPlayer.Timer.Checkpoint}"); player.PrintToChat($"{Config.PluginPrefix} Bonus: {ChatColors.Green}{oPlayer.Timer.Bonus}"); player.PrintToChat($"{Config.PluginPrefix} Ticks: {ChatColors.Green}{oPlayer.Timer.Ticks}"); - player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[1][style].RunTime)}"); - // player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[style][1].ID} - {PlayerHUD.FormatTime(oPlayer.Stats.StagePB[style][1].Ticks)}"); + player.PrintToChat($"{Config.PluginPrefix} StagePB ID: {ChatColors.Green}{oPlayer.Stats.StagePB[1][style].ID} - {PlayerHud.FormatTime(oPlayer.Stats.StagePB[1][style].RunTime)}"); player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Orange}====== MAP ======"); @@ -644,8 +580,8 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{Config.PluginPrefix} Map Stages: {ChatColors.Green}{CurrentMap.Stages}"); player.PrintToChat($"{Config.PluginPrefix} Map Bonuses: {ChatColors.Green}{CurrentMap.Bonuses}"); player.PrintToChat($"{Config.PluginPrefix} Map Completions (Style: {ChatColors.Green}{style}{ChatColors.Default}): {ChatColors.Green}{CurrentMap.MapCompletions[style]}"); - player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Ticks: {ChatColors.Green}{CurrentMap.WR[style].RunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .CurrentMap.WR[].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints.Count}"); + player.PrintToChat($"{Config.PluginPrefix} CurrentMap.WR[{style}].Ticks: {ChatColors.Green}{CurrentMap.WR[style].RunTime}"); + player.PrintToChat($"{Config.PluginPrefix} CurrentMap.WR[{style}].Checkpoints.Count: {ChatColors.Green}{CurrentMap.WR[style].Checkpoints!.Count}"); player.PrintToChat($"{Config.PluginPrefix}{ChatColors.Purple}====== REPLAYS ======"); @@ -662,89 +598,6 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.Frames.Count}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlayable}"); player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.BonusWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.BonusWR?.IsPlaying}"); - - /* - for (int i = 1; i < SurfTimer.CurrentMap.Stages; i++) - { - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].RecordRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].RecordRunTime}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].Frames.Count: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].Frames.Count}"); - player.PrintToChat($"{Config.PluginPrefix} .ReplayManager.AllStageWR[{i}][0].IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.AllStageWR[i][0].IsPlayable}"); - } - */ - - /* - for (int i = 0; i < CurrentMap.ReplayManager.MapWR.Frames.Count; i++) - { - ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; - - switch (x.Situation) - { - case ReplayFrameSituation.START_ZONE_ENTER: - player.PrintToChat($"Start Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.START_ZONE_EXIT: - player.PrintToChat($"Start Exit: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: - player.PrintToChat($"Stage Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - player.PrintToChat($"Stage Exit: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - player.PrintToChat($"Checkpoint Enter: {i} | Situation {x.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - player.PrintToChat($"Checkpoint Exit: {i} | Situation {x.Situation}"); - break; - } - } - */ - // for (int i = 0; i < CurrentMap.ReplayManager.MapWR.MapSituations.Count; i++) - // { - // ReplayFrame x = CurrentMap.ReplayManager.MapWR.Frames[i]; - // switch (x.Situation) - // { - // case ReplayFrameSituation.START_ZONE_ENTER: - // player.PrintToChat($"START_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.START_ZONE_EXIT: - // player.PrintToChat($"START_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.STAGE_ZONE_ENTER: - // player.PrintToChat($"STAGE_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.STAGE_ZONE_EXIT: - // player.PrintToChat($"STAGE_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - // player.PrintToChat($"CHECKPOINT_ZONE_ENTER: {i} | Situation {x.Situation}"); - // break; - // case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - // player.PrintToChat($"CHECKPOINT_ZONE_EXIT: {i} | Situation {x.Situation}"); - // break; - // } - // } - - // player.PrintToChat($"{Config.PluginPrefix} IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlayable}"); - // player.PrintToChat($"{Config.PluginPrefix} IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsPlaying}"); - // player.PrintToChat($"{Config.PluginPrefix} Player.IsSpectating: {ChatColors.Green}{oPlayer.IsSpectating(CurrentMap.ReplayManager.MapWR.Controller!)}"); - // player.PrintToChat($"{Config.PluginPrefix} Name & MapTimeID: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RecordPlayerName} {CurrentMap.ReplayManager.MapWR.MapTimeID}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayCurrentRunTime: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.ReplayCurrentRunTime}"); - // player.PrintToChat($"{Config.PluginPrefix} RepeatCount: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.RepeatCount}"); - // player.PrintToChat($"{Config.PluginPrefix} IsReplayOutsideZone: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.IsReplayOutsideZone}"); - // player.PrintToChat($"{Config.PluginPrefix} CurrentFrameTick: {ChatColors.Green}{CurrentMap.ReplayManager.MapWR.CurrentFrameTick}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayRecorder.Frames.Length: {ChatColors.Green}{oPlayer.ReplayRecorder.Frames.Count}"); - - // if (CurrentMap.ReplayManager.StageWR != null) - // { - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.MapTimeID - Stage: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.MapTimeID} - {CurrentMap.ReplayManager.StageWR.Stage}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlayable: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlayable}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsEnabled: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsEnabled}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPaused: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPaused}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.IsPlaying: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.IsPlaying}"); - // player.PrintToChat($"{Config.PluginPrefix} ReplayManager.StageWR.Controller Null?: {ChatColors.Green}{CurrentMap.ReplayManager.StageWR.Controller == null}"); - // } } [ConsoleCommand("css_ctest", "x")] @@ -752,6 +605,7 @@ public void TestCmd(CCSPlayerController? player, CommandInfo command) [RequiresPermissions("@css/root")] public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) { + Console.WriteLine("====== MAP INFO ======"); Console.WriteLine($"Map ID: {CurrentMap.ID}"); Console.WriteLine($"Map Name: {CurrentMap.Name}"); Console.WriteLine($"Map Author: {CurrentMap.Author}"); @@ -760,6 +614,7 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map Bonuses: {CurrentMap.Bonuses}"); Console.WriteLine($"Map Completions: {CurrentMap.MapCompletions[0]}"); + Console.WriteLine("====== MAP WR INFO ======"); Console.WriteLine($"Map WR ID: {CurrentMap.WR[0].ID}"); Console.WriteLine($"Map WR Name: {CurrentMap.WR[0].Name}"); Console.WriteLine($"Map WR Type: {CurrentMap.WR[0].Type}"); @@ -768,6 +623,7 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map WR ReplayFramesBase64.Length: {CurrentMap.WR[0].ReplayFrames?.ToString().Length}"); Console.WriteLine($"Map WR ReplayFrames.Length: {CurrentMap.WR[0].ReplayFrames?.ToString().Length}"); + Console.WriteLine("====== MAP StageWR INFO ======"); Console.WriteLine($"Map Stage Completions: {CurrentMap.StageCompletions.Length}"); Console.WriteLine($"Map StageWR ID: {CurrentMap.StageWR[1][0].ID}"); Console.WriteLine($"Map StageWR Name: {CurrentMap.StageWR[1][0].Name}"); @@ -778,6 +634,4 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map Bonus Completions: {CurrentMap.BonusCompletions.Length}"); } - - } \ No newline at end of file diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs index 879853d..0a0377b 100644 --- a/src/ST-DB/DB.cs +++ b/src/ST-DB/DB.cs @@ -11,16 +11,6 @@ public TimerDatabase(string connectionString) _connString = connectionString; } - public void Dispose() - { - Close(); - } - - public void Close() - { - // Not needed - } - /// /// Spawns a new connection to the database. /// @@ -35,9 +25,9 @@ private MySqlConnection GetConnection() catch (MySqlException mysqlEx) // Specifically catch MySQL-related exceptions { Console.WriteLine($"[CS2 Surf] MySQL error when connecting: {mysqlEx.Message}"); - throw new InvalidOperationException("Could not establish a connection to the database.", mysqlEx); // Wrap the original exception with additional context + throw; } - catch (System.Exception ex) // Catch all other exceptions + catch (Exception ex) // Catch all other exceptions { Console.WriteLine($"[CS2 Surf] General error when connecting to the database: {ex.Message}"); throw; // Re-throw the exception without wrapping it diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 8ea19a9..5cabab1 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -1,6 +1,7 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Utils; using MaxMind.GeoIP2; using Microsoft.Extensions.Logging; @@ -25,6 +26,8 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) CurrentMap.ReplayManager.MapWR.SetController(controller, -1); CurrentMap.ReplayManager.MapWR.LoadReplayData(); + controller.SwitchTeam(CsTeam.Terrorist); + AddTimer(1.5f, () => { CurrentMap.ReplayManager.MapWR.Controller!.RemoveWeapons(); @@ -41,6 +44,8 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) CurrentMap.ReplayManager.StageWR.SetController(controller, 3); CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); + controller.SwitchTeam(CsTeam.Terrorist); + AddTimer(1.5f, () => { CurrentMap.ReplayManager.StageWR.Controller!.RemoveWeapons(); @@ -57,6 +62,8 @@ public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) CurrentMap.ReplayManager.BonusWR.SetController(controller, 3); CurrentMap.ReplayManager.BonusWR.LoadReplayData(); + controller.SwitchTeam(CsTeam.Terrorist); + AddTimer(1.5f, () => { CurrentMap.ReplayManager.BonusWR.Controller!.RemoveWeapons(); @@ -177,7 +184,8 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo if (DB == null) { _logger.LogCritical("OnPlayerDisconnect -> DB object is null, this shouldnt happen."); - throw new Exception("CS2 Surf ERROR >> OnPlayerDisconnect -> DB object is null, this shouldnt happen."); + Exception ex = new("CS2 Surf ERROR >> OnPlayerDisconnect -> DB object is null, this shouldnt happen."); + throw ex; } if (!playerList.ContainsKey(player.UserId ?? 0)) diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index fec417e..249bbb4 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -22,7 +22,7 @@ public void OnTick() // Need to disable maps from executing their cfgs. Currently idk how (But seriusly it a security issue) ConVar? bot_quota = ConVar.Find("bot_quota"); - // Console.WriteLine($"======== public void OnTick -> bot_quota not null? {bot_quota != null}"); + if (bot_quota != null) { int cbq = bot_quota.GetPrimitiveValue(); @@ -36,8 +36,6 @@ public void OnTick() { bot_quota.SetValue(replaybot_count); } - - // _logger.LogInformation("public void OnTick -> Got bot_quota {cbq} | Setting to bot_quota {replaybot_count}", cbq, replaybot_count); } CurrentMap.ReplayManager.MapWR.Tick(); @@ -60,7 +58,6 @@ public void OnTick() CurrentMap.ReplayManager.AllStageWR[next_stage][0].Controller = CurrentMap.ReplayManager.StageWR.Controller; - // _logger.LogInformation("public void OnTick() -> Finished replay cycle for stage {Stage}, changing to stage {next_stage}", CurrentMap.ReplayManager.StageWR.Stage, next_stage); CurrentMap.ReplayManager.StageWR = CurrentMap.ReplayManager.AllStageWR[next_stage][0]; CurrentMap.ReplayManager.StageWR.LoadReplayData(repeat_count: 3); CurrentMap.ReplayManager.StageWR.FormatBotName(); @@ -77,7 +74,6 @@ public void OnTick() CurrentMap.ReplayManager.AllBonusWR[next_bonus][0].Controller = CurrentMap.ReplayManager.BonusWR.Controller; - // _logger.LogInformation("public void OnTick() -> Finished replay cycle for bonus {Bonus}, changing to bonus {next_bonus}", CurrentMap.ReplayManager.BonusWR.Stage, next_bonus); CurrentMap.ReplayManager.BonusWR = CurrentMap.ReplayManager.AllBonusWR[next_bonus][0]; CurrentMap.ReplayManager.BonusWR.LoadReplayData(repeat_count: 3); CurrentMap.ReplayManager.BonusWR.FormatBotName(); diff --git a/src/ST-Events/TriggerEndTouch.cs b/src/ST-Events/TriggerEndTouch.cs index 5dfd90a..57a3e52 100644 --- a/src/ST-Events/TriggerEndTouch.cs +++ b/src/ST-Events/TriggerEndTouch.cs @@ -21,10 +21,10 @@ internal HookResult OnTriggerEndTouch(CEntityIOOutput output, string name, CEnti { client = new CCSPlayerController(new CCSPlayerPawn(entity.Handle).Controller.Value!.Handle); } - catch (System.Exception) + catch (Exception ex) { - _logger.LogError("[{ClassName}] OnTriggerEndTouch -> Could not assign `client` (name: {Name})", - nameof(SurfTimer), name + _logger.LogError(ex, "[{ClassName}] OnTriggerEndTouch -> Could not assign `client` (name: {Name}). Exception: {Exception}", + nameof(SurfTimer), name, ex.Message ); } diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 4fd781c..15bc27d 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -1,5 +1,4 @@ using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API; using Microsoft.Extensions.Logging; namespace SurfTimer; diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index 2724b27..ab5c880 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -48,7 +48,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); int pStyle = player.Timer.Style; player.Controller.PrintToCenter($"Map End"); @@ -76,21 +76,21 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string saveMapTime = true; int timeImprove = CurrentMap.WR[pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.WR[pStyle].RunTime)]}" + player.Controller.PlayerName, PlayerHud.FormatTime(player.Timer.Ticks), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(CurrentMap.WR[pStyle].RunTime)]}" ); } else if (CurrentMap.WR[pStyle].ID == -1) // No record was set on the map { saveMapTime = true; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mapwr_set", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + player.Controller.PlayerName, PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } else if (player.Stats.PB[pStyle].RunTime <= 0) // Player first ever PersonalBest for the map { saveMapTime = true; player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_set", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" + PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } else if (player.Timer.Ticks < player.Stats.PB[pStyle].RunTime) // Player beating their existing PersonalBest for the map @@ -98,13 +98,13 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string saveMapTime = true; int timeImprove = player.Stats.PB[pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_improved", - player.Controller.PlayerName, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].RunTime)]}" + player.Controller.PlayerName, PlayerHud.FormatTime(player.Timer.Ticks), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(player.Stats.PB[pStyle].RunTime)]}" ); } else // Player did not beat their existing PersonalBest for the map nor the map record { player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["mappb_missed", - PlayerHUD.FormatTime(player.Timer.Ticks)]}" + PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } @@ -128,8 +128,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - await player.Stats.ThisRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + await CurrentRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); }); } @@ -158,8 +157,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); - // player.Controller.PrintToChat($"{Config.PluginPrefix} [LAST StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {CurrentMap.Stages}, {stage_run_time}"); - await player.Stats.ThisRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); + await CurrentRun.SaveStageTime(player, CurrentMap.Stages, stage_run_time, true); }); } } @@ -177,10 +175,7 @@ private static void StartTouchHandleMapStartZone(Player player, CBaseTrigger tri player.ReplayRecorder.Reset(); // Start replay recording player.ReplayRecorder.Start(); // Start replay recording player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_ENTER; - player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - // player.Controller.PrintToChat($"{ChatColors.Green}START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Console.WriteLine($"START_ZONE_ENTER: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); player.Timer.Reset(); player.Stats.ThisRun.Checkpoints.Clear(); player.Controller.PrintToCenter($"Map Start ({trigger.Entity!.Name})"); @@ -199,16 +194,14 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + short stage = short.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); if (!player.ReplayRecorder.IsRecording) player.ReplayRecorder.Start(); player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_ENTER; player.ReplayRecorder.StageEnterSituations.Add(player.ReplayRecorder.Frames.Count); - Console.WriteLine($"STAGE_ZONE_ENTER: player.ReplayRecorder.StageEnterSituations.Add({player.ReplayRecorder.Frames.Count})"); bool failed_stage = false; if (player.Timer.Stage == stage) @@ -218,23 +211,16 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, // Save a Stage run when `IsStageMode` is active - (`stage - 1` to get the previous stage data) if (player.Timer.IsStageMode) { - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player ticks higher than 0? {ChatColors.Yellow}{player.Timer.Ticks > 0}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Player time is faster than StageWR time? {ChatColors.Yellow}{player.Timer.Ticks < CurrentMap.StageWR[stage - 1][style].Ticks}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} No StageWR Exists? {ChatColors.Yellow}{CurrentMap.StageWR[stage - 1][style].ID == -1}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Not null? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style] != null}"); - // player.Controller.PrintToChat($"{Config.PluginPrefix} Time faster than existing stage PB? {ChatColors.Yellow}{player.Stats.StagePB[stage - 1][style].Ticks > player.Timer.Ticks}"); if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { int stage_run_time = player.Timer.Ticks; AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone { - await player.Stats.ThisRun.SaveStageTime(player, stage - 1, stage_run_time); + await CurrentRun.SaveStageTime(player, (short)(stage - 1), stage_run_time); }); - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (IsStageMode)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); } player.Timer.Reset(); player.Timer.IsStageMode = true; - // player.Controller.PrintToChat($"{ChatColors.Red}Resetted{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}"); } player.Timer.Stage = stage; @@ -255,12 +241,11 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone { - await player.Stats.ThisRun.SaveStageTime(player, stage - 1, stage_run_time); + await CurrentRun.SaveStageTime(player, (short)(stage - 1), stage_run_time); }); - } - player.Timer.Checkpoint = stage - 1; // Stage = Checkpoint when in a run on a Staged map + player.Timer.Checkpoint = (short)(stage - 1); // 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`: {stage} | player.Timer.Checkpoint: {stage - 1}"); @@ -300,15 +285,15 @@ private static void StartTouchHandleCheckpointZone(Player player, CBaseTrigger t { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); - int pStyle = player.Timer.Style; - int checkpoint = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + short checkpoint = short.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* if (player.Timer.IsRunning && !player.Timer.IsStageMode && player.Stats.ThisRun.Checkpoints.Count < checkpoint) { #if DEBUG + int pStyle = player.Timer.Style; 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)}"); Console.WriteLine($"CS2 Surf DEBUG >> CBaseTrigger_StartTouchFunc (Checkpoint zones) -> player.Stats.PB[{pStyle}].Checkpoint.Count = {player.Stats.PB[pStyle].Checkpoints.Count}"); #endif @@ -350,7 +335,7 @@ private static void StartTouchHandleCheckpointZone(Player player, CBaseTrigger t private static void StartTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") { - int bonus = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + short bonus = short.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); player.Timer.Bonus = bonus; player.Timer.Reset(); @@ -375,10 +360,10 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ { // Get velocities for DB queries // Get the velocity of the player - we will be using this values to compare and write to DB - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); int pStyle = player.Timer.Style; // To-do: verify the bonus trigger being hit! - int bonus_idx = int.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); + short bonus_idx = short.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); player.Timer.Stop(); player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_ENTER; @@ -399,21 +384,21 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ saveBonusTime = true; int timeImprove = CurrentMap.BonusWR[bonus_idx][pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].RunTime)]}" + player.Controller.PlayerName, bonus_idx, PlayerHud.FormatTime(player.Timer.Ticks), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(CurrentMap.BonusWR[bonus_idx][pStyle].RunTime)]}" ); } else if (CurrentMap.BonusWR[bonus_idx][pStyle].ID == -1) // No Bonus record was set on the map { saveBonusTime = true; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuswr_set", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + player.Controller.PlayerName, bonus_idx, PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } else if (player.Stats.BonusPB[bonus_idx][pStyle].RunTime <= 0) // Player first ever PersonalBest for the bonus { saveBonusTime = true; player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_set", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + bonus_idx, PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } else if (player.Timer.Ticks < player.Stats.BonusPB[bonus_idx][pStyle].RunTime) // Player beating their existing PersonalBest for the bonus @@ -421,13 +406,13 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ saveBonusTime = true; int timeImprove = player.Stats.BonusPB[bonus_idx][pStyle].RunTime - player.Timer.Ticks; Server.PrintToChatAll($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_improved", - player.Controller.PlayerName, bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.PB[pStyle].RunTime)]}" + player.Controller.PlayerName, bonus_idx, PlayerHud.FormatTime(player.Timer.Ticks), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(player.Stats.PB[pStyle].RunTime)]}" ); } else // Player did not beat their existing personal best for the bonus { player.Controller.PrintToChat($"{Config.PluginPrefix} {PracticeString}{LocalizationService.LocalizerNonNull["bonuspb_missed", - bonus_idx, PlayerHUD.FormatTime(player.Timer.Ticks)]}" + bonus_idx, PlayerHud.FormatTime(player.Timer.Ticks)]}" ); } @@ -446,14 +431,14 @@ private void StartTouchHandleBonusEndZone(Player player, CBaseTrigger trigger, [ /* EndTouch */ - private void EndTouchHandleMapEndZone(Player player, [CallerMemberName] string methodName = "") + private static void EndTouchHandleMapEndZone(Player player, [CallerMemberName] string methodName = "") { player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.END_ZONE_EXIT; } - private void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string methodName = "") + private static void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string methodName = "") { - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); // MAP START ZONE if (!player.Timer.IsStageMode && !player.Timer.IsBonusMode) @@ -462,8 +447,10 @@ private void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string player.Stats.ThisRun.RunTime = player.Timer.Ticks; player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; player.ReplayRecorder.MapSituations.Add(player.ReplayRecorder.Frames.Count); - // player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); - // Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); +#if DEBUG + player.Controller.PrintToChat($"{ChatColors.Red}START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); + Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.MapSituations.Add({player.ReplayRecorder.Frames.Count})"); +#endif } // Prespeed display @@ -477,26 +464,24 @@ private void EndTouchHandleMapStartZone(Player player, [CallerMemberName] string #endif } - private void EndTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + private static void EndTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") { #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.Stats.ThisRun.Checkpoint.Count {player.Stats.ThisRun.Checkpoints.Count}"); #endif - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); int stage = Int32.Parse(Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value); // Set replay situation player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.STAGE_ZONE_EXIT; player.ReplayRecorder.StageExitSituations.Add(player.ReplayRecorder.Frames.Count); player.Stats.ThisRun.RunTime = player.Timer.Ticks; - // Console.WriteLine($"STAGE_ZONE_EXIT: player.ReplayRecorder.StageExitSituations.Add({player.ReplayRecorder.Frames.Count})"); // Start the Stage timer if (player.Timer.IsStageMode && player.Timer.Stage == stage) { player.Timer.Start(); - // player.Controller.PrintToChat($"{ChatColors.Green}Started{ChatColors.Default} Stage timer for stage {ChatColors.Green}{stage}{ChatColors.Default}"); // Show Prespeed for Stages - will be enabled/disabled by the user? player.Controller.PrintToCenter($"Stage {stage} - Prespeed: {velocity.velMag().ToString("0")} u/s"); @@ -521,13 +506,13 @@ private void EndTouchHandleStageStartZone(Player player, CBaseTrigger trigger, [ } } - private void EndTouchHandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + private static void EndTouchHandleCheckpointZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") { #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.Checkpoints.Count}"); #endif - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); // 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.Checkpoints.Count) @@ -555,17 +540,17 @@ private void EndTouchHandleCheckpointZone(Player player, CBaseTrigger trigger, [ } } - private void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") + private static void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [CallerMemberName] string methodName = "") { #if DEBUG player.Controller.PrintToChat($"CS2 Surf DEBUG >> CBaseTrigger_{ChatColors.LightRed}EndTouchFunc{ChatColors.Default} -> {ChatColors.Yellow}Bonus {Regex.Match(trigger.Entity!.Name, "[0-9][0-9]?").Value} Start Zone"); #endif - Vector_t velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); + VectorT velocity = player.Controller.PlayerPawn.Value!.AbsVelocity.ToVector_t(); // Replay if (player.ReplayRecorder.IsRecording) { - // Saveing 2 seconds before leaving the start zone + // Saving 2 seconds before leaving the start zone 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 } @@ -578,7 +563,9 @@ private void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [ player.ReplayRecorder.CurrentSituation = ReplayFrameSituation.START_ZONE_EXIT; player.ReplayRecorder.BonusSituations.Add(player.ReplayRecorder.Frames.Count); +#if DEBUG Console.WriteLine($"START_ZONE_EXIT: player.ReplayRecorder.BonusSituations.Add({player.ReplayRecorder.Frames.Count})"); +#endif } // Prespeed display @@ -588,8 +575,8 @@ private void EndTouchHandleBonusStartZone(Player player, CBaseTrigger trigger, [ player.Stats.ThisRun.StartVelZ = velocity.Z; // Start pre speed for the Bonus run } - private void EndTouchHandleBonusEndZone(Player player, [CallerMemberName] string methodName = "") + private static void EndTouchHandleBonusEndZone(Player player, [CallerMemberName] string methodName = "") { - + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 38f9473..ae1acff 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -53,18 +53,18 @@ public class Map : MapEntity // Zone Origin Information /* Map Start/End zones */ - public Vector_t StartZone { get; set; } = new Vector_t(0, 0, 0); - public QAngle_t StartZoneAngles { get; set; } = new QAngle_t(0, 0, 0); - public Vector_t EndZone { get; set; } = new Vector_t(0, 0, 0); + public VectorT StartZone { get; set; } = new VectorT(0, 0, 0); + public QAngleT StartZoneAngles { get; set; } = new QAngleT(0, 0, 0); + public VectorT EndZone { get; set; } = new VectorT(0, 0, 0); /* Map Stage zones */ - public Vector_t[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); - public QAngle_t[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle_t(0, 0, 0)).ToArray(); + public VectorT[] StageStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new VectorT(0, 0, 0)).ToArray(); + public QAngleT[] StageStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngleT(0, 0, 0)).ToArray(); /* Map Bonus zones */ - public Vector_t[] BonusStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); // To-do: Implement bonuses - public QAngle_t[] BonusStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngle_t(0, 0, 0)).ToArray(); // To-do: Implement bonuses - public Vector_t[] BonusEndZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); // To-do: Implement bonuses + public VectorT[] BonusStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new VectorT(0, 0, 0)).ToArray(); // To-do: Implement bonuses + public QAngleT[] BonusStartZoneAngles { get; } = Enumerable.Repeat(0, 99).Select(x => new QAngleT(0, 0, 0)).ToArray(); // To-do: Implement bonuses + public VectorT[] BonusEndZone { get; } = Enumerable.Repeat(0, 99).Select(x => new VectorT(0, 0, 0)).ToArray(); // To-do: Implement bonuses /* Map Checkpoint zones */ - public Vector_t[] CheckpointStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new Vector_t(0, 0, 0)).ToArray(); + public VectorT[] CheckpointStartZone { get; } = Enumerable.Repeat(0, 99).Select(x => new VectorT(0, 0, 0)).ToArray(); public ReplayManager ReplayManager { get; set; } = null!; @@ -126,7 +126,7 @@ internal async Task InitializeAsync([CallerMemberName] string methodName = "") stopwatch.Stop(); _logger.LogInformation("[{ClassName}] {MethodName} -> We got MapID = {ID} ({Name}) in {ElapsedMilliseconds}ms | API = {API}", - nameof(Map), methodName, ID, Name, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + nameof(Map), methodName, ID, Name, stopwatch.ElapsedMilliseconds, Config.Api.GetApiOnly() ); } @@ -160,8 +160,8 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") teleport.Entity!.Name.Contains("spawn_stage1_start") || teleport.Entity!.Name.Contains("spawn_s1_start"))) { - this.StartZone = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - this.StartZoneAngles = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.StartZone = new VectorT(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StartZoneAngles = new QAngleT(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); foundPlayerSpawn = true; break; } @@ -169,14 +169,14 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.StartZone = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StartZone = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } // Map end zone else if (trigger.Entity!.Name.Contains("map_end")) { - this.EndZone = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.EndZone = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } // Stage start zones @@ -191,8 +191,8 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") 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.StageStartZone[stage] = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - this.StageStartZoneAngles[stage] = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.StageStartZone[stage] = new VectorT(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.StageStartZoneAngles[stage] = new QAngleT(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Stages++; // Count stage zones for the map to populate DB foundPlayerSpawn = true; break; @@ -201,7 +201,7 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.StageStartZone[stage] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.StageStartZone[stage] = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.Stages++; } } @@ -209,7 +209,7 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") // 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)] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.CheckpointStartZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.TotalCheckpoints++; // Might be useful to have this in DB entry } @@ -225,8 +225,8 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") 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.BonusStartZone[bonus] = new Vector_t(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); - this.BonusStartZoneAngles[bonus] = new QAngle_t(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); + this.BonusStartZone[bonus] = new VectorT(teleport.AbsOrigin!.X, teleport.AbsOrigin!.Y, teleport.AbsOrigin!.Z); + this.BonusStartZoneAngles[bonus] = new QAngleT(teleport.AbsRotation!.X, teleport.AbsRotation!.Y, teleport.AbsRotation!.Z); this.Bonuses++; // Count bonus zones for the map to populate DB foundPlayerSpawn = true; break; @@ -235,14 +235,14 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") if (!foundPlayerSpawn) { - this.BonusStartZone[bonus] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusStartZone[bonus] = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); this.Bonuses++; } } else if (Regex.Match(trigger.Entity.Name, "^b([1-9][0-9]?|onus[1-9][0-9]?)_end$").Success) { - this.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new Vector_t(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); + this.BonusEndZone[Int32.Parse(Regex.Match(trigger.Entity.Name, "[0-9][0-9]?").Value)] = new VectorT(trigger.AbsOrigin!.X, trigger.AbsOrigin!.Y, trigger.AbsOrigin!.Z); } } } @@ -282,10 +282,10 @@ internal async Task InsertMapInfo([CallerMemberName] string methodName = "") } catch (Exception ex) { - _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to insert map '{Map}'.", - nameof(Map), methodName, this.Name + _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to insert map '{Map}'. Exception: {ExceptionMessage}", + nameof(Map), methodName, this.Name, ex.Message ); - throw; + throw new InvalidOperationException($"Failed to insert map '{Name}'. See inner exception for details.", ex); } } @@ -317,10 +317,10 @@ internal async Task UpdateMapInfo([CallerMemberName] string methodName = "") } catch (Exception ex) { - _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to update map '{Map}'.", - nameof(Map), methodName, this.Name + _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> Failed to update map '{Map}'. Exception Message: {ExceptionMessage}", + nameof(Map), methodName, this.Name, ex.Message ); - throw; + throw new InvalidOperationException($"Failed to update map '{Name}'. See inner exception for details.", ex); } } @@ -453,7 +453,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") stopwatch.Stop(); _logger.LogInformation("[{ClassName}] {MethodName} -> Finished WR.[{Style}].LoadCheckpoints() in {ElapsedMilliseconds}ms | API = {API}", - nameof(Map), methodName, style, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + nameof(Map), methodName, style, stopwatch.ElapsedMilliseconds, Config.Api.GetApiOnly() ); } } @@ -469,7 +469,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") /// Base64 encoded string for the replay_frames internal void SetReplayData(int type, int style, int stage, ReplayFramesString replayFramesBase64, [CallerMemberName] string methodName = "") { - JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new Vector_tConverter(), new QAngle_tConverter() } }; + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = false, Converters = { new VectorTConverter(), new QAngleTConverter() } }; // Decompress the Base64 string string json = Compressor.Decompress(replayFramesBase64.ToString()); @@ -481,12 +481,12 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r { case 0: // Map Replays _logger.LogTrace("[{ClassName}] {MethodName} -> SetReplayData -> [MapWR] Setting run {RunID} {RunTime} (Ticks = {RunTicks}; Frames = {TotalFrames})", - nameof(Map), methodName, this.WR[style].ID, PlayerHUD.FormatTime(this.WR[style].RunTime), this.WR[style].RunTime, frames.Count + nameof(Map), methodName, this.WR[style].ID, PlayerHud.FormatTime(this.WR[style].RunTime), this.WR[style].RunTime, frames.Count ); if (this.ReplayManager.MapWR.IsPlaying) this.ReplayManager.MapWR.Stop(); - this.ReplayManager.MapWR.RecordPlayerName = this.WR[style].Name; + this.ReplayManager.MapWR.RecordPlayerName = this.WR[style].Name!; this.ReplayManager.MapWR.RecordRunTime = this.WR[style].RunTime; this.ReplayManager.MapWR.Frames = frames; this.ReplayManager.MapWR.MapTimeID = this.WR[style].ID; @@ -497,36 +497,21 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r ReplayFrame f = frames[i]; switch (f.Situation) { - case ReplayFrameSituation.START_ZONE_ENTER: + case ReplayFrameSituation.START_ZONE_ENTER or ReplayFrameSituation.START_ZONE_EXIT: this.ReplayManager.MapWR.MapSituations.Add(i); /// Console.WriteLine($"START_ZONE_ENTER: {i} | Situation {f.Situation}"); break; - case ReplayFrameSituation.START_ZONE_EXIT: - this.ReplayManager.MapWR.MapSituations.Add(i); - /// Console.WriteLine($"START_ZONE_EXIT: {i} | Situation {f.Situation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: + case ReplayFrameSituation.STAGE_ZONE_ENTER or ReplayFrameSituation.STAGE_ZONE_EXIT: this.ReplayManager.MapWR.StageEnterSituations.Add(i); /// Console.WriteLine($"STAGE_ZONE_ENTER: {i} | Situation {f.Situation}"); break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - this.ReplayManager.MapWR.StageExitSituations.Add(i); - /// Console.WriteLine($"STAGE_ZONE_EXIT: {i} | Situation {f.Situation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: + case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER or ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: this.ReplayManager.MapWR.CheckpointEnterSituations.Add(i); /// Console.WriteLine($"CHECKPOINT_ZONE_ENTER: {i} | Situation {f.Situation}"); break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - this.ReplayManager.MapWR.CheckpointExitSituations.Add(i); - /// Console.WriteLine($"CHECKPOINT_ZONE_EXIT: {i} | Situation {f.Situation}"); - break; - case ReplayFrameSituation.END_ZONE_ENTER: + case ReplayFrameSituation.END_ZONE_ENTER or ReplayFrameSituation.END_ZONE_EXIT: /// Console.WriteLine($"END_ZONE_ENTER: {i} | Situation {f.Situation}"); break; - case ReplayFrameSituation.END_ZONE_EXIT: - /// Console.WriteLine($"END_ZONE_EXIT: {i} | Situation {f.Situation}"); - break; } } break; @@ -544,7 +529,7 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r this.ReplayManager.AllBonusWR[stage][style].MapID = this.ID; this.ReplayManager.AllBonusWR[stage][style].Frames = frames; this.ReplayManager.AllBonusWR[stage][style].RecordRunTime = this.BonusWR[stage][style].RunTime; - this.ReplayManager.AllBonusWR[stage][style].RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.AllBonusWR[stage][style].RecordPlayerName = this.BonusWR[stage][style].Name!; this.ReplayManager.AllBonusWR[stage][style].MapTimeID = this.BonusWR[stage][style].ID; this.ReplayManager.AllBonusWR[stage][style].Stage = stage; this.ReplayManager.AllBonusWR[stage][style].Type = 1; @@ -555,10 +540,7 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r ReplayFrame f = frames[i]; switch (f.Situation) { - case ReplayFrameSituation.START_ZONE_ENTER: - this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); - break; - case ReplayFrameSituation.END_ZONE_EXIT: + case ReplayFrameSituation.START_ZONE_ENTER or ReplayFrameSituation.END_ZONE_EXIT: this.ReplayManager.AllBonusWR[stage][style].BonusSituations.Add(i); break; } @@ -576,7 +558,7 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r this.ReplayManager.BonusWR.MapID = this.ID; this.ReplayManager.BonusWR.Frames = frames; this.ReplayManager.BonusWR.RecordRunTime = this.BonusWR[stage][style].RunTime; - this.ReplayManager.BonusWR.RecordPlayerName = this.BonusWR[stage][style].Name; + this.ReplayManager.BonusWR.RecordPlayerName = this.BonusWR[stage][style].Name!; this.ReplayManager.BonusWR.MapTimeID = this.BonusWR[stage][style].ID; this.ReplayManager.BonusWR.Stage = stage; this.ReplayManager.BonusWR.Type = 1; @@ -597,7 +579,7 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r this.ReplayManager.AllStageWR[stage][style].MapID = this.ID; this.ReplayManager.AllStageWR[stage][style].Frames = frames; this.ReplayManager.AllStageWR[stage][style].RecordRunTime = this.StageWR[stage][style].RunTime; - this.ReplayManager.AllStageWR[stage][style].RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.AllStageWR[stage][style].RecordPlayerName = this.StageWR[stage][style].Name!; this.ReplayManager.AllStageWR[stage][style].MapTimeID = this.StageWR[stage][style].ID; this.ReplayManager.AllStageWR[stage][style].Stage = stage; this.ReplayManager.AllStageWR[stage][style].Type = 2; @@ -630,7 +612,7 @@ internal void SetReplayData(int type, int style, int stage, ReplayFramesString r this.ReplayManager.StageWR.MapID = this.ID; this.ReplayManager.StageWR.Frames = frames; this.ReplayManager.StageWR.RecordRunTime = this.StageWR[stage][style].RunTime; - this.ReplayManager.StageWR.RecordPlayerName = this.StageWR[stage][style].Name; + this.ReplayManager.StageWR.RecordPlayerName = this.StageWR[stage][style].Name!; this.ReplayManager.StageWR.MapTimeID = this.StageWR[stage][style].ID; this.ReplayManager.StageWR.Stage = stage; this.ReplayManager.StageWR.Type = 2; diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 5b384bb..7f24085 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -10,7 +10,7 @@ public class Player // Timer-related properties public PlayerTimer Timer {get; set;} public PlayerStats Stats {get; set;} - public PlayerHUD HUD {get; set;} + public PlayerHud HUD {get; set;} public ReplayRecorder ReplayRecorder { get; set; } public List SavedLocations { get; set; } public int CurrentSavedLocation { get; set; } @@ -32,7 +32,7 @@ internal Player(CCSPlayerController Controller, CCSPlayer_MovementServices Movem this.SavedLocations = new List(); CurrentSavedLocation = 0; - this.HUD = new PlayerHUD(this); + this.HUD = new PlayerHud(this); } ///
diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index e927ab2..3134572 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -2,16 +2,16 @@ namespace SurfTimer; -public class PlayerHUD +public class PlayerHud { - private Player _player; + private readonly Player _player; - internal PlayerHUD(Player Player) + internal PlayerHud(Player Player) { _player = Player; } - private string FormatHUDElementHTML(string title, string body, string color, string size = "m") + private static string FormatHUDElementHTML(string title, string body, string color, string size = "m") { if (title != "") { @@ -174,8 +174,6 @@ internal void DisplayCheckpointMessages() int style = _player.Timer.Style; int playerCheckpoint = _player.Timer.Checkpoint; - // _player.Controller.PrintToChat($"{ChatColors.Blue}-> PlayerHUD{ChatColors.Default} => Style {ChatColors.Yellow}{style}{ChatColors.Default} | Checkpoint {playerCheckpoint} | WR Time Ticks {SurfTimer.CurrentMap.WR[style].Ticks} | Player Stage {_player.Timer.Stage} (CP {_player.Timer.Checkpoint}) | Player Ticks {_player.Timer.Ticks}"); - 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 @@ -189,10 +187,10 @@ internal void DisplayCheckpointMessages() // Can check checkpoints count instead of try/catch try { - pbTime = _player.Stats.PB[style].Checkpoints[playerCheckpoint].RunTime; - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelX - + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelY - + _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints[playerCheckpoint].StartVelZ); + pbTime = _player.Stats.PB[style].Checkpoints![playerCheckpoint].RunTime; + pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelX + + _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelY + + _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelZ); #if DEBUG Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint} = {pbTime}]"); @@ -255,10 +253,10 @@ internal void DisplayCheckpointMessages() Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)"); #endif - wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].RunTime; - wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelX - + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelY - + SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints[playerCheckpoint].StartVelZ); + wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].RunTime; + wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelX + + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelY + + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelZ); // Reset the string strWrDifference = ""; diff --git a/src/ST-Player/PlayerProfile.cs b/src/ST-Player/PlayerProfile.cs index c905ad8..d9d0452 100644 --- a/src/ST-Player/PlayerProfile.cs +++ b/src/ST-Player/PlayerProfile.cs @@ -43,7 +43,7 @@ internal async Task InitializeAsync([CallerMemberName] string methodName = "") await GetPlayerProfile(); _logger.LogTrace("[{ClassName}] {MethodName} -> InitializeAsync -> [{ConnType}] We got ProfileID {ProfileID} ({PlayerName})", - nameof(PlayerProfile), methodName, Config.API.GetApiOnly() ? "API" : "DB", this.ID, this.Name + nameof(PlayerProfile), methodName, Config.Api.GetApiOnly() ? "API" : "DB", this.ID, this.Name ); } diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index 10b5691..fca7504 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using SurfTimer.Data; using SurfTimer.Shared.DTO; +using SurfTimer.Shared.Entities; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -11,22 +12,19 @@ namespace SurfTimer; /// /// This class stores data for the current run. /// -public class CurrentRun : RunStats // TODO: Inherit from RunStatsEntity instead of RunStats +public class CurrentRun : RunStatsEntity { private readonly ILogger _logger; private readonly IDataAccessService _dataService; + public Dictionary Checkpoints { get; set; } - internal CurrentRun() : base() + internal CurrentRun() { _logger = SurfTimer.ServiceProvider.GetRequiredService>(); _dataService = SurfTimer.ServiceProvider.GetRequiredService(); - } - public override void Reset() - { - base.Reset(); - // Reset other properties as needed (specific for this class) + Checkpoints = new Dictionary(); } /// @@ -37,23 +35,34 @@ public override void Reset() /// Bonus number /// Stage number /// Ticks for the run - used for Stage and Bonus entries - internal async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") + internal async Task SaveMapTime(Player player, short bonus = 0, short stage = 0, int run_ticks = -1, [CallerMemberName] string methodName = "") { string replay_frames = ""; + int style = player.Timer.Style; + int mapTimeId = 0; + short recType; - // /* Test Time Saving */ - // if (methodName != "TestSetPb") - replay_frames = player.ReplayRecorder.TrimReplay(player, stage != 0 ? 2 : bonus != 0 ? 1 : 0, stage == SurfTimer.CurrentMap.Stages); + if (stage != 0) + { + recType = 2; // Stage run + } + else if (bonus != 0) + { + recType = 1; // Bonus run + } + else + { + recType = 0; // Map run + } + + /// Test Time Saving: if (methodName != "TestSetPb") + replay_frames = player.ReplayRecorder.TrimReplay(player, recType, stage == SurfTimer.CurrentMap.Stages); _logger.LogTrace("[{ClassName}] {MethodName} -> Sending total of {Frames} serialized and compressed replay frames.", nameof(CurrentRun), methodName, replay_frames.Length ); var stopwatch = Stopwatch.StartNew(); - int recType = stage != 0 ? 2 : bonus != 0 ? 1 : 0; - int style = player.Timer.Style; - int mapTimeId = 0; - var mapTime = new MapTimeRunDataDto { PlayerID = player.Profile.ID, @@ -117,7 +126,7 @@ internal async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int stopwatch.Stop(); _logger.LogInformation("[{Class}] {Method} -> Finished SaveMapTime for '{Name}' (ID {ID}) in {Elapsed}ms | API = {API}", - nameof(CurrentRun), methodName, player.Profile.Name, mapTimeId, stopwatch.ElapsedMilliseconds, Config.API.GetApiOnly() + nameof(CurrentRun), methodName, player.Profile.Name, mapTimeId, stopwatch.ElapsedMilliseconds, Config.Api.GetApiOnly() ); } @@ -129,9 +138,13 @@ internal async Task SaveMapTime(Player player, int bonus = 0, int stage = 0, int /// Stage to save /// Is it the last stage? /// Run Time (Ticks) for the stage run - internal async Task SaveStageTime(Player player, int stage = -1, int stage_run_time = -1, bool saveLastStage = false) + internal static async Task SaveStageTime(Player player, short stage = -1, int stage_run_time = -1, bool saveLastStage = false) { - // player.Controller.PrintToChat($"{Config.PluginPrefix} SaveStageTime received: {player.Profile.Name}, {stage}, {stage_run_time}, {saveLastStage}"); +#if DEBUG + _logger.LogTrace("[{Class}] -> SaveStageTime received: Name = {Name} | Stage = {Stage} | RunTime = {RunTime} | IsLastStage = {IsLastStage}", + nameof(CurrentRun), player.Profile.Name, stage, stage_run_time, saveLastStage + ); +#endif int pStyle = player.Timer.Style; if ( stage_run_time < SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime || @@ -144,53 +157,48 @@ internal async Task SaveStageTime(Player player, int stage = -1, int stage_run_t { int timeImprove = SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime - stage_run_time; Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_improved", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" + player.Controller.PlayerName, stage, PlayerHud.FormatTime(stage_run_time), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" ); } else if (SurfTimer.CurrentMap.StageWR[stage][pStyle].ID == -1) // No Stage record was set on the map { Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_set", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time)]}" + player.Controller.PlayerName, stage, PlayerHud.FormatTime(stage_run_time)]}" ); } else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].ID == -1) // Player first Stage personal best { player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_set", - stage, PlayerHUD.FormatTime(stage_run_time)]}" + stage, PlayerHud.FormatTime(stage_run_time)]}" ); } else if (player.Stats.StagePB[stage][pStyle] != null && player.Stats.StagePB[stage][pStyle].RunTime > stage_run_time) // Player beating their existing Stage personal best { int timeImprove = player.Stats.StagePB[stage][pStyle].RunTime - stage_run_time; Server.PrintToChatAll($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagepb_improved", - player.Controller.PlayerName, stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(player.Stats.StagePB[stage][pStyle].RunTime)]}" + player.Controller.PlayerName, stage, PlayerHud.FormatTime(stage_run_time), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(player.Stats.StagePB[stage][pStyle].RunTime)]}" ); } player.ReplayRecorder.IsSaving = true; // Save stage run - Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); +#if DEBUG + Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHud.FormatTime(stage_run_time)} ({stage_run_time})"); +#endif await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data - - // AddTimer(1.0f, async () => - // { - // // Save stage run - // Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHUD.FormatTime(stage_run_time)} ({stage_run_time})"); - // await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data - // }); } else if (stage_run_time > SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime && player.Timer.IsStageMode) // Player is behind the Stage WR for the map { int timeImprove = stage_run_time - SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime; player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["stagewr_missed", - stage, PlayerHUD.FormatTime(stage_run_time), PlayerHUD.FormatTime(timeImprove), PlayerHUD.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" + stage, PlayerHud.FormatTime(stage_run_time), PlayerHud.FormatTime(timeImprove), PlayerHud.FormatTime(SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime)]}" ); } } - public void PrintSituations(Player player) + public static void PrintSituations(Player player) { Console.WriteLine($"========================== FOUND SITUATIONS =========================="); for (int i = 0; i < player.ReplayRecorder.Frames.Count; i++) diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index 01c3db2..c551da4 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; -using SurfTimer.Shared.Entities; using System.Runtime.CompilerServices; namespace SurfTimer; @@ -62,115 +61,11 @@ internal PlayerStats([CallerMemberName] string methodName = "") this.StagePB[i][0].Type = 2; initialized++; } - _logger.LogTrace("[{ClassName}] {MethodName} -> PlayerStats -> Initialized {initialized} Stages and Bonuses", + _logger.LogTrace("[{ClassName}] {MethodName} -> PlayerStats -> Initialized {Initialized} Stages and Bonuses", nameof(PlayerStats), methodName, initialized ); } - /// - /// Not used - /// - // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - internal async void LoadMapTime(Player player, int style = 0, [CallerMemberName] string methodName = "") - { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=0"); - if (player_maptime == null) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadMapTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; - } - - PB[style].ID = player_maptime.id; - PB[style].RunTime = player_maptime.run_time; - PB[style].Type = player_maptime.type; - PB[style].StartVelX = player_maptime.start_vel_x; - PB[style].StartVelY = player_maptime.start_vel_y; - PB[style].StartVelZ = player_maptime.start_vel_z; - PB[style].EndVelX = player_maptime.end_vel_x; - PB[style].EndVelY = player_maptime.end_vel_y; - PB[style].EndVelZ = player_maptime.end_vel_z; - // PB[style].RunDate = player_maptime.run_date ?? 0; - PB[style].RunDate = player_maptime.run_date; - - if (player_maptime.checkpoints == null) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadMapTime -> No Checkpoints data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; - } - - foreach (var cp in player_maptime.checkpoints) - { - PB[style].Checkpoints[cp.cp] = new CheckpointEntity(cp.cp, cp.run_time, cp.start_vel_x, cp.start_vel_y, cp.start_vel_z, cp.end_vel_x, cp.end_vel_y, cp.end_vel_z, cp.end_touch, cp.attempts); - } - } - - /// - /// Not used - /// - // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - internal async void LoadStageTime(Player player, int style = 0, [CallerMemberName] string methodName = "") - { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=2"); - if (player_maptime == null) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadStageTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; - } - - foreach (API_MapTime mt in player_maptime) - { - StagePB[mt.stage][style].ID = mt.id; - StagePB[mt.stage][style].RunTime = mt.run_time; - StagePB[mt.stage][style].Type = mt.type; - StagePB[mt.stage][style].StartVelX = mt.start_vel_x; - StagePB[mt.stage][style].StartVelY = mt.start_vel_y; - StagePB[mt.stage][style].StartVelZ = mt.start_vel_z; - StagePB[mt.stage][style].EndVelX = mt.end_vel_x; - StagePB[mt.stage][style].EndVelY = mt.end_vel_y; - StagePB[mt.stage][style].EndVelZ = mt.end_vel_z; - // StagePB[mt.stage][style].RunDate = mt.run_date ?? 0; - StagePB[mt.stage][style].RunDate = mt.run_date; - } - } - - /// - /// Not used - /// - // API - Can be replaced with `ENDPOINT_MAP_GET_PB_BY_PLAYER` - internal async void LoadBonusTime(Player player, int style = 0, [CallerMemberName] string methodName = "") - { - var player_maptime = await ApiMethod.GET($"/surftimer/playerspecificdata?player_id={player.Profile.ID}&map_id={SurfTimer.CurrentMap.ID}&style={style}&type=1"); - if (player_maptime == null) - { - _logger.LogTrace("[{ClassName}] {MethodName} -> LoadBonusTime -> No MapTime data found for Player {PlayerName} (ID {PlayerID}).", - nameof(PlayerStats), methodName, player.Profile.Name, player.Profile.ID - ); - return; - } - - foreach (API_MapTime mt in player_maptime) - { - BonusPB[mt.stage][style].ID = mt.id; - BonusPB[mt.stage][style].RunTime = mt.run_time; - BonusPB[mt.stage][style].Type = mt.type; - BonusPB[mt.stage][style].StartVelX = mt.start_vel_x; - BonusPB[mt.stage][style].StartVelY = mt.start_vel_y; - BonusPB[mt.stage][style].StartVelZ = mt.start_vel_z; - BonusPB[mt.stage][style].EndVelX = mt.end_vel_x; - BonusPB[mt.stage][style].EndVelY = mt.end_vel_y; - BonusPB[mt.stage][style].EndVelZ = mt.end_vel_z; - // BonusPB[mt.stage][style].RunDate = mt.run_date ?? 0; - BonusPB[mt.stage][style].RunDate = mt.run_date; - } - } - - /// /// Loads the player's map time data from the database along with their ranks. For all types and styles (may not work correctly for Stages/Bonuses) /// `Checkpoints` are loaded separately from another method in the `PresonalBest` class as it uses the unique `ID` for the run. (This method calls it if needed) diff --git a/src/ST-Player/PlayerStats/RunStats.cs b/src/ST-Player/PlayerStats/RunStats.cs deleted file mode 100644 index cfd4067..0000000 --- a/src/ST-Player/PlayerStats/RunStats.cs +++ /dev/null @@ -1,41 +0,0 @@ -using SurfTimer.Shared.Entities; - -namespace SurfTimer; - -public abstract class RunStats -{ - public Dictionary Checkpoints { get; set; } - public int ID { get; set; } - public int RunTime { 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; } - public string ReplayFrames { get; set; } = ""; - - protected RunStats() - { - Checkpoints = new Dictionary(); - RunTime = 0; - StartVelX = 0.0f; - StartVelY = 0.0f; - StartVelZ = 0.0f; - EndVelX = 0.0f; - EndVelY = 0.0f; - EndVelZ = 0.0f; - RunDate = 0; - } - - // Shared Method - public virtual void Reset() - { - Checkpoints.Clear(); - RunTime = 0; - StartVelX = StartVelY = StartVelZ = 0.0f; - EndVelX = EndVelY = EndVelZ = 0.0f; - RunDate = 0; - } -} diff --git a/src/ST-Player/PlayerTimer.cs b/src/ST-Player/PlayerTimer.cs index 5211f35..517adf6 100644 --- a/src/ST-Player/PlayerTimer.cs +++ b/src/ST-Player/PlayerTimer.cs @@ -13,11 +13,10 @@ public class PlayerTimer public bool IsBonusMode { get; set; } = false; // Bonus mode toggle // Tracking - public int Stage { get; set; } = 0; // Current stage tracker - 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 { get; set; } = 0; // To-do: functionality for player to change this value and the actual styles implementation - Current style tracker + public short Stage { get; set; } = 0; // Current stage tracker + public short Checkpoint { get; set; } = 0; // Current checkpoint tracker + public short Bonus { get; set; } = 0; // To-do: bonus implementation - Current bonus tracker + public short 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 @@ -44,7 +43,6 @@ public void Reset() this.IsPracticeMode = false; this.IsStageMode = false; this.IsBonusMode = false; - // this.CurrentRunData.Reset(); } public void Pause() diff --git a/src/ST-Player/Replay/ReplayFrame.cs b/src/ST-Player/Replay/ReplayFrame.cs index ede7faa..e5ce8fb 100644 --- a/src/ST-Player/Replay/ReplayFrame.cs +++ b/src/ST-Player/Replay/ReplayFrame.cs @@ -34,12 +34,12 @@ public class ReplayFrame public ReplayFrameSituation Situation { get; set; } = ReplayFrameSituation.NONE; public uint Flags { get; set; } - public Vector_t GetPos() + public VectorT GetPos() { - return new Vector_t(this.pos[0], this.pos[1], this.pos[2]); + return new VectorT(this.pos[0], this.pos[1], this.pos[2]); } - public QAngle_t GetAng() + public QAngleT GetAng() { - return new QAngle_t(this.ang[0], this.ang[1], this.ang[2]); + return new QAngleT(this.ang[0], this.ang[1], this.ang[2]); } } diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 92319e4..025cdb3 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -1,9 +1,9 @@ -using System.Runtime.CompilerServices; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; namespace SurfTimer; @@ -184,7 +184,7 @@ internal void Tick() bool is_on_ground = (current_frame.Flags & (uint)PlayerFlags.FL_ONGROUND) != 0; - Vector_t velocity = (current_frame_pos - current_pos) * 64; + VectorT velocity = (current_frame_pos - current_pos) * 64; if (is_on_ground) this.Controller.PlayerPawn.Value.MoveType = MoveType_t.MOVETYPE_WALK; @@ -206,8 +206,6 @@ internal void Tick() if (this.CurrentFrameTick >= this.Frames.Count) this.ResetReplay(); - // if(RepeatCount != -1) // Spam City - // Console.WriteLine($"CS2 Surf DEBUG >> internal class ReplayPlayer -> Tick -> ====================> {this.RepeatCount} <===================="); } internal void LoadReplayData(int repeat_count = -1, [CallerMemberName] string methodName = "") @@ -215,16 +213,33 @@ internal void LoadReplayData(int repeat_count = -1, [CallerMemberName] string me if (!this.IsPlayable || !this.IsEnabled) return; + string replayType; + switch (this.Type) + { + case 1: + replayType = "Bonus Replay"; + break; + case 2: + replayType = "Stage Replay"; + break; + case 0: + replayType = "Map Replay"; + break; + default: + replayType = "Unknown Type"; + break; + } + if (this.MapID == -1) { _logger.LogWarning("[{ClassName}] {MethodName} -> [{Type}] No replay data found for Player. MapID {MapID} | MapTimeID {MapTimeID} | RecordPlayerName {RecordPlayerName}", - nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : this.Type == 0 ? "Map Replay" : "Unknown Type"), this.MapID, this.MapTimeID, RecordPlayerName + nameof(ReplayPlayer), methodName, replayType, this.MapID, this.MapTimeID, RecordPlayerName ); return; } _logger.LogTrace("[{ClassName}] {MethodName} -> [{Type}] Loaded replay data for Player '{RecordPlayerName}' | MapTime ID: {MapTimeID} | Repeat {Repeat} | Frames {TotalFrames} | Ticks {RecordTicks}", - nameof(ReplayPlayer), methodName, (this.Type == 2 ? "Stage Replay" : this.Type == 1 ? "Bonus Replay" : this.Type == 0 ? "Map Replay" : "Unknown Type"), this.RecordPlayerName, this.MapTimeID, repeat_count, this.Frames.Count, this.RecordRunTime + nameof(ReplayPlayer), methodName, replayType, this.RecordPlayerName, this.MapTimeID, repeat_count, this.Frames.Count, this.RecordRunTime ); this.ResetReplay(); @@ -253,7 +268,7 @@ internal void FormatBotName([CallerMemberName] string methodName = "") SchemaString bot_name = new SchemaString(this.Controller!, "m_iszPlayerName"); - string replay_name = $"[{prefix}] {this.RecordPlayerName} | {PlayerHUD.FormatTime(this.RecordRunTime)}"; + string replay_name = $"[{prefix}] {this.RecordPlayerName} | {PlayerHud.FormatTime(this.RecordRunTime)}"; if (this.RecordRunTime <= 0) replay_name = $"[{prefix}] {this.RecordPlayerName}"; @@ -261,9 +276,10 @@ internal void FormatBotName([CallerMemberName] string methodName = "") Server.NextFrame(() => Utilities.SetStateChanged(this.Controller!, "CBasePlayerController", "m_iszPlayerName") ); - +#if DEBUG // _logger.LogTrace("[{ClassName}] {MethodName} -> Changed replay bot name from '{OldName}' to '{NewName}'", // nameof(ReplayPlayer), methodName, bot_name, replay_name // ); +#endif } } \ No newline at end of file diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 311842b..68b0f45 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -93,32 +93,10 @@ internal void Tick(Player player, [CallerMemberName] string methodName = "") 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; - /* - switch (this.CurrentSituation) - { - case ReplayFrameSituation.START_ZONE_ENTER: - player.Controller.PrintToChat($"Start Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.START_ZONE_EXIT: - player.Controller.PrintToChat($"Start Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_ENTER: - player.Controller.PrintToChat($"Stage Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.STAGE_ZONE_EXIT: - player.Controller.PrintToChat($"Stage Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_ENTER: - player.Controller.PrintToChat($"Checkpoint Enter: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - case ReplayFrameSituation.CHECKPOINT_ZONE_EXIT: - player.Controller.PrintToChat($"Checkpoint Exit: {this.Frames.Count} | Situation {this.CurrentSituation}"); - break; - } - */ + /// var player_button = player.Controller.Pawn.Value.MovementServices!.Buttons.ButtonStates[0]; + /// var player_move_type = player.Controller.Pawn.Value.MoveType; + var frame = new ReplayFrame { pos = [player_pos.X, player_pos.Y, player_pos.Z], @@ -133,37 +111,13 @@ internal void Tick(Player player, [CallerMemberName] string methodName = "") this.CurrentSituation = ReplayFrameSituation.NONE; } - internal string SerializeReplay() - { - // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; - // string replay_frames = JsonSerializer.Serialize(Frames, options); - string replay_frames = JsonSerializer.Serialize(Frames); - return Compressor.Compress(replay_frames); - } - - internal string SerializeReplayPortion(int start_idx, int end_idx) // Not used anymore - { - // JsonSerializerOptions options = new JsonSerializerOptions {WriteIndented = false, Converters = { new VectorConverter(), new QAngleConverter() }}; - // string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx), options); - string replay_frames = JsonSerializer.Serialize(Frames.GetRange(start_idx, end_idx)); - return Compressor.Compress(replay_frames); - } - - internal void SetLastTickSituation(ReplayFrameSituation situation) - { - if (this.Frames.Count == 0) - return; - - this.Frames[this.Frames.Count - 2].Situation = situation; - } - - internal string TrimReplay(Player player, int type = 0, bool lastStage = false, [CallerMemberName] string methodName = "") + internal string TrimReplay(Player player, short type = 0, bool lastStage = false, [CallerMemberName] string methodName = "") { this.IsSaving = true; List? trimmed_frames = new List(); - _logger.LogTrace(">>> [{ClassName}] {MethodName} -> Trimming replay for '{PlayerName}' | type = {type} | lastStage = {lastStage} ", + _logger.LogTrace(">>> [{ClassName}] {MethodName} -> Trimming replay for '{PlayerName}' | type = {Type} | lastStage = {LastStage} ", nameof(ReplayRecorder), methodName, player.Profile.Name, type, lastStage ); @@ -172,7 +126,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, _logger.LogError("[{ClassName}] {MethodName} -> There are no Frames available for replay trimming for player {Name}", nameof(ReplayRecorder), methodName, player.Profile.Name ); - throw new Exception("There are no Frames available for trimming"); + throw new InvalidOperationException("There are no Frames available for trimming"); } switch (type) { @@ -209,7 +163,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, var start_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); var end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {start_enter_index} | last start exit {start_exit_index} | end enter {end_enter_index}", + _logger.LogInformation("[{ClassName}] {MethodName} -> Trimming Map Run replay. Last start enter {StartEnterIndex} | last start exit {StartExitIndex} | end enter {EndEnterIndex}", nameof(ReplayRecorder), methodName, start_enter_index, start_exit_index, end_enter_index); if (start_enter_index == -1) @@ -218,7 +172,6 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.IsStageMode, player.Timer.IsBonusMode ); start_enter_index = start_enter_index == -1 ? 0 : start_enter_index; - // start_exit_index = start_exit_index == -1 ? 0 : start_exit_index; } if (start_enter_index != -1 && start_exit_index != -1 && end_enter_index != -1) @@ -234,7 +187,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, } else { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {start_enter_index} | start_exit_index = {start_exit_index} | end_enter_index = {end_enter_index}", + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Map replay for '{Name}'. start_enter_index = {StartEnterIndex} | start_exit_index = {StartExitIndex} | end_enter_index = {EndEnterIndex}", nameof(ReplayRecorder), methodName, player.Profile.Name, start_enter_index, start_exit_index, end_enter_index ); @@ -249,13 +202,13 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, var bonus_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_ENTER); var bonus_exit_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.START_ZONE_EXIT); var bonus_end_enter_index = Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER); - _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {bonus_enter_index}, last start exit {bonus_exit_index}, end enter {bonus_end_enter_index}", + _logger.LogInformation("[{ClassName}] {MethodName} -> Looking for Bonus Run replay trim indexes. Last start enter {BonusEnterIndex}, last start exit {BonusExitIndex}, end enter {BonusEndEnterIndex}", nameof(ReplayRecorder), methodName, bonus_enter_index, bonus_exit_index, bonus_end_enter_index ); if (bonus_enter_index == -1) { - _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Settinng 'bonus_enter_index' to '0'", + _logger.LogError("[{ClassName}] {MethodName} -> Player '{Name}' got '-1' for bonus_enter_index during Bonus ({BonusNumber}) replay trimming. Setting 'bonus_enter_index' to '0'", nameof(ReplayRecorder), methodName, player.Profile.Name, player.Timer.Bonus ); bonus_enter_index = 0; @@ -267,7 +220,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, int endIndex = CalculateEndIndex(bonus_end_enter_index, Frames.Count, Config.ReplaysPre); new_frames = GetTrimmedFrames(startIndex, endIndex); - _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {startIndex} to {endIndex} ({new_frames}) - from total {OldFrames}", + _logger.LogDebug("<<< [{ClassName}] {MethodName} -> Trimmed Bonus replay from {StartIndex} to {EndIndex} ({NewFrames}) - from total {OldFrames}", nameof(ReplayRecorder), methodName, startIndex, endIndex, new_frames.Count, this.Frames.Count ); @@ -275,7 +228,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, } else { - _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {bonus_enter_index} | bonus_exit_index = {bonus_exit_index} | bonus_end_enter_index = {bonus_end_enter_index}", + _logger.LogError("[{ClassName}] {MethodName} -> Got a '-1' value while trimming Bonus ({BonusNumber}) replay for '{Name}'. bonus_enter_index = {BonusEnterIndex} | bonus_exit_index = {BonusExitIndex} | bonus_end_enter_index = {BonusEndEnterIndex}", nameof(ReplayRecorder), methodName, player.Timer.Bonus, player.Profile.Name, bonus_enter_index, bonus_exit_index, bonus_end_enter_index ); @@ -354,41 +307,7 @@ internal string TrimReplay(Player player, int type = 0, bool lastStage = false, return new_frames; } - internal int LastEnterTick(int start_idx = 0) - { - if (start_idx == 0) - start_idx = this.Frames.Count - 1; - for (int i = start_idx; i > 0; i--) - { - if ( - this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_ENTER || - this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_ENTER || - this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_ENTER || - this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_ENTER - ) - return i; - } - return 0; - } - - internal int LastExitTick(int start_idx = 0) - { - if (start_idx == 0) - start_idx = this.Frames.Count - 1; - for (int i = start_idx; i > 0; i--) - { - if ( - this.Frames[i].Situation == ReplayFrameSituation.START_ZONE_EXIT || - this.Frames[i].Situation == ReplayFrameSituation.STAGE_ZONE_EXIT || - this.Frames[i].Situation == ReplayFrameSituation.CHECKPOINT_ZONE_EXIT || - this.Frames[i].Situation == ReplayFrameSituation.END_ZONE_EXIT - ) - return i; - } - return 0; - } - - private int CalculateStartIndex(int start_enter, int start_exit, int buffer) + private static int CalculateStartIndex(int start_enter, int start_exit, int buffer) { if (start_exit - (buffer * 2) >= start_enter) return start_exit - (buffer * 2); @@ -400,26 +319,22 @@ private int CalculateStartIndex(int start_enter, int start_exit, int buffer) return start_enter; } - private int CalculateEndIndex(int end_enter, int totalFrames, int buffer) + private static int CalculateEndIndex(int end_enter, int totalFrames, int buffer) { if (end_enter + (buffer * 2) < totalFrames) { - // _logger.LogTrace("CalculateEndIndex 1st -> end_enter + (buffer * 2) < totalFrames ({Logic}) -> {Value}", end_enter + (buffer * 2) < totalFrames, end_enter + (buffer * 2)); return end_enter + (buffer * 2); } else if (end_enter + buffer < totalFrames) { - // _logger.LogTrace("CalculateEndIndex 2nd -> end_enter + buffer < totalFrames ({Logic}) -> {Value}", end_enter + buffer < totalFrames, end_enter + buffer); return end_enter + buffer; } else if (end_enter + (buffer / 2) < totalFrames) { - // _logger.LogTrace("CalculateEndIndex 3rd -> end_enter + (buffer / 2) < totalFrames ({Logic}) -> {Value}", end_enter + (buffer / 2) < totalFrames, end_enter + (buffer / 2)); return end_enter + (buffer / 2); } else { - // _logger.LogTrace("CalculateEndIndex 4th -> Last -> {Value}", end_enter); return end_enter; } } diff --git a/src/ST-Player/Saveloc/SavelocFrame.cs b/src/ST-Player/Saveloc/SavelocFrame.cs index 1862cf4..e95cfb4 100644 --- a/src/ST-Player/Saveloc/SavelocFrame.cs +++ b/src/ST-Player/Saveloc/SavelocFrame.cs @@ -2,8 +2,8 @@ namespace SurfTimer; public class SavelocFrame { - public Vector_t Pos { get; set; } = new Vector_t(0, 0, 0); - public QAngle_t Ang { get; set; } = new QAngle_t(0, 0, 0); - public Vector_t Vel { get; set; } = new Vector_t(0, 0, 0); - public int Tick { get; set; } = 0; + public VectorT Pos { get; set; } = new VectorT(0, 0, 0); + public QAngleT Ang { get; set; } = new QAngleT(0, 0, 0); + public VectorT Vel { get; set; } = new VectorT(0, 0, 0); + public int Tick { get; set; } = 0; } diff --git a/src/ST-API/ConVar.cs b/src/ST-UTILS/ConVar.cs similarity index 100% rename from src/ST-API/ConVar.cs rename to src/ST-UTILS/ConVar.cs diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 6140ee1..0e11921 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -1,25 +1,26 @@ +using CounterStrikeSharp.API; +using System.Collections.Immutable; using System.Reflection; using System.Text.Json; -using CounterStrikeSharp.API; namespace SurfTimer; public static class Config { public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; - public static string PluginPrefix = LocalizationService.LocalizerNonNull["prefix"]; + public static readonly string PluginPrefix = LocalizationService.LocalizerNonNull["prefix"]; public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; - public static string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; - public static string ApiUrl => API.GetApiUrl(); - public static string DbConnectionString => MySQL.GetConnectionString(); + public static readonly string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; + public static string ApiUrl => Api.GetApiUrl(); + public static string DbConnectionString => MySql.GetConnectionString(); /// /// Placeholder for amount of styles /// - public static List Styles = new List { 0 }; // Add all supported style IDs + public static readonly ImmutableList Styles = [0]; // Add all supported style IDs - public static bool ReplaysEnabled => true; - public static int ReplaysPre => 64; + public static readonly bool ReplaysEnabled = TimerSettings.GetReplaysEnabled(); + public static readonly int ReplaysPre = TimerSettings.GetReplaysPre(); // Helper class/methods for configuration loading private static class ConfigLoader @@ -45,13 +46,17 @@ private static class TimerSettings private const string TIMER_CONFIG_PATH = "/csgo/cfg/SurfTimer/timer_settings.json"; private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(TIMER_CONFIG_PATH); - public static string GetPrefix() + public static bool GetReplaysEnabled() { - return ConfigDocument.RootElement.GetProperty("prefix").GetString()!; + return ConfigDocument.RootElement.GetProperty("replays_enabled").GetBoolean(); + } + public static int GetReplaysPre() + { + return ConfigDocument.RootElement.GetProperty("replays_pre").GetInt32(); } } - public static class API + public static class Api { private const string API_CONFIG_PATH = "/csgo/cfg/SurfTimer/api_config.json"; private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(API_CONFIG_PATH); @@ -108,7 +113,7 @@ public static class Endpoints } } - public static class MySQL + public static class MySql { private const string DB_CONFIG_PATH = "/csgo/cfg/SurfTimer/database.json"; private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); @@ -128,8 +133,6 @@ public static string GetConnectionString() string connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; - // Console.WriteLine($"============= [CS2 Surf] Extracted connection string: {connString}"); - return connString; } diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index d9c509e..53fc9ad 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -23,7 +23,7 @@ public async Task PingAccessService([CallerMemberName] string methodName = try { var response = await ApiMethod.GET>( - string.Format(Config.API.Endpoints.ENDPOINT_PING, (double)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + string.Format(Config.Api.Endpoints.ENDPOINT_PING, (double)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) ); if (response != null && response.ContainsKey("clientUnix")) @@ -53,7 +53,7 @@ public async Task> LoadCheckpointsAsync(int ru { var checkpoints = await ApiMethod .GET>( - string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) ); if (checkpoints == null || checkpoints.Count == 0) return new Dictionary(); @@ -68,9 +68,9 @@ public async Task> LoadCheckpointsAsync(int ru public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") { string url = pbId == null || pbId == -1 - ? string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, + ? string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, playerId, mapId, type, style) - : string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, + : string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, pbId.Value); var apiResult = await ApiMethod.GET(url); @@ -89,7 +89,7 @@ public async Task> LoadCheckpointsAsync(int ru public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") { var mapInfo = await ApiMethod.GET( - string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); if (mapInfo != null) { @@ -109,11 +109,12 @@ public async Task> LoadCheckpointsAsync(int ru public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") { - var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_MAP_INSERT_INFO, mapInfo); + var postResponse = await ApiMethod.POST(Config.Api.Endpoints.ENDPOINT_MAP_INSERT_INFO, mapInfo); if (postResponse == null || postResponse.Id <= 0) { - throw new Exception($"API failed to insert map '{mapInfo.Name}'."); + Exception ex = new Exception($"API failed to insert map '{mapInfo.Name}'."); + throw ex; } return postResponse.Id; @@ -122,18 +123,19 @@ public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] str public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") { var response = await ApiMethod.PUT( - string.Format(Config.API.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapId), mapInfo + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapId), mapInfo ); if (response == null) { - throw new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapId})."); + Exception ex = new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapId})."); + throw ex; } } public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") { var apiRuns = await ApiMethod.GET>( - string.Format(string.Format(Config.API.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId))); + string.Format(string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId))); return apiRuns!; } @@ -143,7 +145,7 @@ public async Task> GetMapRecordRunsAsync(int mapId, [ public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { var player = await ApiMethod.GET( - string.Format(Config.API.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); + string.Format(Config.Api.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); if (player != null) { @@ -162,11 +164,12 @@ public async Task> GetMapRecordRunsAsync(int mapId, [ public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [CallerMemberName] string methodName = "") { - var postResponse = await ApiMethod.POST(Config.API.Endpoints.ENDPOINT_PP_INSERT_PROFILE, profile); + var postResponse = await ApiMethod.POST(Config.Api.Endpoints.ENDPOINT_PP_INSERT_PROFILE, profile); if (postResponse == null || postResponse.Id <= 0) { - throw new Exception($"API failed to insert Player Profile for '{profile.Name}'."); + Exception ex = new Exception($"API failed to insert Player Profile for '{profile.Name}'."); + throw ex; } return postResponse.Id; @@ -175,12 +178,13 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [Calle public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") { var response = await ApiMethod.PUT( - string.Format(Config.API.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, playerId), profile + string.Format(Config.Api.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, playerId), profile ); if (response == null) { - throw new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {playerId})."); + Exception ex = new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {playerId})."); + throw ex; } } @@ -189,12 +193,13 @@ public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerI public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") { var apiResponse = await ApiMethod.GET>( - string.Format(Config.API.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) + string.Format(Config.Api.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) ); if (apiResponse == null) { - throw new Exception($"API failed to GET MapTime entries for PlayerID = {playerId} and MapID = {mapId}."); + Exception ex = new Exception($"API failed to GET MapTime entries for PlayerID '{playerId}' and MapID '{mapId}'."); + throw ex; } _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", @@ -252,13 +257,14 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb */ var postResponse = await ApiMethod.POST( - Config.API.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, + Config.Api.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, mapTime ); if (postResponse == null || postResponse.Inserted <= 0) { - throw new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}."); + Exception ex = new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); + throw ex; } _logger.LogDebug( @@ -315,13 +321,14 @@ public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTime */ var postResponse = await ApiMethod.PUT( - string.Format(Config.API.Endpoints.ENDPOINT_CR_UPDATE_MAP_TIME, mapTimeId), + string.Format(Config.Api.Endpoints.ENDPOINT_CR_UPDATE_MAP_TIME, mapTimeId), mapTime ); if (postResponse == null || postResponse.Inserted <= 0) { - throw new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); + Exception ex = new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); + throw ex; } _logger.LogDebug( diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index 3ee5247..3de0cf6 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -23,7 +23,7 @@ public async Task PingAccessService([CallerMemberName] string methodName = { try { - using var result = await SurfTimer.DB.QueryAsync(Config.MySQL.Queries.DB_QUERY_PING); + using var result = await SurfTimer.DB.QueryAsync(Config.MySql.Queries.DB_QUERY_PING); if (result.HasRows) { @@ -53,7 +53,7 @@ public async Task> LoadCheckpointsAsync(int ru var dict = new Dictionary(); using (var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_CPS, runId))) + string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_CPS, runId))) { if (results == null || !results.HasRows) return dict; @@ -61,7 +61,7 @@ public async Task> LoadCheckpointsAsync(int ru while (await results.ReadAsync()) { var cp = new CheckpointEntity( - results.GetInt32("cp"), + results.GetInt16("cp"), results.GetInt32("run_time"), results.GetFloat("start_vel_x"), results.GetFloat("start_vel_y"), @@ -91,9 +91,9 @@ public async Task> LoadCheckpointsAsync(int ru ); string sql = pbId == null || pbId == -1 - ? string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, + ? string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, playerId, mapId, type, style) - : string.Format(Config.MySQL.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, + : string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, pbId.Value); using var results = await SurfTimer.DB.QueryAsync(sql); @@ -120,9 +120,9 @@ public async Task> LoadCheckpointsAsync(int ru public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") { using var mapData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(mapName))); + string.Format(Config.MySql.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(mapName))); - if (mapData.HasRows && mapData.Read()) + if (mapData.HasRows && await mapData.ReadAsync()) { _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data", nameof(MySqlDataAccessService), methodName @@ -137,7 +137,7 @@ public async Task> LoadCheckpointsAsync(int ru public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") { var (rowsWritten, lastId) = await SurfTimer.DB.WriteAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_INSERT_INFO, + string.Format(Config.MySql.Queries.DB_QUERY_MAP_INSERT_INFO, MySqlHelper.EscapeString(mapInfo.Name), MySqlHelper.EscapeString(mapInfo.Author), mapInfo.Tier, @@ -149,7 +149,8 @@ public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] str if (rowsWritten != 1) { - throw new Exception($"Failed to insert new map '{mapInfo.Name}' into database."); + Exception ex = new Exception($"Failed to insert new map '{mapInfo.Name}' into database. Rows written: {rowsWritten}"); + throw ex; } return (int)lastId; @@ -158,7 +159,7 @@ public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] str public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") { string updateQuery = string.Format( - Config.MySQL.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, + Config.MySql.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), mapInfo.Stages, mapInfo.Bonuses, @@ -168,10 +169,11 @@ public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberNam mapId ); - var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(updateQuery); + var (rowsUpdated, _) = await SurfTimer.DB.WriteAsync(updateQuery); if (rowsUpdated != 1) { - throw new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapId}) in database."); + Exception ex = new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapId}) in database."); + throw ex; } } @@ -180,11 +182,11 @@ public async Task> GetMapRecordRunsAsync(int mapId, [ var runs = new List(); using var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, mapId)); + string.Format(Config.MySql.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, mapId)); if (results.HasRows) { - while (results.Read()) + while (await results.ReadAsync()) { runs.Add(new MapTimeRunDataEntity(results)); } @@ -198,9 +200,9 @@ public async Task> GetMapRecordRunsAsync(int mapId, [ public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") { using var playerData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_PP_GET_PROFILE, steamId)); + string.Format(Config.MySql.Queries.DB_QUERY_PP_GET_PROFILE, steamId)); - if (playerData.HasRows && playerData.Read()) + if (playerData.HasRows && await playerData.ReadAsync()) { return new PlayerProfileEntity(playerData); } @@ -212,7 +214,7 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [Calle { int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync(string.Format( - Config.MySQL.Queries.DB_QUERY_PP_INSERT_PROFILE, + Config.MySql.Queries.DB_QUERY_PP_INSERT_PROFILE, MySqlConnector.MySqlHelper.EscapeString(profile.Name), profile.SteamID, profile.Country, @@ -221,7 +223,10 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [Calle 1)); if (rowsInserted != 1) - throw new Exception($"Failed to insert new player '{profile.Name}' ({profile.SteamID})."); + { + Exception ex = new Exception($"Failed to insert new player '{profile.Name}' ({profile.SteamID})."); + throw ex; + } return (int)lastId; } @@ -229,15 +234,18 @@ public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [Calle public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") { int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var (rowsUpdated, lastId) = await SurfTimer.DB.WriteAsync(string.Format( - Config.MySQL.Queries.DB_QUERY_PP_UPDATE_PROFILE, + var (rowsUpdated, _) = await SurfTimer.DB.WriteAsync(string.Format( + Config.MySql.Queries.DB_QUERY_PP_UPDATE_PROFILE, profile.Country, lastSeen, playerId, MySqlConnector.MySqlHelper.EscapeString(profile.Name))); if (rowsUpdated != 1) - throw new Exception($"Failed to update player '{profile.Name}' ({profile.SteamID})."); + { + Exception ex = new Exception($"Failed to update player '{profile.Name}' ({profile.SteamID})."); + throw ex; + } } @@ -247,11 +255,11 @@ public async Task> GetPlayerMapTimesAsync(int playerI var mapTimes = new List(); using var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySQL.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, playerId, mapId)); + string.Format(Config.MySql.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, playerId, mapId)); if (results.HasRows) { - while (results.Read()) + while (await results.ReadAsync()) { mapTimes.Add(new MapTimeRunDataEntity(results)); } @@ -266,7 +274,7 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb { var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync( string.Format( - Config.MySQL.Queries.DB_QUERY_CR_INSERT_TIME, + Config.MySql.Queries.DB_QUERY_CR_INSERT_TIME, mapTime.PlayerID, mapTime.MapID, mapTime.Style, @@ -285,7 +293,8 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb if (rowsInserted <= 0) { - throw new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerID}."); + Exception ex = new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerID}."); + throw ex; } // Write the checkpoints after we have the `lastId` @@ -295,7 +304,7 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb foreach (var cp in mapTime.Checkpoints.Values) { commands.Add(string.Format( - Config.MySQL.Queries.DB_QUERY_CR_INSERT_CP, + Config.MySql.Queries.DB_QUERY_CR_INSERT_CP, lastId, cp.CP, cp.RunTime, cp.StartVelX, cp.StartVelY, cp.StartVelZ, cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); } @@ -307,6 +316,7 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "") { + await Task.Yield(); throw new NotImplementedException(); } } diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 0567739..8095735 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -9,7 +9,7 @@ namespace SurfTimer; unsafe static class Extensions { - public static void Teleport(this CBaseEntity entity, Vector_t? position = null, QAngle_t? angles = null, Vector_t? velocity = null) + public static void Teleport(this CBaseEntity entity, VectorT? position = null, QAngleT? angles = null, VectorT? velocity = null) { Guard.IsValidEntity(entity); @@ -39,11 +39,11 @@ public static void Teleport(this CBaseEntity entity, Vector_t? position = null, (nint)pAng, (nint)pVel); } - public static (Vector_t fwd, Vector_t right, Vector_t up) AngleVectors(this QAngle vec) => vec.ToQAngle_t().AngleVectors(); - public static void AngleVectors(this QAngle vec, out Vector_t fwd, out Vector_t right, out Vector_t up) => vec.ToQAngle_t().AngleVectors(out fwd, out right, out up); + public static (VectorT fwd, VectorT right, VectorT up) AngleVectors(this QAngle vec) => vec.ToQAngle_t().AngleVectors(); + public static void AngleVectors(this QAngle vec, out VectorT fwd, out VectorT right, out VectorT up) => vec.ToQAngle_t().AngleVectors(out fwd, out right, out up); - public static Vector_t ToVector_t(this Vector vec) => new(vec.Handle); - public static QAngle_t ToQAngle_t(this QAngle vec) => new(vec.Handle); + public static VectorT ToVector_t(this Vector vec) => new(vec.Handle); + public static QAngleT ToQAngle_t(this QAngle vec) => new(vec.Handle); public static void SetCollisionGroup(this CCSPlayerController controller, CollisionGroup collisionGroup) { diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index f5fdf95..c0305e6 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -1,10 +1,10 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Localization; -using CounterStrikeSharp.API.Core; +using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; -using CounterStrikeSharp.API; using SurfTimer.Data; namespace SurfTimer; @@ -51,7 +51,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); // Single instance for 1 Map object services.AddScoped(provider => - Config.API.GetApiOnly() + Config.Api.GetApiOnly() ? new ApiDataAccessService() : new MySqlDataAccessService() ); diff --git a/src/ST-UTILS/Structs/QAngleT.cs b/src/ST-UTILS/Structs/QAngleT.cs new file mode 100644 index 0000000..d0c1dfe --- /dev/null +++ b/src/ST-UTILS/Structs/QAngleT.cs @@ -0,0 +1,143 @@ +using CounterStrikeSharp.API.Core; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SurfTimer; + +public struct QAngleT : IAdditionOperators, + ISubtractionOperators, + IMultiplyOperators, + IDivisionOperators +{ + private float x, y, z; + + public float X + { + readonly get => x; + set => x = value; + } + + public float Y + { + readonly get => y; + set => y = value; + } + + public float Z + { + readonly get => z; + set => z = value; + } + + public const int SIZE = 3; + + public unsafe float this[int i] + { + readonly get + { + if (i < 0 || i > SIZE) + { + Exception ex = new IndexOutOfRangeException($"Index {i} is out of range for QAngleT. Valid range is 0 to {SIZE}."); + throw ex; + } + + fixed (void* ptr = &this) + { + return Unsafe.Read(Unsafe.Add(ptr, i)); + } + } + set + { + if (i < 0 || i > SIZE) + { + Exception ex = new IndexOutOfRangeException($"Index {i} is out of range for QAngleT. Valid range is 0 to {SIZE}."); + throw ex; + } + + fixed (void* ptr = &this) + { + Unsafe.Write(Unsafe.Add(ptr, i), value); + } + } + } + + public QAngleT() + { + } + + public unsafe QAngleT(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) + { + } + + public QAngleT(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public QAngleT(ReadOnlySpan values) + { + if (values.Length < SIZE) + { + throw new ArgumentOutOfRangeException(nameof(values)); + } + + this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + public unsafe (VectorT fwd, VectorT right, VectorT up) AngleVectors() + { + VectorT fwd = default, right = default, up = default; + + nint pFwd = (nint)Unsafe.AsPointer(ref fwd); + nint pRight = (nint)Unsafe.AsPointer(ref right); + nint pUp = (nint)Unsafe.AsPointer(ref up); + + fixed (void* ptr = &this) + { + NativeAPI.AngleVectors((nint)ptr, pFwd, pRight, pUp); + } + + return (fwd, right, up); + } + + public unsafe void AngleVectors(out VectorT fwd, out VectorT right, out VectorT up) + { + fixed (void* ptr = &this, pFwd = &fwd, pRight = &right, pUp = &up) + { + NativeAPI.AngleVectors((nint)ptr, (nint)pFwd, (nint)pRight, (nint)pUp); + } + } + + public readonly override string ToString() + { + return $"{X:n2} {Y:n2} {Z:n2}"; + } + + public static QAngleT operator +(QAngleT a, QAngleT b) + { + return new QAngleT(a.X + b.X, a.Y + b.Y, a.Z + b.Z); + } + + public static QAngleT operator -(QAngleT a, QAngleT b) + { + return new QAngleT(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + public static QAngleT operator -(QAngleT a) + { + return new QAngleT(-a.X, -a.Y, -a.Z); + } + + public static QAngleT operator *(QAngleT a, float b) + { + return new QAngleT(a.X * b, a.Y * b, a.Z * b); + } + + public static QAngleT operator /(QAngleT a, float b) + { + return new QAngleT(a.X / b, a.Y / b, a.Z / b); + } +} \ No newline at end of file diff --git a/src/ST-UTILS/Structs/QAngle_t.cs b/src/ST-UTILS/Structs/QAngle_t.cs deleted file mode 100644 index 8804022..0000000 --- a/src/ST-UTILS/Structs/QAngle_t.cs +++ /dev/null @@ -1,123 +0,0 @@ -using CounterStrikeSharp.API.Core; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SurfTimer; - -public struct QAngle_t : IAdditionOperators, - ISubtractionOperators, - IMultiplyOperators, - IDivisionOperators -{ - public float X, Y, Z; - - public const int SIZE = 3; - - public unsafe float this[int i] - { - readonly get - { - if (i < 0 || i > SIZE) - { - throw new IndexOutOfRangeException(); - } - - fixed (void* ptr = &this) - { - return Unsafe.Read(Unsafe.Add(ptr, i)); - } - } - set - { - if (i < 0 || i > SIZE) - { - throw new IndexOutOfRangeException(); - } - - fixed (void* ptr = &this) - { - Unsafe.Write(Unsafe.Add(ptr, i), value); - } - } - } - - public QAngle_t() - { - } - - public unsafe QAngle_t(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) - { - } - - public QAngle_t(float x, float y, float z) - { - X = x; - Y = y; - Z = z; - } - - public QAngle_t(ReadOnlySpan values) - { - if (values.Length < SIZE) - { - throw new ArgumentOutOfRangeException(nameof(values)); - } - - this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); - } - - public unsafe (Vector_t fwd, Vector_t right, Vector_t up) AngleVectors() - { - Vector_t fwd = default, right = default, up = default; - - nint pFwd = (nint)Unsafe.AsPointer(ref fwd); - nint pRight = (nint)Unsafe.AsPointer(ref right); - nint pUp = (nint)Unsafe.AsPointer(ref up); - - fixed (void* ptr = &this) - { - NativeAPI.AngleVectors((nint)ptr, pFwd, pRight, pUp); - } - - return ( fwd, right, up ); - } - - public unsafe void AngleVectors(out Vector_t fwd, out Vector_t right, out Vector_t up) - { - fixed (void* ptr = &this, pFwd = &fwd, pRight = &right, pUp = &up) - { - NativeAPI.AngleVectors((nint)ptr, (nint)pFwd, (nint)pRight, (nint)pUp); - } - } - - public readonly override string ToString() - { - return $"{X:n2} {Y:n2} {Z:n2}"; - } - - public static QAngle_t operator +(QAngle_t a, QAngle_t b) - { - return new QAngle_t(a.X + b.X, a.Y + b.Y, a.Z + b.Z); - } - - public static QAngle_t operator -(QAngle_t a, QAngle_t b) - { - return new QAngle_t(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - } - - public static QAngle_t operator -(QAngle_t a) - { - return new QAngle_t(-a.X, -a.Y, -a.Z); - } - - public static QAngle_t operator *(QAngle_t a, float b) - { - return new QAngle_t(a.X * b, a.Y * b, a.Z * b); - } - - public static QAngle_t operator /(QAngle_t a, float b) - { - return new QAngle_t(a.X / b, a.Y / b, a.Z / b); - } -} \ No newline at end of file diff --git a/src/ST-UTILS/Structs/VectorT.cs b/src/ST-UTILS/Structs/VectorT.cs new file mode 100644 index 0000000..05df527 --- /dev/null +++ b/src/ST-UTILS/Structs/VectorT.cs @@ -0,0 +1,144 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SurfTimer; + +public struct VectorT : IAdditionOperators, + ISubtractionOperators, + IMultiplyOperators, + IDivisionOperators +{ + private float x, y, z; + + public float X + { + readonly get => x; + set => x = value; + } + + public float Y + { + readonly get => y; + set => y = value; + } + + public float Z + { + readonly get => z; + set => z = value; + } + + public const int SIZE = 3; + + public unsafe float this[int i] + { + readonly get + { + if (i < 0 || i > SIZE) + { + Exception ex = new IndexOutOfRangeException($"Index {i} is out of range for VectorT. Valid range is higher than {SIZE}."); + throw ex; + } + + fixed (void* ptr = &this) + { + return Unsafe.Read(Unsafe.Add(ptr, i)); + } + } + set + { + if (i < 0 || i > SIZE) + { + Exception ex = new IndexOutOfRangeException($"Index {i} is out of range for VectorT. Valid range is higher than {SIZE}."); + throw ex; + } + + fixed (void* ptr = &this) + { + Unsafe.Write(Unsafe.Add(ptr, i), value); + } + } + } + + public VectorT() + { + } + + public unsafe VectorT(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) + { + } + + public VectorT(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public VectorT(ReadOnlySpan values) + { + if (values.Length < SIZE) + { + throw new ArgumentOutOfRangeException(nameof(values)); + } + + this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + public readonly float Length() + { + return (float)Math.Sqrt(X * X + Y * Y + Z * Z); + } + + public readonly float Length2D() + { + return (float)Math.Sqrt(X * X + Y * Y); + } + + public readonly float velMag() + { + return (float)Math.Sqrt(X * X + Y * Y + Z + Z); + } + + public readonly bool IsZero(float tolerance = 0.0001f) + { + return Math.Abs(X) <= tolerance && Math.Abs(Y) <= tolerance && Math.Abs(Z) <= tolerance; + } + public void Scale(float scale) + { + X *= scale; + Y *= scale; + Z *= scale; + } + + public readonly override string ToString() + { + return $"{X:n2} {Y:n2} {Z:n2}"; + } + + public static VectorT operator +(VectorT a, VectorT b) + { + return new VectorT(a.X + b.X, a.Y + b.Y, a.Z + b.Z); + } + + public static VectorT operator -(VectorT a, VectorT b) + { + return new VectorT(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + public static VectorT operator -(VectorT a) + { + return new VectorT(-a.X, -a.Y, -a.Z); + } + + public static VectorT operator *(VectorT a, float b) + { + return new VectorT(a.X * b, a.Y * b, a.Z * b); + } + + public static VectorT operator /(VectorT a, float b) + { + return new VectorT(a.X / b, a.Y / b, a.Z / b); + } +} \ No newline at end of file diff --git a/src/ST-UTILS/Structs/Vector_t.cs b/src/ST-UTILS/Structs/Vector_t.cs deleted file mode 100644 index af852d5..0000000 --- a/src/ST-UTILS/Structs/Vector_t.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SurfTimer; - -public struct Vector_t : IAdditionOperators, - ISubtractionOperators, - IMultiplyOperators, - IDivisionOperators -{ - public float X, Y, Z; - - public const int SIZE = 3; - - public unsafe float this[int i] - { - readonly get - { - if (i < 0 || i > SIZE) - { - throw new IndexOutOfRangeException(); - } - - fixed (void* ptr = &this) - { - return Unsafe.Read(Unsafe.Add(ptr, i)); - } - } - set - { - if (i < 0 || i > SIZE) - { - throw new IndexOutOfRangeException(); - } - - fixed (void* ptr = &this) - { - Unsafe.Write(Unsafe.Add(ptr, i), value); - } - } - } - - public Vector_t() - { - } - - public unsafe Vector_t(nint ptr) : this(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef((void*)ptr), SIZE)) - { - } - - public Vector_t(float x, float y, float z) - { - X = x; - Y = y; - Z = z; - } - - public Vector_t(ReadOnlySpan values) - { - if (values.Length < SIZE) - { - throw new ArgumentOutOfRangeException(nameof(values)); - } - - this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); - } - - public readonly float Length() - { - return (float)Math.Sqrt(X * X + Y * Y + Z * Z); - } - - public readonly float Length2D() - { - return (float)Math.Sqrt(X * X + Y * Y); - } - - public readonly float velMag() - { - return (float)Math.Sqrt(X * X + Y * Y + Z + Z ); - } - - public readonly bool IsZero(float tolerance = 0.0001f) - { - return Math.Abs(X) <= tolerance && Math.Abs(Y) <= tolerance && Math.Abs(Z) <= tolerance; - } - public void Scale(float scale) - { - X *= scale; - Y *= scale; - Z *= scale; - } - - public readonly override string ToString() - { - return $"{X:n2} {Y:n2} {Z:n2}"; - } - - public static Vector_t operator +(Vector_t a, Vector_t b) - { - return new Vector_t(a.X + b.X, a.Y + b.Y, a.Z + b.Z); - } - - public static Vector_t operator -(Vector_t a, Vector_t b) - { - return new Vector_t(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - } - - public static Vector_t operator -(Vector_t a) - { - return new Vector_t(-a.X, -a.Y, -a.Z); - } - - public static Vector_t operator *(Vector_t a, float b) - { - return new Vector_t(a.X * b, a.Y * b, a.Z * b); - } - - public static Vector_t operator /(Vector_t a, float b) - { - return new Vector_t(a.X / b, a.Y / b, a.Z / b); - } -} \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 6c15758..70b6a5c 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -68,14 +68,14 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) // Globals private ConcurrentDictionary playerList = new(); - internal static TimerDatabase DB = new(Config.MySQL.GetConnectionString()); // Initiate it with the correct connection string + internal static TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string internal static Map CurrentMap = null!; /* ========== MAP START HOOKS ========== */ public void OnMapStart(string mapName) { // Initialise Map Object - if ((CurrentMap == null || CurrentMap.Name.Equals(mapName)) && mapName.Contains("surf_")) + if ((CurrentMap == null || CurrentMap.Name!.Equals(mapName)) && mapName.Contains("surf_")) { Server.NextWorldUpdate(() => Console.WriteLine(String.Format(" ____________ ____ ___\n" + " / ___/ __/_ | / __/_ ______/ _/\n" @@ -134,16 +134,16 @@ public override void Load(bool hotReload) if (accessService) { _logger.LogInformation("[{Prefix}] {AccessService} connection established.", - Config.PluginName, Config.API.GetApiOnly() ? "API" : "DB" + Config.PluginName, Config.Api.GetApiOnly() ? "API" : "DB" ); } else { _logger.LogCritical("[{Prefix}] Error connecting to the {AccessService}.", - Config.PluginName, Config.API.GetApiOnly() ? "API" : "DB" + Config.PluginName, Config.Api.GetApiOnly() ? "API" : "DB" ); - Exception exception = new Exception($"[{Config.PluginName}] Error connecting to the {(Config.API.GetApiOnly() ? "API" : "DB")}"); + Exception exception = new Exception($"[{Config.PluginName}] Error connecting to the {(Config.Api.GetApiOnly() ? "API" : "DB")}"); throw exception; } diff --git a/src/bin/MySqlConnector.dll b/src/bin/MySqlConnector.dll index ea3237d4b05efd88bd8fe7c0f3c829b0afe1f9a4..4606dde323022844cfff0a6c2995882cf7be5e12 100644 GIT binary patch literal 857448 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�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�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℘Qt4Yh#9eLf~zB|EmgK%(IQ*@|Ix z+}QiD@*d7i2O};LuTReNulR5qN_IZ{e^GRe@sa;b{NL-<*45_Lwz;*gS6;2Fbv3Wn z*0yciz2a)y*4A#HeO~0n?=#6HGnr(QnQvw#WdHKl10vimU&iel###`DguGb)xm_@# z4hzY!{NR{kcK+pu{s<1n;mJX01bLhDnX=*g#~t zf~aT9XKZE|Y=?bycYo%m%(s~LSg;l^OqXyqaz8;;FVe19{`LK~1{xBY@>1bjtU*h3 zQ_o)T%6=a><}M?+%L!F*m%Vz~&Vzg%9@P2zVQEH>4B*rd&IYnKy z%(TRV4z9CqbismpcT8=Rcz(i&Yb{p=?nl)kZ>0Gh36obXkbv`%!t-tM+f?xzjQWSM z%NELkADgc%B=W-mac60$Z`BlkZbf~L&p&pQUA328q0N2fjIs56vfsa(oDL|dE*%*Q z*q5>x3YHSRhepdN_{^k=`0*HJMO`ExmU~+vUkN^pTSQ#Q^M|CYxPOCq|7;+*Nmq3P z)Hy$;gH(|=1s@LupI^n@oyFZQ>ITB<22kn-V(MFpby+;~nj`K;2fRIWXWvmHz#9QYntwnhN)}iYS}Hw!!ZV6uWCFm1D%Q; z&4?Z1#SgGOh)Ibbv{gk5a zx;h<1l@;LXY-j7_SgWbgx3QR2HSjsG=}`fF*z>&eL=h|TKd?j_haMr7Z<*`jFXIKk^l9$O*FBYJOPdvD=-hrocYC^vq+Tp7Mx;put@ zy8Qs%xTW3vG|t&%?X-g+DmwsvE%-eokQ2B4!P{ml{??||hmlX0CLf6O5`xxo&cWMc zyF}};KX;hD?goF<4a}-9h>(T2fnE>b$_2Y)gy>wgJS%PjP27)K4Sa#;5O21RL^K-2 zLHAqoJa)b0ln{SFVEp|>x=iqe)Yd!7Y5vjhJoXNKaE^H^lHdm+LBfz9_9^Fa_T@%z+w2&7YFa(2Kns@ zk>1v5DM**SYxbYHzgF}ZYn!>KY>3nlt>LEZ3RL65B<^jHAjtGUCy?y4x!B`Avh{`C zIbTDLIOtJep*PDu3r6C*P?TR81AmhYD-|V^s^{ppOkJ zg$!{w;__?F)N5*`2a5RU8GxcpO{{r=O2??0Tj;Wquz1 z5N6qa)!e*Mk{@MC=z6R6hXe_5s_lD8(WDD)$Ub}{?-DsZ`Wbp~AN>J-{O1CfFk^eq ziH1Q-6q+5a$|%yNp8su)VF6O(Y34dVM4Y+{n9c#TYIM|!*>^Xsp%go1`meHa5Ef(O z_RbVTiH{Bx)QKYaH^gTKMpHSc(#x(t$j~1x{$OaI1TB^BOuoY?#^r$cxm1m@Q2+~zhTf-$FL9#SgH>`$9C=LdDk## zEyO8XgfwcTKDQQyq?-zF$IH`Eu|rKRo$)>-#*2=gA!pb7?^RVjR#|pD^LVFVa|u_X z$%{4M@2mJW%U4Ppk*<{?A1T&;i|{eeZg#w#N6zbvvaERKiq4N28ZT?PJ_xp`ODTdX zYiTcP2@ij~Jn0>4Et>FlNoWwRK;; z^tP1mYg7C8a!$xyhF?)_6YiEA87I;2jD9@(NAf zkJJ9RdB2YLq+CM+6pFvn?+&51o?q|086GT_v-#tM0g4A)HE*O)TdE%@QwC_#?u-Es zu20p5lFhB|UcNnv#W%%EpYKpxj+Jq=Wb9-`MV%K1j-QhKb%GSxSE$ma0ZZ8kTUb#J zx)IO6{p4s@&hBIO;>5;g^!c^;PKok~)>8Av*c1m611XaVqP-KHybXAEU^*Y{o?e%` zJ(p6pUjs3O1p-ZC*#KGl{!4yMu~)~RdaReEMjx=(S(0&Z>inY^HE(oB0$U3Z+(5BQ zk}}q7Xzo?2W($neaEVKjiT;Q4kwEnj&Tk4VHO~=Tec!AbqSTaZ$og;>!MeQm;4BKw z1dr{#_9m!#()zUA62B`yyqU80+cH##Dk_~Y8I-?&VgU*gKnFZhe8rCCPDG@N@_hnB z@AuELd&)&okmzM-juHYj@nfV&0nrk$xFl#_W-5?(QxoJfj|>TLWtI^QWE2tg{9S<{ zGB{-TK;(zTLHoE(!V~Hrl%UU~+gH>vw$396wgV~2BulsOjeQbMP25RQaqD*VI$G`k zZ&M9Y*dfK~fLDJ~1$0)xQW&(~p5Bc!=J+}P{|fYw5(#0l067;d(S0H>jjM_8ihqC? z0Wx00o)DrugqV_K;ttscKb!+0uv9{IaZHtgwL+qvs|g5=2~sp6bY1mf>a49R4b<*p zqDT9#>dd7Sg-`p`4>w4p(yqkGrUCL!;Cg5cfE5oinRiQ!34ak@eqH1fB!WCl2zUZ- zf8yG15ibrdzWIF<7sosab6MIXTxO8^UY$?3Oh~RxhKZ>l-jGjceAQ*7S({?5dG2|! zv{}7e-Szn4Wj<+FPu6|)`o4E`izm!sVev4}=WIT;k|bAH*oHzpB|1)}*7&aBu0tNT z>t;-93=OkaooSs+?tdN^wFcvYfO-gaPFa$_+=4}Jw@NMd+?J=?TuLr zS-C^5a6g-goYs9+k%GDp{7#+ingVpZ`nKFcd1Ird^F2pKiu1gOmnQ%EErMt>+|G6j zYv?$Eg5a%SZs?eU!bi6G$ICYfpmRv9^Nr1JUx}MB{Ijm4U+|G4MSEOyQb-fla>h7> zlk{=S!;jU0kXRywA|YzEcq{0w{^G_INx9X&mOmaI_Lii8KA{iMS74!OYSgE1kYL6W z;(I9hetTGRL)|QE3(4W!JC)iU^-dJd^am!OnmW&KE72mMzVi)wSZ?X)kq3K>Itb@3 zP!VT_mE>x?MBCDxGM9`OPCz=0>V8bMQ1q5Mu_c^k6~Z8MrPtu*@<( zheI_l5|sLN8i!h~J}JOHSiL<$QVzCnQzm3qgIdk$zi!Pz0|SV9tPq5@;9P!(V{e@; zDT<#EOzW$?2%;S8U;<7kOFkx$o+;EarVPR$QgiBNS5wCAu16jz??(cEh9}Xt6jhh~8q&Sxocqj-iuYaIk#kM?rx(yx2+#E<9D^n2cHW)~9-Wdf5 zPvpeB&=@Fvd3JgFm_~bcnpKg+8giNqD%tT#i899d)i($3qSnPg*{W>7nyDp;3%7iF zh!Bn!5+TZ>^tydEyjOa6qnfuyJOO5jvvfx4Sz)u{;{Ju{_RUoi+@YF9j z!m_rcN{aId$%ukrY9P`QjN6an@ohDsZdL~JMu_fagw&gVIR+jBg-teh?e^A!J7-gj zVn_?`ZVD73h)G^IjKm*q>5iKy>SwyWHjxNQ1dg&3{@K#|+TI{3ry~cyg6frz?NNxt z^4Zdm6tW!2q3j0ZHIkD9(@yyBQLViP?f4QCAPbOkn8KPCqEg#M*{a^3^MWj#a;&VY zfJ^GQc)Q7YU#JaT3Tg=tez`cA7w~hyQ>N&Er|cy9tOn#8@isUsDdeqKj*gfiZr3Z$ zNcg0BgwpEcMU;>M#mmbu8m3X2VyG*aRpjCOew_Qq%K;(-%9V84&>ZyBi~keDG=F%a z_fd}@bfdob(h`)Qt^ad>$Tp9yzfv-A6q>Mo6UsAJQqB&%Rq}O}E-wBQlfUPSZ9H(4 z6!Q;%0JnUhX;e`67U?lI#B`B5f=kq?)pl)U_EtnoWDB3FdM^dDP;Vfc2KqF$eYu+)#+<1QJK%-4TN9Q7QIxEwj^) zJMZnHXerb8XC&E^`$v1lfuW-zefBjQON0~NbmZq;5+@y>6~k-60i6OS~Yx?szj!J3rs_x=G5< z^FlXJgv>{9{uzu;E6K4`{#gRHhbfz%Piw#2KnLyYL83l0L?&fJw0N;NnIGV#fk#7_ zc8bpr&RoquelT@DU@;sI(9yzEjs@hF^7dHM5JfR=gCBY%W9MyP2-FQNIctUk}LS=YDZFoXOctTBh zLREM|U3fwTctYxu67LggZ?fExhmXOCWx*kQrg34gpXPPqa?S7;09w;h0n80_v>ezVk56`EEdAZ<3!EYx91+iL|TFNyzGgh@} zL$fX6mS7QAT=^|cg%5|iJCmV(Wn6g?O~!Ypx;yKkS$l?hCeCisJUky6$$`hM%Wz8z zT?UlB9_*YCNeXHr(ks8x*w~W%R}qXE72&6p9hkao?&?p3p5RGQyWV6dr&*Y%z4a={ zkwF_<;yjETrqzsf0>i-rrZU$Vd%I)|r?$OWQ&iz7`-}AKPY4b;{fld!zJ{m`yr2>F zud@F-kdhbU<{#iP5)M)9ztjSd-Zk~V<)0G349XP)dIP)2;d)N;h<^lrQMt||F zS3`I>@Ay)w#KTexbCFm}=~hFuDPO+`oevGjo-PRmY|Huv5&mPb%^L$IlkWS)lkRU# zqTYOJe6hDL3!(lQSw`H~x~+SPcY|5;_7|;Ma1Ty%^-02GR*s5hkRUjSVWW;l zIT7~XXjNIG@H<(Q^dE4#dUx-y@Ry_9k0f7}P;iqWzU*Tl|37Dc{={c-;ulC+ybtC- z-$VM1cZgKq$4rI;he`tob<+j}dw=;0vOfTs`F~DmZ_9^6AC~76g5^5`vfZ=@qviUz zz{LXMsQeOEVN8WDUSj2exP|FHqPMoB)3+ngP8E&;>yu4^onl$MU@qu!m(@s{5gN_D zYSw)QM&%Ltl--^Sc?7pc>b1J;x!P<7$bHNxR(S-U@k$(C08=Kho(V?H!2sarmfrpa ztupill3&XZ-@^0H;hACa6A7w)zS)ShOshG~!X7L&u`!$`{krH8hXEa6z9e(m7#~U? zAC`8Xckka>|6{;!Eee)-5xld{Ox$BXTj-Z9qfU z*QnOV=nlbz7WypnL6k$G)|5dmiQcw}9HUxg6Hk+lWf=>970b(M5@8Jfhg5j#Dhk}7 zlBGrF%w%hBmZOxwUWn^3@kJC|+R@iVXz5s5?aQxbLDvV-$$#OPVpw!YL>dJ@28R&x z*MG0~hWNvj2K6M(P-uune#g1eN4Ze@J;*iPJTJMXhQy>{yAPKejVP%{YO4u7h%)ns zOMD%VNegRGaR$UCu}*ZMg~VQmEQydStZSt7XSx_B!4eK^$IUB-{F1>Sn1<#MbgSzL zbFKpwDN}2QU#U!3DPdJat@TZyQV6h^jch$dVyz5nD(PKSkH@vF=&ChXP!sRrFZWLm zZFlNxcN%JU8c28i%MXaQ-4FOJe$h<37=^nS!m}8NwkWE4hH}6ZdB7BUP~}fu6-HeZ zL|qm2UWoXRP_A`qrL{>=$erva1S&9E-m)|ZcC{FxAPRRit9(kvGUTbm*X)VsWA(*% zLV9H)9>2qVQC5BX%4A`&Kca%j%bB$EJb$<)u?E8(KyKoI9d#4p%C;=l(R=SYNY(;M z`7bl6Jim(2f0dwJ!pfQtX!{WSop`m52+neX+ip7DL@?-@Vjr9it@n)U)NWMc-!>aE zGM!D@+o8vZ;+fnQNTMS@aInaq30K2Ca4ilLBHDPQ>V>ljywYo?o)gN&oymmG5$i_L zmsKzFrq6;QYYF1K4`BcpME#mPVHvcuRDxIC-Y{A;cUWxh)7Fhh1s9Qoav>XV*OPM) z=jyTY7n;Un&7+O1?%g~24wtBXKDXd6N;v6bukU4T`nr@B|fF^!Jc2>$aK8f=wt8oQjR$M>bkxf`yc5v z2lTz@=J|-3{q*fL?48Zs7ombK(Wx&i(+vw4o`Cf(Z0&8R<^vl*EmP4np`bb3w-Cqw zkZu=|82>S7`6Yay8XAG}l$Xc9*&yBNliu{foR5-MVu7=QIyY{mNzr_MiZuSmlu4HJ zp%goHsuZid`nT8FWQ6W+E0@^TF-m?!fEpxsSCikF%Q*e#ftPap`pg)y`}eV6YVZTx zp-dmgSJZnpOZs#2`j-XphbrvWtlWstKMr^GpgT!lW>3oA7V7TfB(ZYhv^_d^Id1p- zxR;}Tdwj;SdSqY#Y@P){RsfbN!I+vdtl0@5v|f~y#@mke#gJvFyY9;xwzuFDGut7e zI+S{i8s5zIE1N0di^+JMeWlD)axv_R_E`x`aeoznBq#IywxlkzK(%`Q}bfa(!DYjVYy#|Az%pqVxl{fv}wUA}CE9H}c|uIjc9CqMSl zeQvUFf&IF?uG^EqJ|U;}_aaN+tJ9-Pd(O8jF7JE4kpaKO>6_1GW#0ubp2dQ0{qDW= zk)kIGa3#0IG*Umo47SgMl($*xWs`H}-uOek6AAKwV6Bt@#Mr)>XA$(fkySNX@>=mStc}LVkpqDz66d%hq3;b<8qaE_%>tD2t$gW4d zvUei9(@iwlK)NA%8bJ&6afBpP02R#qgw`643z z$OXwKW)oLhXu!F*pQA7bo)BYh6Sb|rkHMDB9`j!Y@u6abM}pDjxg(e4bU7;(A(wxa z(THdGLO;>dXV9;&($c59t8l|9*giI1M3A2`thMB?yXo+Uo?cPO@bB87Tj9n?BppJ{ zDB0v;16||DW~6Qp(R=x6-GuloUMn(ont=}9-5Qh+HJ0&Ymw}naC4@_^HJnQr{a9!K zIPffwI19W4Ynw<^VA0G=PfZ0w$uYl~{MQopxRDZ5&rQ@tN&1o(o*?sj3Fe(Y9Zvkq z%;!oNWj>p9BP}PIYpF~~&qHS@+Bf|_OKUsJDe>Y_+G1PH@`=4Mvifeg(dz*#yb4`n1JRZ8EVPWZBAxAgsD$kAp2yt{PYp0 zvC^GfCYF%}8s83ly)Q_m`b2J;ucc8VPylo(2f@Q@p;b8k*f>HW;CBBss7~B*;|w}$ z2?sOi2^l@I!W(O z(<_}(4n;x_ri4ykbrC&lrXRoMc3DL=`5I@uq+(Ym{0hSyu9XXc+2^>QqRff=4 z(6k!Z6`j!AzMRSTm;9fji}MlA^=D6wLs?4GP6oh!Zy+3G;||J2{Z z3j*TW!azxM4uRI28eU3WT&ACi8)q!AjBO(8*b%I1DXUn2xMJI)J`Gn>A^0FqOJAg_ zR4kHIi=s4!3{fT~XKhzP*`oy-)ItYQxg+)R&5YO00Y-PQn1fEJ%x_!3UUI(@1{zQK(~a2p;9kB>7o(*AE@ zS+P1^JR70^@!F#KgraTtJl{OAoXI1rG8hPKf|xz+S#-?a{0o!Aur;ul9#Rv|u4( zJa0l0p*=a}FLSqfcNJiV2_jn9o+p_t4jH$OT(`^N?MXOK{eWyIC|?aX*uQX}+2$`z zTwzUZ9PT$^zAT__GyJDX66BLmyv;eF8^ad^)sVg{*#28zlqpiJ5OT+`y=$I;qaLt1 z*c4x=M-)s23%+_{Z!O61UGmR%9ePVK1MlMq_4r$#=pc8K#$M(^D}A?xRfe7uvcQWj zAJ`UHIo3q-Z>S^*6!1NEJ8>w&=-uWmHuwWQ8Tucr+b9Nm`q4h8IatW#?qagHY$dag zXKC&V3d%yv8TPSYmgmQAdMA!VFh~D6> z`(y)p2%~6c?9m;(3Al^o?vB@C__v{h1@*>M*h~dH?AC}19V@+FYc6nd%+XJEUV^QBl-?`H*mJkfT)~Cw4>7 z!bUJ~Ym?HHbP7S)5bVND4(G1q`$YnkT$ph_FS+k$-&aU7npP!KFfy{w9Ern+f+?7a zy6^pdx#Bl6pS8#9e-004c!Hy%PZlu9qe~p_4-wfT(37}Afd>EOTGZG5KtXN`c%u0b zZi*qBy0A`2d9WR|eof!6@)5Y1*;KIER}YgE1_2MS!n(XMRYzzO)NZf_RWEya$1m_T z+bH*#HCah+PNPaXx%Qas2cE_RI6iJ9Ly~ID$Hs3{BW|P=%%0JU%e_(5e`XKOCq5cl zjd!sBJ`hSHd-lmdeM?hd{+PYYd<);$x@mF6J*YWn9{+6Wp!c(K`}xs- zS^QSF(R0&N6tjO+q`YrYIJI9=$g+=H7}c+9UI9*80eG9T2j?hMJ0t*bF4^INd;ZC) zDjnuqk~yn$#wY#YOiWTuOz{BFk9cF_nE6J?)z?Ri)jLJd*CR%h*ULp%)ze3u)>}oS z*Aqmn*6T#H*Yihs)_X?q*NaN|R|Zh1ECN*x7A1E8XN9ce{0vd`bVH)rno^+34ho4y z^*E-pDm~Qub0vWiS?y!fzZ<1c3Ncn;zNckFM0T`!Q{?lh~Sf&`Z0i9&4@bxZ3X!Uq0yaw(0~xqRB&b$0syVpDCuu(*}@8-tKar~f!jA<-S%Lj`yg zlx{~HkZ3a1f5Uc}=&lfbnT6d||6~W=gV|WScQGrIXboY*u&6*t%dSAY`0!4-K!93&av;8%f2UDk-#eVkFII^^=o$o&4kVc+ zAhc77|I^O%YQO)->3|2J{U%Y0iyuU`0MgtnnyM92m9YQ6LOCU#I6bu7eq4Oim+v@D zrfgL3s%@&sqmrCKGIH#`*b<1bDQxK!)GP2c%Vn^F-f2LtZ9X#&DtM2Edi?lsItOG4 zM06Bl34{F#vhiV7D)^ZeHc0El1c|u#f0IT)sGAWjUUL{r)|f=ob*+%EpGi74R|w`58$B{yqFFM$}xV?!^o-0t6f1C+&{ z!@~^hES|%Q*G<8Yc9MXqi)Jik(_6Ad4Ft-XPZ&HUuK)Zzmy- zN^v?-OCVzSXpN$C;z$e`QIh@?dNWh52sgxD zRx<(A_M=kBK80sLCH1e`Nb=y3@vR~B zW})Ua{fI=|+<(=)dwQloJ08&r7Ra=8^fH{2gSRc&q-^YwP6M6isUs5i zuc;vevtKuZjB&#(^!+=liMMLAtDu<4EI0 zaY-=l3Xv?Lq9u$nS1*;z>itE*+aL#R(Ul=n9Jgj6?XO!JlpwSS-ttMB^94+KJ z9^j(VHsf?mR|(XgDkJ~gAsCUu&|#v9kA~&4U=OwqQ!--@JyXn_A*v>?!#AJAwmt4I z;~cbMt#Bdl6=h%iGf)5f&ks`4q+G)2MnnG8x4 zSYWvFjMW!t*1vhMT>xs?Z7#;KIXpGv-+HRqpt-*pSP?h7F0e^$bPUb#MO?oZG8(Tv zl_b=@=5kXFSwRi{y7#{S&4myjmW!rZ$OUuOs6t@8KMz|lj7@|a`VQYnb;c_yEs-A1 zjaEYL8OSDEG>0l49stmxTv9BWvkK#*Ota3&9~x6Sh%z5kaly>UU-%WqKbl^rVwD1{ z9II4950k7?qTMcxDh>EXp=8aO2J?sK7nN$o5;|51_c0fhn3>!*XB>S3V8U=7U8#oK zoGL?#Nc|7FR0#qXm4%ize^ol=bWQ;>K$=TN;Z9krJzYNJ0hBAGcYHFPM42~Wr4~g5 z!dUgIqH>W1St{B#o@CO@j+8Y`3*^t!6U!*Pu|y48YE+_DBPp>F_TO7krVO4;j0Do# zL=y)VmMQEI5X;0j5sTK{wC^MVbCLmo~)x8!VjXyUMhrcrEBjb2t6@hT3TT7Z=GA!CGuhI!H?dQ9WOAFYHLJr&{~ zrAM>;I_-*uW2JJt|1#q?d*ID};P3Hq^t-}h!61BLEu8N0>}01*i(I_PZ5mTRIOq8$ zNBLi4Vpvor-%uDV+L#4^lLb#c?Jj2FMohlMii>D5T!qKoBu_w^kRn>Iehj{a9q+k2 zx9$!X(*igX3B)U*Pn8NAD^;3K=i{P=tmn*4;JO)$bAF z9vouxtNULk@nE`wOTTi+f3KqnL=}zyf#&CuDF^*6oU{Wz&mZ2QP*+RUuqJG^7k?qM zI72R_KTjyH)Uu^m(CRyYc^2zwpCS>VQQdMs{<}`s57McU)aY-S`AZJcKSPoh@xPYt z%&$@hKoqGQHTqcyP+2VD7`}uRW~Z zXpJGO-#X6Hgjw{PL7iWp*z(5bl+39`uqquh$ z5CAWd#=ydmRFLOpH#n%TG~iOI5n>2yt_~6TlZrTNmPATFqZi*pF7o%D|p9)~WE7QyJTG zW%r$MW$n6G?gW`SWCLPAj$-LbdaMAfBqhR{5|#1KqUU@(3p>ZNuV=d`(p7xq;i6NG zYI(eDTE93SUDh#vN6f${$k)p$H@@s+M<4^tq0(80=9MaD96a-O@=?Mwvs15evsN#q zt5%RtwwH9^&lqvjb7xNxN)RlkbM>+KDZ)ZH(wba=YBj+;OsoiMThUZyuy|48NWb@@ z63Z4o#|k6S1M*S6ONx>;7Bh!%1^^E}3EHr+{yy@Zi7QwF5)tKF(7lgJs-@we@4rP< z9fVJ!lh!Q!4JEj9Tz$gMPRnJ#SCJyr@WdjH2pVwg|1~Js0I%EGpc1GgBS5w@+oa-{ zn@%bzS+)0Fn$0#i88p6RXU(isu{6uLs|DgrrAx~{dZMlzf-5QbCueo#8a2yzmBE?w$_eqfklJD5A;JykKWBOIj}Xb!B9k8QHJa;yGJi2+&}whs-kdp`12d2hibXT64L*qt!-S;2Rfl;H6K!Y7;k$_w}Hh(tQ4RHtadznsPTskWMT{yQsnr~xM zQ9l_k`M>TwX(cmmJlQ|wFkk1Tp?^m!2Fdd{d!i^Pcar8FDAaxv8&?k+eS~3?H;ZDa zpJflwU>cqw6#srInV@7Q2S4yUN>yp6Aeli_Ib(R6c6=kXK4+<(7N%{1*wp@Bj0w&3 zEs(}Ijkl}V8PVb&-A?_k_niIHV)5?MH<~@)D7Cgnax=1G(xm;Y2V?Y5zs)UatCujw<_$lm_= zVjG;j8UM3HowM%0CtxOSP`+SXCg50Ra#YvS(xZpu_aWR<9IVu!P4B%!QP}A{W6fJL z%2yW_2~p@A;zpRmUYE879z-op<0kT)FYyh2pB*%QF@t5z+gnCpYMc%YjI0a@ZZu#F zj1`^eC>tF4ExR*^DKXF!jhsSc{ci7QG*ZQnAxaU76%cNz7y#w}-Rc;9NHMLf^`w8( zguZ`Uh&A9i(HOK_InZ{z{mo7IjtzysiXS=}6b>t`XlZLmG`n5Jx>!|^T{y1b}e zuK?h0D1(pTv6a&586zX?z>OZ>#U7t(99pC*W_1WNQdInDA_@Bnb)hJ>VgE z6;uSjlo2TduGIKGI0m-9qd;;sYH#}YA8Z#sP>#L}0 z`WXCTBxZgD`j_v?-C}K{QnC(gPh=di$(Oxbi81hQY=sE?9>nJD4pP>Vfi^BssMl~h z1jth8wOe@mq5Pa!tSESkltkr=#m2D+yOyg|ld;C5O`UhJz;e{XxS=D26(!vj z=2E86=%2W;7G4MJKsKp-E~#Jl*Ziopld-VPq?KWYr%W3GYEljI9*x&93VRpHizVm# z`Gwi?JS~SVt5j9NW+zac{;1v-<#q0Tk!6G3q8{OTAz}TV`wb{aLT6n7%u}V-ufAB@ zJ!6uTcX86pN*T<9dj)E{{t`Bmv``XF{Z=&uJ4t5AMP?aK#?KNxuQN!Lp!8YZR)L)|l z6tn>D{*#`@t_}b!Wd_8wt3go_qKszFD6}|Yl{harxwf_jN8#rj@_Ih##3YiBqy~o|`=<&Ii7J4-0>oJL8B23V~pnY%hkZP@L;7r+d zKgg{FRpDyeHa#(UFh9ROj1ifmF_yE`>*c@EK)N=mIPzk$c+Xu|;M&q-wZ(0ms=rw> z@N!0*<%Ys4u&zzmIQ@`nGWcO=Qu>|EV=eAujqzQREBRW=CJ?mYWo@Cd+BlTcK*U2Lh~52p)G;EJy+d7AYZYc8 zhHYi7BS(OJlI=?OiFE`CZpG zzF_hnSK8Xj3mS-2kkxMp#DWs?<4)W9U+YGZ3G^qT)n9EH<*(8AAh(+W&mhaEE9`gE zjzJ!g*MnE(giGi%UNiIFcPqP&GySzpLag-_*YKXRbaUZOil~N9>4`Ac^F!t}T^10C zW#eo=fVA#JfYxulVQgW1PQ+}w++HAT__dKoBe1&ns!_ol=NU`4h`ZWv@O>@vnZsVN z-QDeNv-_2zE!aRv+Aod40!zD`8c}G`p{q@QVC$rPaB@QV(v70WqRgpzw%XbGwA#M= zb0n!}RshWpZ2A_r(e+ZzP|d))&C@QOY$8EAaqKkXI6ad7+ASkUa8ue|quSH3HP$70 zQ#{o3=??19)6*Py>6uF!Bq7Fod1^PX`}mh#pP{}|X}>wAqg2lMQ=|1xsjp0$?aDQU zskq|$>idQ1-yGt5wz3noSG@$~@wc;64D+=$m~j@73_`C=Mvr8#AA3?7@1dx%)J6q@ zIW=c_*0UDMp*Yr{EA3IQjI^`tzn9NW0DvRI-jFJsVf?iwX zcGjqW=c|o3>7>x8+|}2|N9)=6a3Aemx9iykP4CT{4dx9E?IHSJD?MP9#f?*2A%5I> z3X5b0dmpd>n349;d@75@)^FiqEq%(fm*3ScubfuM!S?rV>MvzDVmn`pxnoC(H}wa3 zQe<85W1*MWf$z*iM>{FbNOqSy!^us1t@_D^^kfgOau%PTYV+UV*%Eb4JG#=Je6@2z z-Ird*iIt|*X*Mp}uY?8^KXX8O>Cd&s2PZ!tt}30 z4$7((-d0I}Y8f zbZkmW!#wqxf6)o;xv(!lTK;o9-D52?`GM_zI{>0pZ#fxe@!0cyF0uOgw2$9Xg6f=e z1Y>1LeZ6Lv%w%#_$!<_iV)gp0zExX?J4|{m=wj2;l47su=~ko=RWd8AXQpw}k!k8? z=o6Ep?uvJ7y3^H#V=LE%?`7J%Efkt;X46W#aKG`FVM3av;)@)=<%BsgZt;?(#>=N# z>v@??=o_txju3a!B{s&5j&P~i#JP;Rh@#-~x`D>w$oa6BJlyyHl33ddX1|YqDsK2Z z+^49&KHw-lDr?nL+#HS^`%^D8$xU>|Fgz%dcK;SNeDLCN7riFDW(%-co8b~dJx|^8 z4KdKOmNM4A8|0~}hG6ieUuKcn4dE(vv!TjgHE8d%jhaIHv&$(Jn3izh2m4V{*Tj0}sr&|X(hPea15&jwQbjN$ut-epI10d-Zy9%6Kp4XyjE%X^mNB8E3uQoVTR%S%r$ z0~49Q{LLxjii8_gpXCw>5mql%6U1vNC?t!JyNpFLFsK2~@kj-PvwyGi^-PkIcLyq+XEocIJu@!#dulr>kB26x zqjer;rTNn7#(r}h4J*>DJ+NAzt@)Xu&(-m8QfH$JH{&Y-5$kB;_JBz*9Cr;iLv!o8JCKoIa=z?-L!=>-UNwNUI}|C ztV+$aPTuHT-+E?ub{?I5kvKX5G*>@DH+9EpQ$N_lVp_?@KoT_uZ+)J@@{ z-D$s&?B`$gsT+aL+lGs`&ii0Pi+0?|twZwUjeq(UQ-yrUW#9I0$$ky&#-5WGgB!*~ z4v@+LZdnU5g%w`zDd=&is%eA7R=Y^t-766#BEyi(Tq;U-?#eGJBu*5?#*d?Emjdmr zp>UU;PNS59iD=Y$3x8j*f2DrU;j-vLw5!VTIAkGRD$;Dv-lX{D$GB@dT{w%d#ZHjy zDN+`p`Q&FEB3ot?7+drA(H`-w-Bt6MvI#}Q`3K{Z$F=_4TuzGK72kxVz|w|5r=>vR zbQ|r3dCJu1X78`>@t(`bF)A(H5-r_?NiTg~S3AXhfRfvBxOuA+qD`?oZ&fORG^0S> zhf?Bo&&ecwnQu{3UOYC5$q{BYE>qX*1OZEQ) zPC&80_T2`}e`}2V{z4l`n(qGm$A*+Tr|jLs>9Y^mLIuyZi`3ub-E8&OzAFX>P5;rj zLa-fMGv0sDz%iA3-;Io0y13qf8wF8oTq8KMQL8p};UEZEwCrEmB}w(8TDRqHT=S{= zVxZ#Fpk0Q38CUu`O2-YT-mCegMo#5B(pnd92>MMcOliW79xiWG+r_2AnNp7)?p@g4 zx!;Hu&3xm1%H@ae-+iw_`?hY@O^`H@BNiXS-WV!rGCn~+x?6)X%1-%B_if#OIqJ^Y zppbJ1oLySBzIe(t5+4fSRv!o>i=~gU*{?=+Za?$n%h>P-V?Hc?@7Z%z^xDQ}XWV{Q zBQ33uhv~XoF==r-OqH8^zpK;ME&pg>0y)355ZS2jTt2EVn`E7Ol!hcU)IazSZr8E!&aU4&p9ys`lh(5hn|~7bv z^r(9CGN!oDmK7ewi-i+eeW~sl=Z`d9+-E@kfst*RjN6f%J>X}XT{()zFT57^8}-et zn6TLJO*OEDjK_F%03wpKI!``u6G_P5K`>eltaA2SNK)!nx|p{z3wcY#N(Y0@Uxc`Qi<#(otWT#hP7v75=H0DS8 zjS79+e}A>%$oEIw#9`&z&Ul{fePWHxwCwjTMP$Y8I=w%un!jOu-SC)$#=$-XF@x79 z4yyJ->!P^od6r#r=Y9CO5&ksb{ohRG^aX6U$@4oMtGe~6e%OE}BVx0EmhT(*@1tdY zOQ$ScKHT|M>F1@Q2I{br7m@py&5jNF5YRlu*0$U#+w&Do`a8`k{ciK`9ZwC--Yk!*^X_hwEkX7spQztF=M<}1 z&3-sMY-^`kFOOHha%k!C2JgzBFMV(Q<|b926&$?qDtK@5T?b+6EPDT!Z2gfv(>J+( ze^jx#Yt@|lTW|Zvb+q|AAhTlKtJS8HX?IIP;d zooj`D*;d|Ob@$*F$8FVj2A?=NeSu0D22ZTRHP-Ua)Xm(%p4h#x%BRFmIrX|#iqP2H z`Rtni?~Ggd&R=6MG^+m4-7eQNyl9}8WalPj5xxfDX zN&a!Cs%%P_-edU`wnQBz6k}E(yVPUzgDJt&CXcSUx#`q1y)q7+Xt{e;jqkIxrJK$F z(>OpQ?O?Eu^-*85`Ov~hne3BM53rHW71`9(>DO0OzfwX@r@k2gP` z7AXsHEq|$2RL!jX7d4+W(S%&+b!N|^=yEZ0E{}ESXy}yuOPkD|#D+Ay;68E4lFt*p z_c*k9rk%AkCSW78r*n^Ywp%8q&+dQ&nZXVm^pUc{@WsmJ{mmn&}Ze0#TD_2>bOVmf9x zppp=HW@;77VHagqztCh-V7c0(hAoXXXt&<{=g<2)>;@g!R3UQldPl=ScdMAy3*)8t zvz6C-_pZHi*Mo+uZ@MSkl&<<(eQ;oYm9f4n|1Z)dW&S(+@TbA>IBH-> zb)PGBWjkv3@|!+mUi$`~2NyI+Xw|~~VLNB4hkw>&k(?XGdaDM;p7@r!&*onDRtd+C zhNYJ|67c<|c}~pv9kGt6)*q`EvTog~f7oKO zFcfuKwVB#ytELe4%Gzcp_S!$b&1GdCnbKR?rQhscA#9KkF zn$EFZ8h0vp#Q0~|Ys=mTR~hu`;fnLkX79Vy`)1cQOr^HnTq-#g^X;kNd~bF~K_^wE zt9AbUK5oN@S;qAX+l)P0yHWa~>Z!{XA99)@8{{Dtr-hmbKWt=}Dux z1Lkhl-|VP}-Q{K)@Of~=`bxnIYS6~8dev6h>Fm)Zx4$o(y}H}$!>Lopdw0oS?YCsF zWcR^3-qoG@-nw$37GM0|Y~r?RI?KwUr9ZB%8+61;5!!3Si+|?K_tCs=z2Tp3vH?B* z?%y`&VEK|Y8T1;uLCKlZNnS)Z#a4P54V$_ zT9^HuF4){|Rm8C)CTmLPy%{+F(k6Ay)LL0thugNC_`Jf(t}YIv4z77J_%ZDvq6G)P z%6iPq8z1T4!Z&16=>qRO-I*(I*H(}5ENb)HsNmPySr;~ArK3l0YSC%t zw`La~?X}q^ZRIdDH$6Oc(&{;O>jw26-fWy@*5J31S@!Q*l?-KVhns?ONA;>%aJ1Lr z^C^EZixkeg17QI{Hqx zH-^AkbtfMG>#yzk|Iof>-BN3n{mB;g+G~E<&ZTS@@6Qqc?7R5kPJX*#nr(><207k1 z{+YSFQhkcX3S2IcHhVpm95|OzA_7t`NbtO73DWWwcJJ zIr)QY+TEotb6(EL{o1SU7Omf`xGHaUo_tj>dXRe&$@W^&@Lu$e&5HFslD|J8a&Bg~ zPfzPl-?MV#?R`5Htpg%5eg-t^KDZ%&-&ks_Es~1^Smm@hb^5UtEh^O0RY)dDOCOb* zclc79-QAutx0_E*esYsOWT)X9tDKIN*b&vJw=1vwv!M2py|j!wA^2%PZ1FAj)QTJXZ&K5yY5%%e#X=1l4p(fva4&Yd z@7zZXt5q1@H{C`PlGo6C!1IUYpZV!jhw^oApC5nEPp9FO7ZqNBiVxUBvW1 zr;Y1+tHZ3*AGXP^=RWN^*R=Q4mRa3t`?Te^a9Ab(X8U;jld&egdhM$dFVvWN^TdE0 zzl*!>1V-<8F=i-tyZ(y?v_r$gBD#goBD#9a*M@#W);0bx>zipxK+AFuAJ?o{ z`?F22v0OLrYe%lrwr$7h=*ohZYsZdj{`y&}X8zKNqx(cZy>%>S$<)6qdhHl@;o_v1 z$xVF=b7>R7?tA`JZJ4kV)v6_vb=%YTS~aPwgR}dxj7zn*RJb?#%e>4fxBA{}aDVrj zZZU0XU5f%)R^g-Uy6|BmoK`%@@$2-ySIY%YZCzJX|D@R&w5M&?tpnO_>)V;uwKz0Q zMVH#LeG*6JU+?m@@rjN*+l|=xV<_|b%;7|@TU&NI^=`1V?Wf1ofQfd{{o*|>ZOL|Z z)V&!Lz9Mr_$oz6+<=S^m;`6&WblL2eJ>%$Xr?TU{s4Wmo`LAuhG>he=t}9{=jGlHi zbo=S4|1=I6KX&Jw(9A>r+Y7sWB70*hQb&&We1(I|#pjo$M_K=echAL+DLZ}7kksCj zw^zBa{aZmsnPbhXY#VWYV@F|<5g3~8oNL$a3B{Ms1H9NC`Cjt5(2Pxm~q=V7T$ z!&Z))GtRb-ZIAq6A1^ys-Sqm^Y3kDnXYVbiJR)EaSky$ZPyRM+>VI#INqbj0xNu6! zjY}t8&6gW*8+zSs*8+LDKReP2KMpLst{aotJyE8yp}12pB%TOHxck(3(!--Kka`-In)fPc44qH8v;q=!$cNE=&D-=>v*&22|& zVDWsPSaKfQXlmu)9lZ)pO<6y)>g0`AUC+I>o4R9Gm_y&Xt9FKU(y!yU3h`#vB6yJ{ zvYIEIdfuDR>u7rWUX?3;cya5m?K7Ln*7u4?tIpm_UFOI;ZTyrVNUm^T$KJU9J!S6z->cz2dmDqy z5e>^aZT>vz?)^K5%jJ!q&p+Vew>Oud;xK#0=k^xQltqs+wuTHJTJEn4AD4TVBR`(| z1nk*&=7f*-taoU!4frMLStVn4_6XnGapnC7X^;QV+{-V#o99%2^ab^v9-qG7t-JDU zn7dP0QnERtMBz?sY^T(Le&5!&__(ZR(|RM^x(x7pydYXjzIVJqM)XRQmU9R*#FZ#I zfqfaCa3XlDlE`1ucTVr+rIVHHc1P&erjl5UGw=zC!A+X6h6id^Zhb@Rn(JG z?;5W6>ld-@NB-y9V?M3%oEkW6)6rdHrfl|uQ%0r)RU#)eW&L_3sah>zhp&zwDz~}6 zFm+S;E8{Pn|EI>wE-j*`&8)a!NVS0JE4|!3!u49%RiJ`59+aq~6WjfjY2und*YgXX ztUR2#aZ<->HIwJG^*!}7Z|_{~{s&#!d4mqEN@vMciF&59Ub^cwhBfW{*Tq?jBPZL` z&RV;v^XxJ;SH5bJdg#cZpoiy6cd)()qC^!p*tDd(10)w3musFfD74{-PNiq1y8K&B`|# z_kFKpq*fnaqHGWLr`wgfEjP=SzI`{ydFaa(fk7e1&aX@zzIEY}DkuMG|K__Bp64l1 zcm`XcOToG3c^UmftF1Ve+PIT^$fK(hH_3MnQY<}Frfb#0r;eRdv?Y#$HRst&0~eng zbaIvF&bwWYn^)V_yAi38H*0nw=h)z-Guqs&deo~6XW~R>gG*AA=fFm_d0925qT^bv z?Q7}XGnebmobY8@59N!;YX6gk$ctwGY7*4FZq503RgdTS-ne@^ zZj|T4P1 z+J9z@JL1@VOdC7aA&N6<;rSApz|z5@BM0pa{K`YfQ{Kb8_w1}8AKV6X{BUJ-gSX6= zOH(5&?sED(o2zF#vDLv28#;WCF197Ms~HcY7txa6nC;d7W23O2k6sOFI7z1Wt5~Y{ ztSXVd(L?IrxU<;M;!TFq0kpJ@iVBVlFDg2WJ=imCgY&d^LCp%^_{Xn2f3J^cts2ce z!p_&Ozp?Ml=_^*2rpFEGQ7p^iO{8KcdphULvz`a-4_jy9}cgi9QWe>!`#_H-QMNfEN_+ncJjbhKEi{2*cpLc zgFo+BRMAj&xuaq0n)ljuDUZwf-iw@9bkj@2^GZBUL3l^r+y>ndm| zsq?P;%9eWh^iY=pD^gZykG#CM^7>z)q}=&GZ@yfz=Z`icZ5uAHTBgXDDoM%uW~}qo zKl|PNH~8(?vN`|0Iiq+ydiJruJIoq1D!A$7&}(m3gtihdIOY8XLWYXH@^8Db^okohn9m_;Y~soz^4U<=wR# z-nygipBhJxR$SI^>QPnoceUEM(-rdXGO|I=eu;Cs)xLGBO~I`c8ISsQyt<*6&sD$P zhF0N8>8JZv(XDfe5*_mrnWqiy*|+(b?HqR5N%y|k-XQbv1+}8>(v2T?Ki-+S#;rit z>iIuz;Lwjf3h{-dAm=aZLA#4!5eid|}i#D(Y%8p}YR{Y;?S~oHn|aGnEPEk4I27 zmUS>ywz0*=wrs798@B&D-);K1qeDhc4LRFt#OwKkZkbJIHV4(*28<^_1q&Hj4>uLQ8& z^*&n@8!84fXLHziX*eATTDmLGV{HUMYuX2KQv`oE&n-OvY2oh(`fth&90ZNBdp>J5 z*RRNxhXlz=q2lFsjkW=H$|tuPg>~P0g&`RKsr;{*=-=CLCCK~!Gg5j`JXFwtJ%yZK zS!7bii3|Q(qCRmyiLD{7(1P?|UygvAfyF;Lvi@DWlK=pQHJCh|Z8P+bweof8lIUgb z{;+~My+p0~DFTW~CN1)RX^Z^jQUB-GbUjbQE5UwpinzAzcAf-4|B~{joV(f`fIgEP zxRMyXM&B1XLKy7d$eE*pi;2s>Kev&Mp#8wio|ILkTdi9pah{d0i3JDWi8?R2CK9uY z$<&wk54MV}g}bobUrO=6dSGiTZ)foz#y8p%WJ~7935smf%nzzQT@g;Nn@{Lmeh)>e zK*e-(ge=#ILjCV>^FLZ`1OjzoHDpmr89R1d7DoYMeG+bz77%O`QTc)rMyPYGgn6;gufwjutgd$G=guh_u005ilnmE9lc$%aM z=rEt`Jc)XiU_Ht6?SI?vjvz-K`iLeiwHK8T4AJ_%`v?BbD;+b*05?bv13KHIcoPr3 zdr4zRfkdD}KMS3{422c}8^dMq6}&1jQup)yvTftQumysYIcI*~)d{6whq0BY5%>{O z+R@vjVENNJYZ1+iRb|ZXP3J*P{cPSI$X`ZLfpU1J2*5K@#XoIq&+LV^YM%#5mRi3& z;QyAe(fJNQODij){1?C|U~fr0iH)K4ztPVB)4*nYf|rst_v}zs z(mjr!j6Fn7d3O5ecFpi-qzts3pla9VT>pO#NuZ-6;QpJK#>~X_FN(>;n1GHK7Ru$X z*9esUp{H{FXOULX#PnZbCN5YgErWk`gpTEq!r&NB)&ZrB`Q9jw*-7Sp5`Mz9`%jKh(hXBsF5axUOQ-eu7N0It-xB5_9$ zd(lsELwgeM&aI#P+OC4DNwmU+BaCdU?MFqvu4U7insPIUoxD@rMzN121}`ZEC9R1; zF})jLh2+%K)Kpex)Mm}h;Q3D5{cbL+KVaZ59&Z)JavW6Nu3#Oo(bc}@??~2t;-p?O z3yO@wwm4;e+Zy>yeZNADNa=LCoG)J$KLxpZal1x`iFElMTI8mz#6CjlJuhtTk#D_x z(K{D!(bHJ1Y*<-bq->vGr`ayAjN2)@=H+~aPvNh#yxicX`gULH;-sm(Jo7^_+AT?nc7P=F*@$_3aSDdOEJ>XitLkyq((JkRRVQ z_YL2<_trYeg{k0Yb7v}m@wm#FU5-fQ?1(L!e;pYN$GnIBsdJXs-kvDGIM~$Ft+9jf zkh$P)|E%--TnLKW`mwu2)^WeC+hKhg81rbG4%=JZbHwTJ^7Xzu;d4Hb`)>X?I6>cf z;_kT=Ia#pf(AJZC=nT0{JqnqoYl}Hye|bHevvBJj!7pv#;>&i%-|1^3-5cYca$nE+ zu}^RD3Zz1!U0-9~iDbXZm0dW>-#Reh6n7I^bM)k!>g0Mm&AE9J;X7U7q_Yd_zuAX# zy}iEnU|navu_KE3EjRknZd{eE-GtP1C|o-n7(tyef$9^%u(Yh`=g@soV6Me4vq^Tjze(l_~AwMFg1v&Y-f)pe~- z4SfBsnQsF>?Itv=sMgGGqqpmFB~Q^8W5u_wtETcp;Dm<8YuV(*TH6KBLB01nEf=gO z9q;qHPrK2paD_86*4vx)`)di6)>$KM;d%lXmgh$|t`G1Qu@&)R>X7I)EAP|NwNwY)B{(g{NzhrZ|5fB^{~J0k$nliYT=TP>ZHO z^MSutnbS-ML#hM@4!e<|Ro2j-H#MQvYcioTyrD~mLn#M}G$DBn3kw@O4eFA$cIPcy zU`Q>WG?7HffOZvWiWMZCH|RNjnR9M z!N5kLrcgvU{L{hj_XGV;uI{T*xJ`&z?kL_hNq<2L=N8EwYAUQaS}896X(up1iin7$ z`%^L0cQNhJfTu{0EH3&(;K z@@XYnEk~|`$dydW;{^0qIR`hT2QbgqpLk zLPSfm@;K?hQM%=!B-E?j9fA_Tchv04{$or1&WLws^sLy>IM2Jh*&*cc-)P^FB;3RlK>ved%|O zbqDKr{u7NG(_h$RGXLbcu`_k1{kKKRzhF+ZV*fQs+BrJ${y)n7Q)S}#uid5psYm*c z>VH-z2>&}rbtQClbiJaqz|^emNx(CT?so5lb#LNY!@Fj+LU3dN16=xC*>1EiU;h9e z`}z3faJLEp`Edf_In@H;oZ0z#{rz(Xe$ z7oU${YaFn*oaf07nL^!zSQZ;gnT6%30gpi z!*t(*ue^8vK5}%(x9wxh&m>8Wd4nU{b-Ip#$ z#q$e%zuX+j0QdVuzXrg@eJ&z^2OBGBmK#W%(JA0z!xV55uXXc~oYdqqF`mP@E<-Pv z*M~k_=+5k8c0|qszRHc6j$7m~Ggg+W6n3a<{ z71?dA0rf_eCWaPmlNXrQ#>jGL|LX_JItV8bFODl}R96*iA3I9FmZ&1BE4UF(CmeH{@VY`u zy}zq`ZODUE{2mKAaCdRjH3f}p=@?_#@~@uE~vu>x^O0*-f@pUaL=B& zreC<0FJ2gTZc23f)ZTpS9^ZSWUnZda0^YNIAb}4Vd*mTFeEoala4MOY0DFhD6JILZ z`h$*w4s8W*fY)doh;AHmcR(6@3Kr6ALDs%!&!pIbtm94}ORxo7<(%K=We7K@IDO8B zPYf>RJZNr9$CFZT8@bx zFWXAU^de#o2@(rmc7Z;Sqx{|U;}9ct?Vbm^Ub3xwlt-1#=wGIiTDDo1*=&g@DLkDhC~y6A7`M~m6!z=bDng&Il!moz&#e%_~L)w z4V?ONw;sk&8X-zEN9Hn*9Z2#<5-jmXtTIO$yzxdVJ-W3QxOK-*?i5+#)T}Z`sNT4B zD~|6xWggsV@D|noNK#+JTdS)@t9mvxPUP2FWUy{D(MbndQuIAErCdW!pWhTn^w~ffJp%VM|`{KJ?>q(syy)9e@zrp?!G(Pw{_7 zpHv>p{7fM_WhKo#L4L#vhiE9pRU6#{L^-FfwO1z)1Fy?A#RgH(v90d?dPsly8C|JI zwH%z~N*Ct6g^PJPE|x#AQ{Y!jM;%?vqfe}Se314%wJgS~--9LZ9KOk9?F7Hcr0kGz zN`m`XD0-`O#^S9|SK?Qo@qO2m-#x~d*V#XRg^O`r>~r0Y-X$EDdE^0BaQ)5@sV0Q; z!G87;KYIX8xMLZn{rV|k(dY}hxtG#hmN3}eYVwJwSNpB9cN?<1rzJsq+{&*N;Lig5 z&Bg3R9oX1L;0~;aIyJg3=qQ%GjUuG3;KmT4hh<2ci#gByf;I7cb>*hvvk{nL zjin-Ai0Fn7iAuO-NT(%2Geb2>Y@_O4ql-TG@-oMTe$hyZR8XW8dFBP^@U>B%<|E3j znSR}X)*H(sqHDa54d?*zbB+~09|8C~I$9x6o5aiALneb(SXdxob2c6f%)u{xtQNq9 zazrly*F|-Y888naA0Q$;;YEhhS;tN`pjgN`rM56s+6(%TL0N9*^YJDijRz|vUIu@! zHtJ?kvrW?H)3;P^>Q)q`E^5!*fi8Pxs(qDuBg)iCeWv%=ufVV#KSdEl&bzLf(~6b{ z{c?f>%}uT4NG&sGrL!Yj3Uo9L<%&6AtATk~7y?3O3*Z;2RY`HlE5F1eN}=1YcAEO6 zVJ>)4Bp_QRgst+S-~O{u1jR;~T?V#Iqu5)9o>0xXKE|jM+E>nUg)+2a{z&vPc{ z8Q_q>YV<;bEL|XNU+ejK`PLZ=*~*k4q_B;)L>3O<&g;hq`%$__0b zfG*4}gn6LG9*4{%2Ucu2gmtKXk@FI5rIff(%l4=g zk|@8{1I}eY)Cr$hjC_dfwl4{eOUsDtxYT;f15%Ex)tYM^F_R1QT?xTZzeE-8n$Srr zg5gXcA0Zkub|%Thhudjlt|D-0V&Oo$yg@~hF)&NNR&!;z0wFIf{2AesQH!@#ySZ1E z(O60r`b{_LJ+P3x!62h1cwv1mwd#DgsjI8MLOsa2p+`saLBv&K#v?R=w{g19zhJ0= za;Nxq{omASb8`T|=cs#?Da*{EbcAb$xZqREkTlyO;}61xa6RB@kOeRah8Ng8dfdqC zUg95ql8*Wx*SyQ_J<;D6uvhhF%ZjKu!2F~?GduICd7^eIpi_KZ^4TtB=es^C(r zEA*EVrgs;^T19p_di6X4Qh7qB$;7G8=Q_{LfA{NnK*-v7n*Fq%tFMfmb)Sn@>&k@& zMPBZb0@dWTEuMS1yUOx|nP@Po>BV*=X>DP4-q<^TKSNvkp#ISZCYD zjF)n3*O9@xhmKW;VSz8`5^W50P14H$C_13e?o@$v??X_?1Oq11t6kFBVi*J?xhGW< zHO2i2Ne0w%$Trx8%86qCbw+5-)=-7j7ipwT)+LW25TQBr-BCjUqX?>=BjLk4Y2zKi zPVQOyO8KcB0#UD*4CSxLPANJjuSbfHYIF!9KtrlRH4ZJ5QLP+31X@TyqSDhSSdf|? z>A)CRkeCt45ILwo$vsi#o2L$kLjOYI8|oR(`oP`PhE67^Mb8fG6q9 zB~DxCPj6eM2W72`1Mw@dSz`S65a4Upy+CI^P8gP~nOqoCY!WhXP;@S}DXQWSwGlkFEknCrXZYpVU`IVKt4c*Y15X=a@*7y_M z*aMMalU`fm8z0X=lwV9Rn`n*O6R{Qh%E5FhPVX&m96C`mkd!TA5xz2kb)O)#lazjJ zsxnvgUzMPuTvl3KOeq+g8WDT9qsfM+`#}|t-*e|t6%EvGOO4!qjbRs|QndDLaBt!+ z-dum&DI;E$leNjcauAD2$guimsqDR#omUKuIfoq8|DS!NEhd55BYyC z+k;XkYkpvU?;<6MyI_IYgqXLT7Q9oQ^y;ZQO2|L&5Z)W~H>+zNQOz&n!T{f3M!c}| z8=vGguCk+}Ryb;3+Y+q0jB z(Qh7SA-J~%zB9ttaH8|ao)=b<(;x}m~$?etjID)!M|Fph98iRzUzzmQsCPYpCP9eJH z!M#ZOlGHO_g*A&z``DF;?yM1zDq!7A^w9m|0GsBDRx)nsYVDgki)ycWJ3w6){%(o) zQj~U^$XxISI91afxL-pC9paJd1}w=UqLRYMyt7?iJ4JLx8Z1j6?0O2#0FV`u_k=41 zuSfPuQ4)m2!n2O0PN0wbXT>?N!efEp?1D62S@Pp*SkA|9UJ;O`{EfMsBI$rxvw#Ko zWFQInbjI-fNHMXT?sZWsV(ly{Q5`OO*CG1SA$y5+OuF~MU2_NuxCTelrDQe#a?5ik zYCk<9Zq>0Jds;?crwR6(EZgO=#T;~zdYE`jO&2%y&~PH+Qz#d7*VG;BV33V{!vlw! ztQJxkiY0jZ4^>N{d@9qy;7Ahmp>6ul927vtFx45NDNFt#3w-X>fb7r39 z0GC^Q*AN?h|21&5RSyqMK3n~z$xJX^>dZpq5(n!1DitV4$_ap*S&5wq@6AADfyh!; z(x8_<-5b{gMkl~4?^mXYqTXd6;6rc=;?-2pdPp{Y&cgfxl6xahuJ=^C-dv~+_|i@P zCY;8F1@c((vWHTD(2)tS)8Uu`QQvV%9c1b9Zs-curP$2S;*%*oj|)y_k7Jq@Y+*XY z(>ctqt&IK}cCe)R%!_c+o^2Gy`i6CcJ0g^pJ4sgkt#x z&twL=1>(}U=pq##Bg(P%%2VtM5{Wt_(a5W&g=)jnRVJ=6$57Sf%SfGv_MnGdEOo@K z6G(+q5n?GF--S^{n}(UhtCimJ&qZ^ZDkO88zSJeU(qxr1Ne$8u!nY^kSuJvdl$7Fq zl+_WMZ#e~K5Va~8rN)h=^poRt8JTe8+&+O3DI0HX!@beqQ@zaJ%l%44@AlKU6;?b@Aw6|x zaf*RJjVl5M8E0?$pTH|&>rx$Ak4c#Xqu3qvJ#?;!D_uY@7PJMFjFzui5GLNKAf5;2 ziu>k;`{F0uvdwV;vh9h~UfQSollNp{4n%7TWQl{RjJYJkNyhc8a*3Q#qe02*ge8_t zH~DIkF;22XZjW(yx)$4BpyFOf9>CkEt0vrftuqfI%S?Wx5}azb;gT~MCbTj(-C>e+ z`Z1hK%DC`XUdx}B+*Z+MF?ZDrxh$$-JXZJR+0w=NLX^k%QXjg!*beQO;Q6wg0AH3k z{@1JnXj_G-niJVlVy1p;BJf2lYYRq+tb4l2^<^A+1~JdU)yro*%HDwT zGOVajDM|`Ug3)Gko!jLZ?BAs)_QD*^1~>EG9#<+l?2=8P6XB{yejqtmbu+t&2@LiP zHXZ&cMLB-OetpBd3l44{sG7qevF_iNIi-CL!-}S^gNSW7FwOXJNg^2_?BAT;LK$Ay zX7E1vh9B6Q8JRm`7Sj$Kgp~m7j*p+-9IrbB`aJ#FFUB1Bu-TFo>$XSN!MIyM!Xcv) zQLbcC^tWwjuM?a@y83=f1@B96GPHzorO#(`&-#(22$L3Uz7r$5$wiyO7-LU!!IL$G6eda; zO72t*LQly{fvb{Ekz^NAb~j-9d6eT1Kpz{RtyPrmBZl91P=Ph&J6P(v8V5FLh1fJu zbD$Xb%*Bi=V6Mopntk$X{X)tBvp=&0Si;)&BJSZL9(gXJ!Nm*ymXBO!}^2Cx+ywrj+3 zQ0cEbcuuGw3HkJ(&PKgKB{;BmWW6uCdH801`V8!!aljGH?Beu|QI2g@rZ60-ivcuf z{xWG#r~R#O%p&TW3H#$p?1S;a^F5P%9lgG|Bxm_5zY>ocYL3g7rlA;hn|X|=h#=V% zULD>{{W7ZE!fr&YEZ+&7I$*+DF?v6Vt}|evP5H_guUa#j4!)AVp}TLzJIA+Yy}M?6 z%6|e)WE7y*rM&gQ9uqlN9F3I0lYI%OiHas7WDm;@yf_-NyT;8{TX6%+%rRR@WPQ@U z1DmX)7)d3c#>(=3=NajGGCkm}@>DajvZ9h{E1l4ZRR^eOqr&#f1zdSs20%jwaT!Rh zXe>5Ubd^2y^r8#GQ>(`|P4BUCnu8QerVr4X!|M8twRY~yPrdH2 z!#o}C-A(iZ$VwjeC_p!Ng5~##$Rv2PgF!J$P7un%}HlcaWe1owA&?=Gx0TJ zhMng+H5(j!aYLT~sMK3^m6+9KJ*hg_7bh2spDszQN;|nDjbBUjCp;;raZ_zP#M=|h z(^{e-k{dbYY3d24P{#`MPooG4YI^a|Uc<3rQH!=C-smO8rdT>|G_EBTbRO%*2V+f0 zV3vK(v-cx9o~PtmvQeWR=>oE-36h8?oS1U1y6J=5q12#-l!I-Mr9dN+OgC%}}!z2tG37HTZ>Rb@K7KVt#DB znJgMDrZt_$Qb7dk)Y#EyMAG6-G$XIZyFIDX)e~{PY)gks?&!;ZcA4jO)4(KWYhc^C z;wn0ecer)ZuB2&{wN*i49HZ3iRl{6Qi5V|)LXBZ~1TA9fSFpjZTjC&F8?>t9Tp%UZ z6HLK`7Z@JH2n%!NN^Ub#%N>JuY5>;cj?cGmJGDU0nGyQXu z?>>=Y=>@Et%A}YwZTaL*Qo9dXp2<~S_x-4w^a99?T*PDnNF~Xcx03jNEna!+tSaI37(uJ@cQQ)6M5Lee`&ZB-|MCDcg zDrZ(v+Jhc4dT!f4zREXE2z!(~D(p|g@i;QqSFLBbqT%bXHC(AgD=EJlCuo@XzbQ+@j5N^bLK z!f+h|lBE)N#^V?-Gmz0WUOH5O))>_>Cde2vWMc|7;Y2132VbFsTk;FG*Zg1F907_?|HXT-k&JHNiH+Kbr0>-hRP{<@obZN zO8v%aiAO2+5!!t3`ZaEN-6SZR$5}oR-svAcPUMF|QCQ20f8@|M zRj)E%YItn>kf0*dsiqRBNbZn~<$zLSSU_g`@pYTz zWLqgUa^W|wBOWMw%*P=M^ql3vA!U39#@_=O#yC%+fY}Nv_4n8rT0pSFmZU-0U|E0D zHkw5n37j}`Xn)L#ARlV@YE27l6|Q_d<1HU4RC&;n!=t1og?;~a+|r;;eL_rN@J}}6 z&R5CT&t4lbjxEpO5ZsmJvO70H8W_Y@ONrka<#*8e!>ZD49-2R8T1HPw#MUcp%X6`H`cpP}eb9-0QGi@S-O*(tDlh zH|QqKfVzC}+(ZM!F7Id|m$_P!pW+y0050hc~ zV~ru_6@?UfGEDy1>n1zUMv9i<8&sdnHvLM1WW}E3pzf2yqA%{Ao;2_wJv>8w$=c+Zm4z@ zld+5L?=i8;2NLp9C~DO7@%JOnR+cNaxZy-oN;BNfO2OOAD2AcgmBaY>2Xyf$kNW34 zp2wc0^uiLfg~C4T%xPH}q9Y|+o&F)VUeCoMO(Vt+6P`oMYY zp3Bj|(V2I9jp= z#yOyMhack#u=Bi00?=mk4lwC(YtfML62E{`z2XYSJ(PzehU!c!czz0wDo~Qb{~q>5 zJj7rGL#lDI2Hk}dq#INiEpk376o+v*-T1`~HA>M7=UttLwlm&TgB+KjGDsby&cNVn zpaK?PRqCmL>mhA+?rqbF$`pv#C(aaTiB$OBs$1iC&210&Xl|1j8hAvyNP?&%=lPvY zJDxtknAiXud6McrfB0TwbmCCyDOph-n65#a zJFh8KQD`yi*t~4`t4~G$0{yTvJHQ5$HA_n~itUG4&LBWv^3X@P7JHx$#q*`m>zjhm zo{^Lq%}M~de%=rCgX}Kl1gE}TWj%t zK{>*6k@D`Yq~`BR(Tf3?Rjy8y*w)1CH?DL8FejO#mz&)QJRlEkiwB;%hA3YJu9)75 z9vrr@zMguU!-{&-zFzIF8}x{l+adyxwo`CLMRk)h0*Eh}C~RJ8#opF@50{y?LWD)i zx3%+8iVt1rtkWs%pAdV`s1MraPpj_)_j8xUr~PVPlv2dso+$D_#{CC6vDA_xhPFi7P9(;u1Wacew)Z2c}h8G>tSX`DF6vV{783`+ z`z1@}!F(}R(d)NwhIF3WTw)o${1YdIUk1jBrQ!RH=JjbjI(o8mU$r7dI4KasDp{Xx zcp1on8}L0bK!2NyNZ*P_P|x2liiD6Xa2|0B_>imraYXyR49G6aZr<%x17XC%zPO+B z$u|5H`&LFnxT|<;Ld96#RS8vz>5A4lP5LV>`8Vx5zPl;n#Wd7z#mVgN8w;1kQ00|@ zN`M?NMddjuo9kj+3IZ<&xn~TKFwhyt$V;Zo`Thtnu!mPNG_p(BC_^XFrF9SKTEp=L zi~dSc1}CnNjZXFa0)~+bCi3gHfPIHMIoDR#KfSZ4m$JYdhsyKimlMqf0xG%iR}9(e zQQO=U0;bXMj_0jVJ%C11V;3kVDrNj_+Ri0sriCiME*JDxB#2V@=2qv>`<&`2$y8?qqfz&w2GJgo$6eS$;sX?p=O)VOsfoxYA~jm zJU(!{AbZA!&owW5so%A7b>IyGzx3w zfL@1=wt9&6W$!PpH{oD>K+ww6o-(=_D1#zM2+uR&ey;r7ML`CsPXHf$jzdZb_kvi8&o~X*tei? z$(sStruP6h{kU%uulrT-o9!mZeqBl7rDJ})f`$&ff}B|88r7|%Xwsk3z7TG3-ybl$T06Mjhmq?BmO9XBiE(xfX@~9tdFO+lsM7RChcis zPZdqqu6d2|led)Vq-dW{@~kU=q;Eb_v&Q-C|FMVfhqjZa=&UB%U}REULWj zPQVNxOcwZ8L{X+RbwKKAhXM)<&TyaW5M*ETn%EWQASKRRFir*JeY}nL{{~gW$^yKP z@IAM*J;DgANSsNVobhqI3o}!aYK-lJVx2K}IT_H2L3HLh;zKHFTL;=!4<0Nn8q*A= zJ{$U3X!gX8EX`&V_2S=|%k{!0ag6uXI%RQ5@5rM5+1a6c>yX?kMXIZKu6wPtyu}+a zF)B14Z}U%lwSd-WwVe`QST2sDV=?&yXCw=8=3+0~72JDwWf&S$n^GQ)C7-IGcXf>+ zT6v)$>cq;cT$OGZ#$7|&q_nxOT2RyCyiUjXEjRRZUGo7cML0i%M@JEaZ6K!;pg?j0jMU4v@7 zmbx6c0o9O7Y-`Zb+s^oq-8XI6ra~{n;b$|YZSB$sqbl@ri|V|}To)it<+21U4Nwd_ zkla?hr~0_#EJO>}pKU?;NcZb`Jcd9c->Qlor%8kZz>hu2kHdn{R#Fc(b*up;dVxi( zDr(t4z8x!sZz|NU%O_VB|D(qWAKBWdjV+kBki*TRU~XXP#@?ETpps-+KD)@!>}00^ z7wPihN62%%^|)#2F;c536VD{kr|PU+_(qzFUJku=7w*S_l(W`O47KMu@GEEJmeH~f zb0KZ32w?C23hSXo>idJUoZz>YL(P68%;v`v%CTR_Y@Sa;V1QWuO_^4KyCj`EWw>wug7Jy#0f=bs8)wO+pIK!BXEfnG?P&%{nb7XYOm_&i%(* zLj0blKt$qXt-?pKv6XX58Rssx(gG)XBL%5#)xe{xR7q7DWE(YVZz9xksrA!4b)hD} z8)(8^ep2f!+JKUDgpMF7EQE<_DdeL#P+?qn^uR84WAJ>PdMg!hP3G*l#7=1e^WGjB zzrAXA38eI&B`js#tiX3VPcHFPTH~xWX0jPNvZu|Wp-O?G=fU%oy{0UyPz*JcwV;|ZV7yVxU{h5c3~wx)S7iVeNtcv?tEwzYHK8u_ z4-M9--<)@I3iKm^-LWRQyNo=Za{1F_nsn9TE%iVL$j`}yp@`w@(RDRv()-+={}gx< z5M@%=xXhGyTDsTnDx4UWL`r0{iMcEX$IknQ7_VW5tp8z(J_k2Iq=er-eVP4Q9$C;3 z!@sxUs_R9{y?wAcLcsBXxE6fDuD^30CK9u&V4u~z1tWPdwo)`9Wys*#7pg=G14&7Q z#12`oyC*$gbch!V8G+K7AF*Y>O+cl@alW5CgTPabGO4UH zSfH~ifGmJO7Irc6BoOIK~yKu!@1jA&kg&yS$ zlylsZH%J7`*X&#ay_csCoqpa%TtGkl)Wn1t!q4O?tg6IKBi>-$K~&k-zYdp6G$A(q zy>KjgA?O&RHi&GWN(nRMtLCqk%zOn)v-W`Tc!wgONrcIF4f@f{6F3d|W4j9)-#baV zQ+OJbGtx)hu@dMzZpqCsBZ>7V`a&EGwqtbsR!KXj9U-XN7dnH15&c^!P1g5$0BX8V z?kg4Qxj7!)Xb59KZ7|7>w8woaFMA+XMPZ>~1uzPJtlL?ik$fTuOZwcoh{Y;*(Qo?J+Oyh-bGEVI=@L7OhLF5aj;K2J%qcvLIwQy%V6P-87R=go2Jy~X%P_tMWuV@ z$S8%Vv?dmRf~PsZA7vRsN4 zIT+j*l@Axtq35XXK%eFCsSg!%Mdw$i$jdt|=kK8RaAkVTL#0+Qy7Xm#q!7;%*i3ni zQ^Bj-#kyZy@iG?|t!G>G$J^%+O)50ak^J``SWlJNs3Se-;JdNSg&qOyb{BJ6GC(qgV8*yPm zCCFR$FHFxrn&zJ?+Y|LF$x(?6-ky9O~kdU#U0J5dWEP9nJfoU(W^X8H-x?&uJ zP&L>lQd@)LJX0<)lr)JkTmpnNSJ3C^i|1N1UzO^p*BoD>MxEud6q`4>3E+-=K@<1D|CN} zm$djoI&(<$VKn(83u3{4iGKtt%zK<5N=5zIfHII8OB-n3#)>g%sNj0N3;{fPx5XS& z%&P|?BzZ*}J#Pgei1y5`kN3o%X^(7uz)&Nwuo7)>%>_+u38gTp0dN=&tEOmPPC=LATvr8KSZsGZ7oVMr!)~<6)|_Rum!PkwSqb@uO!o^`jZef5!_$yI z(2956f_`?x6R|6Pw8NJ~?b!lC<|;H!m2$y6L`yT)Z|jpl#v`UF6Ue(jhNCZ%o^RBW->e1RYdD3f5uxdDNPYok zy&)Gl9o5Zr4;i!Uy5=UgYRG3%`?HkqO-^^Wo=|pMl3xI`5!Ip{b6~X4x;caQ$s58N z05>tlcl+&6VyMn#yCsw9dXaX(>q}_-Qd1ZKQvSOb+P@jz<$L;xv0OT`TwCN^7SOTE zy-T{sT8EI>W=|d6?IYIA`T);5@lzyF&BIWqonN` z@fxElaEpBgdZTU@gqTozx|0F=WxWj-C{AetlcObhLo6s%V=Ch)(={OD?F6#hh!2RZ z7!Z97P_*(JBMkmaE>IWYgI78bxFc>e>8LmLc0ns}WGr4!hD=Ck8X z*X+K?;s$)8txg$oBC%8&O#m~OWjcK1w#VkV6h*tn^tqH6V;WS{oq`=m!u(1ptbZ}; zV^vz>$RQ;&Qf?e94ux(I8%EqY_ZSwrCO*m*KQFrv z>TSJ59Kq)Pee(wOsm8cq9TF5U7mI;eJq$)fZ-Sh0bS0=_6}uBoWC1x6XEM+d_r;Di zKNY+tku*93uL8)8n4w2y?P3ep6&4aTV^^IVzA|__? znW6FkUNbgC8CRT}qUCjU$ycHDpXdS~%zQ1<>-`{9n1UaGFYHr-JiQ}6{)qrzDaiD^ z^@>Yza{q!~p^R(~t5E#??*M#PAj8$@=?~k(bT2`PjaC%PMV9?*^9fnjEvI9SEDxj@ z7}Z)SymEH}_Mz%TFl9xUCu;nx1>8NJYaipBPa^6nOTKG5r7KI;NlK}XqlzZz<2Ns# zGFJ>ch7j5Ifg5@3wngdIs66{|BO=PFAe2;KBCyarR)oJ$aDAZP>!E2Q~AK{e>=E=BJ-agmkFi%s76PyDfgTN z11^SY`*-{EPpo;IE)n`xRoZSqRM|P1GMHB+m!Mec9d@Gd1X83_)_5N^PJatZTN_x8J> zET|LZdZzz+r=>)z5oY2$C-j2pgqHYh2r}5^Atrz*TTC?uvZ`<^3S7<#Gdgj;vEZav z6;BbUA0{&MU=%(Nd;#saCjHg-dqx1dZj}bS61U`1j=F=BLQz_POtk%iR`COqhmz?o-B17USe zL}AMVXHu~52KJ~b5e}Gtrb1$&VGH^y-$UT{>b!6Ir-_BgumYl<=D^FP zBZT#~^9iE3WWS9_zTz1@gfenKs#oVCRV*N66ZkLfb3?-DATrlQE)<+6D3BylrigWj z*OA>@OsAQy=3n-Am$2!29n%#Duk;Z5{o8yeX}1ht#wUnP{O8L=>%)jy$~#5@PL-^= z{S&d9GAV<){D;W^YbB9UjO|x#3O(WXQ0qJGdh+OV9U>UAIiHQj3HtP5x{;onHFEBl zQv>xBE(!qKFb-TQtA^SElR7laBq{qyt1L&fncY=lYp@io_3N@aeGPS|us-Q`Q%bcx z)D+}f2@Wl+PElkbhxtg3*P@}bRxL0~-79pyp9Xh7fB*nLKfnOw|KeHyJt~J*(ZtNf z+Q3o7{l5=tar$2l8r%P?;NP1j?*CIn{?Fn+BsH}EC8_lTICAv^I2!i@IA)`~iM1OZ zT8AW;1xG6EOvmS+qXH-93?twJ0$lRgIBu|~{wSqzta5s=jHMW@{Z&0*xZ6#GY zbE0zbXdSg9rw5ZLlgkSUwk{=Q>%-TFt>Js6_4Ml+TmTl6C;~v-3bisF$Hit1u6Vic zxVwM${e0)ig5&N|z`KP8CqOvuyT#)1y(4FV0rtW`5W#$Ve4ffBLBNk-=p2MAunP0W z4Be2Gb#3`w6v%@2nEij8y$4hi+xIqnG8sbXz4zWjuY%Ojdle8dK)}$e2#N_+REj7d zSP(%`M8tvxQ4#DNL9ih90*VT@s{;D1GYN{yz5nm`uDkBplbQYO(|4PZBhdZ8`NSu? zuH82l-HWkN?6G{(2*TH&zSDAhI&gEB_?h@uT*l}=UmXFKOtwGJyrZz zf3?~jrIC$yN3HWVtkZuop%=TYKp<;sWLvqMLP0O;wB-rN!j(mSR4qAgR!`^uaZ9Q~ zzdB)k(|9@v6_9v3lD48UXIeDPQ}OfIBLf7D&ITrBD}RhDe0O_&?vPjO;M$m%UG(E$ z-jDVV_P2V4hrH~ff9d_6K9L@$MSK6)e(2!D;96_B^t0n@vcey@BUklyZKnj^&HOPL z{$usZ?)y>-7B)3X^A$(db886A6W~3&w#KQFXK0Or*s%U_S)aNwhG~z$XS3D4dWvUv zb#qfp?Bgv?DOZSBmAS;rj&FJ|@Jc}%RO{~wLMt}AI6SE-a=$B9_nhtAGdVwbqnh4^ zxFeGI8m>{|^ZH_>xJ10Le|59{z0nMD&+7d>J~qi$eJ)!1-m>aEo%-cwIlEN* zouli@oeRDY4=K9ub_!j`bMMPXTBE`B?LOsSKYv-ITEMoT@=}#R*>!pn{dn@7)sD{7 zw>k8*c)i^|)+LDae~KO|-Vw5Kf!Mi#XRdLY!HM_FrFZXi7x#O#CT%bSKIhUGk0k!HSk6yYN5S8-u@EXbHT3pD?biI3B_LRe(q=#wJU;eXYsxP1JwTQ*e4p- z$z$0!U#%Qn`K5Q|0j(b{-`muFj3!CEvFd1FU|nJ3)wgFl_F_-*mPa*4QMR#WTpyC% zt)wd->ug2Mo{BxUIK}I_)Us4J*5`?*7=;hJG{;pY2{{IThO`rAwhaL2`zhIia3RHT*x2x z8+dEZ_2i+m@&kW;`uj60#zaGHkL2HY(@5hUQth+~&l&hmu@GOU9yne3P%LA!{JJC6 z(Zv(WQ3Vs>cH7WBqSjn{Z(0|eYlt0BynOmb%E#G>UNuA zy}r%hYhQY6>$w|l{AbcuNA}e@Z;+E|C~fLFC3QpOgQIWHg%N&g{Iscp+fJKrQ$_e! zk)8$P^BW@tJ+oe0?ZQ2;Jp1NLqZeVBkp3)zZs{-PHyTpSrM5Pj zuRdG6YGi$J8X?`>&CJMA!EfSdMZJM^70&W`^Hn#UOH>IS8ypZ@$aYy=%BBUZLDim} zeR|gEvB+x0R~w$6Q|hR%wBETibSyo3Ezja>ro4BI*DHMxcyWVst zl~koi(^Pt%#F+cl?uNzoM#ViTJx~13U*Zw#x++3YGLgwNJZ>Z%jxiICh&-SJ_?f@=!S@r70f_Xc( zl@G4;xU;T2)xb|lKCNxd#i~aarK(f{7sM3#=l2S!O7>imUG~^$oKg-BJt}`%{E*Gp zvnP9n)>B~I(qX8Tu0rR5WmY`M71A-OI;rd-dxu< z*B9U)yI_!4u(9lLs9f1m!LQqc!;d@W*4wwY1iN&{KB|~3V^kUy4~*}5e0ZtWgB^;V z{JRK8`VbWy*?a1PX3uk{9v7pglKR&I0vCcD&GWxr-emjzhHX{qdE0Xjk61pw>?5ju zwrb5-hvlozCjGnBn;$6$8toZfTENjR_K5Rw5pPAusO1-_gXJ3ql1Ua0oz*hF#-H?8 z2VTjr_=ohix+MIt+tJ-Oh)%ZRp1#ZCn|#tW3_Wff@sed5TDvVuy_b4Z>!MHDppM}} zuXzhKyeIEH{zfet>gPQg6n!k`P4Xn$nbj{U?bSKZDz~Tm{9Y^Wy&%1O+qE5y{7X`7 z$n{IMzw~^}v*e!@r?-2byQg|(^o{I-@0r<~dlq}0h~dyZ6|;rV8aDhSs4^o~j02V5 z_HA*4%RdKX&or)xElhOAOQ!oew%LD-^WwNtbnJ15tnCt`17TuYeYpa?Ln6rA)x6I7 z4z)e$!iRX`baZY$S@Gb5-AjRxn(7a>b@MY!bspdRu|Bn=z$x|03JamT-JX_K3$iTgK_ePWzU0`F@slk`)*WqsGTl*Ui^x9f#!b1 zsF~O@7Ugi_2JKm9OXB)mYK!@Y5%0r|&vR1*@(0KioSkyQ+c9^sSbXf_ zU-}9{*0b?#YY#tXAo=0>TIB;DhPn0|n^-4E-r=dsJ9kxOYy!y_JdZpuOy;#LRA1n+ zQS(&HI!kLR(PPk@FZT1EkKiC#!4kLUK4kwpu ze~^qg|5T;Brl@{lRAh6n4|y<|OXvAE$KINlyyQNEe(N3jqCOqltF(m9-WHCcB>KFZ zH*a(eY10si?*jV9;?>Z%y;k};|J;4!{IDr2a7{%_wbrXUm)C8Yb}n9-*S5ydtZ47f z(I`8`bPWT?UKir%hILKyH?J0b_>#ACUtN;$v5z-4hfFF;>Yp}ZXrDgnmtskj;JY)) zeRt*JxP?z8lqYR_0#l{PJLtPk>QH=dZdKvQcYbj964{%#M1ri+5$Yc#5a9cww#?vW zREfri4Th%2S`S?`>&7ROFI^*gJK1Qz5(y z@?GrMcUP6S)e2p|yy3#;WZSR$l8?2_PN~wKROkv0`yIj~#uTaP0$OrVbB~W~Eg#!3 z`w=Vky#qFBcgEir1-E|bvO>I`ZS58F<$PeuDfi-K%{;d14?CRlM6b18t#~53{?(3} zgp8wAtKwm;5uTxs@-T|bw5JMbLi3Ec7MvkEc(!3^zyXOViWezZ6wF|vt0gr z?<{L>Oc_ebvMs%nQ+w4~tTlArvGaRfUK1(v3;PBK)1tP;zc^v_VfWV?-%!JCyMlGo zwW1bF#kOC3(f;5xdr+@2xxM9*z?%nNG|EMeH~VxCK0B{}dQJ6-hE9)Xb%**3Zrt^o z+%lYDwsqL?#PxlxLmisak{_%(dFE|%z1r&(v2rhsV`^GOiBpYBEd>I!VDbcl*CKRXh9U3sL-FalUJ?>Z@yhxBm$~5V^^m zv2PhiySLV6(sI$s{PO~$F&{oVrs;cbA*F`M7|eTi@>b>KljMT!*DVfpxca&6IN9&B zBIrufW|d{6Ooi9Zrv@q)A8P#g@j%=_b9Pub?~~+P)bJ3uPj`;GhuLpyh>Jc$Y}22+fQ~ka@z`K4IHe}LSl8u-lF!e@cTn^1Xnu8Ed-{RNABT0@%{5;I!1%a?thdjy?^J$)X0gvY(>+Sd;2FwJm{r2 zH{bIciRJXETqEP3UL3GWpPW*dG!ii~Nf11f{o?F?JKx<~h|vKJg9Pl)IU` zRkXLR{k-AUW`gdTeH~5teJAd;b&m9`9Bts5e7=@6b~_}U@rRBid&a6N_*P%uu)C^GhCNif2nRi7WzovIkmlD^-lU=G z&4MbYE?3orcm(rZn zd7q3fD`~uYbNP!^v@gbve*9bOKkaDdce`A}U(Tx@?EkX1KwrdSynaJjW8LVKSdY{8 zAF-AkA#1IvUvD1Ev6SX~oZS-nkPzwCo~B%e%h z*+M-F%ZQP$eOHKq=U%tn4RZT2`DsGwtM#RMUmsq7U%ldHRO*KtbXD#G{RuC-ZPID7H?L@@wU+h=U%hlJ$1!o) zq>O|oUi#$1J+>SEPKGG$EJ`D`cJW$CpKTL=erX6dw$MEp6i7TWTNFHUfs|czHvHg) zK2NHE;?*Y!XPpGPzk8OjqvtAl@ICfL>NWi9M0O}Hy#&_UVYA?;EDVy}tK9yq7)kvQ%2ZElbRG&~cUEJB?jkk_Yt^)y%GQ?Rl7N zlXG>ow|n!%f%pesq8PhyULMi|J4R)H#pq)bdYdktw0+l9QZb_THL);;%+BW(@r^zH z#lh%H`c2VE4;>$id~zw#bA8Dc{XtQ2`j}&F~WiR~sx%!LhY3?i1&D|ClIF56S z1e`MIb1y11VC=fTA*)$sm$n;D9X!gTpYAc5|3dm`(UBBR3R(i^`gCCaF`q z2SqI`L+*`U6_q-@DQQb`)3b{!3e4Zzo!6(nXZPZ~BQNdCcXL8nKCZXyqFM33XNbukf zAo>q4M>#DH>pnfQ{&c~CRT3Mb6o!M?dd@npp$Vf3SBZ>{Pi2dL1c_c!jqVClSMFVLd6wJ?8*$Y6JtLeuhLD`*yX`zY8&r|7p~XwX@hi+yZf)dF zJ3uO}$kMsC@z9dbLe%e4sb?M*rMTfn3^wHZt-^Sws@15Ys3TBhq^2+EWM}~$DeH=T+OfezK8Y* zza0^{_RKpoxAvS--^11ArymP=#vk`yzgIRQ15d1&?}V2=+aSPoQ+!46XzMwm+YfSj zoLqXJe!zSCHTSWQeAR%G-|5y%j@|3Gre#c&-PJCalhhR)o?PoUbnIel`17##{B+}; zJeHNmZZu!Zaw)1eAg^}4%KzeVO}4>Gy#Jp05f2>m^@u%jF3t)6G4)`mGD}zBFGsplZ+UVk>Z%9^oLpSgU{X zhWyEGQvH);)p^q40mWxH@=7m?0Jg`n8$YDoyz%Low8cP5H1E@~F{7-g-6go~y9C3$ zxO|M?@lLcn|K467nX>pI*c4|o=8=6`Ub!v9NL$zEsy<2K`nI5(TdV~=8}zAYSb4E} zO{{-IP1|0H9WSN*HO!>7zYGOdzw!}{^&u2k(Z;WN47@yeY|Oay(vkb9+SX=$r6eam zyGe4?fcUz?rn~)|_R;)Co_rg*uf6giWIH`G-0D2M8eeR8?Zu}U^+WpMMkSl=lNDDR zt!)vii~Qm0z;m$d`9Ue_{*d>3m&^Bz1WfZNT{2tkv7pgzRHlCZxA){OS#pk#>{+Ry z5r63iTMC|Xlr51y__?jZOM+K4%IA3>J5^sR zDGeQp(VG9MyS%!)Ap4`gj+~X7!5#OkEh;*eaVd@Lf^|j%PadprZ=E`N>$v{WQ!VkI zvIGZo3M75q#Rf!rqn|u`Bwv%DB%!^kYiK~oM(p{j{swNZx~RBgjgo5(cE7i2wT?^* z?W{ljd0%$ggB8|OM_bk!Ck1LvBrVnL6>DGKKQt^jv~;J{kMCl`M`hC{Jxd0xa%6sF zw(4?-9bG`oOSGZ}9?#eq>_gqx+S2Fxd4tNN$o$|Z{_E^A+s=)q9y_;8;5?c49l&i=EA)*+m^vNxovJ@B1v zoYj)n44RJC(W3=}2jAz%4TPMWY(AV`WhL7l$P;p9C;ByiaLm<`cOE0> z6dq{ho}b_|7Q(o6d*$JJ+b|?#r@p@1^J0cgkIKH=J#XEZuhATIaHcF>#e{dVc7ZcT)x-<4h*J3R6B`3>D4PoI|S(k)j0_%`-b_d<*seRA37R~t8Wow6Rf zlebCa1J18F{OS)^%?lbhAJMZ{7QcT-lPP6e^gd4XPBBhuo>A=?9;t?PXAhE5_T^b; zS7a{WC_DOC+0n{{ctCrT%9`mNo9rr|f_Z)&A2khh_>S!L+DLe&@${uwYP1B^nRC%a zv(96wqCA;~KW+~MO%@zsSc<=05#vP3w5R8+Z9BJY%lUex;~Z{<+O3WA~sZ{t|g zldR1ZPc|%ARr4YzLw(7F=SNYgBkck2PYWNp=-rfNTb#*ib0Z(P1Bn{}=zlg)12%suR0<92(w zn(z@FhqwdFPTT5Tu+>!fQd||83FK=HcKQ({l#~*B(spy1J>9Oi~OjeMv?{MjACW5*{)K|9X8=f^I@qT1vc5A~jw2 z_df=%Yibg07HO<+9&Ku55@li>p>JknN->UzHjdUeF{2om8W|aznVTD%>1rzGFF4f42+Bo3`s^>(T0XbW|0OIg9xg=2{qEh*uXe4 z!Z0e@G=i#cU~X&>X-GAQqMAp6cuHbS1|^0H?3F+oc1VK`JQztQ5}uj`4D<{@Qbu}m zM!HTMEino>WF=A(Xpxlo_$+r?VjMNfofeTwNzDRD$?3EN){!58_2=(Fay%tHIw>^) z*d}J#Cue|E=3id_*=lo=9!XEjJj$42LNTXMj7$upOrrGlBH(`%lPF_zGlR%TGlOVD z6T_%T!=Fw4iBC?YE~Qa3K_ctS20%{4NJe5BC7KHSQ!}U_3Lf1>1^&La_GE2evOC$< zkF4!U_J{wQh3i3L@8wIj_4bCB#^HM5UcTW@zGPc}vM;=2^4A^HzwVg*b%!NV|KA_z z&;7vQ-w+1>jxd<}f#JW2GW<;x*unAZPX^ojEuo|)lcM6|0l*DAdQ<`i0AE!vHR?Rk z_pG#P@1=3AE1B~z3_DPhsfkh4#KF|_4jDab$teArVY;eFnb*ipK zBiweA>*scA=Z?Yu{Hw)t$O$^p_X5iKVd2q+^51o;;@G*n?^Qo}$ z>}>qL!v4mx(L`$c%pr{|&as@Y@4;yts4C`OdGJDsUGsR1(BV6>4_F`I*d`~>9E!>0 z-@12X9c=f2ru3fIo6WoqcSv~Dn}`mu_?c%r&2+NH(X<;fJM?{f{n&cKqwIu@^m2KP zT`cBbhkvG#?2{5BGg4EbVq{{=8BT{`PrU;dE7__dHfv>5w;vPecs2)?2?+c`-wFT@ z*bbcxySZUCxrR-thqGaq>>L1>goF&{2`EgcYR1|7U@;Ete%)iWaD2o*S9E@+e4M-T z98k}sMC|jTpDW;&$4C`yC+U!hh}5?vo5SnB(AmCngv^2UOQuF*#~)E=Mt{PmZZ`xU zx#?k4F74*E?_17?YUP|qKfwvfNr}|NbaJ)zjN)S6VTjRt zUiPFg^H3F_F0fWlT=liW#;S*;PkB7~hiq zLUC>xe{G;6e$EHr(Ak&gd`;M4=XT7exTKo)7j2OF=VXE9p`XmZG!GB=PRJ?NS$K0WVz>ML(w773 zH!KQRoY)be@SxG3?O3$k`U0p&fcTGRx?>j~xwdFcDykHTe4P!zj%fPrydEa@TBywl z*oP1I5^ldMj&Uk~6*in<^0am~mdVMr zS~@Kf+rF3{o~cCmvib*D361idb8i@@{hl?K8l=yYHRpgjP$M#8Sg>tcg~uQ)!`?#O z4l!{)^5)~3?dL}o=7RmaW}QRDl<>7peH(ZLKzGLC!tr1>@?N89+ieCqDsxD1q{UNN z2e)8nuCUZdYV%=iiv5FEsqW~rUk_HeTr>E5VGaS_sY#L4w3#m7o>nN~~1-oh5Zq9nC%YQKcrA%&j=N7@aySw*VZ!PVC)2Y44%_Ft8Gggh| z+x|QshzZKB*S-lWTOxhfZj9T>>!|7rx=O0{%vdu|1WzN`rX}jnsQY=wWw66MkxCu1 zElqYU6UUd{3eH~h2g08+Go2xyM5bUB`M1v!{lvYT>aVN|dad2v$pSEsi(>iKBzsCc z6`J;$PDQ@@wgG}er)&AYt>JTM@$0j?d(zx;PB3f#DOOHG2saYNT~{^bvVVTQOwESr z4|78P(kzQb-<^eMd|%s{&E9@qrCU7q0sJ{R7TU!N)5@%mcu97tN$K&l8AB#qdKE(X zYo(FAZ(cT4zAmcEjXpWd4P|03){L!zm}U~H8nC6wZCxSh?%f;u`Ez67ZvSmzIbTV~ zd|1Z=iHfp98$9c>eVzNZH*c659zsio5?I*Y7bBk`w&k!IpZ`@p+e0U+-hbd*J5zy+ z7mJUe%X=O2sVUhM2`B&X+MhR(#S`Q6@)zH%X4ytUZGvgiq!~kdQ#djg3J6lG-b^*! z>3J$h@nc{j`@A^?uyjc)4Vy!1%J$4df^@ICl?wf-bXV>xvgnV@L${L%$kQ7`rRl0+z}aG$4)1)!AQlTfH~1Yl=R3YKNZ*H z;|D_!>bvUQ_tlRS-SW~nd|skG-+w_}(vsrgVPAgfNvZGv5+-iQA-522sqI0Zl{;3s z+H`~mua`OD!~P5BK}|?X&GMimCsU(f^)roKq*B`qYg~77!E5fzH2XdMUXP0UUzGep zdef4rX>(4?S{1u@5PL?!+u`sh3_EdGve53d z#FUxHC;$BosKqLpW54bdNVMOr1e)gWx5=K(Ir}SmmKMJHq7%kTRm+Y)1nwdp2OssB ze2x)VHXG$lNu|Wc|8)NjhMgRS6%~GBtJ-*Sn%um3e*hmYTe&M{P`n{WGmL;>}EZ#oN)HhJ)-%RRF8q-TXlABxW z@jtm;(|+ohv4YY9ICbIZj+{rRG+Bys6L7~lzX8)B7o8ZXUygEF#mmuqWDt9U(Lt{d27B@UV&X<~&6rV9mSq}?F zzl5Dy9#;&>8 zwf8PYb&F4!&cgU){EVV{*LQ4$BNldVN@`LI=Tqz?<`SLf&JD_|a zW$JC;COMCyllyoTC^z(GA^fP38L9BdN0yN{$?1ZtYV+vZ3)6YE9_yPu!-6zQlYc{` zE~Q0Mna4}ABK*fIu`ne*t4>z^$36Gff}={uQ&kLRqp)KsXBwuv+w}mf=E;Z%se#?o zrf65vz9zO;v#SZBM%c&Ge#N?SxFZi>@0(mKF6^?R*zMSJ@bNigN3q`_&e)O5zXD1Y z_{D1kGZCtf7F24za`hHX z{*IXG)o(3TzTI^aLQkBfzdR7ba4etq_03A6#<#hlOiVRh;U2ad*oyD@33S`8+plLJ zzdfU929uEh!|54rbDa7qScB#35Si797hDVc&ugdEpR@gwI}l5h&Y)KLhu?y3|Guj4 z)Olmq9P8_8$0_RN2UsYt2$*=99l_f2te=LB$}=zI$31NHIH?xwyIkai^Ir!>lHeJR z&{WMH!JA8K>tQ3qyFKn4#hH*BlHOLv`c>@w8_ndGIWO1<`L7z+-JK^;;?WvkT$Ee^ zIREA+C(vfbddC>`CG1l&E_NQZI>BKT(O^YyNk;X*fLz>J|L*BmwNdTnHLzN(`9b+v zO9t#uo!5@-3=Gfy7YXjPm?i0%R9LbEpzT-AKmk9t-L4k6p6*&}{4&e^g-yX60{(Oc z!3aC%9X}#HiY`W1DGEM3q5&W@GqU?Vmi75PHKqSNZ!s3ZydT3WGuGiEBpQph53NLnjF1$I$qkaOU7~&l1EpZuy2S7v?}QuRJhQKLfwc zq3{sG$FB=^On>KaFSlcldAUvR@LzD&f$2=tscy3$5LJBkZok})9{bi+{ew$SEx7u} zL8kVsw*f4@mPQJ|^4~K-`aa&rP{`Zl)#$y{F^`UW2$t#s-fD_lY>36uEuI?fc4&f6Ggy zC8qt-tAe}xegFmvIu!6{6N5k7G-YkrOg0so{sZTpG;3-1<~(h{T;%Iy&yy?NJlk|% zRDLR5HUAIH9~#`r`_2z&nJ;i1OEr=x3l19a)O%2%VCA~(T{)d zT>s>{rgXqlIBoy?|AaEb=Ja1_=eJikJ%U4SbbS0w#l&HUT0tE8=&}&se@dC<^c9EH z*1=XNE^@ZGwrs?sFva<}UCKX=|B3%g8SQ++RSKne-AvYRxoGFod^f{Zs&jbqKcy^4 zm@!alp^4X__P6i4-1YWoh}-en30*$EmYE&?)3=!`JTbq&s{qcxN<%fvIw3yy+HqCI zQ`W20|HRtIQ)me@?R_)mu?B*iwZ#wac+%o_JX6zD|Cxc`U!c^ieaTNLa<;FrC8MFO`=d)F#q+=^fh_$T_FMlc#r~(Ksq}(@ZP~l|dnNXo__=nB zIz*9A+|T%Lc-oAIZ{oW36{bvw&p%y1IAYgcu{d&luA#zz!kN&iFGrez4FYaiO?743 zoN(_lQUKpdF1h?Cl$;ou6!ptb&YtGMGA=ISev{S(56Q>QGS>5-b{+ap8D7cMM8C9| zscTPte*lV*PBymPt#!hyv1t)rWVq4uKSlht_LeVuyb}uzDtc3;uNgTs{~$XI?;7m- zPbmwG^~~+5@U(JTBqjZ)EVo<#9D=g$lbk>O6CLPY@$J*u&I!inKV>nwy+U681G$&# zR#|a8rPvk3)n)DPex~y$w|6QnF_M-{nd!G{m;4j9?ZFeucQ#ouBb20u;Ja$3#gUWGc*0N0QR$EGRkrGPH^$VwgJw`nXeJev1yVfgQ;-&4dw zgNw10Q*dE$O4mnS*A5>SzG^Ljj@kd({pXUhPl}>?Q(}J3L$&T&tiqS6*D{TGs17@1 zgD=-s6VnhPBwpP5(tg->$FuZR;RW|E%?bI-=EicKzX;K$+qT57^Ez%< z%r5xV-IcszE_B+gkvX*9`4#r&E=TO0zoD$#(d+TO-xjYqH5)fe+t$37I|5(6jkIi) zI~dpKzU#Vk)=r5lCI82t0ATt3+^Kh<(rPx9vhy0)=~17*@3h9F>Y2#Ne@QBp!W_*d zl*B~p&xE(B`44PSp8?{&YwhTc?aE$2|CvVWUpUrKB<;rDvq?1Fp=$@G+Hdid-O*KA z@AenF*HUU~JeF!=Aw$KRkHCtqSE3n3z3VjmMpAHe#~oijKWyaddlr5_u@t+cDH-g0YjG_dk- zG1pnw3Rb}yiL3tDOXhB z{q;;wAs6Z>$;>x5(8eeUpJ0lk^T%&%Rxxx;D@GBy<3%KX#5$i5|!t)xlu zQ}irGn3)Plr^Wx$&hP4nv6Ru0i9?)+hWwpRZt>B{qvz9RK)_TcVf7%S&J<@o;nRIp7O z{eJ3N)6ylUWtTh;M(Ej!yLUuXaTL^3!CAQmNZ z0YwC`1APQ=0#gKV0VP-nzD-kiz(9|5-Pf1AEvJsO6^816LaKw%*U3;47H_Oc@a>lOh3V3NwA2>_7J zU(G=PHgE#f|6>B#0RUF{MH~V+0E7z#czA#?0(jsPO#}e`LP;Jx;6?x*$RL0KxKSbs z2=Iy>0XU#S%pwAuV3sb%YBa;Dga9Iwl^?Ql{N&dnW^q7%BaGkcU-UiCJ6_ z#DfQb(NIX_&;SbwKmY)sR;Ve!36vokhh#DDkOjDbCIUEq-Ju9@13lPf9FoNk01y(@ zY3#r-G>i)Xz{^MA1~_y%96VH1fk{*`A|=Tra*#;KD?`GrC=bP`tD-!>3qiPm z0z!xP1$o2_{-@=KO{FmkfQ}f-1?F=De!$J?zzr3j2=HPD)@X_#V5~+Ed4Mdp-~xab zNORhA0~|uv#{pnufN}v2PEG^@4ACqJFb_q9m}7>|bVq?G@ZkmX_$h)A0b3~Kqz#F% zJJu^{A9F}#7Z^gq7e<0(p%jjXW9td8`EA*Va9rf(`~)P5YMV%j&q|}E3H|;#EB396 z${+WAW;kS!fr0cIC&_x*nUgKa}xlN zVywm!Kqv~#i2%qmw%|Dd89~^AB1&fmJV+omud=|33rHd<1c1;zIRW4R0Eb&Z&=&!W zH7H=`03eLfif88pAe7OLhkMhE(TPzLMh`|g7?(K(xBw7jTtnG`B?9#~K#{m}tT?R5hs2<}bCPItx7NcT}iTQAg2s6In*#RF42ml8g3jmr6 zj9AnhQm$M?4N~@8yf*VbYRsY*Ov zf&j1qF@#72en=KK{Naxfg+UyWWdcVz7$M37Ml<#Z0Wt`Y2l%pn=)lS)P`al86u0o- z#OWYJ-k;)r6CekFFiK~OGezlUf&lR3CQ4y9xj-a45Jpq*z#Tz&fFVNX0Sn<3lBvp? zbHL3TgureI2*J_H#&ZHs1mOq9D4iq%MXi_qCCUp;5eGa7f&yguF9DoP0odBe-K-8b zET;e)JKV6+|MeBE>HoU5x(;JoO9W1!2tokzD8diqnC64Ijil9>1k*040*mNFg44tk zL;;Z2$77nu4AY?Kd|7P6**QSMM#grbj1#7ZL|m|)hPq)yz(W@jelKPXK6a2O_*pZz zrUjEQe+6M16%8_lgb<89GY(P2ZZ5?1H@Z+6j*==jLI8ZgAKsGVGs6@k-(sv9evFSz zKm`(YL99W7LYNF4VT@Br#03&EvlqF$pmcv?UBp-lv(%4KH01#-5 z5CDmQZ~;k9zz(G0@L3{26k1zM9|`^Li(u}+PcMj-a-H#z;4{%bR3~e8#r{R_M*Xj| zj=3A%v>7L(I|mk}zm^01SqqE(zjQ7($d1@Jp^jF}X=IMP8*|=$)R}~-S_7q+L>?nc zunFf*l;Rf$Kqm{&1#CD0H;6=mfH(l!j0QXxaNz_zGj~XgD|jwo&*I?*066D$A)+7$ z4lMv0xrJi~|Uw0^Cu+50p^?KUj!n34t)^7%fDI@G6T7 zFzQgcG|+<>#(qMU3Q#7%%OcoLW$bP#yp$jig@72Eg@=`M0YPZXl%R=Y=3{WZIAld=zlZ`QTS!PskAjWGHnp0;GU6fxr!d&@3^a z1lyDXuhigG5Sk?pmJopy4gjINl_&xNpp(oE&8z@I$HP&}kq1B^Z#9ZS`(Z;sM1UBI za6-L}0CAMg1=pA$5J7-25QM9Z2O2iO2JDD{7is|%NI-W%8>RCDMmYj>m{3LoLRSKk z(9XypKmv$E7#|AYOd$wRa9Yj-xb1+c0sy4E{U|RSIXs+PDX44|pk0-O>w2bQ0^rVw zCjb&q5CMvUX;7>4=xVV80Zv|az{#J%4kA4Kpqdm=XXE+l73<0{2~(XKF?X|(21YC~ zWsX`ZVT2pgfvA8ql{$Wg(bwiYSvXHB&T` zC7LNEjww10vupfmod0h=DpQ+6MS243FmF01CN`Tn!r9E`(8ZWUS{_;%M&XPf%L9jz zhZVnJc7t6J0AI#>F?J;Y>>0I+kj`h+lQDOUF(S>P3XGj90BA7A@LX&J6o>&n)P@7_ zAiNv^L9E#U-~^n63?dLl5N#t;J@2x18`P`VbNAwUaQz?E-~ASPe|R0Ez znIZ%rstSNyt}@C2gi$&UIHCyV7b>Vhr-+2-2Z|`d1wR-B?RFGI3x#Qaqysk=`Lvipp|QD1nmVW*Ad|ab|?_j1b~O}8081j5XO>;a1%$<~7W|L|GsN=*ttB0gEEEFye6Xu}Egd z`!eJLz>>@+4kXbW0t|b(nGu09ywZjRcF*u<0e2vD$NY^jUn_7PK??xsP%Qot31h}M z;n)J#iclgb0pJ#|MbHMoB~gc<1Au+99)ScvSgMyC3^D9Vj9xJOH6<&-k^1sGp%8#8m!<5t)tgKvKm1z`*(h8C$fN z%e_v8MKzgpn+%I8V*WOxamL^FKP*CjfQNB~Q_KJWea3av1TZ#2t0;}o`Dcd6igAmz zLkD$O)BsX$#yu8p7~-lSP6R?AjthWb#tTkSE&vP}uTU-^0iB?kP>@;eKb)cf07u3* zln6p$Peo_YSeS-m4pc5qk4d@Zuv&Aul_8bO<%KE%aOMhfLaLH0gi+31Q3qx?D2e@6 z%ay{YaIUNwI{^T{Ttz(8Keo9fJi9gk*10x#c;7j90Y+VNL-Eq40LbUYvZ7;LHcHoq z>VzFI%9xHDM{b$|GuF(I!=lsNEId1N?c)$Sw;60kJ|44y%uuuP&rlQUOo`mB`2Qoy z+@-#9_1Cu;{cCk2x;? z|3lOk){^?Qz9g_2!Y#_ZWCv>#$h~UFto<6RRoAeYL36KT>jcfcZVD+W_l6?D9Do=O zYs>#R2c`2%M-eOlSjOo9Ng5YT5@v}=Gm1zMbIDj{2WG$sCT#Fg&QHUxY=zk}@%b20 zwZ+tmsvVQq>p>kC=7g8R*ewBYaC5|hFE5NJdV45Z0buNp=LA7;#bR!>AvTgYV2A>k z#>#=5wqZ{E^8rw!;8lSZf`|YuX7wV`KClX+JR28Oqk?~@t!6&K^Q5K=&GG4|;NPEl z%Kr5QMn?J)YGOKkX--Q@)cK#@vHuyfdBUWdnufiq*#}rPxohF;$&uNFTElqc3iKxO| zs?Ewaa|5dxlVc4)`g|iumN@zlYygP!T7>-yTtKh|VA0|fc3S{^X^eG506+kvjIlMh z*a2{(j5ByMfI}VG;M~K44hs|(8EgPYAh3Y|)XNkAJn$xDaIm2;3IQmFE?31|@LOUVEz=Gx;qfHgEj_5`{aU`G%fP=$&WyQhTEi699xZYZ4q z>|h_1V3)jM9Ha~v1WZdKONc-T$Pxe-^v8ri0E+MdI|LDeHXgonpd$cqVa^v}d6FXl z#<}r$ZW#BlL*EdKnfQS%47oWFPAtmNMY9B85H1Wu9S-CXn460MFchMX2|UatAQ$rICBr7~YS~9hwh+ zlXHhtAyvwKufy&FfN$<6aduY#EOY04(q)nbIin(rR-ada84TkND%xlN`;gWYE75sE)qUGpx6Kq{Sg2eZnudG{>Y zy#QE|_gD+kjJy#WNa=awVeH-jB;|c4K)NKK8>6ZDQUrD%0A%xJFwaLLUj=J7N4^f` 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

-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 Z$WnXg>PyLv;ksR*Qp}bb2ENt+`d^!lrj7sr literal 838504 zcmd44d7KqS;8uyQqWC6GgFg@ zfQkV@Q4tYw38He{_XQPqy(;2{ijnIsdR4eyx9c6=@Ao-X)z#gDm-oHDKYsH``Z?!0 z&-0w;?9W;2J<$>s}M;pdT zU6%2oRTsQGjO|fl>3Db7YQwm*$1qYFJNFT|$Kn1ApkWM%+thj!LH+e#uVDcH8?zVb z+Ic-m`L78BZ$P~ga!;ija;u2|NpMe(amfVWqdf*=T)m{*xPthn`!^n`E*vTNkBjk6 zU3Jk5uY&(GcLeETTA_FRZ`?3;PSqRr3xE{en05!AyQop~?{E~?RQ;modmxk$=q$3{ z)Bzy&R+c3<%Q(l6Ui<9Gr4y} za)(2?P{7%X+_uZP)d^!1`XUn7HZALf;*N}Sx^vs$&sHcx3dkYvH-R6ibL)Rc<|rT8*qz)}Li(!dXa9oGbAZ5H7%hfg2aLl#|ftD2c@Z8{5Aq6 zl_T>zb&$Fia;x3-gAifkC2;R^nd*4`%U}q2QBAH;Hv%nE8?S(CHyHN;*6u&h zmo?@KNXDqQ6o^N(e9OBDo^luZD$`hD9P->_7?x1TI@7ue#cqlWlA){8Wa0oBx@}Fikz|a6WQ>4nt_(uG+n~x- z(91;Ak+!oL5Vg=nNxg>JpO!Z4tYP}w1X}(x1?K&&0%vuwLXS|%8sby00~`Aa+Zcl^ z2eM3c6q00G1$8^Z)GA;zkpFrTvb`P)I^+`Lyn}IW3F15=i1R3cwtu9+ynndBSxu(U zfEhP^>Q0G!bhTrk59P4wnh<(CBCI7Hg*skA52X%;U+01MMlkeL+?2N*j;p|6n%-py zPI=3KsQv0dCRK$;*HLwf2vVO(7)~Xju1OkBcOVDRIgR^)Pg;X^LVXIZvZ3qoa%0gJ z0~yc!wTux!l#y!&nSr!B-d%``Ir1hz6hue|riku1(%l`&F3p< zd`WdO@ZMVxcTd(-Z*A4#Bu09%uAUP$bFyrjGpxf!_2Mpsj5M~AV53>|(XpVCzm4or z5_;*1D-Ixnitp_Y2%wZE5C;g*?P-KKm{%xI10J`(OWgym8nSXek9_QM%H|B(sa?yN z=EQvR?Fek#iyxoJ{w^J;-ocnyU(bac{+Y9b2rsC2BEXzMJF}~<`UAR7?eQLmbJ7}r z^2sOnOrZzLGKQ>a&O65-jXDD2OyRLKf%iu+`ILn|MdYh@G2TG|f6tR~|CNSXAh_XRR-<42H*O68g}EZ1t$GS&ML=zRb`jSu4IwDlim zmP)@>L-)NC-M5TPr>#fh3j(|kfimM1rw(<7rq)~8u2Q+Ez?aE()$e0}{}FL(DMHMF zx03BrA47l^S2-;Rz6lkswnTS<0qP|{P4!uI(+VT6EDKuh$04AGpnq=$rI3gdQIw}y zTLkr6o23Y}VE1;PeqpQ1&X9J?rqOa!IApIhSS4aBA?qsdE{?|}o&wU~Dk*?de_?q&vv0J;*GbWN$P2{F+^8ru4hAz9$?u_ z)U3&3S=|b0FzE)>X-pXGvj?C|%ue(^M}N`#Jat5W2kmr!Ck`MAAwnFP0{RQfK!0w) zkNSxb#-6D4PnwOJ(B({Id?MHbZ-=*>DRTTrX~S7SSEMe5(>sKIh$d+{Ge`@DcxpUY z)+Pr+M}trrsNx0=V~f^K96$_(2yq}T3WDLRBa%9LJ#xT%6^BUBLvbxY9EkV}On&2w z`1Qva23tpc2|gK}VMw7$e3@ATGnV1rSLi`nPSAc!=)AASNxnu;YxutsHPZMx!0wHO z#(vC?CQSc)RE{>nZ;;F^n zegB{FL4ivu73PL&&_sr6c4$Vu5|xY=)We9P8^gj60HbjfHH&@5 z4}tTqmOjDsUntP>uMuebFA|vdcL|);WSq*f&!A5|LXncTXGA(Z)YQpt)Ss48k}gx$ zThQ7fy=Em|)1MP)`MyBg-y<;L?-iK$Vd=vjpVj1?HB&$OV)`}n?B`AW)C-=LdZ9gz z_8sZ=5{c9FuNP?f*9o-!8wBS4YX#0~GET&)6Md0R+Qvs+k7T}FcufCBftLRYfwsR- zVBUY3z*$Yk`7veE*CrFyn=)lQxI}nN|5AaLf0;nrf1bd+|6GBynoPmEqD=bqcyROZ zO(R`K*TjQEMEw}Wy%!^tT_`pV(2W^_UB0&dcVww4zx8=Iy`LaVyfW8Col6+#tqim> zxA9)|DjBEG_AiFSKBsOw#uY;A%n;LL(^3Wy+?NpdfMYrH&ilaMcoY$E+F$8TR-c*V zT*0Xhn@R5uIJ6v%R2Q*FZ3*ae)WtwrN$;l+G6_hgzC@qXxE&7fXTX;2>LN3t1*!8$ zooFLKolh8#!~8=>;r83==isVAO5yZE*{Y_jxnBUWc74{^Q$?bN)Gy%;$WSuBf^Sto zoi(s9@@pXa5<$r6#j>?4hg?VEdXsjd&+bd8*Fra-Ss2w}Yd;}p9KQjhCU}h|D3M^y z+E3Vfbfh@_>pkmvU2WRKM3$69E-rF738!{|8Mq8Bh`CGH#|#)*m(1ihznR*X?$< zQ9Z7yb|7Dv6NI@Qx2KKj2~CAsK3W^Mc434WtqobbFyM^V2CQ8eVn%BzYgZNBel=0d z^;WDxuGj6|@H$j>YZuvaeQsZET9Z~Y)_$_&`YWro5Mn4SH&B_v|`{lmV%0?Wdl(TsEivBHV7)X1cft!78+p zIkU#vPna7FDrGUOj{TV9{S|EPV9hSAu9&4Y+xikksgh+fF1Pj*=JMHmeGF|={f%n; z9X~DAuo}=3<_g(DjP}3UY0;sW)nlyvgt=n2sQzZ6IZwcmVUmWjL-p|}rhl|@*Dps% zOU!4pNOxNM33J1ycOwj|;@W(6Sp7Fxht_u2uLDbCGeqy!p+iw<5uwRs7@8e^>UgbP z=P;g{wVyCIk{wa=5dY7WwRW9LCS)ODZZtbuzY($NEPS#xVb-pl0WD!}EIU@eH=@O= zUu$-w7wX!8?#n>KC;6g6ZgF;z>H=NivQ^n)jsG3&X0*Hk`m$ z9E$r!M&rIQAB0%n2thYIz{z>RQ2KqE%5^8!-P9gExHr+d1Ri~hk%w`yAUMRlC_^OH<_K(b)~Np9>i*pIKPq;~-Tvi)9%GNs~0tmmA|Kxjwsa`g)|>m_swihRO+E z7LrsRqVrQ*`z;wkhgO?~azEBY*#ni%+SYFs#Tk-61Wd*may0P{H3$&{7IN6;ritEe zS${9IG9?y7k_Sb1GQT!ydIdV|t+gI|4n4E&72!WEh_S2zhI+t+Vgx1zggz7@g)b`U|y9RmI z30kksznp*a$)dLJZQ~V4!4l*dtcO8Z52XPyACRe)XZIV{32F?{9NXOwJD>TKHIgG2 zl={!r=xzO`&NHCn$ez?tswLaZM538#)(QQARg%GK$j>2lX!UIo5^Ew@CGsmQINQf) zXPXJXMxb5{QmkMWf;}5_K{A0Qsm!#*foTkx1R)L}$Sw$R05L@b;3~p1g53hkCmN9o z7NM&{HgN#4CSaQm1H^%VwIM%stWAmHE-Ar2tchX`>1u^~i= z1Bi_wLL5LG5+cL_#GxTV96%fvBE$j2rVt?xEJJ)1xw-2FXk*MM^|c^xH-}{60N>#u zLLAsjVWEKG9biCZ_&7mh5$cT3%nF>6c+@k_OBVrRo*AvHe+3+zI4%YcCyuZsxdbn? zXs`xaunWN`?2HO&$73|GR00}nfp9(VK@;R01tw_5za5pF`zfy%X!&;twEZ^-%=@ns zIIGDNdL7PD=u@ydWyRo#Fbl-NzUgC@VZaV@>)X(iHDGsQDK&>3j!z+Nv_OkfNjEh# z)m_QfF9Ag^SsAEbryY6-gfZ*5Ne}&3X42$>qf_m1>UV-f;~>p-vguN$=sF6vXeOJk zzZ0Pf7AM@M`T$teR+t$I`=t#1WHZ!*@s*hrh3=t3Fk`7uMl1Ay)?=Xl9qs5;D2%)( z7WvPc;!RH8qImz&4AAk)Muk(|6iVrjNx?=;GYN0~=ThE>;Oba?F<4A4f9vGxbl!-{ z0V{%=yx0%=7gBC8xx|iLr_jXM1jahn&cRKBTz9rxCNz?3S`2K>P)v`>{Qww-^}4Xo zvk*%x=H8_A`YHL{p4OnS=FlMFi-wy;hdFkb?qpD4sQ{(}7-k;qoo2G>iRl}QJcsV_ zLz?tXQJ9{mAWgWoDCZ_Z9oGdqjtw<+{Ag20Oy+d0auY4uIbtBxzbFxY0uk=xl*_BI zx|wP}5MCAT!kS|P`NOfYf2=9L{(F%?22}$?5J~-sCP|nb_cetc5(piM3H@u6BM?f4 zxX_?6ln(KxsHP)kOBsC#_uhq4Sp2@GY5vc>t=?l4b%9Q<%MM9aN6?0-Xh42 zblMm~*WE>a^y%0&H{7`+(M4nm-46OQEbV9Jj$&Wvt!X+@RWp-42Qo=4(yO)bJi>)* zy>6W{mX`IpHD#g)H?SV}6f!Lj2MsdSz90sQ?K#jFms`M+cqn&rs=4;het0GOtaVJ= zGr)*mxPBZx>)=rbT+y zFq&$k{L0q)bC?^ka~YV`VAxgv0fA;hJx);c4q^Pzk^6#I19r1qEbmaDE$<1?SRQ8I zjzkzpw`R_RB%eQRTdbxvgL#1M^9Mo>gAi{M6|%g|bRVwWutgmITut)}x_812C0f__ zNzk&sPlQ+^bZDLRegNE9x1|tGzZf#HA3%iAi+GH^TM+$Kuq8}x;4*FD1}<|d*ubR@ z(Rdi_T_-W8;n;Zu_``Tf14AepUoo)}nj<3`TlTE%o1m z9cN=qITHrE4Ef01t(k*m*;c{6F#X=~(6+lWnDmaJIyfnC4ce!73w|b(dMoVb(i)O_ zE6fSn!k96BXtRxNgWfRug0hl0SX78(C*v4}ySuVD=^Y!y(GNt*gXN*m0C`2Yg4ll8 z8e7<=$BgnZIzDX3{0ii>K2L*iF-ye(1oY4dap<~_v`2e%lBb<}bg|sEY{}K2{4kL= zV7t23Ahu>%uiExS-vrq@K(=)M`MxOmV>`bs`=UEcX_$IL5}ThVp&JP{g|THy&WT#j zWY(O|uR%|ns$>+mNgKx@KUw9D2dw7dIDw8HtNyZx-`ZO14OF}tELF^7Oz%XH^`=Lr zvgsl>l`p`lNSQ01hQL^6h)%L&$X#UQ?>Wq0<|o<`veYK^%Ckf#D(H# z`|CynI|&gcG4nFi$#9*E^Ig+DK?++=m`McdOm91u`Vl8LcoFS0!(tT&5R6glq4z)k z7kcD+n|kJ><5B^jh}{0*NUH)KL8^@k_Gv<$2M z1fo%><~WJhI2Cdh>SUQzcCh7Zt>V<6H8M3&p9h=p>eFYK*9id#PY?4%96&rPM2G{3 zGeSh@;F=oSjg)Zn1Qv%4Vjm75M+FJR?8ubSB;0zHv7~@BIV|_^G!*+iW>3`bS+dpu zTfoHuAKe>;9c?i22={2FK?6>iSG-;34W@suK+AupK-+(Zz`TEtz*$YEhV;CGK0U9H zI!WE6?+)V)JMXQ?@}NwP1|G2=s{}F++noOa7b3Q|# zdZq|vzm0;`c~S@?4m0KSP8yueV5ZnIU@+rC8a>y-gqL}82l{3I%@ViizeS+szg3{^ zzfEA?zgys}CQ~C?H~NBeWX$JNq1!XG%x5vZ?-d@?f1g0h|A0W-|DeEx|9*ja|2+a{ zH93z3GTrp4X9sC=j2)1@4zgh_aQ+!PqsFO$Q(GGAP3qhGP?s;kkM8TdBQbhax(2&Z zsV_iPHRbe~oJq5=kD^bYEjAn9XLUsN#4?&w-h|^^WkXzv50AhQH5J$%3F$X<3G!JIH zRtpQxV+FzbI2+{_^l>&lHhu>w8bID~e4AmuE_nP2Q-ENUYa9Mv9R)n?<4@E!HVC#H ze>yg(EjO_y;rJ6fq5$<0pg2-pi|P}Mi`XqV6h)?oObmI=AyXfEV%A^%dtET%0OHILA&z4Lt*?jH`j`mx;FyUP zYOF_oqno%L?OS1CK^t)F2HqOljh1l)=r*uE*D!d_=g}pMI+apb)-VJjWJXToG@gT; zfQ+I#3yzp@>GLc(PyWA`vgq3oSQgm#__CzdP~mKJdSmJ?AP|5iJJz6yPkDT{6A zkqFrHiFJvE=YqKB!pKv%WTIftT?CYOF@CtIXL{$s;+4H`*z3k2NT1BjGnj!66&=0f z(pA5*AEWq3pmw&)(z_~MvUgIRu3v*7OHcaHV!h`=us&-61DkVD*V;vWGfr)wj+2I0 z)W3osJ*s0sz%<`kixsbV2eU+{aVhrHylwa?x1vrX>PFMM1kx?Zpyn37>0JtJxQA~W zCn4U$7;kmGCF>Mei=VDp1zE~3?OeOma*8`bPu}WI7WMi|aR<4-XEm8xq}wNbs?Pj7BTT!FD4`>Q zuIyj81EQ~DT)5?v@xLjuO#eZFmj5k*w*Qd8g#T@UI8O2%ape7P2%Ob?3Ufvp8T6@L zj6AnGWt?gm#*xgIaSm0h3ub=mXt*&mn_8}MQ%frA;R0%E3E_Y-HArZ5O%*DO3pu;8 zwTPb+hwyXEFn+f1=TQC}oK2LL1P65!^&dpdJ7KC4l}Xh9*2L+9tg@%(+8G_gd{;LN z3z>h>A@i?fCBsNljMPG;uH9o7=B?2)o$bl(!)Ko^}VwP%?AzRp9X=>o= zly|qqox%$BuaFd-^3a`37T2!J_U)M<=dkAN>)`B*afa?>vaqI@Dy+$ox>Rk^9$OR+ zb||uy@jc5ZxI4_GMSCjrXF`APo~7^?nt9ff?P-fWneBMsh8um8UHBItXI(c^3HZUDCj$+#ijGyj$X&{;o&+J7dm3ckxPq44W zu8W$&#N6^;1c}li(r%H;Plr_gE|^bb#1#Kj%F;(S6b(3_UK5G8Hr#`Hhany8PEQI) zxC#aJ7Pw_Wi{1BIQFLaCQgJBERQE6phgh6i+Ab2aQcpEeDOynGI7SP4z2+Hw%cnF`g4G=FR}h1T+En^IVh^|!U;1>^yn${rjQz0#%8RJH-`vByUIG(#wG#` z&5VcJ368n8)I^kl&n6|<;1C#gAYKf_^rr(ytw&Pic-I0uc_5RHK_dlkT3^1w48 z_oa|qd((lDY+}9u6D_!T7f)@hnp9ov`y^|_^)r}m>9tt{ACG*4Wj6_%whJbyX-pXx zz1rwQlI7g{>)sl!^P9$rMB)!i;4eb)`TQ}(X*OO4g}gFqpf#xR){IXY{s2V)w!K90 z2Pgt<7jms)J?l>~R_~*E0PY!?>Q$)n(k5_dyl80`#$8+Px(aW>(h~`+Z?sJ$HlfMf z3M{r17|1yFS^(BRBHR53=!&?P`y+vt|A;`_|DnKy|6_r9|6zf%np`c>!!&)`hKIHN za^!?Iy*bZ;6~T4qXbl}J9I`}oBdBOJVbzSwG0;h`6~RX4S`pK0aXCbY_h};P4V{e> zwBDE;X8h$Dt@ngL%byfz`zr+I{bd4YH5soM(yX9Qy;6iS-cp$V1(`kRe8kd5TRw`8 zE@(GU$7teZaTNr)s_h`^hk6p_>mrHY^cM@X{BeP{UlN%2#{|x5GPO+WN1x75DXYzp z2^*d=Z(*9t!ejasftFttnDn~$0Z$a-8Yg?u#O+zs(EW=^&vrPqCL*Iq^t!zutB8deR_CPH0~_gi$0Y)-F6L30jr8w;F#oVUdItp67tbPWGvJm?9yz8L7II~=W~CCV|& zUqhupN4?lDwA16d>C>mR6ARGlGT%x)X6BH06SaX!(`@_{xr5C@Vx)3Oy-x$F``?w& zLRO=%$Gs3@aW4dm1D*Fm+WEeZ>UGsq@v&sbgGJ11SmAJk32}38nviwQ zs^%9Q1`gnS7+*UG*E=5pgWiiajFAyuh17NNV8CsV6ZRF3^a-Y)4El(K?jtl>tV_MUqt>)&8UfqkgV?#4Sogz0?L zwRAh)`+$;lhZ+nlb~M#gQeed{R=Nr>`FgPDCsgkTspGv4l6uogfnpBSticM5GHQn7 z8Lpkynx(LPq$Oc{2$Hm>s9zTAyuRb8-VBktd#a{G>vr{1-dT*TNXEA1c54B=6!jwL zV0mY;XQJKw8`#aKBdXR8uphy=HF#(c-`cfdaD3;k+fo}r*Sd9=Mk77}$;VdR3>Q9Z zwTT@~;BgW-h4V*bHjd~F3}OXpGXiy4NybkUyx$|a01p8`1hw@h5ev`dS1WGXyB)$g z5v?wU=k@d?(y-YE;PUPRQyOOIy9op46=3$>3q(++DH>s*a*EToi>!?@tU&>%xLT z3lk0JukL_6eOMW1q)OdIH`A~f8O{pTms0bOprK^DacBVYymP^(SJAp<70pdHzKeFk z17ER--T(n%Mp~1Vl;x(>Ay5w{dI~0baJ3!2Bz(ax^?_4zJv#H;9!KGNXHXY-9k2ou z<52RKb3tyqy_nK$fJSP|!j#fZw_rPf(`AhNvag3kbFW_hi0_4e44GW6iWPt%i#dopoPci@o@peG*E zM-T3Zis*iLaD#VdFXG#UG1(lPx5~vRGF%^OMwW|vhUp27h+ay;!DZeXHRa6E8PXPM zl(f!Qad;cFtuxhhFjLhM!gM~)_9o+c31!h8Yunwj+79br)G4bRDvs>m@{T2&8EaIb ztg^tfpFjn$gMI5eta|hEngn3>u@Q|x_AjvO?UR`E@PzRi#QE$fPHzjOVJgXRtGdUW z;ZRh)rtu~eU#5N)M3~Z0x|62NtG&n3YLecg`0c5TJE?+)zB`jDdguf*sUh(-*78^f z=GU2&_g4JY9zHPGd>8_!sDhTGBQu@GHqt+fi{j8t&ykKWs+a3|k%`kD$G@;SM>-EQ z+jX6N8-)A+DYlO3|3#qX|5>2z|4m@R|1W{4vwzi&g#UMOe^xv@

4p7Z0L3+U0~CB{nbU84V{b!tr=ryV%X#8-@LT#&YgrY4=C zwKjI*i@1?uS=F>-HQo*#D!I8UKvU_N^9T}imjiU;*4n*Pkn$~K3*__9OjvTsRWE+q zviNNx+1jf1cCrJ+<^^^(yz}Y4K)bP9;i!x7!zv}izj}uV5ADt~bzXlOy{Z3e$uZOa zjX=x)tw7ty`9Rr!`IW#~O{SnAbCkZ=T!ZtM=fq@Yqzf_qZh@BHE710P1m^v;z*$YE z&~s5HeQh$&(lTZK@(0nw^#3T(@*fvy`#7X1dg2Tt;H)N7uzV?#K5e5&zOw&3Tgpwh z^%2;4GOdwuu<YCN7%o<+82pILYQSj3Z2CWP6GbYgrgdQAg))2AS#o3q9+!$KvS9pjS%J zdvuU^ERM4bx+Tsfo`~z#c#wFapjX9%#1jRq{qZ32L_t`5g}^`!@kBvyj|Yh-3c5EQB%Ubf z9TF5MFP=!lcgBOn69v619weS9=)QQ6c%qJv?zz@X*iYF5I zVF~)Q7APLe`-~O{o4Q#y#1nCSG#(_LDClGHAn`;&AD5sY$>NDL{6w5fJQ3F?<3Zwy zf<6@w5>FI#e>_M$Q4mHqr0cUf$>OoR2XvA@BSAq05KmNIpN$8JCklEX9+!BcxUiVt z%tP@+L7$HYi6;vBLOe)3LD2V1^*Ly&Y8b`l)?i$ms1+yYR>Lvx;O2+Z_yR&F_A*3G zfrc-NRm65M{?$Sdr*CqOnA-BtB_hADG`*cQ=mBcwiMQRI7!b~cvuUQ(^nw*j`|uf za+}qWLAcxWm~?Lhqi9cMTgt-%O6IB@Ha2_bIC6O_A>aE~kYIOo40F)0(}~$=VpM$t zPOK87J*Hq1Vavc<`w`}LHaBDR3FP2mvwL8_@-DaBc1LLNR1fShs_5@y(KtR;G-v23 zYa8}dW919=^vuM`-g9m?29Y#Lwe3z6#dpxA>ooh`aXIop#C;avTsDLHGTl9-LxOS z47P&zLwGs$qmSolUJM#oyW-X9KMwJn$9g9;QbL(Zza!-X^Vafy1QEgONWY1{8Kfql zgXYrlc#kkLy-w9*jIIdh2?+xSjg9jWhg@*m%QUJVgSK%1KN)AyO{^vph58qfinmM@ z>)#PLR6ihaxc+B>BlVs|o;gpHp-h`!m_cofcwy)?sA4azyiQ1USm<@jSg zhB4UqH=(2>1Sl=mdsD8P?;rd>IBlVXF9If9;*cmtNH!X1}lz5MD6zd;d zn1hX@jpH)`$3qKq(AsO`cp~8FDTG=T1{*(>G|~Xn)We}h%SnZNK1twEy&iED2OB>V zF4~4|T-OC$w+CGBjJWC_gmW;+eVUGK+I=SAd??~TJU^FsXic_J|1_Ysi(y(?N|#1v z8^>}|B0Z-p%t70;jpOQo!}aCE)PAJCny^!Qn#OH9 zY!Z%Q{S1La_2;*8&aIV!?bqxUe~G*cD!^Tlpr{F47YMqyRX|u}XhiF%qXzrsh!R!h zS5h!{M^usAhXc7dU>PI{#i*P1n>(o|Nrh^1Ttpqorq!)Ah-6<9P`|d5`mLSRp9!e{ z6j4W-&^B+Ay>wJ-vW-xRVjLD>)Gg6u*Bm2Zq0R*yZxJ|DzhB^R{XirkijP)(8~2|B z?vXLZJ5*mM5L@Jg5`XE!FymMdVRr_jXc$IH3MzoLUzl=Lc=F2Nvn9 zIEGMEfox69DMK6e)dBV0oz$E~v{8RCp#FU)^?jYx*~MCug9tm@2j?4Y@=p&qo*!{U z6^c`kHtK5w>iZ(m*10o!{c@~B)mkxUtF%er zQ2jDOT@4q9DshI?7RikP+q)z3NF`31+Nd84sDB-&{;jCQc~l$qd_aBBl2DaMHm6o? z)EAQqd3s$ZHD_CzIx4%n1L`je9IF4mlZz9vwm6)nI*u{Iuwm%FjdQa$>eT`DnQ>~} zj5u9uquv`(-_}XZ8C)CnJpuJYoz$GvY3iuw{(D3fHHP~ms;GJmEYqnwoG?t?@1%4- z-AVoYfch;Fb&E;xnNE%e1CC!V%<#IaH#%3Cl_a!ZE-vraHS?XG~%RFQ;Q+3)?ya2mlFjXBQOc@-n**81jgy= z-y>J?6v!U{IOhn@z+6C^-&CAoVsi0Egz~HiiOZ&=9w)pf2sdZQ%nK8Qw)X^nD_!GmK#k46Ny1pf} z=EWG7w+5Xa3&M;)7GrHgEC@5rc+lcl&_GMjhP$9i_^8|l%nOWUQ5}Sm?fG$6mtpKW z7!yVwx+zo!degfsWC^y>uosRq#`CUzbclPsINCfhgd>a~$lxIJMr4=Xe@$5Wk)TBV zgeI=3P3&o*ME$xZh3wI$8-Ink-G$5^G{g+}IQsDe=xG zPFF+ytWl!=@n(SDA9i%)xQ{=EEHJ8Fz0dBtcmuM;Fz)YK^rNnGnKQ8(|99ww8vkDa zdM>>flnd8)l9nCy4}xZOCnu{r;Zs%j|3;Xu?i=~CDqO2a=PeRXDSm=-?`ut0L6psOv<3@vZts{p%-6{up1kpMGVLOVkPqLBD7s|<{OA5 zc4h|U!80?&;>^r5P**$NQf=q^KG%hw2H(XJ-#1(1TcqP77V)iHD86<+iSMcSunlY> zbMy&DW}eL~12)lubpmX%1)B=6=@x8;*zE{Tnt5E;&IzMw50aQK=K+&4szBy(G-s(PeO1-3P&Xv>d5O5T)hhr9tz*_U~*)-G-q^4K%nJ#=I6 z!W#nK&q3Lnu5L+ty})9fuZYDvNWwZ_%6m2faoVI0A<+`wqX+V?z0hi&R+KTPynb*b z!?ViQ7|Y)v&p8;!1gR1A20(+sjq9UFA zo=Yi#*eu;V^YeU+TbEK^Gb4(tcw12X1@HM_#rhbN=jFg8OLY2M434}u-d)7IMN2g~ z4yuq|@JU%htwL0A*#i%iMR`5_&1C%v^n;_`U?4MD@0m2N7>ACA$^+Mf2Xn&c8joQH z!824Z!CrUT`z?H45rMf>Wg?qWLv-RuP+_{?O~+K7P7UTVKi-Fq6hy-m_7GMnQ}c!M zWMD_ZJ(Bi*2VuGHlsAI#vF&(&v)jdgIpO8@=o@3+D8leE%ou=nD{m1|D+MJl%80=x zFT~2tO?fMVePPZ{7DC~2{Y;*L$|VXp2hVh_e*mQ!WL>Xh9p38ft!CUrsj45MflFyu z0Z(>b<@Uz|<>Fpek4kDWG?I&Yxd9xcAJ7j(;lR;$99i#^^3yrz_Io8Jg3{Lo}9{{P@Vhl6iW&iOO9?Frnwi)uD z2l-g!#`1o~e-0Lec&_{`ftG)^z`TE^z*!xv4o0_)J%0KWb{)8pE`sp}$J<17BpYod zl6{iMF#VGSTK*{l^ZtnfXLYcG^+wtBY1ty{Sm^nJn5=EWWBSh&X!)lLwEbrZ%=@Pa zoYiCs_9A7{7s<2?^dZLO%v+r)?}O$WFb*0qzXuCEv#SCeu7C1SqvK&&I#U}DGIj@e z+R44W7=E4Vaz|}I4I7_G;MIa6ZkSDhvD)W&s{nB#1sz>xw^N;rlMTnp%R2(H`Avdj zAO(bDL1C(G=po{9u7RNXap+m%Nb0R0Y{EGUDf5p(9PCX0N2mb0>`z5nJ)Nhwp!WKg zNS@&hYk`*ke1W!qnZShqJb`%2THvfER~vQS(x=aRz+Cwes-ETDix_#&oj&h9@L1jt zJ5fI(>Y<>zimKJoK;`=pRKsyV(f>Zu70UG+kWzgRt5x(PouYsIG)4bQhv>2{2tJ;| zF{w-L99+S=qP~UH@aXn5a=41?#A+=ZnTnY&JJ-=v(`kQqr#7D>3SZ8_!&Tt$o`IjX z-1$N)#kx+4&$m#hS3nmSB(WUU&sck3BpuG~MOh!j8V_#GzAv>%_6}y;3rYSm=~`L8 z(#pENQ|+&|Qf%m?_*yH)n`sue#`3SN6dNfc%IttW z=%n~TE5*A=!D;<_;82IRCgWd%KyM51@|bZfjbvy`!9y*wy(1`%S#&oFs&QoCQy&JS z_O<5Fw_CZ%)F%HstrYmsT06yeTPcq2r1)Md#W9@}UvHsMpXyBKH(Dt^-AVDyR*KJ( zVzKER%h>eV%n16`D`i~yG5T74r$K!ebU5Kg3}$H<4vqtZ>0#oRQHP)l4R|rlG3w+E zM)vQ6jXTgMf{wk;Nje(zNsjR{&^9C<=^#G^#c>BqG zzrUS$Ne8j?lKY77AYR%*?6Gg!M|=nIvJPUYK>LXAAYR@}i4J1LinovW z4&uoUVvl{{KH@uwS9B07R@i;ScMy+s5X&&IkN6Ja9aykwF3?q3C~UO*i0>e-c1mXZ z-A8-}G1gGpC9^5-Bff)}=MG|O^afp02i@77>aw5SM|=k{mMP;xkv8eFxmMIke0~S9 z#}RNJ@g0(@uq0d6RqV(+=wRx`HDI#X<#y2B-9lFzjkTIBojG&*LL#t$5s!+AW&gjA z_>TBiwCYb{4m10R?;t+6Q!+=-eZ+SV@9ZSz7`2ahO^mn>la4aHf5m>%s*e4$uJS+% zTxTHJ@%vXxmguh<@5aSdc5z&hE#eyFt?23WWm5LKT(`mWRj2ytML3uk46 zWsh?KGJPkc5yv?lH1ZfEx_g<|%mQA8bf7Q&G-m7mX0feJ|8RkpzeS+!A0aT|A1M$A z(T~!OgnzVlIQ}u(k@Sz%j+B3#IP(4`fwNk++N2G3`qXC;r+kO?Gw7pt{gbhTiyZYY z()4~da&tmfj=k-4#zqzSssx7wZwG!db7$iB!Kj5co&#_9+*w3M-J>9T=FaX!_RgKt ziOkt^=PrOv%_HE?5AYnY(ZSZ1>zn z3t)TZE?xlJJNMiLuzhn_>gv#}f9_IZTeC4YFo(s+4y-%(yalk?Ijn$n(B|e|Kx|t+ z49;D?05)GgfTC}wEu;$i*<%kZt{wLz;vSGO7LR1OF^u1dq<1AjN`tfpSeg+m))+PH z31Dvk2LhN2pc}w!0G$Aa@`700!2q8RU?G5g0qhQ7e*m>wZbrPOSTkN7`sRk#V{UJ_ zLmn1(Ggo2V(aa4y-c<}5aYvHwu(u0t?`r(GBPn>VfotC|*Wxq|uX_x8FKp7J;eAm% z4ds+!Y3;+ps~Iv<8kTu7`_{yy^#nN^2=4{WOgjyum~BTUfb`qrwSk-il3z@bcRzLM z2Eqprx(A`5LOJ;N#{4e)FOK=M@LwDA_rd=X_*)Xt-I{<5Xs&CaalGr{cSpP%G*ooN z>0cxekAVub{R;#p{0jx<{hb15HMu%m%cf6v)!X!Ff;U0_6fJ{bvt#xLNXyJ$l1R{p zTUJu=Eozw?$Vv(}hb7IiB@~Q7Y5C^)3)%O#Uk+BU#kCwfcrBOEOXys&&pe+Ohx##x zNRD9%1DR&Li2Um1%wAlehc?)@{s}?Cf0O_t)-MDm{9j6_cMD|uKc(B85eD@eh)k$& z6MumCJ^zMZbt7c@Kcip$8U)pE*kzM-q{oBMwVbp36st_N^%fguX} zsrUrvA;=V17OXPfdD_h+c^7CmtB`jQ-11H~`eB(fTh$_L!Cyjf`;1x4z}G=pnt{>tI5k49~W~AO5#{mQV9?cAKHRKyBg!dNo zh|Gw$0M|91i$ARs{B^ACXu|Gxz~{=C4X|0E#BfHdE! zlRpzHGqEd zrU>dafu@KdG(}M52{c6vp(%paK%gmN2u%?*6aq~VLud+CQPc(~E3N5mEc`QKIS}SG z^RP<}A2jQ0&1vihJvK|E<^*+SF<_jgt=?Jc9$mp$zm5Yf|Evek4kw^;9IjTjn$tFo z3FB&3J=?>EC)Q>qc3FcrcTD0Msb z7yE4Q4FH&~eHh)cw;c9zcd|V(qsa%)Z%uI8_C5%M>GBF*1`>zu!2oyEUi@74oF3d! z`zTz+b5Wb+X$N&LBE;v&u?Chw;Qge1r*?mw?ssYTr|7;6>m-5-v* zKhoqFYEpj`Se>Ke=#`Cm3ukl=(sD}!B;$d-TFLf4jNIWXgX0(?j2*-L+@r&G}LHX0UF$f^#Y|%Pj7Q^M4xrg*E?g+)#(?THL3Y zEDZg27X~wKG1FmV{U>>by5Hf{cLh<84Z*2kk2WhHjXt0hT;iI+-Ed$BJP(lV1hT*@rwEqtQ z{39WzA3P1Aj>S*;0FPUKSrn>ImGPywufXk-56x{oD17poR{c;0ewBeDlgxBOiQ~!woQf9r-A7PuA>CjwIzXl_UDAn({8O(>R4% zt%I%g5K^LjXhu&UOI0_!y>A1J0^oa>BZv>J1!K#}V8RY&#u;&R)H~F7Lb@(_5bcJD zj+Px|quMmZ!ukyBC!;Els7p8lWIT@XCdBhn^6T%lAj-^OCf{A?sq8RQ@>Y6py0mmE zlP;~8N~B9?twqm>H=gm@Gj1qP<8w`o?;^g8W8#tK6#nn+L!9a zv(7y!{ZY~ByPRrX7`30=N^=KCh6a!+JTGdZnMe|tlIN{Wz4$>N_(RgkX+6J>Br^1P809nNGFnhUJ%=&i zh`+5r9H`EspZTc24TP-3xF~TpCxcHFo8t5PX2N@fk@jJ?+nHQA>L(aqUrHhap@*3p z7!uwAfsXeRfp`o_U|RGw`!bRmW>5x6DPM~mqXQpGfO%qhZ^Wb)!^T|uah9Y!V&AXrZ@6viSD3_=nisW^ae zLWDSg=qG}8=S1jvqv+`qXDT1D)(*jE41%(FKU$@Cx7d|fzH__@n3aXwVg;&8xml_> zi9e2ss-a7+G@wZfVNo}BBLbvH@r)-Yv5grz*k8T}T>J~`u~eU+9!L$*BCQ#gkQ5WP zSgEhbhWqCzK3z90?-z7S1-B%>bz87J=B;jYmgIs?w&h_lDWmJNm_bkwca+~Sk`I2fiy`PYeBwEAXi6G44UHR52pRXgk+&*o;K8~z{fZjF%JiJnM)B6c?{T2>GpVgup&#N$;FgIY~T=dy3;oO^L zIAPAUa8ml5mT;a*VK`wfYq?qP+?H@2L}55#E@!zpZ)Zz5&z>-xFgIvnckaBF@a0Hi zO{I5D5RRvJO%e{JccC0dYXfOsHZW_c^sWyvqcDu;IgIDs{Dur>SWzXU6mH7ZHfhdk z1~C#hS~*n9B!;qcW0j3sYHpFci1d`4TkI~b9ag!}9T8(lVHGly8?RjEjw%;Df9O9m zJEkx%!ZkS^euBHmqoo7V&FfC}^ekGr_qf*(D0= zn~2LD&lR!-v6a+~A~HKJzU?@WUUUn&@j`BC<=E`FyHtOZJh#kUR$4LN#muOc++|e< zezA0xGmDnH%kjxoJ&Pa-f{P+)DSnr_O9ScC@3!2c)VSPG6t6oZreEY2J#xdL5|_u6 zm~ba*E1cT$?2tP=0m;MRSLWLgtG89wxD#l*Y5bnGb`W1^Y^87~wsj!fHeFxZN>N&| z&B7L9Yg^>rbG4G*Pnbh{nXL8hQRvu4rDLmf?^(BCFNcndCyv+@33Ds5E38tVZi=do z&ly~y{=_21OgqC>Yc@sI7IifcK$u&ZU1^nuw19HEfIn*iwSR<@#} z>xV(2P`6F?r-Zqxm95Sf=+qCzwvuw$W5cU4IHC_AbuC-d%cmauKz4DtRqiT@ zDYx2PUFt2mtK8MueB&=rGPedbXH9lZ{SppPe+8zxEV~90xO!F}XSN)JN?z>)$=;9z z)7(iAO>xnY`81`IY3;!`t?g2=V}wE8pUafj)B6c?(^e^{lK+EZ)fUATXVDZ^zX>F*}+P19cTHb~)NiNZuJIG#qZgnDi5WmHk8CpA9zX7Q;z5har>a{AOfa#Ob zAI-p*Sem3okgh@a!lX|($2${t(EpIx&j=ItjJAZG!<@Y)%`2Z>xphxhfzBddr?5np z`5&}6IMm|cHZ68ii!0On33KbMQlYcrE$XOu%!P-U*HZ6TyzA@tvUsrstE=2E!_c70 zZ3yGv&=!9Q@oxCE6fd2*jnkj-CBRY|IukAjuI=i0sw@d&0-^+MtUp5k2~U-vL&5|d(w3m}nV@T7)TQ^+ znLE@Te-z0(H+PtOSZU`});(-|FZ}3k4;#N54$gq1q4N@<4-S515DS&9rYy|LX#!dxvfa%U6Dw?D5{9G0 zDp|Cceh(`*1IoyP!!a-(?jByVOOs$&n%iQzTXg9ikv*cI2dNd?+(WZROuMO6QG&N>=Eu3P*7zVdbY&NWEbuhb>)t7kE)&<>fs*cJ;~fX zCVLEb#4@>~E5+>5s*BEJVFMqlE#-aaN`b~kNT&8Ecq}-ot|jwoF}RD~4kkZL9lokZ z``~!@c$qEjD01C5%C1B8M67yHV7*^JwT^R-t1b%#SnhH4$Ix)RJ|t=(V|bVFoZy~N zg*|sdeI7NmNsGM;Mn8lJLtx0z@(hel2^z=a07f&IhG$@NO7L(qh&E?nZfefIv~beo z46IEFUfkrQwHX+iniJQ#V{y{h)OKd}#L~$_>cc3q?1}Z?v%C0+xV7${%uzo8jK0f$ zV!f}{Mxw9apBVe5*sC-z?kzQ^Pg8w(LP5N?x*pk6*6U%nI(TQC^Dmi1;aY3Ta@2*a zqV_JC=j3gf=X6^*?9aJL7$`G}0b6DiJyz-=U9at3GD+#TWs=fsr9-qBJ&+|vk8R;} z-n*e-fMBCdm+(QOfOrzBjR3k&5 zmF?p`cgTP%rhyKef$-93b%L`iG4}_u17Z`0N^5i|S0)s=f?8eey4h?nC&*?owUSvu z3XxdZY)fR>FfuVhM{R9{ayq5bAaBVNF*6JHelasSqoOeuWvfgmAwVXSc`KXOHe@%Y zT&y!yo0+yYGYb}G9b(ne%nV6rDOOjYW-#r=Y*AZ`$fKfIe(GhcqKRHm)JwdJ+X&IEu4L%T@!}xq%H_0%#B%??)5O^UamEz>A)n239-m>7li>b8Yj^O z*?KfyoCrqal3S9|xD<}YrD!xB=PV7=z75y4kH$-~i`^xzrfeOJH4dY(mWR=JNjMrW z2}k24;b^=h9F3QRqw$hrFdCNzq~t7jiOghm#f7RIH8HKksJR?t-*|4hVpuqtHBst~4O*-1UtU8HNEEC->I%5u;OE4w1L8WfPoaH?EY zMQ5VC=a4TL_{wfMHt>bnSX~hed?j0tUn|{}9KTjJ$GWg{emIlD5 zQF8e3yNmNl(us#dU7w|U&!FSfXg$pJB1lF!EX|{3{{S6;Nz&WH*pPD zUIFMyZNP?(fp-d+5!fmjUe)W`#Y0~O>h(BeX5fnjSq5RtxwILkWukQW;ol{`sPR~# z!>-G5vPQQYzdP4@DLg8J1$O=JKqf^`@GaY^^D3u3W$22jGb(GJGIZ(GiIse-GFTui z9lrL&6kf0hK8xF3+2FK&7q_>9hl=)Lgs*erAdegLR+c#RR}nRxiBNsX-byCbTfw*a zuEuK$>GD_25os_y>(z()qALP@{qX4)Lf=5-)8}vAj?B3k`HP^tUcA zmLTX22&!e_xRZ`F9B+i9YA?cG-UtsWu$QIy+ktLI%@b?QsPBk;&ImTt1F_9;%5M4? zf%Z9z)+F&5uT)go6S@^TW4E6=b3%lh@F_Fs=pD?C(vF@F4Ri$r@kE))D!w~f=~^7b z1crIL87n765);+qyr>!EMmfi@Y~;hoVnXev_e^5SwpgZYfqjK@ZpJOFzkzLB$gKZ9 zQ2k~v4nW|;Icb%HB-e&n57?b7lvb3^QrNku^rZ4@3y{1WS&MH+3*Gk2`qzWo%yt!0 z!XTm9u9PZ(`*_St1}uE}C$^^i+*sgzmvt@<@dcBW%1OGuNT$rAYLTZ7$)!`a- zF$%)M9mFMroOiGRxjp0bo1*9DF&uvhlZ^cj+rVL3$p@tW2Two!MKf8DsYNUU?r6yX zJ@bf^`u|T3j35X2>O~O+V>X8H>knZ6!_?0d57X~*KLn}aUX=nwy>=UXxCxKoaEoQa z_C`R5_GQkH*BgaTy$=LV`2nlBv25xOk+&1Y{KXyRIjep7x z$X2(|l#t!n1~+E4e-wLI2SG>lw65iiQ7t~{3`;h{FP8K{-O1=lXV;Mzc<^B^JhrnG zB##7dI46zKrTnbcKTR{Yix*5Y_tA!fe6n^;qaDbb=8_c0TLhhOViM`_7Q>kry98bd z#zP>_fgXG}PJf3J%Z)hqE4hlR8AwcT)==Yc4SLri(eNyRr}mhBV|y3uZ=H~UXDI^B z85W?o49@T^p&nyoQopZ)^9PaeePrjso;D1g)F#IAmWv3f2h3xPaM)XwRAihymh0|A z!CCdsv-oaADr7G%I8`YUa3GEn7RsL*k5UFi6C}c}qlRTO#GeBqj?$b-6jqOtLMUAFT00_#|i0JdN3E%Mt- zuH9^wwqyqMY8EoSHWyEk_URNQTT@pk{c@Wt6ZD>pAbRVU^<$Js2mAR|_rhk_}%YTzV+rL|2-oI1etR_>Z zYTf9IbW0dmkJ9;8A4l<8vE2J+hlbahny51mZAEIWXI$p=(~&7e{}(d5o`#+{^LYiH zg>5hExSl4z31`6ai}Ld}$uHA?jX=wPoj}`vt-yqTyFgUX*NY?Xzgpm|=2KgB2GbW^ zY+4r9SaG1nQX@8ub1g^V}oJi7%`8UF^! z0@Ht~K+AubK-+)0z`TFGz*$YEPSaULpT0kK01tj5MHt>08M*@I3%Y^{f*JXhER**H zynY<3QkUTTx}IlJmpfzSw@>0S{hI_@{>=hy|CIvs{woB|YBIG=>q1}D9!$Bv1+h3w ztF!1GNTkjv+($hL)w#hg4i_m{kRCG)_*)jjn^WEO2cv-Uwe`Ozut6%nGC`M#xOio_ zlKf4|S>87yP3lqR#}KTB{Qb!OBtDXC&gaj8GvytE9D|h#+xSqps*9ca&}#JAhr!dE zwe+gaVpJS&6EKrJE9US#lyMj2TJq3ace0kU&Q9m3`%i99CcLaZq6zn;IUBw7B;#xuQK&T*kv$ob8GtC;Z7z_ zgrN1FLXJ%u>Thh`d?}ua50P#B#n zJm2?X4p14zO_doB?YU?t9O^dZDMm)g%91-5EG?ufuZ zIvQ?ghlNEROZ^E&n$9@~GMG9h6>b4!9&y>71$AY_c<1MJUEw@#C-84ZdUf(Lg>J4A zFnKs-UDYwCJnF@k){n@owV%m#mbW-YI?{s(L0KC~<|pKqK8@%|QuG|s1!k;=O!k3nacId07c2L4jPN(u32A7sKg27e8=c0#N%cW>1Ov!k*^r31v5isBohG&F|Iu zXBrO}!#~nCFYb7-idx%!Rs)Q7tQqDEv(CzUmm+8q^T&b?EyP0eVdzxj38ZMRw1OMV zD=$UN`}kAMp_imXChq8v=|&ki1`j2`#KhVB@1dQHc$XoTiA;V?(Gi5=;_~)Py|Bi( zqPy5rInAtZgR_N>h>-4Oq(hZkS^qe(JcMQ92py%AwyGfjHO!yqAsT%t#!NaR-t*z@ zP3Ko{tp7e_OA2#+a5@wdO4(7+{m%=)qb_gCkIK7$!*~oK- z&&(wT(aIC!hHV&YhLi_19yGZEU;?kJXP{XwQ_14=Dn6Et0~c;eAGp9FQJgWg-DI{~ zT?t|gozu`uCm@9_(QLASt`!P1*_2lY*#R61>j-EtAdADXy*hfQ;9UjOBWT#V zZ7uCO+3U)pM#65fjS0ZFp?vy{ZmTDWSGj)$`A8bCXV~ftbZb}^nZg;}?d5!Vo4FNV z8E9M$d8e6MEk3WyH3hx{u?^+CA9D0Th12Z9>H2;F3iLW8WkeoA#)7CWs=_Ms|1kF+ zU~&{k8}Q!D?rgXq?SCm}G&# zfH656lY#@3; zb@G4rf$j5|u9ge>^l&PRbOx7@Z3fXKFRdW}zu1u9CTV3m5?O7Nij{}BjVVDUSW;6L2^_GL@+HZiW*h_zpXiQV( z#4v>*XJ(3J#oeRf-J6JSk)!k!ZiXFOtXpA5gVi-YC6XGgF1*$(GUMqs?7fM%;pho& z^rv+eo6|)!#mLbtns-e}OCn*VM5RQiQer#FC?5Z2{w8BjBY&@DJwnrq4vnRhpEQ0{ zET`K)yZQz?@(5tQ<+ECf*qp*r9E~Y#tJ%(RZ+T|z1HmVio3al<0pp3-`ZIz09MkJ= zC62Vube*Z|iMu6kpX6L*`AOPPb3x(kJ??Dd{n4xf3)Fz-aj`!+-a?;FW8k(+`x1rWSBMeXx$AK<;y#ob250`C0-Lz*We zuyz90y}PA-1_Rq=o!q1?vrb{kMx$aA8;eu1Y?axq#gQuy$4>f3vbQ<4FWxoW7oBoq zU#xsN`{JT&tYhIyYX6+P5BKD81e|Dp3Z~=^0(E%8K#z@Ty=J-Eu@#{{vkMF6*4gti zPJU9W<>Y6cmm6pp6@w27aaUAA3uYkobpqgZp!YRqgr-qARy$MkGuLlfJ*dmcT!jr_ zY8K-i6ho#oM76}p;Sc9_ScOHGH2`tu5xjLvl#36+Vt3q~$yT7-7Fh4Y=?&^Xz0pbS zkWBAD9%C>bXazh0JY3AXWZV<*lX)vKK9_k5A17>o_^+`smUs-w=64f#$F67kha>d; zz&$aD+?V6pt1{w&supkj0IFz%H;EwOZ2+*n%_-H)_Hk|@;I#rvbB}|&?0(%|(^eOw z=%Yl=sY=Af`pi6;#|^_|Uz*&TaRvf8oho@g7jbX!^JOe5@$`&SSOmWb*uS$=Jik-E z$0-~KHx`6O#qwGZJH^|@ACqXYfx@tVfu%icp)+=hcYw~$?SvMS-x+L6@$D9aod;!{ zp9E9z>v?;BgT1tEW#qNckBT*<@+oZ%hAT}BLo*xbfAL-+H^ihC6lQO|U9QmxLN$NK&7cNeguqF-jg==}*F~XY|zL1=ISF~L6 z!1oQ79*Vuq()Yo2E_BfjkiyTS#+*14B-G*OBIkSra%$lM5-lU_!tV&X$PbIh`k-4S zPln|%BaTaXfCW-=x$+O;bT4Mi;7bQ_*)Cw45zjGYrR}gvKLSbKKd-4?G91w{Md=cR z;(K;C+l9GhctY6iL~iuBd$4F?Hf-uQ;CBo7b-CQ=>B_hRUq+5@?PmjJix7ToaAXn} zih7Y{i>#!I>1ri|RtavvAPNFg7yt*w^8~;Q3j2U#7GAW z*nE#`Mwc(!D!ZrSgo&cl=e)Fof7w6J6#WrWC%p>fGKj56_t>g*zvHJ{6EZ>)Gm#`Z zei92zVnK62HB3Uo%bCQkg5UDasLZKIRtVjwShFSBPzbW2pxGc9-W8G1sJX^GkeHeW zb^j#~>ViC|(>#E6Z^kM{>lWl`%e?{$!->1(@o9wl`;6IS7~PO)+)N`#n2iB?7Gsj@ zGRw}-$m6$tMh?FnGZN$WehEnD3|y^%yqrJlrOUJq>OA4Cuqjj;RO5`fR_2i`bDcoj zTrV(gZV;F-KNX01!W#u?GfM;xYCIP^zIahTT`sB**Vqzr2TYpv69-7~DGN1AS0N=O z%-Kjix{`#@i+u^_Eip_8=PiFO>@9P%K-=6ZFm7%Wm@u~pgg&VlOe#?y zx^yBe&}VX`K0l#8_|6o4uBJYG`bQoB>MTD8_c4`#*NjKKMfX*pJ9eHzcWd~}kvjlu z7W+gNbX1R$|eHWW=AN?s%Cl{e|uV-L|SNR5bi8RZ0BBajQ z^CGU9LKaWBlh{@uErIRasrh*$nLI4nW0}VV+U5~~ar3Cagn2?B^7Ao4+RQ@&2Q{8M zTl14H_a^Ye4O_O^5-CSFxitZXW9(PWR&cOuqG!+f{ze1NCSksZVY?#w)-=Xr`ys99S25OF6H)B`J+SaKE?oH9216--;mh`p zpkuBi!W$e8@WqI>!H_dbtLtBK83lw=Ps}UE?xM_g)pH^B#H1=_RhXK32V)Xu1U|1g zG;wccp=wyWCPK$x%gg7M|Hh~D`{~2$419e$N7Uzg^0{?bp9(%0uEg#}{@6M1Lgw?{ zEc#BxwbVt=96tMsD+R?YS(lulr8_RUTUXbtxwA9)4z5qylYZ~3i)kL_Ff5$qfW|d; zPqmzJwQ}ZD(8?nUI)sv@L?pddBT35~qmJ0UEOXH_Q;fqNv5GGCJYkgDkZ+oOfAswa z>b=ECzdvUaScz{WNyj%2d>Z+Mz%;==zrP%7Sv&*9S}GM2rSTTM)H(Ew(P}2?vc+I4 zvq1BDA@sVB>5o>*dK8Z^OKP#<8{#X_&DhTSrPEUxMOYdA;R-Stps z4{0c_Y;^=^yEV>TAHEI6CU39RgUka_s^^JlO;v#+ZeX;pu&)g7{=)npUN3=E=IIXr zrCm{Pw7$}?NQ(9A!Drz31%XXJY^SuOW}=6TsmGjTp}ZcbMbX~d!qW`o|x~= zZyfX+SF7K>QGVl;-?*rLTcA>-JJjRcuH12;<;1a-l=ra3`Q7z|#-QUCd86ul^P;uL zz&^-epMGRR;kztybpp4W$}276+Kn#R3#fD063Q|@5c>SGvW&r>bIu*RO<2+YvbhgV z)Yj0l9OR0lxuU-Qw_HSH`Pz|5U*B9P4b$4bIEf zY#D`$%}vdX6@}VBEtjE#=CRY2o-q@TjZpn(wk*VLzYXy}Pl8 zQ9iac)|JaaVB&f?NYec)Y(6OC2B$HVPD#4ckz~vF?J$oqH9S84o;=nL_2{avVe+@{ z!36P{o(P{z4WCc`U-46`rig_@w@HZUDAf(gKQ2Rh(c2hcvCi*YMPH9(Hib$R0OW&NJj-FJ+)=ueT<^$mhYTzrKQz__<6q<@(H($d$NnHgU3`}otwGZJD4DqX!&k@C=%i)--Li6c zMQc|CI%XIA2Agj2`Aj~66YCI;?4Nm*WgahgVuc=`o0RShJ%cau?!Nr^p{Ic?byh-H z`9YLc_acU1lF2;|s@6xh&m`I2>)L55HgRoTS7+W%hyP=cfhPT2c8G6)1K-blQ=o0$ z5{SF-1-6;j1r92idjQgh=_R@%cZP2Zo|s=DPn`PAn3pAvWqu>jHm?Ybn^y%U%x?v@ znU@3(D!DsPdDG?MJessucC01L$A!#3P7}N&87a0>5zb83aLI19C8qa@NP3?WnU;B8 zplyCFFm7HD*k+y;IH+XqeAS6AO)sY^_hh-D`~+e%xw*@~rl&c9VuYN1|PpPOYHl-?GB#AJA0OEzAGA zctyhkU88;h9^?(v82w{7-PLge8u|xe%*&{b{}(uA1b=}J|5q@OGZLiC|BLiRd5$DE z>KEkw{}qhNs>UfZs_XwXUM>HBLmqiX`Bb~3VatnG^9tOA0lvIE{~Ntkn2P3cX8W`R zgjj{y>cC}7vB|iz2|kO|2bUVcag{o7;UFCEsN>fFlb;cvhvH~yaX1x$bwFosauwSA ze*CSDKdwpgFNz0#0bC8?0ewIhz>)mv7sMr9KogZioTgfJYD(xCsLq z!YHq(e>7f{=DYCkiWB4w(-{3DKe`zn@Ft&thWv(RW0^k+w9WehPp49nVt}n;}Lf*k1kdamm{59=KZ(}4#o#Ha*$?t|-rkJzi&J662MhF&vt#SK| z#(pDA=qD!qspmHT8^AVuV#eN0(mU{}kgvr!1;AbtQAWZ$pG(_oP3VQ@UnKv0K`&;o1 zN1DQqA1mM!h0$f}ojzWYD?JM8-U%b{!R`zp<{sJP%$cII@_uX5NMTBzTkUjr6Kslg z4?+HC-b(q9-(SIgr(180rPjhH1j~8~w0v62TE_QZNEuq@OM$lekH9wbxxhgUc1v2e zbot|Xo)MS`Uf_!@;RDRWvzT{&24Szx$53i~cua+w;VM{1Hew;3>ed+RT(wfSXH+-& zp7~aEuuQC;-!-<`2m z+z-F<%?FFfnFAPzb8{~#wwtw(;dqDLe;G=gJ-<}%iZj`UgmUe1LU$nR2PcK#8sUW^ zOk!ILsX_3Whh0k*zV7joh5QbQd@E5c{T3qTU~NUWq}ygb-1z1tfZ;zzTNJ}Tkp61; zmy+Wor74kuUpw;N^ga|M~9q%Zg3m4M|Z z$aL5QQGvN%e*$<#Y~w1%fNfA!+l|4s49f`YqP<(~g6D;?mKA%Jby1?LhLSo|T~W7K z*H(HGYD9yQQWtR(A3nWR>bEe+`nNW3h*GVRT7a5f*E=>|Ar{scssbDxw;70khqJlExM5u$x%Y08t?d7S0z~0x2joLJ}IqM?xXgtz| zjTqQl;xZ>I_LNA}dcQi?zZK&oKv19gc38AN|61$J&)DY0rH)u8;nx|*uQRs5K@D~f z(>gr~UL^3oei3ZxwacuKqg${X&TKlh4A2B!o!vK0`dfJ zItj%M-mJGQ)0ak+A)6lRlO0RzlAU;h=BMPYaSlhEmk}pd-V~l02RA$NjupIFwLgKs zmUD-C+HyUTGkn>-5+(fmQ}k{W=h!?iH_es*1e=fuO?dMRFY63n-Q|=vrv^;c5t^(c z{p8TeWQm(;VmtqviZsdM>^!@-%`cAL1|VyKU!Kp36M%vwPp>*&Bk5O^FDcnAWIg@K14HJleQxW@Om5q*yz(f5QAeNUt>#19fuV?q3b z2#+CAauDGWrdI46WiR`vKxq&K-In__EZ4Vr_fZfctNuIaY7o=3K#ap}j(afj`^iD(<$jkv?&H*q}0GJB^EGPh`9vveK(YMt+ z(#LU`zigkey4}^m-kIU90f3y+^Kw!buJQ8%bCL_08N)fDYeY!SG(?iBt=a@Ne%M>I ztMwLbYrRGLT5m~Bt+%8uuc_-CI*EzgNZOuqJ=9tx9w_>g831?+A;(ZVB3-Xk)Q6h3 zYz#IYoQ$?i_mlXA=mWH3(uU!&${RwGWcgX0izHgQt>uhPHHjLM8hXqLfg}Ptxxyi8H95!_c=twJ2dk9Jbug5&@ED~!}G(j zS1Z%SLAP%|YKPCjn47W5ob9i+$w}|e{FFHj8um_fHGIr)c!Qtsek0$76{s+7V{ujQ zuZWDztK0#MN7VFW36}9g8hPjn>sRHMAW_a?GG$086Na6mk}hpWalj?^JLvDXqZoZ- zzo=cx;-!bacy4|%Wy!&)`LkwQdHxh)1#T#vU5G6Lq#JK?Y5+ncfa@g)2dDY7?UEI6 zateqcUVrDSUD#BS>5Gq>kH>CE)+uD|>WqtMyQ`oAMh5~G1qfDOL>CArvLYDEX@Oa@ zo8q&vfIm;}sB#MMo~o$d);C+nuFh7z#bqmx+jqEyfGO*1bdp_#eKcY*i4$1cBZSQ{ z2A&1JJ38NendK)v0P`*p`w`f`hP<;%uOh_8Q$aZ;%HXAwzcG>Qi>%V`@hcDQ#N7^{ z(C_Sde%F&6?p*BBaK1Evfw+_b*&{8?=-%cL`l$r_sl+fVU(5MgsNL<-3wB$wDx?X3 zv7hA|`Pn`^$A{@^>4EQ1uHpn;+CTJ;g zeoGo#%u#$9#7TD*7zL+!#5ia5Zh;wv#%u~ZNVa2t!urvY!J0Z-hUSa_-Jg$R-DzDk z{IdjcV>gLp{a7}Mkf|0xOVB8Wn|oUl=|1kK=Lm?`Br%B<4ONy0ZyB1xIp6Y|2#$P& zvm8)0Sq!L}ECt>h!cxF%J4Q+YrF9|PY~wh#;QY$%73CpolTswUa=sSBO+xt{N@Zs( zoWSuSas>RL4=?uNk9>HE4=*KzTwObZ=%B^POBR1Y&AnccMt>~Wi}z_#N#WGLT63z) z^=FJy-WP0A?`HX>l93IrsmKObWD9rBapXnF!V-(am_res1&zy7(eP1IJsBy6gWW3+ zd=_F;&kv)(t$wcoiK2dLz=z=?f`N2@EYi(&v_5C2-H$I!_TPfSvH!kb+b!Ox31J2N8 zGzMgXH5EssoT1Cp|1$+9J&I5J-g*)7kYQDtLZWweTcW#k@aIS+3)Uu2;^s2 zeRi%`*B-o|mg~68>Ww??`OENp8p86$x{l%6?g&5J9dIQD?Ggye`*Os$bLgr{ExPAb%+W z`708bek-;nWSjsQS`v1C65hv}7`!dlJKEb~Ox)6g{WkGGay(g=P)Ikuv`N%I!f4Zg(aTk;POl2^RcadAtRkRgwWinxC=31zlC!#%FHFXW!uuuj z|2JTaY;B4+C7PV3WK*gs-IQsnFRzRIZyg$kDS}cN(*?-BP}$9+%0~RRpg(Tel3a=; zV#^AZA^}@1D`4xYfG~C2R0Ql5SBt>>y z)m+Vr7q+JC+*-c2qt<#6;4$7ei>)emWzjGD{-)rV=T}*-`(9LCjGhp;}=v&Q%$9>GztD zFktUx#n>l^QC*$)tqKUubiWE#_u+LnX)#to8SKt7*eX&6=hVe{9Yw2y3ANCn#Vdq!!2)Dzd+#*)CU>W{LMExUHcBJqhRyXW2j`AZ&XBElSqs5;c z;yYEU9wV&6_9(1I3j+~i1BV1D&khaxtXL`EC9^|HvB%ISe5X#^r|;~73b7Q_2YY{Z<-l(4YT z3a#U;sQ+!)l_|{w22h2^(`X*f4Vs5|c~|KaR%hdIWL4vEHkpK-WW>17kpSP4_6z#R zQA6j7RN9^te8iN_^Fx>h-(14XKR=B1CrzE7$Bjc5NJycg~O0b170%ZjAYo4{RmQ7BmS2~ z8AUP@57PRhTE7N`W_LvxBGo&R_A4txB2uo3N{JZHPnO}oxY9pN_SKdCp|M?4=^xTB zsq_!&ul4<_bK^St2W@#of4t2#>>sHy*9#w5RCRXWP$?i3eB(0we-`zR2)L<2KqRk! zt^qLg5480Ojrr!vkoiGtI(q09hUm~kiY{Vjk815{r@!lIw;- z`VvXc3`y@8k#r-F<_a$h*YyLgf|0U_nLe6FxQNxun6x7?_TRCML%eXg(p zN%xo*aQzXnC|4on2Y;?Gg``Jr9FSg5tcH|R0QtgHlAgIqK>8WMAEs_1JXN)MM*_h&K%?l(@47ZW&!EZ#A-;l2We+vW0K}J4@fs6 zRztdjkZwZK`+igmgMdp9)EHTUC)tvN$QxS=fxE z50gO5rE%+#NOu8gM`3f4j@u?6-G*3Hfn#afoVaK&Y)R6!whc)4CKe@KL{iQ`v=_D_ z>E!JK(qo84Nsl8bXC!ikZAiNF_5ta!#G<6fla%ucxx%(2-ED_}^i*O|Qpo{MGqe}B zBk9tRbmJXIBHcr{ZcoyWNFbS&->HgJlC`IB-GQXHkw8fQJtFB|!gWWIKD={4`WdmP zu2QhGK-yW@iKIL45|A2VHKenJbZ3&rcMV9_Ar>W-9GD}dyO8wtkhFeA73m4g*SSKv zD@lJ!0!h{v)uh68Zy}vQQam2#lfF-^MuB~VbS6m;-z^}$i&zcmz97vNb|dNKy9cDN z5vw8HPe}J5=|Ou0q(3AUC6&H_(;>OSo+RCH&wz9xu_)<@%mGf2bQJa?Y42VE>5qs- zNu{(nW71KWMbh%Dfb>0LQPPvhm6Is>!fcX0GCLq`ol`|BTsf_hFU%q7tt5~lc%_;Y z>qNyxoN37y=92W7xdG`l#G(qELQ>ARbQbm|>D_w=q@NRul8OSHddU^`A?d#R1f-V} zi;{{Ba5kp1urEnh+BYEGhgc0M=V$VT{Yd&*NSfWRic}OhOi1@9={+Qn9QZ>u>8VVX z2h#S!0VGWx5Rk4#EUJKTEeqFII3Bn)Bz-W{9lCPW{$`!sQ>BR>Iq^}T*Dj-8L&KKni-;ng6g96e^ ziA71V_oKLoQ%LQFr6k?_;DGcJVo_4bEY2yl7rrHF#~}gfbYf9bsau?8$`@jsez-p* zeY=|UbSf|?T1&4uq^llQMS3PlhlJE7={PSSok}dq zRVw$fLK-LOD-?CkW}@Bz^gafVA$&kw{MzUH2jBsU#3xZy1sEBq80Gq=lmb z(#?oP6_A2GSrpihq|b+>_R&?OqAMrOa)td#dOit6fyYK9<%sL;W?S&tc zbO{NhV4tfd67bh3gN6^h%OGabiH4IjM@Pm^LQ|I|^5k^kotV*Ors3NX4`{U6?OC$%1V?B_Lgs zSTtFZ1D6QtQzShmB)z4YRKAWc71Cdkbdys9u6q-Ua+L<2bBy`I(lLWR1~-j zq@9IlNILPffa|uzqFlvXE(d9Q;aQTVP7g@8A{He*jW%!vNOOhfNSZz)Ae~06hV)9& z6%T{pJ2oV3KeLKdbiGQro<-96B#k2WpRE&`Gtog!yB;De?fa?&k zC|A*yGq1VA{UrT+NZNV+NTi&M%@uw@(kn?I$@*wSQqIq|7akz#o)-k9#}SJvAi8qO zHdpv1Nxuq7e{f+H>AAF5PU7YY50dm>BoGD0UNjOZXLa+1he-M^352xohgGCf3vK~v zN8w?TzC!{bUFqT~QqlERQQ#4h-b?}^eQiY2+d$f0c$B2;{wN@wN313XZWmo2BkA8l z(y^C}M0$sCeVn8}A%Q6Hd^M>Q?482(36idGX+XL)v8Vz-XjY9ilmo>q>opVO1|DJ3M^^>Y2Rf5*S(2Fxk_JfpO79$ z($_-L?B!LYk}S?v=L^S^^hYF+%zC7n^gLSDFF@K}IDw=aT@jF;NGz&===y*ta3V>q zD+AK4h($@ovVJL~Cy_LJRY1Buu_&oj?gv5ISvZ-be+x-h`bib3Bk}aD(fxTi`6~Y`+nWd4(myeAZk|Lq zJgy5po>GtZLyupn#|qa5QJz+hnW4us>TyBn@hm;`gatO^Q3N;TkuxC~5|R%gqZ2X~ z_WwPn;MeNANZ|aTPoZr29BlAj#BleA#3X1_tGpLhc_qyK5xjY)FzyZ>!YGhzRgM1g z6^tQ%o$lo%3xco)k%*lV$Q0Sn$p2L1q_BOhg@h+Tou-wi2bv6)an{@@o&3Xb8W_^L!6FOCqgqb2pn^{}n zpfYeTfK}q=9lG2rcsmZHzCr0;xYzUpII$P<&jbl0fjn1f$878+ehWI4&%&_{JQRgD z3A*i}$zUb73atfXse_Lir~EGzZ1*CDxEDfTJr1!V(@UShyJ7NmD3(;|-@tLy@f!G# z9atsV-8Xi;_a3s=a&Ly0*L@u}A)k(&w`I)UWsbpvP#ZvCTd~1TmzIFQy%IX`lJVRG zeV0?cy>b}uHKgts8&A;ujFh~Ot-|nhY%Aud$LL!$1j34+%(@9zwSBa6#!|AowRKiUt&6ZRW?`~mJHD7N@;u1ZN}dK-(ea4QF5DaMh5x+evMB}iQr+1p+AvKQi*p@n6Tanc*i!9SSCUr zs_DSQ0-UQ!d?oy8?lgwW#l>?VbeghtTs|2CGM{f%$ zzZxzWoDkT_PI`%-qE@|$is4>_YPmy-FGGS7iRZmxy-54JNIQ2YvlS~ZMqXv!>a6$j zh}YzJhXEwq9>h=b^*`sB;df0&kB)UBF3$Pea;HNUe41lp!W*{v&|z>5#X`0)ZX9fI z7sd5dNlqK!q#oOxARrP$P9AXoXgqc{tegMzd2<}A#uJ20r*|v(lwxtZ9|jtAzexAH z!{Hy&{pGMbeSM|7jqWb|`26~++}qMUXE^+@D)&WI?z`!JbeR6RD)+lp?r-SM-r&n$ z#De#_;OrV*#>F(5MQmQY0eG~ln~jUNHQePVU^k1{vUoegS*XJyG$mnc;yE`~_|bYj z7k(OzwbOgPl7yAOTj6IF(O3<in@YD!2=38^U| zH6^5`*bCy`j*4Wt_sQ@5_;qi?kDTY6gFO3#)D?GUR0y*f`b_roTL`qxRs!Q@Yk>)~ zjX?DCTPl(;n+wurrVAWY2JR2FpQp>+6^ylgvtrvo-g~U$t>$wmhobr?fV}|q)xw)O z3vNJDka7B)Vq?C(Snrf97+gKh7S`j!$4?od)%=SfXFgN#ZvZKOOYXb>BY7_*&&ISc z1+#{0;LIYr>`7ZArUigLOl(-g^**QI5lLObR2sF9Lokd28W)@Us_Emc5Yj z`65}OLk{BE>hC^?ye!Scxkr5vb^-=oy`vI1Lbx|hcO>#Pw0InS4yconIHM3JS;EDP zj=MX!C9z#6A~Qd8X}l>uTfK5i>#D-)++z?uV-Hfx?k(`JP?TV4+?mz4NFEJ34yM`D zUw#A<7D+N;Azw1NfdBE~SA-}djsfQb;&>(RL%fEzalVwWWiAwGn;#0qF(83B79LMbDqFK<>X$hMnac+EsbOYYoU|EzRCnj0}1uZbyMiUe|BkYB(%@M zn@yz^fLROcLELY(<0ADZSO{O^C%5KUyEF!&E{fEd9|<{RlGTCZYMQp`;P-*1P3~K{ zNffip&js4%7J+edv%ogvD}1UjH`F}{56sv*GhWv z?u|g({8S*Gw-Gp~!R}?64|Ik3V9A>`A4hoYEpaTfuRz=EBQS0b5STFg3xs{|CrF!_ zD{xTbxtFUDx>N}3(6-1wjOkd1#3oLcD3&=xAl`!(7&m7MOqjC-wwcoe4l22Og|enA zqW^sG{zQ38{XAOYSmqdkxUNB9+$0&f6jcLn$2C!GzrUSNq%%sFbS2G+l*%N2VQj(sQ zxQ)lTn4*@1$igvTjgrhJ23D(xCrM70m}D!hLi<%el`_8Ag-HFK*~CKDC@_*Lbt>qA z{~EymCQzIIXT*leQb?9LT%c_h2y8Q+z(EamuhPP!%eR>^t0iO4A>Th`+4*x4r3)a* zNS(Gz=MlG|4aP$32yjToL7!yUIiSa7Yis3sq%va?LRE?(SUuQoTYUK;W0Hd6O}LCn z0gadb48Hk+E#jrW0O=Um&?@6@hm6Tc;D_)lJ&QuA=eys~FM~_K8I;PECSZJ>lyBg^ zKT2bglq;s*fMUvX+jvRh<4TB3zJDE+knf+UNPGW^zRdn~c{`}um@cBPBlGehup&4| zxp+atDW=@b2wqV{?$61(n9Mhfip20xDdzGVw(GYiy_~yNL;ek6| z5P?mm6A5eA7@t+dFmVX5j*a2hJsrbk!yZ@~&6ao$6AE$M3Xk{sGJZRng`~F{vWZ?C zS=OhTEwLAEfgSfy$YmR?Zx3G>E8L-Nr4{>8UGC>~n9XB-OT=~u{1?i-Tt>R_AdJ}y zcHwu9OPSc!>Ux}f*Lo4J_TBYS-|bKc2f58Gu{{~dSx8WBp$A)Pr*t@Yj@Ps0IHnf+ zjB*zEa%K(7(IRW|Mbsg~MXK*+Ihxs^cASL-C6?(~w;}7z9P)@nQ*BfO_90H`2-Sdn zRXnyaboe)ITuQokU4@Z8N_iDO;KANyXY$2>L-gb}abt zUpbE`i`Yh%+ljF^#+oK4r@y7$$=q=9CVXxzhQ5oIO7Jrbej1({ocnnY{4Ij{d|{?_ zsq;3o~GI-7Xb^L7HsQ z-aTS@OXD*5V}8v5?=P5NOk!yqWR|<|lPMSQTd%$=0xhkCpN3*5X3T)#8ne;KbZHf! z=#tVU)*_ut>wBwmB2C8_<74Bu3i37)dm|BxeM$TA+eni|>Ec8Xdl1tM+%DaXKCYv` z*Tv_Fv(6&uXqRq;SJ#A6H$?TdsMGria@K!-c`v(!yAXJv#C7m2<9!dz4oW0p9m>ievP(DD?{a9ACtMWvI!yDCUcJ3Zth*5rXseoW+3rS^e&p_rLqr(!Tm9O2=O zoq*!Tls~3zd^KMLsZK!mW%FmX;K8}>oCYl}CWf|v4AZJg+zTNhw77$2LMI-NdVg3_ zJ}N1g9tZtZM8-!ZkOyD zhy<(_2CUU=b@lrfWmw+&x1b5Z~O_;VRQ~0ij zXoLsPFGr-nBA*YEzxD%Az3jr^8toE&gW{BaYBDxm;r7dHLMM7Tp^6JC+-smkFJ9bE zmUdaMOV{R|~>+zcVi%Pwgx5BNlL-F_~&3^=L zd2dw-_of5r#z=Wkux0WkMre?h2F9R>Eb=rn8w-18#N>a0` zhP^^MSMk@0{{XmM!c--4?QQ(7S??`?RKHSch`SQHFw>*4NTeP`^>{b*=v9yR;9>d{ zf1h~2;vW#7p!kQxS5y2W;J6ki-HSgl$ljD)q9=zb6P4y;(oa(S6XFAke+pc+SzSGl z?~cLY3btdP6UXTZ;$Mb@I3q!iuNa714TyhBd>zFxm*<#u6}O46r}#IZu}f+AxbL$E z$N33HO^{@L#goK0P`sY_6v4g!tb{4Xr&!Y(nlnq=CZhMwW{UN8sS!lw%@AOwYOGO= zwV~o!;u|U6LL4UxD6x(B#){{O<7@!^JBV+pIOeq-Gaa~GT9YUjP=GymS-mMdZZKxe zG#+H`!)(IX9vjW{7sJKf<85|ZQ1%!C;$o7noZ5bikE<(0jglwA#Y5Rn96#naq%u5ztjALetxI< zcjA7h`48fLr}j-<v-vo{(?l*zFSTAS-n~3}EUNdpO z-D@H4w|lL`wcRVV5%*iR9C5#8%MddLLinb>UuDB zD9zGLtF#WnTjI%58$O*lSI~mz(JFHdeNHr411~={q!3$5k`@2mzf$AD3Um+`%Ht zjszJS2By}~U)}sfXqIjG#%#O6Dh=&q)^jRva7aSR;lH=vZtE{{yj%>%20C+F*5M8zw|j5C$KI zx?pzEL@`ZgAnw<+iG}oR$Tg;LnX@FoTDU8GxLE{m#1hTM578vg-*X=9Hr?x_G(GBD z#>HZpdZY_eXWVM?d*njt4g3T{VkMjO2R>|15+_1}hXgomcP4#+nMO@pI`)G}!FR9~ z<44XnV!K@3;`~YJ6c}Y6j2W+lIi0wIytEUL-gq*P+d509BQQVUB*)FJFE**OeISwS z8a=z-k1-HWc9QZOQ1bRaUZ*>|KH1=u&V&!s-9G#%ZxZ4;e}$#N7^fJDU0sF7;@GaK zjm3P|?8ahqVG(>AiF5&*cjM=?PwjJ9CviZ*{orL7L zOOP!?w_|!tc0k<*WT|ro=EBMwA$s>BhPc;~=8j65Gf6W|Xc*#N2O6t%C|H&@fp6J^ zx@`Wb4Anm(RNu1}QY)F1?Zr}@?_3Esu4`-Ym<#>B75f&-O~H^aTQ}KwOS&{2uEp|P zw&flUu9H6O*yB3 zcV8p0;R#-uu;p#4lsXHMZL5SCAg8p2dd;r&np5qyi`APQ+LMC)xRQv_7wHg=;;Zwq ztHSd6&wU%SJl-Mryzs8&&qMer{S>9Kh`}y-cel(QAG<4_=1pIxPDH1wi75x`vs-fO^0H!zfm)X;&ug3!Z&X(-dHp-aQi4eky?nRE@k2Y#3)#I@5- zc^hPv>i=(2-UunD`s2=MIxHL`ge`Pzrjl_2w*$I*u30r$EAd@Xyedw9FWh=6&tZLtx?9np&qd1Bal(uttFI(?eKfdqBEnmYCWX27x zaAZ%I2z4vGM%e2nEb5CrO6TvBlc@O$uTUH|UNR5+uxvLhzSt1B_Jj78%iP5yv{+V! zlZ{RQnK+IED)-ooyr)aJ1Y2sZ$KI-7PnReV1k~*5g0urDgghT=_O61Z4P!t=gFm$qTG z-|9IZ91daBxG51N%%KGM#ygBqM@(Qy8fF)X2uURg5d2>#&>=(|Zdcr3z~wmpiG8ochKSI+%~f(Hedh2*es z5Wl{N9hvY}W9W~|^$A+t+dzzhri>}%kNSdv|B`6TUng%78m= zo)Xw*a7u)7m5lF)P%d4eTszhYp7YuN`3oYrk<}@!$~uo!xqw(PvfMl9dma?CT@v`| zr#=bQ=LeuP4~jg?JS5OII3^?d;0z34o58sq>Z4@t&8iPwZjpi{Ps!6!p13AGWA2qW zmbp)$ZE*NVc;aM{@VrOhppv;51u~6vxw{~`^5njTr1IRAJbx!~Ec2Q`+x%W&+`J($ zVO|&5W?mIIsO0Xg%9}2CCVBf~2s?HZ_@}_%avwnEV2=9a@o05rGTY8g!n4orJ@99S zZi{qITBo_LYpg#pUgtfD7Fp{*o-c~hg4-wh&<2`OxMd$8KBbSo+(_QoY* zPeX_Ff7O9c6!$nRE@hh zvf?xXzN@7MoXSsu6`U9LL_aC313JG@|H8^>2*g7+A+ZnWh2H2cVH19t`g!7WIv1RL zlwT7r4h*s!>u}F`RXiy#+OZwO+DfcJc-{r2@&kpS(&GMPr?fi^gf_MZAl|~_sw5r} zL8Aq}cI;rpZA4sKh6L_O$kh?d6|cCh;+rc>z=;^yn9`o$+iT%Wh2g2nRl*ofhmyVnJu?vkh(iqv0zu8bn61wg{UuT1r7IEr}~p5-L1fk7*a2 zP6^~sx>(whpCH>tOIy;-1m}@>G#bN&7RMYHZQPUK+Yzh`>_|!DBLDYcd1#`juzfXc z0Izo>njNgnUJebi4yLxS&6AewB;C2NJv_*fQhTkE=C`ew-DRb*FU202IO>Vk3oF(K zqe4lbUbv5-4rQDL_|mECZE%(l)=$P~Xv|r#qVGr8Fd4g1;n(H+5jIXfTl`j{U!${x zuq8hEIPqMWo*8EeA!fLaau#sam_CG<&?-3#x_uu)7}b8x0``8SA#6z%_jDHEQ(Jrp zThhgyodq+|*Yxi1EFr|R4qGQt-m_C^i~wh(6>gWsCKP&1#~Cj{Hpn`PtdUJB(wj}? z=1bU0*|d+fXEQ_C_?>b0VeZx!Q<@_z@*wZ9xggeDk$2I2s-KJro2qQ&``Lu7lx#{DH}%VSM?XcG;uL1w zR;lChyJ`A%{7%ir=~z3Ppledrp{tlp(zQxf2v^9a-F-D{nzPvCZSRjTWD>K$&m{I6 z%%hfyJc`;)kVnyc`~P7lNBmAq;eDS6cbcQmGI0` zCPg(w54J6^qd*U|QFB@G)6Nn?&ekrVY7MF0h7<*o>5Etap) zP1-=eZhx13N9#f3=0G_aJY5dtt@|@l3$RAi`79V^0ijENd znU0S)k*=`W2}co5EXUJh!T8xIXaNg2UE1KjP|1S-WuqF%k!b)F%?~DvN_qD zYEElcn#yF;Rb46HqLQIsmGBX|(gcm7zPVmj_V9vay(|ZEJl2C0<^3;N$2&M2{rD-u zwmjG(Pni{)9q)|cU`>-K9kibv74{_^BkYUt``efHF=CtO38aXmkE!T&q>riUoYBWr zb~}!J0aFSXGWVHN%x;arc@!xLr*h+{CYgJA`#`1J=PUQ+MGvsD#9kMFhyA zQvi@fbRmoAVip|~&7v3Ki7aBuqhl2`kg+ipc_gKZJR?m;rAv7y=^8z;V2Hi6%XS%;3EY?5H*Z0b~ivB;u>HH)$`Qh^_GNQRnXL_s~n z_g$oqk>Muthwy)xKN!nZG8BS6f>iFiVz7U@Cp9-}Rm* zQ#;ZNbH2#;<$ANbI$F|9vDh3BH^#=jE5l|n?zpWnG?j5}9v_#X#{y0*^^StE19CKz z!5SR8XHG$3YSwmNKogsH4}}ut!|gX67e-+-}DGf!^FS5_R$@A$ss zR~DG#ZyLOaG#2|QaJ>`?GWO25-+uDsTFoIoeGMmdAVSA^Yy2|k&eQG@ z(22g#iD?LR>IMh&Zo@h)pf&V1sa4=n=g&DKrY56hV@oe9 zCT=;YQNXfzY#@oHP+A-|a~HzR+`~OR=8KmVUnfN@?+m2K-}zn9@7u9=Apk7|b$T5( zirrcIfxcu_*m~v60Hzc;H=s6~z{8tW!x;>BYKJqJE{G-wqvTY&@NcFnk;liios%8Q zS*gbD-yHB4+Kl30l3IFai`Xpu1o`wGc8 z2Wy`_FTypLc9(CQOs)aj5K>l3CM_206&#cPCa-nNoJnLHh`C-&m8HF~CciSJduT0-z-U(QWL*{L z-`^=enyo6!D5MK@7m1v zHM_EjaWnI4wim}B=4Y@B_|*gbnrMYaElJ*&Gzk4Mjh&>9eY-)(%uTrNlg3|D)gS!= z{{WIE@DHG`GF!G$O#P_rC~{{DCsLqU*XVwn0S+6$3NmzDQZZ+?NAs@*8FnYq-nFh? zuY>tzDY35nS>w>-bmy|p;_iGlAx5*fyFFF97u2$-x463_+mO3-ad&67;p}<&eh06? zUW&sYi@V2U0&XgTX0|>{t-6sNmvy!(|c23 zyZ64p4)0@uo!*xM$9U<-NjKKZ3S7a<3moTl3G7OHYbcWOrYTbI?WjnD+>?WpHhOc^ zr^!1|kx||PMMirI70G%hDbnm+q)3Z*r6R3zKM(k{dAywmNY1-SgYw?JinM#bQl!Is zRgq5bBSpq|bx%mFv0jTJD|jm_GR~W*NSC+1A_Z@{A}e~kE3%TeK#`Tba}-&{yIqlP z?>R-rd#@|9s`s%Xu2=V@2>pTAqDYT7ph(f%T#;UHmLh#};Sti+@9nQX6TBl8SKzc$OhilicIl#S7fTUzakrYM=P?CcbX#8yo(jt*ju8=Cf?18Z0bFz$aL=+ zMK<$ZRb+GT6GgW0zEWgMFa0agc`Glk$ktw$BHMUHMYi?URb)GF6GgW7c2#5tZ=NDM zdPb3*ypt5!**i~>UA)T`+10yQks00tip=z$QDiso14VZC(oc&%dw44)1I<2OS$+2P&QN4Oxd977`+JwD&jH@mip=wF zQe?h&w;~664=Zwz_u@}Qv4g#5)aMZIO+`xH2Z|i(C7uzQ!@O=qJg=xo*;`kU!@W%u zS>Wxi$PwP&iX7=3tjJN`k%}Dc9jnMO-bIQm^p+?x=-sS{@g7uU$a_YSW4%`dkuKkR zTk+%2Jb51}emwCn6~~8Q%u75=wF@T_AEh|PuQ6|B#ZM-_q2i|y-%@dYK6yJUej4%p z6+fN$(Ta16xp#u%XA-|$@w15EsW_*{y?-cv4)JdkKbQEZ=R~*jh<7V~KJm2`$2E>I zZ(GGLB)+HO7ZE>9@gEYuSn-RA->&$Nh~KaHCB&aooXdmWn~LKGnwa;#;+GMx|F!6I zIq@#VuOMDj{7T{6@ixhu`_!7mRCH{=!&k=u3@m~}F zSn=nH*S#n@zd$^#_>06_6n}~Mii*EXd_eKv5TB;_E5vtJ{I|sSRQy%qM=Jh1;-@M8 z8u2R?|2^>s6n~xg`-;CoJo%F7_9pRBioZpCMaADHK0)z!h;OO*yToTI{s-dwD*hhv zg^K@?_(_VtPyAM`M}H!IvHE{N{CdUzO#EiWKP3LJ;(sCjqT(MBe_ipv5`SOukBR?7 z@xKxOR`E}WCsgj=i8sD1>HP=sF^d0__^OJ3N_>Li|02Gw;-3-UT5--wc(WA$ocKb; zzaV~^;$ITyRf?z={D}50SO2ew->vx9#Gh20AK>1riZ3PpuHxSk|5$MzYVp2QypDMC zH^Sc{o>kl?zJlU$;@yfTh_6eWdx^$5$xHpugW&5Rwb&mg=V#O>aeL^bH+1f94ivzI zR=AI~seyYrEz>N}HZ20dF^ntuiLE21C;Go8H(e_XbT`GoS`c)tUGk@9gwOD=5 zI0^G7>=VW1pw3d~I{1{2#Rmfxpg-WmOF)l(DcSN*efkeV`ss=%hG|kv*nKA#8Bv|I(0&VlTz_|HLV8VPM5NZBWkT&zFz(I}YqK##m>2i+;KcvBl z;q0vZYl`=_HO0^F9qnxmYw&)DADuXfCrT%PI4*~EI?h6;hHpmiYlIJfo0WIR$3H)O zI$wb|7YyW+&G&BiHXgydbfR$OsiF@5OVqozg@&8Y1>d4Rq81pJ&(@IkaPa)TwC64d z?X}B6`&Xsy4D|Jp5%vALif_9{8j*JNZ@+)q)>{tR?UsXf_Hxh;EeGwn%RzhXa?t)_ zIcT4)qV4#OviL(4?RVMaf0l!`>D9_I{w^KXtD^lb9cC;C?GejCd+u`3-lnu+z4)%Q zJygZ_yJ-JVMf+X(VEwK#f4_^ivx@e+bm*<3{Vu*+RMCDH-(8e8%(L&}d+2h|E>hYt zK^s-U*TtBc{ea%TkFD!rfBqMAP3+JACeSvY2*j1e0&!=tK=d^qDUvXM7o^R6C~#02 z@Q!zOI&@X^=NvywjL7?1Vp#?!a3R+=xJeE$ZkCFF!h91B-atfN zLbSC^+D~7`PhV1C!lVR3o});@)Cydnux6r|0pB5+U{xDTm3x+3zN*sn2gT<2^GH^;nM!E-!E)Buc%4$7PonIGG*w z9_MlKrA8CX)N&D~)<5HMF{WlD)K%|sJ*Jkv!Q;A2jW(F&7|eUry!np*!+9+zus z{YQITwt0bzHj!k>5X_3Z?{SS$S^|!>o!FjMb=m&lweKj~d{DMmf~rP#-n*6FwS&Uv z>5XN}s7Bo#-uojl>GZxG$$N~q;_pWgIo2DXcXc7I;O#z=_c*USk~hlsf)V{Q-pwQW z*L$yz=-=RdIHG@}_w9)OO%9}Q#|7dR}{kc^xQnpwN@ya9mH+yG|$fw1- zbwvMG?^XJT3o-tv9xDf*j_9BBM!iu>ufu)!j}cRoJs|7{aBd)MxB8{^vCJfawiyr@ zH>(Lun27?>ZcR`mVOAHU&GZQzR0eFT*iO)?s7S)h5Twm)FK|#9xR`9BJi2P+;d;3+eY;C6%ghpJo7n>6W>0|$vzI`~+e49r znIlM>*-hY}GH@}WMR|19$eSFIw~xfK%mD&zGfyDSJ`2Ph(*hxHUqupTz94O8Z-Il# zz{R8)<*SEm9<5P7`bEZJsoFy=B zP8XOkX9$G6(-cXVvju50rwANW1}-KsDUYridFw~yohPv@bCE#Yxi2toE)bY77Yc;D z^A*7@{DQQZa|I460~Zselt)*MybU7qE|pl8xk8|At`rzImkCUm%LPK-j}=Lns|0B? zmk1nG1}-LJDUYs*JSVo3c4d)qN*`LMWsOt9uI$ETH(~FGO;|8)o)SsV)uI*dhZl(Z z;RVLc5`hVGtw5yb8buQ3dO_OEVu6Foz{P|z(?eH9dLD^a_sx%gM|wJgu6Y^rH5BP~Ic`taZS#u2xOqun!n`aH@?KOV zVSXz}n|WT~pfYf=&_{W6)yUf@BJWO#Wtn>g+U7oiadWr8gt z%Q9~Yw9Q)rcHh`c{YEX({!plv=7 z7&m_um@w}PguM3@Ntizi(q`TjIH(LIXVBCBx5O0PF zguK5hk}&@eq|N+A;Gi;av4Bi@bk)e)EFy0$iDj8}1=?mkfpN38z=T;xAmmL}1TS$4 z(q`5aIH(LjNtkT~X)~J%98?A_ z7Q!iyt{QpR=No>1O_f-d*;t@$HW3&%8wpI9X#yc{Lq!s1Q$gCy6oG@vz{O%a<>Qny@Yi|M=S5@}^Pwz}-CX-3h$udckwi601 zm&_!EMxYDOvMMSH2twHxK>_9B1W`LPX;HxqR1i1BqKKk`8@MYX?z_nA>q_Iki{idv z__F`c5n~V_tal)5$ zy7Df>i2N&b{vsWXV>2bWZACu&cN+D z4?TS)$u;gUyPT9Ll(nxZRQj;eC-jtb5LrCLV1@)(O*15pP^RE?x zR~v%H4FbH$C15JR{FMr$mlSi2_sDhn+gx(+50-1q^5W}ZGXF#lph`)HunWFIP|8Hi z@LJEe6V_FNR*IqJ*;r=2OgR4)qJru8cx4pL4B&ZHUrDZmSMhnyHtI+z+!zHn z0}gMD9L#{jO_75caClSXU(sQb@026wx7TPYQKVeU20Sz3_7(CSanu!KYKW^tf~J{N?65#@FxQ`hh>g%*x{( zyHYb2P2|gB2AwN+Z<+X(zPMHTLAgcErbW)Hy%&e!xkbBr%cL_g5WZ!iH;LXzIi5%x z^=dog@%u@dXWz)kWU82-y~0~EGP-oxa{nx4$x>Nj9%m+328R-=8{HnkVHk9!Ofin~ zMjFaY{85!Sy&UqSMRu_W;#|7QlR@YGtnL?E$r=ee`G1R(SZyf-A$4e zGAf9=pWr%LvYzouk4yjVa|w6+g8XKA&1apWxNa21aldIrTQyh+{~6S2^~dz}q57p^ z|4cEYqerr69Ec8A@vT3APn`Q&ymbCEBig5Q4>@tdPQ2tu=U)VZB5g{Q!UM}0E)KRD z*~nCyz@?KwtmLBHl<|Xb+Z0uc>qdL*WT>Bsxbftw3 zgPVtRQsRkC_?-w{lddcEAK)??cl0Jve9f>|S>^dF$a96e^w0aDw5`b&9ER7b6n82) zY-47fFLJsGp8_t1U$*^njk58i*QgjwHYN-XHpUIsowv_%Kk=$;Od<~FxYsX!Ma1>b2w6o$>!e* zVpyZux1177?|O^Hd!+`GjaM5?HC|(|qw!jUJW6V??t=RdIP9{8*rT$X8pC~~#qk<9 z8B8{|8)RA3AnT+CDa*~y(9w9a8CWMZSa%3~4s@z4vbB{(^|RTfXZ*3wVlfXe$UMMc zsQJb-s9l}z^6@&gg=-DJ z*La1&WaE_v2OHNItUGW2M=o92{5OcN!*r7Y*WA(`>4r&I8jq!{XSg(8rLx{`xV*+4 z29u3<8)RL>;9%oj2J0@E|6`YyY&*U8dS(pg+boXPc)P)5<2Hk-#yboSHf}XocftIh zIGnOIaV8TdlTZD5!8&@XF`wn-smCVmX<#SR6{;_xqZ;)uhVhH`P+Dc*|dV>yLE(bJT9mb~Lhn=1ijj&kq_*HqJJfYMg7Z zqj8Qw`1m|$=xCg8hQY=e2I~%i&jDs}oou0|Q^!kcH|NCC3oMq`xXfU(@oa;sM%7?P z<8p(fH*JQ&#-#@9E}s80myB%f{o1*)G)}ZAUgIeSlZ}%MrW#K*$eyJ^(m2%_IvS^# zfjvuub%(&`@U_Y$TU(hfrcCF#^laQY!eTLJHkfQY)?lh}q`{8H(FRHH@y^iEc$_n& z8^@YquyMG-y2Ipi(pw437EPWtc3w}q=aX(Yr@IWk;u7xL-~ji0+(^3m;ijcKUHwvI zsYF_Kr64{TKGk__GWb10NNH1WBdxWvo`*s}Sp5N!MuRuvG_f*t>iu!k30cQNZD=}; z4>6l`b*(o2v>09Xw?cW10}Lh`YYe6uQw9ecs}0s&F#ngX(6YtGW8N&;8U%>?xJQ^PUKhhEpkt;Wy-l=J5_EEu4!qB<-lAei-C1IE#27ox%>Dx{~zPO z`TrRIExU`))WDTFxvDrG+(J-zNbM$oPcCe>o3PnzpxG3sd9y-&-J+jRqbPDrr>l4D z-!zU!uJ^qOuCYTt{#!vC9Lt#O{~opuZpF%*kmcZQ81mV{$<9nM8(a)QQB`LcLtSD? zkQXwQ{&aOigcW<3=8-L@Z0K})%c;|xth4anPBQesLDQFs8FyQ9{&MDU7B1<(0Q&?J zo#!_LB^^>de&MEmn#MEtrc~G)kb|_wn~6lAomyW94Dd!10TE;&p*MUt#z-)yj26@Dn8N)K0GO-*N=>}LzU;~A6D&|;K0_~gXC z2SfYwypiCmux+Jh=98d7ULOeFqmrD*gIb}-J#8KrMlpJ4KIJ?*27>o0O8@>{dI#A4 zqCk`0$k>lETxxoRyx4QY%m{i?Jsmyio=i`6`a$H>ftke&!oeSCICckSJ?&C|#jOW+ zCQ_x|Y2=ff{|9Px+byJ6+&UrWzL@h{aUPl<`c`wE7y506WK>A@k0H^j$k5h#&RQiY zZrw+rSHwcU!$QwNe54IL7KTgXzzr zpp!Dq3#u9z8T*0g_|az2pYI%$;jO$)buzvpRSe!|{HQi`HngPcVco(D6-k?BnpG+@IF%eIcSljD1K9Kg zjT5N^bL7M&YFGZ1RGR011%ye{1Jt;eoF%s|gOE!d9a$qSSH^lx3DZ)ZH&-XKbhH00 zIC$|Qo@}@#Vwg*h<>+uf00~!alaK#rIMDwKpY%}CuaiP~txLg8&$EWV zIOkRN+HIE4B+D!>_N37Dxdkk%k^_yutH^cHX656DPCco7en-jW5CVx1!5b+^vi3I_ z(I%F&2skKShhGCrvMx`>&Sy``#$p8@Cj3-i412QJP}rgIndmjQZq`iHGy5}O8fPT% z8#0JbIuf^diNv$iu7jtNiJWKE;q6o%$>243dahE=poE^IeLS2hCC`Ir3K<+vjb$rO ziu>DMqP7q9HeF?c7wfbsyL@VIOxl2*7{TyT4E?BnFAc5f;=5$d<7tNSqN4kDr#e;F zi9bdteqmE^U2qV2H?@)*x4#u%f&qqEXOdo6UF>UgBQK3nNQ75D+2xH>2F zfgiBX?g-$YMbV4ugNplS34IuXM#6Ajy9NoTKZ}yIwJ2xh(Rj(UAq5JaH@m5ebqjNdS7CO^~uHX*JbxVN_JSkWzq&Ci?n7`QH zjRL4F#OppaQ|JyDhSI?xpIZvu1Vl+yp<~StQw3DI(Q&Ie8o#D=mnm^6{K^oKFRx2x zZ$u&0?qTU_kRvE;S3ukKnqNRZYw!Ee-kBdo?VXL^ z6G0%gbj)jbx-lKx4wL5w^3&IXquUQ9-azr)$mHVE}+#O+$N5Ra>l`*YR9U<4FV z>JWoH_TJxBJ$`4WJTGv5R80bvTf9w?CS$rAy)A@q2I|KYEMfIP3A%#k3qL#otGo*1 zsu0JLsS^sBzTRcwLPT%%ADt1?_7?JvJ$tK9YqDoA+;2SFTYW~$(RkM7H&gE7wa_km zYqLBCHN)gmg}i%z6Wv_SGg-*xBc_fwpN$W~-j1r(-;kT?r6g2H`&?kpRWDbfXJ-ZlDGID6Ror5p6YvCTHKPr?sW!dl7^QJ&IZsDJ3M2h>FdCq3U1(w z%-)gE0?K8^FD^?c_GgfgdHLCxnf0F2adzeGWOvxtzXooe=*Uzu<-ts4MS1_NBNDKO zP0B+(=j2w_cw+YDTMw3fW#r$pwG_AO(GX2a9X;I`Pv1d2={VJ%g|;Kw@jYpeb=p&c$L=yIE$fqQ3u?YV$#sb{KyV?Y9)|nD{iA5|-J+3Vx*CIv#bEb03^Fxd~tQp9@Vk$Rkc6PXB-*+lE zY<5n#3KE330ZljT0*hdM;F)z6ESO+3hEeWY@H zV=J>{U}6eo<4IfL7~Z^k6%5RAoIW}de1@?Nhqke)T(-N^m0$Y?x((v3p4uylH?(>6 zU@YGFRECh<`Eu3Dmm1EOpMhz`RBAL|J`2-1Q>o?o@=2IBQd^7jym3=L2HPfV1+$6& z_BJ%^VxhoV4418BTRc}TLZ6pWDHGkWxhO`9hvORE}JBJXWQ8aZz!J~ni}A=2us*GYXv+p zL8M|nJApJYgi%BLOUV%DS6crxPJ8x#`3s?;SL$>xCu@wAfPMT{@Kd}? z_?4es{T&?ET@I@0UkX0Gx{txLtH&5TC4^7z>nQCNa%D&Ld%Y1?GB!0+^#3OQ?5O^; zI;d(v+mrAh^9T+m6ZP()MIt=uXGGWPtIHFyqgjp-%3{y+aVXq zFbV7SS__HJ5w|DPhGXpiAC4+-t9?xaT1VsS0?EcV+J-kdDs9c>Ukv2>a$))t)NamR zLFKX6*TJO5$3t%EIPax1+3Ee5LWh3Zn=W>j*QYy!yNQ_9YtOdgoar1sEznFyoFWSy zhpdIvX8&!hAqCe!5qr2R9ek3QYwqUZcCA}aUnkDH#o?EFgWSXt9`W6aG}%wI4B3YG z3LMxw{E1gn5j6uGEc5i{%}9wW2dBn7U&Hax*%L)KpQ=6v8pftiuH4DQFDdus8rNHR zkS*MF>U#WmhccQ@Es9O2&;ky(m-`mQW>bsiqlf~v=2UHgvTkxUw`gL4>D8jx^eTMc z(xz7~m86N@aFaypp-D)i@{XK6Fpd3{F4pUT*}6h3tAU2yfv z;*#w%e~(sR{$?vMkI@RuXNj_Mn|!ACro#R0g#7lIxpo8_S_tG5Ly+W%yF$m31>%`cdhG8D$CCz5DaT$QqIsbie&L8xQ(VZfPCKsz=94vb+6!d#PWprb z63zUG_id(`+MeeU*JAIRWVWPpY`Gb>yxh{MYq*EDDn&lAO6m0OW9_ZIR+n#|VcRbr z|FBkk`NZPqy!#Mk+vAsdx6gR(2sXA5$R~!N%QJn%1^0G=N?H9FMQ&%0f2y`z2$87z z@Ge3SeI9vB?<8(+bO9gf0xooo%=c-jbQli%!qU+8l|!ZC_L;vLxA(6am`i_-jxg*E zXMZSDqGb)p%nowolFeR`~h39)k*oBQqVe2csFt?btKklp&8xLe=q zZhf!Wt?!+?^SzIa*!DhaG|msJyw)Sw5yP|v=PYX3Z@V|sf=UcuEY{g3I#h-E|hsRg($j_Dt$|;-N#8=(1 zm!B|Qb+b?#BXl(?oQviYYmahgUbL@#1nEB4($#DrX-~cy682j>cp4RtfKaWCb0? z?2xC2rjqFjJB%FqHKBTZaGV{FQs=lyrGx5wFXo#G+w6 zHNp!msiE+?*J76w7W2ZSGd5Pb4X6PStm3zt-!gup7i8vvw=oUQY+RIW{Kc@sqG4EK zqdSJhHk@L}*odixj`2N2E{TeWWovU7R+t2ZVTlPz7#5oxgdt<1jv=>76|INIZ9+0$ z%8i%u(7Q!Qp6t<*;79qfykPKCn7_e~ zqX*hfNxYOF*9j8e#_bFIPR4vbzti|>mtFRy{Fsg;NS3c->OtI4Gm-c@F8XoSEFoRn z-TWlv{EXlC_;L1C@%U~&S}UkJ9-}J-Pv^%>xb1f-Zj9)OwPO}Za@YQ&o-x9P>u~ps z70kHyKW3NMyZLB#hOE&k%p3S!ZGL0yxlA6g_FGt3xo7h+!G?*VX(vSmVT9eehMq{PiD9M3C2?S@{z_= z`SOOP`SQVi$op_uHOn$5>)pB5}?)$2q5|el++l zOy<*^%k0R#fqrcI*fwS4gEa04EpIMWErxBAHhvC5uljWr<2|H4ewJ7Leq`~gKZ%SC+toicEw;{k zIC;{a=T}(9nqVFpiJFO&J#N=jwvOdLevYV;rBvAqf89)+xQ&2Snicg>lUe%z}GQDt#oW^QnANX8pj z0^`*YitOHSZXU<$T{PrQ{Edlhr=6PXT{P?*x_TEaai)Bj*vS65qT7q@D04({#QApj zE*f>F{#5UxrJ=DSjI-q{9D_(h^6>|D-C}*(3^>@yeI7DHuoYtG?EA3!HAvZls^Vv)b3$QEm4=J#Rb zuPd^a7{?Dv`m%hu8JjLH9{U@LZP7+%{t(9gmSSs(SV_y`B2PA%`RTFb|5pes=E%%L zLeP)yg7-t=dW=WAW`3y3)68i+#&|j)#G`J;72(lR;|f?XWGGlLf29nbIbDNYT;^JJ@;#Ny3N$kF z$B;0yB8(QFMHpF{RApp|Q}u_6Y_UdW{uD<3nIdc5Sal-Hi<+??X^;JL#kOc8Gk*?a z|3pO8Whc46GEY`@(-xT>Ec0nIjY|H!l&=zJMc_@{*7G7zR zr>#@E__SWmVgnl!9t|;&@unV_U{Jw_yW4Os(=2?D%4~FniproBl zOqA2vhoA71__nLx2Z{45{MTy2lM1-;o34yH0u4>|r6+p5iL_tQ2<5+*QjdER9chjt zb)yKNJnTOi4<0x6FEQBTf0H2jGWV|pvpWSoG7Nln82EEx;2$p(_}DP;1!3T?gn@tQ z0{j02awSAZ{g@NE3}2d@Hz)Fo#1F$UFCxTnJgh3GIU!ei+;&qk3V8C?LQJ`R&d;*Y^UF$VZ_a0#CsG0`f-23(Jzyl zUW4CwYWjo1p~^T}cKk@Y~ceppsNV`i6^ePre(@aWasRP|~} zR3D~TeXK!f-sh$*sp{)n9x-uKJWbo9{LJjQFX661XdBcW{~DS3N7zr~Na~u+>gA6~ z`m*>xp*6YALT#M^Ai**9g2(j%t^@qkT3@Ew`qJ}SUl%yG8TOHxe};+lDv|S)$O><0 z`e)#XHO0k>U?E3l{-p@k#~ety+tePFZs|$>&y{coN0}x^s~=ODcM=$;-IM4pl~!Lc zAmoO1Wai&Ot}?abKB|d(A8_Y?zsIYa%AQ|VT8S! z5tsv!H;(1oUuiM=y;k-n!V&bul|!fYCc=UB#FfKX!twURl}oUMa@~n5N3et<;E5|o zv4oQ0i7S_43FXEUS1!X6%DE@5T#jYA>#R@YfmLj!&ZaddYp!YSlC`U8&C8l^TDxWK zZd!X}?P*$jW$g{ErNz_#p-RWMCkR1kut^* zv2f!GX8}fLI7(!7v|1$JPeUuhzbsAR|ClCGO8aYy5*HWu7bZio(*$4$=gc+% zNGiDtIbUIpH49dgqyHFCY2GsmPOXbw37-$-yuL+!XsOZ7rFAp7W=A$X7Z5hJ(Tx|I zC9LSu#3sF8+?&uD^U*@54XvY2n(E^!QHbXt;Thhvx`k)?Mvn9lFXSum^fmD;5AlRj z8PBRmcz)0D1D;<3G80ps6Z;oZ6P0A!1V(e0TsU{hg>#p)XiVYU#SQ)(B`a|Bj2T2x z=c%)64*?@g5%O1QY}1R(L8U5nh1n@}h2`pK<_E10%Fihl*vek&*{C&-7W~`h=@1hO4jhGaPq7dVRSh7%vtx<@5cM;+rA&%u~lb08^%JJ)Y<**s${Bm@yVC;It%{P-5Af!XHhBWeF$4)JG(fyi#jJPA2*7qn6<~rD4cNW;(+( zPE;Dvlv$ScGF;s%!|xsy|5JqD5Rc5{Li`VNAaCax>M06m0Y+xJ!fXL?+*L>)N@QUa}F>3^}tHy9?w650PNIW;{e))~vk5-igsr$D7h& zDKz1e&xMgCgFk|2XHAn1Pc|`Ba_3{8I(;SC*{1AF|B3M1lo14;=|5xHf-)L;bK>wXH&W@riuefMDR~*lIJu@oI7j_QCIR)&#tKHh2ol_abE(T4;-%H4tX>lCbZm4Z! zRya|QyL+`mKF73#zAE(U)i+otRA?--=S@E^d5jb3qOuA%&TosEqG_4RyF4D5T1 zvpZg3Ih04YR~MKYJQ9xe2E$n3`4@P?LNF-j)|a_^H#?jzuI_iP+dMQUv_LI#M-f<9hkSf=lB1>q*>oZ(?OgKV>=TqfM8fM!(~z1g0#|@3DA6zhePi>EVN71bjSBj(|_Z z1Od-V4{aVgQYFq5z1kCS^lA#^h=|(FkciT(A|e`_8Wj&W_Eb_{A=P-2VEu7?`#52p z5*a^5eR?%{aPqe<7^9=&q1;n+MCWJYIIL<|XQYQW+(%}-nq1-;3ZBP)Lovt@p+Cz- zpR5d0{Mm_N;&5*Wu6LQqG@h)uE;AX=haby3rYtk^=+Ag{!MVX-N0n-s$@n>8x@v)> zx9mRY;oT?Am`QmaJ?ZrsTBl1r=prlm+0$H06)DtG1s#VuzEc&p@m;5w%7{=mKgV|i zMqNh_W+{14&+hxEA+vrr0b#Ag;ZGDFx(#s6vcAGEYTt6#c=!?Ae#$RoR9wWZiNtSl z(}|D@=8$j2BdBS0$Tgn655Jci-yV+_e0w5*c7KdG@+p{uUIG_`#e9wZ3K-Ow`+yUN z7#k`kY;=-9{RF;h%xNlq_EfJXS6Z^b+#oYR*OCPob=UMHE`LE{Nmw?eob-p}>)PZ?ArQ-_w8@k4qHL!Cc50KS;^f-oX_#n} z&$Kp)Xq@nhjni?XV?F~QdTf@prYL*bLY1&u=vjjGr}9+`rL!&X(0YJZlZRTUV625I zUaWW#(MVj6AsJTNjMA zP|Hl#S}5(eCZbF%Y6GnKYOlg!6KDfuwc9etHRq_a88_NnW&pz)PjW#E-;cC*-*T&M65ZvrxCLLh-1rU zd;S6iS_9G5K6h@s!_>dY|&DLo+XWk<06aQ9dlOxuW7&B_om6lw>4IN7iWh{_$SzX?UjsE=;F` zk|-E?9hMAkG=b&<0=NatD-qGgpXAjp#3dQrgzso@rlcpWKXM;Q`fTWDo{l&D%tZk8 zGsk#-z~p@_kyxAvF2mG#CO-5l&jMtM4JI;TtV?C{UkK9T0sWcUUbw=q)1M?`=9fs@ zmdvv$Hd}^?mnP`|GBqUURB%4xm_6;)2!wf{ahX8vV!lID&kh4UjX+K$8>5&elI^8R zp#xqavO8HKuNl1Z(o3BNvz(`~ze*kxa3`vp0D1<$Rdn1tgfy zuH(*L!nt@gWiPl8cS>Kyyp{`tAeFJm-O`#7rY5@3#S}R6X*A-3Zs$$Jm{u@u!fUkZ z%ev6?#|mCZpE~_6Nb7%qPtniQPiAV|piJ3B0A(OX46k+xVT0q5_@$n%=gtX}N{G4d<1Ipyph)dlAUFO+~O!QY6{>F*1su#)YZ zhqB2*{@%nPn-DTw?i@uN0dDl_Dxg<)l_ydZExgYe&T@ZuUw1!8VjXR`#+*S*#n0f! zLT0l+zz!9{;SKQ4x}U1+YZAri=kP}q6|tR*OC5b6Tdcd>CGm4NWimB!DfMB@S`)9X zd*gpp3^_QqJ#+m_aq-O4xnCUI4fi@;X2?*i;8VDS#*blKce2G%vDST`dUakaC%RYl z^*U{oh2C<`S5Q+&+27OG$F!r1mG$`$c7rtS+ z-UD73h_}w!6Bw4BXdOeC^Wbt=H>A>@l)p19KgWm~^z*3)X7}yq()XOp>wxQIT({!5 zw@`3X$h~$K$W17t`6Sz!iEq&EefqW&Fh_rqE1!j1jo-eQ!*NLcMQlEqL>qr1ff@t) z#OL5epZGiga^)pn?YWq$pX>-%i72-m{Av1zhWfP3kXL)YT#&)SZp&92)hh)1VNka< zuD0rgPobyV+9FWDg0H$QHX_QiPw{GUrOy(W8~kI`XDJFuDj<>EX%j|6WAUYT1s^AO zww$y6B(6=l^M+fRa;Fky6HLCOfK06esjlobyeRt&fU-Z^t2HoH?~j#T4qk05-|1?c z0^4xeLkcJnC~&1fz0Ow@$cGe=D-;ljQ9x04rhuWb_-zzuIfoRWvtta_4CBUkG-;q@ z+i1|L{}d#^{kc0oMcCHLu2vVO5bwcb^R;RSlKfP&W0p7W&-cO1TV_|GC)^)-C+!Q>7fm`bXG*o{NoFDSvLD{U8}0n(Pa0H_|gB=gFUMcc zENyO`!5aDH(23L;OkoV}WJXlIlS`uhOQBBXsZkvo6>2KC^lmL5nxoqiwldTkW0}k zPx*gUz#l~gELV8*3p!Qyai~Qv|6Fmu@sO+o)31jT!A~Jfq*f~?7Z!xi9j+kK1JQDuXzBdgWipJ{&&kfF7-P)-%uRKm1mOdL>p zT#Or55HR9~KAVn&Ea6TlSsxKbFFLI{o2FLaz^-_@vc#)3lZ?_(qUELYJ$w%Pr=6a% zFgD|_#l8cN5To;VW$G6>`Vk0Kz=Cs&QGMeM6Dh5kAE-t#P<~q9fPavTgM~pWN^#9V zXo^K#b7*Nri~t{u5x~WY+Qk2~d}72Pje|*J;?=W#)N*&_hw`%YtM9jvku{&PpWjV~jbKrQbxomdiXzOpj1Znz*`q28hR^=M*8}`@9 zQ0y!E>y-;Pk8u4>vGH0Z>G~T~>-Va^`KmFJJo<}XU2txYV?1tBW&KUjNBwxNd$ve% z3T-*#4quugsfVp}h3%95=v>qUZJanC&dby^e60T-tk&t|@(o|ZoblD0ucw!1de$0R zTnIFY6SFi|+Gn)cH#wh({{Lyw-+4&GW9BcW;gK?g8Xmio3Ow*B74{muh zJGzItj}Qj#6u#pezEfiO&Td6>^?o?@w#C(?&E-3%Z=qcI*+cr5NYFLv54bUQz{XP= zwT)f_ZZI0KN_Q1gSmLC(B%GkK>B_AQ~1wde-ZGpj7>%7Y+uRXmt?pL$nOx$p$x9^EGCDf5+4D{btZ?r z4lm@8Hvo`BHjYQ zaz<5?UiVhArFUY}-7`nRg3@D$3RK2W9jTl-3a8GDZ)jmi-rs`L!``_=y|a6_EYsrQi~98Qw(-$T2rT6Gx07lw%NHVvZ)xWc0{adce-JHQq!F zNDvlPJ7gkdz-b53^FC$Kjt3$Ad=(Gp9R}hkOxna5pBo@u*7Oot;A z&rI6)jeG-EjMLh12|FAu>{^GTouRP9(SD`s8I5I)n+5By=i7(2rN!!SG~V#YBu-3t z>VmNjNAbX<4hNE`fce>H((kZSsjO&d6l|`Z9>jx|avyaxX7z-*aJqtW%gfnpa6554 zf@x91lB5QEti%%M)bo}^|r>}a?bi2aUGA^6Eb9CFMg1@tv&rZbeAL)Z)fku z{xbXyhX3PhtGuz6;1)~b$+&L-Mt^Egb=+RYZwcnGJ&Amr15!>juS?nK^y{rXy%{gs z(=7nzG$%8MeJe>dDqdD`rhWcDoPiX0JlT?B#6&^|$cV>;>+z*{fWcy$HmFQAJ_) zB4M;Sdo?r`Kic9ning4iZO!J~ZRVQh+)c^0?Pcor;;XHkZ^w&rz5}53%zZNTr_*$c z{=1NQS{uXf#M|wm+>Qx8Xy4a-7mi*{fh201hObB2*Hp6FHF>vS{WiYZHDUWJ+Sina zc1;9hq*1)suE`x(qg@kv3)o*SUagg(_^!#W_BCB*GL82ruFDK7(*4TJ6b!|qK6q$f zQ!ut`Vwq{}YtBnLwjr`m(pr1kP14Na_9*FaUsI{K?`z6M?b>`+NEnu!;nh`zZM!12 zuW31Pe$lRo70Bqpbj4b+?Q8BfWZ1CDd$h0lIn3JE)a~+kTcr)!zNRj1>^%1U9k&j6 zl&&UftKyNiwo3gZZZIBxFJAEQ`vJ7oU9j+-_&OH;ASPH?XB+Oq=+!=8Uj=aN3y1Cg zY=aV{O@BzB{yx5H(=60RXB*@}vlfW)u3|Ntb$GTxd11dpIv#a4BMW%}h{VLD?WIMbhVlKA54 zWB-l{nRy|Y+}hg(L**_fxSDWUSzP_XE`!^SSg@Equ;AHzX1>U!5C2%oSy^3O{SU3d zS=z!J3wjwrt53k&X~1-%Z7XCf!z>(p4jOp1&x;bAUgyZr<3X<#`zHYNyx{jlV7TEZ zVtU!e7X<2`;mdgfE!LEGJlW_gkGxkGm>c|iG&fc(@Eix8l@rg4r5?}ylkoMgX8PWr zDy<#1d8}&U)hdOn308n!ioetBzNq?{HlLTod|tG&8OO~!Lq(cKs_%+aPbRV8olKg8 zzp{_h-%)-%DtiAWBeGyKSoOImpGBGQ+X(2cXQzX=E7En$VprmEt1LTMp~K7)F>5k1 zcLaOG78CD|&5k<4wW}ov9ZYkLj7C2+7q?Cdww%M0f{eM3p~1uFIzw;@P&Z^Exf!c% zsO!wktN6v_U{y_QZ>J-{l5pa6)*ily7o7iP0AuUfjG13i^-y02P+ur-^_hP{g|Fi3 zAKeuGj)!9QfbbU?#^|{OuF!stTp2nA<_7;6v4o<4ZQ96kykPmEz0R8!Dm zIQM&Ul}-%WKdrEko7O@u+O5}VLCWFuYC`4J{>Q%G;v1YmD%vCu_s#Xdo9&xRa1Hm( zHT@KMyh66|ZNd6C__A5=kElt%40?Dqd1$Z^jP08%9+>up z5ZVv-Pm@90_dNF#t+u(;yBgmCTbMd(h)0z=rvgY_9{pWjU2txYnV?&3v(&qM-l%bB z8ha26Lh6=DIW!(1LUVm3wEx#R)L^`u99|6H{J+ZK+K<7AVO`c$mm{75>GR6<_xAOM zw-){LIKfh!){%)edfV&oyRYpf&C)Db+c%McJ+JMy#Pv|~qg3`tGw}s0TgOiJh?F`| zut$~c$hUl9DxSKNbKJVPZ*fTLkyG8pwf_UnN1Mf7kmkM(ntR&9{#%+aZ6>}T&DnPR ze@AoI&1Gup1E;^s@Yu~tAnQlz&iw33chbRHk}>t%;7T=Q>uosaz*}139BAPnSv|-^ z$A0)ya?GFBex6JJeic6uMe|AZT}l6J3uB+0!zapV-p@D$T++uv2*4LONE~CPkVWX|d?$sW!?+(6!HA$O!?xSu>UAa17 zjh)v2x+7f58e``V1?u19tFd#bI>1$yMR_oM3dDL}#R89dU$@!d)s=qyrOwc0@Uhf# zl*d@^tj*f$q;=356|95S>SU~gw(PMw`6IB0`D1l*neulvr1xs_(CVaMtb?`;rmcgP z2)SoX)~FloO>croU~3qbype?ki$kD$LsQ-koTsT5T(_a5`8BG#euhU?e+VcotZxbh-G}*~vuzy1fI}9cgNS0ZrWo^t7XV%@K zcmoYKw?gTtvNdAX-;je$uvTLWZ6_3C#GWR`Xjvd8#wgcXVECP2{a1Xoz`(t^@{U=O z@M`kV0)t?zAt@f1OpF<5NOtpTzs0SRVcnj+$MlYCltbs})s!dytvH8wkko8cy>_v} z>i00aSmCmhX*@{yFgq+jj;VrA^lEa|>Z4$+hAl6dsD_RI4UZM4jsIKDP5zJdjp*Dm zMSC$m7eU(U`%J>zWcB@fyr}Po0Q8L)P}#pziLJ6L&v($P{Sj}k_9woZw9hyRmLfSYhx#3)KI>R}9Xz)AEjASarw)9v6t!hGK!oYQtD;o%RGALj>#Kgi6D) zUR#E~t>I?Z89KyjWS1Gzg@;Ga&43MNG(5ZMWyq{p+<4%8LwM7x<*Y*lnu@fISu3D9 zduj3hN0dqW?EAWTOE&RJ#gDeMt=paK)&7Rj-^R3nT`J8P8V}>ZnDh^T z`kAMJW`gY>IS1XENASo|u2|0CcL9El|0w)GW6ps`0O2uZ*7%ib;+-ajS^Q4oS0+7V zX0w)*Z0(;!@l(Xw>>tl$`I-KOpq+lYa2wM4zZBr@h^&=;yqcUUOUa;Ioi%s{Vf~8~ zLCJeHeOFG5ta9EfCps}HcscJYXt~1x4cAJ)?sdjIA_PrRxoOMZzs*6VWd8I8`W;PJ zi~hwRvIcsI3>t@@iGlro&3{B@oiE~=DV+LOY&u^=Rh4(7Z3ZF_oi7rc8=Q+~Ac_a3 zOTm=iahJ^mj8MNJ*1J8wg!^1v= zhPwo(dp6x<)ukX_P1VxZGdEafv$4<<9Az1_i=51ETqmkoqIIH&dzyN=40bnCitnnX zn>V$U!Ap!$<%*Qbxp41d~+dOA1N@TT~2&=zM08FG(TT#1DZWzDix28VO=Hpb!$ChTRSTp z$Mr=jb{DvKC5EL@J;J#Zv9!N$tPh5)J`HoB&z7U6?KDM9)#Ne#FrJ#-ykvHh<%xWu z-&9l%>J8Q*r=fCQ_G5|nyC=zG>F(P z5%zCa8mU~jkX%J`c@D&m3e|Mb)mt^U_TFrEvVTx=kulCf0UQ?<2K_5QI=kNyVwVC ze=87_7XE6?zvs6Gd-SI{&wh*zoR>;WVirepCs_Se(&rG08$XhXPvN)S#*Yp-8p34& zUFlVH=0|6SGXC|98~!a#0cIECsp2wvX9enMz8bwzrcj=x^N}k2NvYSbmw4f*dNxd$Sd)leHwWY*Tu*>ZM6r`zu;B%U>s=%+~h^(3x(byI#-o?_!> zGVxL9jkt#V+KCtC?gFsh2N@F=@q0c*XX_<_5>DiaZDefSW}J2wbMnOUUk|^Ul;bYo z>~8j^#F)MH2-I_YHG5;?R^Cx2LtafDbWj2@F-Nh&*&DL2r0VH)&}e1GW)%%hEig-% zZkR-!scn&2pOr(68X%v|1jx_n8O|Uv{X(lyAE}DQiUx>@~F+4J}vkK^@OZ z}%MAiv%mCNasn^gC2rA)i0x<;;DQ(e=$ zJ)rt*o+&G{TIFpvlrPt5OWO%nf#xigEzv|o*$XEkNV}ojyTEi_W7e*-#**ruWNqz> z;MRD`jvZserN(ocb7(vdhfA&PS<;1EqacDiW@J3ySIo!>Q__})#&f|~%QbGyS<7|l z4wxW94Z7)2TjhE+(ahfV9x9~6ftB|j+Xq#n>kK8D7ILHWyMejm?G9 zi3{+5v-KT)c+qzZ0GRuJ8oW*t3uho&)%R+H_-Y?Bz2Fj27<4H?B}EJ{f_;s!NFS>* zHBECHONj5)^qn|i9{RTEh#ehHg<+usvE#E671(FXX`_=#z%&ACZe@SkX3uc5A$Ix8 z1kLTV#)ze-Z(y5bFOYb?-@KIDA|6Wu1Nj{lDC)A>-p4pMuVzSJMxW7_44%&*)Sn9X zykflr`x=WoIfm!eie%yJb*fdbHiVg$-^^8L)c#&1{0~?{@I?S zli*cp9yk9+1?&BM`?&eX(rS6fD=aN}=;ohbY*$k8I0dI&$?ndH$pr|qQ0{zoN7H4F zj7xfN#uWdKT@$8D-1Zx( zrMh-p*-#pF2LKR?s;@=LcH7QL=j~^7yCYd$y1MD%_Lg5rbK2rnzem^zJ08L?WcCR#oDDjvs$ zlJNcu_@I=(Rkw2^tJ(rug|{5ttmfO)otsO=Y1!v!+2v>%yb#p0mxUvqriY_63L{RX z+ri)v)?NFhX7s5>0obt4kJ4AM$wxVwS#(LZnV}N z>6N1n?+7|MjEOHz(uh1@Eazxk=yroFkNz&;+Zt=b`OIO|grtK9fNGnX{Q?q^dzv;l zh@Dxxnr72|6x%R%HUSnX!Zv>2!qC8-9f!n8Kt*80OB{ zy<3hKZp3(BpuP`ZiSgrbs^&X#MTQrMU7}YMWca<|ib`rIKfB*-l4l)4UMn_F`5IqQi8Ev zDNDS4SIT7~(5ZpY*gQ2bJLWc_EVTinqf3mnc$>Le8=L35;M%XGp0oIkk-d=DMY3=X zJ30MRE;ViAZC>fL^RHw`8DHab&W$IqUZP5WEiY{V{RxWbPx7gth2C6N%Y}Jgna7bQ zr+G{xy$wwvEi|OpkG_kgv@`ZC3Q4!!nYNXs@e!0Qk4#?!=3H|4T4pFb;9ENVO*~rZ zzbUvD=ceO+ay;`*!7FeqRf@qs>8td<#@YiZ6pC1H?CgGDCp9T0Yqgd15Lw?;7+>7sU@M*rmXZhHCqLUWFA2Rlh1OCl}lPnhwQ#3%SXDgHKS}G zfM5m>^C<47`W)%Wx!46%>FTW$UdeeCL|b!RX8g3|;OfQIJC?H#ehrbQe?rY{Nsj*s zEzeTTH<0wt1595>Q}M4E8HZ($9N0zXJx=SCMP8cR)I zkE{O|G8`c~)qfT&>c3j<${_uw-W1Z8cl2!2>`t!g z#8EBs$kyNph|WOwBqmPlNdl#eeEryoVr|A{DY2III)UM-O=XLo`VfIS&rMrTeE@yN z4wiZ7E#*N^Ef72ItXSa53D32&PmmnxmU!9jOH8f{Gkz{PDs;q4q5RqyLwGF$hkjxq zoz6&Wlx#d8Dzu7@P;O*6hd{2ZhhxfSYCj-cz(GC+sINjNz1PQ#`Udip8SuY`OQ!a1 zg79!fDWVn+99;4r05(UC56q zKthc01b&h1C7CG&uSs)xY&}C28e0oZCG<=g`-8|#yud7e+yr0`@yPdpAX5|RIl1_- znz&6~95)6Zg5ne1q2=RP2=uuBGy@eK=N22P5>$WB4PJmKlGO_%X&CZw(r`3D52egT z2oy}pENT$uD9ny-UhQbPtLkt+M((Oi7sMJvCg8X#nJ_g<$B}XEN}p@3No@z6jYJA= zCdBU5frje9+WoTXW6p6<4eZ-@__dRVpXkJ@V-{F$=~C!`8Abu$T6-p-{6xZ zzV6Rt%Vpb9&Sfl&^IhWXI(u5m+;#R4KSBJc*(*PX42l+ElCj=EJg?rswmhvnaQkYO zCR?75S1d;pwmcsrntaeoBM)tP3dTAFBT9#L2+TxP3xU{VWQwC)s{YzvGeT%ap8iN} zJWtwF4`zyIHC#beg=*TSNfneq$ihiqO>{yZYuEm3$TKsaC2W#1hq2sRE*ZvJ>xV7p z)|@76KcA%#pMiQWrX3(_nY;zC^#g5GBA~?EkC-Ah=SbR}U(O2Aex86A?dLcE{rQ@V z|2;85tE&q2thO9b-$__^k><&mV2~G+a>m86T{$R9J~?PzE*YcmYe!M{aiF_rKBcet3nB)Ps3&bi`vBI9dsiRf7Z@U#@_m~WS z-Td6G5K9EX?RC_dyT`O}kgWfDLPR=Rk4oJ2_vm<5PvVj8MUjqHWjX1;!Gbc+;mlPk zkzoIbx;OzZ2=Np^S9;_iszH@O2f=8RcnZJ!_&tMa*M%beqV{+aF;FMTH+ajVb{9$o z0zOrsej;BH@E{TJ*Q_deHF-clfmnB;Sgr0t2}X%4S-(z3-BASFOVXgk80y;PDh#3O z0ZW^9c?xmD#A%lYEAeMq;_^_t6pT%&EcFg|YQ}o^b=QgKVx82LvKyC1`Dzix-+d>g zO^sa>Nb+LLiqO}G}y4gjJ7Ey zgf+T}8f}~kJre09n}-g1QgQkijQ#8`wZtkDNDf7ITK`OA3mcijR5UUbvc=#S@T@~{ z&`=&63yQ{Ohjj9?^vDKewl+ac7}4ZO2$ZGT*VxPE&35Eum-VcTn%&g%ix~R~8?$liD+-Vd>6%_AGyt=szB9t#e z17RGw6Hm}tVD)!0)l%v_d;R_oNBlE^_6!?(w&R7owSx+DoRyzFFdCN3FBCFekhu{z zmp4jS!$uafPRrI~TFzld741doDH=|@CT^esM(0U!O6WBi?w2J#5_8Ad#+7dRtX2H9 zq!X9j9)sHv{9>}3dYD*$mrO7LPQ26lyVLNZzdHl4r(@5Vgmq)jS(p&uj*FVR`Med1 zg$ki}F;P&O^nPax)KBND-fwMd_>c#^pFnK*P^_JY4`m=;2~nZf(*>^Cyk2I@B1OM+pVTA}$oDpUYRrBG!pKFENbr;ADhAj68}39?hdxxm1Jq zl2TWu82k^=f1c*kUtyF--u^^W-ezs3{A{mw0g;0<2*!!FI}ntll7or6)>#@@E9+Pw z4}O6E#A^TbAj17d5(?ge*_K6yd8IU?K%XX1uu7lrGbtSu;uJT!NYAWl^&b+_Yo@Q9)7Vl zr;2Nn_dbRH?ZzWd$BQ<15rDPS8;I|^mWhrXFSi=L7$2J`<+GJkc4Vsg?07z$)9yIG zJ=~JX153T3iX<9c)t`%2W#GLgHh(=##N7R(S|NKOpDpb}-6cxahPr2Avc09slU;oD zQOYwm5t7(%b6&}a;rRfabX4JV((zKk`ZM_Iq~iv$@4b{1U7k2eybz2DH;MMBRL0}~AmDp0(m@apDb-2?S*7XvNlmSd(1 ztusR{XE%YCegdeUxP#v-`JIYo5J(Xd>a*X;@ALcu>``Bi)#e3kz)62boKHld*t|NK z_zhw2Fg^+Jf=`|epl^OoruJK?088-R#T#jT*+7TZGQM{C*1dpO&WIjYBIu?iqrxH@ z^j8pU!s(&vN|mOb$Lh>&Oei5bl0_{C!PiNK znd|RWQ=#fi!)pkU-CGoGIY*1wk>1Gn-rd}bP&V4=au+3hx6$Qtyr9b!0O)co1N(C^ z`Zqxhw+ZtmnN-&87*y7qf#EGuMW(FJ6R1CjugZF;%E}3Cxl&evxxtR;D5|1>eW&dy%oyscwIyR@m}s#Zy}QN&P0lxsK0dMg$}_} zy_!s3ZJT|sfr# z+OHOD->-IV$YSjq&lZ84SOm)nE2AvSSXl>*=B}(XG+UjqO|f~t5i>+`^%d|p2OkrE zP?bFm_RjHi-` z4F2ykp1KMzc`y!trC5}r}?@1&-ZluTj=|ocd`J(Dqzu)W+mCaax z-;wwzAzb@>IbP86S^%_M*48rL7i*cXBEW81CZ|Wjmig&jw9M-q0$Qd_s3x@UEBWqO z%Tz|8`}G3#Yxs)phpT1Md(txH0qF%|EmN_gmbr_@#(9@?q>)rvQ==7ZHdxwq`ibH)@8>vzC`peC?LzsMB~md=hRsz(WLZe*O<=bNYA9s#YOlBN8~8pN z-#Gb8nS^iNC{VwFulVK&wO7=b$Q7m$h%t?#L`)N%-cjP>3_-^kZa!@22O5?)Lr=Z8 zI?;K)VeM%=^%Boa)PEIKSr1yH7mbB9S2klb=Z$yj!}WbP;RQ{%1EBfwj^=b>H{;;d z-pu#WyD;H^BySO@zlpC%a->M|G_NLCNFosH!W1R!!rI2K_O8ni8^*TVtS#q=c_W$h zRaud_@TUxZ)u=- zu|Ebgj^=kT*=J&T1Yt6@Bl&u@+X)fu!QmIp*>p`L4mhGz3FoRD>Z?AF0=W|e(mtY4)wv8#?8cSx z{(Q^ZOb?YotqoBhHToypxc)7fZBZbrNfkysvQr{JQZinc79Xc*I#F=p= zJha)AF!Xn1BuF!^PQMo<4b@5;l47i;9JRaYGDQE*QyT)BVl?&NzN<4Vgmbn11wJ=t;? z#sNXFI8WS=jh&0la^A=I zB=otl6=$f;qji)vTa0@+Zu%UPk}aL3fywoqrNPMqJ4?mM{Y(3V?5Vum*}~gdy5L}Z#)e-;V1K2bt zP1nX<9Otuo+J%l2(fJ4OheKkhLQJbWcFe&Pp~s&oDTmrQ!w&m4Q@v>ZvncW_UL4#B z&hkEVyRkZRi04O9C&Muc*UQ{RS>1a&);evI0v$bWGfKBfY%}UGyB8}K^tes(0m1rv z`1WzO=4kcvD7%%bu967GcA^vsH2bR1MI(CDYz&>Di4LgR8Hkp1*tth*cWN`Ag74V{ zh_@N#BR00B5*OqD0c$fK#4DE`I%pYm`7ma$_7VGjl<#zj-lp*hOdO6ps6Ysrr|%-4 zjl0ct-AyFO(s08#;A6N2!&)n~18Fptsv&^v>0331I7rKL z+{q07{4aUEa-Vz8vz_Oh^PFctk32L1EH~7nRXmzqO;B)9W;9(o7^T7zYgz8}B8oDS zZSUH8vC=3TZXcq_?yx&^S4D|; zP3@F)@j4t(sjq#M#bn1%E0L814t@>%Tw`1Zw|jR270*I#aS!wzrB>@Vrg4l&~cVDtMBG9zH=&O*A&lvx|O8}VseH{7$OioBeP2(iX-vj2Q zA(XbuCG5hCu0G7Jh#T!=N31@*$gWA-SeqXxeuIK(H7JNC9t5xARJ*lOq0>fbH%8$| zX-nbnG)|aEe;C!_RwjaQDV_gx;o4DUEuIpc$nYYh+IP;ffaUVYw;OvkBOVN*N(m)$}Qi`Qzr4Ar+RXa)f0KhB%a(*>!)}eomJ}> zP*>$;c_3L5ZVm~rI2RJmg3Rnl$b!t=7|`(AnJr7l3c;C@`b>WvTwgmu>=H-tU&>$e z0vh-0C;0ySwaMlk{Ht(YZQPF~g@pFNhU$}u7Me3^O{`(v`?Ds~Kf(){z6S@H{sw6v z(~6!hZ-jRyBl*?GgubxOMqn=mF-^`HjwximLnoTXbrLBE@h@5c@5=;Z6Ny3uO2b~e zwo)JHaj^|GkP>b%{3+=cR*NC@e)8V`s+#C{?YH!zb4}moB`_2symid*7jnx#=BZa-04MK)!BN*Bc<;sNj20~2X=&qKf_~SjSjFd!byxB5_E0pkooJ9 z)oylI5X1G4ZRFMEUsm^aon0oq~3iSVX0sZ zVSi3oR|B=aoK3A74kLLYETTKz=H14G3qxFXEVpusTl|r4b{1zspYZfI?J=NX6XO8PG%QA!i0a+ zeS<_J-eWB_7KV9bgkhI0=i(%mYjyU@QOjw?Zqkw_#SycDl#8qsW^`{;zW80eaj6sC z=#1?Q6s7Zpy9MTQCzsYL+Eo>7vd4XA7Z!Iiu4TLPysPcC>;qlii!SeVx;%jN0O@g% z|9VA4Guh3D#!stA*Z|xM{9WT+2Uji6SMAOpNHuvL^nyM$9fVLOp2PRaxHp90e41{0 z3|L!q&yA{XOZbk6B8+FB0R64|@Ip4W;?UnZ9mVu-(n*(IDDj<6VI($HyxqiSiXh3- zFL`Ut;bx6Z6WrWZjahNiR*iWg7K}?_t}PE)l2*>h0&^W4 ztV4wRWozLB0PUHvqKO4FiA6E2j8(qQV{& z+>|`EKTz_t8BWP-vqVVAD^bm@JtDXKYo3~0V_)F-);=T&Gjf15w_oseP_G zC?Lq1j5a}RB(3pc(4QOFc}T0PKTuJ0d{99;yvg#l= zW;@5ZPE92b0y?^P6qTN4!%S3uNM4iM(JEq_$GU~aHfyxFi_C#Lw`$=qi0~sK=oHLk z&r&d45Oib5_XxayjCUyMjg2X9RG)NpcONNSecIr0-m#iEbJ^7RpJiswkZVlLBg=8? z+qeL;1)PzoDf_}b;MVA6CEfG;?5qs({0V_NslaBo!=%7!08_B_p;aBsEl7mdn59~W z)4X#pTjF^Bp}wGMQ82dB_$FodhJ3;5@SpIa4sRdaxvh~5)&c?Aq_1`MhBvxfxc>o^ z-86q953I@jlP`Mz3YskG!wM6M>!6z~37a~uJgTr|JuUI!jT{rNePIF+k6<9;K9Hcl z0<+rnqj$To&S%&=NzC}E@%wK|*2PZOr{6G@sJI$%Fz#t?Dp5pWLFHd^%75mm1(oMZ z<$OYgEy;s56*-}nL9x&(HrQ~54HajjBaoFZFt%${3K3}UFEfgsvOT=Py~aiXyVv-B zPQIF*Lqn~OdE1++n3_Nz2utxe70{Z^8bw*q@N|E0VK^B^jE zNY}S&-)7>ACMS>KMcsM=hj#fztTSH@WySLgS1~1ow$;7LPvPx+R7>-#d$Mg7WKE|w zQ!erLR~N|p3r`m+UE-GY*1}%_pcI&T`!@-&TKJN5>E(D}h>-7mUpm;Yl`i}YjyuB# zYxSl9g3Yt25edz+2wOEGPs=So&Qp!Z;o_*9XOV{*5xJpx7R4han`fDNE*1_#rDW#X zzO1m=`XlJ3P2TH3*9wH4IUt363klIA1c}PvZse6P?C; z=HeZ17Vw?dgvU6lxTA2d7-AhAV~zG(i8ZwiI&L{vX;IaZ#7&l;;X_sJ2pFR=wV>29 zIU%Mf3iV7(SyAoWSTYtr(z&UFT}7y_Q(kmUZJkniMr2$zhFC_VKhr|Kl(P*789xvi ze`k!0r}2(>4F&?O!0FN}s{*b1&`wyy84J~?n>eTVmO&v=jD5mC7s>3jf|0B?YX}v? z(k#4GIXdK)Ta^a$(nm^~+d&?KYk)M@BR51i#Umvr&3{v7 zSM>$II=Hsrr}LXoB(aJA<@|g3*bnys{`zT6Lo2jz;UAKHkuq9aFNr~I?{7*9YyJ7jDf2X-a{*TQa_J3vW zi2oOJNBw8yZV|y2U-domT>gWl*pT$t>yz0c{J|r~#VE(PY!Wak2NXv2%W11DlGD># z$(!3&88A0h88kOt88WxMGHfnZxy;Q}M$PT4EXK7wS`s>Lb%)8hvOvz21*~_U+pECCLcm(?r-gu-Af!z*t?U`=g1pAV)1mKIp>NCnT|nCSkMLj3|4{y?@V}V< zd-$`yEl-|>H%{~}_p-G+yMEg2i@yulBF*!FWAw7DR)lH-Sm zXRuO`JR344EA2g2d%e9;Lo1Kl+{Wj0>iGmH{u*ttWeKUT`~@{^j2CLyQaNpvWpb!t z+nC!{+1A`tWjk}zmF2kTf|kUuAO!sf`VLBf`lAG>KT3f5qxjSxMOXb%bk!e4r`b=B z?Y-F5jdZAPq=UMV_7BQb1(de46^+%G_BYpILET9Em)2py#n5U0>NQ^v&USc%hbBu*GEudKpdiQZ^oFQJ{EN3N6-a;21zE2V^7DIw%a=}4}Wj^s+|NG~fL ztgW|?9kj&BS9?gl+5`D&_b>0N$`@yKO+?y5^3@*5S9?gl+5`D&56Kt1kYXZN@_JoQBwF@N#TxUxNik`@e4bN`AkHclh3zgvay`7;m6xDXKJ0cZ5flSnnpY+f;a(F^38vgE8%-$lfamRba1kYUSoIsHd%s zZujrOrXTm;@`q*%5_^*OHzYf3({y~5o8hlf6%B{YFpUomWXAJ@neo1%%y`dmW;`>J z8BdL7#*;&t6z0)RCDSdtlYed3#mbh#=V`k~8D}sz*^xLm%^5+H(RBpVezF&(1~(1u zcNOA2vC6{WCTv-r#dKKAXQ?y{mKGeGOsOD56XvR=1;BbjH-F%6??Q zJm>A^x5tAx*w6tMWX>LtRN-&Lb5Q?RaK6A{bZV1A74F5$zjC3f_@4Ua+? zdrpHN>xFw+CVR_+4Kl&a_0!NsGhW^^deJ-=y0E$;E(&R$Yk#@r^*lAt#fgFOt*hY< zHhutUo=a}%QeVX*r6eiQaIk>-Xw>+oP@{%GOxVKg_kW$@6-yxh>iY{R3&8MsRKcWz~7lfc5j-Q;@?0zcB_m6={_ zFFZtk&U2u!5A@dV3Zm5$io2?Icb8*0n9Hvrny#(&jvz;vagQoo@o{Kh*#~S{>uRhc zgL`~cm1Jns;C^#flUtGc|1YYEtfw@oCK=Nz&#sybZDJlfkvN*a1_1ZaQF->&MrKHw zR2$#))!fx)1#EHk<$6X4m3_sinN>Jsk}9qW%RosJTpqLn&I6ZmEVl`np~Cw$T*>wI zyIR$Uak3g>`GPQY0S5|yq+(5AW0_v9Vh&hL_de)6gDY5q0D~4F+n42nlj04knol91 zF2b|XTDlgpNs<2nU+1H>)_1Z3Qayd90z@?cTU-UK2AHV|2q0j!cGhb@Cfp1zx)mOV zNZyeY)9~7*j{%3;H`*II@hsvOQM6-SO)0IM7!fct(ao6eIZ0bD=B>B&PBz>+A*P?GM{ zO~Je)1h378MI<%6;ja`06I#Hrj&1h_SmX{f?5R0i&pqao2F?pz?AU9;8U-9TD9Ht~0VHz$aVZ!Rgp8!GvgY^gK8AVT|z|Jn4(SfN! znL0HhIm(0xx%YQi1a&5(sNsi_vh#F>56nWb`}M#a{p(9;Jue6|H82$Q0-_n$ciRQ3 z)mT?KO39VnvxZq=18tn+H9KaG2Um4`PEHN(kq`re;7ZaF+1>tOhNWzaqD}E@i=w9Z zts`V}{QKA)caF{-m5o*qqca_$V?}4#!b_Iqp^lK;&_ut{xx=j`8wQ2l3`-|XMivtg zhd1`{487;uGb9NZ1FM>|&`n-C{;Yj;)k6+y3mt}Cl}(E4B%v$mZ4`#Tv+~P>4G3kq zp+gRaVOMa-K_oQ{JGW{H`CQd?on^?6FVh~mx{=Z~Y^bt2xQY&2%`khUrde95WV-z{ zMP%7R$g*ut@rn#6GZ%!Gt`5)jVwepdyU;X8L*V@$sX$R|;3jeHznj=T+k5V7nOC3e z<3(#*EOPaps}*rAa`kDEt1sK9put63ap`T?w>NQoE;n{aup6|y?z;r<$~e$wTPTM{XBb!fd!^`galt}xM9_c%U-u`c09 z7@rq$zh$~zewln{s&9d<@+3A$GfI7=?P36uc23RfBVW{4X7jWl2hCokXFi$7ly5s7u6AEMMyxc z3<+8r&ANGi+N{L4!5`Dk3?nm@ZOE~Y(f_vQE~+dux9q^)@u1R|urKec1g|Hi`4pw~ zolsg{kk$@CTHCv{99Xt0m6pC-T1HO{F()|k%E;kmAWL`YWE|ErUt&$A=DU)mlK>5@ zIXhiyW6+F!C_W2+gx8KRE&_$4Tzk(VJ{$!&S(x7`N2>#}k>k&~rUcupLjE~P;>*P546E>>Nc zreM#;H`OhaT2Gf!co*1xn%U%n7XD5K8-pOv5N@ZotsB5J*mT>*rrXx)m0CuS4O__C zrGvF6%i1XO52H+)r?t5@DAwRSG$=OO(B{5Uu^e$|b6+CjJfL!w*1=wUD`cz*TbfDDfkz3?w#AH*p<#>Y%5WZTJv z!y*{tcnt(imQDp%>@z-2=yj5tbSVjm({?iwaMR;kq{mqg6afQ4s$ZU^Lnslm9DCbM zld}ubr^sbKg0$!Cg$J=GKzPN>@}<4=Dc016tsi;%nPU4h6vso*-w20-o%aXMHl+I5H z&uOv565!bo{AS*-Ai$0xxV$wpw=*!z28H#nDr)EqXIp}uQNf+tufhxca3&7=!KW(z zjx@qt_R6p~UG9V`-j%e@D=-1sBN<4R3dHi*3p<+y7N;|m1v=wwIps5WN@tv)YH=4? zVbco`IzvuKXDC*nGlHDh4*mdn6`z7vbcoL)fOfCWmbu$#J!|H=_Iu8p*@R)&ep@Pr zJLDf^MiCRN+H>Xh}Q;0lsT`+Q*B1nG2I13zezDzbRwoJYei5 z+B8ldSnuA`K$E3&cukhhCt7kTzANO~uV_Lms# z<-x8@IU(9B7U|dlSC{iNIj=uYW8c6AsqmT3v*sGyQRpz7UL?rWfL)Yhd0VF8i{$Ij zfMyFu5dwM<*WOa?Vx$w_{y}0O7zYaJ5Y5^X%;hESHkzG}7xcIQhkGXhCywW@eQrOX zZj90oIg~%w{G096Afi+9{>8c?m{u zDwu|Vua#SV4Nq+Vd#Tv=36-cM4>Ok)p{c=T_@pEl`h+B zT$BWgo9l0(zJ@6g;affKWr}O{%*iNr;*{I8cl+953|Aco z+`ZB+kuENCUa0<#q1tbnk=#__aIE%O4bLtNUFJMT*x)i}*`U+!QvNsc_n;-#BWH2z-pr!T^-H(x?W^gzPp`{>a!nD?A*3ho93r_=sR|FOr+YYqlq_=)4-aN z>tE7cxr*-sBM`<{Hj0q$w^4*Nb})+2sWl-g-G7bT@|8TL`(G}N$~qr%A`j_)xuH>n z;*nBDt;mwC7}20)8VyymW_+Xpg!Y=*{1=+`YNBj@Mzfo`rLjL*63z}&kCQiZ-8d-y z@32i*-l&)kTTDxwBy813#d;bZGA$uDbmO358@h3Dz+-PT<2McO!n`m5r`{KU{fb5+ zyUzU9^~@;F=*l~51%{rX#ZKaSzpW0g_S;SZSMc4A|K9vx&R;U7pWwGbPR(UAM-hQ* zzwwWN{|dL-Kj+iJ{1({X`G@*{;%2BXlTJLvgvs|!KCi_K`FslweHqQ8T+BaB{A>CD zF!aBS`SWD?%>;B;Y3fv6YMu7Q*lz7~sS6rzTKW+6W%qJK0no$Hy)kc9)OxSoIv?sf zYNQtGItn8?5$}>yeiKg}pFCNdJqYx$#RiDkJ~^R=U9m_5ai_>I47td@yK%Bhsnc@@gpKa*AWUSL}4+#W7lV1H53U`6xVVcYkw;N zb-f=3CaKAVLBwr{xbEwCVmXk6)S*^_+!CozECTZMTIt7Z@{XV{ZFg+BATG6;f>Jnu zBm=#vnRwHi>XHU}Q!Tym2kS}iQ65fSv7Yn_$?H2>g^N67KuK;$Z<@enOmAv0voN(I zOorBY3#TNq9qVsYQA3gxl+&&YG#mkoucMpl?iDY!BP2O44{XnN*W^gBP&tCvR)i*P;R|iN=DI=>)a}}~w0D6` z4JoUFyVmxdyt->`7 zERdgMNm+(z>{_gaeG7v-a~Dv@m)*w6vwd^_6PHq{SLqL!H+BQ+~l!M{JA36 z$D|@abWAEppuVds@FrPKqdUQ3-3E39EATUXgRXIH&QNoL>nY0dxZyd#)6iZlW+mQ( z8-_nf-&gQqnx871Pg{h?xKHA>zT1Ny1*^-e^q3&LQBL{& zJT*ahs%oU0Ae09agmOYX4aKTY5Gp4o2s!xUCJ1S5J^*O$3Bp-3*9765IaenLEfvE( zGC^1eyN+h{+_q@G0D7wb80sIY?4kZ)OQH=~_<_;n3)vHapX?LlnE;7ohH z>2RMJCD6%($Xz&-UzqBn$IZd|={$RKgDJqNUSQm6GLh;C7S{^h3Y?2b#%4G)J`e`A zTQFTGpGvkDdWcf_FvVnY?VPnYQP5;rpI$E>YK%(@dBH4DDXWkl!3(>gJo^i4DUP#( zx}6mNOgoFtD4R$rJBuHcTfUj6>?~r@f}Vo1E6GDW1-YR$d&LV(EhbCK*Xt7|{SF6* zWDdysLqhYULVY?vS|gGx%=98b5FVQ-HG~Y2h^59lI2fgH?*An414o^{IGF#N`D-Vx zeu8hu{}TQmFTp+SWgA&W_w-(GwB6ls|md@LqZJ_3SQ`kY%6BSPueS43#DZHttaE+6o1OL zhr;fz10he@6G?ToPwp4=nA@lh*|utZur6|tn;SM-@u`zBKgze35aot+p(3#usZrNdcE(s3IkS5mEon=V%Y4{+o0+mzj5@e_l(Z&i zdqI$8{7>K?mZy*My_o;E_*dH=$(iP2UJcK0;@>zP)-=iI_=ohJN*A0@T~)kNJH>AU zQd77CROV=tX^#L0%>uuat=9YQ8MD{CS?5 zt9hk(RmMan-pG5KoGg|m72W241a@PZb1;6RHD zUDj@UPrJ9282Rbs1T)sctFVmk?(qvD*jEXkE>!?xC6o8Lspj;rvJ=(rlv}=?r>OQS z>0j5ClZOZ=H$*tas}e5VQBlTe?~`z<@Dd&qJ2_k?yb#cV^6n-s-MQub`U@uz!+B0F zfqX4Y2o%=^TlZSu)M3Rp}W#r z65m4}*-ngJ{W@Oo;F~z`;L!0iA@Kk5fptrs9_O=z0kHZ|8vkm+&xXJ5u({&Xhx)dh z@;7*@55<)p<5SG^3Q7k`PH5z-C?uj8!xd@oi{zjy(%DP7E8C{pv~3ET9_!ChV-}d0 zvP>*>MjG|$v^u!jP*vsc&DYn?q6?=`o6?rJonu}g4{Do zk2JB7|M`%y`oEQ~?FZ<}<~Wg<_C8IpWa(~VIt6P6-L2xXVbfLI2oN;F%J^anQngBp`{P+>k|F#UrH`k&@`&ODCnfWBso|k*^-U97ct4wM~Xx zVKuMbLwMzoGZA@UO-D9e_?7xM6KU^OAnsZCA+4I7{8)pa20=C!x1qijeuNj0Un|m} zlVQ7;yDI?NwnnCS3cn?U_XX1A{XvBuNPC~+)3&w-u)?;&u6!Z&XL`|{C<8_O4S|=O zFwrtJzF^qI-yu7^;T_DbuKgOE=vb=N>J2YtI)Xy5<5uVFQbT*Z7{woCf~kcRdpF<8 z@F)BQ(dx?#TB9Fy`RTqZ&r3sU$>|lo6jB$z9^2jBgu_- zRlle=^z@S1&U4S8BVG9cY{Awi&&fr=IHQ!*VUhJda{xKklp&DyR*=$i zrjDNTOXOhdC&lvoj-!G7BE!h+7dh$|z79V3XW7_Wr_&o1!_D!sr_yzKa(%h#BC@9L z9E!QhkLxJrlHS=u@jVQ3lO=gD^-z`@>h&0kIqUU|kNQXF7o3&tEZnAk7I+5swdoDB zg}A>WRB=SfDK=fE{VNoFk!{oTn9|Pjo_Qv!O(vy6(0eI1efH(OkMUNyhl23VairAx*TWT*i&RO0MlMx&J8$0haIT_C z?y9=y@r@B!;_XqO%8u<7>~tOI8}NS!G2Bkqflz3-<#oXC9H{xa1gakU)IDt5YhVho ze1Y`|l-1aY$9dGk@pNemFouFIfvYr*sR%Sqvn}`P)11$aOVee2yRWJrw%asS>=l)i z1G28A47_syc`K`B<-DqDYC$BMmdw~1;<~WT zyD&iv?;)hWkJ)>rQH|zJ;^SzDHCW1jjhK-g^1ccR$&v`8tKyA7GyEZbfLKMYOCUFr z%#N_(Ux_)0m>D79xrZ|^&lctzU#90HMB z+_%^}yw*dsE8>?-FJiDVl3gJPwH6PjWy&q-TOym4naQrcdVn5) ztmk9~Ho8EgeWUh0y?_^mK4D^qdhU7eNU}eozz(0_hFVT6bB#-Sz`Gp;v7@PWoiFuM z1F`IWp6oxp5V+`VmCN&D;sM+E zaHwCY95r~~Dz|(uPYvGBRZDiK38y?XewQ2SS1KMUsbASWb!M(3ogQAEYc)Y@Bo=2^F}XBsI~_PS(I; z9pef0W0y`-_6;}i2vyaIP53tSYrWqSdSb&&k04&X!<*p>qG-obC4tT#h~_f6V~2N_ z65T#X)GB7KGtE-pw}IFCr7rr^Daq3P6m(Lz-&wgfDp|W6H(k7g_lmq-AX3z=O9eVx zx{-CW6ycyz)cI?WXVy23b;dFlt9GK!xS@3&-0pG2n!&Sdj_J3S|Hb@eiQ-KDY?_n! z*qoZ~9om6^NKPdTE#A&bLbe*FgkfvtmpHoBU=8qOX&0WwcQMtm@&MM_-^P&_VU;1MnQ#DV^Xn zwQPfCTl|kbm^jNnlLwYbNwUCNSjIaK!QK2TKJDyePKNUF7znxytwf<*3hN1!8K2+~#vZ(Nwz#ADukKBp#b#E~!>(N6eNW!qIeo%D zhmXm-rh57k(|If<1(Vs;b0K#n*0nB>>cKXcBeLvC{G0E4jc)gWcYwo#U^jumUAdT> z-T680b>KSo^{#ei8}h5YYN_v8Os8u1DxCscWkw)XwIC2pZ>XBy;B}qpiPdR4%N|N^ znn!xR zYH1Aq5qd<^GkOR#3q9_NOD{r?XnIBufo7pc%d!~!IIt2+4_f`i&rLp_M~HwP50SX! zLvBD1z5d7ah^A-s5a>UqM>IX7hd}=!JzP2Kn4RkhcHh#r|ACayn19A$J?u&9BQ~q~&{=7@S5GYT`-tt|dk@CK7$GqV zEIoopvh*jO1+Ho!x&Wr?hOfGT?!@xE|3>2c0JXTldz89aWHB})TYsO40}uKS#DRzW z#|7Rlnn-~)*$l&a}n@W56Df`bf}ibapCQ`W&V*D04n^ELcKZJDwU^|?}soojsgJ6;g> zF&y~vUE|9?`4C^^iZ9zy?g(EV#RIF$JOjQ6V0;;@^5t&g#PB7KT~$gyZmB9M|6xbz zCwQNm(kcR!eo{{PA3R0r3j#_D3Z>zzE{`i z`xIW#mlK$xuLnF?dK#GMD_8X0eR%_Yr}0RZp5YnLR{*2$=zl<8OkRGR92y0D`H=r< zfg3B&uzyz^c*OrrOd?g@EuehKlF;=3K`?4b63E=V6M5iWi=vnR&4+kb3V0_dypt2+ zouV}G&Qh`H5#H6oRo;c?`;USbFXbPqCqf{?N6wBU?lb+>3KHu5 zAoK$?LPR}|_@5KFk@2JcPvgLg{XfTnm-xx;Nn}Ms+G8F5E`o302+#N%W8t0tIf6Ih zIMx-f+Q+*6Sk*Gt<6ozA8Vfbn>)#d&U+8};7M}I*iG}z1e~g9a{B|!c-+q6W7`WA! z;$C!NnRJ!>tox>^T)ke6m4Acfuk$XZ0h}Cc_az%uU~zACgP8%Z zEP8}Yb#RqTH5=8p-BDW?RIVY~v?kufJkeI8O&%|3Gl&zM?<+u`U+~vnf^!*P>(2z^ zVnU=#Z_q^PrxEmlJ>EnZ2f%tUo<)w_lhQD+g=1Nynx&`Z&P*y>+K?r3$^$&rhHQo? zTc0#FEf1OzIU%|zR?v*FD_giMpH6dJJ5&4+C>e@aP9{)cA8MTc$WO4idIdh~-mf;? ze*xm{q7{F_98_hDkXoSHgsE9xT`__ZbZDcNEpjrK;}$vh(~FiSpUVp?KiWHQ zsp7kWFq^=gTNsP8g2+RQoN_tzS6%tBlC*l5{mV>Zox*U|bbu+Q!U2G*_Kk#@3?RvM zCeUDpq3d6^LtJ?V|I_klC96q!UMe{#J=^j~q0X~Bfpi@Sz@L`FTp8Od+bE{v8Rsdth-Xww zvXY0+tH})+_%oihI|F~|Bv+j+!=KY?dt3v8<7-1)EbLFVS1n<*!vbna!B{lg6WEF% z&}Fb1&}IixfJi|?)&XU5#Xr(LiWU#noQ5M7mR71tF4lQS2o5T7%8$wMY*L&ExlLw2@=gTzd3P0aK$w;YzF8f>Y&t#Ar+;Oy1y(d4$F=oE*;Fx%r4yj`<0 z!?&uyWJd*2OSHg?>u}$LJBX{F;6D!FXYdh*??-+6D*l{$h``@VxXX#NJOo!-ns9m^ zuw(fzGna(g_mHOw>U zO$uPW$&qSja^ZMPXJo)%83!Kp_l*M&`RB)hhy9ylz|{f3N<&Tf_@5&f1AuYfLoNS| zy~M8J7adUE6LQMSc}jU-jOb~YEGS|pC#1O*rC|VIsaW*L0H6*I1R~u3XD-w35e~Kc z%0I%d`Z#V9FZi`HPHh~wBOl_IT=DBW6g)Cc-U$y(=kg5rB|vl>x93DGzYdNA5BkT& zfrtFlF}m5pl*W1;E4pJ1?Y7vAS)qsRjrca>9K#Zzp&Bw(YUuu)Ek zjf&F1MoYz_N7z^g*Xs2sm5iV2UCcO62t@d}gnH}h-EMfn$31Z1r`GIJs^d zcPnL&aB>}9$4uf#J@8R%sB48Sa`ILR1}%9(#q3&sMh11MLmdJ z$kkqQ%DeNFT+vH2^&lv6B_||TiqargmWoA>$WF%w#J38W*j8aVt;PFd(y2<-u|_`;&A*#q^g=fBK6fuf%tER* z$tk~pr=*Jhx=EFwNR^zBR4Gb>R9Pw(Jt9?gu#>8AU-DPT#b5Y`WJ=jbWU9XAavWaB z)CoAqlv{Io5g(E%xss{BQ0|B(c`+Wz(o1*-G9`e?RBg@W<*Q>@GvJ>gaDz6lQu;(o zRZ0055)4Yel=rzQts+3_m&qv~&r_7<0JBkAP$(@YL}^87ptPl8(Ib?ugRAwox<5U% z=Auj^^exxw?91_jzNg|q-#l;(D)J%v$`yUDrL+)r{X^owL-F-$*gqvM^oW019C$Q-N`7(tl>8F^14?AJnfeCFSUwia)X#tiGxamz z&wi%rKkknVoDWAksl6plTse&S@azc`- zC=HTosaW)gq}IVRb;3T#9UBzGIzib-WL9?pyZ+;=@Iq$K#zAKPY%+T$ACg(QlG!oJ z9g*1r9vIf+8OW>vPG*OyGW%ayU3xn?$Baro9S0uqe=cyNn2h?pyTpJO`-ceJxNNh; zKO+uIJM-39IJ=T!;hp|pV&Pr>$gX5NqfT`Dt7G9k{^7CkUjLL>_(K2kSa{aIJ{I2R ze_HStGtC#?$S{`k?~aA{`@f5YFY=#?O+W8pT|EPT-4KL%c{i@wR7gv~#h zV6+-V-si515_0O|IdaNp@KjyAGN_A!QWxcf>Y}1F)J03hqDSgt9Xzux4pGmx=O1dH zm3^cxwo>O@T|5^r>f!}B)W!E&pYPRts4mJ?T`W=VNL@T1k7VgJJcGI@fUAq0)w=kn z7@D$KrET{Z)(raF#({_Yz2m^c{;_f35&yzC@TmWuIPhZst~l@#|MziV_4(GuAWr)+ z9s{q+PRZyb;`sX!4B093KDX?MDacMmPWe2ZlAYHFvLh(6BPS#~iqarEmWoA>$W9$R zQ+B=$|JidHmK|juk)8W%va=a4WaknbWam87<6HQU?8ueu97VY!vU3q0*gfPK$c_L` zcKWNb6Wf~@@b8KtH5)!2jsp+*9qVEkHXPgTE+6q@o9R)1+qgs)`_G93GhT=TBRdzy z!ZZE{V&R?sow4vP|97$Q?%2ud9?f<9+TrAz`CVyjD*6LY}Ggzz@bhYWBT+8K2#^n#vO^r?XfIqZH42Mytao{0;G7dcKuZaVX_>Vly%*o8I&3{|B9G&sxtmI zX|4GiCg2 z2vX96Phi`&5@dmt*oe$z5J8u2|Mm{7v zawR*Ny&GibCOnd*n|TJZBY??HZT@VN1O9Ds;6eWv0ynC#A-{b?Od`Ynu)qzW zt5y9&hMMs4*AonOeVF&T*(Ex_u8+tme}Jdh^~Qi*g2FC2A$BQB1G_90iymQD9qibZ z2+!HZtp6+jS@`u)yx`ZZI5mF#4=VqhG0~^02r~GN2V&j_v zHVO(G<%HO%C=G11R4jUgjdgIq#z?)3*{>l4X5r(P@q&+E!>RG{c0R;Mx#DC0z72f5 z0}t#S@eKGVK$MUFm3kN3t}&dBtBxa0tK*%9vC#D2PHCu)U*~;pK8if>@f&i=U*Rb} zzB%Bdpzu*nh>wcWz(-5PqDS~x2hWp_LLkCNY$XNl-8b=qk9Xm~$1{OrDTxp9QLgy- z`9?l|8;@k^J3IqE3SfL3>!^+g;^rxtQ;XeKHR!L|Z>CD*Ak0y5;9>tY0yhpYNBnE! zz@z>r;=s(Q#etXjzZ1BjpIueX-?eh9uzu%rO&adzeQs$Gk&%Y)$tnLYPf5dD0%;Hw zX^<0=21RL*21~`FN2H++cG3`TOWz4UwHGEdAE4|bvQR%u^L@OKg?n(21$UO_hkR%+ zg77S6I7k@f!`M}@IIw@T3< z{mWVH=-WSrm}C9^PBCz+E!DF$lAGWxjfs%X(&%NzSsDinpNz5a?ktU7XPl*Rz?m`Z zER8&8oTYKVnKA4vjXXnVX(Vh8)rPY)KOtPQBpLS)rSu#m`6=%qB{16|5mhe^iTq4X z`3F38NaU@777&y}B631nKv5bFiC8KYJ#t8-4vuRRV$RYCfruXHs;zn6ix+xeD-L?# zW}74W1s`e?>nBj9`VnM1CRRejRP{F7qg-Trm4 z@E-pMvG86$c~Bfa3ul=_pM9=8ZcbcUV3*kR`~8>1!Wa3ki-A|$(kYV{35owPg3+`3 zCGVkXG|SFY2`6po19Hkg=c%^zZ9y#(l(tk(s1_+oLtAR8SoBC+S_jW;OED{$_+@ks zO4Q2ku}ukfM{J%~dca_8!Q!-wjST-BjFD0id|{T2_*d+-eEkN{SPYUk>oUH>?? z$2-)t$NPK3K^glSo@ab{i1)eqq5{L0KgcQnny2{k_JA*f!WTIqz9>oqUn~`i9^p$J zJd-am=jxSxgfEZO`n!L`3%>jX2fn=4`n!+tA->2JUrwal5x)Eh4{Rjx4EQ2|(`}im zZu_t7=lC8u&*-xZ`+trDkNE8e$Alh@UD0B@XzT*pl3D6>M{J$0*4@7vJwyb58*rfR z{*Cv!WkkeBM*c3R{4h_+$U6cV5fmAb6Os`{X^;_1#iB=Kqz;}bBcFiJw@3TI%040^ z-|FZ3DLXIu54@0(f8lUmaw%sSFus_;HjnNTPMm@cXr-;x+0?x_S9BkLZazrlu>y$= z&cq9_@%dN&S7TUp0vlW3;eRToG*g}2*~NcfE`3HpB{p|s*Or44U*NwHqtO52{}zlu z?~D&amL3Hee-*^`F5|FcvUm{Z<0?-A47_g6CC(c0zQ;L9JN(L# z*2m0KL40{8OOG4i37*~~wSB-dJ1g$+rf5KCN+cdz4p4u>>nMNXa(@Tlmu^l#^!!xxY6>PR1N(NA6Ly zO0qOJa#vV5(=vNDLr$I%|@Kj@*fLF>*vT`3-M^Y+muZUK zg_i5ww9D18dt)=~?Y@l-B;;#2ejL>8FuoFU^m?yyS}E_U1Pbl$NZ&DRxKA6FaQ%R5|;`X4uIy zg584V*y%2r+51lzFrF9fo>#~2wr1GLGlE^RId;0&ZFcMi=84@$&HTS{_Ks%Q$uojo zOLOd+55~B>YF@PKu4DJ>X4uIyf?aEK?3$0pG;W_yttePHqh0deCi+gE5$xKUW7m8* z_MCZQ_d{kO0)02!3_E#7uuC<^uK9TEeU|Io`fg1fyNPDl$uojox;b{u2V@K8iQQXm zWy3goa5L=W8Nsf-Id;uQWG}Z|=ce6b%rym^J*F9U@{C~D(Hy(xL$YsJu5)9zsgB)Q z&9IYa1iMUg?3$0swx1_!J7xN%&D$y|=53V} zR<4v3#vGMWce12}u+dghLQu7>$PUq=a%@3O=@WiT4?}(AVzwwu1I&?LFnSQV?M8 zP*EH#?fnT;V8HIE!`fqD9WgK_tY=1sn=!yt75L#_As{Ymu1#|x?UtC8J!2h4gJhzQ zdhu0g(6T?>WXzzs8zOeYJ(B-v{4e7FPX3?Z|84$jf6BwZwoP&UMG`RQ^ zhVSZcHh6CXe6c^2X0_%Wcse}CJN?^l!Bq8el;?uf%ID{cmu3>F<;k-#U6`!w+=JX; zf&On2!{avZ$mewG5&{&TkA!YnNb1~@_$aq&JRH6-UWH@a3YND zcU13!0r$EOuY>OOP+o`Xufy(jDbP%6B;_4J%#lsrkvIz%w3euP8LK9peG6K=qxd46 z16#f7O0~N^)0zzZ=J>%LoiZ7Up2ahDg4 z;W@CWa4b%Nn=CSgO+41!SPNG;j_<42y*UcVUxOiv1>W)ajV)dC8khPkDWXo4w=S{C z6keEM#zz!YDd1Vd}m1bv#IJ+1la1=MlbSx6?nlHiWD4CtFIzDzItlBGO2q03)R znw3vsFw;w7vT$Ox^C$`8UPFMP!byZKo?MMmc!fSs;jugvs&FcR734p%Q&-|-W*mQQ zyc!@s^z0B$XKF07Vg*+WGVx-kF<(OO=3)NJ=)WF-GWt9JKY{ak1UQtLEjB$SUfM0I zqPKuou079H#O+*1XE!eAy%*EcaJ|UK@eA&3@8FV|c6YlhCLl$9F6>5e*Io^q)Qym5@CQtCScFtOB3=RN^e|Q3*j76Cz0H&!lNy!S`SForiT?1mw~@ z|Cj12Sgz)&bg=XYLKD7o-Niv%E-PRNhrI~t2f5pOW4;1nHL{+zWjq=r)R=KN^h2PT z>&$izY~o@fr}(v{`g-2i{tqwAKr2-&Yw}z^*|ox-q|8OOOUR7B@JyB#+j9v|?}gU; zNppKnF3u*U#-ieVGTT+zPHuUSXCF2x-=&4Y18w3`9HM_W11tQF=i8F*326j-4n{K+>_6btr<&Z zsS@eZM-lk6*9(>&j7u%;qvH8LeUcV%AMQE2AYJ+d0VcA}EnbFZ$n|7<#(UGH+wds9 zJpj-}GWqME(>Jkg2iLya=NO-uVTCjvHaAn7?E1LExxA)3idW-QKCS#CQgj%&K#uS`#P%iR+fwIPLg_)-n%79ZUR&uxn8VK;)d>cGD+&SJD%v! zVX9a6DtY${mt{(-q-Wu0d@oHT+7qXrFu6?xwj}{>ix9Q?m1_?7f9-R+)` z&vL(41yOrZEQp<19#yZO%WE(M_=Nz&4q%)b5#um3Tr2(9($i1KpFZ6(z93)ujAN1o zY$u?X55<)3^E?5(0iHSCD)|J+d01!UKFmP>4LqNF9{UQX(;~Q9GO^UTPVME~haUtp zB~?W};Kf4dE`%zdbJ)}{g-?kE&d-~0h}Ha-;b&K4TU2jVgSVo#2T{3)sYb6L%Pm2c z-p=@iXtNZ-2)uGBu6$gGcj0Uvo4)~W9)g{jY#m!Ro*bL3d_ma82g#?s9f>rV-h2ar z#z!{aD5t*${eV8(Hs6F(`67>?QZ0f5IbGuUJa|3%qEOm}%gK3U&GW*b*8y6*X1BDr z9a;J7V8ck^UBD)`4U3xdmVV`iyUORuZ0!%B4J@ch$D|jR|3nvv91J4#G!PZ{v;@*6 zC9@7W#-Fwb2ctQ#z|_^(zd zE0JT5*9oZtf~eoQ?jFh+`W~=*(6Xr$*J&~#o0?n}&f4oDiqo|xz>z@Lh9u0Tge`4T zNydXGEW=Q9dn_mg)8<2cL+(n~!M?%jm9CnCb4qfsvO>u?B{`V%Fqb^O6>R~&^2iM) z%W^{-aZE`Lni2lAkz-d@A(X1pk&;R{32X{WcuNLghrD+R@X9vI-xaS~ zGJ&cDw>o0^=(13>sTEjRc|So>RkkN=A~mw=IFi;*0M~dN!r}&N(!(8qrQrvA5e)7l-C(Cj}Hvk(chV2I6sXUkNaz(FDn9d&v z{er%?gDesR3_~ok%S)TLWt#1CxW%?*&P4ot+OoBm)+;Oq%E2)kCDef?FRw%c+dY}D z(u=O!JcAdAq=`L;$l}Tl#14qGn49lJqz~D=qdanplV!Qni|BQ`TZ)ZHi@h_Iq|D#f zZZh2Cl^75Y8rgJErBs!ItGF?dUih6jye;WDX_&}!xv6>ug$wW~o+@AKB&17D2ZqRd z=-jN=grF-`EbD(nPZHDHDpga~KTEdm>-JwX;j5bfoey8*sP_k`5>zv9BSe@uCqU4y z$@DfEr~ieP&&|{f;HKRd4!2m<@MTkF^HB{oJ}rgK7h1`oJR}0p4 z;P3kM-T7Q=2hw0;-IB(*xwdTE;3^(V*0c@eQW^u$SaDbFsP*^L7N~2g7qyk)y46&N z8wj_LZuq27Pfb)JjZz8*1$FOy37Azw@2|Yt{Rq0iGg+Dhy+TDWyGCIhhG zEh}^__$v_Yw0E4IHU$tN%3?RnX)G^yi*`zS3>`xw+Znn?rVe&Hf03PAob0RsNzH=3 zh(CMHYQG|HH}hwjI-!AcmFHFXXaHySmpAT`;ji#?cPxBc1DxC54gOkWN3yvG-&tJU zUl`6;GQ~Z|i5K$^$&?5i*^7KPZToLcrgp>&nOcq0nHt@Vaa?G?w>$i1?j=pCsvW=P ze;g@GCmszN++m(Lo#NH{qaQ-#=#PFFrz_3DrJYHYJ5zajritw?6cY&D$7=$i#vo{f zB@jX<$z3SB$t~~1vyZz_u9rZJT7N_yx(h{aNFWrClq3)+x=_B>z;X?6Fjzz+-C$9I z7+eUf@xIG9UD=&Xs~GcrfsmeBFNZj*m>lup?#BlC;p$x zSA*jw`F;wn&ab@IT(1vSy+reQ7hPS?CMc|)CkkEc+_%xWyCB>#e%?r^)7J!vTafbk z(4mS%vb>84&#ofGnbJSc5+8N+n&2ay@oeyMy(xDK^l(6^NDoVWgz@eO30)cN&hv?* zgAa5}hS?hxYhs_UdT?W+j$kaQQIZQ)(5tsrZAC|QPQi$x2?v*`yPKU$hy4bp(gA>7WZAWo~dOv|*3)!rb<&tD+PcjKj2(=81FBr_VrUz`S zygZ+6eRr|b8sg^=Fj?A*r)NVT(}Jdbr3K@fV-;4-@!oRF>v*a;{(!XLc9yq1qy^=M zw6Ed?%`rkB%Gb1DXJ2QaeRcnkP1UJC5YoOb-%Mo#=>+*^lHQHVmxh)y%OjWJq5#YH zDHaTp+E!)xX53UAZHv>NB^6p~Nn?~j)z6wlZXS$}22n(;Hqc;Q(+%VhCp&4cKGZuJceN25u$JJhqRgR4Eu>b>KWV6Oh*e*E|5U+n{^jn>|aHe@U$gQ9k* z-dJOP7C*n%34jfq3GC00qAlEjMZ7UX$aCIh!3+M3WGIBv5 zC%&z1Ozx9S70$)Ca2}7z(MaaVU&=q|2GYSJ;eGQ zz1fUsqH{&VYCdmf&uJgxBrnB zK*(-KNdLE?k7P-OU;h;=;{LoAPUL#GuJ(1NRSXf!pMlQv>8>hpp`? zWlvvExTC5%nauW7o-en&FVDW7q<524)@M_qwLG}PtSmRADix2EY~->-F6eG&*?6JW zA)77i$eg&hQlz`=C`y$Li${ND5wXKm_$L8PRMyd2C5mNmUv8_%`9h`kKDMy1S%qSz zz23KPOvM{-Ah445E>yhS){7uuu0N3J{^6M{vcMB@DHw~k@kd`6tyUIccX}4t{(x^M z&J`k`TeS8AAd=158b)@}z{U$#cO+WJMv4z=HQ@==NG=~PV|I}@2~OEuc2PFJXjN-A zKi-C|dg>$Vc)PYrAwqtNrWWK*KoVSyrFOHiL6TijF zDKWbr4kk?Db?)6X&J}uh>nnvT9bCnqY{}Eu7G4iT8_VkL!;_FbH=G@wSdkqryaDh0 z>QT+)Mne>@!l!UG5A^q{;~ZfI*1VcqiAY@GMWezsieIpqKuz^NrYS+qkUuWfx=&JL zR7aoD$`OPQRNZLOyIHF4yVfg|M{bmhdvHT*1g7dnZH-_YA$8dn-bjY-!>tV?ESePd{zDYdDy$YMC#3bSKwS}eQd+zXkQndoyp#*d(}vP<0T zBVOv?O$-NBAP91QGze?23vx@dODFgj-UQ}LSGRO*SrY_ab#y(w>EWpnCgrMK8dYa^ zJEl_tQ>$_|-D#}pcYVL0BiHXLl{HDsfj$MPraX0E=LlOLL($bo>=WFOfXlaea9E`{X5)aLhzI5tG=4=`*3%_-94E9d?jNrUFWAU+C?Ea zYA~@$a9j^p=?kBO3HNF8d+PEB<-dw|h2PE9gV2~UmFT8?zqkJSk#Gz7JPL;~ zBlt5v8*X`DC>eglB-391z-zvM&FqY~4X^&k5QJo@!OQrr=5`VtO{NpOaXXbV-;=ta6+3if=}o zE1O7#e%xcwkv>6Vjf8*HY6rbSel&aVW8>YAcK76~%d zv%;ZT5Gki9o$xNa4YVhogD%xqMeT0v-k-r)_WlfXO>LLgCh?5ahY_^jIPB<|&3JFu z{JU)upW)l?y#r5>OXx0MD4t+5g4wy~rLt|{`{!ZA0)RO>Hbn+rb%xb6;D z9h81m_z&_>z5YCIxLzyFoxo&AXDRPNJr(O)ymvZeMt15<^isbcvU>U=yr`!y!J(c` z5r?^*-xEDtsalT-Dis%Z5wP&Ec!VvG#44=T)gZR0`2^Byn_9*f^mlh?Pn7Kz>?4Ja zpcbA;5))kWEZ5b-arfbBp=D-Ou4AGv@Nm(q_3))c4DyxQC>+SdP~~I+^OaZNterv) z(xoy`Nf1*kl~Z`5Mr}-%UdEgC0|ZKzUe42dyH%)n@MN!g+Iy#7RI{$*g{NgOkXCUDEE;DR%G(fWn8I&;KqIYu<#asPep2tn$28(SiEpj}v#0#7 z%XK8P9fgG`%*reIj%s5;k|l*xMv8UyX%5Sz(Jzv{H$uJvUsu7`WJ%G3T}v({E8kt@ zH8B_jlqz*8TEOwDUXnZ$9Y|{Bmm@jFNhT1$HV;4(UJ`3JDqx5qm2(qzD-5H%Ei{s+<{eR2Qszc zYHE;+V7s%ksOwqa-f9+`{a_i6z-y zYpNzYczG>abb{HltY*)$*{njicFN^jmSA{)WON)<$lhv&3`yOnbf9eno!L%FG@|X4 zL9CtHDt(?qDn-?LPOhGXD*3>s4cd?IYGt)f0{j-hpb5(f`y5+8XZF~$^w@LN9o;p> zF|cM$Eu}SHP&jm*Yn1DSWAtwdW-(%At5oC*sGD`u3YRS4Cif{D~`9?=4YNZWC{sro}R zthBs`YtlO30>uhZq#qO#B1fy4q;kb9e3+7*yM=1Ce6o8*U%y-0%Jz?EWX>v6WH~!E53G#y6Mo(u)G#!1rB@ z$LK31SR+X+Glfd^DOfsq!y7Cz|Q`NUV}PHwGprw`Y;vqaPg`>vs`M|Umrz(BtE5ml3O ziNckTN=4WdKMJrqa4MRgLNIW;BRX(852zbBou4e7!^gmeH&ICM7UCyMiZh-1AKr@} z3jsbZz^yzw`U@YSfepr08cx}`O4}+z<0{cm8#G=mw_M<<4H_R+%dE{PT$Be$8#LsG z##M?(O4V_dkm)GgMz&K2H`IKU&_+|?;ZwEt)rjIPgEF(VK_W!74Khf5tfcGvxPbqH*M? zUL|MvR;y%f>_mH<_&ER1Kw_;cqFJnN{GRt|{+j)-);&HGcktJEs{`3rJy9J~T{@J9 z>XJ@THP)q5f$845rT7PRN$ic(rA^eeKU!TnA1{QefT0(M7! zu|kL}+xJl@`{50n6v6>GKgXJmLNL!&vVdhpyqKBneBoe*Y&HlvL@$!ZLwN~CBg$T* zhfCy^U&B+wd$w$hZ~dI~MoAtT-pid{^jLMpSn){dFj87R$zi-2q9K$D&&Steema#) zkF6aneui$Z_P6ff^F{Oefi|Tip^PGrc?h?f1HJ;%e% z-ZhT+Doz(`SynJkEJ9$t)(=~ECp!vnX6CbUA>d^3^OEi@dd*iZl9MdSL$i~q!WZx? zeo-MVZuI(+pqBy_-}g_!|1JD)3v+8v*1GJ@?aoQmK)kQW|?=p ztqUDf3^a^O*!E0K4=354VcVKez^Te>m2!XOa?3y;9<%kS{9Zq?s#dN+t~!bAT(ErQ z^@LliS(Rs9DD4hwQmEtL===&56e6edS?DRoBFVA|T1j%2UAWQnp;9Om7iFr9=3SUT zP`rnTmMmGxODfSdDq=WTc42W+0lzBIaWP5Q39`jtu#xSftEJf7DyV0jVtfB`nl(DN zi|ukG_gT|Q+t`}WY85r$S<%X~eQYrMb#+7^oxyBD9J4Lh{|&RX1+GZ}&Y&7Us{+)S zor^F#H*aQJk!E3bZYHyH^J2Cr_J7T6ZO>~$t9`6pn@?@8)^5k_lBrfFl>d|%n_f(o z93dq8MW(w`g|8szRW%R@NWW>)K(lU3U0MIuC!cv{YLJB^j>}2kPHd6=d`YUQ?NY2`je%F5V zyGFtq<7~fMc-nFPQR?eYhf)>)4}0$&A63!)0ngnen@xpHU=wOs>XJZ!ARVMARV=iC zC>;b8iF88VlQAvY$#GxK(RMa5k)NUe$P2~&fSE7Jn!>+|9L-8 zKA+8g_d7FZ&YU?@?%cU^@ki^pCHU@xR)j6}yoV2zNyfh$(6!!yaXu5j?H|o^z6N&) zpBBLG9ERowQTJ(L&Dp4^A9xP42tnvymf#Ef>2b>tM}_&ZaO>`LaJTG5CSt5kn2E*H z!E&oIzUU#C3{#ftkg#AD>WmgmLyvp{%cn0%-Y^zK>Q;w#%oK4wANz$k@HDY5&u-2;$Sur$+7r6f!x+v*&mg$d9i) zd_@LA>-bH1J~&=Tn8zswPhw$SZi#aJr9{Hzi-Z=n`_=d;`A7W5{=Je_s)?=>fdyk~96w zPF*?E0M8hxdia3_7Q~8UXY32eQmV(J1CbZOmO85k%iw%2MpPY8$zviLDJF8mPLHZ+ zWBa)dRw4*>a2vjo{248ie392sRmH3E8@mMkkcDcQiZ9g5tss3x6cqV`)^r-AWA%qu zx!jvykqo_PI}caX!FaiOj@M#R8m>{;^A7s)5BV@Xe;i5=bi@aJgMo}ew97AH$@shvBd$F4#c z-YiKuv9F+}TwlR_Q+$Os$wv7K83K0^ir3KhtXuFuEx{MTP4JvOKXK_P05=+D(RY-u zP&!C*eI+Vi_7%(~-3cb`FJoV!HSs84k$jVj?}8jRUmS?AfqVsHCnK{=7KW{UBPd#W zX*x2JTAxE3BG7}*S#Of1-%K^@H<~K$SVqd=92Sa6?KIj(49z_sr^fZRJyS+iIG3SniK#`CPy z^SeT}p?N?tK7Zl|eQLRQEWV+SY5g0>Af9AOql6g$vqt5%$Nx)nYqkEkY|x0@Q5-a- z^+pczj+bbAHOay=T9D;EN6^-X;4{@belF^rSphS-FZ|mPA=dJCKW$J-?P@^e;S4~Z2QUClL>9` z;!sc+TW8UCl=D(LNOF7esC>B>=RJlz;?s6v?!~D)cb5rh#Fu=y9r+#{6@wLP+8@`r)Pp!t{uQaLBP>7A;CbHU3&{A_;5tAK z6L5`7gD&4(_^-`Nt05;bFBY2qzcDW@Se(SXojKoAG4EkCvS@F_0;y~fjvU2g#I;v7 zvR51?jsLa1Xz}1A_PUz1`TxQl>6-r}_6l;o|8B4Jpf4EY<`~ry zQVD$@t&H1j@>~<8gQUqM5>H@xJ!BB~&PmBqiu7bf_}GR}S}4NDHqaR=?y(JYz=0@I zx%hcXD~eS1MV=)^!mJ1dq*unffAJl4Yy&G&nU8Hq-m?<}B`WK<{Ck`%fA^q+lPGW} z#QKU(MS*;d$ra(5q_q1qflGx7PGAf7V1twBQt6a+*#d_-(U>1XA3XV1bhmQ+QdIYh z>(I#ooR5+;Idr}T2dz=PLhMtspVbbNd!pF@bwkJdCkhm7a~ zMzF&PCm8I9C;N&vAr0PWjsK_m1U+R&H)vm>?^!S6Ki!~xj+#2%z4sMS0NtRa@2D9L zrSqPkkII*4IQ;A=&u~uk1ij=NU;HZMxcTDQ(dQ}O6wa3d=-E;Fj+)_czVZY;U5C*c z)NxNKi+S)ia3tl&BHZmbk^V4MM)IDUVZyS{5Klg1Fy=c05Ml42X*npk>@7*B;|vSh zU_h_65ho|mAxu%5blvCmCSRawtEVSR?!jbPW}CcD!%Tclu1IXXs3H%EvJu$mcu4e0 zblsW z8lR|j9jg79$U@4It{45LHs-j$uur~*Ao%1P_`(^R2~kej71vAV8+?XC*g<^O!IA4= zl&FE>G840d=kXbgSruIz+4DBA3E$tgKO(tLgHOiq#;B!=_?CmuUdObxU^A9tLQQg_ zBDKl#67d0PZ)m)Zu@oeSgq7`XqaHesq*8lv5BOSwJBfI)I7;O!dK3R6eV_o{Ja(@O zXxhQo1?1~d*9Bw_YBg`u_pI0PpIXfeYlYBrqlzcrixSR|igWU5)ner6Q4xx(xQM}wb#AARkRxUW z!I#^(TQ1QH4nPEU`r}qI+7$^X_?d(j9d{xdhw+JQ52F5x-$PV;A{*gShCPJ}rK^;a zROp518cwA`_qtC{kP4+A%wxu$$QI>nr%>s10dkT`--IeV)ppBwVQ2?Wr)BsT-7X?& zWM^AxR5RF49i?Z~VQsWx%(H%4>~LrObon7VCKYcb`%9;W#SV-eI829iqySp%pzo+z zKc#~tI;`!4Lo#muvHRFV*P=8#h1#WSl9SZ#Y>YamQoAruRKEXObMCdsNvaM{^qg|l zl{-^G2lo&vvh3sRG8sunjqaUVYBGYOa!)uJNp&Y2JQ?A%tqUSD_za2c`S@@$ROMN8&FSrRX2G4Y$ z%PY7MIMH7@H^B!gQXko6Pe<2yfRxDK-|jE$F(DH3taRKzJC6#6KVKw*BvEqlW@yAh zZ%AJ_OP+>0&NZfB1#1u${{|2R^JwWQHf~TcCCDb(vqkiYWJhJjOBvBw#bgf4Y>w?o zlu1@%!8yo1rs}Y&&Fi^|D92h<>BIQHMs3GYeK8rsaB69!4p{-O*dX&}&xDDR}DFU+^Y66knt%TcjEO7j%aL1xqnK^+o7+R>;k#_z;Ok zIjOvK9|=p=ZXHF}PNL6W8MV?FNudYc68$TZp<0MQwa_nA3w>ExtJQXtUzDkQMU*Ap z+d*G$y~L%)4q{DQ?K+BfT%dXdFXAZ?yba(5dRSyJN*7arn+Yfg6}NOKp27CVGz!E- z7N9QiXxIrO;#q8eEZf8C+vVi?#+1@G##OdF15WCX!bRp_ zeK@;YjGWKB1@*P4A)2W&ZxMqP1CM!|7`ip9ao!;Y2iAGayTsIqX5J$PYi?egt;E!e zX5J^Jel+s|F%6;_T!JJnIv;IVbN`T-M$vJ$6O&C0`BRK(=lWC3v~-Y({>Gu0tyvHe z`3?0a1wR6m9hrq|34cKWDwwA~k-t%WizsIHMAtw&AX-x3E=S^`krXx;u73th+9(8Y zCP3p9Uj-+U4q?T+5Gy;TsgKtUxZf!vD$(yoI4#cK7%!wK6`vmYG%;ugHL%`*%Q4&! zl{87AB*sVgrT3;$fLkdRizQPt8=q z`4pk_P$)ekl%5$%uTsJBM?QiQ=~exW5`3XZH>e!S#=8ze>D6-l!D_g(UM*NHttyw` zv|w7*V6{jHh3Ogj7^xcXWx^E@tTreS?+eIji9@7V&}@tuI9AdZ`2=x;iNT~iFAI9B zK)v)Nyo|&u;EU`gMdIm(1j7_$y9ZR;>tU?4sv*fZYj~#;{Pt%Iib{)%Je0~eYcHaw zRgvU4u^!QaArv9x#{mT*zJ@Ebag!*CxwUQ5P_TNiMg-T%{?Zxb+!d@Im7foh$W+0q zlaI#XUe_Tyw`CKiR*i;PNg1A`%xG0x=i_LQQkL-*iP~v3aPk5!Rj}|wM6e23sLJy6 zhQWr7<^&sNbgGV&C=b|BC~tP@STtpiPQ~hZ#tnuVXXcz$D%)VF?KB9?Y2nKV#`zu%`98U`=wGlwdNdDLJR94|hh%u%$Ik4yLrf7YTzYRQQzWoTXL+zRaLs z%8-$qlTj*lpiLZ#8OX@J3-2ZfrDwsdvyfR9PJD=R>#VYFouwLPk%kp=8e{)QIl83= zD>S(-Sb-F)aJ*t@kQvq^O1WhQRLF4c-S$~D6wlyNA_um_CCfPAF)bSg%p3-gg-`Z|w##%8k+!=RD=B609SwMN`(1l(@qb-HW8x$=P7-{*~vU!<+B22}8kfuy#3iZ`~Gk z9SoN;eQmdwsPD(4n^C<)gJ6TGUIM?`^p;+tfjV*na%Ai;hEJsZi#w;MrIq8(X~FdN zzyFOcB%P`uJz6j8&WIA_&c3R_`ekfXUu=ZeV3e{^{nisu5!N6XYLJ|^r7BEPrBz9* zofbZUx3rm!E$Qg#b8p8R975?4wD$-~90^9E+k2#JdylA@B4nn@|6-uZcqPN}?Y&a4 za{JyW2Ts<4fhw10AVi5WP%xwM;MTSNLW&GZkrC?n3HBeCw#~nIc21R277AvxJ%e0& z8RXbWQO5aspICfa3JqvH{m4pe{;JQS1F%AFsS$oQnKtc zkmi87=#CFX<L3I1p4&f0KCEQdp{{iD;Epm4dO8|Dv@6nzvMoe2G|% zX=-*BijLV|m8khjYzX@c$=zv-7fsM)T7@l_$+WT_%QLjb)o53pR7|brscEqp;*qg5 zh}k$bqv8zDnqD>)QjA#)1&x}UDlF6?TrT*w(cXKNufv z2*jhZxdzB+34JqMeRIny;zr7BmFGojii>17*9&govd<#Jz~7Lyjh-F0vBj22ff zWjXDy>k88B#dy*med`u{I-!dhN*U>t(PoLKH+lH1D4m!q!=JOnn;~n|xbmXL<-8U^ zr}!4e88rNQ$r#nNRYl9R-J;~cuIV-7GM!?e@og%@c7*>|onr2kv(YL_;@sC(&N1ja z+g5OnhB9yn7@wGu-PXse0`yi^ishHJi5PxX8+Vj(JxY9{zY#8&a`QsjzRDaC&l4tq z&uN8w?EwJ1Hx>UQr_%u~@$@@`qoJE~<8yeDFCHBs#gzD2$$1+?v{*q&va3$%fbME$ zp8Q-?h{Z!JVB}K?5-HriO_NK2cWEA1f+UKC63{vTuGZ;!cIZ!RDgB9n{;Bw4{Zr_7 z5&ewd1*rbwWcr;^Owm|>j?dwuKgE>zHS;#H`jjF{efnwDA4cj|pzu=aS1Ct*yh1`M zfb<`w`V(DBeMst`?~G!K#`<%74j26?ro_)maP3b?q6$Di zZ2`tn0V+{=sRC3jrvQ~H)^YYHwv_%vK>tj9vHltKyNG^9`jP%2`khfs(O7?u&*7p! z#gzC}T>DdsDD~;5ReutxUxmU;sb8%e^{Y}WY5=AEhv-uJ69N5e;EVOIPQQ!jXJjzx zUyXie6jLep>x!lKwR*-0EK&(~`y*=CcXlA;iRN+AEHy zstuO7;O}8X#@)O+_`+UsT-&$9j$*?X^_T$#eep#@lJMBsSS-vI*F_|dedCLUg2N4E zA_~SKDDpOLXUJ&*beD(@3!tO$`K*ABypf*r@db+O(bufn_@CzUMP8=cPe-WtEusL- zE$A!iqyS0Jx@7n02=utNMA@BTP)ru-&<3L*6(aPBC;RNFfzQRm} zb{U4~Fb6r-3P%Ru!4Qn@IAt%fu{?^yI^xEEeMoba#NyK{P;&1;y!{F+-SwyMqm}WT zhwRT|6=Neft}U@G4Jg?D!H@Z~FfJ*k8+`I2@ACI(`;Z$wJAo;RX^ziKeCVE0^fMB) z=KUx>@8W|yowNpy8j@$Ix&sZsC+eOE**5AKH@~TfM89)gH$V{T8ZVlmJ?|uo@~no; zZ^ox}Tl|YJ8iuMZ^1*%rBd?-nBiG;`oH!dP@s)$G*7RBtafabY;VU9i&Mo{J)(#ci zOg1M2uLK~cz~h67jl~&A5j-)_h`we;@SmoWuaYxd1Rd~J8&G&?fWD$m52I9&L#Kyf zAI=EP^_IrC+vWWXtjU=X{ri?cibG`iWK-HVbpIRQV^fVQHN3}f8JV9FCnV8bqjaMv;-3lA8yq4$D((>%t{QXft zdKPgEK5c;ISsQ6z+u@F1;1kup$X(s`l{;lF^|$n5Z|!e+x^Y1{<3&@7WycF|ye7KT zctHf(SaW>wctLLw$9r?|Kk_q;7tQE*MlnU>@q*)XxQrJRQ{wA*O-Z5#4EiYphUwC; zR~Ov)K&g#M#%KCzxlMDMH*MaG41ottCg6Xgvtmr0+2|wiJ^DJw)af^M^7x5kr%;T) zKaMrAC~chq{qXW5^fln>bRW?3Ong_wZ-e_ob-GL_7)g=w=fj=efuBB^lR%x?pH6eS ziA=fQfq(RY^5*J9oDrLZb4Bd7rJAo%1%z%gSDaxj zd5z1Znf*B4Z6fX(`pq*)AsQUUgQ*|jdeCpaN4|p6uaeL-v%bA!p2B0@escjVL@CnD zmw1ATq@sT4Cj#gR+t2Bxg@+YoTL} zY}&UVnLH6S1N|soHRo52hg%47%ItT^Fno9)o0 zBU|kv*Sa@FS?#$$ZN2%mR+m8MkB^dtrX^Urw1!v z`RsrVb)yOO zE)e852MR@UOH;X@WBOH~Itrmg;s-CH&zF|ht-9E>rJ0XbaR`#q%oxNI{k9=rY4K;m z&V<^UU1;Y=V9C?X!TWHEHjv-+Ldk_&ihe`T_h^lRHo2&a3Bk^j0@PpKFi85QMDq}& zP%o8czQ$9LlJ7W-Ejuu9`%Smmg3`=mum**unTr(_pccd$c^^sIMAK=n$n%?X*I3V) zH_m#~^e==M{eRE~$yTri{6$*8P1w#PK6w**R zz=T2Mt*T=sZ>(j}9a@(Y8d~o127+?U3gjXAJ`1anl$PdsSeM#SOY?xDKnu*FpbB zUYBaNqU2IX^<7U)H8(5wsoSeO?hHY%LVtI~c%$<#XrbbDZ@c4Qo~eOyQU8)>YE7`H zgNyv8rQ7e;H#3ok_(6%H3s55A*0`MC^hNuvjyg&;?cDPE4UXn>>tifDR%+&Ex8Ay$ z!Q;hWa`C1~sq<_z2_>gK5w-71dEBw($Trnh+t43TUh;!%vjTk!rD$oM!pxOwFxv#+ z<056bTVB6ess1;@MY*O4dL&7)9oC=}*`^&Pp@N=9PbgBFAjKs3^I-EU){dxthnfeV z^Uz-8_p=JD-+zIcqIh}SXOnd?N=~CV_#M|q4i0rW$!)*)SP!Ax56-HdrLp_(SO9a=JG z1!$`-hY_)^)oO*J*YQ$PNxMwZqctsejf-lU5c+kASI=+J5?2?$X^s-T6r|jO$|a!< z(Y(ZOhJI@0ZA8BzeZ&Y@gL;O1v+)c&cf6>njrXx@m($H6_%789dQ5k;A89^=uSv<* zViX~*+L|{YMOx^YEv$ZR+?wfP`lAP?(aLZ7t9~P zDmg5KdKBcK#}u>`9!B1jXG#!H=2vry?2MxEDna$lW!kU(?s{07N$p@~BSR5S<|SXE z&mq*(EP{2{ji%Y$j~I_+B>BL#uHR(4EjiCz3kyllu(XD>)wZTNYFd0U6}gdqEzQsK zj61`-0`?=xesd>!w}<|wQF0y9(#X`y3_`up=-11{!B#R3K8!Xa0!xXbUuc zWSiRXMCswaK@Th_1d9vmfKgmf@m!1ix7t4O>m?R_15c!Q7nwG&2F(+*O>c~il6IRL znr$jWE8&8!DQ4V)7D5+7e$yGgL05);^K>n-3yqKG=F)%q;}sL9qA7_trY;sf zhA7hzHWS8&^@b9}8PLOFrVzj5@IYThb31yfN71Jg;&%!&xu{KH;xXn^oXThm#H=;} zlO=I37iOQSj2-0^=MRiF6z4VUpua@opq-1H8fF$V&c`;d9CI@>&yKTka?PF0+>v9M zrsgg%I7t~}7Rg!cbTuC^Ga42brl-q12wxKBY-TE&MXrXgIelE_hei@-yED$zcSz^0 zxRpq$UNGbE-XO|r8UBf!Ip%(jb33jqh^gVb7EB53JF?+BsbQZ^%gFcZN=UsC)7(5Z#T$oFJ zADHpXJRQYc%Bdd0T$@szk2sT<8RIfTz)WH0@A?vFJ8UtHnb_H)^Vz<=W;Zi=Zb=Kx zFXn(R0XiRBE^!>^i1{6Ha9VYtFpJ@I4P&TWmtm}+-0yc{nZfDUH`_RIPEHKTS&kln z;y6xIFjV%l+_JCt zr?{2KY2i4r!t8ciN*kwQ>|)lio~z+M*gEhSrwY!txu?ZG!Ax(>Jrw&a=XIOro*BE5 znSwbs_srN=n0XqmC;C)prlR>JT_nF`>VPSMHMXIo?a{&uO@r9o%nTf2nYOWC#E@t1 z$H*XY+JOmxdDLYbr+w_VoGNaBP1P}WKc{*Jb1O;JG4>~p(*n%Q3BEOrL_FXuStYn&lq$YN_@dC7eUZJkA3 ze5R>}`b#+X3R?CNF4IDpk^XtCPY+yoi-sfpS2A&Q7c5q&UqnMp6XR=CO=HJV4j%l15 z{rgzXugcuv-_OkXs^KdCPs}7oF+VetujRVae~|N9sd=sTABuJ03Fl(`sf5omwoI9EdgVq`^%;jz>V*cS&YxWp7Rr~m(T#DSFmGegYb#c`9R>7={ zBrkT}i@zmq4m|4WTpMQx80veMxp@_u9r3p^Ge$Ms8Gk3H7ZuH4EwL7c*;k?28NY^^ z=W%^1%1^Zt5x)rx z-r$FMtE4&-kK4qEQ=yM#f`KoXng5Z^>#RT>e+evB8M8l0bxxpxpIY4e(}gjnZ=j8z z^qGiuDslP-&fqxx-Q2Oh66nUv6ZIqxF?jP8Qtfw{LNGm;c}1E2f!@sQP$oZc9?QA* za+~|0Kwr*%k>)-qFp}d;>|*m86d1*FN?fMU3<`{8X09^l2k-6w_1}^6~f1hTpF*0x^Ghbb7#0-vpju zhF+Q|alQ{c#Y``hU6>yN&oT2>#4`)v4xnB#^?)eOmTEv5|n{EYLWA1wzbpQs(bK zRc6L0^G_g)na!Hl(Lh~hRx5Li_V7aTN6MIlM$B|p#!1LyCa6qoLJMY2(~`y|v|;8E zwV^+u12Y@76!8gXFw;uo1QL2ObGz!CkZ>+D(=@NdgaOQar>T+>@GcDKd_*NDCk$g| zlEz6%7{knVm7JPzNr3wKJ^dWh0#{RocpKrQ0F5b)5`@9NgoLR`Wy~P=+OrVMbe3}z zGb{QlG_4b61Y#iPbzJYz-x$*-;ilu5mCW?0%~8;+w@J8_nU`x=rc=W0%OW43n!)(hGB|IsN>N7XtX=dgo*f>`uJj=}bDweq^ z;dy3WR_4xx7nylRC9h4`#LSa5ZK_8SUS{Ty=JiyE>aB}CVm!(30E}p+DM#2 z(=_q(GEDQtuLE)L%-ZPr>2JR8w8ZaO&R(@xtHhs~X%G-Oj&pkAL1u2PWtp=Q4>2>h zj%E5J{?5!Otm@OB;|xwjPlmjzhb=Qa@lR&@HMGpw#J`!z(Yh!|Jj%?RS~kwqM3X>r z&eGZ`O!P6cQS&NJjAiCy&FjiUKQnbBR?fAF0cK`uUds{_nVG1ixGga`AqjbPz!HL-%p_v1Nm4zL7-HrD)o@c{CNpWcQlP&=vpKP9Lh1V2 zl30u5+@u=5omhvNbho#0oDUM~F*5^qxaiMuK1poA%&*v?Bh0?UhRjUS-1jHuFms)z zI*cbu$f_eG5#$_AY{JY(Zd1XPW>Pa|#we4R)SQ_N%zNn%J!Db~wpjJi!Z=QPQaffY zP$ny>3p0hvG)U^n%)wDMRnw&Nm}#qVnkNn9yl!-R(L&QAX(%(RH20QCBg@#ZRnl0F zgA-@@5B+M=#mvx281&~j-IFFT|c=5u8xClxVMSzGSqNyW@;kgTBL)k#+{^G{=;9A|0L z9A>TvTV_?#JZ83QYq}@tN@i-PuRfTxu#9~kOS-xY^F-2OPBlV(HmbLQ3)mW`=3s8%lnXneMo=Mt_b|H~DF1E>}-zoct^^BgLT*r)Bc<%%td8&@uT% zW}Z<`=#jjMnHxngQuRrGnVF^Yj!J%=ndh}!6OuPGb41HNEqMzwKWj;6 zC%?_idG#bS$61*CE;EVRXI`JYm6CbW2CGTLSxsHPy zl6Nr^scqxDko++-6EyeNlXo+-Q%mt)@?K_Isy@4tKVxR0>a#ETb7rnr%YUEzB{MDo zeSS^;8Vpue?vmWGo|*hD$El#>pg-kX_SFxyM+&6uFXOAhltY{O zm+6z@XXbWIbzw??nTBeOi7AQ9++E+Mx;!PBnGaN-MJcJwY*C+Ino^OOXVm9crlc{` zPW|WZluFF}sxyZNQbNr9SY0{lp*^-`aMigo&?Kb}Difzd z>QIhzpKFamQ!#Z!nK+eF$8wx@dOcV<_2M#ds-#}Van`!6pwLuJol+)F&D27Ub3+$f z@8Q&9W;Q6(AoYqeshXtD;W(a#O;hJ5HaBM!W5?JedYXjLr;Prdsi@B>VG3FSfi>Kf|$7GUn z3;z3jBxU}Yf*cdu7d!SjUYBC*!bd3XzhZqTnNu`Zw5ltvu9;hWxz%s^RIA^OX+oq?U_;KJtauDG+!p$AZ+=8UF5vgq>F5SvF0 zlt{R%d*i7CPTOT7R*iw0nf_S4>dGi?7vA5=p+hnqwousIT*+}Q^af_9=OENEDvUTjMdgk3J? zv^zB3EiQ+3u+dh7M^Rp>{Ur5+^=k3=HFO&^l^QG_XLV^f4Xu$i*rN9913#eh_A8k` z$8jZ3yg>Y_naTOnqFLRs69sAg{#B<*iu{r>H%ZOpmn@iN(P_5{DjSdT%`a(#l~O?u zOcYc$-b{}762=2TKd->^T�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�{0)dx8Od6`z!8WxLr#* zorC)#?!bL4>p0xWxFOt1Tp70pH;OwSSHabAm*akq`xEXt`*MmISH_Lt&coGkZ^hk= zyAAgsZrOfVo8l&Lf5AP4J9-(X(Q&8X-i%8x=M*(=6gP`ozCX8G;NFS*7;c{fv2{6! zlgPM(aL>bC{4DO$z}<+u7xxF;6S!xd&HE0fK5%*5PTZ$(x8RO@4t0mS?hsBytRZo+*7_cPo>xF>OuBRCcO0_qU=W8B|xha72HXX3_jQ@HQq9>JaYLQdJ^UX8mP z_c7dm<4!#aD-GO6+~v4ia4V0d?r9z!|fUW}{a-iP}vuK!rd z7k2^fo5%BR+>daBCvXZIck_wdD1bZqB-#pY9qxME=T;CG_gmZ(xXF{L7u;KM&)~iu zB^-AT?g88*xI<#3gIkiIK5?(cy%VF!;DTwM#qE!~ z2-~g8aBs$aANLd7nHg@Fz`Y6!s@LP*j{7L?Q@GFJa;K68!pu_KLAcepDO?>lk9+Y- z%IXaAawdH)PkwOYxH;UVxVOsx0w>RL*&^)>SI0eBqF&3~On^HMm&Cmi_etF6a9_dw zU={U^3!i0KN8m2QeH3>q?hf29gdXMuJFf3+%X$IsMXRYZ+>3E{t|9HUoKnX@>B4j$gjX>MIHm69T~WF zsdaE<8Tg#Y;ou>WGr{LZc7o4~Tml{%c?0_+1M65e+m=eFFxh0m?U4%GeghaJdszrwE2 z{qu*(Hyl&!9VX9hc$NE9_qT6*ZSUtK%{Oz(%Kh`dPtr_1<%V>p`8;f@+&}+wlIBNF zSv>41ENR`ph0k5~1~;VpeSdjz@8=GcZ{N3fnB?W7Z(cmjtLlqC_rrS@f9_X%`G%Kb zY3=?k{Jw7&88L8aAh0h)3YiD9d@4I}fsE9<+XiyC2K2U!!|@0QVc*Z*jlF*6jD#g#7{cAnqaD!?-`<9>M(y z_bBc$+@EoexQGG;Bs?StDFw;yhqeF1B| z3uKMQ+U~`y>0XLN_fqV6nyluwvzFV=N^U#rxb3XswqwJy%Q_VIeEXHG2 z_G_@@d5v`>?uEFca7W{g!5xb`4tG561l)gxB{+-E8)txRk*Wo!?+jW&c?Cs zVWo2oE1YXs*<8bl<{DNq*C4rG!^-6vRxH=BQn`i|$~CM^u3<%T4Hih(Vt;fk>ym4+ zJ-QalqieA{x)w?ITGk}ktKl}`CUBFuDsD4w3bzGU!@V506*rBW!PRjM+^qe6Yn%N6 ztBGsbAHp)}L)a}|ht=YB+{m$;Th(@Br?i{f)OK@|+HP*>*v$=UySb%fHU08wzS>YFzv>IX*c#uyRlx{jqTEIESGj;x3n9prQO&p?Z#qhH}*=qu~xbZTcsaj zsq}379k_Se2isTR{u7&`D{=3#53#Spz1x1SeKqdC>?7>=*hkve*e|rN#n$D$IBq4f zR_HA`%(Pb2p=4zsH|NOT#UAx8k9xoDhF;Ncs6Ok_u*-YGqYfHydEeN_jd#nwF7Mm> zzxRXY9p6>Twohc*I?28U9I}559ystv@Iw1Pg2Ucn z#h)ww4!qQU9Q=lz>_=X=FA8z5jp8K=M!uG+9+# z7kC{wWWNKvA@C9KQv2871A$9fV>}pm7r4#-4)|z5(!bQ+wuIYj%gA^Nl9r%{uC&9NVeR=SJeaVmF+Z68& zJ^<&@;Mx00%9km=1KiM;TPCAB0E(I^O-wYlZT*KPpQu}=H zg#MerBZFU3&Yj?@e$>T$(QTkzqKl%cJ4WQT_`eK)4E$O6Ch-37J&L~o?+rhs{Ku63tJ41j9|)iL0(t6m@Mqx? zcz^h8#r4WLAG|j_uAG{3W)*iT=T(ZY15XXU7yO3(E%2Y3s~ZAqkCZm~X7I@1yTMEC z4=dgT-eCV&>8BM3UMQglf@cR0SNeQ#SNLmSVnDunn=S2Obl@6RW0wb49WAX_@bZCI zfmdnSUak0^fxp0cpVF5P3>_mW&ndnh+|YLgc>Tb~z?%oY3~sYG9ZSwN{@3@BcJlRo zAgZHjj& z-lce-;)9A$DB35=TlP_Wj^a^@F~!prhZQ#{RupFxcPaixv3L@FDzXvW&^H0D3O5vA zq4;|6gvb@(p~3fovB+n^BZFU4{2sWW?{|uiD?S5WY7edud@lH>@DYl~fS=#@B*lbs zvfws*4R~efQtBkiR0^X%5?}@Bp1soiF54g|Z&lQ(27acP=4#o!G zr+5RnC-VH1;1IZRP)_P@9F+5F8wcO0_=MtfSRcP)umlDN=av4f;@82;1|J9CG#DQe zJQLhz&w+a)SAriM{3Uqv;1H|mn+Ml}dm?X9e3#;974KJk2)uo;A8Ga8!K)R&qk}e$-zns-ahzt#cRMlk^fe_ z8$4;rGm6KYCUjJB9k?fQ5xC9%5P19G#})4cb4#9ox`bY=cq4fG;CGe&k>b;e1FZk| z8C(nQiToGXSn^lJ{xc}IC1c?2gKq`jvP9a$D+ZUHDV%lSWlKJ)^pC(-3?7^l{(0a& zgYQxLd*C+vpuCjB9~A!zUN*R{Aox#V;vmDSC)Kk;B1z2VbxFcExLy{|Uvj%96J^FtPO8;7LoK0{29YTqSsm;vI_r ztM~`-FFKpp_Mf3gmkN$Aoj*%Pxeo{q+^l#vxF<3&EXal)c!=T|iWe&0sQ4|#A1eMz z@sEm6C_bYYevzcGOz}C2FHk%|F{yaEVp(xS@jS(f;>#6ViWe)sPVudZS1G<<@#Bi0 z2CrE91MnM5rGD>T`ux>`$17$ObBa}PPvo77-&g#J@()=f@s9-eM1G`r&RQwqD;4(} z5&BGUVxR9TeK)u#a_TzT#y+#)p2&L@KMG#9&)>nulGDx+EGb^6_$l!A!5=IBLOF8J zP-Dp&#S0auz{rvt6h9B1wB+DX!6Ot;1}7pigWRt3$eu`POx}Kh;-|sg`}`by_7cIn zl-?70L__}y+Q=*eoIhELwF+D$1-_WW|wZkR2(5zPGs%vWX+RXgSx<+HVIyqWx zwRm$&GDVblB2^s9rgO1Gx?Ii-Ma$)Qw2;ge)1^eLn8~D4*;uAfjF(EKcq*ArXLH4D zIvP*s^0`v6P)P6&$(@%eVdz(kHAbt=?bT*Sh~##Cwbht!PE=Pl8}qa6@0156xZG$~ zYg2W;sU4P-FPUz%s>RA&WmBcq{>E5}pwiCj#Qa=!bfPj{X}>QWwG!FXP_B@QB@6Lr zDp4$#bLB!Ykx9f#`Ftiy-^YYbnaXoS^`%4D9(txr@XSb1a3U27_H6I*g~h52T) zTAy3jXw{@kt>kSR>JydOIqGCXeXcfru|802Nb{1cjcWTFu5WHMXDaR^Wa3;pFbSCG z`1yos(};`Jxk_!?X^^BZAGf}-gYryhwdla$uT*7^&Q&J1uA6Udkt}Vj&26FS*Dj&} zB)orX&ut|?6y|n!%vf(r{VP72Acv^NtN={Yh#-*=lu^HB4uhsbEeAcuRd27ne zy%M=;n%+7Y%UY7#;wDK{L$vnJnXfh(Xl7i3@(QKrbIM#e zI%_5tFfZ=;cr2EY-1u^*{@g4>=j7Cw4ntv;pAL}mcvy*RCr7MAdWl+xK=->FQu zy^mEkO}ERFd}y9K2WSeK0Mkc?>yy=;?iAD&V5S}X?RYjw^m zWnL!PGkyk6{LU~gb>a>jCSYY(yq3p>up{Wx0^e#Y_1V1AMAn%#?Gf zp>!-7E5@>gY_gb7mSd?xB9==JrIXP_CRNB4uoZd_(nO_vN*np5P0 zw%e4R&u}zWX>FA{SwCMNtIbrK++=Nni}hAvOLbzaau>#s!Pxt(SS)6ZkCWzHZ6eof zR(46~*sj^?C|7c+%ev79cJ zlG$=Toy{gorE)x0N)==ALVRc_7AwUI*-SiEEJfpqWU3I4m(sEP5DA>-Ol*!&;eEFR zN&PZ3ZEQ3r+fz9Kiw&9IcKgxAxRV+ zjb-HZ-j|M9)IgCYlt;=*Mv)7KQkhr|DJT`o<`Esr@lrOM$;T7X^iXQ3kcnsG$zo}U z>Wrt5CWc~`49+8!>g?!QxtLW0*nW2M7V_YylNWUT7`ME7pW+GF04Mv`t zcb@wHf1nzhZaLyf%Zyhst2|$ycv;NaSgmYbU)@|q2AZhiUtOzjtxm44wdU3~CWWzf zetKGF0LWpcZ&|E3zEXXjVa@f8G#PiRlby`cwt11c^=aDL=Gqie)ufD!xgC{ScZiC3 z3xkmy=H>NXAheE#(&M1dBD0Mt6Q=MrvcfpveOv#MOfLz@2O;nkf*dfVloAjm$~YU zyl`lId`GjwPen8~Z)Q#q*39^LxmK@CPw&FlF`+RQkcO^$){~pLYR9q%$%a)E3&?hF zjmD*WPgHB$5ddcSCU2o0i|=JVA<&(1q5Ly0-kL7|A-diMPZ$3^m;MCGpjX&J&!o(n z)Mk6aloz@S+{*UqY@=59i5CP)t7=lLx%#BG9aT2KXO3vM6PMXr#)>;RP}yJg7+z^e zP_J=c=3VD0qv%-ItZlDNBciplPJ~tSwMpbcmRa(3Fr>+B7_QGjYHl4`*yY+z#;t07 zYHo|e9o^NUEv%sH*BRL7YBc5*V+|*e?{g@Yg7Umcwf8e7j7JPnh~}85hqAGFJUfKo zh@3Sv6wQ@l<)Jtt7IMx|DKS(k3}s9CTqc<*mokZ=a=M(270R)pr1Rk_Q!oIbd1-&N z1TL)p3_P7wqvKG%Re<_?OxVQgJz?i2C(R4jRAy&cGRTZgjt{$3M}jqtexY)XQ{@S;99)dI4UAF$&Gm z!rTB68`?Tfl4$9I&UsC1M-5%f`s%j%YHQBniF_a{q{h6NAY+O{Wcd)3S38RwgV0%++2Nx~UFcO_3 zzD>q0x<*w>EpDBcie`N^#H`dYsm3jxKI1ZJk}C#E`6Or3j9Zch5oTuN&iom-Hq32K z#K*Q&Y8&dc31q`mJh~BG>y|l!CR&Sy%x%uB*_2r1F%3*E8W>CW4qA)u#{FbUfQ&Dk z%w^@^`}m8A;xv4(eZ-;(E1yp1OUX<=J(MjZqor&tk%{LDi9|eGN|v&zOf(i{b(KwL z%BgfFpNz6#Lkk?uBw5tvqOru@X8MFT(?j`Zdb}MCm1JO`ZfvW;g{5;kLaX-R_5@*K zZ)VEgvd$f&WLd!z%ugiTOXnyN;(N<2fv1^)(H&d8a*`m)3$y#2YDn98af5W!YqJvx zE0#{Cve9@l8f9%7M?;W|<}&eOuAD=Wl1?SFiB!InV8*eDm4_( zCKFbcc{7zLr4sM@tz#gQb5yn=QxVu@qmlx9Z{LjsO7FApqdt+w85wn9STx3JD>6jS=4*J-unB zS9}vXJ8od@;F~_-I}?-)wD{^GlTD+8()m#yGt;P0?P*jWYfqziS#mn!rYovB3F=(f zp3R6_uTC}QYK&$b+*V7Joz5&sEM_>LM|X^zb*`JRHz7F;8TIB{qP=$*vuN4bZj-r( z74X{mnN8KE6GYn7vE$h^KfP6&gvVg=VPQ5rlCN&5Y_GAQXUc||uH`ITWGST+AUWMq zX))QDS;tLLcIv1-oy~~CjuulUGc+qTCyF~CtE}koh&4NpB#M^R;q(ypP@a-vv5Ht- z?ID*FCev5KOkW9iolG0-m?RQrk|4GU$*rEMwOBOSEwjdAFr{TKTphvo?On*g1gB%H z6H(t?$e@CmMo+S-CJSJ%Gs+{~>yxB~!mXT&$LNTqSUO)EDn(PdQiuSh`x6t+lRCCCchS--HJXzbg9z(sp9_{f>7>GMCClnPgcrQ5CTq zXIPwdFobVB!siSnP%<*n*v)>MGZ7~O3!dKp~VNokja*NzBTi$rZkCO=mOh@=@H zlI{RO>zbdPZ8YavNP`G=bQ!OFL>rSd0|sfjq9o*~O7}jkCwesRMX_kgWO!tLu3aAO z;26O=j#PGZFc;U^%M)5O?fB*u&d#NbIWU|tr(^Xk{O*B;h>}by^)1D=ovowN~ z=4i%xM*#O;5{ss-ID0vzOtgeXhz&7T5amKHnU~$0LY9570$XO8cp*_{Y>czL7)>MC z=F5qp1k+P2Ndk*Ek679?D_2Huw<@m=TWUTcS*Wlu&g3ScT{G31bM8*KR3?j_88-6@e<GFqe)- zv#9qnh2&7OP>71D6J>lByLxGBC^=Nh6<9eGaadQ&gR*e zjVF-y3kkLcvqQyFf`tj~CT(RiNGjP>lD*TRTq+hFipGmX6OsZ^>M&E@0eEGt!( zEQLZgkCw5-l4q#IOLLMyA{#@tNoCoKO%&oO@|%t4qwx&+DJEh=EKD*P9%mbt~zmPg|zAJBUDth(bjT=0(S#*loF+5o#BNV3gvFTWVhC#2starlv41@)Ai5S+dt3 zFGq)p`JqfPMMEi;@a5S8PGbl#6iw1{hEiBF&~j-9@q8|t#nd62%d+ysuFYaPZeW+B zyDYi+W}{hwxp-eBE2gp`e3gyQs|^-re2>&RTvx*K3}MSnVNR#QCS ztQ);5g{6~%pCJRAR|rJFWUp@I7M51N07v&m47VB~8NJrGD(mJqv7kUF=S7c2Ge$OA zU}Yj=LM*eFA|Vh-Yofv4!fG~Ay)SLolf>v2C-#6-^#{<*d4j&^fR5WCn|*aG(U)+A3X);v;K zi#2$Zt+Gs#tuL11(OA4hD;O%2m=qJ4Xd2aPB8grQ87LD=CW`E!5hc_>{*VTjnp+&z>^7qRoo zlR!BiM;n+(Mu%d|#2GY%u>?k8Smfk#qG-&cykjO!rwS+{V@PxuJYg%Ail(BuLP>KW z!Zn1i-Qq*KRhe2ED_)w_!WBMTX{((Z4KQ_3YqS?T=IPG8Sa@q1lk?Nnl`?17w$n3) zXV`m?SyETOj>YLp3tORbDuH=hE}28gF_g>1GFh~s7`Vktg>)`k$R`kZqV(HLDwd#< z(4=OwLljdXLm^n0Y$f6aq>yqBkw~nzVyxy92(&rG4Ia&4Z-z29pFr{$!j>+LID?}p zr()#{31q3t_)rwfX=J7>MV}GzDuy{20!uPZorx7wnJv|1hPuzC3aD#P7RO`d6jf@W z(5r3U1u={T#}xZ+IcxoB&N?qwTEDh2m%}nDOA2M_SMfqQk)>lHLS-3_Qn^$T$tY1| zfGWfbRBs7Es+h(s9m~2TCRfQ!DsPpd1wI!8t#|>IZ9a`eh>3S9$<{9Yvs6kWHK7$o zZIPs;ig_&FGg)N!EaFxMu}(*^jc4aAG`T|sE_wM$qnT*v3x#SvM}jKvW{b*KZLbi zp&aGWWEx#yEJ;HwMiJ-Z*ytw`bo6YJ+9>C#0Y-~VJdPEA$%+@K)MT3Wp2QlIVk#m) zBSDpuyrYz0bjcU89X zA=GZZm}EDJ^Ab#itQ6@w@swL3*`2-9BMyp~%wf6jB>qVm@YfU|?&V6*L5R*qQi;&a4PCdk5dcpRZb`{#_KE%Bof zaCeoBuT%35&yyN7KJOJv5FQ;9nR9Sm`+|+4nx==CosT8B^h8CiImt_?D+F~+= zI806%=(|#CCu~ zO_~*?vlO)=F|dS<-Al29z! z)GMDWpRrCi8I&c%>jGA#GlqDP7G!9q{`8^YI<25K`J$HNUoZfatJr9)ZD>@gWMtFJ z84O&X{$~}Y|4l~Z{l=ihEcnrxiPeb4=qNMg$VaTN>`>a55u+wj?#%e4)WUen(LkGn zE!0>=`I3OEoOaHN~iL$2BtW}DeGy(59&umIe?>+9!_>35gav%@xSi>~Y zju@0u5AMwPgtq36sAUcaVHk^H1FL98Bkx$4_d55pjge_(+6$PhPE#x_PLR`@4XU$3 zcWhOM(dgFYOw;zN^HFN1@$aIjT0ilBik1BLv0~|e6Gv)oxaDSWZDVwPf|Em5ZsA!X zRc~(Gy2;vs(?Q$Jl17P;pKffjRx>y&v4-89`57h0YV}=8OtdyJCrwx*lba^YV7;a? zRhw}8H`U}=2YaJ#uOQaI&X~*)qEi@kHr>isS|sILQHn9Ss8>zxn{g;77NWxRdl#%( z`36VBp-No;veno-c5p;7Wl~*SMP*4)x3b!jRUujslfm}E&vyRW8g-2;JFW43Dw&?zrWVGZTj@EVVQJ+EI)FZ5~YELS;aVsFGijhRAr6~=1LPJ=#W zf;(5^e3)4HFmt%4MEH7+@U^{u6DDo#CK)MdC&A{zjbgVE`j?g}+BuS46SdwjE!LHp zOgf8h$9Waqb;8j+xXAfqtEXwQ)nmKSt3w&>=9>&rio$94yc+Gy^i6_n-M8|3^Q^$4Bs0&_=~G& zwOoknH?LEkOsi555@sv}d@rTSnV!u-Y?D{{QdR_h%Q#Jjm_e%sUw3v7PCrZ4~8Zr$f7Ls?}|r z{`&2I#2RaN{%vfyiGXjb7V?%vf7_ws1V>67rx>1D&io8C%=eNO+oW3JZiEf>7E5Ni#K2^+%$fIH=g6^ZWev9^e>yBmjI)vXi`BXt zStf-XpVVqOOzFs7OwDUYG_quFe(K=5!&3*-G?EUM3?r0&tI(L0jL18E-U_3xiZnhR z`eY>AE;fqhb~42(cS9jqseq+fW)#J=#Zp@9QN2cc9q+H~ld@mfnK(NbNe zEoPXOOO+WX=TUXnS|-FDPCZ-f;g%OBpU32rmw6AFfpi>!1SNRBiXpShrn5ALvCh)H zZ|ZZ>%=fa1W@HWLIft$Du}qHoK@M|)qFcTG_h3EX0m5TY>9T3%+K9QEB2IKy zjCQ=T%F=n<@q61%%*E*T9qy>7ym1(!{+(fOr#8XIwsl+#6zwPGWWEyNWUS#bJNNR6 z+?VBqXo2|rW1nA9aR;^9WyS(-af6aUgAxy^{(xt)591A74X~;}V7jaqM zLq1-6B|RcnWf`>rk9)LP5Xk^FLRuc1DDJ=@RWXZSz6qfOIotU>cZk!mV7TRZoetov zaZ5XAITs+^Nu}QfTuD?0KbeALveBrU7c!*W8Io9>O1a zx_J?4+^lHFR=>1OCD!Z6=cK6NEyuH}(O|ISB$eZD(=wnAx7de38%_02a}S%5oJ~Jf z#k2_QE$L@w=24BOoUwLm#EHb(!f>VB93?e^y_&U1+b~*eWOiv1?WcWWfRm!O+HN-c zC-ZU?jaw+%1A_mN-oYCyQU%hi#+tNVsW~(xtYHqMd*t5d+CizRrw2_+}?lb{>q-B4022`=j$S9z)*a4Ma{iYy0)`6~>Lq=IRI% zoETua85yR3xPH@M*3CDkWLIxXZ4+0tIZHTd-?Q6-SDc?gUhH5w6+%Z)`;1XJ1R?jA zEr_vu#H z3c%AnEL=FM#M}{qW;D$bw1Y=NA|$dF_9wL$6^GLRa^}d1Ba4$=mb)Co-)tlfqfl-K znu6&aW-gdwPT+zQ`EC&jOxr7}Vp#g4^iduASn4&V*=}(AXip}2YwTcGPS3Q;byl0z z&(Jc>-Eni=uYwu5iNl;@&gSy@E^AF~C$IA{ya~uRzqygS!c@?ALwhYF+KY`9%)V@g zQgC%SMeO$cc7&E(+u~~ASC~TVc!IH1hF@T~rFJOJfNE$l4s@&tEN9M@G0m8$y8%+% zF5PO9l8BFTctw^u`X3__{Xa9l6YgnD64Jg*{C*6F{ zJf%BGGTP0Rp}lS3(H*{byN2%g-lltxpvi7tTW2L|*Y5BIs>kjK&ajDy)a!9A*&fNN z6f4qDuZP-s>J{2cmC~IpZ!y{(v~UU69YaPlv;OK1YfF^9!d&63J8Yr!)g7b9B-bO7 zZ*1)jbwtAM0NS2jA98k$OImxh^9cmGZvL9uL=&;6cc44WbU)@hx4U>GQ5?9J_jYB7 zsbo&pdEIF{Cl$M%aD*6YZm}5Jk~wX@Sh#dgks4%lWzgvH?gFQqx^tw`aPJ`GI?TCy zJkzy8?j6lJP}BWR*&gqaECaQ3sbBXa#j31>WLa=-ttD4xbw?RS0p5%6t52DWO{C4e$MJNYy_0B9FpGqns~B{Vi`utGVR=@b zNCw0Z$*N~WRrd7^k!IJOaC_=lUE8d~Q_nc`$GHotO)*ehB$8J8-lM3}%$W3bzXaVA z`k2K+-E}sV)cvRtprr!4!(5d}@6f_@vv+`5Hajy(cdG6<)g7XfY7e$tBu&|MhZrlh zMFQJPo$d#@WtM%yZazZVd=K{7=*!W$ZtoGK2g|#9q8ZKea*=!Q2xP$h>4HTfm<#qr znb13eskq)@uFU1BKG^)}{#Z#HS+Pd|ot==LVWyEeCt|xFHItLcn~dd)MjUYrg`LBh zT_xD=TZ_fe((TTHb5&iB1Z6*CvCuV*`rMZ8#GD@BN&&9ov^%0`ayl;ORB^w<1ikwq zCv{^l;+tJsi@(RMS!dS)#l^(d?u2#j=w_SB;XYik(C!Y_g`2F=HQTN^Z+AQ$t-SN9 zj$v@G1YO0lF4(vCim{sQ=N>s3W<{x)SR}mNI4DBhtL)CDwC-*$cG2C;u_hY;Vv(C` z<#~$3irsO16R2t3-BB3vCJ;^dsfMn`cdpqZ><%zhvUspA;1&(mf;GkzPJ`%v11lI& z`8dj|?(nuEt2@lsCUvVp+Lg?+-7%%KojdG%hMT2TQ=P>9UGy&rsjznv=-TV?3tf-JMMnT$PO8R_ZFjw z5`2+_y&nzfYKc;EoO_!y-Mjm>qG~{+?T#etX=A3$t_zY_OYVQ{9c{H3Y40tPqXpheGUsFc z+?}{{|611vh%_T*#)+`5Fjs!+4ry1bncbX=xO+uZyC~^Cz1|>iH$%u`F{EQI7UpZf zi$!T?bg>xj_SjMTR^|28 z?ku2kKj5(VV-0s&ELHg3( z!q370xcey;r<|9!x`W+`)^Bpt9ZN+6(V4l{VLehZBaf`WdIWk^u}FCP#NNM)=`QAa zyku=d)TAve+_soD*dvaSb9)4Etfo+#-GW*bW0_u`(C#wc!nQ{;t{}HSt>0awy1Eff zquR>tC7g&&u0}W8hP^W8m6&cO_KIO7oAPvpJK?&Tm9yj1Bg8!B-ICMwWV=LIrHTze zk2H$NPF&sDBgCZMBf$6D5#70Mi#0u-q=lZx=A!p=bv>e(R@x)LE5#mRd?-Jt#jofX zYu3F*{r6*2hgE%#4-^B-9(nNEOOG(gA1$dz2zwC|Rd*kwG2bJG8MY+#UZIqzyWuIy z=kDyfW>aOvp`H=imvePL?AU(jepHW8cLt$#b%%PwOm|S*?7us#-DXLkSGptm>kZux zdgb38ptY;}>4c$Mq}?ft=pqBlBEj?` z-NfEY6lYjiBmvWT7YX$G$0Ff9`-r<^DW=D!o2@V`M%yY@hKr=-Yn_Y4SjS#&cLC|{ zZVxurU8VXo@@;pR$d(-1=nnY5eoskF2ig|$izbLgkK9k@l*6LY+HG0%`ONvBTx={a;bk;4|BI>x`B+QfD};w&0mm6P0_s2$iCfvDAy@q5NG zY8|4QOGa@Mh7E~n8a+9(zii^k{FjmLdXnl2){2~xWw6z%|32TA&g0mcVGMJI-674W-uAb*_uUvhJB-8B+LlT_~T}`<0RaGI{0Vl)KAF1i#y3qx>(M+&v5uDl(nPDTxkIk_(Ux-^5{#Afu zgWti@!tdaa(+)qAaEE`BD8bzviCvt@mCM;G9Jg)wtCsV^TkHD!P$usfb~BF2UW#04 z;4~)Z==FkfaD%0wjoFIJk%C^xF{bk-K{*{qsfZoC`-miBEQ4H*B+|*D3_2OLmgp?p z2WqWyt=aZ!RjSGW_e8(@vnP6Rj1{n-B2z9#$NuH~`Pl1+Dm zNg3Lve7okR4|GJ)iz~csb<3|))+9#N_I#Jp?l9)P480C}?%LTMQnIjy>a3X^cMbCU zO}Mus?}WGgGwt1a3@m-R$5t7^Mn0b1N$wv~&w`;zE|&1ZCPbfG9p3CQ=o>R_5Bfu~ zX)n=JL)quPP#eCg**jF7cJ6M=EnOoA`B1bOItY zh@FpPLGAQv+BTN6qu3g27(=O}+A>3^GL7y4^Bj(!vm2myTWP?8VX6Hq33>oUqiCj$ z9g=C^4z1tj7$`Aa{C~a)%)H-8jEUIYAydBmMpZjAURD>dG;6v;x4!+m4PIpP(F;RN zwtGHg1QUJUZ*cDXs+BdPMXy%fW-Xw%<1hr#9;#feX1tOo$Lz|QZcMo(mrOf9C*g9$ zcwY8oT)$&I?`N&zrxs}*ZVaR0cYOqMR~y`-d|{9_Q!Z9%&TV4iUl^(If&LDbp-f?JYl4|g0`mdP&nbFGBXgkQMV#A;`xqq`As5T8`*Lz%BiKVMoQ;mi}5%|6C z)p{mS24O=@dX$-dTG}zC@SJoj{2DMTNOP>*>VjhhQKZ4+UAwa(57UD40f6$J&v??83%J6Uoz z-#Hks2}-@J+p>!*!Yb1yH5yBYU%x~W8QE+`B>ziAC=%_2;W_umm`r--7#)F@5x_m! z=q~Fzo*Jo3Um0o2fMNo~Z^pwmZNm#H5%%=xBzRmla* z1j^I9uXNJgqgtru@NS0nB5;nA10`reje&wTMK93lHKu?(u3I|#Ge}~kC2H}ph8M0y zE7Lk((#do+IUcuN79NY6Kbi7!+qtgFtID?VtoAdXx8|AE`hO|g`@Lmpu?yAw6f_h4#%0|7i43UjFW)xXjW;B zG;eE6?;`fjH7QT>(`CA>S?lI%-1fpypI;z-DUT(bZ=I+tj(eJ`yczfD39{bQTU8du zlUH<`Nn5Qh%V}$3K}P&zpT5#Cj0Q{@0?gp-ESq`3$(R`*sc`d@uKQ(7T31DBN~xjh zpO*Vnr8gEjK1|iB3uE}Q<%Y|XtZ4nuwv0}ehb52-TO`!~az_~Sn})k@AbX4fPJVT2 zk>OLb&xh&pytNJUHNJ_*v?4v`tdnVv7f1m}6)zGrGCfI!YQj#!E>=Y%0 zth*81BG4;T7liR;ZZvs=8V5*x9Y2j}yJoOwfZ1I+R}BIA5U0B^j$j+;$PY|}=G*lw zlUNL>H;*pJhycoxB3Qt3g#r!thI{S7%HsYW{qKIgXLaB%;^cs+yl%aky;~won1!Ko z|-0^9wbxsG(D`n_Cw z9>9_&T)3P_}W2xzc{Mw$&TOej>Y3gUoOGJQdbEGH>6!dh%WJmncA^beIOZa)B zTx>rjWno&Uk;8RDl7*A1$)s!cjL|>~>5n8YcST^$Z(9OZ?<%?FqPZOfB2!>}qQ)rz z<;!z&AWl8BD0DwFUVvzZ&51~5nc)}J{@j89Q5O5+Nt$N6TpyD%@OdRvi%A!fUW^4S zE#?JJv4-|G+9en4p0eX2%h7g@JCD$qL3sgh!jsPQF@2M_rdF0wEw#RC?#KZvbHASmCTFb6XB!w{ z7~c-xuf!`yzREQ~)6(=j<`xmT@=Q-nd;IY&=t=e2o(b*6tmO$d5OEeb;!}NCNkc=JXa8B+o z*NJNl>L_`^D5JI*C%FEW-=o5(8=|Uvj=WE6{zSYsg~o4+tfvckTu0mEW1AJ4xn#qQ zN)4+REV8-YBv?cZp$!}r5NdO?p)JLAf%v`l%5^d9mt98bV0xFi%ay0e z8HQPM1r7^`b`0ifQGsZ;(?t-{m%E`>n-W>fsO3f{(8hF#WZGt_L6xKA&L8bD#z&(v zgtSy+6>FmF&kJZWrVkmvG$Oy>=^rjNA!(y7}%**1_ZdW~vkA|;K^ zL(&`BtPI|<0fU?7^e(Ao2|)9x2bt94s+e=r47?1*Gs+b$ODD$|B6sq<m9LlcEb>SU+yaggI|&_C8!CnaPS#40iDNBn>k{i6LEsCH3i`Am&TzlbWNc zigm1syNk<6XIA)agjWokC8<2a%GR6&r;t`SCmguMrpjKX>zkrk8nv;@kwWj}aIup4 zDG+_uy%~#jLKMl6kqf3Q#(b!0`D18?TLcKt9^98SM z82i44{LGY7EX5kupiLTs2GK^`0NwTCXDKSXm<(m$7txZN37mVujvLUq+19}|bEhf?T>fS!PYZ7kkt^@?(nWr^U9YHYGo<6_SGHQY zCdDs51uLIb@q1kT23l@5oadVJCXRMGek};)2RCVA8Y>TSWJUk;3r7w`feo`?T6Jx5 zOsP9Jt5UDnpi5} z^#+jV2rfOsRh^p|lT@gY+hGl{Lq^HM zbY-6C?fn&pfk4RTR&{*y4P{0cXzPf3s(-EK#3uhUt_o4~o__xH#3tEU=r}X3W3T)a zl|Ldq8=+qoVTgb-W9z8lw<21{c-^X+Ek^yAH8lL1<9DWM%mdUSRl}K9I;TMG4{n)qa|kOs#jn1zAC@=-qLqZR9b4jI@L4X+|*Q)jXYk-h2(Ui9+9|(S|>qk z%>0vnVJPVohBCQARH}0GsAK=Som(0vP4L7P*|axwiAx?E&3uzBIMIF@rl#p!En&4> z!hDRg%%x};66I=qnZ6{S?lthn=S;A?-S~Bhr?R`tk(c;xsn9;{_tVhLQCpKhon23T z&$)UDqoGe*85=j(InqE{8tnE)vnem8#O6YG|GtSex@CTD(pb>y4S?Ey+qYt9UVL7@ zbhgP%k8WG%2f38bS;8B?d7ODgUSZ5K08iC*TV6)tJ zfX=G-~|MACNuzGF>4Kvrtm5$m9ofUICbYzok#LW3}=varS z7~YOICMGd+WznKx>AAHXz{D-z)Mlt;+sRTfwjE;?A|LnMHfGwa;d+}k#a3;eRhwkm zZS$QsiQ#*jsTS3;$Z50OaueZ+4%j6`x4e+nxL19y{op8eo&S%zw+qqqy7v5@Mi?W6 z5JCuJgmG(hgw#hzt^Sr|TlQ2}f2c;?UG1)F%Q`-Gx%#cQyUSf)uD7aNC59HiR=ERkCA1d-qvZ01g@JzrU!4@gm%@*3+wpH z9KSSy0mi5|$K`fqwk6*lZ6!KYFokB0Ny;k5t1&*<+|mr@)b+Nctpu30v_BX{bzee8 zP1C1rfaG0hiU_>IT{@Hu{&C-e^xi&#tI^0sNYob-n=Oe;IjH_D04?^X#4M=lJdd

>ABQ`_ZsaNts%le*c<44rz4NHH(sPl# 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 oG1DSnC@DrEq#=gNLMjb$mTqa_VUjmwiVQ$# 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ΝdJRrU=vK+G9qrCN z?tiTlpQ~j95ZZP*0u@CQY5+nNK)4MM%HgPVui%S~l6Hkpt+mQWmwfxK?S08U*S}Hr z5w;AMD9SJ>d7S|X6 From 78ea1a2083493c98761c71089e7df79ddac9b491 Mon Sep 17 00:00:00 2001 From: T <74899888+tslashd@users.noreply.github.com> Date: Mon, 18 Aug 2025 23:25:33 +0300 Subject: [PATCH 36/52] Refactor map and stage handling in SurfTimer - Added null check for `CurrentMap` in `Tick.cs` to prevent errors. - Adjusted timing logic for saving stage mode run times for last stage in `ZoneEventHandlers.cs`. - Changed initialization of stage and bonus completions to use dynamically sized arrays in `Map.cs`. - Updated `ReplayManager` to handle stages and bonuses dynamically, removing hardcoded values. - Modified `SurfTimer` to use a static property for `CurrentMap` for better encapsulation. - Converted `LocalizationService` to a static class for intended usage without instantiation. --- src/ST-Commands/PlayerCommands.cs | 18 ++++- src/ST-Events/Tick.cs | 6 +- src/ST-Events/ZoneEventHandlers.cs | 2 +- src/ST-Map/Map.cs | 91 +++++++++++++----------- src/ST-Player/PlayerStats/PlayerStats.cs | 55 +++++++------- src/ST-Player/Replay/ReplayManager.cs | 22 +++--- src/ST-UTILS/Injection.cs | 2 +- src/SurfTimer.cs | 13 ++-- 8 files changed, 113 insertions(+), 96 deletions(-) diff --git a/src/ST-Commands/PlayerCommands.cs b/src/ST-Commands/PlayerCommands.cs index 0e775f7..5541076 100644 --- a/src/ST-Commands/PlayerCommands.cs +++ b/src/ST-Commands/PlayerCommands.cs @@ -624,7 +624,7 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map WR ReplayFrames.Length: {CurrentMap.WR[0].ReplayFrames?.ToString().Length}"); Console.WriteLine("====== MAP StageWR INFO ======"); - Console.WriteLine($"Map Stage Completions: {CurrentMap.StageCompletions.Length}"); + Console.WriteLine($"Map Stage Completions ({CurrentMap.Stages} + 1): {CurrentMap.StageCompletions.Length}"); Console.WriteLine($"Map StageWR ID: {CurrentMap.StageWR[1][0].ID}"); Console.WriteLine($"Map StageWR Name: {CurrentMap.StageWR[1][0].Name}"); Console.WriteLine($"Map StageWR Type: {CurrentMap.StageWR[1][0].Type}"); @@ -632,6 +632,20 @@ public void ConsoleTestCmd(CCSPlayerController? player, CommandInfo command) Console.WriteLine($"Map StageWR ReplayFramesBase64.Length: {CurrentMap.StageWR[1][0].ReplayFrames?.ToString().Length}"); Console.WriteLine($"Map StageWR ReplayFrames.Length: {CurrentMap.StageWR[1][0].ReplayFrames?.ToString().Length}"); - Console.WriteLine($"Map Bonus Completions: {CurrentMap.BonusCompletions.Length}"); + Console.WriteLine($"Map Bonus Completions ({CurrentMap.Bonuses} + 1): {CurrentMap.BonusCompletions.Length}"); + + if (CurrentMap.Stages > 0) + { + for (int i = 1; i <= CurrentMap.Stages; i++) + { + Console.WriteLine($"========== Stage {i} =========="); + Console.WriteLine($"ID: {CurrentMap.StageWR[i][0].ID}"); + Console.WriteLine($"Name: {CurrentMap.StageWR[i][0].Name}"); + Console.WriteLine($"RunTime: {CurrentMap.StageWR[i][0].RunTime}"); + Console.WriteLine($"Type: {CurrentMap.StageWR[i][0].Type}"); + Console.WriteLine($"Rank: {CurrentMap.StageWR[i][0].Rank}"); + Console.WriteLine($"Stage Completions: {CurrentMap.StageCompletions[i][0]}"); + } + } } } \ No newline at end of file diff --git a/src/ST-Events/Tick.cs b/src/ST-Events/Tick.cs index 249bbb4..05b0d5d 100644 --- a/src/ST-Events/Tick.cs +++ b/src/ST-Events/Tick.cs @@ -7,6 +7,9 @@ public partial class SurfTimer { public void OnTick() { + if (CurrentMap == null) + return; + foreach (var player in playerList.Values) { player.Timer.Tick(); @@ -17,9 +20,6 @@ public void OnTick() player.Controller.SetCollisionGroup(CollisionGroup.COLLISION_GROUP_DEBRIS); } - 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"); diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index ab5c880..cb5203e 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -123,7 +123,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string // Should we also save a last stage run? if (CurrentMap.Stages > 0) { - AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index ae1acff..f7dd9e3 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -25,11 +25,11 @@ public class Map : MapEntity ///

/// Bonus Completion Count - Refer to as BonusCompletions[bonus#][style] /// - public Dictionary[] BonusCompletions { get; set; } = new Dictionary[32]; + public Dictionary[] BonusCompletions { get; set; } = Array.Empty>(); /// /// Stage Completion Count - Refer to as StageCompletions[stage#][style] /// - public Dictionary[] StageCompletions { get; set; } = new Dictionary[32]; + public Dictionary[] StageCompletions { get; set; } = Array.Empty>(); /// /// Map World Record - Refer to as WR[style] /// @@ -37,11 +37,11 @@ public class Map : MapEntity /// /// Bonus World Record - Refer to as BonusWR[bonus#][style] /// - public Dictionary[] BonusWR { get; set; } = new Dictionary[32]; + public Dictionary[] BonusWR { get; set; } = Array.Empty>(); /// /// Stage World Record - Refer to as StageWR[stage#][style] /// - public Dictionary[] StageWR { get; set; } = new Dictionary[32]; + public Dictionary[] StageWR { get; set; } = Array.Empty>(); /// /// Not sure what this is for. @@ -81,53 +81,55 @@ internal Map(string name) // Set map name this.Name = name; + // Load zones + MapLoadZones(); + _logger.LogInformation("[{ClassName}] -> Zones have been loaded. | Bonuses: {Bonuses} | Stages: {Stages} | Checkpoints: {Checkpoints}", + nameof(Map), this.Bonuses, this.Stages, this.TotalCheckpoints + ); + } + + internal async Task InitializeAsync([CallerMemberName] string methodName = "") + { + // Initialize ReplayManager with placeholder values + this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); + // Initialize WR variables + this.StageWR = new Dictionary[this.Stages + 1]; // We do + 1 cause stages and bonuses start from 1, not from 0 + this.StageCompletions = new Dictionary[this.Stages + 1]; + this.BonusWR = new Dictionary[this.Bonuses + 1]; + this.BonusCompletions = new Dictionary[this.Bonuses + 1]; + int initStages = 0; + int initBonuses = 0; + foreach (int style in Config.Styles) { - this.WR[style] = new PersonalBest(); - this.MapCompletions[style] = -1; - } + this.WR[style] = new PersonalBest { Type = 0 }; + this.MapCompletions[style] = 0; - for (int i = 0; i < 32; i++) - { - this.BonusWR[i] = new Dictionary(); - this.BonusWR[i][0] = new PersonalBest(); - this.BonusWR[i][0].Type = 1; - this.BonusCompletions[i] = new Dictionary(); - - this.StageWR[i] = new Dictionary(); - this.StageWR[i][0] = new PersonalBest(); - this.StageWR[i][0].Type = 2; - this.StageCompletions[i] = new Dictionary(); - } - } + for (int i = 1; i <= this.Stages; i++) + { + this.StageWR[i] = new Dictionary(); + this.StageWR[i][style] = new PersonalBest { Type = 2 }; + this.StageCompletions[i] = new Dictionary(); + this.StageCompletions[i][style] = 0; + initStages++; + } - internal static async Task CreateAsync(string name) - { - var map = new Map(name); - await map.InitializeAsync(); - return map; - } + for (int i = 1; i <= this.Bonuses; i++) + { + this.BonusWR[i] = new Dictionary(); + this.BonusWR[i][style] = new PersonalBest { Type = 1 }; + this.BonusCompletions[i] = new Dictionary(); + this.BonusCompletions[i][style] = 0; + initBonuses++; + } + } - internal async Task InitializeAsync([CallerMemberName] string methodName = "") - { - // Load zones - MapLoadZones(); - _logger.LogInformation("[{ClassName}] {MethodName} -> Zones have been loaded. | Bonuses: {Bonuses} | Stages: {Stages} | Checkpoints: {Checkpoints}", - nameof(Map), methodName, this.Bonuses, this.Stages, this.TotalCheckpoints + _logger.LogInformation("[{ClassName}] {MethodName} -> Initialized WR variables. | Bonuses: {Bonuses} | Stages: {Stages}", + nameof(Map), methodName, initBonuses, initStages ); - // Initialize ReplayManager with placeholder values - this.ReplayManager = new ReplayManager(-1, this.Stages > 0, this.Bonuses > 0, null!); // Adjust values as needed - - // Start timing - var stopwatch = Stopwatch.StartNew(); await LoadMapInfo(); - stopwatch.Stop(); - - _logger.LogInformation("[{ClassName}] {MethodName} -> We got MapID = {ID} ({Name}) in {ElapsedMilliseconds}ms | API = {API}", - nameof(Map), methodName, ID, Name, stopwatch.ElapsedMilliseconds, Config.Api.GetApiOnly() - ); } /// @@ -248,7 +250,10 @@ internal void MapLoadZones([CallerMemberName] string methodName = "") } if (this.Stages > 0) // Account for stage 1, not counted above + { + this.TotalCheckpoints = this.Stages; // Stages are counted as Checkpoints on Staged maps during MAP runs this.Stages += 1; + } _logger.LogTrace("[{ClassName}] {MethodName} -> Start zone: {StartZoneX}, {StartZoneY}, {StartZoneZ} | End zone: {EndZoneX}, {EndZoneY}, {EndZoneZ}", nameof(Map), methodName, this.StartZone.X, this.StartZone.Y, this.StartZone.Z, this.EndZone.X, this.EndZone.Y, this.EndZone.Z @@ -379,7 +384,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") var runs = await _dataService.GetMapRecordRunsAsync(this.ID); - _logger.LogInformation("[{ClassName}] {MethodName} -> Received {Length} of runs from `GetMapRecordRunsAsync`", + _logger.LogInformation("[{ClassName}] {MethodName} -> Received {Length} runs from `GetMapRecordRunsAsync`", nameof(Map), methodName, runs.Count ); diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index c551da4..b5a3750 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -7,62 +7,61 @@ namespace SurfTimer; public 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. - /// /// Map Personal Best - Refer to as PB[style] /// public Dictionary PB { get; set; } = new Dictionary(); /// /// Bonus Personal Best - Refer to as BonusPB[bonus#][style] - /// Need to figure out a way to NOT hardcode to `32` but to total amount of bonuses /// - public Dictionary[] BonusPB { get; set; } = new Dictionary[32]; + public Dictionary[] BonusPB { get; set; } /// /// Stage Personal Best - Refer to as StagePB[stage#][style] - /// Need to figure out a way to NOT hardcode to `32` but to total amount of stages /// - public Dictionary[] StagePB { get; set; } = new Dictionary[32]; + public Dictionary[] StagePB { get; set; } /// /// This object tracks data for the Player's current run. /// public CurrentRun ThisRun { get; set; } = new CurrentRun(); + private readonly ILogger _logger; private readonly IDataAccessService _dataService; - - // Initialize PersonalBest for each `style` (e.g., 0 for normal) - // Here we can loop through all available styles at some point and initialize them internal PlayerStats([CallerMemberName] string methodName = "") { // Resolve the logger instance from the DI container _logger = SurfTimer.ServiceProvider.GetRequiredService>(); _dataService = SurfTimer.ServiceProvider.GetRequiredService(); - // Initialize MapPB for each style + // Initialize PB variables + this.StagePB = new Dictionary[SurfTimer.CurrentMap.Stages + 1]; + this.BonusPB = new Dictionary[SurfTimer.CurrentMap.Bonuses + 1]; + int initStage = 0; + int initBonus = 0; + foreach (int style in Config.Styles) { - PB[style] = new PersonalBest(); - PB[style].Type = 0; - } + PB[style] = new PersonalBest { Type = 0 }; - int initialized = 0; - for (int i = 0; i < 32; i++) - { - this.BonusPB[i] = new Dictionary(); - this.BonusPB[i][0] = new PersonalBest(); - this.BonusPB[i][0].Type = 1; - - this.StagePB[i] = new Dictionary(); - this.StagePB[i][0] = new PersonalBest(); - this.StagePB[i][0].Type = 2; - initialized++; + for (int i = 1; i <= SurfTimer.CurrentMap.Stages; i++) + { + this.StagePB[i] = new Dictionary(); + this.StagePB[i][style] = new PersonalBest { Type = 2 }; + initStage++; + } + + for (int i = 1; i <= SurfTimer.CurrentMap.Bonuses; i++) + { + this.BonusPB[i] = new Dictionary(); + this.BonusPB[i][style] = new PersonalBest { Type = 1 }; + initBonus++; + } } - _logger.LogTrace("[{ClassName}] {MethodName} -> PlayerStats -> Initialized {Initialized} Stages and Bonuses", - nameof(PlayerStats), methodName, initialized + + + _logger.LogTrace("[{ClassName}] {MethodName} -> PlayerStats -> Initialized {StagesInitialized} Stages and {BonusesInitialized} Bonuses", + nameof(PlayerStats), methodName, initStage, initBonus ); } diff --git a/src/ST-Player/Replay/ReplayManager.cs b/src/ST-Player/Replay/ReplayManager.cs index b06730b..fe05a6d 100644 --- a/src/ST-Player/Replay/ReplayManager.cs +++ b/src/ST-Player/Replay/ReplayManager.cs @@ -9,19 +9,15 @@ public class ReplayManager public ReplayPlayer? StageWR { get; set; } = null; /// /// Contains all Stage records for all styles - Refer to as AllStageWR[stage#][style] - /// Need to figure out a way to NOT hardcode to `32` but to total amount of Stages /// - public Dictionary[] AllStageWR { get; set; } = new Dictionary[32]; + public Dictionary[] AllStageWR { get; set; } = Array.Empty>(); /// /// Contains all Bonus records for all styles - Refer to as AllBonusWR[bonus#][style] - /// Need to figure out a way to NOT hardcode to `32` but to total amount of Bonuses /// - public Dictionary[] AllBonusWR { get; set; } = new Dictionary[32]; + public Dictionary[] AllBonusWR { get; set; } = Array.Empty>(); public List CustomReplays { get; set; } - /// - /// - /// + /// ID of the map /// Does the map have Stages /// Does the map have Bonuses @@ -47,9 +43,9 @@ internal ReplayManager(int map_id, bool staged, bool bonused, List if (staged) { - // Initialize 32 Stages for each style - // TODO: Make the amount of stages dynamic - for (int i = 0; i < 32; i++) + this.AllStageWR = new Dictionary[SurfTimer.CurrentMap.Stages + 1]; + + for (int i = 1; i <= SurfTimer.CurrentMap.Stages; i++) { AllStageWR[i] = new Dictionary(); foreach (int x in Config.Styles) @@ -62,9 +58,9 @@ internal ReplayManager(int map_id, bool staged, bool bonused, List if (bonused) { - // Initialize 32 Stages for each style - // TODO: Make the amount of bonuses dynamic - for (int i = 0; i < 32; i++) + this.AllBonusWR = new Dictionary[SurfTimer.CurrentMap.Bonuses + 1]; + + for (int i = 1; i <= SurfTimer.CurrentMap.Bonuses; i++) { AllBonusWR[i] = new Dictionary(); foreach (int x in Config.Styles) diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index c0305e6..7cc42e7 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -61,7 +61,7 @@ public void ConfigureServices(IServiceCollection services) /// /// Handles translation files /// -public class LocalizationService +public static class LocalizationService { // Localizer as a Singleton public static IStringLocalizer? Localizer { get; private set; } diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 70b6a5c..487a881 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -67,9 +67,9 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) public override string ModuleAuthor => "The CS2 Surf Initiative - github.com/cs2surf"; // Globals - private ConcurrentDictionary playerList = new(); - internal static TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string - internal static Map CurrentMap = null!; + private readonly ConcurrentDictionary playerList = new(); + internal readonly static TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string + public static Map CurrentMap { get; private set; } = null!; /* ========== MAP START HOOKS ========== */ public void OnMapStart(string mapName) @@ -86,8 +86,11 @@ public void OnMapStart(string mapName) + $"[{Config.PluginName}] ---> Source Code: https://github.com/CS2Surf/Timer\n" + $"[{Config.PluginName}] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" ))); - - Server.NextWorldUpdate(async () => CurrentMap = await Map.CreateAsync(mapName)); // NextWorldUpdate runs even during server hibernation + Server.NextWorldUpdate(async () => + { + CurrentMap = new Map(mapName); + await CurrentMap.InitializeAsync(); + }); // NextWorldUpdate runs even during server hibernation } } From d01e2b0457d04a409e803930e5ea11a05037802f Mon Sep 17 00:00:00 2001 From: tslashd Date: Tue, 26 Aug 2025 16:57:44 +0300 Subject: [PATCH 37/52] Refactor PlayerHUD - Updated timer delays for better replay frame handling. - Added HUD for when spectating a replay bot. - Added new methods in `PlayerHUD` for building HUD modules with color coding and improved formatting. - Enhanced `ReplayPlayer` to update ticks when replay has left the "start zone". - Introduced a plugin logo in `Config.cs` for improved visual representation. - Added a method in `Extensions.cs` for generating speed-based color gradients. --- src/ST-Events/ZoneEventHandlers.cs | 8 +- src/ST-Player/PlayerHUD.cs | 625 +++++++++++++++++++++------ src/ST-Player/Replay/ReplayPlayer.cs | 20 +- src/ST-UTILS/Config.cs | 7 + src/ST-UTILS/Extensions.cs | 40 ++ src/SurfTimer.cs | 107 +++-- 6 files changed, 635 insertions(+), 172 deletions(-) diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index cb5203e..457b91c 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -108,7 +108,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string ); } - if (saveMapTime) + if (saveMapTime && !player.Timer.IsPracticeMode) { player.ReplayRecorder.IsSaving = true; AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone @@ -152,7 +152,7 @@ private void StartTouchHandleMapEndZone(Player player, [CallerMemberName] string if (!player.Timer.IsPracticeMode) { - AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { // This calculation is wrong unless we wait for a bit in order for the `END_ZONE_ENTER` to be available in the `Frames` object int stage_run_time = player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.END_ZONE_ENTER) - player.ReplayRecorder.Frames.FindLastIndex(f => f.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT); @@ -214,7 +214,7 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { int stage_run_time = player.Timer.Ticks; - AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { await CurrentRun.SaveStageTime(player, (short)(stage - 1), stage_run_time); }); @@ -239,7 +239,7 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, { int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.RunTime; // player.Stats.ThisRun.RunTime should be the Tick we left the previous Stage zone // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); - AddTimer(0.5f, async () => // This determines whether we will have frames for AFTER touch the endZone + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { await CurrentRun.SaveStageTime(player, (short)(stage - 1), stage_run_time); }); diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 3134572..0881240 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -1,3 +1,4 @@ +using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; @@ -5,13 +6,24 @@ namespace SurfTimer; public class PlayerHud { private readonly Player _player; + private readonly string TimerColor = "#4FC3F7"; + private readonly string TimerColorPractice = "#BA68C8"; + private readonly string TimerColorActive = "#43A047"; + private readonly string RankColorPb = "#7986CB"; + private readonly string RankColorWr = "#FFD700"; + private readonly string SpectatorColor = "#9E9E9E"; internal PlayerHud(Player Player) { _player = Player; } - private static string FormatHUDElementHTML(string title, string body, string color, string size = "m") + private static string FormatHUDElementHTML( + string title, + string body, + string color, + string size = "m" + ) { if (title != "") { @@ -20,7 +32,6 @@ private static string FormatHUDElementHTML(string title, string body, string col else return $"{title}: {body}"; } - else { if (size == "m") @@ -35,7 +46,10 @@ private static string FormatHUDElementHTML(string title, string body, string col /// Unless specified differently, the default formatting will be `Compact`. /// Check for all formatting types. /// - public static 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,6 +71,205 @@ public static string FormatTime(int ticks, PlayerTimer.TimeFormatStyle style = P } } + /// + /// Build the timer module with appropriate prefix based on mode + /// + /// string timerModule + internal string BuildTimerWithPrefix() + { + // Timer Module + string timerColor = TimerColor; + + if (_player.Timer.IsRunning) + { + if (_player.Timer.IsPracticeMode) + timerColor = TimerColorPractice; + else + timerColor = TimerColorActive; + } + + string prefix = ""; + + if (_player.Timer.IsPracticeMode) + prefix += "[P] "; + + if (_player.Timer.IsBonusMode) + prefix += $"[B{_player.Timer.Bonus}] "; + else if (_player.Timer.IsStageMode) + prefix += $"[S{_player.Timer.Stage}] "; + + string timerModule = FormatHUDElementHTML( + "", + prefix + FormatTime(_player.Timer.Ticks), + timerColor + ); + + return timerModule; + } + + /// + /// Build the velocity module + /// + /// string velocityModule + internal string BuildVelocityModule() + { + float velocity = GetVelocityFromController(_player.Controller); + string velocityModule = + FormatHUDElementHTML( + "Speed", + velocity.ToString("0"), + Extensions.GetSpeedColorGradient(velocity) + ) + " u/s"; + return velocityModule; + } + + /// + /// Build the rank module with appropriate values based on mode + /// + /// string rankModule + internal string BuildRankModule() + { + int style = _player.Timer.Style; + + // Rank Module + string rankModule = FormatHUDElementHTML("Rank", $"N/A", RankColorPb); + if (_player.Timer.IsBonusMode) + { + if ( + _player.Stats.BonusPB[_player.Timer.Bonus][style].ID != -1 + && SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1 + ) + rankModule = FormatHUDElementHTML( + "Rank", + $"{_player.Stats.BonusPB[_player.Timer.Bonus][style].Rank}/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", + RankColorPb + ); + else if (SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) + rankModule = FormatHUDElementHTML( + "Rank", + $"-/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", + RankColorPb + ); + } + else if (_player.Timer.IsStageMode) + { + if ( + _player.Stats.StagePB[_player.Timer.Stage][style].ID != -1 + && SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1 + ) + rankModule = FormatHUDElementHTML( + "Rank", + $"{_player.Stats.StagePB[_player.Timer.Stage][style].Rank}/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", + RankColorPb + ); + else if (SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) + rankModule = FormatHUDElementHTML( + "Rank", + $"-/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", + RankColorPb + ); + } + else + { + if (_player.Stats.PB[style].ID != -1 && SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML( + "Rank", + $"{_player.Stats.PB[style].Rank}/{SurfTimer.CurrentMap.MapCompletions[style]}", + RankColorPb + ); + else if (SurfTimer.CurrentMap.WR[style].ID != -1) + rankModule = FormatHUDElementHTML( + "Rank", + $"-/{SurfTimer.CurrentMap.MapCompletions[style]}", + RankColorPb + ); + } + + return rankModule; + } + + /// + /// Build the PB module with appropriate values based on mode + /// + /// string pbModule + internal string BuildPbModule() + { + int style = _player.Timer.Style; + + // PB & WR Modules + string pbModule = FormatHUDElementHTML( + "PB", + _player.Stats.PB[style].RunTime > 0 + ? FormatTime(_player.Stats.PB[style].RunTime) + : "N/A", + RankColorPb + ); + + if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values + { + pbModule = FormatHUDElementHTML( + "PB", + _player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime > 0 + ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime) + : "N/A", + RankColorPb + ); + } + else if (_player.Timer.IsStageMode) // Show corresponding stage values + { + pbModule = FormatHUDElementHTML( + "PB", + _player.Stats.StagePB[_player.Timer.Stage][style].RunTime > 0 + ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].RunTime) + : "N/A", + RankColorPb + ); + } + + return pbModule; + } + + /// + /// Build the WR module with appropriate values based on mode + /// + /// string wrModule + internal string BuildWrModule() + { + int style = _player.Timer.Style; + + // WR Module + string wrModule = FormatHUDElementHTML( + "WR", + SurfTimer.CurrentMap.WR[style].RunTime > 0 + ? FormatTime(SurfTimer.CurrentMap.WR[style].RunTime) + : "N/A", + RankColorWr + ); + + if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values + { + wrModule = FormatHUDElementHTML( + "WR", + SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime > 0 + ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime) + : "N/A", + RankColorWr + ); + } + else if (_player.Timer.IsStageMode) // Show corresponding stage values + { + wrModule = FormatHUDElementHTML( + "WR", + SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime > 0 + ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime) + : "N/A", + RankColorWr + ); + } + + return wrModule; + } + /// /// Displays the Center HUD for the client /// @@ -67,100 +280,238 @@ internal void Display() if (_player.Controller.PawnIsAlive) { - int style = _player.Timer.Style; - // Timer Module - string timerColor = "#79d1ed"; - - if (_player.Timer.IsRunning) - { - if (_player.Timer.IsPracticeMode) - timerColor = "#F2C94C"; - else - timerColor = "#2E9F65"; - } + string timerModule = BuildTimerWithPrefix(); - string timerModule; - if (_player.Timer.IsBonusMode) - timerModule = FormatHUDElementHTML("", $"[B{_player.Timer.Bonus}] " + FormatTime(_player.Timer.Ticks), timerColor); - else if (_player.Timer.IsStageMode) - timerModule = FormatHUDElementHTML("", $"[S{_player.Timer.Stage}] " + FormatTime(_player.Timer.Ticks), timerColor); - else - timerModule = FormatHUDElementHTML("", FormatTime(_player.Timer.Ticks), timerColor); + // Velocity Module + string velocityModule = BuildVelocityModule(); - // 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 - + _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"); - if (_player.Timer.IsBonusMode) - { - if (_player.Stats.BonusPB[_player.Timer.Bonus][style].ID != -1 && SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.BonusPB[_player.Timer.Bonus][style].Rank}/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); - else if (SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.BonusCompletions[_player.Timer.Bonus][style]}", "#7882dd"); - } - else if (_player.Timer.IsStageMode) - { - if (_player.Stats.StagePB[_player.Timer.Stage][style].ID != -1 && SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.StagePB[_player.Timer.Stage][style].Rank}/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); - else if (SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.StageCompletions[_player.Timer.Stage][style]}", "#7882dd"); - } - else - { - if (_player.Stats.PB[style].ID != -1 && SurfTimer.CurrentMap.WR[style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"{_player.Stats.PB[style].Rank}/{SurfTimer.CurrentMap.MapCompletions[style]}", "#7882dd"); - else if (SurfTimer.CurrentMap.WR[style].ID != -1) - rankModule = FormatHUDElementHTML("Rank", $"-/{SurfTimer.CurrentMap.MapCompletions[style]}", "#7882dd"); - } + string rankModule = BuildRankModule(); // PB & WR Modules - string pbModule = FormatHUDElementHTML("PB", _player.Stats.PB[style].RunTime > 0 ? FormatTime(_player.Stats.PB[style].RunTime) : "N/A", "#7882dd"); - string wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.WR[style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.WR[style].RunTime) : "N/A", "#ffc61a"); - - if (_player.Timer.Bonus > 0 && _player.Timer.IsBonusMode) // Show corresponding bonus values - { - pbModule = FormatHUDElementHTML("PB", _player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime > 0 ? FormatTime(_player.Stats.BonusPB[_player.Timer.Bonus][style].RunTime) : "N/A", "#7882dd"); - wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.BonusWR[_player.Timer.Bonus][style].RunTime) : "N/A", "#ffc61a"); - } - else if (_player.Timer.IsStageMode) // Show corresponding stage values - { - pbModule = FormatHUDElementHTML("PB", _player.Stats.StagePB[_player.Timer.Stage][style].RunTime > 0 ? FormatTime(_player.Stats.StagePB[_player.Timer.Stage][style].RunTime) : "N/A", "#7882dd"); - wrModule = FormatHUDElementHTML("WR", SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime > 0 ? FormatTime(SurfTimer.CurrentMap.StageWR[_player.Timer.Stage][style].RunTime) : "N/A", "#ffc61a"); - } + string pbModule = BuildPbModule(); + string wrModule = BuildWrModule(); // Build HUD - string hud = $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; + string hud = + $"{timerModule}
{velocityModule}
{pbModule} | {rankModule}
{wrModule}"; // Display HUD _player.Controller.PrintToCenterHtml(hud); } else if (_player.Controller.Team == CsTeam.Spectator) { - ReplayPlayer? spec_replay; - - if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.MapWR.Controller!)) - spec_replay = SurfTimer.CurrentMap.ReplayManager.MapWR; - else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.StageWR?.Controller!)) - spec_replay = SurfTimer.CurrentMap.ReplayManager.StageWR!; - else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.BonusWR?.Controller!)) - spec_replay = SurfTimer.CurrentMap.ReplayManager.BonusWR!; - else - spec_replay = SurfTimer.CurrentMap.ReplayManager.CustomReplays.Find(x => _player.IsSpectating(x.Controller!)); + DisplaySpectatorHud(); + } + } - if (spec_replay != null) - { - string replayModule = $"{FormatHUDElementHTML("", "REPLAY", "red", "large")}"; - string nameModule = FormatHUDElementHTML($"{spec_replay.RecordPlayerName}", $"{FormatTime(spec_replay.RecordRunTime)}", "#ffd500"); - string hud = $"{replayModule}
{nameModule}"; + /// + /// Displays the Spectator HUD for the client if they are spectating a replay + /// + internal void DisplaySpectatorHud() + { + ReplayPlayer? specReplay; + string hud = string.Empty; - _player.Controller.PrintToCenterHtml(hud); - } + if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.MapWR.Controller!)) + { + specReplay = SurfTimer.CurrentMap.ReplayManager.MapWR; + hud = BuildMapWrModule(specReplay); + } + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.StageWR?.Controller!)) + { + specReplay = SurfTimer.CurrentMap.ReplayManager.StageWR!; + hud = BuildStageWrModule(specReplay); + } + else if (_player.IsSpectating(SurfTimer.CurrentMap.ReplayManager.BonusWR?.Controller!)) + { + specReplay = SurfTimer.CurrentMap.ReplayManager.BonusWR!; + hud = BuildBonusWrModule(specReplay); + } + else + { + specReplay = SurfTimer.CurrentMap.ReplayManager.CustomReplays.Find(x => + _player.IsSpectating(x.Controller!) + ); + if (specReplay != null) + hud = BuildCustomReplayModule(specReplay); + } + + if (!string.IsNullOrEmpty(hud)) + { + _player.Controller.PrintToCenterHtml(hud); } } + /// + /// Build the Map WR module for the spectator HUD + /// + /// Replay data to use + private string BuildMapWrModule(ReplayPlayer specReplay) + { + float velocity = GetVelocityFromController(specReplay.Controller!); + string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; + + string replayModule = FormatHUDElementHTML("", "Map WR Replay", SpectatorColor, "m"); + string nameModule = FormatHUDElementHTML("", $"{specReplay.RecordPlayerName}", RankColorWr); + string timeModule = FormatHUDElementHTML( + "", + $"{FormatTime(specReplay.ReplayCurrentRunTime)} / {FormatTime(specReplay.RecordRunTime)}", + timerColor + ); + string velocityModule = + FormatHUDElementHTML( + "Speed", + velocity.ToString("0"), + Extensions.GetSpeedColorGradient(velocity) + ) + " u/s"; + string cycleModule = FormatHUDElementHTML( + "Cycle", + $"{specReplay.RepeatCount}", + SpectatorColor, + "s" + ); + + return $"{replayModule}
{nameModule}
{timeModule}
{velocityModule}
{cycleModule}"; + } + + /// + /// Build the Stage WR module for the spectator HUD + /// + /// Replay data to use + private string BuildStageWrModule(ReplayPlayer specReplay) + { + float velocity = GetVelocityFromController(specReplay.Controller!); + string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; + + string replayModule = FormatHUDElementHTML( + "", + $"Stage {specReplay.Stage} WR Replay", + SpectatorColor, + "m" + ); + string nameModule = FormatHUDElementHTML("", $"{specReplay.RecordPlayerName}", RankColorWr); + string timeModule = FormatHUDElementHTML( + "", + $"{FormatTime(specReplay.ReplayCurrentRunTime)} / {FormatTime(specReplay.RecordRunTime)}", + timerColor + ); + string velocityModule = + FormatHUDElementHTML( + "Speed", + velocity.ToString("0"), + Extensions.GetSpeedColorGradient(velocity) + ) + " u/s"; + string cycleModule = FormatHUDElementHTML( + "Cycle", + $"{specReplay.RepeatCount}", + SpectatorColor, + "s" + ); + + return $"{replayModule}
{nameModule}
{timeModule}
{velocityModule}
{cycleModule}"; + } + + /// + /// Build the Bonus WR module for the spectator HUD + /// + /// Replay data to use< + private string BuildBonusWrModule(ReplayPlayer specReplay) + { + float velocity = GetVelocityFromController(specReplay.Controller!); + string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; + + string replayModule = FormatHUDElementHTML( + "", + $"Bonus {specReplay.Stage} WR Replay", + SpectatorColor, + "m" + ); + string nameModule = FormatHUDElementHTML("", $"{specReplay.RecordPlayerName}", RankColorWr); + string timeModule = FormatHUDElementHTML( + "", + $"{FormatTime(specReplay.ReplayCurrentRunTime)} / {FormatTime(specReplay.RecordRunTime)}", + timerColor + ); + string velocityModule = + FormatHUDElementHTML( + "Speed", + velocity.ToString("0"), + Extensions.GetSpeedColorGradient(velocity) + ) + " u/s"; + string cycleModule = FormatHUDElementHTML( + "Cycle", + $"{specReplay.RepeatCount}", + SpectatorColor, + "s" + ); + + return $"{replayModule}
{nameModule}
{timeModule}
{velocityModule}
{cycleModule}"; + } + + /// + /// Build the Custom Replay module for the spectator HUD + /// + /// Replay data to use< + private string BuildCustomReplayModule(ReplayPlayer specReplay) + { + float velocity = GetVelocityFromController(specReplay.Controller!); + string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; + + string replayType; + switch (specReplay.Type) + { + case 0: + replayType = "Map PB Replay"; + break; + case 1: + replayType = $"Bonus {specReplay.Stage} PB Replay"; + break; + case 2: + replayType = $"Stage {specReplay.Stage} PB Replay"; + break; + default: + return ""; // Invalid type + } + + string replayModule = FormatHUDElementHTML("", replayType, SpectatorColor, "m"); + string nameModule = FormatHUDElementHTML("", $"{specReplay.RecordPlayerName}", RankColorWr); + string timeModule = FormatHUDElementHTML( + "", + $"{FormatTime(specReplay.ReplayCurrentRunTime)} / {FormatTime(specReplay.RecordRunTime)}", + timerColor + ); + string velocityModule = + FormatHUDElementHTML( + "Speed", + velocity.ToString("0"), + Extensions.GetSpeedColorGradient(velocity) + ) + " u/s"; + string cycleModule = FormatHUDElementHTML( + "Cycle", + $"{specReplay.RepeatCount}", + SpectatorColor, + "s" + ); + + return $"{replayModule}
{nameModule}
{timeModule}
{velocityModule}
{cycleModule}"; + } + + /// + /// Calculates the velocity of a given player controller + /// + /// Controller to calculate velocity for + /// float velocity + private static float GetVelocityFromController(CCSPlayerController controller) + { + var pawn = controller.PlayerPawn?.Value; + if (pawn == null) + return 0.0f; + + var vel = pawn.AbsVelocity; + return (float)Math.Sqrt(vel.X * vel.X + vel.Y * vel.Y + vel.Z * vel.Z); + } + /// /// Displays checkpoints comparison messages in player chat. /// Only calculates if the player has a PB, otherwise it will display N/A @@ -172,56 +523,47 @@ internal void DisplayCheckpointMessages() float pbSpeed; float wrSpeed = -1.0f; int style = _player.Timer.Style; - int playerCheckpoint = _player.Timer.Checkpoint; - + int playerCurrentCheckpoint = _player.Timer.Checkpoint; 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); + float currentSpeed = GetVelocityFromController(_player.Controller!); // 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})"; + 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 checkpoint in this case but they will not have for the map as well - // Can check checkpoints count instead of try/catch - try + // Get PB checkpoint data if available + if (_player.Stats.PB[style].Checkpoints != null) { - pbTime = _player.Stats.PB[style].Checkpoints![playerCheckpoint].RunTime; - pbSpeed = (float)Math.Sqrt(_player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelX * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelX - + _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelY * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelY - + _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelZ * _player.Stats.PB[style].Checkpoints![playerCheckpoint].StartVelZ); - -#if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [TIME] Got pbTime from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint} = {pbTime}]"); - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> [SPEED] Got pbSpeed from _player.Stats.PB[{style}].Checkpoint[{playerCheckpoint}] = {pbSpeed}"); -#endif + pbTime = _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].RunTime; + pbSpeed = (float) + Math.Sqrt( + _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelX + * _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelX + + _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelY + * _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelY + + _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelZ + * _player.Stats.PB[style].Checkpoints![playerCurrentCheckpoint].StartVelZ + ); } -#if DEBUG - catch (System.Exception ex) -#else - catch (System.Exception) -#endif + else { - // Handle the exception gracefully without stopping // 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[{style}].Checkpoint.Count = {_player.Stats.PB[style].Checkpoints.Count}"); -#endif } // Calculate differences in PB (PB - Current) if (pbTime != -1) { #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting PB difference calculation... (pbTime != -1)"); + Console.WriteLine( + $"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting PB difference calculation... (pbTime != -1)" + ); #endif // Reset the string - strPbDifference = ""; + strPbDifference = string.Empty; // Calculate the time difference if (pbTime - currentTime < 0.0) @@ -237,11 +579,13 @@ internal void DisplayCheckpointMessages() // 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 + 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.Red + "-" + (pbSpeed - currentSpeed).ToString("0"); } strPbDifference += ChatColors.Default + ")"; } @@ -250,15 +594,31 @@ internal void DisplayCheckpointMessages() { // Calculate differences in WR (WR - Current) #if DEBUG - Console.WriteLine($"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)"); + Console.WriteLine( + $"CS2 Surf DEBUG >> DisplayCheckpointMessages -> Starting WR difference calculation... (SurfTimer.CurrentMap.WR[{style}].Ticks > 0)" + ); #endif - wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].RunTime; - wrSpeed = (float)Math.Sqrt(SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelX * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelX - + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelY * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelY - + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelZ * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCheckpoint].StartVelZ); + wrTime = SurfTimer.CurrentMap.WR[style].Checkpoints![playerCurrentCheckpoint].RunTime; + wrSpeed = (float) + Math.Sqrt( + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCurrentCheckpoint].StartVelX + * SurfTimer.CurrentMap.WR[style].Checkpoints![playerCurrentCheckpoint].StartVelX + + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCurrentCheckpoint].StartVelY + * SurfTimer + .CurrentMap + .WR[style] + .Checkpoints![playerCurrentCheckpoint] + .StartVelY + + SurfTimer.CurrentMap.WR[style].Checkpoints![playerCurrentCheckpoint].StartVelZ + * SurfTimer + .CurrentMap + .WR[style] + .Checkpoints![playerCurrentCheckpoint] + .StartVelZ + ); // Reset the string - strWrDifference = ""; + strWrDifference = string.Empty; // Calculate the WR time difference if (wrTime - currentTime < 0.0) @@ -274,25 +634,36 @@ internal void DisplayCheckpointMessages() // 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 + 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.Red + "-" + (wrSpeed - currentSpeed).ToString("0"); } strWrDifference += ChatColors.Default + ")"; } // Print checkpoint message - _player.Controller.PrintToChat($"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["checkpoint_message", - playerCheckpoint, FormatTime(_player.Timer.Ticks), currentSpeed.ToString("0"), strPbDifference, strWrDifference]}" + _player.Controller.PrintToChat( + $"{Config.PluginPrefix} {LocalizationService.LocalizerNonNull["checkpoint_message", + playerCurrentCheckpoint, FormatTime(_player.Timer.Ticks), currentSpeed.ToString("0"), strPbDifference, 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}"); - 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}"); + 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/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index 025cdb3..d108ce6 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -163,6 +163,15 @@ internal void Tick() { this.IsReplayOutsideZone = false; } + else if (current_frame.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT && this.Type == 2) + { + IsReplayOutsideZone = true; + ReplayCurrentRunTime = 0; + } + else if (current_frame.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER && this.Type == 2) + { + IsReplayOutsideZone = false; + } } else { @@ -173,7 +182,16 @@ internal void Tick() else if (current_frame.Situation == ReplayFrameSituation.END_ZONE_ENTER) { this.IsReplayOutsideZone = true; - this.ReplayCurrentRunTime = this.CurrentFrameTick - (64 * 2); // (64*2) counts for the 2 seconds before run actually starts + this.ReplayCurrentRunTime = this.CurrentFrameTick - (Config.ReplaysPre * 2); // (64*2) counts for the 2 seconds before run actually starts + } + else if (current_frame.Situation == ReplayFrameSituation.STAGE_ZONE_EXIT && this.Type == 2) + { + this.IsReplayOutsideZone = false; + } + else if (current_frame.Situation == ReplayFrameSituation.STAGE_ZONE_ENTER && this.Type == 2) + { + IsReplayOutsideZone = true; + this.ReplayCurrentRunTime = this.CurrentFrameTick - (Config.ReplaysPre * 2); // (64*2) counts for the 2 seconds before run actually starts } } // END OF BLASPHEMY diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 0e11921..2325f24 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -7,6 +7,13 @@ namespace SurfTimer; public static class Config { + public static readonly string PluginLogo = """ + + ____________ ____ ___ + / ___/ __/_ | / __/_ ______/ _/ + / /___\ \/ __/ _\ \/ // / __/ _/ + \___/___/____/ /___/\_,_/_/ /_/ + """; public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; public static readonly string PluginPrefix = LocalizationService.LocalizerNonNull["prefix"]; public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 8095735..bd14373 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -75,4 +75,44 @@ public static char GetTierColor(short tier) _ => ChatColors.White }; } + + /// + /// Color gradient for speed, based on a range of velocities. + /// + /// Velocity to determine color for + /// Minimum velocity + /// Maximum velocity + /// HEX value as string + public static string GetSpeedColorGradient(float velocity, float minSpeed = 240f, float maxSpeed = 4000f) + { + // Key colors (HEX -> RGB) + (int R, int G, int B)[] gradient = new (int, int, int)[] + { + (79, 195, 247), // blue #4FC3F7 + (46, 159, 101), // green #2E9F65 + (255, 255, 0), // yellow #FFFF00 + (255, 165, 0), // orange #FFA500 + (255, 0, 0) // red #FF0000 + }; + + // Limit velocity + velocity = Math.Clamp(velocity, minSpeed, maxSpeed); + + // Normalize velocity to 0..1 + float t = (velocity - minSpeed) / (maxSpeed - minSpeed); + + // Calculate which part of the gradient we are in + float scaledT = t * (gradient.Length - 1); + int index1 = (int)Math.Floor(scaledT); + int index2 = Math.Min(index1 + 1, gradient.Length - 1); + + float localT = scaledT - index1; + + // Linear interpolation between the two color points + int r = (int)(gradient[index1].R + (gradient[index2].R - gradient[index1].R) * localT); + int g = (int)(gradient[index1].G + (gradient[index2].G - gradient[index1].G) * localT); + int b = (int)(gradient[index1].B + (gradient[index2].B - gradient[index1].B) * localT); + + return $"#{r:X2}{g:X2}{b:X2}"; + } } \ No newline at end of file diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 487a881..6a2db88 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -1,15 +1,15 @@ /* - ___ _____ ____________ ___ - ___ / _/ | / / ____/ __ \/ | - ___ / // |/ / /_ / /_/ / /| | - ___ _/ // /| / __/ / _, _/ ___ | - ___ /___/_/ |_/_/ /_/ |_/_/ |_| + ___ _____ ____________ ___ + ___ / _/ | / / ____/ __ \/ | + ___ / // |/ / /_ / /_/ / /| | + ___ _/ // /| / __/ / _, _/ ___ | + ___ /___/_/ |_/_/ /_/ |_/_/ |_| - ___ ___________ __ ___ _____ __ ______ + ___ ___________ __ ___ _____ __ ______ ___ /_ __/ ___// / / | / ___// / / / __ \ ___ / / \__ \/ / / /| | \__ \/ /_/ / / / / - ___ / / ___/ / /___/ ___ |___/ / __ / /_/ / - ___ /_/ /____/_____/_/ |_/____/_/ /_/_____/ + ___ / / ___/ / /___/ ___ |___/ / __ / /_/ / + ___ /_/ /____/_____/_/ |_/____/_/ /_/_____/ Official Timer plugin for the CS2 Surf Initiative. Copyright (C) 2024 Liam C. (Infra) @@ -33,6 +33,7 @@ You should have received a copy of the GNU Affero General Public License #define DEBUG +using System.Collections.Concurrent; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; @@ -40,7 +41,6 @@ You should have received a copy of the GNU Affero General Public License using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; -using System.Collections.Concurrent; namespace SurfTimer; @@ -68,7 +68,7 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) // Globals private readonly ConcurrentDictionary playerList = new(); - internal readonly static TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string + internal static readonly TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string public static Map CurrentMap { get; private set; } = null!; /* ========== MAP START HOOKS ========== */ @@ -77,25 +77,40 @@ public void OnMapStart(string mapName) // Initialise Map Object if ((CurrentMap == null || CurrentMap.Name!.Equals(mapName)) && mapName.Contains("surf_")) { - Server.NextWorldUpdate(() => Console.WriteLine(String.Format(" ____________ ____ ___\n" - + " / ___/ __/_ | / __/_ ______/ _/\n" - + "/ /___\\ \\/ __/ _\\ \\/ // / __/ _/ \n" - + "\\___/___/____/ /___/\\_,_/_/ /_/\n" - + $"[{Config.PluginName}] v.{ModuleVersion} - loading map {mapName}.\n" - + $"[{Config.PluginName}] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" - + $"[{Config.PluginName}] ---> Source Code: https://github.com/CS2Surf/Timer\n" - + $"[{Config.PluginName}] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n" - ))); - Server.NextWorldUpdate(async () => + _logger.LogInformation( + "[{Prefix}] New map {MapName} started. Initializing Map object.....", + Config.PluginName, + mapName + ); + + Server.NextWorldUpdate(async () => // NextWorldUpdate runs even during server hibernation { + _logger.LogInformation( + "{PluginLogo}\n" + + "[CS2 Surf] {PluginName} v.{ModuleVersion} - loading map {MapName}.\n" + + "[CS2 Surf] This software is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information.\n" + + "[CS2 Surf] ---> Source Code: https://github.com/CS2Surf/Timer\n" + + "[CS2 Surf] ---> License Agreement: https://github.com/CS2Surf/Timer/blob/main/LICENSE\n", + Config.PluginLogo, + Config.PluginName, + ModuleVersion, + mapName + ); + CurrentMap = new Map(mapName); await CurrentMap.InitializeAsync(); - }); // NextWorldUpdate runs even during server hibernation + }); } } public void OnMapEnd() { + _logger.LogInformation( + "[{Prefix}] Map ({MapName}) ended. Cleaning up resources...", + CurrentMap.Name, + Config.PluginName + ); + // Clear/reset stuff here CurrentMap = null!; playerList.Clear(); @@ -112,7 +127,8 @@ public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) ConVarHelper.RemoveCheatFlagFromConVar("bot_zombie"); Server.ExecuteCommand("execifexists SurfTimer/server_settings.cfg"); - _logger.LogTrace("[{Prefix}] Executed configuration: server_settings.cfg", + _logger.LogTrace( + "[{Prefix}] Executed configuration: server_settings.cfg", Config.PluginName ); return HookResult.Continue; @@ -127,39 +143,51 @@ public override void Load(bool hotReload) try { - accessService = Task.Run(() => _dataService!.PingAccessService()).GetAwaiter().GetResult(); + accessService = Task.Run(() => _dataService!.PingAccessService()) + .GetAwaiter() + .GetResult(); } catch (Exception ex) { - _logger.LogError(ex, "[{Prefix}] PingAccessService threw an exception.", Config.PluginName); + _logger.LogError( + ex, + "[{Prefix}] PingAccessService threw an exception.", + Config.PluginName + ); } if (accessService) { - _logger.LogInformation("[{Prefix}] {AccessService} connection established.", - Config.PluginName, Config.Api.GetApiOnly() ? "API" : "DB" + _logger.LogInformation( + "[{Prefix}] {AccessService} connection established.", + Config.PluginName, + Config.Api.GetApiOnly() ? "API" : "DB" ); } else { - _logger.LogCritical("[{Prefix}] Error connecting to the {AccessService}.", - Config.PluginName, Config.Api.GetApiOnly() ? "API" : "DB" + _logger.LogCritical( + "[{Prefix}] Error connecting to the {AccessService}.", + Config.PluginName, + Config.Api.GetApiOnly() ? "API" : "DB" ); - Exception exception = new Exception($"[{Config.PluginName}] Error connecting to the {(Config.Api.GetApiOnly() ? "API" : "DB")}"); + Exception exception = new Exception( + $"[{Config.PluginName}] Error connecting to the {(Config.Api.GetApiOnly() ? "API" : "DB")}" + ); throw exception; } - _logger.LogInformation(""" - - ____________ ____ ___ - / ___/ __/_ | / __/_ ______/ _/ - / /___\ \/ __/ _\ \/ // / __/ _/ - \___/___/____/ /___/\_,_/_/ /_/ - [CS2 Surf] {PluginName} plugin loaded. Version: {ModuleVersion} - [CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. - Source code: https://github.com/CS2Surf/Timer - """, Config.PluginName, ModuleVersion + _logger.LogInformation( + """ + {PluginLogo} + [CS2 Surf] {PluginName} plugin loaded. Version: {ModuleVersion} + [CS2 Surf] This plugin is licensed under the GNU Affero General Public License v3.0. See LICENSE for more information. + Source code: https://github.com/CS2Surf/Timer + """, + Config.PluginLogo, + Config.PluginName, + ModuleVersion ); // Map Start Hook @@ -169,7 +197,6 @@ ____________ ____ ___ // Tick listener RegisterListener(OnTick); - HookEntityOutput("trigger_multiple", "OnStartTouch", OnTriggerStartTouch); HookEntityOutput("trigger_multiple", "OnEndTouch", OnTriggerEndTouch); } From 43afca388e28cad91501c2f2772b627af0b17b97 Mon Sep 17 00:00:00 2001 From: tslashd Date: Tue, 26 Aug 2025 17:00:43 +0300 Subject: [PATCH 38/52] fix wrong data being supplied to log message OnMapEnd --- src/SurfTimer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 6a2db88..ca2e527 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -107,8 +107,8 @@ public void OnMapEnd() { _logger.LogInformation( "[{Prefix}] Map ({MapName}) ended. Cleaning up resources...", - CurrentMap.Name, - Config.PluginName + Config.PluginName, + CurrentMap.Name ); // Clear/reset stuff here From bc6f6aed08e32f2ff956d90de086b91c6bd5e39c Mon Sep 17 00:00:00 2001 From: tslashd Date: Tue, 26 Aug 2025 17:43:25 +0300 Subject: [PATCH 39/52] Moved `GetVelocityFromController` for reusability --- src/ST-Player/PlayerHUD.cs | 27 ++++++--------------------- src/ST-UTILS/Extensions.cs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 0881240..3d21ab0 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -113,7 +113,7 @@ internal string BuildTimerWithPrefix() /// string velocityModule internal string BuildVelocityModule() { - float velocity = GetVelocityFromController(_player.Controller); + float velocity = Extensions.GetVelocityFromController(_player.Controller); string velocityModule = FormatHUDElementHTML( "Speed", @@ -349,7 +349,7 @@ internal void DisplaySpectatorHud() /// Replay data to use private string BuildMapWrModule(ReplayPlayer specReplay) { - float velocity = GetVelocityFromController(specReplay.Controller!); + float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; string replayModule = FormatHUDElementHTML("", "Map WR Replay", SpectatorColor, "m"); @@ -381,7 +381,7 @@ private string BuildMapWrModule(ReplayPlayer specReplay) /// Replay data to use private string BuildStageWrModule(ReplayPlayer specReplay) { - float velocity = GetVelocityFromController(specReplay.Controller!); + float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; string replayModule = FormatHUDElementHTML( @@ -418,7 +418,7 @@ private string BuildStageWrModule(ReplayPlayer specReplay) /// Replay data to use< private string BuildBonusWrModule(ReplayPlayer specReplay) { - float velocity = GetVelocityFromController(specReplay.Controller!); + float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; string replayModule = FormatHUDElementHTML( @@ -455,7 +455,7 @@ private string BuildBonusWrModule(ReplayPlayer specReplay) /// Replay data to use< private string BuildCustomReplayModule(ReplayPlayer specReplay) { - float velocity = GetVelocityFromController(specReplay.Controller!); + float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; string replayType; @@ -497,21 +497,6 @@ private string BuildCustomReplayModule(ReplayPlayer specReplay) return $"{replayModule}
{nameModule}
{timeModule}
{velocityModule}
{cycleModule}"; } - /// - /// Calculates the velocity of a given player controller - /// - /// Controller to calculate velocity for - /// float velocity - private static float GetVelocityFromController(CCSPlayerController controller) - { - var pawn = controller.PlayerPawn?.Value; - if (pawn == null) - return 0.0f; - - var vel = pawn.AbsVelocity; - return (float)Math.Sqrt(vel.X * vel.X + vel.Y * vel.Y + vel.Z * vel.Z); - } - /// /// Displays checkpoints comparison messages in player chat. /// Only calculates if the player has a PB, otherwise it will display N/A @@ -525,7 +510,7 @@ internal void DisplayCheckpointMessages() int style = _player.Timer.Style; int playerCurrentCheckpoint = _player.Timer.Checkpoint; int currentTime = _player.Timer.Ticks; - float currentSpeed = GetVelocityFromController(_player.Controller!); + float currentSpeed = Extensions.GetVelocityFromController(_player.Controller!); // Default values for the PB and WR differences in case no calculations can be made string strPbDifference = diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index bd14373..1ef0dd6 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -115,4 +115,20 @@ public static string GetSpeedColorGradient(float velocity, float minSpeed = 240f return $"#{r:X2}{g:X2}{b:X2}"; } + + /// + /// Calculates the velocity of a given player controller + /// + /// Controller to calculate velocity for + /// float velocity + public static float GetVelocityFromController(CCSPlayerController controller) + { + var pawn = controller.PlayerPawn?.Value; + if (pawn == null) + return 0.0f; + + var vel = pawn.AbsVelocity; + return (float)Math.Sqrt(vel.X * vel.X + vel.Y * vel.Y + vel.Z * vel.Z); + } + } \ No newline at end of file From 59522cc9dea4afabeeb19c398ed27460da6e902e Mon Sep 17 00:00:00 2001 From: tslashd Date: Wed, 27 Aug 2025 04:02:36 +0300 Subject: [PATCH 40/52] Update README, refactor code - Updated MySQL database schema link in README.md. - Reformatted entries in `en.json` without content changes. - Refactored `IsPrivateIP` method to `Extensions` class. - Changed access modifiers of methods in `PlayerHUD.cs` to internal. - Commented out addition to `ConnectedMapTimes`. - Updated `CounterStrikeSharp.API` package reference in project file. - Changed `NextWorldUpdate` to `NextWorldUpdateAsync` for async execution. --- README.md | 6 +- lang/en.json | 78 ++++++++++++------------ src/ST-Events/Players.cs | 27 +------- src/ST-Events/TriggerStartTouch.cs | 2 - src/ST-Map/Map.cs | 4 +- src/ST-Player/PlayerHUD.cs | 8 +-- src/ST-Player/PlayerStats/CurrentRun.cs | 3 - src/ST-Player/PlayerStats/PlayerStats.cs | 1 + src/ST-UTILS/Extensions.cs | 28 +++++++++ src/SurfTimer.Plugin.csproj | 2 +- src/SurfTimer.cs | 2 +- 11 files changed, 80 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 1741b47..02bf6b3 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source *Note: This is not definitive/complete and simply serves as a reference for what we should try to achieve. Subject to change.* Bold & Italics = being worked on. - [ ] Database - - [X] MySQL database schema ([Design Diagram](https://dbdiagram.io/d/CS2Surf-Timer-DB-Schema-6560b76b3be1495787ace4d2)) + - [X] MySQL database schema ([Design Diagram](https://dbdiagram.io/d/Copy-of-CS2Surf-Timer-DB-Schema-6582e6e456d8064ca06328b9)) - [ ] Plugin auto-create tables for easier setup? - [X] Base database class implementation - [X] Maps @@ -205,8 +205,8 @@ Bold & Italics = being worked on. - [ ] Stretch goal: sub-tick timing - [ ] Player Data - [X] Base player class - - [ ] Player stat classes - - [ ] Profile implementation (DB) + - [X] Player stat classes + - [X] Profile implementation (DB) - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) - [x] Replays diff --git a/lang/en.json b/lang/en.json index 966395e..c8686ca 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,45 +1,45 @@ { - "prefix": "[{blue}Surf{bluegrey}Timer{default}]", + "prefix": "[{blue}Surf{bluegrey}Timer{default}]", - "player_connected": "{green}{0}{default} has connected from {lime}{1}{default}.", + "player_connected": "{green}{0}{default} has connected from {lime}{1}{default}.", - /* MapCommands */ - "map_info": "{yellow}{0}{default} | Tier - {1}{default} | Author - {yellow}{2}{default} | Ranked - {3}{default} | Added - {yellow}{4}{default}", - "map_info_linear": " | Linear - {yellow}{0} Checkpoints{default}", - "map_info_stages": " | Stages - {yellow}{0}{default}", - "map_info_bonuses": " | Bonuses - {yellow}{0}{default}", + /* MapCommands */ + "map_info": "{yellow}{0}{default} | Tier - {1}{default} | Author - {yellow}{2}{default} | Ranked - {3}{default} | Added - {yellow}{4}{default}", + "map_info_linear": " | Linear - {yellow}{0} Checkpoints{default}", + "map_info_stages": " | Stages - {yellow}{0}{default}", + "map_info_bonuses": " | Bonuses - {yellow}{0}{default}", - /* PlayerCommands */ - "reset_delay": "Please wait for your run to be saved before resetting.", - "invalid_usage": "{red}Invalid arguments. {default}Usage: {green}{0}", - "invalid_stage_value": "{red}Invalid stage provided. {default}this map has {green}{0}{default} stages.", - "not_staged": "{red}This map has no stages.", - "invalid_bonus_value": "{red}Invalid bonus provided. {default}this map has {green}{0}{default} bonuses.", - "not_bonused": "{red}This map has no bonuses.", - "rank":"Your current rank for {gold}{0}{default} is {green}{1}{default} out of {yellow}{2}", - "saveloc_not_in_run": "{red}Cannot save location while not in run.", - "saveloc_saved": "{green}Saved location! {default}Use {green}!tele {0}{default} to teleport to this location.", - "saveloc_no_locations": "{red}No saved locations.", - "saveloc_practice": "{red}Timer now in Practice.", - "saveloc_teleported": "Teleported to #{green}{0}", - "saveloc_first": "{red}Already at first location", - "saveloc_last": "{red}Already at last location", + /* PlayerCommands */ + "reset_delay": "Please wait for your run to be saved before resetting.", + "invalid_usage": "{red}Invalid arguments. {default}Usage: {green}{0}", + "invalid_stage_value": "{red}Invalid stage provided. {default}this map has {green}{0}{default} stages.", + "not_staged": "{red}This map has no stages.", + "invalid_bonus_value": "{red}Invalid bonus provided. {default}this map has {green}{0}{default} bonuses.", + "not_bonused": "{red}This map has no bonuses.", + "rank": "Your current rank for {gold}{0}{default} is {green}{1}{default} out of {yellow}{2}", + "saveloc_not_in_run": "{red}Cannot save location while not in run.", + "saveloc_saved": "{green}Saved location! {default}Use {green}!tele {0}{default} to teleport to this location.", + "saveloc_no_locations": "{red}No saved locations.", + "saveloc_practice": "{red}Timer now in Practice.", + "saveloc_teleported": "Teleported to #{green}{0}", + "saveloc_first": "{red}Already at first location", + "saveloc_last": "{red}Already at last location", - /* TriggerStartTouch */ - "stagewr_set": "{lime}{0}{default} set the first {yellow}Stage {1}{default} record at {gold}{2}{default}!", - "stagewr_improved": "{lime}{0}{default} has set a new {yellow}Stage {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", - "stagewr_missed": "You are behind the {yellow}Stage {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", - "stagepb_set": "You finished {yellow}Stage {0}{default} in {gold}{1}{default}, setting your new Personal Best!", - "stagepb_improved": "{lime}{0}{default} beat their {yellow}Stage {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", - "mapwr_set": "{lime}{0}{default} set the first {yellow}Map{default} record at {gold}{1}{default}!", - "mapwr_improved": "{lime}{0}{default} has set a new {yellow}Map{default} record with a time of {gold}{1}{default}, beating the old record by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", - "mappb_set": "You finished the {yellow}Map{default} in {gold}{0}{default}, setting your new Personal Best!", - "mappb_improved": "{lime}{0}{default} beat their {yellow}Map{default} Personal Best with a time of {gold}{1}{default}, improving by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", - "mappb_missed": "You finished the map in {yellow}{0}{default}!", - "bonuswr_set": "{lime}{0}{default} set the first {yellow}Bonus {1}{default} record at {gold}{2}{default}!", - "bonuswr_improved": "{lime}{0}{default} has set a new {yellow}Bonus {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", - "bonuspb_set": "You finished {yellow}Bonus {0}{default} in {gold}{1}{default}, setting your new Personal Best!", - "bonuspb_improved": "{lime}{0}{default} beat their {yellow}Bonus {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", - "bonuspb_missed": "You are behind the {yellow}Bonus {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", - "checkpoint_message": "CP [{yellow}{0}{default}]: {yellow}{1}{default} {yellow}({2}){default} [PB: {3} | WR: {4}]", + /* TriggerStartTouch */ + "stagewr_set": "{lime}{0}{default} set the first {yellow}Stage {1}{default} record at {gold}{2}{default}!", + "stagewr_improved": "{lime}{0}{default} has set a new {yellow}Stage {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "stagewr_missed": "You are behind the {yellow}Stage {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", + "stagepb_set": "You finished {yellow}Stage {0}{default} in {gold}{1}{default}, setting your new Personal Best!", + "stagepb_improved": "{lime}{0}{default} beat their {yellow}Stage {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "mapwr_set": "{lime}{0}{default} set the first {yellow}Map{default} record at {gold}{1}{default}!", + "mapwr_improved": "{lime}{0}{default} has set a new {yellow}Map{default} record with a time of {gold}{1}{default}, beating the old record by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", + "mappb_set": "You finished the {yellow}Map{default} in {gold}{0}{default}, setting your new Personal Best!", + "mappb_improved": "{lime}{0}{default} beat their {yellow}Map{default} Personal Best with a time of {gold}{1}{default}, improving by {green}-{2}{default}! (Previous: {bluegrey}{3}{default})", + "mappb_missed": "You finished the map in {yellow}{0}{default}!", + "bonuswr_set": "{lime}{0}{default} set the first {yellow}Bonus {1}{default} record at {gold}{2}{default}!", + "bonuswr_improved": "{lime}{0}{default} has set a new {yellow}Bonus {1}{default} record with a time of {gold}{2}{default}, beating the old record by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "bonuspb_set": "You finished {yellow}Bonus {0}{default} in {gold}{1}{default}, setting your new Personal Best!", + "bonuspb_improved": "{lime}{0}{default} beat their {yellow}Bonus {1}{default} Personal Best with a time of {gold}{2}{default}, improving by {green}-{3}{default}! (Previous: {bluegrey}{4})", + "bonuspb_missed": "You are behind the {yellow}Bonus {0}{default} record with a time of {grey}{1}{default}, missing the record by {red}+{2}{default} ({gold}{3}{default})!", + "checkpoint_message": "CP [{yellow}{0}{default}]: {yellow}{1}{default} {yellow}({2}){default} [PB: {3} | WR: {4}]" } \ No newline at end of file diff --git a/src/ST-Events/Players.cs b/src/ST-Events/Players.cs index 5cabab1..ead08c4 100644 --- a/src/ST-Events/Players.cs +++ b/src/ST-Events/Players.cs @@ -106,7 +106,7 @@ public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventIn // GeoIP // Check if the IP is private before attempting GeoIP lookup string ipAddress = player.IpAddress!.Split(":")[0]; - if (!IsPrivateIP(ipAddress)) + if (!Extensions.IsPrivateIP(ipAddress)) { DatabaseReader geoipDB = new(Config.PluginPath + "data/GeoIP/GeoLite2-Country.mmdb"); country = geoipDB.Country(ipAddress).Country.IsoCode ?? "XX"; @@ -208,29 +208,4 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo } } - /// - /// Checks whether an IP is a local one. Allows testing the plugin in a local environment setup for GeoIP - /// - /// IP to check - /// True for Private IP - static bool IsPrivateIP(string ip) - { - var ipParts = ip.Split('.'); - int firstOctet = int.Parse(ipParts[0]); - int secondOctet = int.Parse(ipParts[1]); - - // 10.x.x.x range - if (firstOctet == 10) - return true; - - // 172.16.x.x to 172.31.x.x range - if (firstOctet == 172 && (secondOctet >= 16 && secondOctet <= 31)) - return true; - - // 192.168.x.x range - if (firstOctet == 192 && secondOctet == 168) - return true; - - return false; - } } \ No newline at end of file diff --git a/src/ST-Events/TriggerStartTouch.cs b/src/ST-Events/TriggerStartTouch.cs index 15bc27d..121e667 100644 --- a/src/ST-Events/TriggerStartTouch.cs +++ b/src/ST-Events/TriggerStartTouch.cs @@ -8,8 +8,6 @@ public partial class SurfTimer /// /// Handler for trigger start touch hook - CBaseTrigger_StartTouchFunc /// - /// CounterStrikeSharp.API.Core.HookResult - /// internal HookResult OnTriggerStartTouch(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) { CBaseTrigger trigger = new CBaseTrigger(caller.Handle); diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index f7dd9e3..4d3b8be 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -380,7 +380,7 @@ internal async Task LoadMapInfo(bool updateData = true, [CallerMemberName] strin /// internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") { - this.ConnectedMapTimes.Clear(); + //this.ConnectedMapTimes.Clear(); // This is for Custom Replays (PB replays?) - T var runs = await _dataService.GetMapRecordRunsAsync(this.ID); @@ -403,7 +403,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") WR[run.Style].EndVelZ = run.EndVelZ; WR[run.Style].RunDate = run.RunDate; WR[run.Style].Name = run.Name; - ConnectedMapTimes.Add(run.ID); + //ConnectedMapTimes.Add(run.ID); MapCompletions[run.Style] = run.TotalCount; SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames!); diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 3d21ab0..5e23326 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -347,7 +347,7 @@ internal void DisplaySpectatorHud() /// Build the Map WR module for the spectator HUD ///
/// Replay data to use - private string BuildMapWrModule(ReplayPlayer specReplay) + internal string BuildMapWrModule(ReplayPlayer specReplay) { float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; @@ -379,7 +379,7 @@ private string BuildMapWrModule(ReplayPlayer specReplay) /// Build the Stage WR module for the spectator HUD ///
/// Replay data to use - private string BuildStageWrModule(ReplayPlayer specReplay) + internal string BuildStageWrModule(ReplayPlayer specReplay) { float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; @@ -416,7 +416,7 @@ private string BuildStageWrModule(ReplayPlayer specReplay) /// Build the Bonus WR module for the spectator HUD ///
/// Replay data to use< - private string BuildBonusWrModule(ReplayPlayer specReplay) + internal string BuildBonusWrModule(ReplayPlayer specReplay) { float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; @@ -453,7 +453,7 @@ private string BuildBonusWrModule(ReplayPlayer specReplay) /// Build the Custom Replay module for the spectator HUD ///
/// Replay data to use< - private string BuildCustomReplayModule(ReplayPlayer specReplay) + internal string BuildCustomReplayModule(ReplayPlayer specReplay) { float velocity = Extensions.GetVelocityFromController(specReplay.Controller!); string timerColor = specReplay.ReplayCurrentRunTime > 0 ? TimerColorActive : RankColorWr; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index fca7504..dc9dfa5 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -183,9 +183,6 @@ internal static async Task SaveStageTime(Player player, short stage = -1, int st player.ReplayRecorder.IsSaving = true; // Save stage run -#if DEBUG - Console.WriteLine($"==== OnTriggerStartTouch -> SaveStageTime -> [StageWR (IsStageMode? {player.Timer.IsStageMode} | Last? {saveLastStage})] Saving Stage {stage} ({stage}) time of {PlayerHud.FormatTime(stage_run_time)} ({stage_run_time})"); -#endif await player.Stats.ThisRun.SaveMapTime(player, stage: stage, run_ticks: stage_run_time); // Save the Stage MapTime PB data } else if (stage_run_time > SurfTimer.CurrentMap.StageWR[stage][pStyle].RunTime && player.Timer.IsStageMode) // Player is behind the Stage WR for the map diff --git a/src/ST-Player/PlayerStats/PlayerStats.cs b/src/ST-Player/PlayerStats/PlayerStats.cs index b5a3750..fc55c05 100644 --- a/src/ST-Player/PlayerStats/PlayerStats.cs +++ b/src/ST-Player/PlayerStats/PlayerStats.cs @@ -135,6 +135,7 @@ internal async Task LoadPlayerMapTimesData(Player player, int playerId = 0, int PB[style].EndVelY = mapTime.EndVelY; PB[style].EndVelZ = mapTime.EndVelZ; PB[style].RunDate = mapTime.RunDate; + //SurfTimer.CurrentMap.ConnectedMapTimes.Add(mapTime.ID); // Needed for PB replays? await PB[style].LoadCheckpoints(); break; diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 1ef0dd6..009d399 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -55,6 +55,34 @@ public static void SetCollisionGroup(this CCSPlayerController controller, Collis Utilities.SetStateChanged(controller, "CCollisionProperty", "m_collisionAttribute"); } + + /// + /// Checks whether an IP is a local one. Allows testing the plugin in a local environment setup for GeoIP + /// + /// IP to check + /// True for Private IP + public static bool IsPrivateIP(string ip) + { + var ipParts = ip.Split('.'); + int firstOctet = int.Parse(ipParts[0]); + int secondOctet = int.Parse(ipParts[1]); + + // 10.x.x.x range + if (firstOctet == 10) + return true; + + // 172.16.x.x to 172.31.x.x range + if (firstOctet == 172 && (secondOctet >= 16 && secondOctet <= 31)) + return true; + + // 192.168.x.x range + if (firstOctet == 192 && secondOctet == 168) + return true; + + return false; + } + + /// /// Asssigns a ChatColor to the given Tier value /// diff --git a/src/SurfTimer.Plugin.csproj b/src/SurfTimer.Plugin.csproj index fc8784f..7114bd0 100644 --- a/src/SurfTimer.Plugin.csproj +++ b/src/SurfTimer.Plugin.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index ca2e527..26dc050 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -83,7 +83,7 @@ public void OnMapStart(string mapName) mapName ); - Server.NextWorldUpdate(async () => // NextWorldUpdate runs even during server hibernation + Server.NextWorldUpdateAsync(async () => // NextWorldUpdate runs even during server hibernation { _logger.LogInformation( "{PluginLogo}\n" From 08954463cdc93a3905c6d798c03422e4da4d5f1d Mon Sep 17 00:00:00 2001 From: tslashd Date: Thu, 11 Sep 2025 19:59:44 +0300 Subject: [PATCH 41/52] Improve formatting and readability in Comms.cs with CSharpier - Added the `Dapper.dll` binary file to the project. --- src/ST-API/Comms.cs | 117 ++++++++++++++++++++++++++++++-------------- src/bin/Dapper.dll | Bin 0 -> 245760 bytes 2 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/bin/Dapper.dll diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index d5449e8..81d2f3f 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -1,10 +1,10 @@ +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Shared.Entities; using SurfTimer.Shared.JsonConverters; -using System.Net.Http.Json; -using System.Runtime.CompilerServices; -using System.Text.Json; namespace SurfTimer; @@ -16,7 +16,7 @@ internal class ApiMethod // Custom Converter for ReplayFramesString private static readonly JsonSerializerOptions _jsonOptions = new() { - PropertyNameCaseInsensitive = true + PropertyNameCaseInsensitive = true, }; static ApiMethod() @@ -35,34 +35,41 @@ static ApiMethod() var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); -#if DEBUG - Console.WriteLine($"======= CS2 Surf DEBUG >> public static async Task GET -> BASE ADDR: {base_addr} | ENDPOINT: {url} | FULL: {uri.ToString()}"); -#endif - using var response = await _client.GetAsync(uri); try { - var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && - !string.IsNullOrEmpty(values.FirstOrDefault()) - ? $"{values.First()}ms" - : "N/A"; + var responseTime = + response.Headers.TryGetValues("x-response-time-ms", out var values) + && !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; - _logger.LogInformation("[{ClassName}] {MethodName} -> GET {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", - nameof(ApiMethod), methodName, url, response.StatusCode, responseTime + _logger.LogInformation( + "[{ClassName}] {MethodName} -> GET {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", + nameof(ApiMethod), + methodName, + url, + response.StatusCode, + responseTime ); if (response.StatusCode == System.Net.HttpStatusCode.NoContent) { - _logger.LogWarning("[{ClassName}] {MethodName} -> No data found {StatusCode}", - nameof(ApiMethod), methodName, response.StatusCode + _logger.LogWarning( + "[{ClassName}] {MethodName} -> No data found {StatusCode}", + nameof(ApiMethod), + methodName, + response.StatusCode ); return default; } else if (response.StatusCode != System.Net.HttpStatusCode.OK) { - Exception exception = new Exception($"[{nameof(ApiMethod)}] {methodName} -> Unexpected status code {response.StatusCode}"); + Exception exception = new Exception( + $"[{nameof(ApiMethod)}] {methodName} -> Unexpected status code {response.StatusCode}" + ); throw exception; } @@ -71,7 +78,12 @@ static ApiMethod() } catch (Exception ex) { - _logger.LogError(ex, "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", nameof(ApiMethod), methodName); + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> HTTP Response was invalid or could not be deserialised.", + nameof(ApiMethod), + methodName + ); return default; } } @@ -82,8 +94,12 @@ static ApiMethod() /// Type of the request body /// Relative URL to call /// Request body to send - /// API_PostResponseData or null - public static async Task POST(string url, T body, [CallerMemberName] string methodName = "") + /// PostResponseEntity or null + public static async Task POST( + string url, + T body, + [CallerMemberName] string methodName = "" + ) { var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -92,14 +108,19 @@ static ApiMethod() { using var response = await _client.PostAsJsonAsync(uri, body); - var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && - !string.IsNullOrEmpty(values.FirstOrDefault()) - ? $"{values.First()}ms" - : "N/A"; + var responseTime = + response.Headers.TryGetValues("x-response-time-ms", out var values) + && !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; _logger.LogInformation( "[{ClassName}] {MethodName} -> POST {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", - nameof(ApiMethod), methodName, url, response.StatusCode, responseTime + nameof(ApiMethod), + methodName, + url, + response.StatusCode, + responseTime ); if (response.IsSuccessStatusCode) @@ -113,7 +134,11 @@ static ApiMethod() _logger.LogWarning( "[{ClassName}] {MethodName} -> POST {URL} failed with status {StatusCode}. Response body: {ResponseBody}", - nameof(ApiMethod), methodName, url, response.StatusCode, errorContent + nameof(ApiMethod), + methodName, + url, + response.StatusCode, + errorContent ); return default; @@ -124,7 +149,9 @@ static ApiMethod() _logger.LogError( ex, "[{ClassName}] {MethodName} -> Exception during POST {URL}", - nameof(ApiMethod), methodName, url + nameof(ApiMethod), + methodName, + url ); return default; @@ -137,8 +164,12 @@ static ApiMethod() /// Type of the request body /// Relative URL to call /// Request body to send - /// API_PostResponseData or null - public static async Task PUT(string url, T body, [CallerMemberName] string methodName = "") + /// PostResponseEntity or null + public static async Task PUT( + string url, + T body, + [CallerMemberName] string methodName = "" + ) { var uri = new Uri(base_addr + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); @@ -147,14 +178,19 @@ static ApiMethod() { using var response = await _client.PutAsJsonAsync(uri, body); - var responseTime = response.Headers.TryGetValues("x-response-time-ms", out var values) && - !string.IsNullOrEmpty(values.FirstOrDefault()) - ? $"{values.First()}ms" - : "N/A"; + var responseTime = + response.Headers.TryGetValues("x-response-time-ms", out var values) + && !string.IsNullOrEmpty(values.FirstOrDefault()) + ? $"{values.First()}ms" + : "N/A"; _logger.LogInformation( "[{ClassName}] {MethodName} -> PUT {URL} => {StatusCode} | x-response-time-ms {ResponseTime}", - nameof(ApiMethod), methodName, url, response.StatusCode, responseTime + nameof(ApiMethod), + methodName, + url, + response.StatusCode, + responseTime ); if (response.IsSuccessStatusCode) @@ -167,7 +203,11 @@ static ApiMethod() _logger.LogWarning( "[{ClassName}] {MethodName} -> PUT {URL} failed with status {StatusCode}. Response body: {ResponseBody}", - nameof(ApiMethod), methodName, url, response.StatusCode, errorContent + nameof(ApiMethod), + methodName, + url, + response.StatusCode, + errorContent ); return default; @@ -178,11 +218,12 @@ static ApiMethod() _logger.LogError( ex, "[{ClassName}] {MethodName} -> Exception during PUT {URL}", - nameof(ApiMethod), methodName, url + nameof(ApiMethod), + methodName, + url ); return default; } } - -} \ No newline at end of file +} diff --git a/src/bin/Dapper.dll b/src/bin/Dapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..a837f481afef4ca5569b73126428c64c28351473 GIT binary patch literal 245760 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 - /// Spawns a new connection to the database. - /// - /// DB Connection - private MySqlConnection GetConnection() - { - var connection = new MySqlConnection(_connString); - try - { - connection.Open(); - } - catch (MySqlException mysqlEx) // Specifically catch MySQL-related exceptions - { - Console.WriteLine($"[CS2 Surf] MySQL error when connecting: {mysqlEx.Message}"); - throw; - } - catch (Exception ex) // Catch all other exceptions - { - Console.WriteLine($"[CS2 Surf] General error when connecting to the database: {ex.Message}"); - throw; // Re-throw the exception without wrapping it - } - - return connection; - } - - /// - /// Always encapsulate the block with `using` when calling this method. - /// That way we ensure the proper disposal of the `MySqlDataReader` when we are finished with it. - /// - /// SELECT query to execute - public async Task QueryAsync(string query) - { - try - { - var connection = GetConnection(); - var cmd = new MySqlCommand(query, connection); - return await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.CloseConnection); - } - catch (Exception ex) - { - Console.WriteLine($"Error executing query {query}: {ex.Message}"); - throw; - } - } - - /// - /// Automatically disposes of the connection and command are disposed of after usage. - /// No need to encapsulate in `using` block. - /// - /// INSERT/UPDATE query to execute - /// rowsInserted, lastInsertedId - public async Task<(int rowsInserted, long lastInsertedId)> WriteAsync(string query) - { - try - { - using var connection = GetConnection(); - using var cmd = new MySqlCommand(query, connection); - var rows = await cmd.ExecuteNonQueryAsync(); - long lastId = cmd.LastInsertedId; // Retrieve the last ID inserted - return (rows, lastId); - } - catch (Exception ex) - { - Console.WriteLine($"Error executing write operation {query}: {ex.Message}"); - throw; - } - } - - /// - /// Begins a transaction and executes it on the database. - /// Used for inputting `Checkpoints` data after a run has been finished. - /// No need to encapsulate in a `using` block, method disposes of connection and data itself. - /// - /// INSERT/UPDATE queries to execute - public async Task TransactionAsync(List commands) - { - // Create a new connection and open it - using var connection = GetConnection(); - - // Begin a transaction on the connection - using var transaction = await connection.BeginTransactionAsync(); - - try - { - // Execute each command within the transaction - foreach (var commandText in commands) - { - using var cmd = new MySqlCommand(commandText, connection, transaction); - await cmd.ExecuteNonQueryAsync(); - } - - // Commit the transaction - await transaction.CommitAsync(); - } - catch - { - // Roll back the transaction if an error occurs - await transaction.RollbackAsync(); - throw; - } - // The connection and transaction are disposed here - } -} diff --git a/src/ST-Events/ZoneEventHandlers.cs b/src/ST-Events/ZoneEventHandlers.cs index 457b91c..e80c2fb 100644 --- a/src/ST-Events/ZoneEventHandlers.cs +++ b/src/ST-Events/ZoneEventHandlers.cs @@ -238,7 +238,7 @@ private void StartTouchHandleStageStartZone(Player player, CBaseTrigger trigger, if (stage > 1 && !failed_stage && !player.Timer.IsPracticeMode) { int stage_run_time = player.Timer.Ticks - player.Stats.ThisRun.RunTime; // player.Stats.ThisRun.RunTime should be the Tick we left the previous Stage zone - // player.Controller.PrintToChat($"{Config.PluginPrefix} [StageWR (Map RUN)] Sending to SaveStageTime: {player.Profile.Name}, {stage - 1}, {stage_run_time}"); + AddTimer(1.0f, async () => // This determines whether we will have frames for AFTER touch the endZone { await CurrentRun.SaveStageTime(player, (short)(stage - 1), stage_run_time); @@ -551,7 +551,7 @@ private static void EndTouchHandleBonusStartZone(Player player, CBaseTrigger tri if (player.ReplayRecorder.IsRecording) { // Saving 2 seconds before leaving the start zone - 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 + player.ReplayRecorder.Frames.RemoveRange(0, Math.Max(0, player.ReplayRecorder.Frames.Count - (Config.ReplaysPre * 2))); } // BONUS START ZONE diff --git a/src/ST-Map/Map.cs b/src/ST-Map/Map.cs index 4d3b8be..31f5f7f 100644 --- a/src/ST-Map/Map.cs +++ b/src/ST-Map/Map.cs @@ -403,7 +403,7 @@ internal async Task LoadMapRecordRuns([CallerMemberName] string methodName = "") WR[run.Style].EndVelZ = run.EndVelZ; WR[run.Style].RunDate = run.RunDate; WR[run.Style].Name = run.Name; - //ConnectedMapTimes.Add(run.ID); + /// ConnectedMapTimes.Add(run.ID); MapCompletions[run.Style] = run.TotalCount; SetReplayData(run.Type, run.Style, run.Stage, run.ReplayFrames!); diff --git a/src/ST-Player/Player.cs b/src/ST-Player/Player.cs index 7f24085..cdda246 100644 --- a/src/ST-Player/Player.cs +++ b/src/ST-Player/Player.cs @@ -1,6 +1,7 @@ -namespace SurfTimer; using CounterStrikeSharp.API.Core; +namespace SurfTimer; + public class Player { // CCS requirements diff --git a/src/ST-Player/PlayerHUD.cs b/src/ST-Player/PlayerHUD.cs index 5e23326..4da145a 100644 --- a/src/ST-Player/PlayerHUD.cs +++ b/src/ST-Player/PlayerHUD.cs @@ -1,4 +1,3 @@ -using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Utils; namespace SurfTimer; diff --git a/src/ST-Player/PlayerStats/CurrentRun.cs b/src/ST-Player/PlayerStats/CurrentRun.cs index dc9dfa5..5bf1485 100644 --- a/src/ST-Player/PlayerStats/CurrentRun.cs +++ b/src/ST-Player/PlayerStats/CurrentRun.cs @@ -97,7 +97,7 @@ internal async Task SaveMapTime(Player player, short bonus = 0, short stage = 0, if (mapTimeId <= 0) mapTimeId = await _dataService.InsertMapTimeAsync(mapTime); else - mapTimeId = await _dataService.UpdateMapTimeAsync(mapTime, mapTimeId); + _ = await _dataService.UpdateMapTimeAsync(mapTime, mapTimeId); // Reload the times for the map diff --git a/src/ST-Player/Replay/ReplayPlayer.cs b/src/ST-Player/Replay/ReplayPlayer.cs index d108ce6..5313245 100644 --- a/src/ST-Player/Replay/ReplayPlayer.cs +++ b/src/ST-Player/Replay/ReplayPlayer.cs @@ -231,22 +231,13 @@ internal void LoadReplayData(int repeat_count = -1, [CallerMemberName] string me if (!this.IsPlayable || !this.IsEnabled) return; - string replayType; - switch (this.Type) + string replayType = this.Type switch { - case 1: - replayType = "Bonus Replay"; - break; - case 2: - replayType = "Stage Replay"; - break; - case 0: - replayType = "Map Replay"; - break; - default: - replayType = "Unknown Type"; - break; - } + 1 => "Bonus Replay", + 2 => "Stage Replay", + 0 => "Map Replay", + _ => "Unknown Type", + }; if (this.MapID == -1) { diff --git a/src/ST-Player/Replay/ReplayRecorder.cs b/src/ST-Player/Replay/ReplayRecorder.cs index 68b0f45..180588b 100644 --- a/src/ST-Player/Replay/ReplayRecorder.cs +++ b/src/ST-Player/Replay/ReplayRecorder.cs @@ -80,7 +80,6 @@ internal void Tick(Player player, [CallerMemberName] string methodName = "") return; // Disabling Recording if timer disabled - // if (!player.Timer.IsEnabled) if (!player.Timer.IsEnabled && !player.ReplayRecorder.IsSaving) { this.Stop(); diff --git a/src/ST-UTILS/Config.cs b/src/ST-UTILS/Config.cs index 2325f24..7585a36 100644 --- a/src/ST-UTILS/Config.cs +++ b/src/ST-UTILS/Config.cs @@ -1,25 +1,24 @@ -using CounterStrikeSharp.API; using System.Collections.Immutable; using System.Reflection; using System.Text.Json; +using CounterStrikeSharp.API; namespace SurfTimer; public static class Config { public static readonly string PluginLogo = """ - - ____________ ____ ___ - / ___/ __/_ | / __/_ ______/ _/ - / /___\ \/ __/ _\ \/ // / __/ _/ - \___/___/____/ /___/\_,_/_/ /_/ - """; + + ____________ ____ ___ + / ___/ __/_ | / __/_ ______/ _/ + / /___\ \/ __/ _\ \/ // / __/ _/ + \___/___/____/ /___/\_,_/_/ /_/ + """; public static string PluginName => Assembly.GetExecutingAssembly().GetName().Name ?? ""; public static readonly string PluginPrefix = LocalizationService.LocalizerNonNull["prefix"]; - public static string PluginPath => $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; - public static readonly string PluginSurfConfig = $"{Server.GameDirectory}/csgo/cfg/{PluginName}/{PluginName}.json"; + public static string PluginPath => + $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/plugins/{PluginName}/"; public static string ApiUrl => Api.GetApiUrl(); - public static string DbConnectionString => MySql.GetConnectionString(); /// /// Placeholder for amount of styles @@ -51,12 +50,14 @@ public static JsonDocument GetConfigDocument(string configPath) private static class TimerSettings { private const string TIMER_CONFIG_PATH = "/csgo/cfg/SurfTimer/timer_settings.json"; - private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(TIMER_CONFIG_PATH); + private static JsonDocument ConfigDocument => + ConfigLoader.GetConfigDocument(TIMER_CONFIG_PATH); public static bool GetReplaysEnabled() { return ConfigDocument.RootElement.GetProperty("replays_enabled").GetBoolean(); } + public static int GetReplaysPre() { return ConfigDocument.RootElement.GetProperty("replays_pre").GetInt32(); @@ -66,7 +67,8 @@ public static int GetReplaysPre() public static class Api { private const string API_CONFIG_PATH = "/csgo/cfg/SurfTimer/api_config.json"; - private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(API_CONFIG_PATH); + private static JsonDocument ConfigDocument => + ConfigLoader.GetConfigDocument(API_CONFIG_PATH); /// /// Retrieves the `api_url` string from the configuration path @@ -98,16 +100,20 @@ public static class Endpoints public const string ENDPOINT_MAP_INSERT_INFO = "/api/Map"; public const string ENDPOINT_MAP_UPDATE_INFO = "/api/Map/mapId={0}"; public const string ENDPOINT_MAP_GET_RUNS = "/api/Map/mapId={0}"; - public const string ENDPOINT_MAP_GET_RUN_CPS = "/api/PersonalBest/checkpoints/mapTimeId={0}"; + public const string ENDPOINT_MAP_GET_RUN_CPS = + "/api/PersonalBest/checkpoints/mapTimeId={0}"; // CurrentRun.cs public const string ENDPOINT_CR_SAVE_MAP_TIME = "/api/CurrentRun/saveMapTime"; - public const string ENDPOINT_CR_UPDATE_MAP_TIME = "/api/CurrentRun/updateMapTime/mapTimeId={0}"; + public const string ENDPOINT_CR_UPDATE_MAP_TIME = + "/api/CurrentRun/updateMapTime/mapTimeId={0}"; public const string ENDPOINT_CR_SAVE_STAGE_TIME = "/surftimer/savestagetime"; // PersonalBest.cs - public const string ENDPOINT_MAP_GET_PB_BY_PLAYER = "/api/PersonalBest/playerId={0}&mapId={1}&type={2}&style={3}"; - public const string ENDPOINT_MAP_GET_PB_BY_ID = "/api/PersonalBest/runById/mapTimeId={0}"; + public const string ENDPOINT_MAP_GET_PB_BY_PLAYER = + "/api/PersonalBest/playerId={0}&mapId={1}&type={2}&style={3}"; + public const string ENDPOINT_MAP_GET_PB_BY_ID = + "/api/PersonalBest/runById/mapTimeId={0}"; // PlayerProfile.cs public const string ENDPOINT_PP_GET_PROFILE = "/api/PlayerProfile/steamId={0}"; @@ -115,15 +121,16 @@ public static class Endpoints public const string ENDPOINT_PP_UPDATE_PROFILE = "/api/PlayerProfile/playerId={0}"; // PlayerStats.cs - public const string ENDPOINT_PS_GET_PLAYER_MAP_DATA = "/api/PlayerStats/playerId={0}&mapId={1}"; - + public const string ENDPOINT_PS_GET_PLAYER_MAP_DATA = + "/api/PlayerStats/playerId={0}&mapId={1}"; } } public static class MySql { private const string DB_CONFIG_PATH = "/csgo/cfg/SurfTimer/database.json"; - private static JsonDocument ConfigDocument => ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); + private static JsonDocument ConfigDocument => + ConfigLoader.GetConfigDocument(DB_CONFIG_PATH); /// /// Retrieves the connection details for connecting to the MySQL Database @@ -138,102 +145,10 @@ public static string GetConnectionString() int port = ConfigDocument.RootElement.GetProperty("port").GetInt32()!; int timeout = ConfigDocument.RootElement.GetProperty("timeout").GetInt32()!; - string connString = $"server={host};user={user};password={password};database={database};port={port};connect timeout={timeout};"; + string connString = + $"Server={host};User={user};Password={password};Database={database};Port={port};Connect Timeout={timeout};Allow User Variables=true"; return connString; } - - /// - /// Contains all the queries used by MySQL for the SurfTimer plugin. - /// - public static class Queries - { - public const string DB_QUERY_PING = "SELECT 1;"; - - // Map.cs related queries - public const string DB_QUERY_MAP_GET_INFO = "SELECT * FROM Maps WHERE name='{0}';"; - public const string DB_QUERY_MAP_INSERT_INFO = "INSERT INTO Maps (name, author, tier, stages, bonuses, ranked, date_added, last_played) VALUES ('{0}', '{1}', {2}, {3}, {4}, {5}, {6}, {6})"; // "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()})" - public const string DB_QUERY_MAP_UPDATE_INFO_FULL = "UPDATE Maps SET last_played={0}, stages={1}, bonuses={2}, author='{3}', tier={4}, ranked={5} WHERE id={6};"; - public const string DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT = @" - SELECT - ranked_times.* - FROM ( - SELECT - MapTimes.*, - Player.name, - ROW_NUMBER() OVER ( - PARTITION BY MapTimes.type, MapTimes.stage - ORDER BY MapTimes.run_time ASC - ) AS row_num, - COUNT(*) OVER (PARTITION BY MapTimes.type, MapTimes.stage) AS total_count - FROM MapTimes - JOIN Player ON MapTimes.player_id = Player.id - WHERE MapTimes.map_id = {0} - ) AS ranked_times - WHERE ranked_times.row_num = 1;"; - - - // PlayerStats.cs related queries - public const string DB_QUERY_PS_GET_ALL_RUNTIMES = @" - 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery - WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1}; - "; - - // PersonalBest.cs related queries - public const string DB_QUERY_PB_GET_TYPE_RUNTIME = @" - 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery - WHERE mainquery.`player_id` = {0} AND mainquery.`map_id` = {1} AND mainquery.`type` = {2} AND mainquery.`style` = {3}; - "; - public const string DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA = @" - 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` AND subquery.`type` = mainquery.`type` AND subquery.`stage` = mainquery.`stage`) AS `rank` FROM `MapTimes` AS mainquery - WHERE mainquery.`id` = {0}; - "; - public const string DB_QUERY_PB_GET_CPS = "SELECT * FROM `Checkpoints` WHERE `maptime_id` = {0};"; - - // CurrentRun.cs related queries - public const string DB_QUERY_CR_INSERT_TIME = @" - 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`, `replay_frames`) - VALUES ({0}, {1}, {2}, {3}, {4}, {5}, - {6}, {7}, {8}, {9}, {10}, {11}, {12}, '{13}') - 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), replay_frames=VALUES(replay_frames); - "; - public const string DB_QUERY_CR_INSERT_CP = @" - 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 ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}) - 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); - "; - - // ReplayPlayer.cs related queries - public const string DB_QUERY_RP_LOAD_REPLAY = @" - SELECT MapTimes.replay_frames, MapTimes.run_time, Player.name - FROM MapTimes - JOIN Player ON MapTimes.player_id = Player.id - WHERE MapTimes.id={0}; - "; - - // Players.cs related queries - public const string DB_QUERY_PP_GET_PROFILE = "SELECT * FROM `Player` WHERE `steam_id` = {0} LIMIT 1;"; - public const string DB_QUERY_PP_INSERT_PROFILE = @" - INSERT INTO `Player` (`name`, `steam_id`, `country`, `join_date`, `last_seen`, `connections`) - VALUES ('{0}', {1}, '{2}', {3}, {4}, {5}); - "; - public const string DB_QUERY_PP_UPDATE_PROFILE = @" - UPDATE `Player` SET country = '{0}', - `last_seen` = {1}, `connections` = `connections` + 1, `name` = '{3}' - WHERE `id` = {2} LIMIT 1; - "; - } } -} \ No newline at end of file +} diff --git a/src/ST-UTILS/Data/ApiDataAccessService.cs b/src/ST-UTILS/Data/ApiDataAccessService.cs index 53fc9ad..e36f8f4 100644 --- a/src/ST-UTILS/Data/ApiDataAccessService.cs +++ b/src/ST-UTILS/Data/ApiDataAccessService.cs @@ -1,8 +1,8 @@ +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Shared.DTO; using SurfTimer.Shared.Entities; -using System.Runtime.CompilerServices; namespace SurfTimer.Data { @@ -23,82 +23,118 @@ public async Task PingAccessService([CallerMemberName] string methodName = try { var response = await ApiMethod.GET>( - string.Format(Config.Api.Endpoints.ENDPOINT_PING, (double)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + string.Format( + Config.Api.Endpoints.ENDPOINT_PING, + (double)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ) ); if (response != null && response.ContainsKey("clientUnix")) { - _logger.LogInformation("[{ClassName}] {MethodName} -> Success -> Client: {ClientUnix} | Server: {ServerUnix} | Latency: {LatencyS}s | Latency: {LatencyMS}ms", - nameof(ApiDataAccessService), methodName, response["clientUnix"], response["serverUnix"], response["latencySeconds"], response["latencyMs"] + _logger.LogInformation( + "[{ClassName}] {MethodName} -> Success -> Client: {ClientUnix} | Server: {ServerUnix} | Latency: {LatencyS}s | Latency: {LatencyMS}ms", + nameof(ApiDataAccessService), + methodName, + response["clientUnix"], + response["serverUnix"], + response["latencySeconds"], + response["latencyMs"] ); return true; } - _logger.LogWarning("[{ClassName}] {MethodName} -> Unexpected response structure.", - nameof(ApiDataAccessService), methodName + _logger.LogWarning( + "[{ClassName}] {MethodName} -> Unexpected response structure.", + nameof(ApiDataAccessService), + methodName ); return false; } catch (Exception ex) { - _logger.LogError(ex, "[{ClassName}] {MethodName} -> Failed to reach API.", - nameof(ApiDataAccessService), methodName + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> Failed to reach API.", + nameof(ApiDataAccessService), + methodName ); return false; } } /* PersonalBest.cs */ - public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + public async Task> LoadCheckpointsAsync( + int runId, + [CallerMemberName] string methodName = "" + ) { - var checkpoints = await ApiMethod - .GET>( - string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) - ); + var checkpoints = await ApiMethod.GET>( + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUN_CPS, runId) + ); if (checkpoints == null || checkpoints.Count == 0) return new Dictionary(); - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints for MapTimeId {MapTimeId}.", - nameof(ApiDataAccessService), methodName, checkpoints.Count, runId + _logger.LogInformation( + "[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints for MapTimeId {MapTimeId}.", + nameof(ApiDataAccessService), + methodName, + checkpoints.Count, + runId ); return checkpoints; } - public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") + public async Task LoadPersonalBestRunAsync( + int? pbId, + int playerId, + int mapId, + int type, + int style, + [CallerMemberName] string methodName = "" + ) { - string url = pbId == null || pbId == -1 - ? string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, - playerId, mapId, type, style) - : string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, - pbId.Value); + string url = + pbId == null || pbId == -1 + ? string.Format( + Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_PLAYER, + playerId, + mapId, + type, + style + ) + : string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_PB_BY_ID, pbId.Value); var apiResult = await ApiMethod.GET(url); if (apiResult == null) return null; - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Personal Best data", - nameof(ApiDataAccessService), methodName + _logger.LogInformation( + "[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Personal Best data", + nameof(ApiDataAccessService), + methodName ); return apiResult; } - /* Map.cs */ - public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + public async Task GetMapInfoAsync( + string mapName, + [CallerMemberName] string methodName = "" + ) { var mapInfo = await ApiMethod.GET( - string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_INFO, mapName)); + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_INFO, mapName) + ); if (mapInfo != null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data", - nameof(ApiDataAccessService), methodName - ); - - _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> MapID {MapId}", - nameof(ApiDataAccessService), methodName, mapInfo.ID + _logger.LogInformation( + "[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data. MapID {MapId}", + nameof(ApiDataAccessService), + methodName, + mapInfo.ID ); return mapInfo; @@ -107,90 +143,130 @@ public async Task> LoadCheckpointsAsync(int ru return null; } - public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") + public async Task InsertMapInfoAsync( + MapDto mapInfo, + [CallerMemberName] string methodName = "" + ) { - var postResponse = await ApiMethod.POST(Config.Api.Endpoints.ENDPOINT_MAP_INSERT_INFO, mapInfo); + var postResponse = await ApiMethod.POST( + Config.Api.Endpoints.ENDPOINT_MAP_INSERT_INFO, + mapInfo + ); if (postResponse == null || postResponse.Id <= 0) { - Exception ex = new Exception($"API failed to insert map '{mapInfo.Name}'."); + Exception ex = new($"API failed to insert map '{mapInfo.Name}'."); throw ex; } return postResponse.Id; } - public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") + public async Task UpdateMapInfoAsync( + MapDto mapInfo, + int mapId, + [CallerMemberName] string methodName = "" + ) { var response = await ApiMethod.PUT( - string.Format(Config.Api.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapId), mapInfo + string.Format(Config.Api.Endpoints.ENDPOINT_MAP_UPDATE_INFO, mapId), + mapInfo ); if (response == null) { - Exception ex = new Exception($"API failed to update map '{mapInfo.Name}' (ID {mapId})."); + Exception ex = new($"API failed to update map '{mapInfo.Name}' (ID {mapId})."); throw ex; } } - public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + public async Task> GetMapRecordRunsAsync( + int mapId, + [CallerMemberName] string methodName = "" + ) { var apiRuns = await ApiMethod.GET>( - string.Format(string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId))); + string.Format(string.Format(Config.Api.Endpoints.ENDPOINT_MAP_GET_RUNS, mapId)) + ); return apiRuns!; } - /* PlayerProfile.cs */ - public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + public async Task GetPlayerProfileAsync( + ulong steamId, + [CallerMemberName] string methodName = "" + ) { var player = await ApiMethod.GET( - string.Format(Config.Api.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId)); + string.Format(Config.Api.Endpoints.ENDPOINT_PP_GET_PROFILE, steamId) + ); if (player != null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data for ProfileID = {ProfileID}", - nameof(ApiDataAccessService), methodName, player.ID + _logger.LogInformation( + "[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> Found PlayerProfile data for ProfileID = {ProfileID}", + nameof(ApiDataAccessService), + methodName, + player.ID ); return player; } - _logger.LogWarning("[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> No PlayerProfile data found for {SteamID}", - nameof(ApiDataAccessService), methodName, steamId + _logger.LogWarning( + "[{ClassName}] {MethodName} -> GetPlayerProfileAsync -> No PlayerProfile data found for {SteamID}", + nameof(ApiDataAccessService), + methodName, + steamId ); return null; } - public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [CallerMemberName] string methodName = "") + public async Task InsertPlayerProfileAsync( + PlayerProfileDto profile, + [CallerMemberName] string methodName = "" + ) { - var postResponse = await ApiMethod.POST(Config.Api.Endpoints.ENDPOINT_PP_INSERT_PROFILE, profile); + var postResponse = await ApiMethod.POST( + Config.Api.Endpoints.ENDPOINT_PP_INSERT_PROFILE, + profile + ); if (postResponse == null || postResponse.Id <= 0) { - Exception ex = new Exception($"API failed to insert Player Profile for '{profile.Name}'."); + Exception ex = new($"API failed to insert Player Profile for '{profile.Name}'."); throw ex; } return postResponse.Id; } - public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") + public async Task UpdatePlayerProfileAsync( + PlayerProfileDto profile, + int playerId, + [CallerMemberName] string methodName = "" + ) { var response = await ApiMethod.PUT( - string.Format(Config.Api.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, playerId), profile + string.Format(Config.Api.Endpoints.ENDPOINT_PP_UPDATE_PROFILE, playerId), + profile ); if (response == null) { - Exception ex = new Exception($"API failed to update Player Profile for '{profile.Name}' (ID {playerId})."); + Exception ex = new( + $"API failed to update Player Profile for '{profile.Name}' (ID {playerId})." + ); throw ex; } } - /* PlayerStats.cs */ - public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + public async Task> GetPlayerMapTimesAsync( + int playerId, + int mapId, + [CallerMemberName] string methodName = "" + ) { var apiResponse = await ApiMethod.GET>( string.Format(Config.Api.Endpoints.ENDPOINT_PS_GET_PLAYER_MAP_DATA, playerId, mapId) @@ -198,64 +274,29 @@ public async Task> GetPlayerMapTimesAsync(int playerI if (apiResponse == null) { - Exception ex = new Exception($"API failed to GET MapTime entries for PlayerID '{playerId}' and MapID '{mapId}'."); + Exception ex = new( + $"API failed to GET MapTime entries for PlayerID '{playerId}' and MapID '{mapId}'." + ); throw ex; } - _logger.LogInformation("[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", - nameof(ApiDataAccessService), methodName, playerId, mapId + _logger.LogInformation( + "[{ClassName}] {MethodName} -> GetPlayerMapTimesAsync -> Found maptime data for PlayerID {PlayerID} and MapID {MapID}", + nameof(ApiDataAccessService), + methodName, + playerId, + mapId ); return apiResponse; } - /* CurrentRun.cs */ - public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "") + public async Task InsertMapTimeAsync( + MapTimeRunDataDto mapTime, + [CallerMemberName] string methodName = "" + ) { - /* - _logger.LogDebug( - "[{ClassName}] {MethodName} -> Sending MapTimeDataModel:\n" + - " PlayerId: {PlayerId}\n" + - " MapId: {MapId}\n" + - " RunTime: {RunTime}\n" + - " Style: {Style}\n" + - " Type: {Type}\n" + - " Stage: {Stage}\n" + - " Start Velocities: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + - " End Velocities: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + - " ReplayFramesLength: {ReplayFramesLength}\n" + - " ReplayFramesBase64Length: {ReplayFramesLength}\n" + - " CheckpointsCount: {CheckpointsCount}\n", - nameof(ApiDataAccessService), methodName, - mapTime.PlayerId, - mapTime.MapId, - mapTime.RunTime, - mapTime.Style, - mapTime.Type, - mapTime.Stage, - mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, - mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, - mapTime.ReplayFrames?.Length ?? 0, - mapTime.ReplayFrames?.Length ?? 0, - mapTime.Checkpoints?.Count ?? 0 - ); - - if (mapTime.Type == 0) - { - _logger.LogDebug( - "[{ClassName}] {MethodName} -> Sending MapTimeDataModel Checkpoint 1:\n" + - " Checkpoints?[1].CP: {CP}\n" + - " Checkpoints?[1].RunTime: {RunTime}\n" + - " Checkpoints?[1].Attempts: {Attempts}\n", - nameof(ApiDataAccessService), methodName, - mapTime.Checkpoints?[1].CP, - mapTime.Checkpoints?[1].RunTime, - mapTime.Checkpoints?[1].Attempts - ); - } - */ - var postResponse = await ApiMethod.POST( Config.Api.Endpoints.ENDPOINT_CR_SAVE_MAP_TIME, mapTime @@ -263,63 +304,29 @@ public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemb if (postResponse == null || postResponse.Inserted <= 0) { - Exception ex = new Exception($"API failed to insert MapTime for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); + Exception ex = new( + $"API failed to insert MapTime for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'." + ); throw ex; } _logger.LogDebug( "[{ClassName}] {MethodName} -> Successfully inserted entry with id {ID} with type {Type}", - nameof(ApiDataAccessService), methodName, postResponse.Id, mapTime.Type + nameof(ApiDataAccessService), + methodName, + postResponse.Id, + mapTime.Type ); return postResponse.Id; } - public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "") + public async Task UpdateMapTimeAsync( + MapTimeRunDataDto mapTime, + int mapTimeId, + [CallerMemberName] string methodName = "" + ) { - /* - _logger.LogDebug( - "[{ClassName}] {MethodName} -> Sending MapTimeDataModel:\n" + - " PlayerId: {PlayerId}\n" + - " MapId: {MapId}\n" + - " RunTime: {RunTime}\n" + - " Style: {Style}\n" + - " Type: {Type}\n" + - " Stage: {Stage}\n" + - " Start Velocities: ({StartVelX}, {StartVelY}, {StartVelZ})\n" + - " End Velocities: ({EndVelX}, {EndVelY}, {EndVelZ})\n" + - " ReplayFramesLength: {ReplayFramesLength}\n" + - " ReplayFramesBase64Length: {ReplayFramesLength}\n" + - " CheckpointsCount: {CheckpointsCount}\n", - nameof(ApiDataAccessService), methodName, - mapTime.PlayerId, - mapTime.MapId, - mapTime.RunTime, - mapTime.Style, - mapTime.Type, - mapTime.Stage, - mapTime.StartVelX, mapTime.StartVelY, mapTime.StartVelZ, - mapTime.EndVelX, mapTime.EndVelY, mapTime.EndVelZ, - mapTime.ReplayFrames?.Length ?? 0, - mapTime.ReplayFrames?.Length ?? 0, - mapTime.Checkpoints?.Count ?? 0 - ); - - if (mapTime.Type == 0) - { - _logger.LogDebug( - "[{ClassName}] {MethodName} -> Sending MapTimeDataModel Checkpoint 1:\n" + - " Checkpoints?[1].CP: {CP}\n" + - " Checkpoints?[1].RunTime: {RunTime}\n" + - " Checkpoints?[1].Attempts: {Attempts}\n", - nameof(ApiDataAccessService), methodName, - mapTime.Checkpoints?[1].CP, - mapTime.Checkpoints?[1].RunTime, - mapTime.Checkpoints?[1].Attempts - ); - } - */ - var postResponse = await ApiMethod.PUT( string.Format(Config.Api.Endpoints.ENDPOINT_CR_UPDATE_MAP_TIME, mapTimeId), mapTime @@ -327,13 +334,18 @@ public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTime if (postResponse == null || postResponse.Inserted <= 0) { - Exception ex = new Exception($"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'."); + Exception ex = new( + $"API failed to update MapTime {mapTimeId} for Player ID '{mapTime.PlayerID}' on Map ID '{mapTime.MapID}'." + ); throw ex; } _logger.LogDebug( "[{ClassName}] {MethodName} -> Successfully updated MapTime entry {ID} with type {Type}", - nameof(ApiDataAccessService), methodName, mapTimeId, mapTime.Type + nameof(ApiDataAccessService), + methodName, + mapTimeId, + mapTime.Type ); return postResponse.Id; diff --git a/src/ST-UTILS/Data/IDataAccessService.cs b/src/ST-UTILS/Data/IDataAccessService.cs index 35d1681..9bbe2b4 100644 --- a/src/ST-UTILS/Data/IDataAccessService.cs +++ b/src/ST-UTILS/Data/IDataAccessService.cs @@ -1,6 +1,6 @@ +using System.Runtime.CompilerServices; using SurfTimer.Shared.DTO; using SurfTimer.Shared.Entities; -using System.Runtime.CompilerServices; namespace SurfTimer.Data { @@ -47,41 +47,41 @@ Task> LoadCheckpointsAsync( [CallerMemberName] string methodName = "" ); - /* Map.cs */ /// /// Retrieves Map table entry for map through API or MySQL. /// /// Name of map /// MapEntity data - Task GetMapInfoAsync( - string mapName, [CallerMemberName] string methodName = "" - ); + Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = ""); + /// /// Adds Map table entry for map through API or MySQL. /// /// Data to add in table /// int mapId - Task InsertMapInfoAsync( - MapDto mapInfo, [CallerMemberName] string methodName = "" - ); + Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = ""); + /// /// Updates Map table entry for map through API or MySQL. /// /// Data to update in table Task UpdateMapInfoAsync( - MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "" + MapDto mapInfo, + int mapId, + [CallerMemberName] string methodName = "" ); + /// /// Retrieves MapTime table record runs for given mapId through API or MySQL. /// /// ID from DB /// List[MapTimeRunDataEntity] data Task> GetMapRecordRunsAsync( - int mapId, [CallerMemberName] string methodName = "" + int mapId, + [CallerMemberName] string methodName = "" ); - /* PlayerProfile.cs */ /// /// Retrieve Player table entry for the player through API or MySQL. @@ -89,25 +89,30 @@ Task> GetMapRecordRunsAsync( /// SteamID for the player /// PlayerProfileEntity data Task GetPlayerProfileAsync( - ulong steamId, [CallerMemberName] string methodName = "" + ulong steamId, + [CallerMemberName] string methodName = "" ); + /// /// Adds Player table entry for the player through API or MySQL. /// /// Data to add in table /// int playerId given by DB Task InsertPlayerProfileAsync( - PlayerProfileDto profile, [CallerMemberName] string methodName = "" + PlayerProfileDto profile, + [CallerMemberName] string methodName = "" ); + /// /// Updates Player table entry for the player through API or MySQL. /// /// Data to update in table Task UpdatePlayerProfileAsync( - PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "" + PlayerProfileDto profile, + int playerId, + [CallerMemberName] string methodName = "" ); - /* PlayerStats.cs */ /// /// Retrieves ALL MapTime table entries for playerId and mapId combo through API or MySQL. @@ -116,10 +121,11 @@ Task UpdatePlayerProfileAsync( /// ID from DB /// List[MapTimeRunDataEntity] data Task> GetPlayerMapTimesAsync( - int playerId, int mapId, [CallerMemberName] string methodName = "" + int playerId, + int mapId, + [CallerMemberName] string methodName = "" ); - /* CurrentRun.cs */ /// /// Adds a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 @@ -127,15 +133,19 @@ Task> GetPlayerMapTimesAsync( /// Data to insert/update in table /// int mapTimeId given by DB Task InsertMapTimeAsync( - MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "" + MapTimeRunDataDto mapTime, + [CallerMemberName] string methodName = "" ); + /// /// Updates a MapTime table entry through API or MySQL. Deals with checkpoints for map runs of type 0 /// /// Data to update in table /// int mapTimeId that was updated Task UpdateMapTimeAsync( - MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "" + MapTimeRunDataDto mapTime, + int mapTimeId, + [CallerMemberName] string methodName = "" ); } } diff --git a/src/ST-UTILS/Data/MySqlDataAccessService.cs b/src/ST-UTILS/Data/MySqlDataAccessService.cs index 3de0cf6..fc6b28c 100644 --- a/src/ST-UTILS/Data/MySqlDataAccessService.cs +++ b/src/ST-UTILS/Data/MySqlDataAccessService.cs @@ -1,9 +1,10 @@ +using System.Runtime.CompilerServices; +using Dapper; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using MySqlConnector; using SurfTimer.Shared.DTO; using SurfTimer.Shared.Entities; -using System.Runtime.CompilerServices; +using SurfTimer.Shared.Sql; namespace SurfTimer.Data { @@ -16,308 +17,512 @@ public class MySqlDataAccessService : IDataAccessService /// public MySqlDataAccessService() { - _logger = SurfTimer.ServiceProvider.GetRequiredService>(); + _logger = SurfTimer.ServiceProvider.GetRequiredService< + ILogger + >(); } public async Task PingAccessService([CallerMemberName] string methodName = "") { try { - using var result = await SurfTimer.DB.QueryAsync(Config.MySql.Queries.DB_QUERY_PING); + var val = await SurfTimer.DB.QueryFirstOrDefaultAsync(Queries.DB_QUERY_PING); - if (result.HasRows) + var reachable = val != 0; + + if (reachable) { - _logger.LogInformation("[{ClassName}] {MethodName} -> PingAccessService -> MySQL is reachable", - nameof(MySqlDataAccessService), methodName + _logger.LogInformation( + "[{ClassName}] {MethodName} -> PingAccessService -> MySQL is reachable", + nameof(MySqlDataAccessService), + methodName ); - return true; } + + return reachable; } catch (Exception ex) { - _logger.LogCritical(ex, "[{ClassName}] {MethodName} -> PingAccessService -> MySQL is unreachable", - nameof(MySqlDataAccessService), methodName + _logger.LogCritical( + ex, + "[{ClassName}] {MethodName} -> PingAccessService -> MySQL is unreachable", + nameof(MySqlDataAccessService), + methodName ); + return false; } - - return false; } /* PersonalBest.cs */ - public async Task> LoadCheckpointsAsync(int runId, [CallerMemberName] string methodName = "") + public async Task> LoadCheckpointsAsync( + int runId, + [CallerMemberName] string methodName = "" + ) { - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Using MySQL data access service.", - nameof(MySqlDataAccessService), methodName + // Dapper handles mapping. + var rows = await SurfTimer.DB.QueryAsync( + Queries.DB_QUERY_PB_GET_CPS, + new { MapTimeID = runId } ); - var dict = new Dictionary(); - - using (var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_CPS, runId))) - { - if (results == null || !results.HasRows) - return dict; + // Key the dictionary by CP. + var dict = rows.ToDictionary(cp => (int)cp.CP); - while (await results.ReadAsync()) - { - var cp = new CheckpointEntity( - results.GetInt16("cp"), - results.GetInt32("run_time"), - 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("end_touch"), - results.GetInt32("attempts") - ); - cp.MapTimeID = results.GetInt32("maptime_id"); - dict[cp.CP] = cp; - } - } - - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints.", - nameof(MySqlDataAccessService), methodName, dict.Count + _logger.LogInformation( + "[{ClassName}] {MethodName} -> LoadCheckpointsAsync -> Found {Count} checkpoints.", + nameof(MySqlDataAccessService), + methodName, + dict.Count ); return dict; } - public async Task LoadPersonalBestRunAsync(int? pbId, int playerId, int mapId, int type, int style, [CallerMemberName] string methodName = "") + public async Task LoadPersonalBestRunAsync( + int? pbId, + int playerId, + int mapId, + int type, + int style, + [CallerMemberName] string methodName = "" + ) { - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Using MySQL data access service.", - nameof(MySqlDataAccessService), methodName - ); + // Choose SQL and parameters based on whether a specific PB id is provided. + string sql; + object args; - string sql = pbId == null || pbId == -1 - ? string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_TYPE_RUNTIME, - playerId, mapId, type, style) - : string.Format(Config.MySql.Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA, - pbId.Value); + if (!pbId.HasValue || pbId == -1) + { + sql = Queries.DB_QUERY_PB_GET_TYPE_RUNTIME; + args = new + { + PlayerId = playerId, + MapId = mapId, + Type = type, + Style = style, + }; + } + else + { + sql = Queries.DB_QUERY_PB_GET_SPECIFIC_MAPTIME_DATA; + args = new { MapTimeId = pbId.Value }; + } + + // Fetch a single row (or null). + var run = await SurfTimer.DB.QueryFirstOrDefaultAsync(sql, args); - using var results = await SurfTimer.DB.QueryAsync(sql); - if (results == null || !results.HasRows) + if (run is null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> No data found. PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", - nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style + _logger.LogInformation( + "[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> No data found. PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", + nameof(MySqlDataAccessService), + methodName, + pbId, + playerId, + mapId, + type, + style ); return null; } - // read the first (and only) row - await results.ReadAsync(); - - _logger.LogInformation("[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Found data for PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", - nameof(MySqlDataAccessService), methodName, pbId, playerId, mapId, type, style + _logger.LogInformation( + "[{ClassName}] {MethodName} -> LoadPersonalBestRunAsync -> Found data for PersonalBestID {PbID} | PlayerID {PlayerID} | MapID {MapID} | Type {Type} | Style {Style}", + nameof(MySqlDataAccessService), + methodName, + pbId, + playerId, + mapId, + type, + style ); - return new MapTimeRunDataEntity(results); + return run; } - /* Map.cs */ - public async Task GetMapInfoAsync(string mapName, [CallerMemberName] string methodName = "") + public async Task GetMapInfoAsync( + string mapName, + [CallerMemberName] string methodName = "" + ) { - using var mapData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySql.Queries.DB_QUERY_MAP_GET_INFO, MySqlHelper.EscapeString(mapName))); + var mapInfo = await SurfTimer.DB.QueryFirstOrDefaultAsync( + Queries.DB_QUERY_MAP_GET_INFO, + new { mapName } + ); - if (mapData.HasRows && await mapData.ReadAsync()) + if (mapInfo is not null) { - _logger.LogInformation("[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data", - nameof(MySqlDataAccessService), methodName + _logger.LogInformation( + "[{ClassName}] {MethodName} -> GetMapInfoAsync -> Found MapInfo data (ID: {ID})", + nameof(MySqlDataAccessService), + methodName, + mapInfo.ID ); - - return new MapEntity(mapData); } - return null; + return mapInfo; } - public async Task InsertMapInfoAsync(MapDto mapInfo, [CallerMemberName] string methodName = "") + public async Task InsertMapInfoAsync( + MapDto mapInfo, + [CallerMemberName] string methodName = "" + ) { - var (rowsWritten, lastId) = await SurfTimer.DB.WriteAsync( - string.Format(Config.MySql.Queries.DB_QUERY_MAP_INSERT_INFO, - MySqlHelper.EscapeString(mapInfo.Name), - MySqlHelper.EscapeString(mapInfo.Author), - mapInfo.Tier, - mapInfo.Stages, - mapInfo.Bonuses, - mapInfo.Ranked ? 1 : 0, - (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + var newId = await SurfTimer.DB.InsertAsync( + Queries.DB_QUERY_MAP_INSERT_INFO, + new + { + Name = mapInfo.Name, + Author = mapInfo.Author, + Tier = mapInfo.Tier, + Stages = mapInfo.Stages, + Bonuses = mapInfo.Bonuses, + Ranked = mapInfo.Ranked ? 1 : 0, + DateAdded = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + LastPlayed = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + } ); - if (rowsWritten != 1) + if (newId <= 0) { - Exception ex = new Exception($"Failed to insert new map '{mapInfo.Name}' into database. Rows written: {rowsWritten}"); + Exception ex = new( + $"Failed to insert new map '{mapInfo.Name}' into database. LAST_INSERT_ID() was 0." + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> InsertMapInfoAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + throw ex; } - return (int)lastId; + return (int)newId; } - public async Task UpdateMapInfoAsync(MapDto mapInfo, int mapId, [CallerMemberName] string methodName = "") + public async Task UpdateMapInfoAsync( + MapDto mapInfo, + int mapId, + [CallerMemberName] string methodName = "" + ) { - string updateQuery = string.Format( - Config.MySql.Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, - (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - mapInfo.Stages, - mapInfo.Bonuses, - mapInfo.Author, - mapInfo.Tier, - mapInfo.Ranked ? 1 : 0, // Table is using TINYINT so we need to send 0 or 1 instead of Boolean - mapId + var rowsUpdated = await SurfTimer.DB.ExecuteAsync( + Queries.DB_QUERY_MAP_UPDATE_INFO_FULL, + new + { + LastPlayed = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + Stages = mapInfo.Stages, + Bonuses = mapInfo.Bonuses, + Author = mapInfo.Author, + Tier = mapInfo.Tier, + Ranked = mapInfo.Ranked ? 1 : 0, // TINYINT(1) + Id = mapId, + } ); - var (rowsUpdated, _) = await SurfTimer.DB.WriteAsync(updateQuery); if (rowsUpdated != 1) { - Exception ex = new Exception($"Failed to update map '{mapInfo.Name}' (ID {mapId}) in database."); + Exception ex = new( + $"Failed to update map '{mapInfo.Name}' (ID {mapId}) in database. Rows updated: {rowsUpdated}" + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> UpdateMapInfoAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + throw ex; } } - public async Task> GetMapRecordRunsAsync(int mapId, [CallerMemberName] string methodName = "") + public async Task> GetMapRecordRunsAsync( + int mapId, + [CallerMemberName] string methodName = "" + ) { - var runs = new List(); - - using var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySql.Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, mapId)); - - if (results.HasRows) - { - while (await results.ReadAsync()) - { - runs.Add(new MapTimeRunDataEntity(results)); - } - } + var runs = await SurfTimer.DB.QueryAsync( + Queries.DB_QUERY_MAP_GET_RECORD_RUNS_AND_COUNT, + new { Id = mapId } + ); - return runs; + return runs.ToList(); } - /* PlayerProfile.cs */ - public async Task GetPlayerProfileAsync(ulong steamId, [CallerMemberName] string methodName = "") + public async Task GetPlayerProfileAsync( + ulong steamId, + [CallerMemberName] string methodName = "" + ) { - using var playerData = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySql.Queries.DB_QUERY_PP_GET_PROFILE, steamId)); - - if (playerData.HasRows && await playerData.ReadAsync()) - { - return new PlayerProfileEntity(playerData); - } + var playerData = await SurfTimer.DB.QueryFirstOrDefaultAsync( + Queries.DB_QUERY_PP_GET_PROFILE, + new { SteamID = steamId } + ); - return null; + return playerData; } - public async Task InsertPlayerProfileAsync(PlayerProfileDto profile, [CallerMemberName] string methodName = "") + public async Task InsertPlayerProfileAsync( + PlayerProfileDto profile, + [CallerMemberName] string methodName = "" + ) { int joinDate = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync(string.Format( - Config.MySql.Queries.DB_QUERY_PP_INSERT_PROFILE, - MySqlConnector.MySqlHelper.EscapeString(profile.Name), - profile.SteamID, - profile.Country, - joinDate, - joinDate, - 1)); - - if (rowsInserted != 1) + + var lastId = await SurfTimer.DB.InsertAsync( + Queries.DB_QUERY_PP_INSERT_PROFILE, + new + { + Name = profile.Name, + SteamID = profile.SteamID, + Country = profile.Country, + JoinDate = joinDate, + LastSeen = joinDate, + Connections = 1, + } + ); + + if (lastId <= 0) { - Exception ex = new Exception($"Failed to insert new player '{profile.Name}' ({profile.SteamID})."); + Exception ex = new( + $"Failed to insert new player '{profile.Name}' ({profile.SteamID}). LAST_INSERT_ID() was 0." + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> InsertPlayerProfileAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + throw ex; } return (int)lastId; } - public async Task UpdatePlayerProfileAsync(PlayerProfileDto profile, int playerId, [CallerMemberName] string methodName = "") + public async Task UpdatePlayerProfileAsync( + PlayerProfileDto profile, + int playerId, + [CallerMemberName] string methodName = "" + ) { int lastSeen = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var (rowsUpdated, _) = await SurfTimer.DB.WriteAsync(string.Format( - Config.MySql.Queries.DB_QUERY_PP_UPDATE_PROFILE, - profile.Country, - lastSeen, - playerId, - MySqlConnector.MySqlHelper.EscapeString(profile.Name))); - if (rowsUpdated != 1) + var rowsAffected = await SurfTimer.DB.ExecuteAsync( + Queries.DB_QUERY_PP_UPDATE_PROFILE, + new + { + Country = profile.Country, + LastSeen = lastSeen, + Name = profile.Name, + Id = playerId, + } + ); + + if (rowsAffected != 1) { - Exception ex = new Exception($"Failed to update player '{profile.Name}' ({profile.SteamID})."); + Exception ex = new( + $"Failed to update player '{profile.Name}' ({profile.SteamID})." + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> UpdatePlayerProfileAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + throw ex; } } - /* PlayerStats.cs */ - public async Task> GetPlayerMapTimesAsync(int playerId, int mapId, [CallerMemberName] string methodName = "") + public async Task> GetPlayerMapTimesAsync( + int playerId, + int mapId, + [CallerMemberName] string methodName = "" + ) { - var mapTimes = new List(); + var mapTimes = await SurfTimer.DB.QueryAsync( + Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, + new { PlayerId = playerId, MapId = mapId } + ); - using var results = await SurfTimer.DB.QueryAsync( - string.Format(Config.MySql.Queries.DB_QUERY_PS_GET_ALL_RUNTIMES, playerId, mapId)); + // Convert IEnumerable to List + return mapTimes.ToList(); + } - if (results.HasRows) - { - while (await results.ReadAsync()) + /* CurrentRun.cs */ + public async Task InsertMapTimeAsync( + MapTimeRunDataDto mapTime, + [CallerMemberName] string methodName = "" + ) + { + // 1) Insert the run and get LAST_INSERT_ID() + var mapTimeId = await SurfTimer.DB.InsertAsync( + Queries.DB_QUERY_CR_INSERT_TIME, + new { - mapTimes.Add(new MapTimeRunDataEntity(results)); + PlayerId = mapTime.PlayerID, + MapId = mapTime.MapID, + Style = mapTime.Style, + Type = mapTime.Type, + Stage = mapTime.Stage, + RunTime = mapTime.RunTime, + StartVelX = mapTime.StartVelX, + StartVelY = mapTime.StartVelY, + StartVelZ = mapTime.StartVelZ, + EndVelX = mapTime.EndVelX, + EndVelY = mapTime.EndVelY, + EndVelZ = mapTime.EndVelZ, + RunDate = mapTime.RunDate, + ReplayFrames = mapTime.ReplayFrames, // assuming this matches your type handler } + ); + + if (mapTimeId <= 0) + { + Exception ex = new( + $"Failed to insert map time for PlayerId {mapTime.PlayerID}. LAST_INSERT_ID() was 0." + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> InsertMapTimeAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + + throw ex; + } + // 2) Insert checkpoints in a single transaction (only for Type == 0) + if (mapTime.Type == 0 && mapTime.Checkpoints is { Count: > 0 }) + { + await SurfTimer.DB.TransactionAsync( + async (conn, tx) => + { + // Insert each checkpoint using the same transaction + foreach (var cp in mapTime.Checkpoints.Values) + { + await conn.ExecuteAsync( + Queries.DB_QUERY_CR_INSERT_CP, + new + { + MapTimeId = mapTimeId, + CP = cp.CP, + RunTime = cp.RunTime, + StartVelX = cp.StartVelX, + StartVelY = cp.StartVelY, + StartVelZ = cp.StartVelZ, + EndVelX = cp.EndVelX, + EndVelY = cp.EndVelY, + EndVelZ = cp.EndVelZ, + Attempts = cp.Attempts, + EndTouch = cp.EndTouch, + }, + tx + ); + } + } + ); } - return mapTimes; + return (int)mapTimeId; } - - /* CurrentRun.cs */ - public async Task InsertMapTimeAsync(MapTimeRunDataDto mapTime, [CallerMemberName] string methodName = "") + public async Task UpdateMapTimeAsync( + MapTimeRunDataDto mapTime, + int mapTimeId, + [CallerMemberName] string methodName = "" + ) { - var (rowsInserted, lastId) = await SurfTimer.DB.WriteAsync( - string.Format( - Config.MySql.Queries.DB_QUERY_CR_INSERT_TIME, - mapTime.PlayerID, - mapTime.MapID, - mapTime.Style, - mapTime.Type, - mapTime.Stage, - mapTime.RunTime, - mapTime.StartVelX, - mapTime.StartVelY, - mapTime.StartVelZ, - mapTime.EndVelX, - mapTime.EndVelY, - mapTime.EndVelZ, - mapTime.RunDate, - mapTime.ReplayFrames) + // 1) Update the run using it's ID + var affectedRows = await SurfTimer.DB.ExecuteAsync( + Queries.DB_QUERY_CR_UPDATE_TIME, + new + { + RunTime = mapTime.RunTime, + StartVelX = mapTime.StartVelX, + StartVelY = mapTime.StartVelY, + StartVelZ = mapTime.StartVelZ, + EndVelX = mapTime.EndVelX, + EndVelY = mapTime.EndVelY, + EndVelZ = mapTime.EndVelZ, + RunDate = mapTime.RunDate, + ReplayFrames = mapTime.ReplayFrames, // assuming this matches your type handler + MapTimeId = mapTimeId, + } ); - if (rowsInserted <= 0) + if (affectedRows <= 0) { - Exception ex = new Exception($"Failed to insert map time for PlayerId {mapTime.PlayerID}."); + Exception ex = new( + $"Failed to update map time for MapTimeId {mapTimeId}. affectedRows was {affectedRows}." + ); + + _logger.LogError( + ex, + "[{ClassName}] {MethodName} -> UpdateMapTimeAsync -> {ErrorMessage}", + nameof(MySqlDataAccessService), + methodName, + ex.Message + ); + throw ex; } - // Write the checkpoints after we have the `lastId` - if (mapTime.Checkpoints != null && mapTime.Checkpoints.Count > 0 && mapTime.Type == 0) + _logger.LogInformation( + "[{ClassName}] {MethodName} -> UpdateMapTimeAsync -> Updated MapTimeId {MapTimeId} with {AffectedRows} affected rows.", + nameof(MySqlDataAccessService), + methodName, + mapTimeId, + affectedRows + ); + + // 2) Insert checkpoints in a single transaction (only for Type == 0) + if (mapTime.Type == 0 && mapTime.Checkpoints is { Count: > 0 }) { - var commands = new List(); - foreach (var cp in mapTime.Checkpoints.Values) - { - commands.Add(string.Format( - Config.MySql.Queries.DB_QUERY_CR_INSERT_CP, - lastId, cp.CP, cp.RunTime, cp.StartVelX, cp.StartVelY, cp.StartVelZ, - cp.EndVelX, cp.EndVelY, cp.EndVelZ, cp.Attempts, cp.EndTouch)); - } - await SurfTimer.DB.TransactionAsync(commands); + await SurfTimer.DB.TransactionAsync( + async (conn, tx) => + { + // Insert each checkpoint using the same transaction + foreach (var cp in mapTime.Checkpoints.Values) + { + await conn.ExecuteAsync( + Queries.DB_QUERY_CR_INSERT_CP, + new + { + MapTimeId = mapTimeId, + CP = cp.CP, + RunTime = cp.RunTime, + StartVelX = cp.StartVelX, + StartVelY = cp.StartVelY, + StartVelZ = cp.StartVelZ, + EndVelX = cp.EndVelX, + EndVelY = cp.EndVelY, + EndVelZ = cp.EndVelZ, + Attempts = cp.Attempts, + EndTouch = cp.EndTouch, + }, + tx + ); + } + } + ); } - return (int)lastId; - } - - public async Task UpdateMapTimeAsync(MapTimeRunDataDto mapTime, int mapTimeId, [CallerMemberName] string methodName = "") - { - await Task.Yield(); - throw new NotImplementedException(); + return affectedRows; } } } diff --git a/src/ST-UTILS/Extensions.cs b/src/ST-UTILS/Extensions.cs index 009d399..1127fec 100644 --- a/src/ST-UTILS/Extensions.cs +++ b/src/ST-UTILS/Extensions.cs @@ -82,7 +82,6 @@ public static bool IsPrivateIP(string ip) return false; } - /// /// Asssigns a ChatColor to the given Tier value /// diff --git a/src/ST-UTILS/Injection.cs b/src/ST-UTILS/Injection.cs index 7cc42e7..ccd77e8 100644 --- a/src/ST-UTILS/Injection.cs +++ b/src/ST-UTILS/Injection.cs @@ -11,9 +11,10 @@ namespace SurfTimer; public class Injection : IPluginServiceCollection { - private static readonly string LogDirectory = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/logs"; + private static readonly string LogDirectory = + $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/logs"; - public void ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection serviceCollection) { var fileName = $"log-SurfTimer-.txt"; // Date seems to be automatically appended so we leave it out var filePath = Path.Combine(LogDirectory, fileName); @@ -34,41 +35,39 @@ public void ConfigureServices(IServiceCollection services) Log.Information("[SurfTimer] Logging to file: {LogFile}", filePath); // Register Serilog as a logging provider for Microsoft.Extensions.Logging - services.AddLogging(builder => + serviceCollection.AddLogging(builder => { builder.ClearProviders(); builder.AddSerilog(dispose: true); }); // Register Dependencies - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddScoped(); // Multiple instances for different players - services.AddSingleton(); // Single instance for 1 Map object + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddScoped(); // Multiple instances for different players + serviceCollection.AddSingleton(); // Single instance for 1 Map object - services.AddScoped(provider => - Config.Api.GetApiOnly() - ? new ApiDataAccessService() - : new MySqlDataAccessService() + serviceCollection.AddScoped(provider => + Config.Api.GetApiOnly() ? new ApiDataAccessService() : new MySqlDataAccessService() ); } } /// -/// Handles translation files +/// Handles translation files /// public static class LocalizationService { // Localizer as a Singleton public static IStringLocalizer? Localizer { get; private set; } - public static IStringLocalizer LocalizerNonNull - => Localizer!; + public static IStringLocalizer LocalizerNonNull => Localizer!; + public static void Init(IStringLocalizer localizer) { Localizer = localizer; } -} \ No newline at end of file +} diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index 26dc050..ba1dbd5 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -41,6 +41,8 @@ You should have received a copy of the GNU Affero General Public License using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SurfTimer.Data; +using SurfTimer.Shared.Data; +using SurfTimer.Shared.Data.MySql; namespace SurfTimer; @@ -68,7 +70,7 @@ public SurfTimer(ILogger logger, IServiceProvider serviceProvider) // Globals private readonly ConcurrentDictionary playerList = new(); - internal static readonly TimerDatabase DB = new(Config.MySql.GetConnectionString()); // Initiate it with the correct connection string + internal static IDatabaseService DB { get; private set; } = null!; public static Map CurrentMap { get; private set; } = null!; /* ========== MAP START HOOKS ========== */ @@ -107,7 +109,7 @@ public void OnMapEnd() { _logger.LogInformation( "[{Prefix}] Map ({MapName}) ended. Cleaning up resources...", - Config.PluginName, + Config.PluginName, CurrentMap.Name ); @@ -139,6 +141,12 @@ public override void Load(bool hotReload) { LocalizationService.Init(Localizer); + // === Dapper bootstrap (snake_case mapping + type handlers) + DB init === + DapperBootstrapper.Init(); + var connString = Config.MySql.GetConnectionString(); + var factory = new MySqlConnectionStringFactory(connString); + DB = new DapperDatabaseService(factory); + bool accessService = false; try @@ -172,7 +180,7 @@ public override void Load(bool hotReload) Config.Api.GetApiOnly() ? "API" : "DB" ); - Exception exception = new Exception( + Exception exception = new( $"[{Config.PluginName}] Error connecting to the {(Config.Api.GetApiOnly() ? "API" : "DB")}" ); throw exception; From ebca67179ebd444865912b38badf6ed74d378d34 Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 01:48:50 +0300 Subject: [PATCH 43/52] Add GitHub Actions workflow for SurfTimer.Plugin release --- .github/workflows/release.yml | 206 ++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2e596bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,206 @@ +name: Release SurfTimer.Plugin + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., v1.2.3)" + required: true + default: "v0.0.0" + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Resolve release tag + id: vars + shell: bash + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${{ github.ref_name }}" + fi + if ! echo "$TAG" | grep -Eq '^v[0-9]'; then + echo "Tag must start with 'v' (e.g., v1.2.3). Got: $TAG" + exit 1 + fi + echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV + echo "tag=$TAG" >> $GITHUB_OUTPUT + + - name: Checkout Timer (SurfTimer.Plugin) repo + uses: actions/checkout@v4 + with: + # In this workflow the file is in the Timer repo + ref: ${{ env.RELEASE_TAG }} + path: Timer + + - name: Checkout SurfTimer.Shared + uses: actions/checkout@v4 + with: + repository: tslashd/SurfTimer.Shared + path: SurfTimer.Shared + # If the repo is private: + # token: ${{ secrets.SHARED_REPO_PAT }} + # Optionally pin a version: + # ref: v1.2.3 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore (Shared + Plugin) + shell: bash + run: | + dotnet restore "SurfTimer.Shared/SurfTimer.Shared.csproj" + # Find .csproj of the plugin automatically + PLUGIN_CSPROJ="$(cd Timer && git ls-files '**/SurfTimer.Plugin.csproj' | head -n1)" + if [ -z "$PLUGIN_CSPROJ" ]; then + echo "SurfTimer.Plugin.csproj not found in Timer repo" + exit 1 + fi + echo "PLUGIN_CSPROJ=Timer/$PLUGIN_CSPROJ" >> $GITHUB_ENV + dotnet restore "$PLUGIN_CSPROJ" + + - name: Build SurfTimer.Shared + run: dotnet build SurfTimer.Shared/SurfTimer.Shared.csproj -c Release --no-restore + + - name: Build & Publish SurfTimer.Plugin + env: + OUT_DIR: out/publish + shell: bash + run: | + # Publish aclass library, to copy dependencies to out/publish + dotnet publish "$PLUGIN_CSPROJ" \ + -c Release \ + -o "$OUT_DIR" \ + /p:CopyLocalLockFileAssemblies=true \ + /p:IncludeBuildOutput=true \ + /p:PublishSingleFile=false \ + /p:PublishTrimmed=false + + echo "Publish output:" + ls -la "$OUT_DIR" + + - name: Prepare package layout + env: + OUT_DIR: out/publish + PKG_ROOT: package + shell: bash + run: | + set -euo pipefail + + # Paths + ADDONS_DIR="$PKG_ROOT/addons/SurfTimer.Plugin" + CFG_DST="$PKG_ROOT/cfg" + + mkdir -p "$ADDONS_DIR" "$ADDONS_DIR/data/GeoIP" "$ADDONS_DIR/lang" "$CFG_DST" + + # Copy main DLLs from publish + cp -v "$OUT_DIR/SurfTimer.Plugin.dll" "$ADDONS_DIR/" + + # Copy SurfTimer.Shared.dll from the built Shared + SHARED_DLL="SurfTimer.Shared/bin/Release/net8.0/SurfTimer.Shared.dll" + test -f "$SHARED_DLL" || { echo "Missing $SHARED_DLL"; exit 1; } + cp -v "$SHARED_DLL" "$ADDONS_DIR/" + + # Copy dependencies (try from publish; if missing, fallback from Timer/src/bin) + need() { test -f "$1" || { echo "Missing $1"; exit 1; }; } + + copy_dep() { + local NAME="$1" + if [ -f "$OUT_DIR/$NAME" ]; then + cp -v "$OUT_DIR/$NAME" "$ADDONS_DIR/" + elif [ -f "Timer/src/bin/$NAME" ]; then + cp -v "Timer/src/bin/$NAME" "$ADDONS_DIR/" + else + echo "Dependency $NAME not found in publish or Timer/src/bin" + exit 1 + fi + } + + copy_dep "Dapper.dll" + copy_dep "MaxMind.Db.dll" + copy_dep "MaxMind.GeoIP2.dll" + copy_dep "MySqlConnector.dll" + + # Copy data and lang + need "Timer/data/GeoIP/GeoLite2-Country.mmdb" + cp -v "Timer/data/GeoIP/GeoLite2-Country.mmdb" "$ADDONS_DIR/data/GeoIP/" + + need "Timer/lang/en.json" + cp -v "Timer/lang/en.json" "$ADDONS_DIR/lang/" + + # Copy the whole cfg/ folder + if [ -d "Timer/cfg" ]; then + cp -av "Timer/cfg/." "$CFG_DST/" + else + echo "Timer/cfg not found" + exit 1 + fi + + echo "Final package tree:" + tree -a "$PKG_ROOT" || true + + - name: Zip & checksum + env: + PKG_ROOT: package + shell: bash + run: | + ZIP="SurfTimer.Plugin-${RELEASE_TAG}.zip" + (cd "$PKG_ROOT" && zip -r "../$ZIP" .) + sha256sum "$ZIP" > "$ZIP.sha256" + echo "Built $ZIP and $ZIP.sha256" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: SurfTimer.Plugin-${{ env.RELEASE_TAG }} + path: | + SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip + SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip.sha256 + + release: + runs-on: ubuntu-latest + needs: build + steps: + - name: Determine tag + id: vars + shell: bash + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${{ github.ref_name }}" + fi + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: List artifacts + run: ls -R ./artifacts + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.vars.outputs.tag }} + name: SurfTimer.Plugin ${{ steps.vars.outputs.tag }} + draft: false + prerelease: ${{ contains(steps.vars.outputs.tag, '-rc') || contains(steps.vars.outputs.tag, '-beta') || contains(steps.vars.outputs.tag, '-alpha') }} + files: | + artifacts/SurfTimer.Plugin-${{ env.RELEASE_TAG }}/SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip + artifacts/SurfTimer.Plugin-${{ env.RELEASE_TAG }}/SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip.sha256 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0a7692094c5c83c08323997cda58492aaa6363ad Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 02:05:59 +0300 Subject: [PATCH 44/52] release workflow --- .github/workflows/release.yml | 174 +++++++++++++--------------------- 1 file changed, 67 insertions(+), 107 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e596bd..481a2b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release SurfTimer.Plugin on: push: tags: - - "v*.*.*" + - "v*.*.*" # auto trigger on tags vX.Y.Z workflow_dispatch: inputs: tag: @@ -35,143 +35,108 @@ jobs: echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV echo "tag=$TAG" >> $GITHUB_OUTPUT - - name: Checkout Timer (SurfTimer.Plugin) repo + # Repository of the plugin (Timer) – we want the specific tag + - name: Checkout Plugin repository (Timer) uses: actions/checkout@v4 with: - # In this workflow the file is in the Timer repo - ref: ${{ env.RELEASE_TAG }} path: Timer + ref: ${{ env.RELEASE_TAG }} - - name: Checkout SurfTimer.Shared + # Separate repository for SurfTimer.Shared in the same workspace root + - name: Checkout Shared repository uses: actions/checkout@v4 with: repository: tslashd/SurfTimer.Shared path: SurfTimer.Shared - # If the repo is private: + # If it is private: # token: ${{ secrets.SHARED_REPO_PAT }} - # Optionally pin a version: - # ref: v1.2.3 + # ref: vX.Y.Z - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - - name: Restore (Shared + Plugin) - shell: bash - run: | - dotnet restore "SurfTimer.Shared/SurfTimer.Shared.csproj" - # Find .csproj of the plugin automatically - PLUGIN_CSPROJ="$(cd Timer && git ls-files '**/SurfTimer.Plugin.csproj' | head -n1)" - if [ -z "$PLUGIN_CSPROJ" ]; then - echo "SurfTimer.Plugin.csproj not found in Timer repo" - exit 1 - fi - echo "PLUGIN_CSPROJ=Timer/$PLUGIN_CSPROJ" >> $GITHUB_ENV - dotnet restore "$PLUGIN_CSPROJ" + # Restore + Build the plugin; ProjectReference points to ../../SurfTimer.Shared + - name: Restore + run: dotnet restore Timer/src/SurfTimer.Plugin.csproj - - name: Build SurfTimer.Shared - run: dotnet build SurfTimer.Shared/SurfTimer.Shared.csproj -c Release --no-restore - - - name: Build & Publish SurfTimer.Plugin - env: - OUT_DIR: out/publish - shell: bash - run: | - # Publish aclass library, to copy dependencies to out/publish - dotnet publish "$PLUGIN_CSPROJ" \ - -c Release \ - -o "$OUT_DIR" \ - /p:CopyLocalLockFileAssemblies=true \ - /p:IncludeBuildOutput=true \ - /p:PublishSingleFile=false \ - /p:PublishTrimmed=false - - echo "Publish output:" - ls -la "$OUT_DIR" + - name: Build (Release) + run: dotnet build Timer/src/SurfTimer.Plugin.csproj -c Release --no-restore - name: Prepare package layout - env: - OUT_DIR: out/publish - PKG_ROOT: package + id: prep shell: bash + env: + OUT_ROOT: out/package run: | set -euo pipefail - # Paths - ADDONS_DIR="$PKG_ROOT/addons/SurfTimer.Plugin" - CFG_DST="$PKG_ROOT/cfg" - - mkdir -p "$ADDONS_DIR" "$ADDONS_DIR/data/GeoIP" "$ADDONS_DIR/lang" "$CFG_DST" - - # Copy main DLLs from publish - cp -v "$OUT_DIR/SurfTimer.Plugin.dll" "$ADDONS_DIR/" - - # Copy SurfTimer.Shared.dll from the built Shared - SHARED_DLL="SurfTimer.Shared/bin/Release/net8.0/SurfTimer.Shared.dll" - test -f "$SHARED_DLL" || { echo "Missing $SHARED_DLL"; exit 1; } - cp -v "$SHARED_DLL" "$ADDONS_DIR/" - - # Copy dependencies (try from publish; if missing, fallback from Timer/src/bin) - need() { test -f "$1" || { echo "Missing $1"; exit 1; }; } - - copy_dep() { - local NAME="$1" - if [ -f "$OUT_DIR/$NAME" ]; then - cp -v "$OUT_DIR/$NAME" "$ADDONS_DIR/" - elif [ -f "Timer/src/bin/$NAME" ]; then - cp -v "Timer/src/bin/$NAME" "$ADDONS_DIR/" - else - echo "Dependency $NAME not found in publish or Timer/src/bin" - exit 1 - fi - } - - copy_dep "Dapper.dll" - copy_dep "MaxMind.Db.dll" - copy_dep "MaxMind.GeoIP2.dll" - copy_dep "MySqlConnector.dll" - - # Copy data and lang - need "Timer/data/GeoIP/GeoLite2-Country.mmdb" - cp -v "Timer/data/GeoIP/GeoLite2-Country.mmdb" "$ADDONS_DIR/data/GeoIP/" - - need "Timer/lang/en.json" - cp -v "Timer/lang/en.json" "$ADDONS_DIR/lang/" - - # Copy the whole cfg/ folder - if [ -d "Timer/cfg" ]; then - cp -av "Timer/cfg/." "$CFG_DST/" - else - echo "Timer/cfg not found" - exit 1 - fi + BIN="Timer/src/bin/Release/net8.0" + PKG="$OUT_ROOT" + ADDONS="$PKG/addons/SurfTimer.Plugin" + CFGDST="$PKG/cfg/SurfTimer" - echo "Final package tree:" - tree -a "$PKG_ROOT" || true + # Create directories + mkdir -p "$ADDONS/data/GeoIP" "$ADDONS/lang" "$CFGDST" - - name: Zip & checksum - env: - PKG_ROOT: package + # Validate artifacts + test -f "$BIN/SurfTimer.Plugin.dll" || (echo "Missing SurfTimer.Plugin.dll in $BIN" && exit 1) + test -f "$BIN/SurfTimer.Shared.dll" || (echo "Missing SurfTimer.Shared.dll in $BIN (check ProjectReference path)" && exit 1) + + # Copy the required DLLs to addons/SurfTimer.Plugin + # (Dapper, MaxMind.Db, MaxMind.GeoIP2, MySqlConnector, SurfTimer.Plugin, SurfTimer.Shared) + cp -v "$BIN"/Dapper.dll "$ADDONS/" || true + cp -v "$BIN"/MaxMind.Db.dll "$ADDONS/" || true + cp -v "$BIN"/MaxMind.GeoIP2.dll "$ADDONS/" || true + cp -v "$BIN"/MySqlConnector.dll "$ADDONS/" || true + cp -v "$BIN"/SurfTimer.Plugin.dll "$ADDONS/" + cp -v "$BIN"/SurfTimer.Shared.dll "$ADDONS/" + + # data/GeoIP (only the mmdb file) + SRC_MMDB="Timer/data/GeoIP/GeoLite2-Country.mmdb" + test -f "$SRC_MMDB" || (echo "Missing $SRC_MMDB" && exit 1) + cp -v "$SRC_MMDB" "$ADDONS/data/GeoIP/" + + # lang/en.json + SRC_LANG="Timer/lang/en.json" + test -f "$SRC_LANG" || (echo "Missing $SRC_LANG" && exit 1) + cp -v "$SRC_LANG" "$ADDONS/lang/" + + # cfg/SurfTimer (copy the entire SurfTimer folder from repo ./cfg) + test -d "Timer/cfg/SurfTimer" || (echo "Missing Timer/cfg/SurfTimer" && exit 1) + cp -vr "Timer/cfg/SurfTimer/." "$CFGDST/" + + echo "PKG_PATH=$PKG" >> $GITHUB_OUTPUT + + - name: Create ZIP shell: bash + env: + PKG_NAME: SurfTimer.Plugin-${{ env.RELEASE_TAG }} run: | - ZIP="SurfTimer.Plugin-${RELEASE_TAG}.zip" - (cd "$PKG_ROOT" && zip -r "../$ZIP" .) - sha256sum "$ZIP" > "$ZIP.sha256" - echo "Built $ZIP and $ZIP.sha256" + cd out + zip -r "${PKG_NAME}.zip" "package" + sha256sum "${PKG_NAME}.zip" > "${PKG_NAME}.zip.sha256" + echo "Created archive: out/${PKG_NAME}.zip" + ls -la - name: Upload artifact uses: actions/upload-artifact@v4 with: name: SurfTimer.Plugin-${{ env.RELEASE_TAG }} path: | - SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip - SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip.sha256 + out/SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip + out/SurfTimer.Plugin-${{ env.RELEASE_TAG }}.zip.sha256 release: runs-on: ubuntu-latest needs: build steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + - name: Determine tag id: vars shell: bash @@ -184,15 +149,10 @@ jobs: echo "tag=$TAG" >> $GITHUB_OUTPUT echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - path: ./artifacts - - name: List artifacts run: ls -R ./artifacts - - name: Create GitHub Release + - name: Create GitHub Release and upload assets uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.vars.outputs.tag }} From e1fc88f5db1de5504788b7ad305a63904d954daa Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 02:11:26 +0300 Subject: [PATCH 45/52] release wf --- .github/workflows/release.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 481a2b5..c8b4858 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,11 +58,13 @@ jobs: dotnet-version: 8.0.x # Restore + Build the plugin; ProjectReference points to ../../SurfTimer.Shared - - name: Restore - run: dotnet restore Timer/src/SurfTimer.Plugin.csproj + - name: Restore (Plugin) + working-directory: Timer/src + run: dotnet restore SurfTimer.Plugin.csproj - name: Build (Release) - run: dotnet build Timer/src/SurfTimer.Plugin.csproj -c Release --no-restore + working-directory: Timer/src + run: dotnet build SurfTimer.Plugin.csproj -c Release --no-restore - name: Prepare package layout id: prep From 0fa37b1c7df89169cb0f2a0cbc5dba32e7d64070 Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 02:21:36 +0300 Subject: [PATCH 46/52] release wf --- .github/workflows/release.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8b4858..e52120c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release SurfTimer.Plugin on: push: tags: - - "v*.*.*" # auto trigger on tags vX.Y.Z + - "v*.*.*" # auto trigger on tags like v1.2.3 workflow_dispatch: inputs: tag: @@ -35,30 +35,32 @@ jobs: echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV echo "tag=$TAG" >> $GITHUB_OUTPUT - # Repository of the plugin (Timer) – we want the specific tag - - name: Checkout Plugin repository (Timer) + # Plugin repository (Timer) at the specific tag + - name: Checkout plugin repository (Timer) uses: actions/checkout@v4 with: path: Timer ref: ${{ env.RELEASE_TAG }} - # Separate repository for SurfTimer.Shared in the same workspace root - - name: Checkout Shared repository + # Shared repository as a sibling folder in the workspace root + - name: Checkout SurfTimer.Shared uses: actions/checkout@v4 with: repository: tslashd/SurfTimer.Shared path: SurfTimer.Shared - # If it is private: + # If private: # token: ${{ secrets.SHARED_REPO_PAT }} - # ref: vX.Y.Z + # Optionally pin a version: + # ref: vX.Y.Z - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - # Restore + Build the plugin; ProjectReference points to ../../SurfTimer.Shared - - name: Restore (Plugin) + # --- IMPORTANT --- + # From Timer/src, the ProjectReference ../../SurfTimer.Shared/SurfTimer.Shared.csproj resolves correctly + - name: Restore (plugin) working-directory: Timer/src run: dotnet restore SurfTimer.Plugin.csproj @@ -79,15 +81,13 @@ jobs: ADDONS="$PKG/addons/SurfTimer.Plugin" CFGDST="$PKG/cfg/SurfTimer" - # Create directories mkdir -p "$ADDONS/data/GeoIP" "$ADDONS/lang" "$CFGDST" - # Validate artifacts + # Validate main artifacts test -f "$BIN/SurfTimer.Plugin.dll" || (echo "Missing SurfTimer.Plugin.dll in $BIN" && exit 1) test -f "$BIN/SurfTimer.Shared.dll" || (echo "Missing SurfTimer.Shared.dll in $BIN (check ProjectReference path)" && exit 1) - # Copy the required DLLs to addons/SurfTimer.Plugin - # (Dapper, MaxMind.Db, MaxMind.GeoIP2, MySqlConnector, SurfTimer.Plugin, SurfTimer.Shared) + # Copy required DLLs cp -v "$BIN"/Dapper.dll "$ADDONS/" || true cp -v "$BIN"/MaxMind.Db.dll "$ADDONS/" || true cp -v "$BIN"/MaxMind.GeoIP2.dll "$ADDONS/" || true @@ -95,7 +95,7 @@ jobs: cp -v "$BIN"/SurfTimer.Plugin.dll "$ADDONS/" cp -v "$BIN"/SurfTimer.Shared.dll "$ADDONS/" - # data/GeoIP (only the mmdb file) + # data/GeoIP SRC_MMDB="Timer/data/GeoIP/GeoLite2-Country.mmdb" test -f "$SRC_MMDB" || (echo "Missing $SRC_MMDB" && exit 1) cp -v "$SRC_MMDB" "$ADDONS/data/GeoIP/" @@ -105,7 +105,7 @@ jobs: test -f "$SRC_LANG" || (echo "Missing $SRC_LANG" && exit 1) cp -v "$SRC_LANG" "$ADDONS/lang/" - # cfg/SurfTimer (copy the entire SurfTimer folder from repo ./cfg) + # cfg/SurfTimer test -d "Timer/cfg/SurfTimer" || (echo "Missing Timer/cfg/SurfTimer" && exit 1) cp -vr "Timer/cfg/SurfTimer/." "$CFGDST/" @@ -119,7 +119,6 @@ jobs: cd out zip -r "${PKG_NAME}.zip" "package" sha256sum "${PKG_NAME}.zip" > "${PKG_NAME}.zip.sha256" - echo "Created archive: out/${PKG_NAME}.zip" ls -la - name: Upload artifact From 8612c0988ab3b1d33bc0b7004ee2fdcf2388bf8c Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 02:56:49 +0300 Subject: [PATCH 47/52] - Build now includes all DLLs required by the Timer to run properly on the server - Created api_config.json for API configuration - Update release.yml --- .github/workflows/release.yml | 41 +++++++++++++++++------------------ cfg/SurfTimer/api_config.json | 4 ++++ src/SurfTimer.Plugin.csproj | 23 ++++++++++++++++++-- 3 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 cfg/SurfTimer/api_config.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e52120c..18d0b71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,14 +35,14 @@ jobs: echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV echo "tag=$TAG" >> $GITHUB_OUTPUT - # Plugin repository (Timer) at the specific tag + # Checkout plugin repo at the tag - name: Checkout plugin repository (Timer) uses: actions/checkout@v4 with: path: Timer ref: ${{ env.RELEASE_TAG }} - # Shared repository as a sibling folder in the workspace root + # Checkout SurfTimer.Shared as sibling folder (ProjectReference resolves via ../../SurfTimer.Shared) - name: Checkout SurfTimer.Shared uses: actions/checkout@v4 with: @@ -50,7 +50,7 @@ jobs: path: SurfTimer.Shared # If private: # token: ${{ secrets.SHARED_REPO_PAT }} - # Optionally pin a version: + # Optionally pin to a tag/commit: # ref: vX.Y.Z - name: Setup .NET 8 @@ -58,8 +58,7 @@ jobs: with: dotnet-version: 8.0.x - # --- IMPORTANT --- - # From Timer/src, the ProjectReference ../../SurfTimer.Shared/SurfTimer.Shared.csproj resolves correctly + # From Timer/src, ProjectReference ../../SurfTimer.Shared/... resolves correctly - name: Restore (plugin) working-directory: Timer/src run: dotnet restore SurfTimer.Plugin.csproj @@ -72,7 +71,7 @@ jobs: id: prep shell: bash env: - OUT_ROOT: out/package + OUT_ROOT: out run: | set -euo pipefail @@ -83,30 +82,29 @@ jobs: mkdir -p "$ADDONS/data/GeoIP" "$ADDONS/lang" "$CFGDST" - # Validate main artifacts - test -f "$BIN/SurfTimer.Plugin.dll" || (echo "Missing SurfTimer.Plugin.dll in $BIN" && exit 1) - test -f "$BIN/SurfTimer.Shared.dll" || (echo "Missing SurfTimer.Shared.dll in $BIN (check ProjectReference path)" && exit 1) + echo "Build output listing (should contain only selected DLLs):" + ls -la "$BIN" - # Copy required DLLs - cp -v "$BIN"/Dapper.dll "$ADDONS/" || true - cp -v "$BIN"/MaxMind.Db.dll "$ADDONS/" || true - cp -v "$BIN"/MaxMind.GeoIP2.dll "$ADDONS/" || true - cp -v "$BIN"/MySqlConnector.dll "$ADDONS/" || true - cp -v "$BIN"/SurfTimer.Plugin.dll "$ADDONS/" - cp -v "$BIN"/SurfTimer.Shared.dll "$ADDONS/" + # Required artifacts (these should exist thanks to your KeepOnlySelectedDlls target) + for f in SurfTimer.Plugin.dll SurfTimer.Shared.dll Dapper.dll MaxMind.Db.dll MaxMind.GeoIP2.dll MySqlConnector.dll; do + test -f "$BIN/$f" || { echo "Missing $f in $BIN"; exit 1; } + done + + # Copy all dlls that remain after your KeepOnlySelectedDlls pruning + cp -v "$BIN"/*.dll "$ADDONS/" # data/GeoIP SRC_MMDB="Timer/data/GeoIP/GeoLite2-Country.mmdb" - test -f "$SRC_MMDB" || (echo "Missing $SRC_MMDB" && exit 1) + test -f "$SRC_MMDB" || { echo "Missing $SRC_MMDB"; exit 1; } cp -v "$SRC_MMDB" "$ADDONS/data/GeoIP/" # lang/en.json SRC_LANG="Timer/lang/en.json" - test -f "$SRC_LANG" || (echo "Missing $SRC_LANG" && exit 1) + test -f "$SRC_LANG" || { echo "Missing $SRC_LANG"; exit 1; } cp -v "$SRC_LANG" "$ADDONS/lang/" - # cfg/SurfTimer - test -d "Timer/cfg/SurfTimer" || (echo "Missing Timer/cfg/SurfTimer" && exit 1) + # cfg/SurfTimer (copy entire folder) + test -d "Timer/cfg/SurfTimer" || { echo "Missing Timer/cfg/SurfTimer"; exit 1; } cp -vr "Timer/cfg/SurfTimer/." "$CFGDST/" echo "PKG_PATH=$PKG" >> $GITHUB_OUTPUT @@ -117,7 +115,8 @@ jobs: PKG_NAME: SurfTimer.Plugin-${{ env.RELEASE_TAG }} run: | cd out - zip -r "${PKG_NAME}.zip" "package" + # zip the *contents* so archive root is addons/ and cfg/ + zip -r "${PKG_NAME}.zip" addons cfg sha256sum "${PKG_NAME}.zip" > "${PKG_NAME}.zip.sha256" ls -la diff --git a/cfg/SurfTimer/api_config.json b/cfg/SurfTimer/api_config.json new file mode 100644 index 0000000..98449c5 --- /dev/null +++ b/cfg/SurfTimer/api_config.json @@ -0,0 +1,4 @@ +{ + "api_url": "API_URL_HERE", + "api_enabled": false +} \ No newline at end of file diff --git a/src/SurfTimer.Plugin.csproj b/src/SurfTimer.Plugin.csproj index 7114bd0..5fcdd7e 100644 --- a/src/SurfTimer.Plugin.csproj +++ b/src/SurfTimer.Plugin.csproj @@ -1,13 +1,14 @@  - net8.0 enable enable true + + + true - DEBUG @@ -20,7 +21,25 @@ + + + + + + + + + + + + From 290f92daade8de531d675befa113d916f01565d0 Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 03:02:15 +0300 Subject: [PATCH 48/52] Remove binary files - Added them to the build sequence --- src/bin/Dapper.dll | Bin 245760 -> 0 bytes src/bin/MaxMind.Db.dll | Bin 44544 -> 0 bytes src/bin/MaxMind.GeoIP2.dll | Bin 58368 -> 0 bytes src/bin/MySqlConnector.dll | Bin 857448 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/bin/Dapper.dll delete mode 100644 src/bin/MaxMind.Db.dll delete mode 100644 src/bin/MaxMind.GeoIP2.dll delete mode 100644 src/bin/MySqlConnector.dll diff --git a/src/bin/Dapper.dll b/src/bin/Dapper.dll deleted file mode 100644 index a837f481afef4ca5569b73126428c64c28351473..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245760 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�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℘Qt4Yh#9eLf~zB|EmgK%(IQ*@|Ix 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%0A%xJFwaLLUj=J7N4^f` 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

-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 Z$WnXg>PyLv;ksR*Qp}bb2ENt+`d^!lrj7sr From 5ea9184de3441f1b356a12c566595aaa4c89b5b9 Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 03:37:49 +0300 Subject: [PATCH 49/52] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02bf6b3..70397bc 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Bold & Italics = being worked on. - [ ] Points/Skill Groups (DB) - [ ] Player settings (DB) - [x] Replays - - [x] Personal Best + - [x] Personal Best - Data for the PB replays is saved but no functionality to replay them yet is available - [x] Map Record - [X] Stage Record - [X] Bonus Record @@ -220,4 +220,4 @@ Bold & Italics = being worked on. - [X] Bonus Record - [ ] Style implementation (SW, HSW, BW) - [ ] Paint (?) -- [ ] API Integration (Repo can be found [here](https://github.com/CS2Surf/CS2-Surf-API)) +- [x] API Integration (Repo can be found [here](https://github.com/tslashd/SurfTimer.Api)) From a036729735b6ba517242506be95ddf2c3c31893a Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 05:43:51 +0300 Subject: [PATCH 50/52] Add Dependabot config and bump package versions - Bump CounterStrikeSharp version - Bump minimum CounterStrikeVersion to avoid broken EyeAngles --- .github/dependabot.yml | 43 +++++++++++++++++++++++++++++++++++++ src/SurfTimer.Plugin.csproj | 2 +- src/SurfTimer.cs | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..927830c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,43 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +# .github/dependabot.yml +# Keep GitHub Actions up to date and open a SINGLE PR that aggregates all updates. + +version: 2 +updates: + - package-ecosystem: "github-actions" # manage versions of actions used in .github/workflows + directory: "/" # repo root (covers .github/workflows) + schedule: + interval: "weekly" # run once a week + day: "monday" # every Monday + time: "04:00" # at 04:00 local time + timezone: "Europe/Sofia" + target-branch: "main" # PRs will target this branch + open-pull-requests-limit: 10 # safety cap (not really needed with grouping) + commit-message: + prefix: "chore" # e.g., "chore: bump actions/*" + include: "scope" + groups: + all-actions: # SINGLE PR with everything inside + patterns: ["*"] # match all actions + update-types: ["major", "minor", "patch"] # include all types of bumps + + - package-ecosystem: "nuget" # manage NuGet packages in .csproj/.sln files + directory: "/" # Dependabot discovers projects recursively + schedule: + interval: "weekly" # run once a week + day: "monday" # every Monday + time: "04:00" # at 04:00 local time + timezone: "Europe/Sofia" + target-branch: "main" # PRs will target this branch + open-pull-requests-limit: 10 # safety cap (not really needed with grouping) + commit-message: + prefix: "chore" # e.g., "chore: bump MySqlConnector" + include: "scope" + groups: + all-nuget: # SINGLE PR with everything inside + patterns: ["*"] # match all packages + update-types: ["major", "minor", "patch"] # include all types of bumps diff --git a/src/SurfTimer.Plugin.csproj b/src/SurfTimer.Plugin.csproj index 5fcdd7e..36ce64f 100644 --- a/src/SurfTimer.Plugin.csproj +++ b/src/SurfTimer.Plugin.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/SurfTimer.cs b/src/SurfTimer.cs index ba1dbd5..cc7629c 100644 --- a/src/SurfTimer.cs +++ b/src/SurfTimer.cs @@ -47,7 +47,7 @@ You should have received a copy of the GNU Affero General Public License namespace SurfTimer; // Gameplan: https://github.com/CS2Surf/Timer/tree/dev/README.md -[MinimumApiVersion(333)] +[MinimumApiVersion(337)] public partial class SurfTimer : BasePlugin { private readonly ILogger _logger; From 25b5d7cc802eade70e0212d785e4e995e3d6ef21 Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 06:12:27 +0300 Subject: [PATCH 51/52] Update README.md with dependencies --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 70397bc..78d0ec5 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,11 @@ Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source
+## 🔗 Dependencies +- [`CounterStrikeSharp`](https://github.com/roflmuffin/CounterStrikeSharp) - **required** minimum version [v1.0.339](https://github.com/roflmuffin/CounterStrikeSharp/releases/tag/v1.0.337). +- [`SurfTimer.Shared`](https://github.com/tslashd/SurfTimer.Shared) – **required** shared library for DTOs, entities, and database integration. +- [`SurfTimer.Api`](https://github.com/tslashd/SurfTimer.Api) – *optional* REST API for faster, centralized communication with the database. + # Main list with tasks (more details can be found [here](https://github.com/CS2Surf/Timer/blob/dev/TODO)): *Note: This is not definitive/complete and simply serves as a reference for what we should try to achieve. Subject to change.* Bold & Italics = being worked on. From 753104719c1b4f8696bde22cb837c9df3a339e9e Mon Sep 17 00:00:00 2001 From: tslashd Date: Fri, 12 Sep 2025 06:14:51 +0300 Subject: [PATCH 52/52] Update README - Wrong minimum CS# version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78d0ec5..976eaf9 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Core plugin for CS2 Surf Servers. This project is aimed to be fully open-source
## 🔗 Dependencies -- [`CounterStrikeSharp`](https://github.com/roflmuffin/CounterStrikeSharp) - **required** minimum version [v1.0.339](https://github.com/roflmuffin/CounterStrikeSharp/releases/tag/v1.0.337). +- [`CounterStrikeSharp`](https://github.com/roflmuffin/CounterStrikeSharp) - **required** minimum version [v1.0.337](https://github.com/roflmuffin/CounterStrikeSharp/releases/tag/v1.0.337). - [`SurfTimer.Shared`](https://github.com/tslashd/SurfTimer.Shared) – **required** shared library for DTOs, entities, and database integration. - [`SurfTimer.Api`](https://github.com/tslashd/SurfTimer.Api) – *optional* REST API for faster, centralized communication with the database.

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 z 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 diff --git a/src/bin/MaxMind.GeoIP2.dll b/src/bin/MaxMind.GeoIP2.dll deleted file mode 100644 index a665688c4e2fad5a10df3ef61f491d319b161be0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58368 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 diff --git a/src/bin/MySqlConnector.dll b/src/bin/MySqlConnector.dll deleted file mode 100644 index 4606dde323022844cfff0a6c2995882cf7be5e12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 857448 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�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 z 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 Date: Fri, 12 Sep 2025 01:15:57 +0300 Subject: [PATCH 42/52] This commit includes significant refactoring and improvements across the SurfTimer project. - Streamlining database access with Dapper through `SurfTimer.Shared` library, removing unused MySQL code. - Enhancing error handling and logging in `ApiDataAccessService` and `MySqlDataAccessService`. - Improving readability and maintainability --- src/ST-API/Comms.cs | 8 +- src/ST-DB/DB.cs | 116 ---- src/ST-Events/ZoneEventHandlers.cs | 4 +- src/ST-Map/Map.cs | 2 +- src/ST-Player/Player.cs | 3 +- src/ST-Player/PlayerHUD.cs | 1 - src/ST-Player/PlayerStats/CurrentRun.cs | 2 +- src/ST-Player/Replay/ReplayPlayer.cs | 21 +- src/ST-Player/Replay/ReplayRecorder.cs | 1 - src/ST-UTILS/Config.cs | 143 +---- src/ST-UTILS/Data/ApiDataAccessService.cs | 314 ++++++----- src/ST-UTILS/Data/IDataAccessService.cs | 48 +- src/ST-UTILS/Data/MySqlDataAccessService.cs | 589 +++++++++++++------- src/ST-UTILS/Extensions.cs | 1 - src/ST-UTILS/Injection.cs | 37 +- src/SurfTimer.cs | 14 +- 16 files changed, 663 insertions(+), 641 deletions(-) delete mode 100644 src/ST-DB/DB.cs diff --git a/src/ST-API/Comms.cs b/src/ST-API/Comms.cs index 81d2f3f..038eb95 100644 --- a/src/ST-API/Comms.cs +++ b/src/ST-API/Comms.cs @@ -11,7 +11,7 @@ namespace SurfTimer; internal class ApiMethod { private static readonly HttpClient _client = new(); - private static readonly string base_addr = Config.ApiUrl; + private static readonly string BaseAddress = Config.ApiUrl; // Custom Converter for ReplayFramesString private static readonly JsonSerializerOptions _jsonOptions = new() @@ -32,7 +32,7 @@ static ApiMethod() /// Deserialized T or null public static async Task GET(string url, [CallerMemberName] string methodName = "") { - var uri = new Uri(base_addr + url); + var uri = new Uri(BaseAddress + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); using var response = await _client.GetAsync(uri); @@ -101,7 +101,7 @@ static ApiMethod() [CallerMemberName] string methodName = "" ) { - var uri = new Uri(base_addr + url); + var uri = new Uri(BaseAddress + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); try @@ -171,7 +171,7 @@ static ApiMethod() [CallerMemberName] string methodName = "" ) { - var uri = new Uri(base_addr + url); + var uri = new Uri(BaseAddress + url); var _logger = SurfTimer.ServiceProvider.GetRequiredService>(); try diff --git a/src/ST-DB/DB.cs b/src/ST-DB/DB.cs deleted file mode 100644 index 0a0377b..0000000 --- a/src/ST-DB/DB.cs +++ /dev/null @@ -1,116 +0,0 @@ -using MySqlConnector; - -namespace SurfTimer; - -internal class TimerDatabase -{ - private readonly string _connString; - - public TimerDatabase(string connectionString) - { - _connString = connectionString; - } - - ///