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 1/6] 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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�%c(Igd2+ASI`nY56z0h(Cp#5x4@!}xyChVL!&f6Vx zyibbUupgRCEUG|_I9eRXC~@xAxo(aw46~_ zpjC`2%*C5j80Dc=Nb4Shc78v&X6EhP#yrfZV!(0Hjy{(2>ar7djXcirUN{pwAArK9 z$80?3!N|6SuouTfP#5Tmk6CGG>+r_B&UB>KTbm_Ua7Q7zh^y9|nr{gJTyu=5KKMCA7;`(Dd3CeGIP?Zj++w8t*4~v4kGE)}mZo zjS`0%)N-5!kEOb7W-fzYQM+tuUTOydp3>4>HO(>cjMAH-Z)bEdQV2TjPK)ZIr%vV2 zn;pkwFzUC&hW=K9ce61!`Wnl9Uu4mG*e}ANZx+~m6Opgvv1Fb_KcKw{*CE+*@6s%b z(=JB8)|An$%keN9qgAkmaJMK*MLUwv4n?+X4{W#G2a4(*wcNZ#R`RHSEb80SN?v)R zvA-9OAqX@?83?i@NEULqx$ zc&p9#3EWW;?%j1(=hpBw;S9zPL5<)!g0`xyrq00|(OL56*IMnr%CqS2IGbYqaEs=q zTJ%r8pgtwfXGwq1%&fxwmGdDlY$lFy%qT{+q1Df+#61l!ImGCAt|gE5mmmdp9isgf zFxuM}Mq|`}gzde%?X~0O8Po7=7l$swI5Ue;PoOzKVRI=gel>G7JJ{am{?3kB!W^~6 zWsEMb>X*-^a85Q{#Yk6q1(7dAUF3f;%Z_Bmm*!5vA0rd~JEG;F*c z$2i9_#2FZCLuahP3h zVe}ftsP}-PXDw6Q*6o-tzmZ-vY^IJk=0lFR9{s^CM*Yxcs26ZlE6fO7tt^K43wORLaykHk5W4OK6&+&}z$d zL0=-F^o)_9?#k^^Db5-jS`D5p@!E{FdAy7ECfpX)o}N1rZW_E;&~&Vg3%W$nNJTRh zjjU@^(C%(Yv2wBHu28$s-f9V@9o2$pAGM&MqShEcgxhzGMFDuCaQiVA5cHy=WDTV! zsVKCW*?~Ud8=$Z`AN>LOTG&*dEPfC+|4g!I7TT7Cet&`G%!N23nA2VWM4{2O|K-^d znqP9m0E@yHe8?Tn8I2%2Uq!^8s zE2s{}OF`@Iw4nnv-e9zI39WIrMN`o03D-vL_oe2$s-Bg1MiU#)(Q?dDO)tPGDN>Fo z_xyaD*00*vQ?vwrFDXWLvhfyRb}d{>Tt^A=oo-R2w@q<|qNV*TxAJm}R;x9V)drQc zjxw|^C&3dXkFK?B9`9nT5bja5UqSazv+?fM+^SEup^;`5Ig4$)-_r*|)hHFi4#e74g98j*oXI6vzmD@1a#%rnN-Htv- z(tfPIdxu)>Os$z2B{uE57g$t5t+8=|l~POX><`;`k12XrHOTF1Ltj_@zSnr8)F-#A z@4j`G&10d4J~`S-c}`ndEA{M+>Ls1krvB@!lx135{YP0T4|KGs^X)d?rHcA0x=hQv zZI+Gq&m9(3$g)x@_qS+(vFIPw`Q;v#J5#OEXorU0Uw7sH4`=lUg%ZUSU%VRlEGC<$YCc znx>_U8*ft>)u4l-2UN=Ss>=awAKSGqubFPOx}%eo=V&jw;7rTipJCCN^K6POIvUJX z{TgDAoRmCE<*n8JU~+$(wyJvAmC7~z#D@09NGwwR&{D3|vc0p^hR#W}x%JhujnE!x zy|&*iYRUb>ZHj){Gj!FuY+hm|*Vn%DJC$5fH64jnE79)*%s~XL{KKYgr9N~cuEr#E zt=g}yN_kP+V7%6XQLA08zHyG)>K7eT=HeUBZM^C_ z&h*z5LvZCU_PbWOG;PUWtNrd){mxat-=KB*ruJ7~sh7lkWVQMX>nf7R1FGLv?FCk- z{q~Qr@iyq3@J&V49UEGR)hLm7Ltl&5Dmt2H3op9|eDGqABd8$iTL;Z@L)X}AW zMVodUR&_{4$#bv{&A>({6LvPR3XLTjd_)^9(Z0c7hKx@@4$W8ZCxwAC(EFZo)f6szPRS6HnM z>g-{=UN7{iZ9|_??iDT3Snbh+>UC*4Q`mw%{8HXFpILN+w$Pijr~XrC;X5@&pN=-) zLwDG;Ym}>~GpHWgJ9g1l`;F>y=j}G_B1O51Zrou*&(ofDQ5(y()2oE8YJ;odtXA)7 zZEc%wxq52JcXWj9qt~1hwT_MrGAGDPFG{s$tDybzGXn*jON*Sm= z6w*1|dabwqI@Z3X*M9wUB;GXImZ*xh(91QqR@z@3!^)l1Z}rX=wNsR=ZR;ELoLR{> z-p$$<|D^qM)n?Wj)zv#2&$e78MJv>cv$fGP+M*FkIoMU>G-iyXJ7X!>Y;6Vnp*s9 zMYGj&&bZEMny;;=-!RMlsdag2HOoD9jz!liYNpyRt!hIrn`6;CYK(kNL&t#6 z6#cl@T5V?7N_kTI;urLqWVDXNa|YUYHB{5*Ro>^?@(ya+Lz?1x4PAGw&G&58Z(pXB z{Eeolsbj5Q(GwGGyn||kXB1sl&xVfCQ7Be5U83=ND7Rg?nOY0?Dp#N_{teY?r21}# z_R5FVBlc?!r)N_thfR_6jYtCTx-+O&ssmht&CYtw})Wtz5`7j^E_T>Godx(0QQ+U2w*R&oWk z>5cQO=YAK(&-AegJQ8)E=N736?*?8a2 zwz@p2XrQ8dRjc=vo2BR$McvhhYUv1@sHnPLA5~LZ#TM9nXX||OHtk;$3To1RLeX$V6||RXrR}n@+F-GcoUdu^8=djgQfriGIi^*z+CQZ8>^pUI z`9OVRwx(^f-P*K8u}wQvN9T@O3wO1&@&3?wJ7R2#My+h<13E9MrrM`!U$-pFrkJBB zR^yE?vQn04zqV1)-`cA@t9d-DE%`pR!Nb~)X4ba3U7~4|jOFU-tT|VE`7;$=r}9Q= zZmo4L+D%8;)7>?#u&F=(_&aB-hub^6U0|V~r_lYkV$2VI1B$+zb{S^PN5ILhCP6pU z!>MA-bqrcmQMuphT5gAn!se+0*#QZJSthDM~;b z?$*Nwv>P&PmW{^lKycpf1aH4Ue#zMDMEm@b2Ncay6o=MGcF8ZpX-d3gbdCSHdt-53?_qw9yiq6-xZz=jzbvdT--d1_nT;sTPG*mS$(0ElA z+R%F!ShQBt10qS-g*uFN2P4nc=a{j#UI+*`by*FY1&zemT2fliY94j zlA;ERe$d=DXo^Rb^Qi_GY3M;kDH>W`?YvwiJh87^+dMW6kXJx zUr=tnw&YKqus;++OeqQ<*YxyL;ziY`~wN8_zld4DSRtfCrf$@3I- z)7&0Y)JY{j^pMr6)uR^eRPO!tmb*+tcPMK6hz(89c(*9G>tPL5Znbh3E4N1F#VdMC zLw%1~Da{nM)7(}mN>s_GEAl;P(>BnVR9N)&sg-lq6-z>@VpJ(r71c;Yq>v_JNlgE@{|iI`cS15 zspQ|4dtA{BO_8PCEt=vP4Q;F3Q=0Eu)oPTYCK@_VxlSs1qM}+FI$2X}yV}k`-_+0t zud-ZT0Pc)AdMU^$hdCDDAd0jN^bsBGiqBV+ksV)aKMXZMQ zRP?fgXz6^+)=_Y}3#wBM@+{WP?JhOSg@qoR;X&Qk7iP0>I@UsuV~ zHC`(XU9MI;U(^2dl=aDGnxdcjPy-P)<)T+Q z8aj>{GL^3AFTmQNpnR;G396`w_Ph(X?p%xN;u>8zx;_#_yXgfryVxSy<1ZZTuorZ1 zJwegB&=UnCS(ksV#A)V?R%2aHP_sQYl%6OMj-DtGL{Ah5qTB?v)D-kYfpDSK7SR(0 z!qF22g6KH{LFpQg_V^1&&k+cEBxF;JRkRvwn-WUT76>A12%?e;qGt;P(X$1D=-C26 zWM@J2{DL5Qwm=X)TR7B+!AEG{lduONR`n9m!vRf_J=zfb@V?~Zq zgiUaoF%uZ|M@`fEi{v(y^Pp9i8H|n=S@hCy$K|d>ZW8*(DvQo)VbM~a#Mbon^xAh9kP3mXST+O$W8&9;NmEQc4tv&FJH=|&5vcH5Bzj9f@RmqCn6?u$rI%(vmg8 zW=yVQ7BGrWl_xvHW?Or#OEdR(yko8h!YJp)!wIXOO1@YvL>{s_c-i@Q_X(#cK|ESz z$0{Ofb(M1WGWzKvyJq>9qAa9%nDg+`_Qt)A$2mp6299}-(P=Jn%uaYB>5L~Sv2SS; zqrr;Sp|_w?V)asy)x|L@&b-Rt2bdT@BJg=%Wc=9pm!6`egP@5G>_Eah|{ zkz${_3!8?6@Ww3IIlpA#9a7Wfab9SCNr@XTzvLoKu>$o*)(D#hgDn~YeWe^1U1sZ- zo@b}kH$2re(mwltbeQysc=Ogo8IMpaKvIt0sQulX;u-WW;w9eQheL~GjoUHzU2jqQ zt1L=hU{UH)L9%xJC0nDT>em2TNz3zWKu=GJ_SX!Tv|(ePqw=hQyG9eFX9%0y#)u`u z<``@$X*;^s#q;|^MM|HNXOnE}@p7|IX%@KD&aKyIS5S8w#>yl_2_8FPTOdiy-7%lwijVQ~u0 zFWIGN=`zbbspyY$ELROZgQVDr{zcI9=_gu!@j6tn@s>8QXxrHqjetkExncAZbbZ94Dyr26)#_2)f0K9xxW6xGg`y3L?!|a1 zp*>WqxDJ+UthqJOJcbOgp&!q+k_TUFxy9(m-F(%ilh6YTcRlJuP&?%oD!Ne7ezjz* z+hgL%KlHnj_C7@wFnbVg#Y`L8AH9@t!&X~V`x=XmDJnk8rfsj<@6ph9nl?*ApF7P; zd9B2vcYd~LYa5H2V*Vl;{G?Xvh_)|WO-22&?@PGRU2SNgqPD$l=+ocW&8}YOq)}SU1^* z4zF!dhUPY2LwohHd3>buHg&ftGPS0C+P~bbp;y$m@zS+sdT5)@QJwvY=4xHO;l^ub zvcAVz|2$Tl1r#%lMvaX?X^aj4g&Cdwl?~l|0C$X;d+?w|Ip8`l_siFod+$q&W$0V%}Jb--=lw8vmck(`CJuQE4jzvXU4*=WCgGjknE z^c|xus@1zH`7T8%f7rCUVAI&?l=d)?26?FY?7PZsdj;if9!{XvOm%|T2EM>6f zyAqs~<3Yr$&!Jm>!p;ju!@>1nv=m56S^XEA+b*?VG0HoL;~j$bQd>vV_Eljk;okq< z=C)KrpHNf)3rYPx0{@em`3Wf|u;ibim6SJ5z4KXUFY$8K|DJ+1MCW!0m2zzT6_Q!X zFB-3krkxL)O4-_?lp^mzaAKFIpp|HUP(5}BN+d0GEZU4%cc+F)0;I z&{WU`u$5@K4<(m))!|oy#z3B+?=`nq5h{|uQuL2%kOsR*A9gJyOWJinV&}f71xcF; zZWZg&R`W;#5?yL&owq_fN%1c1Ea+~{_d3KAZUjpCD9igEd5DzpZfMv%Ki=^9Vb}+C zu|>Blw^va|4gJ&Qnwdv0lJlE_=2{m;UunKEOXltQCGTJbjb`xqCG9Zw2zn~ZqAi^) z+Wxo1lWT*QpmW%KI9A4^uz3dPb>_S)$hEkBqthzF=Dm?NUXuc2-s1A!iMhabMy;@( zLuXkuGd&a~VcZh#2Fy1E&55(9#xmn_wJ>{?&{=CN_X*Y>h{L&CE$p?>iV3m@4OdF; z^;XynQFPa3HeN0?-OJ_hv_Ast$TjR!BkVbHKb>zmuWWR-9PN3kZ(6uBBR{|S;S0#a z+puwMPcsU?IjZQ=i?N2lHlS-TI!PyN9vEb8Eq3F0_Rpcy3DJ3prcXD86Pbhs*?f%B0#wBl{J?q(EL^eJX2A?9WQ z)nb%|c|;DQ)!=C79^QlQ&YK)_+6sI1p3zk_aLlLk;0MSJr!HYHSW~2ROo@xU^K-&x z1#JJLLs~sPTF&Ca*+3JFd+t#yScrBsM(+-+r#1Me*5E4xt&~5o;vn)a(<}9A7s_rv z>|_Iy&@@OE6f?x4`idqis)lw%SH59$7iw8jJcF8P!PaPUsWELC9f6mKzqCaQpnYy( z6G95%s6~(;gv~U>6SP9nV3b_ACCFD&Gy^Bdo>dn%^B`HcO=^P{kStsdYDJ`+r)a3A zxCeGQlQn%#b%}%hB<(^V+MNnJqfHCf2lf;6+!%}MKxZl2LDZ3i4jN}s8tSqq=W!bB zOnWcGrk7gslY!QLK9$#1HTYDyUtwL6?3nXlU0L-T)=kdCYi5RGe%c3lIA(rxtTu9P zt)b~qMj@ma#i4e$Cf3ZLi^xlISEI0J7_~Yc@q%W+K(U(iaOC423Y!k-eI|nQ_BKZE zW2_1drXUo%T7fR-c>AEaTbIKfQ_S*Sz_T6;8D(G(q@ayh6TFVOOBOrkCPsr5UE!iW zB}4m*)%ui-RkRf?nLM#iNr%POroRug=qlGw9kUA7khRjqs4Y5|4r_;szN1j&i_;6C z{k@F-$&*!~=oO%IA#Xj0(y5bA0)-9r=!9_B0%@OT?mciXF*+UNk(|hv3g38(<2{Eu z+RDhTS~=z#<<3G{siTs!@t#kPw+e_XDXYYLn2RYv&(G+K;dcG+@qw@gbEDDckXF%S z9^KcVeDh0gK_4dQEuegHndu??l9;gXgX?N&_kmvTJMf;jKE94;%W6azhoptf;Yz%0Jo7+c!K9HCPckhs}NGIwq5~f9Nh_YA_m& z`y{m)*|l)T*!67eZ^wFe0}frZ+L%U+HZ;Q<@fa<|cq!%WiZvra1AANa`YvmOAm$db zCcV`y8{YYKj=lS^9e12sLS8fT(2v+L&FC*hoA%*-S>VEEx{hk2(XU;^+;xK_k7lND z8fq0>OS8F_gtjzIakWkHTAGH6o`f}obFQ>#9@f2ylU_<@55q7Myab$M`ok0HY+Q^z zXi=Ae3!CNeI`WOOt%5@HOXw6vK@ShM==?5{Hos)lUlv_e&!QGVi~P#{P}g#AD_Z)e z<>+3PT4& zQZ|wIB*$BKqu4oY#w+@(P)0wA_Z;F0+N9_;MOzfTr|3gPA1T_a=nF;PD%!8;prXTy z{#N8bGttGbC`nO8MU@p*Rg|TuuA+uOVe{`c$ki0h6}48>K~Wb)-4*p#)JIXiq6-ua zXXKp~`zvOzW56|cp3a4DFxsAN%rr(vz|CYdu9-1cFglP4k6`p|4tixqUpB$4nbA&T z%rZvb)iH*i+HCHugiqei=zd69!)OL1i(Sn*gD-cig+lM@!v@*#pKOo~mb7yo7Tec6uiKdrTG3FyqF&K8)%cxVL zmC^vE6sw&Zu{O9B@u<9bb_w-CPk8vv8uYQGmEUY`j6M;ee)9vyJUUa`Z??CWJDIqO ziMMxd3o01%E#g5!GDbg6QCiC3GE!_>zp2&1o+VmP2@+;nDSn``x>RJ&)5ViwwQ`In zt;xSw?3n74PoSOfb*Zx7Q{j$n9FNuQ=EquDsvUG_;}9EmPq_+B6El5 z%NelZVWO3-Rgpa7glVrIj;lxxC6AD_tK60H=n_%wpIAzA1BYkp|8F$#@-5GjKPDM- z4fJ#HW@0-d+XO$jj=AZusdz*-Y}y`e3v(RUR5(8@Dd%ywge8}8iqc+u6PLWSO;<5T zT2UUJCq`RiF6vkEsG&X?fi7MiHJbj>%Jq zWz)V>PTHL(OuPI_hp*W^t3}Hj0WX$o9I_v^Y3Wb*m6L~*E%Q2ew#nm%Xj)z=yF)VV zKaduGDw2m`UVLJCq+fZXkk|i}O}lH2Z55@-=X6%XZ z98Z@&Idm9ix%^D4j<9|cgS*#qGWS<#zj99g`)I${veoj?e(CgT$GnI!MG$EaTSVL+ zn0d%~`VXUjk+Fw*hH|tGaduwU>UElg6XBmw38Fk6>O)-h(=#QCH-ZH{my7g?^fADnnbL37*xTwZzCA5&PX%GfVOeQbID;x{v~Cz{4uzxlX@y(&5vy)tRw zm?qV5x1N#p1FwHBYmK>_+l6_um)LK}2DBOdDgXM^Bb$5 z-}J0!cSn1g-mIEFjTw|&uby0IPg5(;Z;sE`!5hG=zoh9^bX{Eem!3bD^%BqGYfD+& zF^*dIX7pTga)$MdvQoUBOQZ~F9i4A^eVx}<*ZrU5D^g15OCHwDyxdpr*flfDFb8=Q zxrNQ(@mN7)0sE)#BO{%Qbht0L_s(siDzycs$*M?2cRuvcxnF-n}D#x4&JJbG%=$#GrexBd7I4W->l9{S^Jt!z0Gh}+-liwT$ zs#Q+S$k^?bocdEq>sjdY?c(izb7LH?Dy0^5{@k>(XmH}0v^PHx$zEttKM-uMwc>+`Oo7NX9j-WH+G+p`8g>3Zx& ztiyR@fLtBZb+XL#4`9t)W(uXpvVXUbr?c3~yLzJQ^YTk>tsCtdo+UjST+z+WppLgj z*(*pX2VHl2YXG7Pd2D&Q{CnB{t@gbdwA#ycMQ66X*9K`UD{l_sjb-A0$IsV14Wu{g z3&|n#GscO5jK0JDkC8wUTEN`tX?T8yQAdoXgv!>!eC9~|6J2jfzVtj}*|gpj}QRfxZI0+LqI5$!|vdHjGCCF07K z{LX*1n%^Xq*j*b`=cHBGcqfV3y>wDr4?teX48=P7BaHT<-k#+=y!!?}FSg@RxBiZK zlSA)0M`js*Qywa>{d)1rlT3a<@0TpUf1z@mxc~c~U4VN2&p6(8j1{tjiTX*ok4Wv~#BK0@bsk;k|Fg#b zta1EUA*0p*eB*z<@ju^?^AG+vkBGi%`~QJpD|=z{OFrFg(VP7&^6#W+do7TrUWkgZs{ca_rw|{7n$js zVw&j>i8Ce+$ZsxkclSoG!QUUi?pqEe`;~VsUZqT^w|?)f5QOW?(<|l8<#DC1_ulO_ zHl7(kErcN%cP%hGl=XvqF(>p^fV?YuxytfZfR^ft63rN8eduP`r9Nv~-j#?~Ht&j= z+Cq6($KDm+iSA+Gc^qgT%d^~(xZ;yt=vLl|?^lQ=J^OiDow3Y%vDL3^tCdpH{Q1Q9 zamthIS)6uGHDwDm#F;g8;!gRsRU3O2>De2{yp6eh3$CNCD5dQD9)LQMolahPf7bf2 zD?4ScVZEz&v8ku2=R=;C*tY8F=a@For7i11Yb_36cb}*?JC)o{=CN3Tg{?YrZucNx zS}}x8&>H2My_@=J61?tAjz_xmV$=)mQsV8x>X!I3t#XRaWTEoB?o@Ox&n^`pkIt-d z%)i@3Je%%r%HDwEeN9%zsE+XT1tg31-=g;E2^7a9puNenBk!ZmrIgK3-T`U@S+}J< zF~gYalw+T5Ie@(tFF1pM>cwp%hhTpZ{6N{owOc%56F`& zV_A8ARW>c%YopcpQ%NhO^iq^p%JO{3wrpp;V6vLa0VczM0Iw+HA%eX-y4 z3d*1LB31nknwjeklX`W2oycbpl7C* zfy@7Y__`A~pQ`_V;OE?XKcBlGOGuI}N=6|wV7q&i#zv@Bi!Z_&t8}eR`jB?sD$A=bU@*`HUCK{lD&| z_;`+rSCk3y%-QCa_mennTm@6#5&pm(tK zQhYvM&$3IAFXBG|q%Yj)JGo-w>jC=Pxnkn~N|9gWIH|u;)JJ?7a?URbTH5l9hG@s> z3 nXCD8rNJ0Ov+-S%3zm7s{rvEyESy=toHniUQuOeu}^j}BOhUvf1jPPIc(1vMq z(O&7l{(eONrMK!swZDG9M8A8!8m8N!&(*({$9;y{Q=Q_=|1-tc|4WMW|1-t6|4WMf z|7VKB|4WKv|7VJy{+ASIVfn=n6JL(2ze?kL{DZ$9Y|(>0S7#RB8Wz=ZJon5Z}4+Icka43M>2-c&{$r zA;4ew7Ewu3ft8TTDt3jE=$PyOBL>wRiIc@x^pC5lRHaxN;aY1535hmQ(hA~;#3<w7@wOJH7o@k6m0Cn1TEgBa9jh`Z zIU`3wC{;0$t|VTJt|G+%6?2vI2@jECIyR`7xXbw*!hP)x6_biS8TLZ7Ss?`NMRAd% zgc4_jCB;a@ln{?;5!JEMAnjL4QA)`L8HY1C4lyM~RgO7y1Yh_lX{6+g?1Y%oNcE&> zsU$wLCOj!#)2X6lK$OfxK2M4TN-ntXVoj>~kWyl)j&VMQyrX25l5}SaWHH*Vr^Py! zeUSGM^R!6gRFOPVtb&vlyI7JSYn6P*sg^=ELSn>mmd_wxDfyLCjjb<5I;4!a$}zJb z-zvGOW5knqGw6U4r=&WnVnq!+!-YH}+9*jE$1oZkY6$VHh+`RzmXN*~a`-N1d;lq|WU7)gZvJ{gZDBPrn`IVnb2YJ$rSK@c zTY~dmO)O*S4rzq8t(y3VWhCC5p*^iGK4aMgp*^iGwrG*#uoop!dJSRWeukd4{31@r zyeI}~aSx5alRf-fO%bOgBH|H4$ET*CHTuxj2|Du6BA=S#919(J`Zdzpq6$Vg6w^Ky zE4x7IiK29QKoW4AN$OL@Q_=-Bstu%p7{)?JzP*y=EN566iCbEnjxEt@A$?=fwT#Ng zxq(#*dLdO~@rDxJYSA_~7I9HREmC8#NJ)k>7VDEztJOrj$1(##F-^p3mck3LCX9>i z7VBA_g%q|(zG7+ivOa^&M4FNWCxWlm`(t~WiSs%}#G?1}wL@~5Q?-Q9UNjR|m83i0 z}qrmRGn&H4`>^A2@^Vb$myx%gV`epeeq$Qu*XziQ~4VnJCE; zi`so8Or<}gMJ~j*)?-wvax6;_GxtS`d4W?k;If*F+AO0W@krHNG-f%D{i61{g=o!^ z6D{^s#Iz6{SkAwNI~1g)c#UOyS$sjSWR#M4=kQPH8!DN=@)Km4l4)8p+tpU$T_p*s zRbPRaR$?{FP77xl(pqd#lHvIGi#B3A%UhH5ac(0rv!z>Mjv`h`x~PTrrvg%S!s@?NR=Q{jp?o@vkxJs7*0{d*BA>2eq7r>w z(0!+?_)d$n|f>pnmaaff9y zgs!F@!YWH!nBaVcnn3yV6xmt!K`5V|q5#Xlrl{2@y_aaoGJ)lFF^Oe*ORVXGnBHPK zOH0I1&b`HaEn*qkguhhGF_z6}qi#d`h%)8W_NW+dJl@?DgIOr10A!%p8x`{`WT?nq zUZf?DUjJBXLS;N z-am(=h%!+^E#v}GO^fsM9eoTJh`KC)Lg?%)5KUR~;=5OBGZ%_BETtiImKTanEF1Ce zD8(!iJy|j!6thSSWa)!5xMjQ$i^W?k#w)t?#bT@y-H+O-V&-tni5@!Vcg13s%Mi-> zUGYB4JzSMXkZOten8mN*OT_0aehptDwzBxWuT+u2;`hE%#Q~Pem8AFyIWHAQS(-r3 zC^^lt_7z?FGI5cm3#VEpuCa{fRLjJ_EW_{WJKB3fqTbMzG8mM?4TeP1r} zuzAqP#vE1fs@O|+Fi#Q6{odfmVlBt`y}^&g7h2Rk zg?ineh>Kdp-wkxlTqE+LMC2o$#Fcjw+p|Vgi4y9It`TjO#H$!|dc<1skrrp$7(JF* zCq84@3+as<*NH7kBI0#JpPkRdMD(%HZiwR8(;nzKd?wPBB#XGQLgX{BQlThNmtvCD zI3_2``dkcFk|grtyg!FlV53-|C37^oQLIrC^pQ4-FQbHN!bY(pO6Y#ENgRxl{3z>7 zaUx0{hinmr>e2S-aqd%)uSKeophuJ@XpP?JeoLA-$T6py=zdF@xWO@gPbN*=(=n3Q{@5W+G-C0`4r!vl5}ZMdT%N%erinRGLSvaUk zq6Sb|{lr_r=I?y@w8P_f^L zP(vy!-Z_e^fUd9YA_vO_2*qp{1(Zb8_Rw7>U9@5G``8(xXCuld=;iJdgIWAu?oP2^ zNkV3idY3rK;*TPCiE}LeC~}v$q9hoxek*QqjNfK{D_Xv!OW(v}ncZRz%N|Gw|F~Q1 zWbymhd&B`2zmL5~Tw_U{tXrGC;ugyW2px^RLZYumd-@yB-p3Bc`NC!S5AvCkJW4LO zBT;9m-@9KFR1$F#A=Jy=FP>0xM*98U1EP$QVBCFBRAlkT-3LV-7QgrYooLMBkFviL zty%n0_IIMI7V!h_1vD-=Bzm*>0&h6JQ|yy5(UtsqJ9UB=xJ7)nzwlw`CJrdlmzoO7sYQ%(#6qKeZDS=yGk;|Yt?Ynvtb2XQJ@)>btZFs`kUy= z;*U>%7hh}19G_kids+Mu^d)hG#UC$U5+_;w@$w~cp2Z(8Ulvzb{PFT-@ehkXPProf zRT9Y@X0B8xxry(03q(EH_&t^N=_wYdH`?hi45#UES!A>L&1$5wxc zu`K@B>Z(X!@yAwI#Y`4|By~+JSCS$^7^Aht*|{e6vgE6UQ3B*oQK%(tVTvH>1^G+# zR+6M-2;{ohtR%Q!+!Q}Y$vDK^6n|)Os#cX^66BV+!_pW+F}H+8%^B{VDZ_=J+VZc+ z%~EfqFNIlN&Wwpsk}j4M!zfavYQ&N~SqRq?;+}{^NhKR=Sc(}e-#>%rl!&=6=Cka1 z4y&!Y*bsR|NrniQ$0}n;C1tMGw1pWWKZ_xID~X8xNHrg&n{tSf;An*8*eJ<`^dUJ{ ziSEHJL8`D^tMgI4iw_{SJf~BMTzU0;ohu7r9)XSz$tQ?$Wt%A32=U}(CFx=Z=IgdW zvdc7%If1_9Zb%M!E=rC;a!RKS<(w|IBjz_q9@#-jf}m8lA^GGmB@rdrCtzJOc}z*N zsMZMm7D!=Pu`T73AzEMyzeFFXn55r~p;n?BMB=aFvSoXch-jJv&*_n>xNO@&OM{S} z&nzL=D#;M*QuT~kNqLB+650gU!Yq|Mqa0$k7IT%dCrg2vLi~=H>TBtfbEhSZSTm86SN zm`A#SYq^%(tHo)Sh_e$N{)`c~{3|-eH@{n%!x8 z^qrcXGBlO#lmvI`rm_de+&YcfDU{VzTCY*6cxQcfDLO-%$Oh%Z)XskSbQzXX%xw_cT_v zX7T&av9b%xxVe5zf0h?AWhBc^++}u!Ddz;1FGuOK6DyNhWOw}jkt#ivrP0H>A08{e zW@)>~FKaK$H*R$5Kvg+&l_@s^(B2kszh@~mU zJv8$lE8k%0$zuoX1xs&?Wpr6{w1_we{U%AQ+`{tLNnJPE%DCQCRz%dn)wBRH?c{Gt z;+?B#??u0`ZVJ`!d0kQZ5~<==&v9pd8Qg>lB^Oo03*drft3hp?uzy zi&*yazP}|`uw3AMe@kv)8HzV=ZR9*$ZfBVZ$*$x(mX8MM^EE=AV%Y(qvPQ_uEVqa0 zn33{dmN3p2#f+5p0Q^5};kbAmGfL)VnF^tpQL;G8X5O#Sl2$FJRNwJ_jg~c7KCGzs zJx(@e`4mF=#L2cSrP20MA7G5^$?^h(V#dffST;1#F=OR;mfaAF87pV7j5(&S+;MUd z%M=KWpvTFTEaT_k-6fpm@p1#pJjiQ^886dWUg@P#&FC8d6^}J zVwG538Y~GA zikT#vuzble39>EAevV0yy;!p0{zcpKwtR!7D1`EPTSi#?JJDo0gJoQzK8BO!VwSr1 z^l_ddSF&`0(D9ifH?sKmucd=7pDVAh_na?ZWcd_A`OKG1S+;zn>)`^~ zj%7E5_G^KBoyG5OER@4oDxwEM`7D$ZSgzL7Wi663S?)q8W|4e{C0B7BvskWTDFvaJ z#c~tN3T`*vkvmv6a=Y=4Jj_xpf)OH)*t_yH%PWvFO8#KkK26uvCGrl-DM&7qwM4o@ z@c(dB4?m}4Qe}RYGZ2bNl_h-Pp4C!Wp5+#Vwt1(jp2^lwy~XYb^9U zm*fMPZzvLDTtIIh-Er}Lqb$Kf&#CDRr4_Q3l6Vouc_&#VJF|*3PT<~DTggq9Q!H!cqr)hl1SeyHzJq)!pJJi? zYKT;yQl||6WrRYn;Wr|(NKVMBkae=GN;Of&TPa@>Q z<0C#S8^VU zzlB@n3MCQIZUN>|a$=re(l7I3-_MQkNlct+%r;)!qM0xGg$n$NA}2lEI*XN zC`rW}VHt#Zwu>s~6Iz_=b#(3BBdPaGweA2?wZdO}4{D6EeN}fXdazIk8_RkuB*A+$ZmWGqWj+^s0)xa`659E4(y z%K&dx*086TA7wmCSuX2@oW)X`%Q_(!C`lJvu@{9s%;C#*EGMze3$PbI z$vrGr24YfT{`Ks(>Vh)HU zLw3Nw&yVQ$>?tNgQrmL~|My2`OjG=?cUoW``+wx~cV-Ob^E3YMMy60c7w~^KGsVy6 zj=YFoyI-ShRMxxlh8Cv*W}oN`;#KXbS!0AjD@GwzW{hPNQj#Io^ww9LWwd4aw1Mt1 zT1Fg8AB!DjAI;Av9^w}jS{$zqSq`)D8?~HD2a#~J<*G+hxX1G zfqp8Dpnt4|&BfWwW*kx`&MeT130M zxGGWl!^Tf6#lw1g@*1=@368TMp*w6o<37jiSc#f|RQZiOGjt3|ZtVLbMlqJRAo(E$ zjEYh6801l-u96J#B92B;NI|0`$B>kU6fs6~Oy3gttuM?VlrUy+3`s@Alr-MqnA>eJ z9}aoa*ue5}EczqZ!WiRQPDN4|F=dQn9J9Ry-jzViGsY#3A!&@5XN}u_Om4hokR3D9 zMvj?!Pq#zph?O%6DajBY7e6yzm5oVSoZ4sf^O49Q@`R5OmT9PEYf86nk;OHncwQp32- za(50!hmaSI9JBNhBT0nRGzzgKp@B+<)H2FO$wEkN<3(TYpw|hhW3-Br6_C0{SC%E% zuQiZ*#=t1q0I6?`Vd-Pz?Q2K_V_KAKhcq-6D#;M3rW8Ns!tY5MA9D=Jw}^Sk*upVG zG5`Dq&V6HJH^-11L`)NdekYu+6-6~~a!9p`1ED@!u!^F>G- z7Kz*cc^rpK51hD9Lb&aI4kcIH*M& zoT#4;v^V}?`H*M++Z)+u>m#-1wR! znAVWWO1|P4|9!S@#zB^DFQdLArkin$WhA7bk~1v+Jb!oNt`<=m_mozM>26G%PkRwB zzUCv=*XaL_64if310ebu ze^$qnETkG~Y*&(z`8;m4u}8-^{`~4_!&xfuU%EJzU-#AHj9M&55nCVs7H3Rk`F)Xo zt6_{WOG!i>jlC#qjB%2sK%9QcIo2q$jB<{5)=K;i8)C*9PqFyVLB<+oSvKHqOf!LF zjmkc2;On&70U=LCpRa{6h^8X1S9qy}P=jO@#GK6}w(x5A&Zji*@-KB4<0vyJjB#~~pV zQX522W3qX|nGV`1u@&M{tQc?d!=bBvBGck1exxke8btDcUTYYbqi zf!+n>lVZHd(g;HNq!?pa=r?XCW}cD2QWHWk^Ng7+eK}^nF^^>w$ILfUS?Kp^D4zw! zCoJtbW`U8WMcqC3hcQQF6nY;8;9igiEkD($g+_fPDNd)?v8E=D^FpI5$82tmugQ_~ zB4Z{Cy-iPLEjH3P)eZd5W=OTz_>M)Q)gpP_U2X6>}*@UiyE?X9o6k9sXKlJi@X)B=IgJNnw_T=;?h8 zS!Mh})hC!>zx41Q_!XIYB-J02^D z&Bk~odTibk$8d}BI_6pE${mX1GX#=m3}T^g`i3isXb~ZlRlOj^Br6H(Semg|Nigb9 zGg_{uoP+0a+l+2Xbj&8y%xy-Tk_6``zISgoCbE2$DTz_3#$bE48*4cx=Y3tnGmK3v zleqR~7-=kDLM9?rhOvv~K*UdVkYzm1GS&GEKLjwz!>-5rn>VyY=g5p7=9Z;zZY>ak4y5$Cidu3_V zStGnwm%g}z{wm=YgZ_8)EQBw57UFNDI?pkB7UB-%*G%ze4=x!G>73Ozqxy);Mu8}y z+1<;=peS*1W&UA|ijv%rYer&}6omY3{2nDGAUBPtKBcYBaGpj(mrxyjR zVr=sbmg8tt={j}H(JW^nA;dW5M3yhqIGmEGLM;(zs=-7NAM{sAq(FF5j4H*%QOl<2o~ z=q>jgW?d!0TRJ(+CQ9PP?b#Tu;(X;W+bGd(3w>wzkePEm<*cOvVse_LmBc$A;tbOH z%57F=*@n`!)MPmXp*_uQHewlvvZ!UtW42ThaTY=7h~+Wcam?&|`c3$U&2B8KAoOkE z!)AXiqBm}E&9Oas%^lm27^8_XQ!#S_$!}g(5>e6?Qou~yMKQ_Z7nDWU@?++vZ%LBG zN0acR4>5(z%PfD)!TS`D!e;7j9djOc{Na$IW{o{coB^1(84oFLHdT@=TBD_ztfb9e zib)phvS}%4Hs430+g>_5rOaVU64bYWbCIf)xk5>X66%wbHjhWeEI~|+`EQi0fRr(v z{gkuLXC35Ovw)He(QbkeUqQ;7y*cJ>j3Uz^&zXxkhQ2za{#ZqGTa?h;*YoB<7Wxh` z2{Dz-(=7hii&e}<2dH%YWnyXkRmE(tkNrs@WAm7C{zi2km z;ynGmu9>yW<}ChqnzhVUEHz^Nm^PdbJ!|WYGHRK_Sk86RJ@eXTiSMZN3_+3$f7LN7 zDoGb}u&3FuUvg?37%vW>&fkDEGZU3W#KlTj=O5D2j5|y*$>POl(Hc*| z?27r5lAw3d#{4x(Xir}?uPI5$d}k@vyv0IqC{ddkYpO0O?!s$u`P20tYdScZbYJ%0 zEQ&RADbd#j&G^Kc&-*dRrxl_*m^E1DL8v9^U^ZZ(?=C>G-I`7 z-YaSLKkHrC7kwAbkJ7tvjJ^vOhIGpm|1R9q?5c9k5H9B4v7@4wIhCbwZQM)ilB`e? z5hV0Y&Fkh}jyZ8lx6ggdCs8A*{-jsasrs1plth$Js=nrYj%kppTgd+AE|#Z|ir!Kl zU|wdSoayWgG7I4f@pGn_!DcO%$>=px%usVM%XH4?4Rel?i0F)ME`xo4)4Zi5!SUyR zMw$Xw8tw&|t4WMBT_wR+Eu+lbN-~^2_i-H~pHXHZ9h3Rh#wfFgj>&wdbhJ59NqpvP z&uDX(l8ESrEu{IMG3H~(sB~RhY9Z%wW*a3DaUAfe|7jZZzA(S!GeBuO^74$`CnK4mP0jXx0b+o9msuaL=ZZ>9FgPCo*PLs{n zEPm<9W_u+mPAiPxU&IJ7*&MA!B#y+f!Jf`Fr?IR-jhYXcZ+^0k?7V2nWSRLX%UB%eLy-5)T`WDH zl;SkxL$lObo$5K9olB69OmR-j%9UtoA*;=DQF0gZvDuG>p3u2dsZAWk(seodEReNk zB1=W|E}np_GY_(aaF;0y`P>xeby<5bFHu2BZ&4Q;YM*G(1m%>@@GPJk2Aroo0bwsq_SA1CAl}-glZ+lw6QUQ}wmH(;Ub# z{rX9fhSGPMZ?VKd4ncOA^rZpi<6mFjn)IasiC^b;o10ktI=|aIq9t?Pp511Siz-#- z9b~szKuL=DrWwwAGmNRtmMoD}%!y*Z_L#j{=(l-D_L+@-qkK|CbB;M+c2$xj7UN2x znD5M{zf(+t<2J%?ghCFRZIncu;*b<2opg-ao@J0D<}4*iq7G{28pu&|i;@iWg!hHV zFoHIVUZR{c1l78pFJgpjHf5Pm4NpZ7bJkqTLiKPuVlJ4+wK%UK72Sn@Gf%R#yzR@+ zETxf3$DC!k__Mw*|7QNia`{n=-@}y8HI{!Nbf^B!ys1UBMk>0Ne>eYSiA7nPuwTEM z;xDVbl)g&GI{GA~5BOb=cOZF3bR^+}}{ZAe9`;We?VtEhp2ui_K=mAg|?>y^2uqHh?4G*Jl5hU=?8h(+Hy0tETk8wHjDQ&f6F`mO$hf3OV zs&ZBFWB?LlZDFYmNryaZ<@%3G*KNyTNI9!N%d`@D7OR4l$dUn}tDu6ln#IL=zlc<{ zE)V^ekz)$pSErcgGv!?fot;bxQOkA{sVey~p%W0=o+_E*?>nvUL;q!j{C&@q9R9vn z^J8-O`<^Kwf8VS7F(H57Yg+f!Ug&c{S3zydxld&!IQu&5tGkYsOG%2DhaRmSuN)@>I5sJ5|8%;i!2@sH{&RskguarOm_R#8^0b&X{gjx$MHt2$;RDV2ZZ z+gXW9Fit@Xz4zb2I;te7s~xRdoa$(*z7KY?vS-uz_~)yWm5;?gU!ANXS~BZuXR8v6 zf4(|fy*QPBzPeZwSp4(V-I}i?g10nu8{Wg(s6=let&Y;e`i)chTiDb3M@!~0>}mbS z;vd7FmP3z%qK;uNt1XLve0p2Um0-ROm6?uVUn?#LrHY6N*|hYt(pZd#F@8l%e=GY# zI!2;rK}U6<6{`e&Nc=CFa~NcGVWH`Hu%3w$1A8*es-Yyo2{+Mwq_?d4EFZM=rKJ+RJ?|pt zx2%a#vJx`fvT_AwZHB~Id6gtMGl%LnVXRe%B_~D-t*|{~trALf&fAb`tksQU{Npp; z8qPvrWb8xCcx#-Ji25Rf-s>B0P3D;Pr*#h`V$I^1eh|7BM63lIb729V&;8G>;WjV~rz<&{L7v5;bjo@iB7k|L`9fVB#+1(U3nI!1{x9pCR*L@ zNn-zxxRXOBTQ4gK#;{YYj#|W;m-Jm`ik1CgN|oZ!=r}J@O|c$U5{y`%AQy4g(|(4lFyXrvOXV=Z+xu1N|M9|j9i)^Cds;% zPp4Xdr+A$qDVCEz$Y&sAp|v_nB9K&TrxJX@*HDVdkfqiE7WyWguH~iHQI_ZG>-}14 z{mjy#lD^)TTEDV1PtyCf)ViibUl%h}&W-RS!@rgnK;E<7RFdG_#{Z(@v%(s$M5p=y zy29ETl`0p~ue5e@DsPzXKd-V5u-NDcQ;&L;b(Fm@=S^V?9%KDY%U@`r4X_a-A zr9av++MZR`EhV^np26Dz*q&8Z6ZE9%?nx~ht(UjjdX=T`M7+@oS!?xBlBC9ihasO? zCGky#zwc)to2>Rqf||M2ny4hGy<4qm|08C(j!~swK|WipD-Wbfv#vjoD$Vi=scp`@ zF4C;rN`lhUtWru6oCAE8)2wnVe(RTJRbjcq*G`&MONl-j|6+U6tZ7PgU2Rhqvl-TM zol3Rg)(rHMtP4sm$dcvoEes?hvv2cnQ}nMPJ2Pe3Pv`|hc4dm6YOi&bOW%O@=PAVO zvs_$7bav?7$?}lztl}(5$(SjH9I>jgY=F=^!$+-`S#CAQ)dV?i^=CPXduA-;Cu<_h z7QC58^0T#=r3$XNE|4?U=PWr!;(iJ_XYFG-+)T>_>pV+0Tq(T~bJ6T0CF>E) zkkVevDy8KQs~pRtD18W0{b`M2S&!1Gtb5jMmbKOJ6c2LWTE#NxEvd>fLfctVSVEzb zem*Qt=myJ&OLXbkLJt+u+taAI-iwDqPqKt+pfy5Sxk5Erp5eX78*0l^7TZjF`bcO9 zOQ%P4p3Hn-_+PvJ{5Up1u=$fu+q% zeN>l*TCwy%JN624elIkLWh8`hUKx6uWi!qxor~3>B`j5&YWXCzk%eY4DQ0cx086Pj zUHZDvMV2iibW5@!Brw9HZLW>$y&Fp36e`H_C+>$-`j*ghEQ1l#4>4bdny?H&KEol~ zL$9;^g#P#h$gWU4%lhHC3q$sW-eEZbp|kT{Xd}yg^lfRIzYiT?S;jT$Xy_u#?06hw zr1~)=F(#*NE`;{`CQ|(rdWhcO#V+dK?y9f=h# z%u*k+7OBGFCM>&foT;|B;T|k~n(La7BOJ$arWtyJNR=y`!m<)QD?0Ld!|PZcc?wT7 zAdiOkvMgw$>&D~Zb1Zcl>bhDae2?YBdiv}X4^w*D<`vxfJsB>?vI8-6#XTK49hX}zDXK|>#}^pF^$6=SSqkI4Zp!c zePl}2JUophouy^?J(h8(^RySO!<$+DtF6yhoA6{C=iDV+k!2aiG*ni%a8s7jll4)3E!>kO4Sj%}I2yge<5+6rh>`RSFJSoz z_wer^1HYPz|tGrL)$zme4phi$|4yPE>K$U#Z{@daD2EN zOBj1f+dLuMh-EtO>BMkXmXFXwqux$JcqB_#w6B!&l<;hpaj2oxt|o>*VtE)>3gtXA zyq$&m?5D6_N#UPaVzABBM@k9bVhKa2wk!62#_*YB zS-3sRao*GAVfqG!YIs^ho$7<|RF(p0wSGfcE5aYJ7|qf0K|T&|V_D9r)`d?h33|Ef z!}aP?&U(J(I#R65|p(i+>-P0d)-^Yu@9vBD%>?n@*(H1 z!hdVYeD1J4e22wSW|q!q`IC{)`S5lY|A?Ir?_t^eww|Fn zA3n@-<9q$Y@O=0L%fc3Ve(Zes7Z(3GpATQsqPAIYq18<91+6c-K$SjBiQd!R*u%@= zgIdI#Zc@;w>`M5N<|>sMdwvD^BV1I;1vzvi&YqG#l?3(hukgQ0f_nH@IE48u9974v zt82nv;T$afJlkL4d@M&=>h+`k3K!KPMxiyPG2vg~6eaOa<(c~U{2N}OBqH*k!CW_D z{te&dRL^2hZ$fT|TOvo4F6u$(9hke} z(i(S%?vT9piZ(hX24xL^JYrv0qOXgQkVoyyuj%$m4cXmU|dQk`%VPvi!zU#2%(3MbyBSE$mnPdFF8dBUI&$5H%346JcWYHZ$IhU{tv{zfG<{Um#f`!Xy zo5iiucvAyX($1zNNfg2L^$nzy{eq6k{Nky!U7N*U`?0kB5{tj$R%yExO9(9it-n#) zZpY%U+!bSYWAWeIi?RE#_^Wco*h5%O_0-pUj6IUYUu`VLjV(cWA z)bY5tqb|nSw7LqNFMs8(7<(y;zfM<-y^_UW1uMq>l%+97h|~ha*k7<%7=LNm#^SH# z6=Q$P;;-BlV}Hj|3H6y)nTxTHv(PG7dvWAr>@zH5j_DRD#{P}PU*Rgo{*%RD&nm|L zmxWfHqPs(kEz$1Kp3)jqT3i-dQA$g07Fs__%cCs*YEdzEF_yab{Ox&~#a{_3#xBp| zulp2ZzrfzhV;V4~xG(66z0&zZw$i4~xI{5$X@imXGwa3Dh4Je_bQg9~OUwBGexi zdRwr39#wx>szvnbLoxO?7JscF)E^dqWg*lb7Jr3C)E^eA*R62AQGZzcHG)uoSo{@% zP=8pipw`jMHtG*c827qzDjy4N0o9)x=p9hqC}a0lqT9@)sH;){9AeaUbRqSOf9b4*{7wlC^B2GUD z#k^pD#xbotXAWPrSb)#-(KBpleY-=F?*{SEpgg!)T zL%-w|$eB2us=gnSS&~hXYoYilSeS_wtKLAfbDr0=c0i<4Er2i4;%XV zg#4aWASK3N79FXY*l{YAe#-DPq^TXRD2pX>P|V39g;y_G?OlzF%{Dgpy!;Xq^uFFC&Nl`(G_G#s4LOK>YSOkm1$! zZ@LANzk~krC6GVS0?;pTWlCsEC;jVMnUcdlr-AstO^_*}ZFTj#bD5H(H+mxUYhNuh zw|OyyzOo7APG$WIT&*%=%2(09z!iv%Sw8v&uGX0`=gaERXluJ@A3Dx>`w{)AvN)$f zjKAv3%YLfRs@k~v5fjKOV|1+xgw~U(tzu59ob}mh2x((qQgXrdbAH9XrA7KVzhZlR zX?re6Kj&BMM_BxvU$LK5qH}JJoL{x8DACW7UV+5gU3IF=vfA1MI3K^Pw)Q9%zpS?Q z+bn)rZS52#x~$H~xt+aUi7u-jq=Q|epW5ckvO3yjv}EpANBad9zpRdSTNb~pj&`Dw z6j1;z?J(rr$zH-zl%=!1j^&wFI;M-ggC&lot4+V=tIp|2DMlloZg#%@T8_-cGjqsm zb}5!m=4k0@S7RxeqUCkFIZIXa!)bf^*gaU9vh=m%lmu60KYPspDl1u(!X0foQuVWc zWQllM`rDy_6q6)giRiW92H4q^q=@=>vQOWe4X}GC(MLWXq6gUv22rX6=TAIApg!af zdnwCgY=xH9EdD5ah;0wnsT!hY(p!r|?3^s^A@uIk5W4`&_b=&RbQ)?GWx2@mhFywf zWDEQXC$?~yT~3R-Q&Snk>~>MITEz_FRBxb7*bI5g9pC&!$R{L6ccCvrbTV@CCC{2KF81&(szdA zY|#pvxuDtqadtT^VkPb^6g}Ra!_pn2S{k2@w@)ic5?e6i5yJDfh;3mU zh%+dz;r_3_2eI3-(EHD*z+$33mc?HeYoa|TO8&+uX_9@BB|q}XhMeEFtK&XOW8odO z@m3>bsy$ChLgsozGwj7$GFQQxVHZXJfVSsT%$(3VIWz5BEdJU@v+T8R>KF^XuX4ya z$-by1b6u?2wuS3~YNo&D)oi<#mdy36X4_w|EJVkqGV)2b&vB|RpTrw45t7R+CZ40# zP%+n)M4S?kx=LuAOlA2i!_2WgjHOBZyUZN>5f*=qmpOI`md@|t9X;eT$1bBJ;tYhe zRPsE>yc3I=0mxjt4vYWhLyFy)Q`Nv3pngk={W8bUn<|v^Ji7zOtbtI@^XwiR(*pfF zI?MCzM3!Z!nOf#>sx%1oA?MqvI!4viZpdeWT>`xvI$!7T_DCPdB0CX%8WMkHs>OD7 zjOa-Gb;I7VN1(Sv;;#VquDuxdEfRm-uqAfdSS|hvV5xRn>VP7~U+?REdoqi^y4MHx zX%>HVuaE4w@j8{ime*=~G1@rFht}z$mAgK+PfpO{uhaF3jU|p%jK4n88oR_KE&ggq zYwdaoTKu(-KDB$lt;Js{Yn}b*WG()>SfAPDSW->BqSSgjnZ;il>T`QJi@y@o20Paj zoezEUN9#Uqv`4U9L?4@0dD>)WpQ>a0^|ijRYq9vNX?uc1bO2-P8s6$F26WQ9`TCeQnoK5^>6+M$xK4X?9~4|0+nc+iDTB zJY84Q>_Rj!!+mE{XRMut`m@cBDA6q&{Sw>`dzO-jXq!ulv53jAFV4`Z1~$g;-9UEQ zdy}-JHqf%m-n*CtzYd8ODG@Q>+ST6)WCdiuoyxMI7WN5p$Uew&3GZ)`{9vokVv$d$ znp%Fe%e_naBnbMIiUWu_WzSI(5z7(t3*;Aj@p4_(tH_z;qMiMHEwec0iXF%DOdTEb zmwlO~DaYKjJAR;3Ey4CsK6mZCEXy%}NFtpTD|E~VjtM(?Khp9#OEzaB%S>!DrOM@8 zXE~3%14%xo$7-D_hPSz(bA;t6Z%+}Y-p4wo8cPXhJuIONCpuw^7HKoT{g@o~0(I>g$x)q+>qhm_g1QmQ5_f9P0}m zbA?llbcV5X;k_8^oKuo4zD6JF2F^vi)B8(GrLTg!kcrOtC^4yz=#<~AV$>`;^|hur zFR;-2emM~{#i`A53X)GrV;29HET%exS?Jd;=u(^Nj8GDBx_VGB+dRYB%ko!oKh+Ty5Bbp77BifmShjM^Oy|6kh;s}=F*BV%IHtuo-KU-9 z{KL`%f`(Sia_+Odf-^%$KFP7S(7A{>-60f{rB=nzWxt_n0K98TUCtc1!)Ue;^g{T%YH~V$Wo`b7AJ9<-uGqBFpi;Lne2_2WzIpC z&3*9=CggqR7K{8z=kuYHn5J{4UwEOvRyYT@Y4K--RygNa=3=PpZrv`EV!8=S7&DOE&F#nIRR`O-PaG6LJYAM&+RCSAvLf}DnY z<1A)5R2yvzWTzu`=+c*?bO+sQEiCynJlTzD&Q^|NebL{#>FiPxFCM`&R{GtgTTb;oI#mk#>N}D1zs{B@ z*$=txT;5AD!7uv=x6D2*{xACow?9j**1A3mcQuRu%Ra)r#Nz+5k8s6)U3!tGQtW>k zg}BQPXqksMoz9_j>E30T&`Ix?>GnRTV`|0;LBIcDy3ThbNg^lq;ua+27GwDx^_j$R ztFqL{i4-Xq-MMX8lAgjUHISU{d@Y&Z>=$rP9ip6*v63C$-$YCS_ae(5XfI!cJnptR zOfmW%M_>6BbvGSR;=FP~x2wh6G?rSpf3-%cV(u=MNAUEYTAO0-K_w|p_L=ylMHO>Y zNibJg%)Ox`m=!7JTHmXE&)nu>ZZ{=*HkF=k7jt{F{FH=u9Sf2SRuVk#F6MeiDIctY z)fm6(h|){CWt1d|lbVAGC#W?sKYI7|7Z z%b!_lxSLp9SNF_cbiZVIy|R{??iQA85Lz9smb;au@>}};UdK&ic>?u$D@w2HrYlJj zU!vAsMyp-Vy~}b0??u7NY2db1f|ZJg>Dt@KoqSGhv+$o6G;$MJ{O1KP zxd&PN=LL=3yyt_QcVK&(xaGAtVa%@{gfw@nYY~MyV5lgG!{RF&c*3y0IS0#==v)#%qt3=nRUyx5Lw~CI*oS|yvuK7!Cp}3Cw z@^30;OO#w!vYTbte7r#kY3-h4SqTZx!wj(7@o&97gYiB|PDmSfHOm&{TmbTldre8a zh#;m2)DGzDqzRZ${vZ_KlxjmFb)Hc_Jbaj_2NfBKT(-hLf z?Rt}PP7xb%$8QID-QD=FE-SSq){lbpalcg()aO3#_eyk~r<%~mJ*_0D3H{vWw<(|W z%$m^8?Zx8Pg#PX@7QZG8aE~a#YAtW+t6-p8=8oDz^)|~uY|kLKf|3mJ-%89KDyhY? zFgNDUAVb`iBr-|dMNAUp4Y#w339i#&ZZ9RlehqVnM#a#mc9@&0Bq-}m_xjyndln++ z;qDzJLCkR1xkoWUStH$CN`kURx`mYJT9=A^M!HWZ3Gx}~mU|$d(QcIo@)_;ci^}H% zM_hrx&u^9u+8J$H5c(c4TbEsu96D+#uF zygQf0FKdFkjD*Yj=3$JI+*K+jC~K0tPDxPKBzLP4UDi63HObwfBq(c=dx*s^>uvWm z3754AsiwFWRZLLURQH;apscCxT_w7#ZAdlMHUFbC7?d^D&BfxEmFPZB!e#A5su}Ln zDkdmvrdwV~P}WShh7w&Ctvfx_t*0a?Yo;sjQ|UoINv^9T$S29or$p!T1M*373n>Zm zNphcN@wYkItwh4x{437;T=zv46O@(WHc%3jmEyKmqRTpovQpf3N`kUd++HkxS@Ydt zBwQAK(Y(-&Q!znVi`n~L0PNZt}K38tKGpmAJt}NN2*WI&7i#q%3AB@RuYu87N37nj4rDHQmu7MC<)41 z>pt^9KI`1)AIN8&TTkbszOyTgeAcMm0f?E6-C4GGp7S{{q%JBUehH>sE)=WTA9k|5`8?p`H2=cY)t%{`(d$a$NaFGNQp z$Y;CzxRM~B?e5b`bUv+-&vv(*k|3Y$?vMxa+2M|SAfFxXq^Nw_BA*>@;sg2Ya2Jq7 z?deW;nTiRvaF@GENw9^x+zm?f7IsF?yWFpo1Y5Yv-AfXc^KN%fIP1ReaStmAa^B1DSBq-~=JC4OK>sL3CB&vP>&E2SCg0g;hzg7~I^}D-A zi7sm~%KF_sq$DWoclT!&zpTsdH4-k1`Zj;KkL94_6O?t;eL_i4)>XHx5?$6Zly%ju zq$DWos#}-EFY8aYB?*^BeVf1C(JCe=>$)4SBq-~;J5z}+YX!=>?xrXS%DV2p_dq`X zxF0=`&p+MFz_9ycJ54MIFpk?1NXN^5a{ z9ju=_qoslTI5R1KD~+kdDI)slBa~8IVtE3XW9HR-or=E1-(fu zCx6hX9`lk}YP_jSFXSy``QixX!;y1gZz;>3ZhA(jh_`~}+d5JdM$SdO&$YN8@$o6< zea%uD`$ZGs#k_qi^xn;MNOA8d3q50}IlbcEk6Pqt>=#XB7WdAuoP9#?*Aw1FmdV&J zOcjVHynk5M;5gGnT?sFgo6hnXIT?FFF;9BgwYc|8y**EP1z954LYn3&fIl~~^8qw$PaLrJ=*F$N`~jy>zuQxaTF&w5S&N6gFrBc`L0GwO{y z6oq`Q<=k_=rixdJV_Fy0F;%?kN`fQ*f>*0p)}!%)*OJ9QVpYBB z#k0m#^_sHyG1a_UPh^d$=Cx$;W2$@AOJt3y?loocV`_NmWwOT9@Q$$fF)w;~%Vmvu z(JR5?$JF%hJ`hvW%U)im@?&awX%({OQ_DNZ;>XnXvOk|SrnXmz#gD1uWv`MorjA#L z#gD1$6{?msrmk0p#gD1yWv`w!rk+=b#gD1)<*Ji4roLB{#gA#=)vK2^rh(Un#gA#| zRj;2lrlHrA#gA#^m1&qYrjb{j#gBQ(OK+4l<|Xe4iyzb2vsz@0Y3$`?@nf2JcOQsp z;$?5CQ~5DXy((?8=F`+`#Nx*^^PIL>W14vdSp1mg-jNPjW14&CSp1k4-sO&2V_JB3 zS^SumURvj@F)h9QN`gK>EAK}o!Ij&}JF6sK%(ZX_$83Bn?JeRnPYq_%U&5J@%U;Q&s*?T^~+wHZj>rsoXn=*4Qu1|Q=(Hw?d2Kzjyko zx0c19`G3{hq$J|ZK-)|21H9^`D@hmr)00@QVs|PlUHDH}+If?e1Z_)u&*_=<__X&5 zu=vNYgV(5c)|d`nTNXd2qj#iF)|igoITk;rlb1d)YfLBa2#X)n*~>dPYfNXa1dAWj z#j7$jYfKlf5sM$w)$2MeYfM*fFpD44%^UG%)|hVIWEMZByVv%utTEla-YkC1Yu>Km zSz})Fj$Gr+q(DQnCC&q~nZ#|-p#P0kuK&^yNB#|-kCPR$xK$ZM}e z@0YOvYYBRTl>~1)5A}v>$y_yYs5gm6h9W5#)Fmt>6@=WSJ@k8^gMdX28rp2{(f26kg6pqmKHDqGLcgT^ zG*Zp>QkJWj|Ht0@z}GcpedGJwd+xpGo^$i(-rQ7LL0cJGf|f8XVN!yYpe1NEXbBpE zmY^kQHE0Q1QW}Dk7>x;v&P;1EqcuTG&=T5`(h{^IXekYPk=);!Ceo|v!4aWi+!HG5*!)(F%)W^EgoY% zUhH-D7vOlY*V$WKj*7j`{^a+Gu|H&Yf#b#gux&ifGG6Qt+gaeq*iWX|AGXJWmOWuF0>_KJ$xeSF zG4>`q?s8P@P4+a&(K-K=eFr#R>`&YC!0}>#+HL|z#?E8z)AkZ@yx14n?cjJhzsTO; za#ZY#?0x@`nD%GwBf#-uf6hJz9543g?6bg;v7bdb|D0Xs>iL{%??mD(wkNwB{{BYw zK>8ktUGH+%)XNpnpX^6^=$S;J{$xMn%6vh)CYKZE&-N0R!(Rw_c~83gZNKJnxb2MH zi_RwOPH^UmA-@PQl|uc^&THayc{aab7l0#e=JnSL_DFDM()aJE-BG9)>|$^z?d~da znO(k^^=vfMYIK=h>2j3Ja(meGi89OW-Kh+qh#K&N57XS z^M-v6I9?rl)4muSua3QGUjfcc(M5qDpi+L*p6YVGrk%S-h_lMRE=kX7`_?2qtL?ko zP$OgXiy{>da-?p3IPqg`MyVd2WbhX(9>l0<#?9nbq$-HCd ze~>8ij$Pz(l+3$!&89?|ckKq3qh!|D`JX4stg%l3N9Gvcvs!DP1&;iEnLksy-m@>2 zGNP`OzJExZ_w5@cNB?HWdiz$FqrL;P-o6VQuXNVi4@eo^cGlaEf-_TGL@S_H(!AdO zgUk7h^7(Dzd|`q<8LIZCF(p7c$k zOou(y>=MK%6wuMxEv+(sXdG^W(PGF$9= zm!o91+SwV2GF$Cjm!o99w#Q~B%6x54ayd%o8@oI!QRW-F(&Z?bZT5tIi89;l$u399 zd}|l(ktp-6UF>p{%y;(my%J@YROcpeyt!zaa~n8bub$>C*n#sTI9_j@<}3k+ z`gvOCZYK-Voc1D)%ae&Z9pKCqzoj0V=Z8^;eiAWB#sX)im_vK`;rm&p@MM;mDbC)F zPO*rS=`04v8-;gsn(5O$bN@g!7az~=fUQV{lQ8N9VraKd5`a8`oN68FuhRjKn8Q>JS93?Z*S$ub* z%s{8bTH!=Q41lJ--F#4sg8Q=C@9(%TYVY!<_G2j*9(oC+kUQM(n4M zo+F$U;Is&JSO0fTpQmK({GEX_Nv6Qr)8!mPceeS93I)zSE=O5d;2a2!m(K;x;ox}r zT;LoH&P=g4g$+ul5-`7IWylKxsbJ84Hf5`7~#(%Tbz( zo!H+JHJ|SEV@?mvr#lBg#w*h^oZ(5K&TvM6Lno>#s z!0{}c(H53D16&zpVVQFvIG%+UJBNegS$MH?G&nQGl~fNO zBnvNg#`KV33(K7e;CL2Z>Xf=1W#OgHTau&m@KWbJS4QRGWzI%dMp<~7^CdW*g_k=& zfa6(sx#N3L*28NF9w!SgcN~{<2z4&c5@)g#2gjQcO>wsT9XY0V)uuS~!=PNsdgtv5 zXY?zHGFLd~faBS7rE@Vjo;_DOSAZjH-Crs8E1elVWH`sJa^`^J<=E9ujmuFvcD3VQ zf!KA9UF|rMqs#OfC+5ni9J_{6OuwkwMUe=fc!S=#@(I7#Nb^abFhwcN8I%3#EkpSC z&firyp$rZ>IGKRunky`M#=#pynA ze_ZmD_4IsZ`M>A0JpD3xNXqv=vM2gnKF7^}hLHX~@eYOKeCBXocnI})reCn7^mzXA znf3IvgP&FW_Z%nVm-=^%zbD^E;Xa^CV+vVMPyd}gKTD&?&f#$+ReHG zqQGW;P<%{pdeSG9g_S15alUx%#V-aRzfEBs$ozmfnabHm;YuwjFU!&A`ukmfYUfG* zQjg!2GhMkruW~XTPmdpV`(QWco3dBcqhzc?dVM0}bWUII^)ETxf7i3jSFb%Lm!pbz zfBL!>+Z&uI^Oup|D>{sB+%aFZGgVHiKazYO`YBINJ^OXPwGQ=S$Novhudt_mJ^KOW z|LVvd&>3f4_Tkre!Tuw>i5F$tlw3>oR2&AH^hzYET!zB zH;>)^wP(KkvwW+XCc`J9*MF#VNq?`Oxcl&)?amO-(L2uEUY+TTezBcq5wxczzN=@X zZ>pl*7-Fw`5}zM(^?c*jhi_dp(9V70cDi57;Xiy|;tBLViTCUiW1`ZIN;<=2zC@p> zxw$8w{d$g5@>$7y<&((g^dy&4PyJrMxnnpPzuz6NOx+*%4A1#=%1P2r*?v4dsy(T8 zBFllkY>Ih=M9*$-T$KLFa{{uzWm&?-pcyp*|YO!uRl}$3Zt^Cr#{tB$oRZ^ zG>G;PxEv(<#A7EX+QEFKZ$~*#PtW?!a;jaZdc*Sw=F58Q6Sov{yngYgpBdF3y>(9a zqPW~=qh0M-ekvY0PSRIo(Z74)&!YWZ4u9_6jDF$2i_vuJK|t4oJ09gQhLGj!6S7|W z-Fj|{%TJO1Yad}$^D#ptKld!ZouymZ`@bmj^Z(U+xS>+oukgYuS)V0(^VQ_%zg;K8 z{WzZG)t?~}!_#{*2S|MqJ$;iWO8ygL8EJmCmwc9dpUA=d-!Go0HyXJ9^`@sc{+Cb4 zaQ|t2s@;#dN$M{cDDB_q?)>{9tQXY$gx+j%pH=w(bbUD%@%!9<$Qvh*Stsq> zmA|q__G27||7d*uKw{qcGM(qS<(@o_CO-ScZ?Gpt8FE0q1e8V5Xo#ZQ*o`Lj>FMDL<-yfwus;uL!yG3ai)lpyoTO9mx5DHy>5~oJ2LB_Ph1d z6jy!ADGG?~TNr~PGnefNiI;xjXG>fUWZFwN*Kd_Bm0lUIAyogX@Uc7@PTBEXHS724 zhwPsWaUkOLiEW6_FPeb#i%ICeX#X&W!}-J!8`%=S=!1P4Q=}9!KOj!S{#sDnbuIgc z#BD%BEG(7rsd=`t``^U^%qQtrdtnFJeG%*Ti9>+@`DYc6vR~mJkza;T7{hwZr=J!7 zzZ+$}_lZZa&*K+Q&g1+y#e?%1y?XM^6q!$N0uAvX)^k45FoWg%qPKB8d0ba=5`AK< zJMaGm`xaEc-T6S`vwsJq@Pme4!ZUikLAi&-)7Yo9-0)#K-(Wqq?kALe|6Qo|9aQ^}=+pZVy~SHa z=k8p7a{f!}$6lUgc)ymD`RB>MY(>`kV7r`Jae>->>thH@-}lPuR5- zoWiSg$o&|#-=fMX`B{}4twY^;QqpOR0O zlYDPoqU4k1RKBbFtMse=sW|qT{o(_6A4>6+zQAu}d1nI+(Odtrg1$Du^`ep9_U8WS z`C~c%O>tyBqgM_p{@&WN+TT*`Sw8Q)J@=eX^j!D%#%DVYb<@e%b06hj<*Rvw4A=X< zNaA>}_Rn_Qe^UEFazD)Po)hdSC(jW$Px(*I3pgE5(ESsBmisF{oD=bLDfVON1QGi# z2-M?0Lu{ijF0h=MhpYPhlRIA@0)G|65cgjqk6FO1XX9 z^Pz{`^SNc{|GoKu^rv<>i}k2=xKBKHsC<_CeL~r@)?IHO=IUL&LCVdcH%53rzHBO^ zAN7#WOAkIw+M()QGQR0&f1kT9F@$=S@zTB-`T?J~7U!;1ujzc0_mAJC^*_h=1J2_p zzK5h8a-WONC9&V-6Q=+TG! z?(X=k%2C!UZ{EFg|9?N6vRB6Eqi@V6)`N*x%jat`ANGm&P~ZF_6X$I^?oXB+EYmIR zR_&d>$L#W@{eCg^I8Lt$uk2Ul;-PA9J45cjI`5GCzB!kB$oVMar!PI={L<5>(%T!} z;kMhy+;Mg1a$flS40nFQ^}$oYJf0Re0GxJ-L6^j`z-y`9AOh zM)gPSe_vS-Wjg(06w+ymweGr3+8xyCQT0>G`GricS3eW$gX#zUZhvd)`X>8zuRN2} zEuX#mo}3S=-};(GV(}9A|u@ zqMrGF@g4m}EcXMl`~u?YB3YjD-hof-?d~JX@IEmDbw>P7D8e`5Slb@kKaZzqs3-U(P_f1L9h`N!n9ho!6`Uy<`ph zt3RdJCnVo5a=X~CH~y}|nc}_UW%#`?U-F48j01jgCi0u^RUrQZ;==)Ke^6ZO&Tr&- zCyhV1%luUJ=|iN?CuDu`i_|OSIB+8E1Ms;2hFkyC{9f)4c>4sruIKYuUJs|In|eN| zM_M?)o_eWsA*84zXDWxJb-{j^Xo_0TUKEnzuRdXJJ_YtQ$X#W?ULn9^$&IiT{}arot7J4*tOGk?R0j~tLlwPuga&H zSigJrD!EsC$^YK?CAcr8^54^^(j)bnLdxx!PtxBDxASMZA3*ctp7l6c|ErkKC+qQx zirY9pJ$`byUHR|4AM=a!Ui2oHU$UIaXSMH=T+f?OZ_OR__m(d|O;1c;Z*t=dS>LBO z>`dM-lz&Eme+D|llNyled<1qwBHo(-tg=4DLI~Gy(%9i-xS}Ye+uaPhspIu z$(hKn-sLLX@qbtGse6uz@p$Q0_HA+ROD4+oHjev+l=t=vlf&JD`I0H-mvcGq7~ih^ z&qw?D*TeOucZYI)=@#qW*}9zWU6 zZN<#N#oY8 z{AGE0@&Bivljr$~`RB#k8()T3_xU{eWWB%cdB=LL^viUcV!69Nrt)`J{>pAC=gqT{ z$N6NrWdFo?lGC{>yOZVr)n~tG`={yl^r`mE`{q6S8EL;Kr_#gw>RfN-`A$IZtE>FI zbO!sY{$1tYj(Sx2Y{7Wr`6t)co!jBb|2ls<&%=7o6zgz)Nb{Ey6Vsbqo@ySv^Kzwo z-PJOD^1jT!y8b@gz1Pw>gR+hCO|HvL(SJTa2gG9R>j&L)8RqeycOE3qi~J&Sdhfw>!>b`TmnrEx<@?MeOJ#RL}DL7}6v2Z-ZXS9@}e_j6dExQ+S^PG_0BE>`!8 zWK$)|}09KJg<^y=SP} zk?L>Mvv+SmmA~p28Q|%@>9+l6EFYiHn=W+7Bw5i8s*{^%;O0_SAsvgK^T3=%w zW4iUzd+#P$ZqIADY`yU%*`MD>`p@}uT>R(NbNSwrAMZW=vv^c`q&>Z-=YQq6yuAEP zt`Gm4f3lzY-+bYG_}_N)KeZn0eWPs0a=q=XgXO%)FW#p+HQZmS`*ppI%l{sqRO8PZ z-+!H+9mA>dWZrHvJ!*b=`Ohrt6E{v}q9dZM~dv*Y?k?e{5pxi0hOcR6YD9Q-W8 zp8a6*yh+^yljADg&)Zw(=YV|}1MYfK-Kz=d^>uPMnLcm)RQ~ebL-PF_uf0kC zlf(6GSpvq~0C#N8MkQ=`q~>a6_DidPn;pbZ?Z)@6Xwcrr6Y3tFGx0&La1spCQZgBUX_?`>zH}Ux$?>DJ)k*V%?4gUGQ zjOrhJV(07KfH)uhM^K!e)Q`MRv=h-3k z(f13mZ)Cdh2Xy>L94htjJ{#*(sP>@}QQi|~ImLe(`DlpEA8C5HRn1>4Af^!_aN_2j-H@1v-F#r5vK zp4{hDd>KlGA^PrpCcm-Ejj(t?b%-o}29ir1%~e{H_Vb-P-h`dt0JJ$vhN)t^aw zdXwLkzGVMjtLOig-2ZF#{@Q#>PM7zr_Az8R)4U){w%`9u`S)gDZ+y?LoyXr>xZe1? z(wFStTmARit+MNqdCc?PZ~V#ahn4?-t=;_E^!(qF+cDpMUAq7C_MXz?{X`yb)w7y! z&cXdzLqy&0PTYWbk19`vhEVSX$??JCtMNjj_dZ&0>x16pdef`E8>dj&vGecEsqxaY zLxofECO#YD<9j(RyT(T_zw*+3ix4_#aA^{maBY^!dvgk_XX7Y$!n)_eej=4r+ok1 z=bi_v``ilU``SCZ*DdG!)WRcjUZn2F%X`WJ_nvaly{8;{@_PuX_wDLd}` z*Q@Wz?Nh}k?~nT3d#I*+4^`dg47&GFL+(A)|Fk~Kz1O;Hz4E^0uH<_6zc*Ko`|A4u z1Mpoewchg9d-5I@y(f$Nx<2s>?koA-^`N?k9}wAiUslQW-fuj5x{Ozy+o^L|b#At+ z`uuDCRlDvDf2|(%zMbj^SRcPH$$TDvdh)s7Rq?6*q&NJv@&ErtPUS;l|4QG5Lw)iI zxjy#`xjr}D^?ATup9kIbdB|O#TkiV2_xi2+H+heb&XwGC{OAQ7#xLse-B43J?ylc6 z@P3m|Omy!%|4+$xRiET~oA#CPeFCo^?`@oVeLA-@pEw-jw_m)xfzwUx+Ff@mee(R* zFD9JA^7I`VSHBwXdeg7o*O&45-FQqLj|zXzK-O!B|5NyY`#xwg25~>h5XpS?to&tt z@d3 zWRUAGrup_3wd7L8-t<$KdyAMjSS<#VX@ELW;$A6Y*{q&-iZ>IlkM;Rg;?|@_lp3-A(RZ z@}Ezxo@7^1e5=F&!V%^waUjtLlRJ#u;pC1IZ;OD@M(!PQ?~+?XZY{mvv{u|f?itZe z&+Ey3K<-0w8^~=W_Yt{|$#szXgxsg(J|nk@+-7qBAon@BPI6z6`;y#OVMC3hLQ%gN0(_7iiBJIFmF?jiaf zqVFMk9?|oNo=5b3MBhjBeMHw1T}yN=(GO7gI&u#Y?;+wnM7*$g^cbob)CWaD1&Hda zc>_S%#2X5VexyVU*VG72jndQ@O^wskL`{`xYKo?&fyyM!Gc<25sNIQI3ySSq0E+Ev z1jQkq(Z0(;4ItiXO>Ne`hEaW=5c`mC*x>YXI->Xmto?|RVTTY^3*V!N+6d}cqQXA@ zy#lr%>{Gw}H`u3gZ5k*JG0|94&pkQiAAVnff14yOZn%ubD)1UFlv?^4%wt=|`IRlT zpg8O}Q}HHhs!UT;G$nn{rx22wMAS6+vdxm`QJiaY;d?1jGG2~tXyCE_^qVO}WecWe z5|tzVa;hQt^E;yg2huLpd{D7=E&=B8FSRTL^~jC%+Y_MHQ0KA))c71jya4Jl`hwca zQjVzCLH#91h&E8S5yeLtqXK`43h@!BTZ!5X>ajhA*aGSXqP_=JJzI#MK?R7S2r29z zf24Oti5eGB<&i5YseJh(UVVd#H&j!jG$r$35Xp@Jk88wgP<#*}DI&y1@Qxy~SeNCP z5Nn=ByosT@2k4h!K!qn6;ucWvZ>QhgAxh>rYaU?+>Aj9Sh&RGK@=W0M~9 z2P4d1DDUTz?+k0IX;3X>>J_3M0yX#@8DfTY_094(Z)R9;oj|{_L1AZD`IO@%MpPT^ zO*J4y<{IUD##KtoTa@u!OJ-Od6Ae0P5;RV#yebvSzH-49EGI1$npYuIycL4ueG|Sa z-U`9-a($@~oQ`+k%ds)9QtPV}oT5+2w^FcuT-PcE+b4O-s-?QsE*1PWn@$R`R5a4} zWWECR+;#L-9#HFv`T^8Gs_6@#pl*JbW~n~Pd#jyx?lOs5Dpt61UBbB25dDa^GH?UQ z4J2x-j;+=jd>4H`gm_)LkEqglu)x|txqLkN_O<$u+(|@LA^%Pzs#@2LTI*XX<_m~d zYyCj^dlx@P!g{?#l@NpUa1WM)j03YW#|625NU8>8CJ4qkM zyWBXiR){^xceeKJ(Dh)p!EN;r_;TAioTv_6V>@&W>(I5SL$}qXNIBx;0W* zx7KwC)h;`9J?+r-v{T!%+`9N)`UNktV5PO+O#0O+qLy2%g?ZahU+yHTTh|xU;M{nM zc&4Fp!!$TI{zklY))>0cv7M;d7-4($5h!F8umKLOf5@ zm@r$qjHrp4+U!%j&Aw;pL}>;2ZuK?S2=N9{UE0_1E4eO_Nuzxm`-(wCt>qBNp$|ZD zz2}KSMg4`SLfzhq(64dbEk?h_ zeP@x8MKcfXSxa;+F448PMAzaedWJW}KbOiQ+oGNZyuCrG{$-k9<+GH_C0{vy97fa> z{}`%?M-f%1`CTd3b^5cXkC2Dr~&(s%BA*vK9nhr{}>Dl1}sLk98 z-VaaEI89X4|Hm_Fh6jrCp36g8@HphuTxzzSsm=|bMq?;zneErTlwb9Pv;Fn6WG_G4 z|0kl3rCgis-+Y4*Pa?$6L_G)UO!_s;7lsXEU?hWzc53N295p?HFfym zG`c|U1fsqKb?-God<&|OYJ{XtQ@rriH2U#eYJrdYk{=-#p<8ibn`3*LG;^=l=zr6t zYOS-V9%RAyje}`UO;oLU3r*TtZklc%O}c$7)@9VJbE8@3MzhY17VX=jeOt6|uJK!% zUvUY_w)!T;n~T^!AZjaCCk>R2Rzua@Hf>9*!Q3r$b^{QLv zLATC>Zk-2xP3})$q;&K()!5kApGf@u?Y^=~Fdvf$lMf>(VmJgQ(BG zG(u@Hr%+$Vz4tWZTq&eHCa=g*t`5KpC4)pODpQH8VKp)0<)bY0|>@>rsIm3FQ23vPfuhKzOt1i{y zSh=#L!_5b%{_%K}>-*^mA^N2-Wl{SbKvbUQDCJ z=9Oq(spgexUb*I#YhH!sRcKzN=2dFma5H}*_1=`C&DL@%t!s#?Lf-QXd9(F1wa~fn zExg7M&k!}-+_TCMD?o8AmK3*z*NIna@w$iCB-K8(;;ZqgorzkD*Zbch?BbDhb1jut z*5=J8OAG3H#Jh^tx_gsv1GI2kXwq7ihd-shgnNcjCR;Fx%BTrDCtME}`&3))t7~U7 z`XO$OEvPx%Y72F%T^Szvun^Z%Y%Q>0CQ+@R<`I<(s*b2UP33E;9*H}2nRnPR# zqNe!N%B$9LHqssq+b0S>pxhy9Yq&4++OqZH#t)T(6 zev4mC`v;UUwbnFrUs<4wSDFk-ue z(RlJqn(u=5_Z*T2_2ixu8>lGN$>)eNY@R_qPn4XubG(hl3-f3!BVLy;g{YM*=O06B zoVSQ-G`ODDl3=4z?>EGI#0x_U5geB0*&893O6_A4s7*8*{sL6GyQ?#>_>XUw7b-_Jx1M?UwXaVi{8I}>@jw&#NyNxp@CwQ`WYJR%nQxy*S*kmpz9Tb_Rz z^|s?FM5A%V{)V^`RDVjxwV>2oZ!UTZGC<7rHIP-`lJ8vKTB^*fxiS3hTsb3)V)wF} z!-n^z^59uoW7wi`P*NY*au=*Id^>58yxrz2>Y(+CnnyXnzEOKNt%W3YUPOhxk$Nc^ zHjAv6z6a55Z%I8x!(mNoqQ!jpG>Of8332jQ;J1j&EK#B^8THt&4c-giZ^<1S^&sA1hhmw{u4xxF2QL}mM8EO|$j`Qy2 z0F?gaBv*o($iC~0Xn(ppPrOpit2NJ}cY^AOS8Hx?sdDrdJijW}y+t{C3!Y!qnrrIi zs$;Wn)!wqN+w9wSqd;Mw5_q?{t7upD9mQDbx)9OIuWXoZ`>yc#Ma zZZl*2&r=ymY9p1Aq-IbVNov0K-J5F3NQ5=1%}6Rh?c*qDx%y5O;sT<$d~2fjX)CM7clCc`y+*hp949 zJc=(cc|OiP`T~>Z=)8_!YP@k6{X#f=Eh?kg6vqOSDV`lJHU5665O)!8N`PD5JffsN zo^On^|48G)GsMd_CQ^NACTg7hF!5dlkN02J5hZszxP0Xf#&^V<2CJA?hMFU(kEvuN z^);o9dy6vPIS%a^QHV0%M>Kcj-m%O#m2Q#;<4m>PDr<3>@6VLG1K?Xuxyw`q#xh

;bImUEHIkMCp!qz~BB`g=DY;jPIu;>{$f}_vSD{DF zG9Q=6KIALcrazN!rJwifc$cmU6z|+s`_;bQN+0hc4kWW016Lksh{Hf}PVydEt;KSz zWej#3S#F}HmLmT~5N~OKcZzsczcj!*MW+()nZVf%LQEj4%s2jSSxcnvjl_$iN8(-1 zsK4tZI+-Hg6#v20YVRRxd4Oj|lH!?BEqH2XG)0et`PjMZ--l)odM7ncQ~7%BoR8Re zWSW4Y3Ng_RolU#bM6J{<-teD8c{Pl9ef?L_oZ(2Kx^&;vrTeBX-8XfK3bLNd ztxG(8tfXqReKmen|3-v)|L+pgSEEPlDZ19x>7J$m6t5=Ae0fw8@1YQNdJeY|`LZ9C zZ=GMAYEAL+ZX>s-Iz69k(j!=to=?{K)tt3KQ+2R~*En^4b>`LJS8E}u`6`NSicjrF zH2BpnM4eyp7VCJMb-XQpu8BX9^)3FFsW$OGSA$>eel+*6ig}-q1*G`|fxVF_zM5f% zaCYN(ZzU=N6!ZEK)!^r~BvmM~S)I!=-k(J1=v#Vn`WceYeU#KsGfdE zRG~h#Y}F}m)wwa(xRb08W>MR+n|@C-f1<{T0&2;uc_ZSb$}To*s?C2D<=;S(Yt!{$ zN;qx0+%InTe^epFLFC)+|Lqicw%G1}Ra3bXb{N7k#l2&@|LF^5J#E+eI#BC)|H=qf zPout`LKwl1cc**Onwmh=G<*CNWGSVxQ-|%;b*NL9N2jhEa`tdLh3(XJd|MAn%~`kU zI@GP}&=iALq*OITx2`V(0(;M-Qw7pjYjKU;1d2~Gw`ktaM9Kay)R*PhH%gRgju}mB zSWrXCXtqSu=J5Klh8RIqG&p1?jk-iFwZ9!8Dbuw38{%x@jRSJ z(w6(kcR=`ydRm6zVmi%EEp27vddIJo>%Ql%IE_z68Z5ira}=Y41U? z+5MQ}9EuaQ+U}y-xffCE?6QC%egodN8Irf!?wd!g2fmwV5_>SHxew6@OVmbtN{ztT zNtftzoFVcxuYjm7@e%c0%p30KQ^E0?DkUlxee4xPZMMs(4$UPh-%zzFZeC1v?MdRr z%}XvZ#4@6?P345HPjg2rFmt#HZ|&2TjtL`oKiy}%KSGSA;jrKMyF8+nOBjI%Rn zbol^O2k|}wHJPX_MAe3Ke-e<(Lyl);tGw?|R37ZhCu&4ER!ws_qQ==jQrIb=ZY8P` zR4Y-p5+(EWA)><0#2l)LM9EeW--B~fohas^pX7ApBfUJoH-c-Z?sCgBf{)YOv4%nv zL5_372p&Lw3y4?dS8`?kU+5OY7z!cfmc#cbci(oYaV*tI-WM2ZeonV?){}3c$#aXN zXpLSLe(+$~D#rP`is)7j@rq1t`|U&(gW671iKa?H?X?%JD>PN1eJeH9h1)TwlH54o zBkO5jil{31-bPe4D2}5B)NV+P57PYwplg zr}o_jisR_kzGgtBJff*MDAtm#slg7{KQ5!e&Z%8;PB_%L;$E7&44`}lb(E$q(A3SE z`je(Y14&EZH$x0jP1JDbnCodqK~xS}6xWDc%shB?oEK1gRZE3B@6OjPuhuG~Ql)FL zLh^X7oR9I6Yt8_yFk<9e7*Ml}&A1mdka$JV!gHS@gy0=lxth3?w3GzYPF8V1?Q_XF zHB~FoH*~<2a#SnTwv-3dTxD=b)zbRC^qmeCjYrP$I+9kxnSaz;kRi)w8_ zEBYL^kK!OLoVG6U4fUe;lD@&vS4UI>#zx*#tqZ6bcR4hFPQDd74$0#h@N^WckdOZ(=*m&dIcsCC@7@`Gw0xG<>pf#sN4SU2HIiWw~6UKCG|!2Q&fhgax`@qQF14c z*8$UvR(A$pjFfXtED5T!tkR(BF^hxTX1Kl0H8|ybiX!*x)|0++U8)tjWGZyYROphK z>zjCo+%xKHkGn~TL-ywtJ=bwwPs`geb91Th3=KX~_K8bFlQi`cE$Dc?D(}S9lU${? zS)L_eC1Ow6o9@g)%VsLUW};SxPN7-z%S0K$GcPwpD^XQJ)sI)BmfT4-r$*P3I$i(j zbUNyE{cF&vY|y?9I+e8;7q~~NwR{_?_ETPUp@wnpb{MlSps$4d+{IPFpC%GtRy=zn@CfM6tjvH#vj9g?Q8K<;TjEVXmHSR-r5# z$+rn*`2takgKCA_6jVDCRl4Ps`qgP#bC4+><6A)Sd}AfXg}n~q8JROFFK# z#I=@A?Ym7=-I_9CACJxv-P^W!Jt0n^x+sp$pgMhsgxFHHDiY$h!1)&saa-X0%MPjh zlQV#WhcZv?6~sd-Pqzitu0gk^I?;}}&o)DEQi~cxVI!KFYg`?WbJ1J3e8)YMm+`iZEV5cfmeKj-M&$kC;`5Bu6tw}4yovM`Lh}zq6=}^yT3@4a z(rL0!Y}1<;w9YOLDa|EXbE(!`qb(@aanxwdHCi*txtiN`h;}X4 zuI0$rmFv(qy35gy&Y}D(*DWR6$D;wymO4>JoTn8!Pb;;3mD;M|$f5lXr8z6kAcqn) z)UWb>C{BI3w6^Ios?z$ZwLa5OWmK&-n}#YQ(@d75#@!*btoicih(nsj*)>gIX5Y<7|8r!tycCEQx>ub^aTC_gW;-<2%scJ-DQ`LjMrfU1- z>&i9g+mP~RA=l?Fk$aq6M-9M!1h1n8nC&6DkvyEKyKWHT3ZgoYe_Y2qwWZs%r8&AE z+NN#p);Vrk+|zJ>5V2Hkk65aISrGc@A=+<4Z0hd90Gw5FTgcJ9Sq^$L?tLgWH$|m- zeHqsll;YfqYh9^ctHrgJxYj};TrC^12l_CrUk00>x+x!L@I9#Kh&RnRB~G^(i0anm z)~(BN#cz6~m@0W}uUab|s znobJg%j5ZTL6%b|fTq56P>ZmqAq3An#u2r^KZjbO|_>Ov+zq7Brt-emLo{Et#TubB%gQiF%$Mc8_ zj$(@UeD45t{{|uE5j6sHt~Eq$bWWo({{XznH23ZRRXBp`JW)Bim5m5KL92;hh_}@l zaKC#>(Zd3Dv!>WmH*0cqyUanmL@n0sQtm(=^E=+rRHc=tL*(fYxHqWFGa*wft zs9aN(QHjpUQk|2fIwxhmAP=BrKmq5=f%FT{`x8~E=hXSwBRhxkFJJEgRO;DlWl-%p zR|eH?ZDmmHFjofE?sP78K39@vBX}RxQ9tFT5j>4r(=kNJo#WY{c<1=9M9KT@AA&lU z()+ol_`ZBJatN)hwh}KIIi9HRL0v)A&qVc&aQX5avmEt+*YOpas?=1Krm8hnqw8c@ zm`@9Q$56@x>ddRrm`W{+Z$#zmUZpgk&c;fy&%k|7oz~Z&slK5&+4c?T>l-?f=174; z-uqG`vGk1)Px|fy-S9VSjStA z-4wp@)@-RYakHgn`ZC^Mbi7%#4yz1IohtQJ1}-CtEvO94Ad2Iy(y6S{*0&%w>8ob- ztro98GN{e9AznV0Y=<1@Pz&BxP~~nk zpzdGghSeG|4>u~fMdfP?3VZOl%(>i(!m6(2;=T%(eoCbFO0K8jm<7I@d~cuF;J$Ds!}WtoJ#`EtkH6LWhCwxM81NfLZAjI)R(T5hU7UCpO`%ExIF;T-K4pHO5yMuNiCK5F&a%`Ot7cqtQaVb$z zv=7#@F4X_G0yjwH%JoY4svKVzdhIqLZYJK=(Bsrw+<_3he>H@1Xsh0DF4$&^e=UU5F_C??1JDE#nO+EcjIkX!2!u5V`gmaCl)rdDta+2DOsG&Nx zq1HE4n+Ae+A*F+D9%>a)FUs|FsCE5xX-kc6N43y=FhV>?WquSi^UUK|P&^ws3)F=T zw2wyAIBUg?vcI2*cyA=$MCvmp=#nYdy;Q5# zBKLA9jOMOQeu_jOfzoTm`X3d~(CF)X8{4Iv7K=HR2t_Q{6UAP$(e~aNRQ2c#{`#|yi z&WDML;{AokiCQOK8B4e6X{?Ypwmu@>YU{~LnrFlJYpNTZr|ZPuNRDexjbFV5wod%6 zi+U}{@w$Ay~j)|*)mM~43F3WV|HQjj3m!L1(_3!Wc=eYiZOc{T! zDdQbtz6$v~^G)C|vkmbLcf;qK>%cECKL8e*AH)Ax*MFqB3H&1S3*cxMi_Nd`Jl6ak zSYmbqCz!@qPR~R)e5o0vXG=^n9bmbc4xDUe0xQgZz^Ue5z)CX*INcl!tTGP-&N2@H zR-40sbIc=wHRjR4`R4J!IhU~XU~ za7f?{U|!&D;IP12V18gdupsaeurTl$aAcqpSQOX_93A)$SRD8fI5t2R{-}IQ0s-KJ zfDJ4SqyZ-d;=uAiU*P1xp1_L0-oUAWLBPtu0l?{jgMn3n-vVa^@`2TX5x_Zt1HiR`dSH9tG2n*46TptZ)4)xE#lX(MQs9=r-+v8U*gyCKFems6a8S@Zi_0fB7zPdr zrULVV8Ngw|EMR_c4`4xXAh0mFFK}cq7g!V=3LG6g3|JgI0ys8U04xa}3!D%<0azM5 z1vn{K3@i_h15OT}1FQ(151bmD1gs2R3Y;Fi0$3Hi1~@BN39Jsz0L}^C0;~z%4xAsH z1FQ?q1J(!c2Q~yB1}+RX0Goo1z(v8QfQy6A0ha`q0Goq<1uhG|2y6+y0$dSn1-1rP z16Kv#1-1p>2d)in0JaA|0d5HX1K1J#iZCWV3Cec7DYy;%&R`dCOYmplwxEAJm(TW~ z1?&!{07WPUG()=qtxz^F5*k2A{X@v@A42}Y=Y(Dc z)`VUI&JV2u)`i{y)`#8$HiSL|E(~=5n?jp`i$Y%l7l*z9E(vW1Hiv!!E(`fexO`ed zA>fKo1lSsi0#}9l0NX;l1J{Q71KUHt0d5HG2kZzP1l$zL19pan1Gj{Z0&WW(1Kb`O z3G5D?1Qgb(K+`%CXjvt|h&2(IZe0kBTbBT{tjWM^>ndP>>snxrbt7<)RRzqoW&?*< zcLMXQdw|2N`M`YZL12OP2(Zw495~Wy0v1`%07qMY0v20;0gknn0ZXixfD^1&fu+`) zz)4mcu-sY)oNRpntgt=?PPH}xE3Gep)2*+8Ro3^wSynf&+M#u*T@387E(dP1DuA8VG~gEN2H-YpCUCno3)pSl0Tki8foAwV zpcSqIM#2k#>EYi4faf9M7GPd@C2&~y4Pbuw zZD2uoEwC`W9yl`m5wIxy8E|yC6Ikrx*zi_7mxR9qP6+=9q%VG)!{s$88~~PwZ9|cPxZ1Y?$$9y{sEU;67g?0vTq@4vUviAUvwg&=>?R|k`?Ob4qJrp>>J`7lD z9|4?X7XZucV}X61xG|Y&Qaz*-rsm?B{?h>?Ocf`>()N z_KUza`xW3?yA{}OuLf?g-vxHq?*lj48-ShmC%`TCKY-ipuYlX_ZNP533n-kQfu`f1 zz~y2&7BJ$Z0MnfqFz)OI%yP1U+0Fo9e`gl_Lk;v5dlbAAUL<`e?+ zol(F7=VV}^a~g1@GZt9noDCf9oChp+E&`5q%7G=$6yOBsYGA2z9dME}9a!$%44mxT z2CQ)I0#0>mfR#=yaJuslu*!K9ILlcGtahFR&T*av);NC#&Uc!Dbv^gQ*S|0vpaB;(;wLB{06wi*$=qQIS9Dj$pdyf!+|1l6wr(u1GFL|fsx2b!1Tze zzro2>>s%hm=n1KI4Ck1m>anYI3#i{FfVc=a9E@Ym>-!9EQs6*ER5U( z92uDpEQ&k`936QCSR8pAI5yG*EQve=oDlgFur%@);H1bhV0q*v;N-}wz>3J5z^Rco zU}a<-aC+neU{&N};H=0dV0GjR;GD?Uz?#VS!1z?_sn00*Tk0_LXt5jZ5}d0<}33&3G1Ex`PgmB50OH-LpHZv#iB ztOXXOtOt%x`3P8?@)>Y!N++-+Wh-z(%6GuhlpleUQpEXOF6Ai!;N%n=Sdo$joSG5` zR;Kg?PEXkrSe3Fja8}A7V0Fp?z&R-g18Y)#3!I;lPe}WYDRR85OBn%veadmbhLjWG z|CsB)Fl99OO(~}X7p0s9T%2+)a7jujVN5J_<(pH=z+aYf8L%bgO8CFz`mact3Vv(K z^}tmrH^G0k>))1gEBI?us)6k(bAcOD?ge(FJOJF3QV;A*c?`HEfeB7>feD@>Iz^a^>tu+>RZ5g>Kb5HYCAAHbt7R+?2#(-tAFaJ;OC@%4jh!a z1(=)qEpSNc55T%~CGU{M0b8ATIUGJ)K7rh zQvU(mp86HAJ9QgSq;&z!w4Z@in*RbWpGcYoOixPz#?xZJthC(-eWGvLK9{mwc3L+0 z{nG{jbJF$!4oce}n42~PI3(>*U|!ncz+q{>1Lmg{0t?bc0SnVk298WS4Oo;m7C1WX zY+!NPdBCx07XeGs%7GKorT|ORt_Ds@yAD{MHXS%Q?Pg#_+HJt8X?FoD(`taz(`tcL zX%7Ksr9BF)PFo0^llCOAChb|^{Iov<>(ZKm^=Zq24QVd}7pA=iY)V@NT$J_>aBerkz)jH=z|QDvz%9`?fZL+0f!m{P!0zZ8poqN(G-K<5R%`<>68jjK z9{Us+k8K8K#X5o6v9EyrV_ySvV&4J>#kK=;V?P3i#C`_m#f(XukHcanFh3Ro7Q}2| zVJrnWGL{Z3ie&&t$NB(^V|@u7aeC~hOIXj?ST^`2vAuv3VgrGtvEKkE#RdV(WBUUq z#|{Km#D)T=#`1ubvEKrx#|{Tp#f}8diWLB>W5)pJ#Et{j#6|(<$BKY;u~UHcu~UH! zvD1MIV`G6$vGKq~v2%cnV-tZ(Vx_?5*hRo)v5SE%u}gs~Vv~Wbu`7YAVpjv(V$*8-vTQd#;uF5zJ*p@LIxHcmn*q-q_;D(H&fgKsg0ykxh1a@Ye z2;7o!GH_eQ7~uAdVqka1nLrUg3uwmA23qk6z)1XjV0!#QU_4$1%!-!-v*VWo`^T>U z=ESc84vJ3&=EkoB4vF6Y%!|(e4vSX-^W(Py3*xr{3*&bHN5i{keHN5}647RPIW zW8-zelK8{G3GqjOrSZprli~}3YqHPK_@HR>uDfoF0E3SQY;(a8`U7 zusZ&C;GFo&z?%3<;QV+iurB^4us;44up#~qaAABcuqpmNa8djN;Ntj3;F5R;usQx2 za9R8xz?S$Iz!mW=z}EOTz*X_@fNk+E;M#aMus!|@a6{a8G1u3QcmTL5ZUH;v4sc66 z6}T-P1#XYWf!*=lfFiRW(9GNeXl3>XMl$yXrf2R0jAsr8W@Y9Avoj9@_Rl;Rn3H)Z za8TwjU~c9Sz#*AO0rN6P0EcB30`oJE2NqTG4!A1wPr$a!rNFhB&A|4|7l0cwUj%k!z69KqxdPak`5JIb z<{Q9mnX7@@GuwdOnQMTe&wD_#&w8NMX9FP}0}1nJ8{2?{9UzQh3?)D%>;PegK!KWqgq%W} zP@rKRrPzT4!aPF?5_Sqap-_gFwm|=P?|sj!kAJbg zwf?ofpSAWq5AWV*pMB3g_v&6rv9z7s@`bj$ws?Jg-=n2j+r3&=*EZd&bA zmb0~erR6+rUu(Hg+t*tz(Kc$Cr|nqFd~M%qS)lE^Emvs!LCaOze%x}cwx71#pzUWZ zH*4!!Z_~E9_50e6Z!KxNR_l$Uzs+l^Y6YTMR&zqXyNKhieY`eSXoTOZYS>(4DEQgN2@(A?bT|}OX*g7F6(Xm1MVBfy8~L~`%UBB!L3z|KfLu>ZI5bw zUfW|^U)1*a)>pO7w%Ye&`dj~~|9?vBpS3-`)t=kVjK@1S9`C|dd#)?Q;|{jk^WcJ3 zdmdcWD&OD2=bmvrH?-RKwr**a?`@5Di(B8)w$%Ebw!^LVJ*|6M?R#4Hx7zo#9*p9Pj6+ab2Iqb-1>_X&udNA89+j?NhXCy0$jC?_Se& zwyict+GN|h+IF|KX}fh>O55$)w$XNS+b-Jf+P0Urd$di{cCWTWwN1Ajqit_nR@(#G zW@&qH+xN6RyzK&Qk7^s#_Sm*7wLQM=CT+8AcWT?;c8|8Fv^}KloVF*mJ-uy2+cVo< z(DvN6SGB#c?G0@UZEtEj*!F?83)=pr?V`2>?q}C@*R-vv_qDgQ+4JqYS8BKK-3_B@XKHdIJZ7c03YCGEAukCZ~v$TDo{kz(}*gjX=SK80j_Oo5^gtlvS{7l>RI!3hJsN*?p+d6)wZD+^J+9o@GuWfh7>)LMJQPXz2j=yR< zx#JyeckTE<+dVoy(RQzne`}lW_^-CT9nIgB`8%Lvg0=^DtfTGW9UE$UR7ab($98P0 z?eQI3Xq)ZWO56U9?X*3mV<&CrbnLF}=^aziCfpevc0AAQNbCQf761Ra9n&=Kybhbc z3p)9|JQ$2)G+_UVq>w5@d9sqJXT zu(r>2EY*w7sTtNZT7a@6q;_&Ihzz+-dJ? zmpbiz?cvTJtA0=CPqe+i^J#4#?5t}0NawTKKHmAfwoiAysBNY5Rc%K*|ETSAoqyK$ zh0Zaw8@OL}+IoJmbCv3^bpB1-*E&Da_VvzBwT(JI*LJKkak|XUTb-+G`)=o2Xz{t! zrjlm^{Jkj4m)_j2zrCB={mlWJ+x^DDo7;WP;hWq2#!;Kg{l+?OL27;ES;s9(?X2xJ zscG8YkeY`U-#6I8@~*XoV_gUGlEzb~tf0x~Fozk_f{{Nh=$=aUYW%placFFx0e&Knd zT{q5{DC2G5Hr~eCO|%{FKHkOZ_;eSm^$PwfnG* zcC~sBpJI8Anqqm5onm>8pJM&{WB)0!e@^T_eTt29W*m3!6dUKlDK<`Fij6Zk#l~4M zA4vHQKnQ|x}PG{x@shNswk-ZRDS_wJt}_j@Vl+N<1hHm>iP3v&G$JFJHj%izWLQK23 z#W78FKaQ!#JrmPG?wy!sxJycwug@)tDeo#V%_hxtuf*B~uC;7EE^yZ`3G|<(_*W|~ z>{}1$cDM9V{F`XdkXAnpw8CwU{hh9HvTp~H@X=-5L&$o}RIkj?TqT*c0no!be?s>; z@e0{!^Qoak_mqx11hO7N)&ph7^stiDQvDKH-w|lmSIGJbJ@2-kfWOH}t*&vBMabe- zl0H%rulJ#NC`YOzQ#J9tl~# zX{6~&k_CUZ2VSW!3$ngBQXXVkge=QU(p)9!TL4+#B543*eTA&=d{UWlLmDSpge-0e zX&EU3*%&LS;W89!<3QFo39=a%vL0R3ge-0%DNV1C_2^MeW=P0-Oe5v#6*BKkkgfaq z)D|j9mhzX##u$QTGbChLgzT7CK^8Z{I3bIx(Ys1D8N)Y)9!((2EMz@W^mfrJWZp?i zy1(@H(3@9Hj**b{D1)rNDrtmqLKZiwBzOPf~<#-jh0fcWbUF@D4qjqJ)qm&6kOr5)N&xplBXtQ zeP@zpDajZG#tGHkY-mN0Wtp#Wa`i4TuB@8$6^dtwaTUf1)!i`Ss*GF4xaFX!eowrH z*66K+;`w9TN|IkKjFtk~tFw?*&<(QlcanOgzBI`4Wf<2(O~~dYrzERHD4svYWwJwll?grU0ny6km zKhhwpB}3`~#k0$}X{4NzWS&W_NNoVrr`L@^&^vkuHJ@=yNXtkO$VL;YyE-!0LGjgx zajTSMTsJ}DHv19oZa_BLIFQXo7qyAhCMikZY1C$t3LvXs;7err<}0c3rPr1|ugsFg`e=&e#)MlAwa^)-4|(u?6^uLs$< zO(0t(<3LvF1SOg06yqk+n+Dk!8G3uD30anDq?x4oq$Q-Pl8m;D+Hz`jkc}o}nT6t& zO|5CI5GQ1DLh-&)>!Q}Jn!Hv_q<0!A3$hu?gDlHTdS@v~%>~8{fUHs>t6)B9p^}VN z0$JuI^j4^ifNZp7^hTg~4!%V3Y_P{l_Hb*59v-y9eY6_>x&bIY=JYm!EXz1jl5r`} z3ik#23R&N7)#Mp*BFOrt>CLDndnsf+dO%jakoCwhPRQc&j1#grA)Ad^Alr|kdSwhD z>pK9lCkG+xyAWj6ml?NIz0!A7y|N$U)(JhjKsMS$YSWdZM-H^Yt+Bdy{Yuie09w;8 z!ZoZ2vZt{DkkvAzB&%fPOJwzpLbJP;Wgye)j1w|#rN(W8(fqn$jB%h9PP7!YE|AT$ zkoBENlHc%*j{>!6Aj>k7G{86^%QBzd63Aw}tlGwyjUkX_7P1~oNEMLvsH!G2IRdgC zLe^s$$ZCl|)}y9g>9LYt`Exer^&p$^CXnS5vV7w}mM^JZ$s%NZyHt}SBV;`$lG5sx z9zxcmM>XjoWId*VY>X`9@{AL*IQdl!iz_g0fN??=H=l7O^-2XoR>6>JQh|{5SVF3( zS9%Cpj}dC48YkXmpm?4cR|nZE)0oD|JHQF>?q2#4 z=8ADbR&%eC)G`fZ`KE&o@{909AT+~E7NI`BIM(w19Z(LM9rHZMj=4}g&!kx(%O^D3 z{}X+M`tYMuMtQ#mk*pNu%6KL5>_^8Qv3-V{dPD*BEC*|-xx)>mk*zYlRj zeg5^B@_ysBaaRS+j*O7?ok*GlvMfR~{0`_L)aSnzQ{K-5rJ==V9?0_bfGpoM(sa;4 z{#x`9n(J?isn1tp%6q>Ko|ECVe0fm3azK`ECTSMP@(I~766*8oB8yPomp}!O~PPu03?Efvf`2`ur~FA(Z#$gR&s&n+L^f z4P;qFvv(3^Qkw;`eG|(2OOZv$#t`cBH$y9cEb{;;UUeYLJfE}>Wch?Fi%_5cJ+cVp zedh*Pbs)<(1hRZWmTw7ZDd-@-3wj6*csb@meLfv)dG%JH+4WDzGLNXH>knj^mywo( zEVIyTzuFx9EjCb}&mpr=-aihCK$cm^GS^kp^#`)dD@m(BmRZPZ7V7gaA)ip*w{3`P z-9{l_6UgR3$nuRNO#s1g(j-v4{y?_pqUHTn z$RcFCY`DT)4fh?bpRVCEt zw?Y=7yuTb&09n2P@#>THe30c^2(m0fvwayog!=rwG3EVVK_zHbi;(3Tq9$bdmXMZ$ zEVGc+C)DRZM?Rsv-(zE32|<=`1Y~0fS-xeY<)DN7;pibWpjQT=KHnE>dA|e{L9={y zkmVDyd@D(-K$cI)@(K0%pCgM<-nX=1)omQI2wCPPYC@KI9BBf`G7DK|p+3Jm@(Jbr zLQoQ9^$A(#E^0!Sc_L{N$TACAW}!YmgnUAI{|`_aWchkPHinSpn?{-rvV21EbrOWv zQ2c7NXnB7OC=0TDd64B3vV1d1vp|+l$j&^WK7T5*2;mw5Du67DkYygACS;lClNN$3 zv(Ob@KBX1v^UIM>DDRJM!#V|7z9Eo}A!PZMkd}h%oEEY@7s~t7kfj2$EF&PxB4k;X zk(PtvJ3LUIpNGDp&GxI~Z;uP*{d%AXWclhK%O_;{R+3hMY%hi4H3{nTTO*5Td4CSb zwS;_4Aj>CY`NokZfGnTTTz?(<3ibJ)#+3J8faF(ut-dahj7ClA`g|2xg!2Ahpe)Gpr3I zp}Bs&4y-y5?pb5X`-?#ZXqImPWch?F-+axdvV208 zZwYBB$nptUKA}FpUMH@Jpu9f>Q~_DO5s>8*vV5aTa?d67j(&o_jNav-cuj(A4AJm9 zjm$!M{{v72vN2YIEb}Um^%b&fwospc20euGzG+jezt+&V31syNS(b662_V~FA*)5G z&u@q@pJS(cTgRUn%+p}fBfJ%r->KM<}` z(3;vp79q+bmSqyi`U+WJAsjdK5X$>m zpft$(_JC}Uge=Q6(sYpJ6SDmf>hl*Pi%{Ob49bEmUmj%nge>1oQh{*;j1#iB`J@uP zLti3`Tf(>zdWFopj1s$?ptlHG;kHNS0ct{4%Y0Ia z-k~p%#Vui6h2AR2Y8j!njBydQ8sqAum5g)kVShcy@{I$ntU3*3}p31AUj$$)ug^U$SPRLIM)&4JZOcJ z_o%?ka-=+| zKq`_3NF`F4G(@VBMo2YMowN#MpA0l5!(JvqHXB{kdPsRvfpJA@1EdnYL)0qNMnE>t zLiRalq+U6rgz9dWhp+|3SAkTZB&hCAhqsGb8e}#1P|H#)e2MDrKJ*=6TnSWn??M}* zHVm>W%~FsZtr2=7kd0QSHU_dDZj0b;0$KH4q)DK7JyYuiS>NgEl{v`MTLew@`^l>~ zwIR|l$oh_e;#CZ?*^L-irzT`GE@bci3f=BL{TXHgbVcHAd=JHSh3cC?mQTp~3Rzzv z>nmh^lc0FdLDshmWPOFKuaNZ>vcBCQ+oLqdvh;u~i;!gzvMfTDMaZ&bK{gwCko6U^ zzCzYl$odLd-vN;I7$S{;td=^dsXJ&BKz3A<)VfrYV>yZ5G`&6a_R^cBH%}^%21q54 z?b{Hwr69ZhjZmwTnkI%ylc2iW>c=>|)OtvHQUPS|aSu={Q5&K*LRt>8<5s7(N;P?H zY}ztZ&_&9SdPq5v{LZ<}ct6PQ%!||pNP{5T^D?y|kbOcVWXF7%aaG2RkVe%j-!G|A ztCPm)ZQ3eS)kR8!Y_58!xkTo!S`4#%P+vsz^PgJZXS5L>eL0Np2gd`F&K? z1d6X4q;8On+e0l+DuAqdp}O1QVO-nk9U_g8B8|%O|6HNCFMy4 zkgZrDt7?E$qE{%cnKVMGlbW^8(>6qt>)T z7`GW@S-L=W+=T4YpdM;@kR8E(ko6d#R?;|G_d@ZWlZHXoqXM$|6SBAw(kSDacMLtc zNxh^jsO~m+1i!jQZICof8YQJCOOFH5BMY($gsg%BH6hbVq)9u4RWb;&{a6UHr=6vw z<)ksjHSHYob&+~V(?NCx&Qlv84UtAj%R#nc>(rWd31cKdwr@gpw*dRwMQ;yjh%`bP z1KGYc?;0o#s=HsvtSNl~ngy~73aU+c)H%0Xs6ePm-wWynS-yUd^%y0sB8}}XUitjD zdy344yr0txGOeG~yazRqy*o3inyk9zq_L@CvK zQjo2tSv^AdB>~sn8+w#Lwz8vX_}7pA>__pu0J4A4m)%cRj_g~3R69WKpD)GnNKF@7 z0xE!R_haZ=0a=fTjycX04}&{Y2> z`X)i~Xrw`qy=PPcS-vrltweW7&?bOZxF2EMG_~o}7LqFTE~h5H<7a)7ARA*k$i~Q0 z>!&sdvSTz%ZB#XR2O|R2UDIQDcjho=2G!jZXx*yGdnjqu&VVMrT!-f}Q19Vk+~8o#wZ;jmC9O69vNux=+JkRR5&(x3&;IWvUwhaW_u~W5@h*=%v+%*WLiW`$TauO z&^HM(ts7*S<@a|?%QCJ2s=K$aza?ssYMcE8-;o2^`R4j0?igs@pg14MD(zQI&t#H2 zUV7Yy9`ZXMc>M)MO6xt2%%C{)w?Y;Nvih1q=1o#dGfv3jO4Ni*t2DF|f|fiH*G~O5 zhWutggW}nMW>lb8C?11Up;ySf5y@pkW+C$?Nojh8%$p?@=oN~`B~|DZipM35iC14C zTrO15Eu>nSTJ}q1=T_;Y(4#_%NXfh$89AzHQg44aGV)i7<6|_FaiDn5za1z|lE3$A zE2luMM5>S?k~=wMNs`haTXjM@5KdY+G-6|yv+9ViQmR|zQIOZCdt zyFx7@xpP8Xnv^9KNF|axSL%~i# zV3-tv>aOnzWTEEH53?a;dz7RmWLh`K@}=p`(i?&5?)%7`yg=wRPzhw;v95q@y+k0( z=PnGCyeLqblqHowaTbuxONCmMaS^FT@>msi%!O>+6t#?!%ySlG_4QL5B}M9$ICn{? zAWh1Wav&R{L@feY^{x;o1+usdDGRcF%TX(m%A_i(M)JjwF9ovw6|yn1Aj@2$R%2Xp zUWiMRGNc@-NGg-6q#7wbfOli`Z%-6RC6LupAteW8m8|q2*MQGfK$% z7D@f{Lyrhlch8|m`ck2mr*O>%*{id^Ec8v0av(d;i_``|RzaCsl~g13FOWF7auq-} zMu{}IFnCLshZ(PsB2xB>;2i_Sd%h^FjRL6zvg@Q!dO#ctzYE zC`n3_vLKr)A?s04P0q#&wTN1BafnNkvZMm3M5>S?kj(*pC_1JjDNQPY;_B}RlqMBO zB~pcyzEd*G98^FTByRtik-g6vskYNIm?4m9t|V~_pb43lRPFcBgiK4TmaO2sq?T1} z9yB3~E2#D)G$GSUs(k=W$h3-T+f`8?wMey-pb1%=D@%PhLK8AAsoEGcA=A>T?eTNe zM=h(`EzpE4uAtiXqo|KsNwxE!30Yi4wMU@|nHH&b@G{g#&D|ySJqJz5;*zTE_blq8 zmR9Wt(1a{5tJ-JKgiI@_cF-?SAGMNdKY%7=aTV1zeh&3fi&R?xO~~T#vWfbB15L=Z zq-r}YM}5@Nsyzoy$l|i99r#PsN3EdRKcNX(TuHU>K9BmSRX_uYx1b4GT%>W87f>HH zH!St-`76{1itAHt9<(I2v}%8WCKT7F+Frj#ebfr7{ScZ^T%T(1Kr2zJs5bRCsE=Bt z+AYw8tgl-l^?d|QD6UVnZ@h^5sHIiA7n)F9pK9B@g!-rzKm&;aG@-aYjavn+M6IIQ zK`)~|YLRLWLKBMX`+?NA&2Ld3D6UVnA3#e|ORMHzL4DM+s%4=GS>J+cpF$Ig>r-v7 zS5Y6eifUIt6SBBSwFsJ!X=9-Hb?|PfH2phN3Nmj}wHh=b)6%MK`+HPMEvwo|(1a|m zpxRx~giI@`_8~MO(<-Wc>ortLEmG}KXhIg}?veU7`2*?$nU+-T+t7qeORM$(G$GTn zs%`T})JLtL+TGBEEUu*5RbRQnz@A&ZMt8-XTdn!8u(oAGDV2Qn?G z+GEg!OiQb_MTGjOWmP*2nvlg6Km&CC1@%!&tM*N3LKc@*?OJF;rWI8CGc+O7N~%q(qdsaC)qVm^$l@Z^wjM)$ z)ZG12-(%2(EH0_qN@zl+rB&N&CF-M=RqY~ZLKat0?S5!Nrj=CN_^+ssT1B-2G$D(N zRI5W1GR-|8^=I0dURP9h`LZ+ovyA+y`X<5~N3Qfqgf@=SSCS+PkwaIUxK57-! z=0Fp&xJb37(1c8Lxa+wfQHLgET2i&`SD`*?Y1QUJ6SBChYCnf2WLiPB@o%F(Y9-Z< zgeGKh71c(e37HnDw#z%HkDB|D)ORa1A&X0@_AWFb)6%MK^DgS6mR0RMXhIfOQ0+Nr zLZ+2e`wW_pX%*FWdJpwci&Q%anvli02c^C{p$VCmR4sxgWLjFat^bDlsAW|<7n+d8 z6;xXWO~|y8YU{s``lwY@D?k&nxJb1X(1c8L4@rHKKR|sT(~_#44^7Clv}zAP6EZEU zS`C_zX$94K{*L;nl~lV8nvli0ABVf}Bq>eGk_w~}sX~fKxIquk8X$ZAC{c5dggf{w zD1Ig(l}Hs*L~@VHljiSGeG+7SvyTNTkV>QqDI&R_gdRy!nv^A#K=D&K2=BUlh@%Cv zcQD-JAxnW&0>%4FtwM@Gb$0}^jDhT)A^n7CS3}Dx{Ss7AYW@iC%z`W~Qf+T&?n&u! z87Qf=43t({?_=}@#TBSF1Dbm}RGK8ENflB=azAAcQks+{6-XseJTA2gDIz&Mui0@+ zlG3CssX!``Dx`?y@C+B{Bc(xBi%`5uNCi@fR3SwqR|$QSq%$kt(E!yWQVDuL|%p)$29sYdd@34K$f z3@Jw{lFA@^hqu_ZivhSRf8CNCM zNbaRji%`6>sU@kUNEuR=lp_^LB~qDGAyr8c$d09uRZt`OmxGo9*<1-(T#-~Fl}Qy+ zl@yU`B=_5p&y$j*6e&Z>l8PW(lVwts-Uw88t8hGO)Z~4Dc%DIZx9unR%Vg9tq#UWB zas7xZDcuaJfa0sSYA-@_zhh>Qd6U%S>EARVtDr)PNXg#^Z#T&HEln*;Dv&CqQBZtW zKrQ*2j4S70Kghm&R8dXOg+GL1|Li$VMxuw%w=5 zr!)stQF<6O3X11IwU42>KTEzT|3W^c^Fe8)CqY@I&p`#H%)gOYX#f-{y$ouOLVZb4 z-EHz8WKlW^lvOH&3QBK-N=iF_hWeDw21QCg0lAuF`2v(w+Us-78p!rCty%$ER;dE& zSMMauPyu8|W*8LjFEx3pH*Yh@yh&(~_h#sTX9Qk!GnCNF`E*6p`FtnVFO(Wl8-Y`?quQeumXjAw?wjW~jLcTH$0h z3E2~0nVOJk6;R!c`vU()QN4183YoV?O~^ElcOopakZCDuLZ)S?37M7!+0n|;D`egR zsP59JrAV)kc}wcO5Z*GqLguZg_i@<|_3kVCL2snq|H50NSIGLhRWj}Y|HVB6XoZu$ zLgsB&P39%3aaSQOrJBsHkj16d`x?9%dWFoJ1=*EasE&X3ioYVLaWa~a#TBUunN|YD zcTn_}>8;Q!RCmWBbCq#ImL+0bO}$dRka^wP()U*M^`LkTK;}(?tOB9B`#s`Pj1#iB z3^gIsvY_~w)0?BW0IItUJ=Upub;Z(KQt#35menhBC1jZ^>b(WtD!oGHjnw-`cx&oC zT=thOB!&A@gQblO95*x` ztEr|(hMIqu=Nri4QmVlvwG88gEH0~Y{fHAX zEyp+^)B07DzoA>CSIE4B^p-$&wJkGF$l@v>n=2uUt1?c=;zmL7t2oGd)HF_xhmgg& zzlAIw6wejNyv?Ba)sJy0jnlbeT$lOi%Y4d`#~)YvU$mmRj@YO)_f7T2qq`~`~~ zy+Yrqrq zDiAWQOijqNVb$c@vsHS9%o~B?)vjI{w??n~D0n?6o^g=%O{pe5giOm&6EZEQnw&F2 zrWF|{WLlY;kZD!bbe^fzs0mq||CsX(GA#+RSMe0RLgvj-6EZDFO~|w&H6hc=)Pzi{ zs;2j%)M|_qvN(??^Y~sAWLk=vkZBp!B%hFJIch?t6{!iCR#r`pjF4$nYC@((p!n$1 zTcg+Gxj4R}gRDmqWUmG(_3F{5H%)JbULot7qb6ipk(!WcWz}TQg-okb6EZCV#nsbW zqu1jZJ-$+dtVc>Ut(jUzHN8eq%YrO(PQAKz=`E_JXCAe(YC7Z8s*DqgYX-$Nt5*X%J-h zAXUaiq#DWN{SnL346+_6Y8g_Nlp_^MgCOf$0$D8;(kRH@O-bTi78|V_WIeK=xO&nk zy%ETIB=OFR#ic-|rKx482|AL@JZ2qzGhV)TlW;i<;&^@mzu8xuTXO73nR3 ztnV$Eo8UNHx+J!y|l*~-q)o28bcRwR{3Wm1(i z3bN5^)I3(QjhiH;NEuR&)DN;OMQSBdnN%f3Ae)yOHHS0YG!L>0nnCewP|J|Aq#UV8 zDuFCtnVLLDSiTCqReEbAkAD|uaYFHIkb3FOf^3W&$lgB{vK|F`i`2@bDk&n>NMoS5 z0=%}y6@YBC6ty(99JL~85M)_O)T*k-vvmN`XDD6Wsx3$n~PYDH3+ULosS zCDlk{jPv+ii+Eg824rItK~~=&wKA!uUU?$*_yq}z>jqihQBn zCGnT}Enhb%{&bjHmNW{ovnK+@XBqx3zU51TOiNMAka|g3kd2n3)=w&uN{lO0i>M{> z_vtN5mQ(~;EhTDYkX?bRN^-|2WKTsA<7)H@nb+g*&c{#rAk&f{8#e{AxzCWY^ya7) zNhNwCQWAeBKHd*f4rG~&q!PVlknN?A#Z^fW<7(8B_*?Js7^D&@A|>%R*W)-+iBtvI zXc4s}{(iaj=ml9-1(2$#tzmLX;7%~30oO7xbg zRY?)OHEKQ?#%%^!ElH47(5;%h?q{hLK~`yrTA5mv6wzCw=2M|>5)|(xwG1gsZ;o1# zRHC;`txAeWZVQe^%7Sd%5-B1jyMi|jvg>$1$mV5?)Y~oRzPw*j09n2YX_Q_!F?iFY z0;xiBTQZJRAPs_SvCc6? z3bG!4Qiw~D29@N#t^``sKaU<|daEGoTceiRCS=Kw3ZON8*EsKrAnQ@3cT`Ens8Q?Q zR7@p1;rImYu+yO%_vEa9LVB|q!P$xx5~KW_ThS!0@<8qL3YfG z)JoK<)M_9bV~m>LA!JE{>^w?QOM|QeA@laCSHBwy!Z*&b=fkSWepFQ3eKj06YBkar z$?X_w_8{AjZc>`w49IHkr?)_Fky@E?6?&`mjw(sbZgLp68D!(8KsH92-W|H zRox()gEYw2P>x!GT9MkIl8jphS(Xaps*D>Yxm`kL4~l1;)U71>(v0h;w*sY zMfBFF`8`4{Nl-i*C_dv!S$g}a6-gy}hp9!>YM>QP-WO^9TF9IP#iLQnkh1jlQ!7y` zgW}^xZ$xj6+8D|2$#Fq8Zm()GYguX~Qdzxn?hlh9dTZ3il=Ns#4Vg0_o4+iz9JN7G ziQZ9a5jDS8$kI$of~@*1DMxR=lCA@Khv|)|HSaB%wPsNKnxdNQZ<<;!wF0$a)uitj zy?&oiX%e)izZI`8y{bvvAiZT$1!QM@mD(t&M(X~0s5A|-SGt^PQfWV_NGgGBhN{#? zm86!4-Wt6=9ad8_$c|eQ6t7Nd8B&(s9LQF7zj|ei61~H!Nu?3B8nxz3*taAoKB}ag zdZq86lHAD%y`#si#JEw$Mbv84lKY0t-5@*Wy`(I?In{Jd=`B(#F|JH)SV^ji=xy$i zF+@v(;`syFUuNv4H=`t@^-{}%tVa$M&ndkndWWf1l_bk3HP;)~RWrzbUpxh}qnZZU zYR{-w-fzlMD>80Sz4E=PGBr0X^z|Uimm`%xwmQqyMwN8cfo$9wy<_yc{estnEOWDx z^i9&6qPLsg47FZrIci0ajXOxKOs%S#9Ie#;Vca5VR7olrBl!bDT(gpRyGa>(d#U9> zcJzz%RzOx&m0D(c$eab4w@9r_ia@qX+<}2SC_cW_GSsrva@6{jWX}uq;zyz4%t=y~ zlmpoq1!_fVC2BQl$%8|_G${j$vrsFM%Jf#KRjH*834PNbn}ZCsEVUA;LW)RZp!nYQ z(2y?+vU~+nncfPuDz%8(m};^P++iV$2idE0Gss5kCS^f(<VTnQijwE zvU6cjN&YHuncga?M#{_x`EsN|kY%Y-8&ys6WsVAGPmWY1RY6v1jWh@w;N>d zH5aK3swRJ(ca+{5z0KcX7Lbk6tD00*rB)+ljtOx&QooWMg(9^wX&AJ^t-XeKHEP|* zN)}xuN^(y7@O>M^qgEu9>8(;reMfr8Dk+m{N^L zp@J01D$S@S&k#9kRmRn*`T3zoic|x|XWpgsk}@QJS%^!4;&DkiQffhn%YZB{M=Fvs z3qxEEWN}4OnUuRc#1%mnS0+_S#VbNw8Dw!)QjJtz6ymBNi>s0Rl|ie5;v-GXUlqJ5 zQVkT(-_`VzG9-UZh)aQN+zcs4N?jY`G9Zh~k&2|ubs;VXvbZ9tOv+s!;))=PE0e0E z;te6L46?W?sYWW_7~-lRi>s0RO+l-I;`yWIZw}rRsRoMY?-qJV8Ir#>#HBzsZibX2 zrEUvx8IZ;0NJUcS_7Il?SzM7+Cgm1~xFX2n%A_i(`27%923cH{R3jDd2;MTOO7eGx zEE!OorNk>M$=@Y$a&MLbSzHEW_jN+1+Nzs?nZ{b~Uu;pxrwj?VQ0Zgu;*Z}z#`bYuyrs+UxR%eQ*$5e1=uRshp=(u@r!h@*|0^h z+hI?_mcd?y)nN%tWhZQB*io=KuybKoz;1@!4|^2$6s!t+9p=`+xUdaj6Jax8vtZwY zT?ShWdlR-54$nTY*|2k9H^9CRdl2?C>=jr98;`@cA#4-awy-I%9@t#i0@%&4M`6E& zy$$;ZEP+G44r~PWTi6${6W2ivu>Zm~#S%ClwgC2h*kiD#V4Gq|>}=RP z*oUz7u~d?>$`Bum!NkV1I&r3fsQJx#_TzV5h(y zfc*ycFW48bCY%my!*+rl13MXZ9qew{i?Dye#&3$Rg}@Gg9R-u$1OFcEV%X)drLad~ zFTm=s4`E-x*4WIsHdq&I8`xynp|E3MS=cPtIj~D$cffuIyJZW^73>Yzgf7f0>;TwN zu+w0dz%GSd2fG7yFYGZ`4fYRMq8n=n)(+bewgYSi>_pf#u)AOnz()5Fu>1eeu;0V#u=ipAfvvtB z&STi-ux(*eU_G$IV12NYVRK=Zz+Q&^0rm#$ZP>qIP1|Fw!}ft42>TXn7VIoo5q2f) zR@f5QldxxDFT*0(yRc7T%{w@^9&BUS*I~!Ou7KSDdlk0jjyPtpKG@B$AH#kF+h{V5 z1#EBFF|fI?3t?Zt_S*@0U?;*(hb@7<0s9oT_RjcR4R!$RB-ll;YhXjL$6)f?ldr=z z-38}5Y$>dDS6nAxPr!Z&OYeqSU=w$D?g-cb?AI_i1#1d61bYvb-2>|#_EXqLuywxX z+#g{d!nWNLKXL`D!Zw?Va|bpDwgmQP*cyA`bpm!gY!U3ou$N%l?Tvb2Pr)MC`um_Z z*br>PuVYNu&9EtH%mM6i*q>nC8C(xxcf($VHSde-H*5;*2-qChZLkt-d=KUg_Au-j z*z>SlFJ75p%V0Z9gAaDZet0#8y$YMKzjHgn(g)!9!QO%W9rh`#YdYo|)(?B?KwSS| z>mP*E0Ja!5_h777*cfc5L$K}+#rY3A<}j=^*rSId{}EX0u%}^f!p=GppH0Iant^>e z3a`DeB<$O;HE>6=p-Z?9*W_?jMb_8Rt~(Fk&_gSKt?YEPZ=m;?xMPvKl_s}3XuR79 zcPbOne-iFgcEnxE&bUL_6?Y^1<4&X>IcK_!-Q}*uEyJD1bIAXK+swU$yN#D!(*4$@ zT+MCa{))SfH<9H7x0U<5+uD7IY#-w%gg5_h` z>+*ZMiGClql~22E{QhoRe~{b3ABwN89pU!yN4h=zQEo4PwAbX#pXyHbr@2%7 z>28)k!+pn}<4*PGxwHKF?reV{?&B`PJ=(>%FDv4nY98*V2He$t&^_npyI=ZC-LL)S z?iGK9d(~g*e&?@tfAQD2y1&+q`Rm+De}nt0ztOGoH@Wxx&F*jh7WbjQ)qUh|cRulb zH!g99>rRy1)`_y)CNbo;O)PQSC+>DTB$m1z6Zg8wiTm76iTmBoi3i*+i66RM6F+jh zB_4FUCmwQB5CB%uP=`>kdr(!X1=&&K;as?hZ-((jA(3-W`^B z!5yCXwL2p5qB}D2n)_DbAMV7&YCe}()AuJ@{LDnFKRMCnXC*fAvlH$9)I^6rEwP0^ zBe9#GoA{C0B(C)rC$9H}#EpJl;$}aXxYb{pxWg|<;L3fqbHBI^ z3((ED59gNBHfMVG8zuP;?{AfkZ1e69N@FeFy`i*dBYb+H^veyrdt2$IY2JOHbOY!U zr9CeS`R?7zyZ@**hO^klIPSdQeFKC;ga7uK8?-ym^==K-PQ!U;v>~#rquMYsTlG8N zimR|{MVy0{`P+TC7gz15DIv?V8+(^jEjl1*U94*Ko1w>@2jf0n;||0A+MaKTnzvQ0 zg>iQy^Ny-5J{zwMO4se^-BhK?Z{XfbNoI0CrN-4>SHYMzabUYpAip8Y{ao2Y)_4 zRN7c;Pc8`ikw8@owB}K+lE#_RSappvrLnRb=fYQ=0gZLlSoe(;`&BEkvHmXI)4Qv* zsuxy--i-Y#jn&D{u6|bYVuEu|YJ15KXCSJ3ZUj6{Dx=KeZ^6m+x zGcF7>^shgKc{v2@?-}*(#jC|$>wC9MwSh$;u5n~;y)kGGaJpA>K>s#*GRFJV`o%ehMCY!&)`B(LNecNOjd zjC%2!ZFCOl5bxceG_E=|%;bWz@a=rnHry%9^LH)`)Hoj97vXL~l!wxOR2CY9P?73qy~0b`7-mTEYA4g92UoTG+SSZ@_)J zX1?>=VK#0ft#ecG?tMYn%X?o9+RLQwFM`%M=K0UV7+-bt+pY`sop4SVcN?y-#+6G} zoz?f{_3}@xS=Nhr-#am6`QKO0qict_U*i?g`ZiXRd^^yN`F6N+8NJB4I{Db zToI~YxpSb#N_;3A`p(1oVOc)^Mxa?ayoa?|UI*n>V}jChtjTqhP8tlyNa9*mOYa-{ z{&u@Sb8zlk=Em80>p8eD)gJrfj57NBxq-ex?@2grcJ5!uxCU)=MmW!mK)yO`zQxE61tS!9gKN{#F8MR+~mUA4xwxvSxk|cSd(BZG~g`fYNDLO%E$I>J#k=)jr1c)GBCrd+=&7 zqTU_w3cFmX#N+lPR-(;)VYYWKtG6-6SLNFh&%Uo~+{t)lw>>)Q-+^`*4)jVC_Hur^ zkN3Bc?^ayvtWxnBNd<3e=0kVJ-M`XZcr`F@Lwo6j5cgHy|Do|*mGx{Zwn1wg6e_)! zPXyP0FO0S!u8Fp9jWb?eEB;|KgnhFs`sz4CKT~ZS?u+p-i2qjMZp4oM;%fpuacQ70 z<_E&#WgIu}$Z#&m(HgH=tKzyYu`0-RE-+rHfnh#70$-fcMBCXYW^zvEh}iu?ti&&t4$8i zIH!?5=5x>P)E*~IBi**XcbjR=^GNsOb*o!7sbw3b#tLg(hZ;wI%{&CxzJ<#IuZcAm1eZ>ZoF4+)VJ;t z-tDj6saJ=5Ex408L^V%(@9dy8UYQ!NYK=R=3Am%RSNq0&qTE^9o;U6jJ8>J4| zxnpcRoqYK|@&El?@qcrl_~pC9Z|iKF`G0bEm^(OJBQpDi`77gUcAAbc53A%XrN(R2 zp19|?YvPxma4*!j^YPqc^W1pCy$W~D_AE5UcVZg(zTkT@at6%PEDdTrPdA>E8~67Q z;mOP10ooqts=cz0=QDd_jK))R>6;;LH@y2{@3pLnJ5_tHMgE1az1PyXEB)W;B3}O% z%?W2p<7#mN?lSDP>>s$}ws%C{C0%rwcMEiXQ+O@AR;h8FJO;0Wi&c|%Q|?ldE0U4) zSgP7I-eWc@ZQM0`Jaayxaa(WZ-IGdRbzj`L8~z?%Qz{xK-=Tk2sd43(R~+-s!0Vu` zgVEyxebpU&9&s;fmd5drE1X?bTNtH%d@ksp~e&1SG|I5iTiB3o;L2$Usx}! z;&teKeTxux3SK#F+>3dSc`%-EU)Ks6SGd7e@BX4%`uAbAFTmY{?eFebVMhL6L3^L~ z5x3*0zN>v3bN`DOA?_N!8vK8M58${Jq56~8!n)TnI=&V%ABxune}~+8G+s$+c#U6O zwVijvHx!g^J{I3WP->i8(xa7qHzjqECXsd|?M9kP%8>Ra9YUHxI*xQAX(nkl=?v02 zq>D%cq=lrbNjH*iCzVNelO7;FOnQR!3~3qZdD2Uy-;@4Cs*_eJHD1q;JUy(_555tO z;O>0FxMWv+0;l(r@4OPc_)e|`&V8p$zncv1LwsYqB!4Q|CxAj!Kd#-V_K=u^V zxcW5Kft;6iN4xJ$;T-JS0BcSAHtwnA{>tAeYptCfoJ7;k^r2`7ZS~X7c}kZM>V0 z&-~VNmtvd1H;S5^e4nTZpX#0En?Yw|o4{vz@`>LD`f1-0iPfMViERR(>8*~)8Hw>w zj>0y9PxvNao5Cl79-r#13GTwDfw*VJHi6Ig)&*~gZ33V3tqevw>LgHOt^jArr3TRpB~~j3$RVN3`X;AUu+X@8b(XFLorUm z9flDS?nvbJZU(jqcN9KP^zLYE6Yd*sdu)%vCyNPp9KP941+!8k% z+aI7VybFMB!rg(wlY!mo#(Tl*3VViJ2!Jc{dIJOD*1ojN~3)m*yQ`j@_p2jxeeu|ao-OqGA zK7+mVu7YjCRoz1H&#_Io5qAZ66x)RRrMnXRJhlnu0lf`doJamCa67j6MBe`t+fM%swn<;bHswd)-2z*jUw#?5 z8(W-T{ukJ8=a)m-9$Tz!|2+7c*kWz_UtxQ^{|&a^@-Ja~g8wbHC;C^h&3gIunH;tW zcar}DIFD_@_4^gznb=|_`#)iOrvEdx=lL48m-sri1AZl92C+@x#pF$FFZHXiz0ALZ z?E?QEwhR6H*k11cj_noxBWxG>Pq4kx{}bD*aKn?pC+7db_8R{=w%7XqVtbwUKH;wS zP1xSx$6T<1<_Y=S6)P3!5ue5zzuLmMe%jXi>Ro)sE8XNf<#mV@%K5W?!A3`x+lK>=lRd` z)OSvus#~{it*1_%s=f)N_1e#c{xnGIwGV~fb~`TIOwl|v|jrJ=&yscUi&2IZ-N2q4*O*2Z-D{p+jb24PB377 zhbwPvECv$L-?dZF-va~I_qmS6#)@DFdbgd0{vR-4-DRH&{X;Nd-EE%^{Ub1doxvH< zKLG>QJ$4TIryzaAEtDo=>G)+)_wLW=wE;V>zDR0^sm5xb-#T!^sm7H_6o9V z^cygM&B6%u??C#OeGc>=K>C+G3jGimupYMM7l!@_(%0;b(0>ByYaED0{{{xEKid~V zKMn@4cX$c(U%-I%ggp-ZS1@4x&8|TI9Sm4c+7rZaf&puvfLQA8 z3o=Uv#7cJ=$jlgc1@!)4z&aox*0~3Q^y`3F<~|FgUk5IQJ{Y852QGs?1f*XF#2WW` zV8A*w@Otp@fY{$20n(QPZvu}EyahZi@HQ}yeMA5YjVr(+HWC4>Gp+(RU?;(DJjjd| zcn|b>Abme@E%XH-eLwI%=ofN1H3J_4=K|M(mjpfvUK;o~_?p1=;AMdu zz}E(D1YZ}p8GK*hGvGG@p98-axP^4S4+g9s1a5=g4F;_L349T}EAVAFKLlx+fv!K`>zT1@DFK2Lo0pcpr2a3|IrfUqVMf2o0W#7Cmq1s+fVDZe5A+m>L=oH%x&{WU zmj{Y;D>_8fgcW@0DdBP5_o;^Wbl*081Zia1J4_2?|TLGJQ%Pp>U$;hD?m!S@73Vz`!0p^29TMq?=t8sz<_mi z-|L|N3q*43dp-EUzRSVu`rZV7yzedGC;Hw7eyZ;s;EjD(fH(JD34XfoDxUoe7_dIu zcQy3qKx(1yJ%Kd{2l~DZ{-*Cc;BWiB3;wR}``{yeyTQl$?gAh0yBqvV-;cp3`tAY$+V?Z? z?fv(H@94h|d}seJ!7KXj2miDG0r1NH--7Sz|2=qB|ATzRyA}Vd|6w@S_CErCsQ*#$ z!~K5-uj}6fex&~i@T2{I13%vXBzR;0Q{ZR%{|SD!|6kze`t2a)*-yu|Zt3p_Z|x6* zxAjNBFZ3?~zu3PI__hB1z;E_12k+=V0Q^?}LExSJ&jx?q{~Yk%{^x@K+kYtdyZ*z# z-}fH@{-OU!@WKA0z=!&e0Uz!^4*X;P3E(6BCxL(JKN)(Wr{xiV8_2Mq)p$%Xlv=I!3&IkKK7lQqvmw=(rI2aC9z=6;N7ztIu!O#@ABvb>JhPHzHgl52f zLk)1h&^B;cs0A($&4c@gc7O+jc7X?mUI88ydL{U*(5t~`hb{#V4qXO5C-ge-kkISF z=Y}o^pBH))cxdP?L3%V8unr5o4SZqf9pF)+E5KtzR}y+0$Se@L3i<>P$vJd2^hsdA zS`m5=^vNJ&e&|~8l+gRYQ$rtse;UY|H1r|pl^|=<&~;!w^ilAv(8s~mq3gl*p&P)_ z(2d|&=w@(3=riECq0fOEL$`qEhi(He2z?QJap=pW`4W&>BlK11agbRfbURoHeFK~b z-2rY1eH*NYz5~vMz6-t~^nLL1&~EUJp}W90h3+PWH-kvpp&x^94c!C2E%Y<+ouPZd zD?|5z?+X1Ayef1*@!t(H3WXj3-xK;R_|ees!B2)B1aAmE4Bixa1iU%)DDgiH(nmsn z20t6x1AZ>_1o-*T-w3@0q>qH21aAvH1?LMOeI)cx=r4hcIiY`nUkTZ0c)kkKBSJy& zzED5-i%=N+Whesf7hVD`3-3eBYAag|c1nA>I!k3>&?~@zb#gcc9R-m(!U^a&7_buI6m$|q_6QGw z>2Ma#5J(FQp9-EHJ{=qnp8>usoC7Q20=Ow$0w=<&z}fIHxGj7(*bJ`$Tj3FKNBA6Y zXLuBRO?U(N&hSR?s_^;XyTccPH-=vV-Vz=MzZkB7Uky)ycZ93pcf(WQUEvydPk1YM zZ+Hg0KimL+AKnH&5^jNe!t>yh;T_<=!n?rmz$?Ig23`s7Kk#bsSp%1XhYVZ>9yahg z@Pz}f2ag@N96WL0O<-!^Ens%wZQ$tx?*Iz}SAfF%}a4lFJ zcptcR-~-^?z=yzH1J{8U4SW=Q#lXkGR}NfH*}e*7E*`i6yky`;@bZD1!8Z~i+qRB zLqTN7$aleP^~+6lhKx(Ix&busuK)+OK@tk-~V zv|hV(0Nv5Yz+0_PfM2ja34Y1?6!;bE)8N;v&w^jKZUVn)eIERlbt`zM^#$<1tuKM! zv%UiU!1@~aKi1cmE@!3xN$~Ubr@-&o9|NDZKMx)rh@pu*I&cZ}v4Pis#|K^uo)~C? zD+1fWXkaH84_pK$0~dqoz}LW)ft$cH1D^o%fm^|1;0s_m@Fno9z*oQ*1-`!Y09G_H zG?d4OYT${Xt>B8#3>XbHz<6jIm<+YRbZ8#Tgm!?Zgm!_ag@Q~#{1E072Uhw(L z?*k8C{!8!$%kKwYxcmX|=;gl!k6r$I@c88qf+sG2m=h%F<)?$0k{=M~f=-+cn^9<_` z)_+3(!Fm_;gVwvDAGH1p`XTEY=!dNLLO*Q1ANpbIgV2ApJ`DXw>m$&QSRaFa#QFsE zpR7+p|H=9k^rO~I(2rW5hJMWYEc9d6=b`^>-3t9@>kH72TVH~H-1-Xi9_wq+d#tZR z|IPXp^xv#sL;v0S4fNlw-$6fV{Q>$(>mlfWSbv26hxI4ur>w`IpRyi@e%ksA^wZW~ zq5o<99r~ZvKcJtno`!zLdItJm9Ns$}4D5df`qMsW#7}739)Py(LFjHd=(e~reN85jaKF0nl^fC6|p^vry0e!6fH1u)yGtkFz z*y{{VOa%@&9qfZX!45&6U=Ki_Xb(c4XfK66$=(=n=nI|`k&Lqrv?rlg z+MA)zu(v>;VZR*uOnVyoOuG)9vuB}mb`v^p&q3$y?a&2#Cv?HS2)bxr3|+Ke1zoZ) zfiBsvfiByxg)ZCw0lmt81N18UjnHS=Z-ze0ek=5_{dVYK`<>7)vi}qMMfSU(&$izU zeYX8y(5vlhpjX@Pg+R1&kJ`6FkJ?{=9<#p$J!XFedV~En=neMQq0hCy34N~pE$EH*ozNTY z|As!#{vPys_79*hw10Cz3Z4Gn;f&k=fb%l@Y3P^P&p=lYkxvH$2cChJzYlto9fF>) z2cReHLFh?)DfFbhFLc#j23@uHhu&--2))^U7W9;TF!Ypt2=o^FdC*(z=R?=*!=Y>T z3!q?rh%9fz*lN$9$rhHlsy=!Sg?^gHbL zA6NoEc;G7V!v|gme&oQj!H*rdhP{DL6MBXHSwgR{KTqft_N|0oVSj zLa(&%B=k!AzX`q4{vM%M**_ulD*LB|USQmgB-rcewrL!V?P6Zjcpxt273>K2bI932dx74J?J`c*+FN6`yaH% z!bp(1Mb(jzvFfe za6%XNB<_F+){eMUxL4t>#eEN#8{`x-ZYS<7xc())19t�{0)dx8Od6`z!8WxLr#* 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

>ABQ`_ZsaNts%le*c<44rz4NHH(sPl# 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 oG1DSnC@DrEq#=gNLMjb$mTqa_VUjmwiVQ$# zfm!25;yQu(6cKk^i2jJu8?|t53fS&IJkV>%1!4kTHEoi`FrH`(pn_FHVG#FiIw7?U zrJ__^X)@j~(_dU?# zrHw?yK-g!bdBfq}gN*=KK&QXTfGGnA3#>Zo@quiin6 zQGCCK>lF5AxZ$jl35%oQHWjyA8t&SnfNRol&z3Le0Tql*8Xnr3;_7X92G%`GDGW`d zAWYMFi|vY@&A0H*07u7_g&_blkQnAhLk7g8nv!@Zi2(~9IL`FzfQ0fJS=i2!Ivs^< zXgsnD-H8Sx)2Li*J~D$g)_!DHcRszlqg8jHpd)($1$97rf%J?85!?rbaWuBqqGQ8* zWFZ(!C|`w@xZB=3yTANeE#L}|h`j(i5I zgp(se4PM;Qf{4#jpgdGZwKqg^2Zo|HyzS8yr9<;d68^gb5-#UgC>+Wy(#F;*lovuQ zp+*?rX3@{fi^_a~-V}K75}nZHW?WQn_+e2XlcY?xr?j(hw+$9QQxXVI$ z_k{B9Q|QILpfHeoNuj5lP#7Rr5}q}LXH5$ITb9i68%Z zVchCve0se)pRb-*BO~g0HL_#9qmbW8$nPtp`|U%2G_e`kotx(@PzMckU9nubT#59S?2b z)$x!9OTM2CRumRAI60{FhM1)WPZ7et4FQytgf@gpE#SFhLnOF{fqRI6-2sCEs%L=) zJK%ztO}im6D7O#zw;DPFnx2r20T~BaPzT)WEaR#0J|ay;Js6D+Jyc07TaKnj5D}SS z7%xEi4^ulBXe=09lt?G7u2h~?A)ewo*S=Uukw`@ZR#ji3BS8-xQnqXf=osZPFzvYT z(iU&mVH;{)u|L5L2WWq~H5}$$-VH|$5sDj*IigA))^HlAl7>K)oU@VjpEsl}Rw0Ed z!1l_aeRWm}!f(U&F~FR1kA9G+q1IPQa=AU0YK)TvxVF0lm@3?*;>DZ4WJ!Sf_LTrr z@s@y~-dPHdEpI^*rjNm&U*esm5)#KaWkMD(Svg>uJJYWDqIjgGbi}((e40BJ56x(O zp%o#T1PblIIO#zhF5qhB-}c5!%VX;2xNz|oO8)!;;sHJWtG%7rb{Xdy!15Qrt< z-H?Ip15_dqRb@opS6s{eM0}(WMFUcgjTBdz5_svv-Fp)lbtpxNq{|?B0Sv+51{p7A zGB6z@Qfm1YrMBIo?m~-t3N7j_w5V@4go%wg)D^9xMLo;<`hL_XVL}5t^em~%*Vv9a zAqgGJyg&!F1ke1x zod@~P(L-0Wv20xJswXsy8&cks0X9q=#Z-R!C?eM48A1$!{&|!umu|7Wo-Z6yt zolU`>AUKI^>?u<>aiGA)m49l-Kk;IN2`@I-1n^IR6oj;op`t+tDu5<-;+7*!2;-L0 zp^f0ZFA>zuqZ#E*L%8BZ+GqiwZAusb;$(`5ieBmvkpZSah)2@~MYv9z1)bS~q=9n- zw07{c0g2EhX{xZ=%Qb*R1VSJ5aG^O#qR_xgr_7?ii%CATXP`(EwNol{Yoq!EMr;X@ z+ZR9whKV$IqqU0|lwZeYXP_yHso*tIX;yOG*%}l6ooHB=n3$6YO%kh-EueGLppj^M z5NzNc;yPhD8;U!^#z3(>Llq1L<$9O_$*wOYnNb>XE~qM)puJxc`M7=_%AL zOKz1H6(zYfRQL7d)**|9(5o%1n4z-=hE8r5RXgmsJ?{L=L#$Y71piW#J7KF*hQ7YD zRN=DZ&eIaTxeHX0GvhAObPw(_Rj`b?D^!>9jWG-zMBkswg^guW9I*1^0$(>2};( zq33_m_PTH%XnA_v-&FUqmJ@0Tca@9iAehTp3Vq}%R6#YDOXzcuTuLqEwsJKJ1Lf+0 z2yNsV6b8z*glBCE1LV5?eEE75y2=fO@{K4olbZ_9W)ynKEvO~lRBlPFAa-)=_B_4~ zZMT=)PIzxm-+Rj)sXxI??nD(}FS(2G-j%-hl)LfcCU>Xf<|X%}>85fo3cch$G~HD0 zE4=ro^Jpp$pin9ga#bnEc69Q>Md2k+0#fG;i=qOot@2cxkV=F-z}ya&M+RLXYGA~K z4M?4bU}dBRHyR{b!PGbsQC${G%t(|J4DZ=|iIq?y>~QW4&KJ00OkvZ5Io^VSRcw!U zmU!pz`JENsIpUppbDBkSn#JcBj-QtY5wk)aEv0;2GDrn@=ZtAic<1qXDUNsth8gu% z7;#LUKROFq<2v9SSQ2$G7E?%T^;TSG1w*MJ-wM{EOolN-0RxC4T8mm6ih9cT(*?rH zk60m;$&c{fNBL1|K$6Li(h1X*ALG4|^5b+u0aD9`nf!#kd_pyGNt7{xp+&d@go|nOTVuh1D&f)S zol!G}Npn6u;RS@7u;IYol!5y~jIuoo0y||faM44XEQAu_9hg8MN(C=ap(ZkTebg@W zk>l!<2;}X+b+mZOO)o%7wFrcl>tkkG9^65!4DU~8x&&(oN{g;PaD3R7^Lr}Vgft4Pd1L9!uWui2un?1g;nKbu^rAEzzv&5n3(Y7Ojb zWAw)8U@Q>N)%k5qtxBjk!Tfb$!5s;9E-fSk5CX<95CsBVMK@$PgRx)2Lnj78nBXA< zV&*`Yfradk_WgcX61{+-Q4MlInGm>PONL6o;0YqFA+9WHCqriFoI=H7$8I!YKqub~ zEp)C!!0HSZQW&Qww$6a~2_{Br>EcF;WXi0R(4EE2gq_uq;UhWP*je`6Lf$3I9j4|u zmOCnhuN*f)sK%X4H2{OIA@@DZOn?}C4zw~|^jClgAL@oIyd#9RVej<+PonP^ld@+G zO1D-PB0B!`kU+bZd#6DD9z&emW8fb4m~attcy;A-$ZFA|oGmhy&_ff@r?OCVyRmN2 zQ)MX(gh&c&jFrNqh@~)~VzhXwNu^jwKO^OEg`k~dtsI~bv~8VSXi;w94MAW`fj4+r z5b!4!0(5y`#K&gwIYaoIVSJEN7$4*m%|Au+PYD8`0arSyv=kFCSB)S_+=U(@@d)6+ z{ivfiBtJ}C2i}Y_s!l}>rXXl|5(Tvs1dgDg^g(q=lrwW2)x?50d6J}#Fx&}lya-kS zG2p%hQCo`AD?~470=vSHIlQB3M$Kt@&1rhgX%yoNT$AQBdiRfM;JM-(v?UHYNW!^o zVF<&x?HwSU3FW?{Dx@a2mr`~sZeIuw_fyfviaS6>11s*J@O(&kKHPzaM}+jFLi#Z( zvRHA)g>p^^@MH%G$KlPXPTWdP4jxxESj?T#JAm#Z2FO@A=?FxWcP>FBA$2Lq_&Z9{ zfvU=o>YIZ}gE-*^ks%>fC0!WC0XQebT>-~1K?*O!-~|~050`lzSQqw z&FfH5f*O#0#t$bzmA|uOYqfc@4}0uCM@qF4zgzjKdEJ_Z^!aL={O9O&!B|ag%!V|w zPsPm9gR2K4vlXyes=xt#vmlvB0(-)wBUx1jbr3cm^~t13D3MF_0@49SI#f>_P~Ej? z9kQs-#|~ln|>Ovmk*R4NOuCoKnVM)I?`J(=2m?;%y5aa|m0Y z{2F9*2yMhz18~-Zu3R39@=GsO5@3*R1pA8yvPa_j!Z0&_>9wQdtjo`fZtLTM86^cd zpcwc0&rKfob$Cfjg#WW8ihU|*$QY>TZOE^A2%W%wKtzQ$c9hPt8p>ldTnJlgV$%}p zXzi%1p$K)%a^-`81!R+E;fAhEMG2VG)gSgCtRq2)q@tq+^^!K<3Cl_nF2z-@9R&t& z5C^qCTXX73^kv7+tTyj0Z2dBzo79jS16*xX4_23ygA$I0Mn2>PM?II{w7GnYa~>ZF zoX2n4{`{ea{xplYP-@DJ1u>+A)j}c4faIcw*snD`sOE!vt7&kr#!zbZG8@JR{*ItR z6+41zr!2=&Z4n@SR^diU9k_8|Z{$Wp7wI6yB1r>zScL9l4RjiVbz3|PZK8JEOk9LG zKva?!1T0l<4t27saPz3HZNx2zO2z%nE#l3ELa+-Bt8ED^0g@)effHPrl*2&1G8{)r zWrPM(2bR7U|Lb_S`S3rqt` z0wmQYKw{7|gMw!<#Er7$9w_#}Y7>JQ(M*jv$-rs{RswpOOY8I84s5v!9wDIYp89`$ zB|wtEh_8*Yi`ul10w~1PA{%xWx|b9kfUq8$<_`30JtkH%NXcoPVv8wJcp?TK91+1) zm^^n@Vxl--B4+wGW4IXM2q+OdiP(^Eqr^4Y&9CfafYKigoe+x7n3Dbs9xUM)m?v(& zxUMCmryf+qZIsls6l-UINu$LzeVdbvu{dLlQKGT-#zJ5els;ZuH>^3EnI2^8E*yk` zm<~rVY)UYw8j%Db@+3wFNI!%j!39ZwTNhYpYDBs9=v%VoX6Po7fPRM|m@w8g7HY&q zmQAZnMmLCyun8eT44c;lOwG7EHF)_9ayg5|KxM_SfQ*@TFlJcQ097Eq)bwqq3KIk# zJ+PV}Ev};|Wl~8Xzhgi~fomtO$OVV2i23ddI`n7)AyGt+v7gePh-7jdklc z)~(+t3g9M7=uvLh`r#sdVlTs}eYUp#2sZ)5;rcNUBMQPO^##!*Q~tuHhqZ+?p?TYA zq7hq>4YXnw<85g+EhqryhTnu>-fh{0T6oe(QGgZ}(DI-tnic?SfM2Yv1Uy(6`2}-N zo8gbIJ0tC(E&#rBT0o&81*Vpcmp7&Wv7muD{940uQt)t*wSWaHA0hy;WKfeeKv1u1 znMT?KPhs$_gu*BmJ@l0&yrCzSx(N?0hqE9;ndUV_8I{%~)P50NQGQ1}1ajxa*fOMi#pv(*j*sLC&SFol>lkC2}j z`UFH@U07;(1cV<4XB=0cNBrVuB|fB+@J5FP>&RenjS>2aq|Iv-217B8>PVwy&2LPE z2l~de^&8vPZ_I=TT-V9mM9?rA%3)zcYoYrXmnWqjr{=3BO$UL2O{Y_0%&@e5W=yRP z_~8p!APm{SyMSt=(Z#T#7F1FY+BJ~tY9O=5V=i^Qxk(r&Fj}OG3>$w|fuXLQ7)D|W zECS+*(4CKGVCdTfg%>-cBgb)-V zZ3&9d;*U)A;;ZSu6=M{ir+LDwnEY%`+!7kCW5O+^(3az)iHy0`Qa9*i7PmoUiOYz(d7aQkZG?2z5%=K`Y%6#~(9vES z&&?!c7`IPkhR14YI_xI4T}%IrzUrTYtcN;UbkyVNM=<`n|rfBTwWd_TWT1%+1Mt_`z+VK_2jf6L7NO;SkItA+L*bHK;$wxw{ zaNoeX(zgB2CL?;>L9ocS=%ZLerf~)>HO8MVi*6me(#L_RG+1L;pCbBLQb^-7>S~H5 z_}GtYq?YDNZh8T)W^AO+Ng211IxnT%HvSk6x7(XHT_5`&P9g51yJ-OGH@AT|j>B}6D@OkJ0p94wB7>5NDlvP2C6f*RbFA| z(O5NV8xK(Liojg8x5dBz9&f<^X_vIrqMH-~EsE*y5jpWKQ*p@T z7J{)7ccGn(KSZL9KdPb6{}>WP*Xe@-3QGWYHyT`0>mba)1tb9+Y4Gad!CV96qf)j` zx#a|G2W}B;JYR^6QDvbdzy^sMHBC_O;aQo82B))N0geOer`6}ngRyA8(~Cg66LKQp zcP7m=y$`n-$Xp`OWXMd3j<=LG;mK_OVRh|5uLC**=0xZpL%IUDb_o!mue0zEY6}Q}xg@O>K#WLMmkeDjxXK)Cf@Yv3I=f zm8~`Vw*y;Q|My?OXk{rww2zN44c~s+PiBas62rz zuFl8Q=m^QBK(I-wjE3O=Q{+amOfU(Q!8`yJQK!NZ1Gf19k}ic3qmc%Hg;qpD(T|3b z9j*c;35>9zm|;-}!$UZ=uo9$ooiyBt;xC*;kr)z3l7SYbk~9(quru7nkxaNxhqqQF z0j}MfG2BS|R`^kX8TlV@{r;iu&Ljh#6X7YM4J@xVYW#N$b#5s}9!Em30!W;wLK@3c zU`x@nJg`u>?c6eqp2&a|1g2II2tcq%dZ89V5AqsFz}ROjfje-1u>5g15Qdfcx8C>z z7}V8DAHjX1!QTPrhb;%Y0}Z_t6Fkn~z;DI`T}c-;6J;=T)EV+W>_A^+;iEcM=*YsX zgO6H@v0G|N+(J~6V1I*s3W6}EbIUPWq)CJ`i>4_&xCG)MREKx{BtQ(nXlxEOfmfqO z49*@$e1ygr@{a}*LVwJIY5=+tx;Nn+Hv@l#Ngvw+&%mDQym34CV2P%rT!i~xgjFc> z7g)o}*k&_Q4gMnhz=ix4P+b>{-Bbs20k?t>2l;*KATz2b)M0B_&m`$`E74;vgT2X( z1Zf86!VpaU5dG`~p`I{2NONGt!hSX4R)HIEWWBd21Wbm|q%dTl$Xda{9#P*(gfOl? zjt}IlF9}NHR%0zyU>2Yvh>jTAOaqYE?}4N!%+X?nI>n}q`LnmenOj!Mo4BQJD%^SyYGsBL>ZzaLOeZy~AJh!`_bXKo|tGj6b$sIuPyqZNQEYGNfixb@UAfrf(IcwC|Snnc-184;B&yO zsg4Kw{#UuIJB?A5xqG9Fzs`gItt=W;BPdAJ=iwyGi?roH@_&sXQ4$w79|-=f`S(Qp z|DWdG1U`YGws&+INHc08-@+Ycqp_#MT~J%$<4b+)ppn;_hPt zyvTQ-FaV6th_=4P{j6-}YH^ zY6@svQ1i}vFg$i>GAXJh43O<)&Zkh(#7SF0wa~!?3F#`T_S#lEyFCE`I4HtGyf9!K zq=@#xw-A+-@@99LU8F5UF=Lh}0#A!J1|)Y0=8r^MK(O`bD)?!~#er@N0~=2`=hAL3kCm9jo1nI^-jQ1*hlrrBWQPym(Md(3l>lB%&2$Z(!P6FmzMfXTc&3dDXL|Vw=bncsSb7Krtal9qC z=N8ij*Eh&blTWF83b`i=BFB8!)*a&m z^}da?dkQ`l=QZwApuB0w3K@Z-E{tdp6KnOo&(g;N?nQa;B3fOt?=AK3%{%wr+BbP_ z%c%E6XR~WY4eQ!`DYQbec?gP{IEAWKKTG))vf!@1NOv%Kmz5mL zd}Bn;=NntxJ5Wa$q#`hFVWNh%5zXr)-Ba%82s^${dlM<9>g*RBqI>R@QMW*%Hk=P39e}71m0v6kkL?9U-g^GK`aq zyPqeN=*#G6T{X*3`g(5CLid}1s^lbn=iHH74izibHvklQLpO z!&-`oUhqsEzSk@Yl5?v>&DccjKj#9m$2T>F67Cn2+TI4f!)@ME{1rBZUOZLnKGh>9 z_3qPCIhM-`0%i;+-nnJZ7l6Pn~SNmg)-njs%7geg2 zN%)L87pVM5@yEP`rO{8?XK0`Fp;aLAM=!kZT$^o@sC4jtq>?u$o}9u&<|!)WNUnHh@%eJzpm7pp8VQWn`>2?_C*(}oZ!BQ?<~Q% zsF5{q5VLqKKO2N7s-LA;kx`(o!~}f)bYz_3$>+1r!ecasHa)lsDr4?Hp$ZXQ1&8Ew z{?er&4vXS5_>KzFMQi3eN;OKays5%{39qX1Uq!uw1^wAP_nQzqC~9J*f%Pq)h22Z+ zwL){^nni%{vZ;c)cUsptPu0`6BUp44Pd8`2G_E_kw6Vhd7G7G3mkQ$hDyre;XlqX_ z0uveRdeG4MRh5wyF4bt@a={pd%e4c+O2I3JE43TND$$09t3*_ZHTVTVGLeLFenN#w z==KwgFcX{WCnQY5GC!fxRA!9`Md3Aoq<}g%U|2O7O-zz2gL(p@A;(Ab^5+c?SSA&+ zJs|+_02o3m3`kp=kzgti^+Ub1k^B=?`w)G08j z5cjNbY9sQV^#;O^#!{qpaTaA9ufp!l$Bep#*$vl(n7=WeG%Sv4w)7>7ogTDnSKM|5eWgdpFS?$1p*uO>7EL zuF|bA&1cZHzz7sjD;?l21#O3ll&Gft1dL*`Y9rRfQnSNgG|L@`v6$tc01XZV)K)t8 zyN-%dG)_KkAVDl6At=D~83g zohRd|Um|5%R-V_9(vz9uIY?QZ`IJ|dQ^S>BBT^P-rQ8xJ%->`?Jt{A1Qf|wwq4yr7 z%+E^sFjBT>mGeoYtj|h$1}UquQoe?i9a$;gM#|!>lK=IbJij>SU4KH#y3CZpg50MD zOOdiHv*f`Bq+FJjXFO7NXQZqioQ{;6Y^N-p=cI0*PQQKD58ka?l`hXs%KMQ5+*I68 zX&C%1p1Q<#CZ}3;C?9#Y(a7bOJQPF9(oFgpszb`g%o+}jL&};=IvkpcluI(+zH(>* zQs!oU>Cl>7iW=IEl$BW}@5^nmp}t&N9U91O`Js`VQLu98E~ISANNE`QM|}GM+i6XI z`)kN^QPxvGL(0C4?^-?dd;P9domZc*k#c$FQzuF^g{QykM6G^G%85%#J5t=+ZKph) zsRv2RvmTqD+t4Rg=YIT&ZAh7t_0;7^xsj+nzcWr8${iRdZq9A`6T|1^d50$7jFkJ4 zvLWm3AJYRhBhTX*pQ_*foE~ziJnK)qnA^=KUPa2D46<8);>SqYpGk-(K&*f?rb}LV zQt5`yND1qb)Ac!-K+1|t@;%v_Gc4DioQ#x*b0Gh5^2?V1Iozx^cH`qZrN`bbW{H=O)F2pU7BIYrPX z$&%(|mHemNL3pYl=i67FD%A`z{q|F2NjoyX;?($D`Z+aSQ*ZiRrxxi!m>xW**5$O= z>Qg&&D17y)E0J<#CgV7DICly;l|;(starU7cj7s9&pA&$m_se=PyGW@uFCrM&md)Q zR?3&p`O2!DQ zgnIg-bKbQomwrxf)oeYZoV}VhGRnCQDO)mI8W3KmAxv%KFn^$Zh%4|CrmVr@xoe%d1cShn}i4O8yN}7G$OTHD^j&Ib5Vi zO1k9XNN1j$e}FQ%C32i5$I0mr(={2oMveGd`m`iJR--3<@j4*$MN%GMz83s&&utW<$PjB&kbhujeOGJr{sLGoWGQjqI*}L z-nLUL@C|2K;ETAbJpYM zdO~Go*MY>y8BZe{lmQC~rH7%Tz(DDvxT7x?@uH0vVd~hRzN?@yJ*_3F(0G;3=~=iUij z+I*_f=As5VI$emmk-0%wai=k@!)U1rjR8qqgUPm=fUYT2o>3jM2TG-KX>iV9aqh43 zQ)IS`rBWskt$GFOO>Al%z13E$z`E5`bo3Ug#Ed;_-PeE?bDu-)pcSU~9UO}2GF`Tu zN?$LxtvcBBIllg^tImZj*lqV+*~Z?L0BRH42qGO>{~ z5k|Gjs93PcQ(|Mz9~J*;-%yl^il6HKE6Rfs8QR=@z%IDdZ$oce6B!H@YByT5?4yF) zFzQlJWxe}v08Lj0>Hq-j1ZY{|ks|tn>#=5H%|y?)=dN6vSY=Z$~FG_OWjowM^mJc7A**RtmF-h=mb!YSvM+zc(2p|?-l1nqUa^>08^2Y)79vv-+pLm z1=L<<2lKILf!SKD5aD3bJ>&jHsVfFIqIrOz7T+YY;apUt;;+PL@Y$h#0k? zFU%lM1>8sEZ`Fu|E*|W5{{RFpozg(RFQ&S%azDjk4pqNHZ-_LKV-{Fqp$;=R6^qwQ z*qVxD-yAnec5@@bU|w7Ya=)UkK|+C(SdvtmXnWnbB`#I z*O?b~#>GCE!c=hI#(d&GW2&ZckE8bT!&+&Bm=a_=S)HW@W&|)bchhguwWP`3Vraw` z*aNsT83V|2Du^kSJFpK_%8u`Y%`n0xARJUg@!hb$)Mofxz$Zs_kj2Ra+Mp|A{92r` zRi)4fhi+Ds63)2WmhuE4 zugnWx0FWewXVr|h&VAQ9M({+}uXpdy2e>l|9xCAg;>0q*;HJ^4pPa9MA2mKE9y7i) z9@oK&-6ymoKKIEogKn+SzfaZc-_KU*-=|IbGv@bMar9@q&$St!6vk)8mrU5PFPm^< zUlGrhw)<7{p65-z7j!Hq_r-+%{U=kumrS|eH06BD_{8{U^ZT;7{~dE*d+@Z~@0sUc zH7MPGG1uQW_5Fdleoaujw);a|azl_ds4D+zzI24FC#R^lK1Hqq zq3ucKle_JyG&z%lyt2#A7akg=A(4K0dK6AhrO6Z1vhTL1#*`HA zhivgy3Glkhy}J$Eg%Vvs0Xn z(t%7^tD2d;gIjvUPrh8Xa`^=VwAjBL?i0Wv$Y_F)wMNikY8C~tncO=g5udfo2f$kB zIRR^oYHApUm_99*#)xO88#?5g51Yx*AFqI*hJc@&u zlf*BJ_G18~t4+5LJt}Z?IUnz{RY}Pb7FkOVOn^(XtVC6p_$u$BTPL*?W>?5F(*q)( zG?|$nLy((&^qz-Gs~_w)xjd+GhK2Ut%;X*lc>C#AZO;Ms&q|G<>qJN>mb!rm-{mx) zW2S;ym1o>SXrV{z)_TWw{h$<9~IZI^bQk5_!D5Ua}WA>g>%r zdn@t5s%Siz+3L27!fJW1_;YpNHcoK|sWU(V2SQY8}~stHFUY3T317^pGH3W)z9DWB1!(31mJq zB8~v2MOR6l;??}3^3p(n-l}8%uG#V`BuqC6bGWVJ^~qL!p8I=S)#rIp9^H4y@i{p{ z-mAAfXO-24BLl{{f5B1Z#bpfL(qYTb1o>GqWmmHYTDHnTQgqW{Llr%_1gNUxB@kN& zv`dTg=T}EA^#JEuSgXMuqB>wqE`VtOp#ky(QWAqskE0XRO8N%{ODzQZAs zt`?$;s#S%R=kHSPSr!^9nnn^UE!NSF4yPC0MSn>~NDD2G_;sOA_LPJL>zD0m;iSPDWDEhql{&D`N5 zJ{=D{Y^nQXpx_!hWOz!F?om@ zK6A!fM`7hypcnG4FA8Inkp!F>%}ot7wPw=P>;+;vVw@hDkl->KBy0-rjO zLS&e)QJZy^Dg&58TESuP6QP! z7T-kp59WjaSny{4r(UZKT5Agc;3%06&I+M3z#(E`I*#%zs@;;=A$%9CjbUpihSB97 z9a*_oyhwzg9E6^w}j;_lRcDK7hOi?xPwj7+|kBu+aODi)VLvd?rUJ z9-r$~@TVjcgORdD;*7DQ51UDr8JWwMpNtR$8gZj(uJ=m*wzm}76ZzpFzC!CD&ro8F1_1eR znL&gg%lVcTz9nkjf|#x>_Y3janZy_8*z&&x;O=^F+Ie(m+$xc` zki_*{%nJ&2$HKr-W&w*_n(od?ULD`E0wf0oT1btfFG%+x(zDDjfZ2`<4ipoNO-!v! zBn!Olvr0XvY-+rVF*LxE>MeIz@7S#NF7KG&HL%R90!~oZ99#$gF=^Xf`Do1nZtKUP z*xHL4Oz@y5t@nIgGm%-hG%v+o_rD zv3%kNBdyijUqq-riWRE|MMsDCG4S9OA>8y=1PQ$&@Hb>d>^<7K&to#r#=Iv)QPb*u z#zdI-tjwcj-qY=E9t7Em-m`%+te`No1lZ5$vH@_7C2DyH{R1JE80Z<^H~C&lKIn-e zE~AU6rjM%bYDy;WBfuDvzgZl0I1LvDFn(<1mEKs<9+>?vkhn^;9(oVAP$DfR#kG@wLGit z<)QAJ4Rqy8QF-qH?lGYy5_2T8G$G4TKSvdE3{Fus(`Y!-?z0vSu0)Q0KSuyL+8G#D zGSG)OavVU88~q$XsH)YLkjt_H|es=mO z`J^bcdpB!Q;H^^XV4F>a%B;G4!f1~dr+2ZgzxvR=!*w~LYIdl3_m1;kA|Cf+wx(A{ z%U5N)hM+CH}K9WI1U&tOs)VhvmS3}U{A)MO6 zplvidY5=41n(gw4yG!&fd%LyzuSaa;;U0}onX1))Q}yb4n${Oi)1Vcz z4xg?|nV}PBYPI1k4QQFIOPQ_RcFz$#V|b2!(OjK4Pgc6&dGg%ki?nHezAX5|^R>Nx zfnF0Y&qXNaGN$|6)>%0+THgvRD*0%VXS8`yvTMthEYaUg>M90>8(ckrC5i<&MG!} z3Ow?`oK)>hzyX6C=iOkViYjP zv3PQ9YOkVS08X3vSi~Jh6;|6;B^8j&l5+T!ihFijv*WE(>X=sj zHI0T<7R=yd=<~0{9-#C7aYM$fjK0``3o#uqKI=`;3gUvi(S6I%LxBnOZXDEh1F?3y z4-y0b_R;BG!my^F4v1h6+n(JVQMToN$p#a8W{lV}eNTFQb>3d3R-IZOn4{MN+k(l2 z@wt-l&B$;ne+3#EEa&w0ANtY0+&HjAEmG>n)7N7#-$J1Z;HpXpEc7_`PC33S`&_x7uLS1x6HQSdCa?10u8_o4 z<;lz4uQz$n#f?36EZ=N0yqO_ez#|!}S3?`F_4Z@AN$&;tBCtSecw2&ay(~`AOcS`z zN#JSGVA90nfZ@7?*2P-(LmfSWRdU;GB$ZZ~n>IdrOnPe01x?Kv4rA;odLro3D%NhJ zTW%A3)N~ybqlnsulszhc>Lj`%m=~n$KgJ?}IzOtG@C>s^gt1CwPKnHP-{KtrGArgI zv)0;B$x8Tn`j%6d*)ZmeC3Az?p@dpdfVa2{bD``QCTWyn~opH-NU?)0l}; zHHc|zrhBiWdbL9;(XsT>xlpI}8w0+*k&1o1aHgaE?G|#AdA>TzZxMY-k28Re3?Y{v z-gJ%K4-nt%y=7P(O}8Mrad&qJ?(V_e-QC^Y-95Owy9Rd;5Zr?V4H^ipca!%!XTEdq znVIMQoo7~4-K$ouT3X$`H@myahZ=XXZ1FXXUA?w7@WM^N=C&*30o7N&K*8q>^sdpi zFY-pF=FORIcy`pOl@I#SAE`Z$Z-@UvPo}(%6vvLW};BkU9tf%seO2>1fjINL>SW5Jn?y(Fsd~?JDyzNj57ecBSIA zEQjmBjGgj%Vivy>ui7g*-!PgBzchGLsAZBVB_D^aqrpv`NupduOY5_NqB~$X;TLQH z-+1++3k>fdjnL<1T)mF%FSQ(LWyWj+qDqs9AiNGLXaqVGvB(>op})qp$E>oZK~HmS z_b?)qt%xot>L9lWR~^+7Py3ZyO=j{id-5?!zlxsk;{{^2sQ>m^7Y59=qG&-6K#4=~ zuz{G--y_bctPwG})iEz$2Mb$*R}r)K9|xy;!w@_(p%dmE4c@sO{cPOdg84~N$Z{MDY+9s78qF+>G>^G%hnZJDBLms^PGqVcztlh( z5*zW&*$-O3q?Api{B%)%xz5sR1-AkF)p#tY|Ctru92)PNXX}u>{C-ARkS11JzdJ6D zP~coqrg0_~F5{~b?R^pXA5bL78)RcxB02#|F<6Ij*-Dc<*y5_>!A@*k5+i=p+J~Ex z)Io64su3SIAHio~2#T&%-|SMIfX^0c0uU9Ds6Kjgmk;P8A`#L<(uyGr|&~KqkQ6__BWNtfv zw8Nf^PWj532Y;O_tZ2t1lRhKt!9@D%mEa@6j3?1*LWyhqVm9@um@c#=KtfTkWD{xj z`e!eD#o;$=P-=%(Pk-LRGFdHRYOcJbz1q+AB*lFx%Dd)-4Lp^fWXtmyV5_Oy7OQJr zOT+PQr8ai+Z5RawB-E52$?sl~!@BBL256pif5 z0NVVmLxAB)UI~#&q%!@Ks-I9D)&idG%$&r;jUQ`t1mO;F_4XvQx~}$xt{@UDd9wM_ zVk>t8j0ig_4ERC{*Tx!mo*t8%J`dWoYRz$!1ip@0Px3LI#0)2;Ghq>x3)|WyimrRX z3uI~9!l~QB$kxP#U2CN zS(1F3dJ6BSOA;xTMIvG0&v>-Z(FB*v@|NL^VYOBvOi<#gO;~3o_}$u2s(o16DVfN0 znw`~wAqtIOct?Y}P&%oL@Xdtg#On3(pVj}!BC2%{J42-mf~mR!_9Yvvl99eJARX)8 zgIrShK1n&47hop028Xq-_GX&XSU#9Zq!XWvAgC;^2Obz}>IrE)GFQi)G$XquG`E)- zXoS`@T&3t#V8vSaIzu2CFK)o>#z{9CpJLap#)4N7Wc@@w0znuW8*V~~3P@IfPY422mx*G?BhWL~L$|;-c04pFA`J6&!kS3~ zMw4y5ggm2WdFE?DIjog{TN7z{MRj0z+u z7tys1Pmpu6+m-W_%c-R>nBl6zakg}IcC5)|;483>M*7XH`e?Li!FbYck700+D&_UK zw>{o|X1t&zvA^DE@17NCdvfd8x29v;gVMsqqpvReAqps~=e`NfmU9R0j4JKIUPzGW zytGfU=pp`;cr%u$X2^M*rKUQF5CVDH62r090Gz)c8hskfk#k zjI|qN)`FUiqCujxhw`hoB1y@sB`ENPW+QnhXox$Hdw1;|P14$XM&z&nwh|j1rfJ&W z_8?e{lZHPt!p>O~OVnbQXiRf3yW;E%1*?n;Q@9i-Vb=?Pc!uAv(-E;RUu?T<{U*dz z3{2`oQY6EsT^InHg*jQs#87-2h?ff!VL=ZC?La)u)M|+a73K{>Ubl{+kE!JM#75)r zbVSaG=JZ`roBP4snlx@eB^8a)LN#Iyhg5sR1XT^MPWSn&6L49k)mSia4r{+Wti|1F6Mj0&j)DEbD3F$&!Ceo4;?hI~q)4~c5NPO&gdy&7S}PT}I&oENK0O$^lL zcOSsTvbx|cM3@cvknG}IAd7zf`3_F3J_(P-D7?ZlfXPnqkHNhf%ML6Ql%wvD?Ex}N z^|tb;&!%}bj4CWgL;F-SY&2S->v41jDA>iE5bfZa5Ur|y-gA=Wwz(Sqd>bxyHS}6J z&Jdnzl>n?x((zz6b~a+wyD`;J(+tx#mIxWg1!lSIbwkcpxF z%`J%y^j(gfdjpu~h|eh;+gI{GpH!{-jedW=Zz*gWw2JqjVJ+SY+8u`s&4SA?V(D7T z{N-7$^bq-d$3ni3IC^=goH@EvV6<}#zLCN8IAp87OOI>FXjABY;~EZ&ZH{ggC8T{G zV|1Qw(9z3KwA5-U$|EhZ?Q zk;O>PaY;vHuY!J68Oz->+LB``oufh3H3|TIE8vG_c}?$#pd1K;1lA<7ovJ=gngpF> zrNk9*;`H$Cg@d?<(v^0P-Hk?;l!`b>z%puJVqBA^#2|JMPByAxn<6nR`-^_GS7iXi z_{yo#ZojS~k>`1)Soo5hs!jFzA)a>#W}WvE;^Pm{PW;FxwdKn*w#$-CC85U3tx1oq zd#p+8c0OMNf%wZrR>8aZcc!Frf7X?s$`S;7NwdH0j z+EX2$Jk3`E(kuIHpAuO51?e$bjC~=H5*N1Kd1B1Mvi`R)o zgnqMZ8G}C#!MsWh$AYRguYxr!UE2Xu>A?4W(7Z0FibF-~ysY9X_DQW;27a-hPKX`= zRg?(JzG^2<+9(f)#H?~_TLKiVjLbsHi>;raftwKJ(9IK30CA|4kzD)Xk;bOWE-5grzdiR>Y;_A4oK@wOC5|1@iS%X+NSy-O`il#t&;P z70Yk+hiNP@pW8se(%RC5#d&H}gMOr^IRyxnM!PRg-o0h4OW%k4-46P}MA{lbX2_UFY;M2Lxs<{oN`ptG;A#!-N{u0x61 zu#Ob>2fPX|GDRhuB%>HSE^W}O^qMWq!druJv{F4)C@$D_IKv^TeiM6X^U{1GYzoW` z*C+~Nf%?sM5sbMpvNY1Y|7UxVY}Tcc3mB*MwE}MBGgOeiYP(kK0;e=03fRCZgrS6E z=7V;HZGofEhJ2^@nifdy*NSbR!q&d|q8Bow^NXnhx15`BnNBdOS)VVm#Kk zTVa{h_wZ)6X9;QM2Y)wf{qdl_Cirt99dQ0rsh5L~;L!K}J>)S_}v z3BQlpLhH}2UKzdkvs*+@mlMt1{uTL7!~%$Ig!g2Y)Re3)K~bncoa50qFGv{8o9Sh; z1OIVz%_O&VcnBtiW**r$Rgp0DR(6Aw=2BNk*Ghz%Ixg3Af*JMq*dq7~@GtO;S2|r- zR#1-+235Z=u6WPaMS`rfz$466nUJe@>)S8yC6OiV)Tb{9W_19(g1-Lqw((!w=o1kI zq31q3k*3dRO6r22#x0EufU&mzHg=pccWj7-`z>;BLINv;CQEf3(OR*vag!wn_zvYWkm1&{6PKWv2HR=aJ#g8xA`_1b5@*jdBr~!;%uw ztI{Wj3^lUy-b|_Z0{gx@G>x45N`0qh;Ns6kBWYOc{}F&3}fvK@(POv#;)a4l20rawa=L(^qJWL z_?*+iS6#h{#KG)}xiEKB;o)s4dRbMSKlZSdQXrPbCy{MI5cGzg?avp);v_thMZUw4 z`vngm@I}*WE?{$*g77d@3T!ePR70i7}#T`9E4!x3#cL%0pzt zmQwqV%1|MC2i`R=7n0H7+nY{Rl96FEE!QQu5@S`~{7$7mgTi6X;H&nA?LXH}WR`g2 zRo#jAB@{$jTycK$$+y?~hI|%YsKpdn;NweO7)wk6VpF0Oky}(17S0UcclOrokZl2(uF0YA#OC@lg;8ndR~qn}|e{eM6v!bA|l zFE)7&p>wSF-%B5t%eAp*e@la9&rgyjfgB~l4Nis|>|z18Iz?5keOoWx+&t*rZe#eQ z2D>q)rwVth!ZIjrFP|+TNKn&CaTn{4%0o$Trgtgtv8#o>&b$eVe{LG?k0gOc8+)?Nk7$-g5KfAe#! zRckGXU}RaB-uw5J(saLr-BF@UP5A;+P-Ig@kv5)Q<*3JP(6n$&Re3GMTyfqE+f|3Q zFYhSxBCD`BLDryb5@BHhmc?$mEt89&)h^YG9Mh#8@khl&^{*%rxWnNF->UArZnVYR z5L)lAJ-#a%ke59|F-PQKCp-A!37;IgpCCdLKehlC!myE>P@M*P;%rjXr5Q7pFqIw< zn00awT5S5@gU@hZK{+OA@kFYMP?k-iB#Y85Hq*etkz-Su_+h|!vvbNpBaE^b)O3u( zYqo~`mc=M9roJ-c8SY@6&_DZZ)Ab zf!963#6gIBJ?l>u)XrIoltCnsVf1Otqn?!C z=_mD=q2?vPh|FlhtVnN9aaOZ@Lz*Tmn_M?4+8-%Z2WkQOmy;necls^hPCsOlH+&6Y?VjR4gG9RM52rV3ss)08en=S1j@682F z>@hq#rd8SZ;j$;YWu^kMGym_;k&JdJN=u^=)l1937xG4V;u(B*AQ2{iw`SgWUnivt%@7;-KEo3b7gRxwar-p<#1V_KZQD!^?j>Tqnthyq`ngM$vL$B$1_ZPud#eJ8D$W_W{^oNHzv1Fg@sX?D{EAfK*O%>WOAm7{>@9Xr+)%8Uf?`{v6pzKc$9{|XE3}(mjgT=k)%i(w zT5&V!RF-3jkx5SJY^t;e@qSq$D|eW{(P~N?jY*)&vPuTi_~;$FmU6Bf-$ftFLfr65 zpI`qzZSGEZ1m5d9QNu|~0!Z3KxYyVoW<8^wnsVV!A_sH6)cvvAVZ_+lekQlu#m?b12Hvra;GV6)n=j*exB>bWUlP-1aw?em+?}q`1GE}R5Oyzgwu{4Yoyo+5Nkt6vlmoCF`suhXg z=LNflaoR!@LFv5Yx3_5_+Sga)GA6=V9=AguMax|;KNjGZ%ii^gr+vz6)7mQifo;T{5lmULyLjVVkQ!iXRIAM(B7A>kUw2})D*TTRdg4@ z70nYA;N~OXO)AX-zDK;4MRg(hA8-}? zXgqDu!T>mR)gs8B?$@Mb9c*sHL(Y&2w2nYt;!+51_)EQ7YZ*NH%pzCH#6)9YQLLXv zzwtY1eywzFLx)y+*oA$nb`-!4T?WZ2@-}{fD=0Uiw7r&+CVIV%o;^+feJ`S3^mDKM zR_!HD)f9zAFu!uUcB|Y^xvOO@oWs0WJR6L~t{Xe}3-Q{V9ODz;Fm*|e2_x_|m(mn} z(1umP287FfYoh7dB-VGpzTAwEm=8{ZOv8vHPz= zuzo!1H_=tYgozRWs_?eyM?mJgn zBi3jq(RJSM(h{0WkcN0L#F4qUV7zZ#L7m;xB#?Q>z!yX3>#ZN<=8IyCv{EeXNfhi%xofR&WP?fJ(C9hChs!q+ml6y)$)qv zTD?=Fx(4Bm(|0i4v{!wj@b7O=0RbQU(g{7UdvTe%mE4=>5_A6=csw;eUil?Bw=FyJ zhH8`+EIBdVC_}ZXn4@&lZLyWC1^tI{8>=?;M)j|=8t%q%O-}Jq3mo&_II>PYohX9} z!$t6yJD>b3t;zUj_TN+JxbL_`)%l_vK*G=~ZTKQfqM^l+%$XyZjN7O-K(Z;)mfnX`Am4~VHGL~c1 z(Yv~*LivrH#s*p#>7y~V8G66{k|ZYtoW;;0sF@Bg++t0?|MeWkLGx3Og`#MxUIkwi z=m4$1q!}I=$uhRDGh_({J(M^6)Iq3$a2`S;ldDRJaUgRC1PU8#8d~YY7YIHSTd?IX zK{s}VjjIuj_Q)xjJ&mxo9kY1^nyS#Z1D};UIZ4gCe@^HJq#Pad%_S1X<}ZvcmTt~= z6`#L7ZXF3WZe3@wbCH zCLWnvN1%(rxdA>i=)x-7H8 z|KWH?E$XWCDG)_95UbGMxxWVZl9%*x$TO6l+JS}}lT{Uur&eg!ZyPA!3_I29Mkl-@ z&!DHzo9~(izqsz<{7Vh2vm&G=;t_juGE($@Whv3dg{f3tPdeMYx#<15hwsHMRIYMA z2CINx9Agu|8S6Px{92eW8hh3_((QYkEEnhLH858=hHB>PMa?IaLUTXFJ%Z$?hV}kd zs33YQT6ni#%uKDNTg9H|3#-8@S4i16s~RF4BB9{VtyBSbz@J}pE#F)8&1YreyF~0! zF$Rni;f5+-aiE#KB{EW(S1pK3U$x%$CEd>Vhp6C}ZH|$=9X}M>N5U?pC6?Lnv*X%E zfJ)8oXplA!cP&IXR(c>RoVVv-68RQtg7c>n)JFAmaPK%>9)8*_H&jDux0zTmatYr1h-QW7ezV^&nAfFCVzrcJZUPb%aY@FK=WeGG@2m>M+{Z;A%M>Q$$jc(9*Ji`Hf5YH`?^ItphX>69`{XEK?sT#y zco)bJRn_B;1>W{~$)vt|myiv40J8-qxBduZxw%K(J-t@O#)Sf6V;;^`DZsq?Nc5Z= zQ46a%3TZP)$CGlVlLVo5WP2QB^3!!f^aWCJ>07yhVJvVZMkqdBJda&PFSEgC*nV^o zHEfu~yRRild8V}FB5`P3`Gky$G4UqKVjl49)_pf3a&i|A-Sxk`aMleSP{l8#VVX&B zx1D;uvEdNK)vH{Y8b^;u(ME}oTgSWBPd+)jM}Yl;A0-Yl8=vk#43EyS3Lc*16|`~1 zxwTcQ(F0lSKauT zFNGiTt)LzT>3E^We(j9$HvV>ZS!YdX%o0FmY_YE?JtVXXkD79Vg4YB6y0=*m5i0_z zlwxulHlsy}+RV6WI{u`A_hVM_T??Q3GU4X?wf?LKX@N8()_jz29`m5#n&GV!l;*^t zXZTO#lXmUSA81j(jKQjKxGyj~4OTF!)g`)-AhlbN5eUJ<(j3a1iJZ}=mN!}hL+-|) z3N2&`$|u@_6H4_!C3Ug+gV|bS$h@@nk61D7Axf^^9yfpJP7*l({8}>dwIzTJdMTPw zH`>ZD?hbw5CnQLyM$+zxMckzoLD%&uw}n);X)Up;P^m3Si?FXbc=pX6mLOri48NF4 zMMwcP%ve6X4O8s_>9<7k>Ucl2^eceZ%@AyK4J5u5WR`Vt{PQze{B~mV;=^8F0#35` zg~MTALeY7FwvdC*_ACOC5;J2|;}IfcGgKH;4kFYL3dhg1#e!q^0)J@)*p^L^!hQH4 z7Uj^9uVa>98JNGrS=ktZQc#U4vQyDzXvZqO-hb}n@nW|BF`PV7 z0Bs1)=o;|^Q@955#8~>1=cXIHKRJaUwob!^fC+h_&sk|C`L63^J2}fm!3t$EsFHN( z{--wFC;C7w6ih?al8~&Z3w%88{WfAbZVcbDye=mhDnBVz62Q>~41%Eb_C$ERWwKvq z#Y8biD~sCXZKd+$JTWRhvyzpnA;_VrPi!4inJx%LN!-S>rNm-!;DG0iC#@8t7R0tF zI_6R~sXhU0&W!&Jf9&4Tfi2GZCChvx)Eso8N}e8671t2VdNGPC$eZ|aH`nW>g5-&0 zbDg{8jj>hu?X41yx^$+%jU0Z!CyR@ot(abIm+Z0Ei`1Xy+9s})Hkb9*jK$-3sg?@d zmfKVq)gzNUn`wi7DTQ`yonS7k9x_%4pQgZ5GqlyMPuvv?&E%{*7FNh?gB6LCdbRvy zSiL?~5#&c%r{7)hP7xT7-425H-xaFNwF9cFv(^zeS)ub1umkpS2)6oZm= zZ`!GqR>Rs;{8YY(-J=s~hdhh+3DQ>UP#0XewuBW)&<_HQHFamZQIuroS&XP03L(5j zKz+gFD$YefD9TeV;;dYUwa}vK{)@r8ouJ7#%W%3+%%iha zo1Ub=X_Q?Gp{rw^Z1>~Iw>afUye?iZ;6R`=H*nw@bJpXuh1mebu7&(<`F+euR3xu{~vOowtfr{B|F=HH?=VhE`uxtV^>{0P!-BK z7qeklHtKu3Go7g$n86+bMRMHwh$@+*pK^e?s%g!^7Kd={kSU~TdUB)N*t<@~4A;w* zSsEd{@iYX<^Q9U!gaVLQRBCk*no%~H+eV9{#7?GOyeo;>KjVA?gS7%Gpk2U6_3TNy zx+2aHo4{uDhp)<=?;vJJ?>h)P%JIT#fVT!d>XuD9Fq-}&nEtw0VujCl3J)WnOY8?j zo}m#uzuq?qxH7l|;9Abg2-CCb5eBCYVOfA*fQ0$wToAQ$1WJK0ZHX7tBo}Ds2zsC; zQ4GUOROr?}fgi?GQ%G;ye=#2#=VA&!-D(dv7w!;0nm^JsQ$D|DsM`VlE{O*L5ad_< z^$Kn%qF8e8l0Bo|l33b9_};1f{wD5Ex@8pwtG?S~rjrikNQpjOetzznsb7t_x-32Z z32kUJHzcIh(SX*EI}$9id*@JVi!38 ze)Fd|h8P5ffHI{Zrp2iPQde9w!y*{(dylY~W?pp*RDBJPQ4LX*Lxk9SVm6{CJeOU{ z$7JIVZ_WQiac8ITWY08*3}P$+3yh+-NT zBh7a<`28k!R1eAUi=ZJvs$XgmGQ^T#OaXKglhLEkG+h;Y;GX5VBEzDKE2m}r%!Xle z?99@>EzPC56$*hZyKK9d+08Hfx+JlBR9b#b>HJIWR{`WM+hoYKhr z0@uf=rX4AKS5zHPtE@8x5vpdkl@j@*Nq8ONgwrf+n=s*jSYMQFpLkga&=bXA`T*n) zZ`me`2Z9l80{pn-qJAo)dzlb|#IZrQ7RmGM4)W3p=8AvX3vcpGf$kA_;s4&6_rb-7-RVB=eEI4ERIxs;dqG$r`~Ht2NoBMqo=+3(zF& z7*!@Un55b1odUJyDymn6vH+$iNOyCO?>e;xv^PTv24xvw9{4l|OH2`Kc$IY24EvBi zg{#^Sm|y*xEI|u6R)tt?Jg?}Hl-**l8Q^4r!={_YM?gkLKNExE zk)Sn9awzm;PG(1PPhv+pWE+%o*n(y>QzMB1@=65%F${H=0<-eHq&%?)!kJa8IzAvo%5hU#!!c5O*fGxhnb79a)2YzQZ| z<3z!E^k9LPTYpA4N*)7zhH&hPoO>g73Hern)FL3xBqOG>`Rm?8V$Ph}gY!x9OmEN%%Vss0$ zXGF0|+K4=FKU8>vt+tlSI*e_%er;s=u{JldAK@E5L$#tjACJ{xfjcosJi&pK9>uDD2+*?v@k;oK~Dfq3FX?P-S8D(+gS>v+6g zN4p!2BQ*4fQ_x3Hg?&BW&|~QH*7PVit(N@{Tb+epF^n@kjjci(+=y;l#*LJ{+lP@L zuts1tNkTdD2ftc33sN2EnFwtXqPyx@_O1=K)(0i`n=*fCGI7Vpq(*`VSQQ2gGO1yC ziZR|357*WaD{eMds!MoyIQHmCBY2Qv@=7fyCQ-?96*xVi3R}q1yU@)=iQw+8faI&g zvX2YW|0+%EMaQjpVM$Miod-=ei#gzTn3FrA6v==sbJV~+7EF=(CSo10K2>+1&snho zyQWLh`X!rbMUWL@IE1w!tuSEJNu*bfKr;3-Y~&vBZ}ET$r9JsQ(92zNE` z-D#3^KR@<|lhpm%`ujqgZRxs)r|E)qpK&+?1w$tHWUSsdK)k}l8na^_?*yE|J9)g= zepr1U#u#wyPcF(M{0fnre0>Ge7b-6D$-l4f{Zu`%0cN8Rx_&B!uL^^RE+q6Q>CHxY z2xjn|J-iXI;1iwk_I-)0qt!ld?@A7PY;btJxnL-^SL$zkxuxrPmo_@I@Hae${I{R{ z`>)w`jAiXA@MA?7Jr_7nmH=;6*&sY@dsw~JumR^sT6K(ml8F@npS?^anFqI2hbB0K zpaJk_WCTN;ugnxJNam(G!h#fXD`>Y{B2nJaTl6bWe7|l|I_J|jM1`b?AVk~qm6ARq zIqdr|sDdvCz4wX)ta&Lz96YO#Pqw$%Gfj$RS<%!z)w}D15v1 zx(3t7%WRBDCtUglR@mVXGl;6>fK#!N%LFSGd>M!i(klfi_>NN{qjLyqiJ8TQU>0NT zjdJev{P@Nkzoo9JXULj1IS<}61KBCQp-aw6j8LmOt|3eXe!nUf5QGx(oGLn@Ur)gj zTl)O@_MthqW?gSm;sCW9($&W)yExv}{&Jo|5~S`~a#HMZ;fjPGdenXH zroJgxTNdK=0XfDYDycH;&t?KQ*uo;qC^wg>#0J8jvuUF|p$+*NwY^C}to{j0G%Rdq zY&&17NZk1+!%A)p&tEO5Y{Y_`C$3j3ua9;0`ko_#dJo+wPBl%FbV*GYwtWl>;Nd$xc=(x+@agd!q%&}qxQ`=EX9lmC7BEsVyK4H_;Z=eCl7Sy?rXj;y?#P#%q%6H*y z6{KI^-WTae1NOl}rZ6_V=>oGEm{I0`P}<32yuw%Q`qO0SR%SzA%Y`r)g*g#3DCV<_ znxT&&X9OF2$F@ou*0HtZdX7GR>Dtmy!SSTgLp}Z!quKwQX7#K*!K!v)n1d3>T-+g` zgQ_;7@O~ucG#W69eP5+vI-5L&bmP~YcC)`pG>bSo!^L_Gc?42N)E-K;7!p+hRprx8 za}&_P{z$(6=TDcx?b`y8Bdfw$nAEGa{RC>nF}{sgPRLmGZDA0Vom;OkveNFcV6Ccn``W1m-pQ$653%%X|2mrpox4}%Z{ zbzHq>wDS@;N^)z~dcL=brcGX-He)!>o3_K=$wFfgcgwN4dJ3)D88O3!)S~j5SZ$gk zEK~4hF~%DWXJN1UZ0lM<;~?k|*q{Dd+GdYZiJ(iybP>)I{DnwLS2pdWSv|q^!}|Uk zsWH;U(maZ}E%f_d`#!CD(9p(D&#NX_HG*xh;Y81bUuIH?GSCwsD5Ka3&jECQG{rOV zs8ecjYNz5%TOI z#@n6$H2Ba`$QOisp5#;)rs89#$IO^9L(AHB0No?P&U78)l+F^avnt3z&n-QC8u$E# zbuMb_yRWIj<&Fm!Rcf2B>kt>}Bn1i*=e}gH1}>++g|xpRTb70NrqE@duUEJIZ-GYt z*+^@AN-j|IyHgk$TC!XZNr$gSc09>~`rAE2pR4jgBX`?HF{a zPVE^{rO}8DMT~EGu)@DgZmwa5Laay|;!3jK?wal8<38`Al@%O0iM5u1;o=>m6@sl3 zw#c!VY?{0kr{x4L%&rcAeudNfP1ox5yG;y#;Qm1aL8mrV3AKorHGvNi7yJPxe?sPlXny0HI1FwhS@Qo|dBh1`nM|}w+Sk=Aw zceTE>MT@_^nh(kE1x=nE7Im0XEWeZfHe%9pD`uf>!k&)d?jDk^LHe_W>{E9 zIQc5hT+iaP0!fQmZ+jp;H&BMsQj}kETj@)h4|W(V7i28H-C_B(i5?<+w>Lg_H!xjM zdF-d6K^AEUCk~U>vzB=DLKRDjAJeF5(`k(bIBJK*e~Z#FakM$2=*q@TCWgR?-2Sd9 zl@__+Ho6zt?Y4q#Z;9CPRqp7^OqsGuRIdxN8|sl>~Sypzgb<))QyIhx%C@2}2yEC(gS!a70IFzZD#e`P9aK7YXki^=B*n z#XGB~XlK5C(0zfhg+AqnhM8WbAPD^5Wf@DCoaL2+4_08ASzv5%Zr^S>!NKSs_1@Xb z+bItz=;@8s)LWLmSQ8MGqD z9qh68pZ0pPwL~BA&@PD1!LD5d3a$ntPaN$03u|(oj3j5J=M}A; zZ(02*a9eqN?Kvd{cX2ztzW)m1o9o3?Lm5SwBNFKjR#WHjMj)sLt1$&QfY!C^Q;tUP zYhuWGB)1+o8YEBBo*EnqP3!U9KtHf{0%8DAaq!g&Nu#}E*ym^oSm0M(#sz&@OSHz^ z*Q0F3M7CG{--3G&SiP-8cXJ25$zSSykxgB!D|10B*n%tcKOZ;G4JIm}{uG=zO5#|2 zAP(h#YB8|lC6VJ%8GKd4Nslx74TkfTo{V7a*^g|0css?5yNdEe)jWlTc~K*3tZ8)m zortI!@1Rlk3!kwhX0T)2iS1of&pL9Kq%^T&{QOd-5`fi86)PpOh`G3Ayo$-Z%@v7# zjaYHkT(^(?i(GbwASh1ec7~wB3v-BIB|X(uTc=075i(kAgz)7P>{`D%L)i%^2DT;R z0Q6W+%8}r^<(m;T9Sx$F|L>;QBdBMWdjd*0V& z=_URLQUAC$!{;jd*XwUNdq0yAxqKUFv)V093h80-5}wmfYh|0TQ3;A216ET+xx*yd zwk_GJ4{~XvEyYt3(|@8^9W({0P9K%O#IyQ>5}fBsm8FqRJWp>euxip}2Bg$=q#NP* zcc4WgXaz$<)cXG+@=fkld&g4x3J>@Xu=&7=|D*O_k9z;c2LKp|I5>;i+R7PO*#qoc zOdOoe%ot2qCjXk*k%vnSrB`i;IVYvnh}d2A~Uo{mXA;N^Ig_2h{Fr25k6m&0^yH zVEqd!VzOc)s>CX?Vq%I^Ow`0u@~R5Nf){~7a$YJd>Q_+}QWcXElmBl5#7r!%piKOaNB}hO z2n8%iz=8-Y9|`*?AMoKkv77zBdG-(eKlXp4L+s&fb!;d}wkpbG9N^Q^dBHBCLtwHEFz|?DkUx@0u1(F>R(B~ zoH)A@yE+@$yBL|cS~>h<{fFH^SIt~pfRul4CAM;Lumuh-Al25)-OQHwU*`Nz5C7F4 zUVy8!+ut~R(Ek^CXZrWN|4)Vg--iF65disnF~SA_0{?4A5C8~}!1UkHKlmhpMYA6s zF#9n8{{^$)Y(PvMpaL)e?!^GgKuijt0FVdPrGR^JApQY8;FoUzpmZ<*D6j_gpGSee zt^bVakCuN2QD6lGDAfqy43uL9umzX_W!eMG0SNyY&8MmazIQ4pahTw;z9sXp!W>GD2M@6fpte<9|wRt&<<0e zG-m)EkmdrE;t1Rvfb|dCKP>#OQdIu(y8mYnIiS7|UyOj#h5yPB0$OJT@c!%fhgP@0 zdOo7^p+N}P@TttS4L zZy!7#G5?_0{gq$_^z#FR0Vw|&GsnL?UO@ki{>Ja0_7A&E|4ROdodi(3(ZBruh{*?t z1F!5L|CuLIpbaj-*narz_%}xXk>7t}?F5wdkt2KHW(MSQ{cF#M946qM;(y3d2evu` zCH;@_!2+}yIKwx96Ied-Ee=2f@_&pl*T1~>K+A0Z&HDe9K8C+O{d2>&2ap5h$O2_p z{FU%wog;8ue0X31umnm6{I&SM(ue^pKr5I58~`-p4~zxC28@p|u-E@CUh;qKR`{3v zk1_ON_kWx*AGMFs3;5UA{O{xS@Ad;1cLdt62$bgl^vdmTO#eNH|Eo9v)c}G4jQ^kh z|L^GkkJ!@!p6M^n4m{gg0o*_u$6p#JfE~zh1dJ0mkirV2 zu>osFK?#~~p8k$WLvzCXsf9dN||bFTh(b};?T|372k!~Xvxp#lEyXQV*R z|0BKozfS#cB49}lWB>&PFa?uqLc-95ONQSgfIxv31d~evNn)TNAk0Wi2#_#_;gTR` zVBnx25CA4!NLX4(5O9$Hr@1Q+hw5$rW-)eSOJr$~CBm5*`@SYi_C2Y|zHeixh?%mB z3L$HV>{7^5O30E@vV|g~Y>`N|euq-}zWuKE`~I%?y1u`^&fL#4=bZa`&i&k<`}298 znM0I>Lf~1tfDW*CR|^uLfI&#I;Mvd+ke&SHTsR8SqKk#)S&?+*>kfZ*4mR&uXXa!+ z*kF}C*xpioRpwk(pbO7y^O-4<2#KYGI@8OPS0hqU1D5;S~Mmi{vnt6J7 zZx^d*G$6Xw1EUrIdqhB%fHL&(z@t>{yzx%#+%e^#W=w0SN>>Uxxy?eO8#|9%pk>F#7QbLJP2+3FEaE=i8|8s(yGY+A7 zlOk-j{-a|{Ww|q}TE{OpnQ6GN_`CRh+K3;QM>aBNP}x5+O~}}LKJ=;S$4|@~B&U|R zDU9FS?bTJT8PPkORLItP*Zq{W)2$POg3xp8uI-D;{j*)~*A;O`X3Jqr9CWnWtRr>0 zGhue$Bo?h5pj7E$Cb*3=QnQ$w@e1elv5Mi#=prEw z__rHcaLEs$xmcl%x4kA}fvmY&j*lyDc2~DwkB^vsm{cG0m1BVW!U6naxV&@0!T2ZB zcaM#3p5QGMGjDy!W1P;GO#etK?VNm=>kXt*SRnMX6;w~4qtq)CDbtGGq~{f%TCZ(B zDL)X%(o<2;?Hd5|=5CjHwEyFw36X-;j6+ohhd0V#y;CP0r$U(}TtYu^KDa&HbYLh% zkf7|ApwK$fMo=cnuxTf(zqW43XL*9gNJQWi`_oEaD+3XBq2PVq z0+S?_&+#pN>J?{&m3ljq)YnTik$muV z-hFwzP92SI?SXGCP9zmie-vr0?_;x&6MFKAwKd*_--h+s`Uk$1cCXan_)Rf~3%V)O z>$d|au%5N{d498OKGXYd&@^!~q7qCgbq~4f-r9JUMp2uMoehVAa}<(e1KSEXZ@znY|!ang5Lapc>N-AW*%#z^N)v6Y|h=ScHZ8;crl z`kJ^uN=j7`5_&@;U;c1K{RFq;m$Pa8hZHEeT7|L24b6lUH2$HlSM&W_^E~RG(_PkC zZPIh<;VmpwymdyA$lD?Muhr+&f{PLYY?Hf}1%|`>J?-#ov^9*=`AA#==IWEzb+a}) zEOiA+U$TtdQ4wCgEK_Ihhk843*RJI(BZLW!%P*)5XtbM_zbZgV=sNx7A zyVm;7%$||>VehIP2{<5wYPb9BxB;HTG2{h zG@E-`{h_R5J0 z9>!f|$4@&`m8o)a+@Z1Zl`>URcQlTzes%w4p*}S->)M0G1I-^k8Xd8m4p|T|=jtp+ zn5^+D8eQ@%fODGh*8oI>EkK0pd{6LnZrq+Ot}z#rtfcvZ&NeQh{t_}kz<-+P>9@lc zO!VYCi5@KmV8FNpGrZ7FhFA5lwL=*>Ik-EyJD@b(@pj&BcD7D9yd6qac`L)C4AEOj z9wP>7YyetHTmp*$lRQQeK%=F9n1I{wo-qRSTQ?!6rZmI(ID*&RLY*ItWYm-@cCM~q zqa)4@%?e`XHWbmRc;jqc?M|aKyg`xB!OnZ93r77fT>uf1y?bo7vpOUW%<7ORC=o&+ zX~67XP3!OK+rZ3`50BU5mM57*jfWNpMxQ-1oSt0c>7;v;T_|efykxA%Us=&ReD)EJ zi~eZrgp#tR|2gDP=J4opWtfHPjTy^euOrpYkT>>oGi3ZK^Uhr%6A!=Csmy6BpJUeu zrm?WEkK{RO_26ctg{=7L=5oaCQSJ3;s(_ZBuaQw8zj=i4KEJxH~ldxey#>B`lXG8W6PFvXWds=?jf5aOx7&dN>pAR&b<}# zSbM^)Zs6ICh4L@WO<{-d{hxT+ig1-eo>zqw6oaha33|zV`-0RAT94DX%=)rW7JNT$ z9@mir_AV7niW>+I_9i|R)rGYSb&6SuOQoOGtUVuep!&og#{!N$*XYk{9d!+Ga5&EW zw(QY^@XMrnT$joO_(p_QMT}N#&z?oZ#RRKr6imGw@#DD1cFIGbq?`;p1-bv6!ML}j zNU)Df0DQgV$I+|gipo*$NM{R9|NdjI*t)-GRU*U(d?Fs(<;eHc2?Yg&-Fefvag#2Q zB)-#mY4SneK!x_*IO;LL96=72Rivb35C~ic&;pL^*Z?$4&JmCIlo1uR0SgilA0OAP z5=9gYWbhzt1%CR-^uA4G3<*ab=Ih4p|9(uNJdG7fdbL)!>P0z0bZMEaG!KG)IJd4wA zwNz56u{q<>>XS-DQORCp;5}wMQZflga-378;ewS_`xDW`6E~nv72XZ2_Y(HKPRl;V z>u)j!XTNf=!1nk>@SQJa6Ne{e#H_RU8GGwmZE6@^8cK>y`_CVU4PTe_Lrwd^a-Set z_ekBA3#=HOd9@}LV5cjgWUJnUd8M zBopnz*aah#K|tV|*rnsk78Fmo+O& za^$%%a~qq{ozG-0LtggoMc=K@BUT?YPDsCtrXm!NG##6kd&I`pK=J745zUlOx%-!d zfje(pGb;36Ljw#>nBK3B)}qN%ZP$O*9XDi}>`uRuQic}0Sm!kTm>+hrlsj}9I0sK#ieV_L*N$H))!et;D3RS7a@RvP-+BcR5S zXba$EY(FLS`RD|l<$<~_`c9k3dlU*cmEFe|AK!GYTA#8y{Q;?(R%u9z{HSp2R$P~6 zN#2+WtFhvuDDCx*z)i-lJkqat+3zZlXvuhB07+ZU$EChx{U$G1+GwNdbFAhADf2@K zjkINPDOTMldF#(xI6>lCrBf+yyCIa9YEs6hbMj7&)n<|Ey^=0Qd;R@XFCz24^@Si79??Uw zSlr$(ru$<>WgRE?Eclyh9n65eY82wbVUA^)PG}yyD3QPP*f84^h7y$u2)Vb6C{-5g zNKn6*`Z$thKUM9wf>LLt6vp9?`fnh$pWwY+=r>|g)@H|Ar8rGr79Yu*{r2_lL)qEJ z1NYTV=KFW{$R}IeQJpG08PwT0A-XWV^b*yRB<6k`?m|lcqWek=1;5&?o;dpOIXo-r zshP;JDIU+{@#ms1qZnBZ$Ji(C zo1V+c^#$MIj<5z0R{jq>0oXwf5bq8E+yH9F1|VTfKk&}O#>ev~a%>k@AdXQ2c@bnWK;*2lwS9PB!>&xJeJbEG4_&I9sr0SrVirnM!#O3=> zrgG$C^$;yW7Gc5DK5Xq1N_->o9#593dr4oEC6G4+HyTt6Rh~W1HE;83 zB5l-3#Fs<21IM{^1N#P=zGq<8Dc-F1u)0V{z1{su28Gue-!7%ml`NZ99~0g#PneAp#+rgC$6Z*_EK70ncM+Y8@+YYuWFOo1C9C8+O{Pw;t_@$z-fc zytZfUv`}b|n}UOeI~$qRnz1^PnO=ncQF(o(J3H1afpb$u>~JhvUw05|mbfZ4{*Ga= z$XrCddVstAv!#aSA%XXLF)DJljvwBs7PDS1c+5yVvF=4FS)HzbR_^$hY(t{>c;#d_ zrH9Mr#jm-2JsjGTL8r)92KtIqGzTj$C^LyHi=0d{(!RF25OD@yo8kLm7ECT0SCXyPxNa?D9glAq7HQP=_-{afH5Q>k3u0WEROd|pZ41XY+cnG17PR{ad)JrN4mA)dI`C-%9O<7CvM zwNdF<=u0e4^hTe%J)vYx*7t5iwB1^M_^X^64`MD(L%$_ET(&#ADug*p(0tKZoGsTF z(xmXAn9I0rT7t8MaSR6Clhv53mY&|yk3eHyVo}y^^>y*zo=09dX6SKOAf%@$;;aBC zzxX>=!QP^^=NK2J>Ti_k9iP8so+I$zM3SEiWNWI>gwBULG}k>%rT|Vmg$ z68S~k)zJ6GOY-@Pj_M^w)r_gCyr$%fvch=|jBO#33JhF(*iYk0P}hk_PJoEq_cJU& zGJz%{DZw_x&!@YkDARrch_s7P2n`8l=$hbx<4~|Di}&S{A?GC+>ddRMsTY$@TY&&bb>-XmQ4{0yYFzeuw> zpb9AO*dS>Z-$DJqkmfD&EWEX$%4loD5je)en%k85U)l0+92tzk|BNH=aBb-JvI7V2 zmL$5HFp~p^zZWWsfIJ|ZC7mVt%jMmL%8w`Uzq$Nd&7!Y&p!j2@f(;`N#TCApTA8ck z$GQIwQ%=r`gOOaWFKV(?4p!lM+fd|+Iz4AR_t2a;<+U7?kQUL7%FDf7!k_5i5aE$J z8uzMuPUhsz%HV%OaX z7q2n!p6y>?+uzA%qPJ0;G4kGv-9tSx{Wulxn((b>z%gz1cTg^oduS^A>%GL5#i^ zhhSB(l(FhHhzo^;Op9Nf4sCF49rR{nby4uw<#(1c4=h4c_9;FWk#;M1u7|inSlj*YcjBGS2eYJU~$p zkjN8|Jz(P=DOVR{=fHW?q?AV}=S*$t_<6oF*=?!Gp+%oDqvu;%S8gWJdiLt+2F$@r zZL64y#(Um>e01Te#(1R}Z~m2DsWH22*M+>`s6>k-9dk3nik9}LDLRP~olv3R@=zYb zN!zw7t**`bu?mAzu)Z{fJne@kEm0w1W8y9=Ct-6pdmBxo(Zeb2=Cte=pGLj;e8Yh8 z=HUjz9ZLQr^5h;qzL=eJYy5mUE>H50v;m$N#7$DR(& zG@bCed-gii8DhL#7xNeqIw2B;L8;mFC4{|F`NucL)hceNUfcJ{zgqdIsHeN2{VJ|Q z$BUHoRr}4XC&P>Vc*dd}zj@W1JWmPVn=K0cDN8w;0g^+shil@zQ!lV$<1d&}%AKzN zM)n>Pj*1rR$Bndn8)IwK;n|biD}-1HBXkd(s?S@vx;`ofO%Y1!O3@ZBy((lHJ5}Jx z#W%QAc{u!O?HQHS1kpaVjXNP!m6WnWxYmS_m(wifa={NM2(YIVox$Y%mb`qxg=>a> zd!uCVD0%^!lrX&uM#T5TYXIrq_(bt4u^}b_();s5)-Ue50L=Phl{Wsvi7z0c*<xB$2uuZlZVo?FI@}@-pRKO zeq^bcH5#?{EPdpD{gJ`&>IbdY7&St*WEtd{Wnq|JhPY1bg0q>Wxs2R9U0x0QF80O4 z4+70<4pp~3`6NSo_Go({TR!~tJxYz6^PS6u7iBxAUmq@?N7}6TozkWWIgy0$g?+x{ zCK@g|Lbjs~6F^`K0vNV3|Ms#P$Ynub02jr61G_)2#SzZ$pc_b;{Q=)!=m(;m`VX}G zvtau}EZ*k1yBD>8u4#j121tMLe}jc9Zn=(vO92vq*bf%|pW)&kv}1DMzr@-9SS0)n z&VGf}mnP3ref_yP0*==A}ie*B;@h z;>8Nz5x9i$-Xqi8n-(47s`zS;B-3TgL!LnI+cR)yxjrXvb90jO+OZE01GA^}d;21& z`1h_K%j(Js8e>O?b+jQ47~P=0Ubhj|KC7I`*`-7$Zarm{vT{tNqDB_4*c#EWsfw35 zqR(*Pj)Ya5d=$Mqp_l4oc{X*=G>;8wMFQeBX%mUFMolhE$M@iRwZX7l5qIN6rVLeu zI^s4YSm1=hl|=Bj07t2!o>_Lgw3znc=(F$|UJE=t20-IgGpka~iM`BIoXz%SL$-Rh z!pyk)c&*BYq#0C+D980_ODuItc2M-3AWOpe7ait7v}Ijij_chz$VQ*OSo*-XG}M7> z-hG_Gshh2<1lcuGRaQHHa}Q@1b4-Cmt*~n^cRwV?f!57Grp3Oi&;UCSt>mdSZoh&z zCsC!=Wtw+Q{1z1Ink=qNGy9kfK zuU;`U8^HP-1-lNe;%wLN;EFxC6Xxf2wmJEk6LfErO$D%paΝ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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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