From c408a9f83c07aba1108d7c461dcfac1909875396 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:05:42 -0500 Subject: [PATCH 001/188] WhereAmIPointing: Initial Release --- WhereAmIPointing/Main.cs | 105 ++++++++++++++++++++ WhereAmIPointing/Properties/AssemblyInfo.cs | 32 ++++++ WhereAmIPointing/README.md | 14 +++ WhereAmIPointing/WhereAmIPointing.csproj | 6 ++ WhereAmIPointing/format.json | 23 +++++ 5 files changed, 180 insertions(+) create mode 100644 WhereAmIPointing/Main.cs create mode 100644 WhereAmIPointing/Properties/AssemblyInfo.cs create mode 100644 WhereAmIPointing/README.md create mode 100644 WhereAmIPointing/WhereAmIPointing.csproj create mode 100644 WhereAmIPointing/format.json diff --git a/WhereAmIPointing/Main.cs b/WhereAmIPointing/Main.cs new file mode 100644 index 0000000..4771375 --- /dev/null +++ b/WhereAmIPointing/Main.cs @@ -0,0 +1,105 @@ +using ABI_RC.Core.InteractionSystem; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.WhereAmIPointing; + +public class WhereAmIPointingMod : MelonMod +{ + #region Melon Preferences + + // cannot disable because then id need extra logic to reset the alpha :) + // private const string SettingsCategory = nameof(WhereAmIPointingMod); + // + // private static readonly MelonPreferences_Category Category = + // MelonPreferences.CreateCategory(SettingsCategory); + // + // private static readonly MelonPreferences_Entry Entry_Enabled = + // Category.CreateEntry("enabled", true, display_name: "Enabled",description: "Toggle WhereAmIPointingMod entirely."); + + #endregion Melon Preferences + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(ControllerRay_Patches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #region Patches + + private static class ControllerRay_Patches + { + private const float ORIGINAL_ALPHA = 0.502f; + private const float INTERACTION_ALPHA = 0.1f; + private const float RAY_LENGTH = 1000f; // game normally raycasts to PositiveInfinity... -_- + + [HarmonyPostfix] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.LateUpdate))] + private static void Postfix_ControllerRay_LateUpdate(ref ControllerRay __instance) + { + if (__instance.isDesktopRay + || !__instance.enabled + || !__instance.IsTracking() + || !__instance.lineRenderer) + return; + + UpdateLineRendererAlpha(__instance); + + if (__instance.lineRenderer.enabled + || !ShouldOverrideLineRenderer(__instance)) + return; + + UpdateLineRendererPosition(__instance); + } + + private static void UpdateLineRendererAlpha(ControllerRay instance) + { + Material material = instance.lineRenderer.material; + Color color = material.color; + + float targetAlpha = instance.uiActive ? ORIGINAL_ALPHA : INTERACTION_ALPHA; + if (!(Math.Abs(color.a - targetAlpha) > float.Epsilon)) + return; + + color.a = targetAlpha; + material.color = color; + } + + private static bool ShouldOverrideLineRenderer(ControllerRay instance) + { + if (!ViewManager.Instance.IsAnyMenuOpen) + return false; + + if (CVR_MenuManager.Instance.IsQuickMenuOpen + && instance.hand == CVR_MenuManager.Instance.SelectedQuickMenuHand) + return false; + + return true; + } + + private static void UpdateLineRendererPosition(ControllerRay instance) + { + Vector3 rayOrigin = instance.rayDirectionTransform.position; + Vector3 rayEnd = rayOrigin + instance.rayDirectionTransform.forward * RAY_LENGTH; + + instance.lineRenderer.SetPosition(0, instance.lineRenderer.transform.InverseTransformPoint(rayOrigin)); + instance.lineRenderer.SetPosition(1, instance.lineRenderer.transform.InverseTransformPoint(rayEnd)); + instance.lineRenderer.enabled = true; + } + } + + #endregion Patches +} \ No newline at end of file diff --git a/WhereAmIPointing/Properties/AssemblyInfo.cs b/WhereAmIPointing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..97e5356 --- /dev/null +++ b/WhereAmIPointing/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.WhereAmIPointing.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.WhereAmIPointing))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.WhereAmIPointing))] + +[assembly: MelonInfo( + typeof(NAK.WhereAmIPointing.WhereAmIPointingMod), + nameof(NAK.WhereAmIPointing), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.WhereAmIPointing.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/WhereAmIPointing/README.md b/WhereAmIPointing/README.md new file mode 100644 index 0000000..c78a56a --- /dev/null +++ b/WhereAmIPointing/README.md @@ -0,0 +1,14 @@ +# WhereAmIPointing + +Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/WhereAmIPointing/WhereAmIPointing.csproj b/WhereAmIPointing/WhereAmIPointing.csproj new file mode 100644 index 0000000..728edb7 --- /dev/null +++ b/WhereAmIPointing/WhereAmIPointing.csproj @@ -0,0 +1,6 @@ + + + + net48 + + diff --git a/WhereAmIPointing/format.json b/WhereAmIPointing/format.json new file mode 100644 index 0000000..e352352 --- /dev/null +++ b/WhereAmIPointing/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "WhereAmIPointing", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.", + "searchtags": [ + "controller", + "ray", + "line", + "tomato" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r39/WhereAmIPointing.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", + "changelog": "- Initial Release", + "embedcolor": "#f61963" +} \ No newline at end of file From c206d98a979c966dd59ac0897322c8f4e5afa539 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:48:53 -0500 Subject: [PATCH 002/188] Stickers: fixed nullref when materials were destroyed --- Stickers/Properties/AssemblyInfo.cs | 2 +- Stickers/Stickers/StickerData.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index bfd8006..818da71 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.Stickers.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/Stickers/Stickers/StickerData.cs b/Stickers/Stickers/StickerData.cs index 9c1e510..c2bed17 100644 --- a/Stickers/Stickers/StickerData.cs +++ b/Stickers/Stickers/StickerData.cs @@ -196,6 +196,7 @@ namespace NAK.Stickers { foreach (Material material in _materials) { + if (material == null) continue; Color color = material.color; color.a = alpha; material.color = color; From 2f656340317826874c54472d0075fbc65f0bad56 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:18:57 -0500 Subject: [PATCH 003/188] Stickers: minor changes --- .../BTKUI/UIAddon.Category.DebugOptions.cs | 13 -- .../BTKUI/UIAddon.Category.MiscOptions.cs | 24 ++++ .../BTKUI/UIAddon.Category.StickersMod.cs | 4 +- Stickers/Integrations/BTKUI/UIAddon.Main.cs | 2 +- .../BTKUI/UIAddon.Page.StickerSelect.cs | 13 +- Stickers/ModSettings.cs | 14 +-- Stickers/Properties/AssemblyInfo.cs | 2 +- Stickers/Stickers.csproj | 2 +- .../Networking/ModNetwork.Constants.cs | 5 +- .../Stickers/Networking/ModNetwork.Inbound.cs | 114 ++++++++++++------ Stickers/Stickers/StickerData.cs | 28 ++--- .../Stickers/StickerSystem.ImageLoading.cs | 5 + Stickers/Stickers/StickerSystem.Main.cs | 41 ++++++- .../Stickers/StickerSystem.PlayerCallbacks.cs | 43 +------ .../StickerSystem.StickerLifecycle.cs | 27 ++--- Stickers/Stickers/Utilities/StickerCache.cs | 7 ++ 16 files changed, 204 insertions(+), 140 deletions(-) delete mode 100644 Stickers/Integrations/BTKUI/UIAddon.Category.DebugOptions.cs create mode 100644 Stickers/Integrations/BTKUI/UIAddon.Category.MiscOptions.cs diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.DebugOptions.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.DebugOptions.cs deleted file mode 100644 index 0a8fc32..0000000 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.DebugOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using BTKUILib.UIObjects; - -namespace NAK.Stickers.Integrations; - -public static partial class BTKUIAddon -{ - private static void Setup_DebugOptionsCategory() - { - Category debugCategory = _rootPage.AddMelonCategory(ModSettings.Hidden_Foldout_DebugCategory); - debugCategory.AddMelonToggle(ModSettings.Debug_NetworkInbound); - debugCategory.AddMelonToggle(ModSettings.Debug_NetworkOutbound); - } -} \ No newline at end of file diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.MiscOptions.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.MiscOptions.cs new file mode 100644 index 0000000..3c6e385 --- /dev/null +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.MiscOptions.cs @@ -0,0 +1,24 @@ +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using NAK.Stickers.Utilities; + +namespace NAK.Stickers.Integrations; + +public static partial class BTKUIAddon +{ + private static void Setup_OtherOptionsCategory() + { + Category debugCategory = _rootPage.AddMelonCategory(ModSettings.Hidden_Foldout_MiscCategory); + debugCategory.AddMelonToggle(ModSettings.Debug_NetworkInbound); + debugCategory.AddMelonToggle(ModSettings.Debug_NetworkOutbound); + debugCategory.AddMelonToggle(ModSettings.Entry_FriendsOnly); + debugCategory.AddButton("Clear Thumbnail Cache", "Stickers-rubbish-bin", "Clear the cache of all loaded stickers.", ButtonStyle.TextWithIcon) + .OnPress += () => QuickMenuAPI.ShowConfirm("Clear Thumbnail Cache", "Are you sure you want to clear the Cohtml thumbnail cache for all stickers?", + () => + { + StickerCache.ClearCache(); + QuickMenuAPI.ShowAlertToast("Thumbnail cache cleared.", 2); + }); + } +} \ No newline at end of file diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs index 22b67bd..65d874d 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs @@ -36,8 +36,8 @@ public static partial class BTKUIAddon Button openStickersFolderButton = _ourCategory.AddButton("Open Stickers Folder", "Stickers-folder", "Open UserData/Stickers folder in explorer. If above 256kb your image will automatically be downscaled for networking reasons.", ButtonStyle.TextWithIcon); openStickersFolderButton.OnPress += OnOpenStickersFolderButtonClick; - Button openMultiSelectionButton = _ourCategory.AddButton("Sticker SFX", "Stickers-headset", "Choose the SFX used when a sticker is placed.", ButtonStyle.TextWithIcon); - openMultiSelectionButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_sfxSelection); + Button openStickerSFXButton = _ourCategory.AddButton("Sticker SFX", "Stickers-headset", "Choose the SFX used when a sticker is placed.", ButtonStyle.TextWithIcon); + openStickerSFXButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_sfxSelection); ToggleButton toggleDesktopKeybindButton = _ourCategory.AddToggle("Use Desktop Keybind", "Should the Desktop keybind be active.", ModSettings.Entry_UsePlaceBinding.Value); Button openDesktopKeybindButton = _ourCategory.AddButton("Desktop Keybind", "Stickers-alphabet", "Choose the key binding to place stickers.", ButtonStyle.TextWithIcon); diff --git a/Stickers/Integrations/BTKUI/UIAddon.Main.cs b/Stickers/Integrations/BTKUI/UIAddon.Main.cs index 76345c0..a56f8f1 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Main.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Main.cs @@ -66,7 +66,7 @@ public static partial class BTKUIAddon Setup_StickersModCategory(); Setup_StickerSelectionCategory(); - Setup_DebugOptionsCategory(); + Setup_OtherOptionsCategory(); } #endregion Setup diff --git a/Stickers/Integrations/BTKUI/UIAddon.Page.StickerSelect.cs b/Stickers/Integrations/BTKUI/UIAddon.Page.StickerSelect.cs index eed6307..c36c8cb 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Page.StickerSelect.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Page.StickerSelect.cs @@ -18,6 +18,7 @@ public static partial class BTKUIAddon private static Category _fileCategory; private static Category _folderCategory; + private static TextBlock _noFilesTextBlock; private const int MAX_BUTTONS = 512; // cohtml literally will start to explode private static Button[] _fileButtons = new Button[80]; // 100 files, will resize if needed @@ -49,10 +50,12 @@ public static partial class BTKUIAddon // Create page _ourDirectoryBrowserPage = Page.GetOrCreatePage(ModSettings.ModName, "Directory Browser"); QuickMenuAPI.AddRootPage(_ourDirectoryBrowserPage); - + // Setup categories _folderCategory = _ourDirectoryBrowserPage.AddCategory("Subdirectories"); _fileCategory = _ourDirectoryBrowserPage.AddCategory("Images"); + _noFilesTextBlock = _fileCategory.AddTextBlock("No images found in this directory. You can add your own images and subfolders to the `UserData/Stickers/` folder."); + _noFilesTextBlock.Hidden = true; SetupFolderButtons(); SetupFileButtons(); @@ -88,6 +91,7 @@ public static partial class BTKUIAddon string absolutePath = Path.Combine(_curDirectoryInfo.FullName, button.ButtonTooltip[5..]); string relativePath = Path.GetRelativePath(_initialDirectory, absolutePath); StickerSystem.Instance.LoadImage(relativePath, _curSelectedSticker); + _rootPage.OpenPage(true); // close the directory browser to artificially limit loading speed }; _fileButtons[i] = button; } @@ -162,12 +166,16 @@ public static partial class BTKUIAddon return; } _stickerSelectionButtonDoubleClickTime = Time.time; + + // quick menu notification + QuickMenuAPI.ShowAlertToast($"Selected sticker slot {index + 1}", 1); } private static void OpenStickerSelectionForSlot(int index) { if (IsPopulatingPage) return; _curSelectedSticker = index; + _initialDirectory = StickerSystem.GetStickersFolderPath(); // creates folder if needed (lazy fix) _curDirectoryInfo = new DirectoryInfo(_initialDirectory); _ourDirectoryBrowserPage.OpenPage(false, true); } @@ -203,8 +211,9 @@ public static partial class BTKUIAddon _folderCategory.Hidden = foldersCount == 0; _folderCategory.CategoryName = $"Subdirectories ({foldersCount})"; - _fileCategory.Hidden = filesCount == 0; + //_fileCategory.Hidden = filesCount == 0; _fileCategory.CategoryName = $"Images ({filesCount})"; + _noFilesTextBlock.Hidden = filesCount > 0; }); PopulateFolders(directories); diff --git a/Stickers/ModSettings.cs b/Stickers/ModSettings.cs index 0278f3e..85dbae5 100644 --- a/Stickers/ModSettings.cs +++ b/Stickers/ModSettings.cs @@ -11,7 +11,7 @@ public static class ModSettings internal const string SM_SettingsCategory = "Stickers Mod"; private const string SM_SelectionCategory = "Sticker Selection"; - private const string DEBUG_SettingsCategory = "Debug Options"; + private const string MISC_SettingsCategory = "Miscellaneous Options"; internal const int MaxStickerSlots = 4; @@ -27,17 +27,14 @@ public static class ModSettings internal static readonly MelonPreferences_Entry Hidden_Foldout_SelectionCategory = Category.CreateEntry("hidden_foldout_selection", true, is_hidden: true, display_name: SM_SelectionCategory, description: "Foldout state for Sticker selection."); - - internal static readonly MelonPreferences_Entry Hidden_Foldout_DebugCategory = - Category.CreateEntry("hidden_foldout_debug", false, is_hidden: true, display_name: DEBUG_SettingsCategory, description: "Foldout state for Debug settings."); + + internal static readonly MelonPreferences_Entry Hidden_Foldout_MiscCategory = + Category.CreateEntry("hidden_foldout_miscellaneous", false, is_hidden: true, display_name: MISC_SettingsCategory, description: "Foldout state for Miscellaneous settings."); #endregion Hidden Foldout Entries #region Stickers Mod Settings - internal static readonly MelonPreferences_Entry Entry_HapticsOnPlace = - Category.CreateEntry("haptics_on_place", true, "Haptics On Place", "Enable haptic feedback when placing stickers."); - internal static readonly MelonPreferences_Entry Entry_PlayerUpAlignmentThreshold = Category.CreateEntry("player_up_alignment_threshold", 20f, "Player Up Alignment Threshold", "The threshold the controller roll can be within to align perfectly with the player up vector. Set to 0f to always align to controller up."); @@ -58,6 +55,9 @@ public static class ModSettings display_name: "Selected Sticker Name", description: "The name of the sticker selected for stickering.", is_hidden: true); + + internal static readonly MelonPreferences_Entry Entry_FriendsOnly = + Category.CreateEntry("friends_only", false, "Friends Only", "Only allow friends to use stickers."); #endregion Stickers Mod Settings diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index 818da71..cf05529 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.Stickers.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.5"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/Stickers/Stickers.csproj b/Stickers/Stickers.csproj index 990d2db..de4a315 100644 --- a/Stickers/Stickers.csproj +++ b/Stickers/Stickers.csproj @@ -4,7 +4,7 @@ net48 - TRACE;TRACE;UNITY_2017_1_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2019_3_OR_NEWER;USE_BURST;USE_NEWMATHS;USE_BURST_REALLY; + TRACE;TRACE;UNITY_2017_1_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2019_3_OR_NEWER;USE_BURST;USE_NEWMATHS;USE_BURST_REALLY;USEDFORSTICKERMOD; diff --git a/Stickers/Stickers/Networking/ModNetwork.Constants.cs b/Stickers/Stickers/Networking/ModNetwork.Constants.cs index 152b952..0f24904 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Constants.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Constants.cs @@ -1,4 +1,5 @@ -using NAK.Stickers.Properties; +using ABI_RC.Core.Util.AnimatorManager; +using NAK.Stickers.Properties; namespace NAK.Stickers.Networking; @@ -12,6 +13,6 @@ public static partial class ModNetwork private const string ModId = $"MelonMod.NAK.Stickers_v{NetworkVersion}"; private const int ChunkSize = 1024; // roughly 1KB per ModNetworkMessage private const int MaxChunkCount = MaxTextureSize / ChunkSize; - + #endregion Constants } \ No newline at end of file diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index fbada5b..b59d5db 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.Savior; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Savior; using ABI_RC.Systems.ModNetwork; using NAK.Stickers.Utilities; using UnityEngine; @@ -15,58 +16,91 @@ public static partial class ModNetwork private static readonly Dictionary _textureMetadata = new(); #endregion Inbound Buffers + + #region Reset Method + + public static void Reset() + { + _textureChunkBuffers.Clear(); + _receivedChunkCounts.Clear(); + _expectedChunkCounts.Clear(); + _textureMetadata.Clear(); + + LoggerInbound("ModNetwork inbound buffers and metadata have been reset."); + } + + #endregion Reset Method #region Inbound Methods + + private static bool ShouldReceiveFromSender(string sender) + { + if (_disallowedForSession.Contains(sender)) + return false; // ignore messages from disallowed users + + if (MetaPort.Instance.blockedUserIds.Contains(sender)) + return false; // ignore messages from blocked users + + if (ModSettings.Entry_FriendsOnly.Value && !Friends.FriendsWith(sender)) + return false; // ignore messages from non-friends if friends only is enabled + + return true; + } private static void HandleMessageReceived(ModNetworkMessage msg) { - string sender = msg.Sender; - msg.Read(out byte msgTypeRaw); - - if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) - return; - - if (_disallowedForSession.Contains(sender)) - return; // ignore messages from disallowed users - - if (MetaPort.Instance.blockedUserIds.Contains(sender)) - return; // ignore messages from blocked users - - LoggerInbound($"Received message from {msg.Sender}, Type: {(MessageType)msgTypeRaw}"); - - switch ((MessageType)msgTypeRaw) + try { - case MessageType.PlaceSticker: - HandlePlaceSticker(msg); - break; + string sender = msg.Sender; + msg.Read(out byte msgTypeRaw); - case MessageType.ClearSticker: - HandleClearSticker(msg); - break; + if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) + return; + + if (!ShouldReceiveFromSender(sender)) + return; - case MessageType.ClearAllStickers: - HandleClearAllStickers(msg); - break; + LoggerInbound($"Received message from {msg.Sender}, Type: {(MessageType)msgTypeRaw}"); - case MessageType.StartTexture: - HandleStartTexture(msg); - break; + switch ((MessageType)msgTypeRaw) + { + case MessageType.PlaceSticker: + HandlePlaceSticker(msg); + break; + + case MessageType.ClearSticker: + HandleClearSticker(msg); + break; + + case MessageType.ClearAllStickers: + HandleClearAllStickers(msg); + break; - case MessageType.SendTexture: - HandleSendTexture(msg); - break; + case MessageType.StartTexture: + HandleStartTexture(msg); + break; - case MessageType.EndTexture: - HandleEndTexture(msg); - break; + case MessageType.SendTexture: + HandleSendTexture(msg); + break; - case MessageType.RequestTexture: - HandleRequestTexture(msg); - break; + case MessageType.EndTexture: + HandleEndTexture(msg); + break; - default: - LoggerInbound($"Invalid message type received: {msgTypeRaw}"); - break; + case MessageType.RequestTexture: + HandleRequestTexture(msg); + break; + + default: + LoggerInbound($"Invalid message type received: {msgTypeRaw}"); + break; + } + + } + catch (Exception e) + { + LoggerInbound($"Error handling message from {msg.Sender}: {e.Message}", true); } } diff --git a/Stickers/Stickers/StickerData.cs b/Stickers/Stickers/StickerData.cs index c2bed17..f193d12 100644 --- a/Stickers/Stickers/StickerData.cs +++ b/Stickers/Stickers/StickerData.cs @@ -8,14 +8,13 @@ namespace NAK.Stickers public class StickerData { private const float DECAL_SIZE = 0.25f; - - private static readonly int s_EmissionStrengthID = Shader.PropertyToID("_EmissionStrength"); - - public float DeathTime; // when a remote player leaves, we need to kill their stickers + + public readonly string PlayerId; public float LastPlacedTime; + public float DeathTime = -1f; + private Vector3 _lastPlacedPosition = Vector3.zero; - public readonly bool IsLocal; private readonly DecalType _decal; private readonly DecalSpawner[] _decalSpawners; @@ -23,9 +22,9 @@ namespace NAK.Stickers private readonly Material[] _materials; private readonly AudioSource _audioSource; - public StickerData(bool isLocal, int decalSpawnersCount) + public StickerData(string playerId, int decalSpawnersCount) { - IsLocal = isLocal; + PlayerId = playerId; _decal = ScriptableObject.CreateInstance(); _decalSpawners = new DecalSpawner[decalSpawnersCount]; @@ -54,7 +53,7 @@ namespace NAK.Stickers _audioSource.maxDistance = 5f; _audioSource.minDistance = 1f; _audioSource.outputAudioMixerGroup = RootLogic.Instance.propSfx; // props are close enough to stickers - if (isLocal) Object.DontDestroyOnLoad(_audioSource.gameObject); // keep audio source through world transitions + if (PlayerId == StickerSystem.PlayerLocalId) Object.DontDestroyOnLoad(_audioSource.gameObject); // keep audio source through world transitions } public Guid GetTextureHash(int spawnerIndex = 0) @@ -95,8 +94,6 @@ namespace NAK.Stickers ? FilterMode.Bilinear // smear it cause its fat : FilterMode.Point; // my minecraft skin looked shit - if (IsLocal) StickerMod.Logger.Msg($"Set texture filter mode to: {texture.filterMode}"); - Material material = _materials[spawnerIndex]; // Destroy the previous texture to avoid memory leaks @@ -117,9 +114,9 @@ namespace NAK.Stickers Transform rootObject = null; GameObject hitGO = hit.transform.gameObject; - if (hitGO.TryGetComponent(out Rigidbody _) - || hitGO.TryGetComponent(out Animator _) - || hitGO.scene.buildIndex == 4) // additive (dynamic) content + if (hitGO.scene.buildIndex == 4 // additive (dynamic) content + || hitGO.TryGetComponent(out Animator _) // potentially movable + || hitGO.GetComponentInParent() != null) // movable rootObject = hitGO.transform; _lastPlacedPosition = hit.point; @@ -128,7 +125,7 @@ namespace NAK.Stickers // Add decal to the specified spawner _decalSpawners[spawnerIndex].AddDecal( _lastPlacedPosition, Quaternion.LookRotation(forwardDirection, upDirection), - hit.collider.gameObject, + hitGO, DECAL_SIZE, DECAL_SIZE, 1f, 1f, 0f, rootObject); } @@ -162,8 +159,9 @@ namespace NAK.Stickers _decalSpawners[i].Release(); _decalSpawners[i].staticGroups.Clear(); _decalSpawners[i].movableGroups.Clear(); - + // Clean up textures and materials + if (_materials[i] == null) continue; if (_materials[i].mainTexture != null) Object.Destroy(_materials[i].mainTexture); Object.Destroy(_materials[i]); } diff --git a/Stickers/Stickers/StickerSystem.ImageLoading.cs b/Stickers/Stickers/StickerSystem.ImageLoading.cs index 254cd02..def203a 100644 --- a/Stickers/Stickers/StickerSystem.ImageLoading.cs +++ b/Stickers/Stickers/StickerSystem.ImageLoading.cs @@ -149,6 +149,11 @@ public partial class StickerSystem if (!Directory.Exists(s_StickersFolderPath)) Directory.CreateDirectory(s_StickersFolderPath); return s_StickersFolderPath; } + + public static void EnsureStickersFolderExists() + { + if (!Directory.Exists(s_StickersFolderPath)) Directory.CreateDirectory(s_StickersFolderPath); + } #endregion Image Loading } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index 24338bd..ad02060 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -1,5 +1,8 @@ -using ABI_RC.Core.UI; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.UI; using ABI_RC.Systems.GameEventSystem; +using NAK.Stickers.Networking; using NAK.Stickers.Utilities; using UnityEngine; @@ -22,7 +25,7 @@ public partial class StickerSystem DecalManager.SetPreferredMode(DecalUtils.Mode.GPU, false, 0); // ensure cache folder exists - if (!Directory.Exists(s_StickersFolderPath)) Directory.CreateDirectory(s_StickersFolderPath); + EnsureStickersFolderExists(); // listen for game events CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); @@ -30,6 +33,36 @@ public partial class StickerSystem #endregion Singleton + #region Callback Registration + + private void OnPlayerSetupStart() + { + CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); + CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); + + CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); + CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); + SchedulerSystem.AddJob(Instance.OnOccasionalUpdate, 10f, 1f); + LoadAllImagesAtStartup(); + } + + #endregion Callback Registration + + #region Game Events + + private void OnInitialConnection() + { + ClearStickersSelf(); // clear stickers on remotes just in case we rejoined + ModNetwork.Reset(); // reset network buffers and metadata + } + + private void OnWorldUnload() + { + CleanupAllButSelf(); // release all stickers except for self + } + + #endregion Game Events + #region Data private int _selectedStickerSlot; @@ -63,9 +96,7 @@ public partial class StickerSystem private const float StickerKillTime = 30f; private const float StickerCooldown = 0.2f; private readonly Dictionary _playerStickers = new(); - private const string PlayerLocalId = "_PLAYERLOCAL"; + internal const string PlayerLocalId = "_PLAYERLOCAL"; - private readonly List _deadStickerPool = new(); // for cleanup on player leave - #endregion Data } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs index 3c99106..3637abc 100644 --- a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs +++ b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs @@ -1,7 +1,4 @@ -using ABI_RC.Core.IO; -using ABI_RC.Core.Networking.IO.Instancing; -using ABI_RC.Core.Player; -using ABI_RC.Systems.GameEventSystem; +using ABI_RC.Core.Player; using UnityEngine; namespace NAK.Stickers; @@ -10,17 +7,6 @@ public partial class StickerSystem { #region Player Callbacks - private void OnPlayerSetupStart() - { - CVRGameEventSystem.World.OnUnload.AddListener(_ => Instance.CleanupAllButSelf()); - CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) Instance.ClearStickersSelf(); }); - - CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); - CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); - SchedulerSystem.AddJob(Instance.OnOccasionalUpdate, 10f, 1f); - LoadAllImagesAtStartup(); - } - private void OnPlayerJoined(CVRPlayerEntity playerEntity) { if (!_playerStickers.TryGetValue(playerEntity.Uuid, out StickerData stickerData)) @@ -28,7 +14,6 @@ public partial class StickerSystem stickerData.DeathTime = -1f; stickerData.SetAlpha(1f); - _deadStickerPool.Remove(stickerData); } private void OnPlayerLeft(CVRPlayerEntity playerEntity) @@ -38,25 +23,16 @@ public partial class StickerSystem stickerData.DeathTime = Time.time + StickerKillTime; stickerData.SetAlpha(1f); - _deadStickerPool.Add(stickerData); } private void OnOccasionalUpdate() { - if (_deadStickerPool.Count == 0) - return; - - for (var i = _deadStickerPool.Count - 1; i >= 0; i--) + float currentTime = Time.time; + for (int i = 0; i < _playerStickers.Values.Count; i++) { - float currentTime = Time.time; - StickerData stickerData = _deadStickerPool[i]; - if (stickerData == null) - { - _deadStickerPool.RemoveAt(i); - continue; - } + StickerData stickerData = _playerStickers.Values.ElementAt(i); - if (stickerData.DeathTime < 0f) + if (stickerData.DeathTime < 0f) continue; if (currentTime < stickerData.DeathTime) @@ -65,15 +41,8 @@ public partial class StickerSystem continue; } - for (int j = 0; j < _playerStickers.Values.Count; j++) - { - if (_playerStickers.Values.ElementAt(j) != stickerData) continue; - _playerStickers.Remove(_playerStickers.Keys.ElementAt(j)); - break; - } - - _deadStickerPool.RemoveAt(i); stickerData.Cleanup(); + _playerStickers.Remove(stickerData.PlayerId); } } diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index a24ebd8..ee2d76e 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -15,7 +15,7 @@ public partial class StickerSystem if (_playerStickers.TryGetValue(playerId, out StickerData stickerData)) return stickerData; - stickerData = new StickerData(playerId == PlayerLocalId, ModSettings.MaxStickerSlots); + stickerData = new StickerData(playerId, ModSettings.MaxStickerSlots); _playerStickers[playerId] = stickerData; return stickerData; } @@ -40,8 +40,6 @@ public partial class StickerSystem if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) return; - // do haptic if not lame - if (!ModSettings.Entry_HapticsOnPlace.Value) return; CVRInputManager.Instance.Vibrate(0f, 0.1f, 10f, 0.1f, hand); } @@ -67,6 +65,10 @@ public partial class StickerSystem 10f, LayerMask, QueryTriggerInteraction.Ignore)) return false; + // if gameobject name starts with [NoSticker] then don't place sticker + if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) + return false; + stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); stickerData.PlayAudio(); return true; @@ -78,6 +80,12 @@ public partial class StickerSystem ModNetwork.SendClearAllStickers(); } + public void ClearStickerSelf(int stickerSlot) + { + ClearStickersForPlayer(PlayerLocalId, stickerSlot); + ModNetwork.SendClearSticker(stickerSlot); + } + private void ClearStickersForPlayer(string playerId) { if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) @@ -100,6 +108,7 @@ public partial class StickerSystem texture.LoadImage(imageBytes); texture.Compress(true); // noachi said to do + ClearStickerSelf(stickerSlot); // clear placed stickers in-scene so we can't replace an entire wall at once OnPlayerStickerTextureReceived(PlayerLocalId, Guid.Empty, texture, stickerSlot); ModNetwork.SetTexture(stickerSlot, imageBytes); } @@ -138,7 +147,7 @@ public partial class StickerSystem foreach ((_, StickerData data) in _playerStickers) { - if (data.IsLocal) data.Clear(); + if (data == localStickerData) data.Clear(); else data.Cleanup(); } @@ -146,15 +155,5 @@ public partial class StickerSystem _playerStickers[PlayerLocalId] = localStickerData; } - public void SelectStickerSlot(int stickerSlot) - { - SelectedStickerSlot = Mathf.Clamp(stickerSlot, 0, ModSettings.MaxStickerSlots - 1); - } - - public int GetCurrentStickerSlot() - { - return SelectedStickerSlot; - } - #endregion Sticker Lifecycle } diff --git a/Stickers/Stickers/Utilities/StickerCache.cs b/Stickers/Stickers/Utilities/StickerCache.cs index eb5ae87..3d4c2a2 100644 --- a/Stickers/Stickers/Utilities/StickerCache.cs +++ b/Stickers/Stickers/Utilities/StickerCache.cs @@ -66,6 +66,13 @@ public static class StickerCache } } + public static void ClearCache() + { + if (!Directory.Exists(ThumbnailPath)) return; + Directory.Delete(ThumbnailPath, true); + StickerMod.Logger.Msg("Cleared thumbnail cache."); + } + #endregion Public Methods #region Private Methods From 192ba3cceb93524271135a7c0adc6a6c09ea1b71 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:36:49 -0500 Subject: [PATCH 004/188] Stickers: Added back Identify button --- .../BTKUI/UIAddon.Page.PlayerOptions.cs | 14 +++--- Stickers/Stickers/StickerData.cs | 18 +------ Stickers/Stickers/StickerSystem.Main.cs | 2 +- .../Stickers/StickerSystem.PlayerCallbacks.cs | 48 ++++++++++++------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Stickers/Integrations/BTKUI/UIAddon.Page.PlayerOptions.cs b/Stickers/Integrations/BTKUI/UIAddon.Page.PlayerOptions.cs index f44fc40..efc7a8e 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Page.PlayerOptions.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Page.PlayerOptions.cs @@ -15,8 +15,8 @@ public static partial class BTKUIAddon { Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.SM_SettingsCategory, ModSettings.ModName); - //Button identifyButton = category.AddButton("Identify Stickers", "Stickers-magnifying-glass", "Identify this players stickers by making them flash."); - //identifyButton.OnPress += OnPressIdentifyPlayerStickersButton; + Button identifyButton = category.AddButton("Identify Stickers", "Stickers-magnifying-glass", "Identify this players stickers by making them flash."); + identifyButton.OnPress += OnPressIdentifyPlayerStickersButton; Button clearStickersButton = category.AddButton("Clear Stickers", "Stickers-eraser", "Clear this players stickers."); clearStickersButton.OnPress += OnPressClearSelectedPlayerStickersButton; @@ -30,11 +30,11 @@ public static partial class BTKUIAddon #region Callbacks - // private static void OnPressIdentifyPlayerStickersButton() - // { - // if (string.IsNullOrEmpty(QuickMenuAPI.SelectedPlayerID)) return; - // StickerSystem.Instance.OnStickerIdentifyReceived(QuickMenuAPI.SelectedPlayerID); - // } + private static void OnPressIdentifyPlayerStickersButton() + { + if (string.IsNullOrEmpty(QuickMenuAPI.SelectedPlayerID)) return; + StickerSystem.Instance.OnStickerIdentifyReceived(QuickMenuAPI.SelectedPlayerID); + } private static void OnPressClearSelectedPlayerStickersButton() { diff --git a/Stickers/Stickers/StickerData.cs b/Stickers/Stickers/StickerData.cs index f193d12..da33888 100644 --- a/Stickers/Stickers/StickerData.cs +++ b/Stickers/Stickers/StickerData.cs @@ -12,6 +12,7 @@ namespace NAK.Stickers public readonly string PlayerId; public float LastPlacedTime; public float DeathTime = -1f; + public float IdentifyTime = -1f; private Vector3 _lastPlacedPosition = Vector3.zero; @@ -200,22 +201,5 @@ namespace NAK.Stickers material.color = color; } } - - #region shitty identify - - public void Identify() - { - Color color = Color.HSVToRGB(Time.time % 1f, 1f, 1f); - foreach (Material material in _materials) - material.color = color; // cycle rainbow - } - - public void ResetIdentify() - { - foreach (Material material in _materials) - material.color = Color.white; - } - - #endregion shitty identify } } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index ad02060..a58e724 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -42,7 +42,7 @@ public partial class StickerSystem CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); - SchedulerSystem.AddJob(Instance.OnOccasionalUpdate, 10f, 1f); + SchedulerSystem.AddJob(Instance.OnUpdate, 10f, -1); LoadAllImagesAtStartup(); } diff --git a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs index 3637abc..89394c9 100644 --- a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs +++ b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs @@ -25,24 +25,38 @@ public partial class StickerSystem stickerData.SetAlpha(1f); } - private void OnOccasionalUpdate() + private void OnUpdate() { float currentTime = Time.time; for (int i = 0; i < _playerStickers.Values.Count; i++) { StickerData stickerData = _playerStickers.Values.ElementAt(i); - - if (stickerData.DeathTime < 0f) - continue; - if (currentTime < stickerData.DeathTime) + if (stickerData.DeathTime > 0f) { - stickerData.SetAlpha(Mathf.Lerp(0f, 1f, (stickerData.DeathTime - currentTime) / StickerKillTime)); + if (currentTime < stickerData.DeathTime) + { + stickerData.SetAlpha(Mathf.Lerp(0f, 1f, (stickerData.DeathTime - currentTime) / StickerKillTime)); + continue; + } + + stickerData.Cleanup(); + _playerStickers.Remove(stickerData.PlayerId); continue; } - - stickerData.Cleanup(); - _playerStickers.Remove(stickerData.PlayerId); + + if (stickerData.IdentifyTime > 0) + { + if (currentTime < stickerData.IdentifyTime) + { + // blink alpha 3 times but not completely off + stickerData.SetAlpha(Mathf.Lerp(0.2f, 1f, Mathf.PingPong((stickerData.IdentifyTime - currentTime) * 2f, 1f))); + continue; + } + + stickerData.SetAlpha(1f); + stickerData.IdentifyTime = -1; + } } } @@ -59,15 +73,13 @@ public partial class StickerSystem public void OnStickerClearAllReceived(string playerId) => ClearStickersForPlayer(playerId); - // public void OnStickerIdentifyReceived(string playerId) - // { - // if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - // return; - // - // // todo: make prettier (idk shaders) - // SchedulerSystem.AddJob(() => stickerData.Identify(), 0f, 0.1f, 30); - // SchedulerSystem.AddJob(() => stickerData.ResetIdentify(), 4f, 1f, 1); - // } + public void OnStickerIdentifyReceived(string playerId) + { + if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return; + + stickerData.IdentifyTime = Time.time + 3f; + } #endregion Player Callbacks } From da90e4623fa9677b5d14ea6696e5c1c7946a39b4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:44:50 -0500 Subject: [PATCH 005/188] Stickers: update format.json --- Stickers/format.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Stickers/format.json b/Stickers/format.json index 4acba2e..c29740a 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -1,7 +1,7 @@ { - "_id": -1, + "_id": 232, "name": "Stickers", - "modversion": "1.0.3", + "modversion": "1.0.5", "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r38/Stickers.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Initial release", + "changelog": "- Added Friends Only setting.\n- Added button to clear sticker thumbnail cache.\n- Added Identify button to Player Selection page.\n- Added `[NoSticker]` GameObject name check. \n- Adjusted inbound network buffers to be cleared on initial connection to an instance.\n- Adjusted selecting a new image for a sticker slot to clear stickers in-scene for that slot.\n- Stripped all unused classes a bunch of other methods from decalery.\n - Completely removed Skinned Mesh Renderer support as it required running on CPU.\n - Most uploaded content is not marked as readable anyways (plus it crashed consistantly).\n- Fixed nullref spam when clearing stickers when sticker was already marked as dead.", "embedcolor": "#f61963" } \ No newline at end of file From 832f39268246c7d93e30a597f2e3445baf36f491 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:45:07 -0500 Subject: [PATCH 006/188] WhereAmIPointing: fixed dimming of ray when no menu is open --- WhereAmIPointing/Main.cs | 5 +++-- WhereAmIPointing/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/WhereAmIPointing/Main.cs b/WhereAmIPointing/Main.cs index 4771375..24529b4 100644 --- a/WhereAmIPointing/Main.cs +++ b/WhereAmIPointing/Main.cs @@ -40,7 +40,7 @@ public class WhereAmIPointingMod : MelonMod #region Patches - private static class ControllerRay_Patches + private static class ControllerRay_Patches { private const float ORIGINAL_ALPHA = 0.502f; private const float INTERACTION_ALPHA = 0.1f; @@ -70,7 +70,8 @@ public class WhereAmIPointingMod : MelonMod Material material = instance.lineRenderer.material; Color color = material.color; - float targetAlpha = instance.uiActive ? ORIGINAL_ALPHA : INTERACTION_ALPHA; + bool anyMenuOpen = ViewManager.Instance.IsAnyMenuOpen; + float targetAlpha = (!anyMenuOpen || instance.uiActive) ? ORIGINAL_ALPHA : INTERACTION_ALPHA; if (!(Math.Abs(color.a - targetAlpha) > float.Epsilon)) return; diff --git a/WhereAmIPointing/Properties/AssemblyInfo.cs b/WhereAmIPointing/Properties/AssemblyInfo.cs index 97e5356..48c359f 100644 --- a/WhereAmIPointing/Properties/AssemblyInfo.cs +++ b/WhereAmIPointing/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.WhereAmIPointing.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file From dbdf4308c7931e702218798f1c36f03a5928ef80 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:46:13 -0500 Subject: [PATCH 007/188] WhereAmIPointing: updated format.json --- WhereAmIPointing/format.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WhereAmIPointing/format.json b/WhereAmIPointing/format.json index e352352..654911a 100644 --- a/WhereAmIPointing/format.json +++ b/WhereAmIPointing/format.json @@ -1,7 +1,7 @@ { - "_id": -1, + "_id": 234, "name": "WhereAmIPointing", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r39/WhereAmIPointing.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", - "changelog": "- Initial Release", + "changelog": "- Fixed line renderer alpha not being reset when the menu is closed.", "embedcolor": "#f61963" } \ No newline at end of file From e61049462bf87033a3aa34526d0173fadf195b55 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:53:54 -0500 Subject: [PATCH 008/188] SmartReticle: made work for VR reticle --- SmartReticle/Main.cs | 15 +++++++++------ SmartReticle/format.json | 10 +++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/SmartReticle/Main.cs b/SmartReticle/Main.cs index ae94900..4533b0c 100644 --- a/SmartReticle/Main.cs +++ b/SmartReticle/Main.cs @@ -65,15 +65,18 @@ public class SmartReticleMod : MelonMod if (!Entry_Enabled.Value) return; - if (!__instance.isDesktopRay) + GameObject pointer; + if (__instance.isDesktopRay) // in desktop mode + pointer = CohtmlHud.Instance.desktopPointer; + else if (__instance.isHeadRay) // in VR mode with no controllers + pointer = __instance.backupCrossHair; + else return; - - GameObject desktopPointer = CohtmlHud.Instance.desktopPointer; - if (!desktopPointer.activeSelf) + if (!pointer.activeSelf) { _lastDisplayedTime = 0; // reset time - return; // pointing at menu or cursor is active + return; // pointing at menu or cursor / controllers active } bool shouldDisplayPointer = (__instance._interact // pressing mouse1 or mouse2 @@ -93,7 +96,7 @@ public class SmartReticleMod : MelonMod } if (Time.time - _lastDisplayedTime > Entry_HideTimeout.Value) - desktopPointer.SetActive(false); + pointer.SetActive(false); } } diff --git a/SmartReticle/format.json b/SmartReticle/format.json index 958ffac..b01de27 100644 --- a/SmartReticle/format.json +++ b/SmartReticle/format.json @@ -1,12 +1,12 @@ { - "_id": -1, + "_id": 233, "name": "SmartReticle", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Simple mod that makes the Desktop reticle only appear when needed.", + "description": "Simple mod that makes the Desktop/VR Head reticle only appear when hovering over an interactable, holding an interaction button, or when using a tool.", "searchtags": [ "reticle", "hud", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r39/SmartReticle.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmartReticle.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmartReticle/", - "changelog": "- Initial Release", + "changelog": "- Adjusted to also work for the VR head reticle.", "embedcolor": "#f61963" } \ No newline at end of file From 45bbfe0d937e6ab06339f87d24c37a116624633c Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:03:04 -0500 Subject: [PATCH 009/188] CustomSpawnPoint: removed unneeded logging --- CustomSpawnPoint/Properties/AssemblyInfo.cs | 2 +- CustomSpawnPoint/SpawnPointManager.cs | 2 +- CustomSpawnPoint/format.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CustomSpawnPoint/Properties/AssemblyInfo.cs b/CustomSpawnPoint/Properties/AssemblyInfo.cs index 867796d..480612c 100644 --- a/CustomSpawnPoint/Properties/AssemblyInfo.cs +++ b/CustomSpawnPoint/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.CustomSpawnPoint.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/CustomSpawnPoint/SpawnPointManager.cs b/CustomSpawnPoint/SpawnPointManager.cs index 0aa5504..12bcd43 100644 --- a/CustomSpawnPoint/SpawnPointManager.cs +++ b/CustomSpawnPoint/SpawnPointManager.cs @@ -97,7 +97,7 @@ namespace NAK.CustomSpawnPoint internal static void OnRequestWorldDetailsPage(string worldId) { - CustomSpawnPointMod.Logger.Msg("Requesting world details page for world: " + worldId); + //CustomSpawnPointMod.Logger.Msg("Requesting world details page for world: " + worldId); requestedWorldId = worldId; requestedSpawnPoint = spawnPoints.TryGetValue(requestedWorldId, out SpawnPointData spawnPoint) ? spawnPoint : null; diff --git a/CustomSpawnPoint/format.json b/CustomSpawnPoint/format.json index c5541ff..57d83fd 100644 --- a/CustomSpawnPoint/format.json +++ b/CustomSpawnPoint/format.json @@ -1,7 +1,7 @@ { "_id": 228, "name": "CustomSpawnPoint", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r37/CustomSpawnPoint.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/CustomSpawnPoint.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint/", - "changelog": "- Initial Release", + "changelog": "- Removed unneeded logging when opening a World Details page.", "embedcolor": "#f61963" } \ No newline at end of file From a17ad66bddd8393d8257441116f1d3cb5570565a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:03:12 -0500 Subject: [PATCH 010/188] SmartReticle: bumped ver --- SmartReticle/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SmartReticle/Properties/AssemblyInfo.cs b/SmartReticle/Properties/AssemblyInfo.cs index d393fda..e6b4778 100644 --- a/SmartReticle/Properties/AssemblyInfo.cs +++ b/SmartReticle/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.SmartReticle.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file From 96369f565979abbc8fcfe7ef98f2e65a75514fb7 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:21:10 -0500 Subject: [PATCH 011/188] Stickers: oops... --- Stickers/Stickers/Networking/ModNetwork.Inbound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index b59d5db..d725d55 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -156,7 +156,7 @@ public static partial class ModNetwork return; } - _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxTextureSize)]; + _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxChunkCount)]; _receivedChunkCounts[sender] = 0; _expectedChunkCounts[sender] = chunkCount; _textureMetadata[sender] = (stickerSlot, textureHash, width, height); From a2d65520c97dd54d8a10feca062053e27db29345 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:28:08 -0500 Subject: [PATCH 012/188] Stickers: nvm --- Stickers/Stickers/Networking/ModNetwork.Inbound.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index d725d55..7693c98 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -67,36 +67,28 @@ public static partial class ModNetwork case MessageType.PlaceSticker: HandlePlaceSticker(msg); break; - case MessageType.ClearSticker: HandleClearSticker(msg); break; - case MessageType.ClearAllStickers: HandleClearAllStickers(msg); break; - case MessageType.StartTexture: HandleStartTexture(msg); break; - case MessageType.SendTexture: HandleSendTexture(msg); break; - case MessageType.EndTexture: HandleEndTexture(msg); break; - case MessageType.RequestTexture: HandleRequestTexture(msg); break; - default: LoggerInbound($"Invalid message type received: {msgTypeRaw}"); break; } - } catch (Exception e) { @@ -156,10 +148,10 @@ public static partial class ModNetwork return; } - _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxChunkCount)]; - _receivedChunkCounts[sender] = 0; - _expectedChunkCounts[sender] = chunkCount; _textureMetadata[sender] = (stickerSlot, textureHash, width, height); + _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxTextureSize)]; + _expectedChunkCounts[sender] = Mathf.Clamp(chunkCount, 0, MaxChunkCount); + _receivedChunkCounts[sender] = 0; LoggerInbound($"Received StartTexture message from {sender}: Slot: {stickerSlot}, Hash: {textureHash}, Chunks: {chunkCount}, Resolution: {width}x{height}"); } From 9c45d8179d356fbe8441b48593242d043da11cee Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:37:17 -0500 Subject: [PATCH 013/188] Stickers: deleted whitespace --- Stickers/Stickers/Networking/ModNetwork.Inbound.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index 7693c98..7a6af74 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -18,7 +18,7 @@ public static partial class ModNetwork #endregion Inbound Buffers #region Reset Method - + public static void Reset() { _textureChunkBuffers.Clear(); From f711f6fb9c34314ce5417aa3035e267d5cc85072 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:06:25 -0500 Subject: [PATCH 014/188] Stickers: Fixed error saving melon prefs --- Stickers/ModSettings.cs | 7 ++++++- Stickers/Properties/AssemblyInfo.cs | 2 +- Stickers/format.json | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Stickers/ModSettings.cs b/Stickers/ModSettings.cs index 85dbae5..0ea6971 100644 --- a/Stickers/ModSettings.cs +++ b/Stickers/ModSettings.cs @@ -51,7 +51,7 @@ public static class ModSettings Category.CreateEntry("tab_double_click", TabDoubleClick.ToggleStickerMode, "Tab Double Click", "The action to perform when double clicking the Stickers tab."); internal static readonly MelonPreferences_Entry Hidden_SelectedStickerNames = - Category.CreateEntry("selected_sticker_name", Array.Empty(), + Category.CreateEntry("selected_sticker_name", new[] { "", "", "", "" }, display_name: "Selected Sticker Name", description: "The name of the sticker selected for stickering.", is_hidden: true); @@ -78,6 +78,11 @@ public static class ModSettings // ensure sticker slots are initialized to the correct size string[] selectedStickerNames = Hidden_SelectedStickerNames.Value; if (selectedStickerNames.Length != MaxStickerSlots) Array.Resize(ref selectedStickerNames, MaxStickerSlots); + + // ensure theres no null entries so toml shuts the fuck up + for (int i = 0; i < selectedStickerNames.Length; i++) + selectedStickerNames[i] ??= ""; + Hidden_SelectedStickerNames.Value = selectedStickerNames; foreach (var selectedSticker in selectedStickerNames) diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index cf05529..45de81f 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.Stickers.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.5"; + public const string Version = "1.0.6"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/Stickers/format.json b/Stickers/format.json index c29740a..8eb9979 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -1,7 +1,7 @@ { "_id": 232, "name": "Stickers", - "modversion": "1.0.5", + "modversion": "1.0.6", "gameversion": "2024r175", "loaderversion": "0.6.1", "modtype": "Mod", @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Added Friends Only setting.\n- Added button to clear sticker thumbnail cache.\n- Added Identify button to Player Selection page.\n- Added `[NoSticker]` GameObject name check. \n- Adjusted inbound network buffers to be cleared on initial connection to an instance.\n- Adjusted selecting a new image for a sticker slot to clear stickers in-scene for that slot.\n- Stripped all unused classes a bunch of other methods from decalery.\n - Completely removed Skinned Mesh Renderer support as it required running on CPU.\n - Most uploaded content is not marked as readable anyways (plus it crashed consistantly).\n- Fixed nullref spam when clearing stickers when sticker was already marked as dead.", + "changelog": "- Added Friends Only setting.\n- Added button to clear sticker thumbnail cache.\n- Added Identify button to Player Selection page.\n- Added `[NoSticker]` GameObject name check. \n- Adjusted inbound network buffers to be cleared on initial connection to an instance.\n- Adjusted selecting a new image for a sticker slot to clear stickers in-scene for that slot.\n- Stripped all unused classes a bunch of other methods from decalery.\n - Completely removed Skinned Mesh Renderer support as it required running on CPU.\n - Most uploaded content is not marked as readable anyways (plus it crashed consistantly).\n- Fixed nullref spam when clearing stickers when sticker was already marked as dead.\n- Fixed issue where saving melon preferences would error due to null sticker selection.", "embedcolor": "#f61963" } \ No newline at end of file From 0cf0d4601888fe6bb37f9651c89c3e8eaf28a55f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:36:26 -0500 Subject: [PATCH 015/188] removal --- LICENSE | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 4480281..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 NotAKidoS - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. From 6ebc55ee165d4d8ffd4cb7d375e1e96df4690908 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:39:46 -0500 Subject: [PATCH 016/188] updated readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 024e796..4271937 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,12 @@ If preferred, you may also download the most recent version directly from the ** It is important to exercise caution when using anything outside of the ChilloutVR Modding Group releases, as it may not have undergone review and could be harmful or non-functional. +# License + +Copyright © 2024 NotAKidoS - All Rights Reserved unless otherwise specified. + +The repository history contains MIT-licensed commits. Refer to specific commits or versions for the applicable license terms. Commits up to #848 are MIT-licensed. + --- Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. From d409bf17438c649818f395126cb4a5a683996314 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:57:23 -0500 Subject: [PATCH 017/188] ScriptingSpoofer: fixes for latest scripting builds --- ScriptingSpoofer/Main.cs | 10 ++++------ ScriptingSpoofer/Properties/AssemblyInfo.cs | 4 +++- ScriptingSpoofer/format.json | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ScriptingSpoofer/Main.cs b/ScriptingSpoofer/Main.cs index 9de6130..e0d34d8 100644 --- a/ScriptingSpoofer/Main.cs +++ b/ScriptingSpoofer/Main.cs @@ -83,9 +83,8 @@ public class ScriptingSpoofer : MelonMod private static class PlayerApiPatches { [HarmonyPrefix] - [HarmonyPatch(typeof(LocalPlayerAPI), nameof(LocalPlayerAPI.Username), MethodType.Getter)] - [HarmonyPatch(typeof(PlayerAPIBase), nameof(PlayerAPIBase.Username), MethodType.Getter)] - private static bool GetSpoofedUsername(ref PlayerAPIBase __instance, ref string __result) + [HarmonyPatch(typeof(Player), nameof(Player.Username), MethodType.Getter)] + private static bool GetSpoofedUsername(ref Player __instance, ref string __result) { if (__instance.IsRemote) return true; if (!EntryEnabled.Value) return true; @@ -95,9 +94,8 @@ public class ScriptingSpoofer : MelonMod } [HarmonyPrefix] - [HarmonyPatch(typeof(LocalPlayerAPI), nameof(LocalPlayerAPI.UserID), MethodType.Getter)] - [HarmonyPatch(typeof(PlayerAPIBase), nameof(PlayerAPIBase.UserID), MethodType.Getter)] - private static bool GetSpoofedUserId(ref PlayerAPIBase __instance, ref string __result) + [HarmonyPatch(typeof(Player), nameof(Player.UserID), MethodType.Getter)] + private static bool GetSpoofedUserId(ref Player __instance, ref string __result) { if (__instance.IsRemote) return true; if (!EntryEnabled.Value) return true; diff --git a/ScriptingSpoofer/Properties/AssemblyInfo.cs b/ScriptingSpoofer/Properties/AssemblyInfo.cs index 94c7f77..f6ce835 100644 --- a/ScriptingSpoofer/Properties/AssemblyInfo.cs +++ b/ScriptingSpoofer/Properties/AssemblyInfo.cs @@ -20,11 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.ScriptingSpoofer.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/ScriptingSpoofer/format.json b/ScriptingSpoofer/format.json index 09662f0..581884e 100644 --- a/ScriptingSpoofer/format.json +++ b/ScriptingSpoofer/format.json @@ -1,7 +1,7 @@ { "_id": -1, "name": "ScriptingSpoofer", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2024r176", "loaderversion": "0.6.1", "modtype": "Mod", @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r25/ScriptingSpoofer.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScriptingSpoofer/", - "changelog": "- Initial Release", - "embedcolor": "#00FFFF" + "changelog": "- Initial release", + "embedcolor": "#f61963" } \ No newline at end of file From 9433779641a87f25885ba4e6cf6a8315e01c1deb Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:57:39 -0500 Subject: [PATCH 018/188] Stickers: added lazy placement preview --- Stickers/Main.cs | 22 +++- Stickers/Patches.cs | 2 + Stickers/Stickers/StickerData.cs | 104 ++++++++++++++++-- Stickers/Stickers/StickerSystem.Main.cs | 11 +- .../Stickers/StickerSystem.PlayerCallbacks.cs | 2 +- .../StickerSystem.StickerLifecycle.cs | 35 +++++- 6 files changed, 157 insertions(+), 19 deletions(-) diff --git a/Stickers/Main.cs b/Stickers/Main.cs index 8847dac..4b223f4 100644 --- a/Stickers/Main.cs +++ b/Stickers/Main.cs @@ -1,4 +1,6 @@ -using ABI_RC.Core.Player; +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; using ABI_RC.Systems.InputManagement; using MelonLoader; using NAK.Stickers.Integrations; @@ -35,11 +37,19 @@ public class StickerMod : MelonMod if (StickerSystem.Instance == null) return; - if (StickerSystem.Instance.IsInStickerMode - && Input.mouseScrollDelta.y != 0f - && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus - && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom - StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; + if (!MetaPort.Instance.isUsingVr + && StickerSystem.Instance.IsInStickerMode) + { + if (Input.mouseScrollDelta.y != 0f + && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus + && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom + { + StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; + } + StickerSystem.Instance.PlaceStickerFromControllerRay(PlayerSetup.Instance.activeCam.transform, CVRHand.Left, true); + } + + StickerSystem.Instance.UpdateStickerPreview(); // flashy flash if (!ModSettings.Entry_UsePlaceBinding.Value) return; diff --git a/Stickers/Patches.cs b/Stickers/Patches.cs index 717ebb2..4fc6b1b 100644 --- a/Stickers/Patches.cs +++ b/Stickers/Patches.cs @@ -26,6 +26,8 @@ internal static class ControllerRayPatches if (!StickerSystem.Instance.IsInStickerMode) return; + StickerSystem.Instance.PlaceStickerFromControllerRay(__instance.rayDirectionTransform, __instance.hand, true); // preview + if (__instance._gripDown) StickerSystem.Instance.IsInStickerMode = false; if (__instance._hitUIInternal || !__instance._interactDown) return; diff --git a/Stickers/Stickers/StickerData.cs b/Stickers/Stickers/StickerData.cs index da33888..4b4aaa9 100644 --- a/Stickers/Stickers/StickerData.cs +++ b/Stickers/Stickers/StickerData.cs @@ -16,7 +16,6 @@ namespace NAK.Stickers private Vector3 _lastPlacedPosition = Vector3.zero; - private readonly DecalType _decal; private readonly DecalSpawner[] _decalSpawners; private readonly Guid[] _textureHashes; @@ -27,7 +26,6 @@ namespace NAK.Stickers { PlayerId = playerId; - _decal = ScriptableObject.CreateInstance(); _decalSpawners = new DecalSpawner[decalSpawnersCount]; _materials = new Material[decalSpawnersCount]; _textureHashes = new Guid[decalSpawnersCount]; @@ -35,14 +33,14 @@ namespace NAK.Stickers for (int i = 0; i < decalSpawnersCount; i++) { _materials[i] = new Material(StickerMod.DecalSimpleShader); - _decal.decalSettings = new DecalSpawner.InitData + DecalSpawner.InitData decalSettings = new() { material = _materials[i], useShaderReplacement = false, inheritMaterialProperties = false, inheritMaterialPropertyBlock = false, }; - _decalSpawners[i] = DecalManager.GetSpawner(_decal.decalSettings, 4096, 1024); + _decalSpawners[i] = DecalManager.GetSpawner(decalSettings, 4096, 1024); } _audioSource = new GameObject("StickerAudioSource").AddComponent(); @@ -54,7 +52,21 @@ namespace NAK.Stickers _audioSource.maxDistance = 5f; _audioSource.minDistance = 1f; _audioSource.outputAudioMixerGroup = RootLogic.Instance.propSfx; // props are close enough to stickers - if (PlayerId == StickerSystem.PlayerLocalId) Object.DontDestroyOnLoad(_audioSource.gameObject); // keep audio source through world transitions + + // this is a hack so judge and fuck off lol + if (PlayerId == StickerSystem.PlayerLocalId) + { + Object.DontDestroyOnLoad(_audioSource.gameObject); // keep audio source through world transitions + + _previewMaterial = new Material(StickerMod.DecalSimpleShader); + _previewDecalSpawner = DecalManager.GetSpawner(new DecalSpawner.InitData + { + material = _previewMaterial, // default material + useShaderReplacement = false, + inheritMaterialProperties = false, + inheritMaterialPropertyBlock = false, + }, 4096, 1024); + } } public Guid GetTextureHash(int spawnerIndex = 0) @@ -97,9 +109,13 @@ namespace NAK.Stickers Material material = _materials[spawnerIndex]; - // Destroy the previous texture to avoid memory leaks + // destroy the previous texture to avoid memory leaks if (material.mainTexture != null) Object.Destroy(material.mainTexture); material.mainTexture = texture; + + // update the preview as well i guess cause lame + if (_previewMaterial != null && _previewSpawnerIndex != spawnerIndex) + _previewMaterial.mainTexture = texture; } public void Place(RaycastHit hit, Vector3 forwardDirection, Vector3 upDirection, int spawnerIndex = 0) @@ -166,8 +182,15 @@ namespace NAK.Stickers if (_materials[i].mainTexture != null) Object.Destroy(_materials[i].mainTexture); Object.Destroy(_materials[i]); } - - Object.Destroy(_decal); + + if (_audioSource != null) Object.Destroy(_audioSource.gameObject); + if (_previewDecalSpawner != null) // local player only + { + _previewDecalSpawner.Release(); + _previewDecalSpawner.staticGroups.Clear(); + _previewDecalSpawner.movableGroups.Clear(); + Object.Destroy(_previewMaterial); + } } public void PlayAudio() @@ -201,5 +224,70 @@ namespace NAK.Stickers material.color = color; } } + + #region Sticker Preview + + private const float FLASH_FREQUENCY = 2f; + private readonly DecalSpawner _previewDecalSpawner; + private readonly Material _previewMaterial; + private int _previewSpawnerIndex = -1; + private float _flashTime; + + public void PlacePreview(RaycastHit hit, Vector3 forwardDirection, Vector3 upDirection, int spawnerIndex = 0) + { + if (spawnerIndex < 0 || spawnerIndex >= _decalSpawners.Length) + { + StickerMod.Logger.Warning("Invalid spawner index for preview!"); + return; + } + + if (_previewDecalSpawner == null) + return; // uh fuck + + // clear previous + ClearPreview(); + + // place at hit pos + Transform rootObject = null; + GameObject hitGO = hit.transform.gameObject; + if (hitGO.scene.buildIndex == 4 || hitGO.TryGetComponent(out Animator _) || hitGO.GetComponentInParent() != null) + rootObject = hitGO.transform; + + Vector3 position = hit.point; + _previewDecalSpawner.AddDecal(position, + Quaternion.LookRotation(forwardDirection, upDirection), + hitGO, + DECAL_SIZE, DECAL_SIZE, 1f, 1f, 0f, + rootObject); + } + + public void UpdatePreview(int spawnerIndex) + { + if (_previewSpawnerIndex != spawnerIndex) + { + _previewSpawnerIndex = spawnerIndex; // update the preview image + _previewMaterial.mainTexture = _materials[spawnerIndex].mainTexture; + } + + _flashTime += Time.deltaTime; + float baseAlpha = (Mathf.Sin(_flashTime * 2f * Mathf.PI / FLASH_FREQUENCY) + 1f) / 2f; + float alpha = Mathf.Lerp(0.5f, 0.8f, baseAlpha); // 50% to 80% alpha + + Color color = _previewMaterial.color; + color.a = alpha; + _previewMaterial.color = color; + } + + public void ClearPreview() + { + if (_previewDecalSpawner == null) + return; + + _previewDecalSpawner.Release(); + _previewDecalSpawner.staticGroups.Clear(); + _previewDecalSpawner.movableGroups.Clear(); + } + + #endregion Sticker Preview } } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index a58e724..8bdc480 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -83,11 +83,18 @@ public partial class StickerSystem set { _isInStickerMode = value; - if (_isInStickerMode) CohtmlHud.Instance.SelectPropToSpawn( + if (_isInStickerMode) + { + CohtmlHud.Instance.SelectPropToSpawn( StickerCache.GetCohtmlResourcesPath(SelectedStickerName), Path.GetFileNameWithoutExtension(SelectedStickerName), "Sticker selected for stickering:"); - else CohtmlHud.Instance.ClearPropToSpawn(); + } + else + { + CohtmlHud.Instance.ClearPropToSpawn(); + ClearStickerPreview(); + } } } diff --git a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs index 89394c9..e8ab380 100644 --- a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs +++ b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs @@ -31,7 +31,7 @@ public partial class StickerSystem for (int i = 0; i < _playerStickers.Values.Count; i++) { StickerData stickerData = _playerStickers.Values.ElementAt(i); - + if (stickerData.DeathTime > 0f) { if (currentTime < stickerData.DeathTime) diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index ee2d76e..2977dff 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -20,7 +20,7 @@ public partial class StickerSystem return stickerData; } - public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left) + public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left, bool isPreview = false) { Vector3 controllerForward = transform.forward; Vector3 controllerUp = transform.up; @@ -37,6 +37,12 @@ public partial class StickerSystem ? Vector3.Slerp(controllerUp, playerUp, 0.99f) : controllerUp; + if (isPreview) + { + PlaceStickerPreview(transform.position, controllerForward, targetUp); + return; + } + if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) return; @@ -53,7 +59,7 @@ public partial class StickerSystem return true; } - private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0) + private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool isPreview = false) { StickerData stickerData = GetOrCreateStickerData(playerId); if (Time.time - stickerData.LastPlacedTime < StickerCooldown) @@ -69,6 +75,12 @@ public partial class StickerSystem if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) return false; + if (isPreview) + { + stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); + return true; + } + stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); stickerData.PlayAudio(); return true; @@ -154,6 +166,25 @@ public partial class StickerSystem _playerStickers.Clear(); _playerStickers[PlayerLocalId] = localStickerData; } + + public void PlaceStickerPreview(Vector3 position, Vector3 forward, Vector3 up) + { + AttemptPlaceSticker(PlayerLocalId, position, forward, up, true, SelectedStickerSlot, true); + } + + public void UpdateStickerPreview() + { + if (!IsInStickerMode) return; + + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.UpdatePreview(SelectedStickerSlot); + } + + public void ClearStickerPreview() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); + } #endregion Sticker Lifecycle } From d2d0e86e26fa722d0439dfbb012760ddf95df005 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:58:02 -0500 Subject: [PATCH 019/188] NAK_CVR_Mods: new project references --- NAK_CVR_Mods.sln | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index ccdda68..cf5c2e2 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -91,6 +91,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVR EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartReticle", "SmartReticle\SmartReticle.csproj", "{3C992D0C-9729-438E-800C-496B7EFFFB25}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAmIPointing\WhereAmIPointing.csproj", "{E285BCC9-D953-4066-8FA2-97EA28EB348E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -273,6 +277,14 @@ Global {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.Build.0 = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 83139bf1da88b613972887bac77a41edb33135f9 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:59:52 -0500 Subject: [PATCH 020/188] OriginShift: fixed missing harmony reference --- OriginShift/Main.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OriginShift/Main.cs b/OriginShift/Main.cs index 840caaf..eb617e0 100644 --- a/OriginShift/Main.cs +++ b/OriginShift/Main.cs @@ -20,12 +20,14 @@ namespace NAK.OriginShift; public class OriginShiftMod : MelonMod { internal static MelonLogger.Instance Logger; + internal static HarmonyLib.Harmony HarmonyInst; #region Melon Mod Overrides public override void OnInitializeMelon() { Logger = LoggerInstance; + HarmonyInst = HarmonyInstance; ModSettings.Initialize(); From 16a1a35a9179d373f6a9d3587c8801b32f2f21e3 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:04:54 -0500 Subject: [PATCH 021/188] Nevermind: fixes for r176 --- Nevermind/Main.cs | 17 ++++++++--------- Nevermind/Properties/AssemblyInfo.cs | 6 ++++-- Nevermind/README.md | 14 ++++++++++++++ Nevermind/format.json | 8 ++++---- 4 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 Nevermind/README.md diff --git a/Nevermind/Main.cs b/Nevermind/Main.cs index c631850..935e2ba 100644 --- a/Nevermind/Main.cs +++ b/Nevermind/Main.cs @@ -5,23 +5,23 @@ using UnityEngine; namespace NAK.Nevermind; -public class Nevermind : MelonMod +public class NevermindMod : MelonMod { - #region Mod Settings + #region Melon Preferences private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(Nevermind)); + MelonPreferences.CreateCategory(nameof(NevermindMod)); - private static readonly MelonPreferences_Entry Setting_NevermindKey = - Category.CreateEntry("Keybind", KeyCode.Home, description: "Key to cancel world join."); + private static readonly MelonPreferences_Entry Entry_CancelKeybind = + Category.CreateEntry("keybind", KeyCode.Home, description: "Key to cancel world join."); - #endregion + #endregion Melon Preferences #region Melon Events public override void OnUpdate() { - if (!Input.GetKeyDown(Setting_NevermindKey.Value)) + if (!Input.GetKeyDown(Entry_CancelKeybind.Value)) return; if (CVRObjectLoader.Instance == null @@ -35,7 +35,6 @@ public class Nevermind : MelonMod return; // too late to cancel, world is being loaded // Cancel world join if still downloading - CVRDownloadManager.Instance.ActiveWorldDownload = false; foreach (var download in CVRDownloadManager.Instance._downloadTasks) download.Value.JoinOnComplete = false; @@ -49,5 +48,5 @@ public class Nevermind : MelonMod ViewManager.Instance.NotifyUser("(Local) Client", "World Join Cancelled", 2f); } - #endregion + #endregion Melon Events } \ No newline at end of file diff --git a/Nevermind/Properties/AssemblyInfo.cs b/Nevermind/Properties/AssemblyInfo.cs index 01933eb..befa5b6 100644 --- a/Nevermind/Properties/AssemblyInfo.cs +++ b/Nevermind/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Reflection; [assembly: AssemblyProduct(nameof(NAK.Nevermind))] [assembly: MelonInfo( - typeof(NAK.Nevermind.Nevermind), + typeof(NAK.Nevermind.NevermindMod), nameof(NAK.Nevermind), AssemblyInfoParams.Version, AssemblyInfoParams.Author, @@ -20,11 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.Nevermind.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/Nevermind/README.md b/Nevermind/README.md new file mode 100644 index 0000000..0626800 --- /dev/null +++ b/Nevermind/README.md @@ -0,0 +1,14 @@ +# Nevermind + +Provides a cancel keybind for downloading/loading worlds on Desktop. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/Nevermind/format.json b/Nevermind/format.json index 710a545..6055a72 100644 --- a/Nevermind/format.json +++ b/Nevermind/format.json @@ -1,8 +1,8 @@ { "_id": -1, "name": "Nevermind", - "modversion": "1.0.0", - "gameversion": "2024r174", + "modversion": "1.0.1", + "gameversion": "2024r176", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r25/Nevermind.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Nevermind/", - "changelog": "- Initial Release", - "embedcolor": "#b589ec" + "changelog": "- Initial release", + "embedcolor": "#f61963" } \ No newline at end of file From 51f29106c39fe37a176e766648d870aad5afa08c Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:08:37 -0500 Subject: [PATCH 022/188] FuckToes: fixes for r176 --- FuckToes/Main.cs | 50 ++++++++++++++++++----------- FuckToes/Properties/AssemblyInfo.cs | 8 ++--- FuckToes/format.json | 4 +-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/FuckToes/Main.cs b/FuckToes/Main.cs index c98b8e0..7e6a936 100644 --- a/FuckToes/Main.cs +++ b/FuckToes/Main.cs @@ -6,46 +6,58 @@ using System.Reflection; namespace NAK.FuckToes; -public class FuckToes : MelonMod +public class FuckToesMod : MelonMod { - internal static MelonLogger.Instance Logger; + private static MelonLogger.Instance Logger; - public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(FuckToes)); + #region Melon Preferences - public static readonly MelonPreferences_Entry EntryEnabledVR = + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(FuckToesMod)); + + private static readonly MelonPreferences_Entry EntryEnabledVR = Category.CreateEntry("Enabled in HalfBody", true, description: "Nuke VRIK toes when in Halfbody."); - public static readonly MelonPreferences_Entry EntryEnabledFBT = + private static readonly MelonPreferences_Entry EntryEnabledFBT = Category.CreateEntry("Enabled in FBT", true, description: "Nuke VRIK toes when in FBT."); + #endregion Melon Preferences + + #region Melon Events + public override void OnInitializeMelon() { Logger = LoggerInstance; HarmonyInstance.Patch( typeof(VRIK).GetMethod(nameof(VRIK.AutoDetectReferences)), - prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToes).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToesMod).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) ); } + + #endregion Melon Events + #region Harmony Patches + private static void OnVRIKAutoDetectReferences_Prefix(ref VRIK __instance) { try { // Must be PlayerLocal layer and in VR - if (__instance.gameObject.layer != 8 || !MetaPort.Instance.isUsingVr) + if (__instance.gameObject.layer != 8 + || !MetaPort.Instance.isUsingVr) return; - // Not in FBT, and not enabled, perish - if (!IKSystem.Instance.BodySystem.FBTActive() && !EntryEnabledVR.Value) - return; - - // In FBT, and not enabled in fbt, perish - if (IKSystem.Instance.BodySystem.FBTActive() && !EntryEnabledFBT.Value) - return; - - __instance.references.leftToes = null; - __instance.references.rightToes = null; + switch (IKSystem.Instance.BodySystem.FullBodyActive) + { + + case false when !EntryEnabledVR.Value: // Not in FBT, and not enabled, perish + case true when !EntryEnabledFBT.Value: // In FBT, and not enabled in fbt, perish + return; + default: + __instance.references.leftToes = null; + __instance.references.rightToes = null; + break; + } } catch (Exception e) { @@ -53,4 +65,6 @@ public class FuckToes : MelonMod Logger.Error(e); } } + + #endregion Harmony Patches } \ No newline at end of file diff --git a/FuckToes/Properties/AssemblyInfo.cs b/FuckToes/Properties/AssemblyInfo.cs index 68c3504..c26d688 100644 --- a/FuckToes/Properties/AssemblyInfo.cs +++ b/FuckToes/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Reflection; [assembly: AssemblyProduct(nameof(NAK.FuckToes))] [assembly: MelonInfo( - typeof(NAK.FuckToes.FuckToes), + typeof(NAK.FuckToes.FuckToesMod), nameof(NAK.FuckToes), AssemblyInfoParams.Version, AssemblyInfoParams.Author, @@ -20,13 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 255, 200, 0)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.FuckToes.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/FuckToes/format.json b/FuckToes/format.json index de762bf..c02103b 100644 --- a/FuckToes/format.json +++ b/FuckToes/format.json @@ -1,7 +1,7 @@ { "_id": 129, "name": "FuckToes", - "modversion": "1.0.2", + "modversion": "1.0.3", "gameversion": "2023r171", "loaderversion": "0.6.1", "modtype": "Mod", @@ -19,5 +19,5 @@ "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r14/FuckToes.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckToes/", "changelog": "- Fixes for 2023r171.", - "embedcolor": "#ffc800" + "embedcolor": "#f61963" } \ No newline at end of file From a1d73bf156aef3116aa123f39def61199ed214a2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:17:59 -0500 Subject: [PATCH 023/188] deleted unfinished mods --- BetterShadowClone/BetterShadowClone.csproj | 8 - BetterShadowClone/Koneko/BoneHider.cs | 130 -------- BetterShadowClone/Main.cs | 144 --------- BetterShadowClone/ModSettings.cs | 60 ---- BetterShadowClone/Properties/AssemblyInfo.cs | 29 -- BetterShadowClone/README.md | 16 - .../Resources/bettershadowclone.assets | Bin 9661 -> 0 bytes .../Resources/bettershadowclone.assets_OLD | Bin 9425 -> 0 bytes .../ShadowClone/IShadowClone/IShadowClone.cs | 12 - .../IShadowClone/MeshShadowClone.cs | 158 ---------- .../IShadowClone/SkinnedShadowClone.cs | 296 ------------------ .../ShadowClone/ShadowCloneManager.cs | 251 --------------- BetterShadowClone/ShadowCloneHelper.cs | 168 ---------- .../TransformHider/FPRExclusion.cs | 35 --- .../ITransformHider/ITransformHider.cs | 11 - .../ITransformHider/MeshTransformHider.cs | 98 ------ .../ITransformHider/SkinnedTransformHider.cs | 253 --------------- .../TransformHider/TransformHiderManager.cs | 213 ------------- BetterShadowClone/format.json | 23 -- .../FuckCameraIndicator.csproj | 2 - FuckCameraIndicator/Main.cs | 34 -- .../Properties/AssemblyInfo.cs | 29 -- FuckCameraIndicator/README.md | 16 - FuckCameraIndicator/format.json | 23 -- MirrorClone/Main.cs | 80 ----- MirrorClone/MirrorClone.csproj | 2 - MirrorClone/MirrorClone/MirrorCloneManager.cs | 247 --------------- MirrorClone/ModSettings.cs | 31 -- MirrorClone/Properties/AssemblyInfo.cs | 29 -- MirrorClone/README.md | 16 - MirrorClone/format.json | 23 -- NAK_CVR_Mods.sln | 24 -- 32 files changed, 2461 deletions(-) delete mode 100644 BetterShadowClone/BetterShadowClone.csproj delete mode 100644 BetterShadowClone/Koneko/BoneHider.cs delete mode 100644 BetterShadowClone/Main.cs delete mode 100644 BetterShadowClone/ModSettings.cs delete mode 100644 BetterShadowClone/Properties/AssemblyInfo.cs delete mode 100644 BetterShadowClone/README.md delete mode 100644 BetterShadowClone/Resources/bettershadowclone.assets delete mode 100644 BetterShadowClone/Resources/bettershadowclone.assets_OLD delete mode 100644 BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs delete mode 100644 BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs delete mode 100644 BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs delete mode 100644 BetterShadowClone/ShadowClone/ShadowCloneManager.cs delete mode 100644 BetterShadowClone/ShadowCloneHelper.cs delete mode 100644 BetterShadowClone/TransformHider/FPRExclusion.cs delete mode 100644 BetterShadowClone/TransformHider/ITransformHider/ITransformHider.cs delete mode 100644 BetterShadowClone/TransformHider/ITransformHider/MeshTransformHider.cs delete mode 100644 BetterShadowClone/TransformHider/ITransformHider/SkinnedTransformHider.cs delete mode 100644 BetterShadowClone/TransformHider/TransformHiderManager.cs delete mode 100644 BetterShadowClone/format.json delete mode 100644 FuckCameraIndicator/FuckCameraIndicator.csproj delete mode 100644 FuckCameraIndicator/Main.cs delete mode 100644 FuckCameraIndicator/Properties/AssemblyInfo.cs delete mode 100644 FuckCameraIndicator/README.md delete mode 100644 FuckCameraIndicator/format.json delete mode 100644 MirrorClone/Main.cs delete mode 100644 MirrorClone/MirrorClone.csproj delete mode 100644 MirrorClone/MirrorClone/MirrorCloneManager.cs delete mode 100644 MirrorClone/ModSettings.cs delete mode 100644 MirrorClone/Properties/AssemblyInfo.cs delete mode 100644 MirrorClone/README.md delete mode 100644 MirrorClone/format.json diff --git a/BetterShadowClone/BetterShadowClone.csproj b/BetterShadowClone/BetterShadowClone.csproj deleted file mode 100644 index 09ffc35..0000000 --- a/BetterShadowClone/BetterShadowClone.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - - bettershadowclone.assets - - - diff --git a/BetterShadowClone/Koneko/BoneHider.cs b/BetterShadowClone/Koneko/BoneHider.cs deleted file mode 100644 index 0e0c57d..0000000 --- a/BetterShadowClone/Koneko/BoneHider.cs +++ /dev/null @@ -1,130 +0,0 @@ -// using System.Collections.Generic; -// using System.Linq; -// using NAK.BetterShadowClone; -// using UnityEngine; -// using UnityEngine.Serialization; -// -// namespace Koneko.BetterHeadHider; -// -// public class BoneHider : MonoBehaviour { -// public static ComputeShader shader; -// -// public SkinnedMeshRenderer[] targets; -// public Transform shrinkBone; -// private ComputeBuffer[] weightedBuffers; -// private int[] weightedCounts; -// private int[] bufferLayouts; -// private int[] threadGroups; -// private bool Initialized; -// -// -// #region Public Methods -// public static void SetupAvatar(GameObject avatar, Transform shrinkBone, SkinnedMeshRenderer[] targets) { -// BoneHider shrink = avatar.AddComponent(); -// shrink.shrinkBone = shrinkBone; -// shrink.targets = targets; -// } -// #endregion -// -// #region Private Methods -// private void Initialize() { -// Dispose(); -// -// weightedBuffers = new ComputeBuffer[targets.Length]; -// weightedCounts = new int[targets.Length]; -// bufferLayouts = new int[targets.Length]; -// threadGroups = new int[targets.Length]; -// -// for (int i = 0; i < targets.Length; i++) { -// List weighted = FindHeadVertices(targets[i]); -// if (weighted.Count == 0) continue; -// -// targets[i].sharedMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; -// weightedBuffers[i] = new(weighted.Count, sizeof(int)); -// weightedBuffers[i].SetData(weighted.ToArray()); -// weightedCounts[i] = weighted.Count; -// -// int bufferLayout = 0; -// if (targets[i].sharedMesh.HasVertexAttribute(UnityEngine.Rendering.VertexAttribute.Position)) bufferLayout += 3; -// if (targets[i].sharedMesh.HasVertexAttribute(UnityEngine.Rendering.VertexAttribute.Normal)) bufferLayout += 3; -// if (targets[i].sharedMesh.HasVertexAttribute(UnityEngine.Rendering.VertexAttribute.Tangent)) bufferLayout += 4; -// bufferLayouts[i] = bufferLayout; -// -// threadGroups[i] = Mathf.CeilToInt(weighted.Count / 64.0f); -// Debug.Log(threadGroups[i]); -// } -// -// Initialized = true; -// } -// -// private List FindHeadVertices(SkinnedMeshRenderer target) { -// List headVertices = new(); -// BoneWeight[] boneWeights = target.sharedMesh.boneWeights; -// HashSet bones = new(); -// -// bones = shrinkBone.GetComponentsInChildren(true).ToHashSet(); -// -// //get indexs of child bones -// HashSet weights = new(); -// for (int i = 0; i < target.bones.Length; i++) { -// if (bones.Contains(target.bones[i])) weights.Add(i); -// } -// -// for (int i = 0; i < boneWeights.Length; i++) { -// BoneWeight weight = boneWeights[i]; -// if (weights.Contains(weight.boneIndex0) || weights.Contains(weight.boneIndex1) || -// weights.Contains(weight.boneIndex2) || weights.Contains(weight.boneIndex3)) { -// headVertices.Add(i); -// } -// } -// return headVertices; -// } -// -// private void MyOnPreRender(Camera cam) { -// if (!Initialized) -// return; -// -// // NOTE: We only hide head once, so any camera rendered after wont see the head! -// -// if (cam != ShadowCloneMod.PlayerCamera // only hide in player cam, or in portable cam if debug is on -// && (!ModSettings.EntryHideInPortableCamera.Value || cam != ShadowCloneMod.HandCamera)) -// return; -// -// if (!ShadowCloneMod.CheckWantsToHideHead(cam)) -// return; // listener said no (Third Person, etc) -// -// for (int i = 0; i < targets.Length; i++) { -// SkinnedMeshRenderer target = targets[i]; -// -// if (target == null -// || !target.gameObject.activeInHierarchy -// || weightedBuffers[i] == null) continue; -// -// GraphicsBuffer vertexBuffer = targets[i].GetVertexBuffer(); -// if(vertexBuffer == null) continue; -// -// shader.SetVector(s_Pos, Vector3.positiveInfinity); // todo: fix -// shader.SetInt(s_WeightedCount, weightedCounts[i]); -// shader.SetInt(s_BufferLayout, bufferLayouts[i]); -// shader.SetBuffer(0, s_WeightedVertices, weightedBuffers[i]); -// shader.SetBuffer(0, s_VertexBuffer, vertexBuffer); -// shader.Dispatch(0, threadGroups[i], 1, 1); -// vertexBuffer.Dispose(); -// } -// } -// -// private void Dispose() { -// Initialized = false; -// if (weightedBuffers == null) return; -// foreach (ComputeBuffer c in weightedBuffers) -// c?.Dispose(); -// } -// #endregion -// -// #region Unity Events -// private void OnEnable() => Camera.onPreRender += MyOnPreRender; -// private void OnDisable() => Camera.onPreRender -= MyOnPreRender; -// private void OnDestroy() => Dispose(); -// private void Start() => Initialize(); -// #endregion -// } \ No newline at end of file diff --git a/BetterShadowClone/Main.cs b/BetterShadowClone/Main.cs deleted file mode 100644 index 81591fd..0000000 --- a/BetterShadowClone/Main.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.IO; -using MelonLoader; -using System.Reflection; -using ABI_RC.Core.Player; -using ABI_RC.Core.Util; -using ABI_RC.Core.Util.AssetFiltering; -using ABI_RC.Systems.Camera; -using UnityEngine; - -namespace NAK.BetterShadowClone; - -public class ShadowCloneMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ModSettings.Initialize(); - //VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(_ => FindCameras()); - - SharedFilter._avatarWhitelist.Add(typeof(FPRExclusion)); - SharedFilter._localComponentWhitelist.Add(typeof(FPRExclusion)); - - try - { - LoadAssetBundle(); - InitializePatches(); - } - catch (Exception e) - { - Logger.Error(e); - } - } - - #region Hide Head Override - - /// - /// Return false to prevent the head from being hidden. - /// - public static WantsToHideHeadDelegate wantsToHideHead; - public delegate bool WantsToHideHeadDelegate(Camera cam); - - public static bool CheckWantsToHideHead(Camera cam) - { - if (wantsToHideHead == null) - return true; - - foreach (Delegate @delegate in wantsToHideHead.GetInvocationList()) - { - WantsToHideHeadDelegate method = (WantsToHideHeadDelegate)@delegate; - if (!method(cam)) return false; - } - - return true; - } - - #endregion - - #region Asset Bundle Loading - - private const string BetterShadowCloneAssets = "bettershadowclone.assets"; - - private const string BoneHiderComputePath = "Assets/Koneko/ComputeShaders/BoneHider.compute"; - //private const string MeshCopyComputePath = "Assets/Koneko/ComputeShaders/MeshCopy.compute"; - - private const string ShadowCloneComputePath = "Assets/NotAKid/Shaders/ShadowClone.compute"; - private const string ShadowCloneShaderPath = "Assets/NotAKid/Shaders/ShadowClone.shader"; - private const string DummyCloneShaderPath = "Assets/NotAKid/Shaders/DummyClone.shader"; - - private void LoadAssetBundle() - { - Logger.Msg($"Loading required asset bundle..."); - using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(BetterShadowCloneAssets); - using MemoryStream memoryStream = new(); - if (resourceStream == null) { - Logger.Error($"Failed to load {BetterShadowCloneAssets}!"); - return; - } - - resourceStream.CopyTo(memoryStream); - AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); - if (assetBundle == null) { - Logger.Error($"Failed to load {BetterShadowCloneAssets}! Asset bundle is null!"); - return; - } - - // load shaders - ComputeShader shader = assetBundle.LoadAsset(BoneHiderComputePath); - shader.hideFlags |= HideFlags.DontUnloadUnusedAsset; - TransformHiderManager.shader = shader; - Logger.Msg($"Loaded {BoneHiderComputePath}!"); - - // load shadow clone shader - ComputeShader shadowCloneCompute = assetBundle.LoadAsset(ShadowCloneComputePath); - shadowCloneCompute.hideFlags |= HideFlags.DontUnloadUnusedAsset; - ShadowCloneHelper.shader = shadowCloneCompute; - Logger.Msg($"Loaded {ShadowCloneComputePath}!"); - - // load shadow clone material - Shader shadowCloneShader = assetBundle.LoadAsset(ShadowCloneShaderPath); - shadowCloneShader.hideFlags |= HideFlags.DontUnloadUnusedAsset; - ShadowCloneHelper.shadowMaterial = new Material(shadowCloneShader); - Logger.Msg($"Loaded {ShadowCloneShaderPath}!"); - - Logger.Msg("Asset bundle successfully loaded!"); - } - - #endregion - - #region Harmony Patches - - private void InitializePatches() - { - HarmonyInstance.Patch( - typeof(TransformHiderForMainCamera).GetMethod(nameof(TransformHiderForMainCamera.ProcessHierarchy)), - prefix: new HarmonyLib.HarmonyMethod(typeof(ShadowCloneMod).GetMethod(nameof(OnTransformHiderForMainCamera_ProcessHierarchy_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), - prefix: new HarmonyLib.HarmonyMethod(typeof(ShadowCloneMod).GetMethod(nameof(OnPlayerSetup_ClearAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - } - - private static void OnPlayerSetup_ClearAvatar_Prefix() - { - TransformHiderManager.Instance.OnAvatarCleared(); - ShadowCloneManager.Instance.OnAvatarCleared(); - } - - private static void OnTransformHiderForMainCamera_ProcessHierarchy_Prefix(ref bool __runOriginal) - { - if (!__runOriginal || (__runOriginal = !ModSettings.EntryEnabled.Value)) - return; // if something else disabled, or we are disabled, don't run - - ShadowCloneHelper.SetupAvatar(PlayerSetup.Instance._avatar); - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/ModSettings.cs b/BetterShadowClone/ModSettings.cs deleted file mode 100644 index de687fa..0000000 --- a/BetterShadowClone/ModSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -using MelonLoader; -using UnityEngine; - -namespace NAK.BetterShadowClone; - -public static class ModSettings -{ - #region Melon Prefs - - private const string SettingsCategory = nameof(ShadowCloneMod); - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(SettingsCategory); - - internal static readonly MelonPreferences_Entry EntryEnabled = - Category.CreateEntry("Enabled", true, - description: "Enable Mirror Clone."); - - internal static readonly MelonPreferences_Entry EntryUseShadowClone = - Category.CreateEntry("Use Shadow Clone", true, - description: "Should you have shadow clones?"); - - internal static readonly MelonPreferences_Entry EntryCopyMaterialToShadow = - Category.CreateEntry("Copy Material to Shadow", true, - description: "Should the shadow clone copy the material from the original mesh? Note: This can have a slight performance hit."); - - internal static readonly MelonPreferences_Entry EntryDontRespectFPR = - Category.CreateEntry("Dont Respect FPR", false, - description: "Should the transform hider not respect FPR?"); - - internal static readonly MelonPreferences_Entry EntryDebugHeadHide = - Category.CreateEntry("Debug Head Hide", false, - description: "Should head be hidden for first render?"); - - internal static readonly MelonPreferences_Entry EntryDebugShowShadow = - Category.CreateEntry("Debug Show Shadow", false, - description: "Should the shadow clone be shown?"); - - internal static readonly MelonPreferences_Entry EntryDebugShowInFront = - Category.CreateEntry("Debug Show in Front", false, - description: "Should the shadow clone be shown in front?"); - - - #endregion - - internal static void Initialize() - { - foreach (MelonPreferences_Entry setting in Category.Entries) - setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); - } - - private static void OnSettingsChanged(object oldValue = null, object newValue = null) - { - TransformHiderManager.s_DisallowFprExclusions = EntryDontRespectFPR.Value; - TransformHiderManager.s_DebugHeadHide = EntryDebugHeadHide.Value; - ShadowCloneManager.s_CopyMaterialsToShadow = EntryCopyMaterialToShadow.Value; - ShadowCloneManager.s_DebugShowShadow = EntryDebugShowShadow.Value; - ShadowCloneManager.s_DebugShowInFront = EntryDebugShowInFront.Value; - } -} \ No newline at end of file diff --git a/BetterShadowClone/Properties/AssemblyInfo.cs b/BetterShadowClone/Properties/AssemblyInfo.cs deleted file mode 100644 index 58368ed..0000000 --- a/BetterShadowClone/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MelonLoader; -using NAK.BetterShadowClone.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.BetterShadowClone))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.BetterShadowClone))] - -[assembly: MelonInfo( - typeof(NAK.BetterShadowClone.ShadowCloneMod), - nameof(NAK.BetterShadowClone), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShadowCloneMod" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] - -namespace NAK.BetterShadowClone.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.0"; - public const string Author = "NotAKidoS & Exterrata"; -} \ No newline at end of file diff --git a/BetterShadowClone/README.md b/BetterShadowClone/README.md deleted file mode 100644 index af9d39c..0000000 --- a/BetterShadowClone/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# EzCurls - -A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures. - -The settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too. - ---- - -Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/BetterShadowClone/Resources/bettershadowclone.assets b/BetterShadowClone/Resources/bettershadowclone.assets deleted file mode 100644 index 42c173453cae0026e57c0f831dbca9a40ff90d1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9661 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 Xzmy29udSO(taj=*6R@}c{xlMt`RPha diff --git a/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs deleted file mode 100644 index e6cc161..0000000 --- a/BetterShadowClone/ShadowClone/IShadowClone/IShadowClone.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace NAK.BetterShadowClone; - -public interface IShadowClone : IDisposable -{ - bool IsValid { get; } - bool Process(); - void RenderForShadow(); - void RenderForUiCulling(); - void ResetMainMesh(); -} \ No newline at end of file diff --git a/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs deleted file mode 100644 index d27883b..0000000 --- a/BetterShadowClone/ShadowClone/IShadowClone/MeshShadowClone.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public struct MeshShadowClone : IShadowClone -{ - // We technically don't need a clone mesh for MeshRenderer shadow clone handling, - // but as the shadows are also utilized for UI culling, we need to have a clone mesh. - // If we don't stick with UI culling, we can just set the shadowCastingMode to ShadowsOnly when player camera renders. - - // lame 2 frame init stuff - private const int FrameInitCount = 0; - private int _frameInitCounter; - private bool _hasInitialized; - - // shadow is used to cull ui, clone always exists - private readonly bool _shouldCastShadows; - private readonly MeshRenderer _mainMesh; - private readonly MeshRenderer _shadowMesh; - private readonly MeshFilter _shadowMeshFilter; - - // material copying (unity is shit) - private bool _hasShadowMaterials; - private readonly Material[] _shadowMaterials; - private readonly MaterialPropertyBlock _shadowMaterialBlock; - - #region IShadowClone Methods - - public void ResetMainMesh(){} - - public bool IsValid => _mainMesh != null && _shadowMesh != null; - - public MeshShadowClone(MeshRenderer meshRenderer) - { - _mainMesh = meshRenderer; - MeshFilter _mainMeshFilter = meshRenderer.GetComponent(); - - if (_mainMesh == null - || _mainMesh.sharedMaterials == null - || _mainMesh.sharedMaterials.Length == 0 - || _mainMeshFilter == null - || _mainMeshFilter.sharedMesh == null) - { - Dispose(); - return; // no mesh! - } - - _shouldCastShadows = _mainMesh.shadowCastingMode != ShadowCastingMode.Off; - _mainMesh.shadowCastingMode = ShadowCastingMode.Off; // visual mesh doesn't cast shadows - - (_shadowMesh, _shadowMeshFilter) = ShadowCloneManager.InstantiateShadowClone(_mainMesh); - _shadowMesh.forceRenderingOff = true; - - // material copying shit - int materialCount = _mainMesh.sharedMaterials.Length; - Material shadowMaterial = ShadowCloneHelper.shadowMaterial; - - _shadowMaterialBlock = new MaterialPropertyBlock(); - _shadowMaterials = new Material[materialCount]; - for (int i = 0; i < materialCount; i++) _shadowMaterials[i] = shadowMaterial; - } - - public bool Process() - { - bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy; - - // copying behaviour of SkinnedShadowClone, to visually be the same when a mesh toggles - if (!shouldRender) - { - _frameInitCounter = 0; - _hasInitialized = false; - _shadowMesh.forceRenderingOff = true; - return false; - } - - if (_frameInitCounter >= FrameInitCount) - { - if (_hasInitialized) - return true; - - _hasInitialized = true; - return true; - } - - _frameInitCounter++; - return false; - } - - public void RenderForShadow() - { - _shadowMesh.shadowCastingMode = ShadowCloneManager.s_DebugShowShadow - ? ShadowCastingMode.On : ShadowCastingMode.ShadowsOnly; - - _shadowMesh.forceRenderingOff = !_shouldCastShadows; - - // shadow casting needs clone to have original materials (uv discard) - // we also want to respect material swaps... but this is fucking slow :( - - if (!ShadowCloneManager.s_CopyMaterialsToShadow) - return; - - if (_hasShadowMaterials) - { - // NOTE: will not handle material swaps unless Avatar Overrender Ui is on - _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials; - _hasShadowMaterials = false; - } - - UpdateCloneMaterialProperties(); - } - - public void RenderForUiCulling() - { - _shadowMesh.shadowCastingMode = ShadowCastingMode.On; - _shadowMesh.forceRenderingOff = false; - - // UI culling needs clone to have write-to-depth shader - if (_hasShadowMaterials) return; - _shadowMesh.sharedMaterials = _shadowMaterials; - _hasShadowMaterials = true; - - // Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow - //UpdateCloneMaterialProperties(); - } - - public void Dispose() - { - if (_shadowMesh == null) - return; // uh oh - - // Cleanup instanced Mesh & Materials - GameObject shadowMeshObject = _shadowMesh.gameObject; - UnityEngine.Object.Destroy(_shadowMeshFilter.sharedMesh); - UnityEngine.Object.Destroy(_shadowMeshFilter); - if (!_hasShadowMaterials) - { - var materials = _shadowMesh.sharedMaterials; - foreach (Material mat in materials) UnityEngine.Object.Destroy(mat); - } - UnityEngine.Object.Destroy(_shadowMesh); - UnityEngine.Object.Destroy(shadowMeshObject); - } - - #endregion - - #region Private Methods - - private void UpdateCloneMaterialProperties() - { - // copy material properties to shadow clone materials - _mainMesh.GetPropertyBlock(_shadowMaterialBlock); - _shadowMesh.SetPropertyBlock(_shadowMaterialBlock); - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs b/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs deleted file mode 100644 index 89d7494..0000000 --- a/BetterShadowClone/ShadowClone/IShadowClone/SkinnedShadowClone.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public class SkinnedShadowClone : IShadowClone -{ - private static readonly int s_SourceBufferId = Shader.PropertyToID("_sourceBuffer"); - private static readonly int s_TargetBufferId = Shader.PropertyToID("_targetBuffer"); - private static readonly int s_HiddenVerticiesId = Shader.PropertyToID("_hiddenVertices"); - private static readonly int s_HiddenVertexPos = Shader.PropertyToID("_hiddenVertexPos"); - private static readonly int s_SourceBufferLayoutId = Shader.PropertyToID("_sourceBufferLayout"); - private static readonly int s_SourceRootMatrix = Shader.PropertyToID("_rootBoneMatrix"); - - // lame 2 frame init stuff - private const int FrameInitCount = 0; - private int _frameInitCounter; - private bool _hasInitialized; - - // shadow is used to cull ui, clone always exists - private readonly bool _shouldCastShadows; - private readonly SkinnedMeshRenderer _mainMesh; - private readonly MeshRenderer _shadowMesh; - private readonly MeshFilter _shadowMeshFilter; - private readonly Transform _rootBone; - - // clone copying - private GraphicsBuffer _graphicsBuffer; - private GraphicsBuffer _targetBuffer; - private ComputeBuffer _computeBuffer; - private int _threadGroups; - private int _bufferLayout; - - // material copying (unity is shit) - private bool _hasShadowMaterials; - private readonly Material[] _shadowMaterials; - private readonly MaterialPropertyBlock _shadowMaterialBlock; - - #region IShadowClone Methods - - // anything player can touch is suspect to death - public bool IsValid => _mainMesh != null && _shadowMesh != null && _rootBone != null; - - internal SkinnedShadowClone(SkinnedMeshRenderer renderer, FPRExclusion exclusion) - { - _mainMesh = renderer; - - if (_mainMesh == null - || _mainMesh.sharedMesh == null - || _mainMesh.sharedMaterials == null - || _mainMesh.sharedMaterials.Length == 0) - { - Dispose(); - return; // no mesh! - } - - - FindExclusionVertList(_mainMesh, exclusion); - - if (exclusion.affectedVertexIndices.Count == 0) - { - Dispose(); - return; // no affected verts! - } - - _computeBuffer = new ComputeBuffer(_mainMesh.sharedMesh.vertexCount, sizeof(int)); - _computeBuffer.SetData(exclusion.affectedVertexIndices.ToArray()); - exclusion.affectedVertexIndices.Clear(); - - - - _shouldCastShadows = _mainMesh.shadowCastingMode != ShadowCastingMode.Off; - //_mainMesh.shadowCastingMode = ShadowCastingMode.On; // visual mesh doesn't cast shadows - - (_shadowMesh, _shadowMeshFilter) = ShadowCloneManager.InstantiateShadowClone(_mainMesh); - _shadowMesh.shadowCastingMode = ShadowCastingMode.Off; // shadow mesh doesn't cast shadows - _shadowMesh.forceRenderingOff = false; - - - _rootBone = _mainMesh.rootBone; - _rootBone ??= _mainMesh.transform; // fallback to transform if no root bone - - // material copying shit - int materialCount = _mainMesh.sharedMaterials.Length; - Material shadowMaterial = ShadowCloneHelper.shadowMaterial; - - _shadowMaterialBlock = new MaterialPropertyBlock(); // TODO: check if we need one per material on renderer, idk if this is only first index - _shadowMaterials = new Material[materialCount]; - for (int i = 0; i < materialCount; i++) _shadowMaterials[i] = shadowMaterial; - } - - public bool Process() - { - // some people animate renderer.enabled instead of gameObject.activeInHierarchy - // do not disable shadow clone game object, it causes a flicker when re-enabled! - bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy; - - // GraphicsBuffer becomes stale when mesh is disabled - if (!shouldRender) - { - _frameInitCounter = 0; - _hasInitialized = false; - _shadowMesh.forceRenderingOff = true; // force off if mesh is disabled - return false; // TODO: dispose stale buffers - } - - // Unity is weird, so we need to wait 2 frames before we can get the graphics buffer - if (_frameInitCounter >= FrameInitCount) - { - if (_hasInitialized) - return true; - - _hasInitialized = true; - SetupGraphicsBuffer(); - return true; - } - - _frameInitCounter++; - return false; - } - - public void RenderForShadow() - { - ResetShadowClone(); - RenderShadowClone(); - } - - public void RenderForUiCulling() - { - ConfigureShadowCloneForUiCulling(); - RenderShadowClone(); - } - - public void Dispose() - { - if (_shadowMesh != null) - { - // Cleanup instanced Mesh & Materials - GameObject shadowMeshObject = _shadowMesh.gameObject; - UnityEngine.Object.Destroy(_shadowMeshFilter.mesh); - UnityEngine.Object.Destroy(_shadowMeshFilter); - - // explain why this works - if (_hasShadowMaterials) _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials; - foreach (Material mat in _shadowMesh.sharedMaterials) UnityEngine.Object.Destroy(mat); - - UnityEngine.Object.Destroy(_shadowMesh); - UnityEngine.Object.Destroy(shadowMeshObject); - } - - _graphicsBuffer?.Dispose(); - _graphicsBuffer = null; - _targetBuffer?.Dispose(); - _targetBuffer = null; - _computeBuffer?.Dispose(); - _computeBuffer = null; - } - - #endregion - - #region Private Methods - - // Unity is weird, so we need to wait 2 frames before we can get the graphics buffer - private void SetupGraphicsBuffer() - { - Mesh mesh = _mainMesh.sharedMesh; - Mesh shadowMesh = _shadowMesh.GetComponent().mesh; - - _bufferLayout = 0; - if (mesh.HasVertexAttribute(VertexAttribute.Position)) _bufferLayout += 3; - if (mesh.HasVertexAttribute(VertexAttribute.Normal)) _bufferLayout += 3; - if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4; - _bufferLayout *= 4; // 4 bytes per float - - const float xThreadGroups = 32f; - _threadGroups = Mathf.CeilToInt(mesh.vertexCount / xThreadGroups); - - _mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; - shadowMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; - - _targetBuffer = shadowMesh.GetVertexBuffer(0); - - //Debug.Log($"Initialized! BufferLayout: {_bufferLayout}, GraphicsBuffer: {_graphicsBuffer != null}, TargetBuffer: {_targetBuffer != null}"); - } - - public static void FindExclusionVertList(SkinnedMeshRenderer renderer, FPRExclusion exclusion) - { - var boneWeights = renderer.sharedMesh.boneWeights; - - HashSet weights = new(); - for (int i = 0; i < renderer.bones.Length; i++) - { - if (exclusion.affectedChildren.Contains(renderer.bones[i])) - weights.Add(i); - } - - for (int i = 0; i < boneWeights.Length; i++) - { - BoneWeight weight = boneWeights[i]; - - Transform bone = null; - const float minWeightThreshold = 0.2f; - if (weights.Contains(weight.boneIndex0) && weight.weight0 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex0]; - else if (weights.Contains(weight.boneIndex1) && weight.weight1 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex1]; - else if (weights.Contains(weight.boneIndex2) && weight.weight2 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex2]; - else if (weights.Contains(weight.boneIndex3) && weight.weight3 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex3]; - - exclusion.affectedVertexIndices.Add(bone != null ? i : -1); - } - } - - public void ResetMainMesh() - { - _mainMesh.shadowCastingMode = ShadowCastingMode.On; - _mainMesh.forceRenderingOff = false; - - _shadowMesh.transform.position = Vector3.positiveInfinity; // nan - } - - private void ResetShadowClone() - { - if (ShadowCloneManager.s_DebugShowShadow) - { - _mainMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - _mainMesh.forceRenderingOff = false; - - _shadowMesh.shadowCastingMode = ShadowCastingMode.On; - _shadowMesh.forceRenderingOff = false; - - _shadowMesh.transform.localPosition = Vector3.zero; - } - else - { - _mainMesh.shadowCastingMode = ShadowCastingMode.On; - _mainMesh.forceRenderingOff = false; - - _shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - _shadowMesh.forceRenderingOff = !_shouldCastShadows; - } - - //_shadowMesh.enabled = true; - - // shadow casting needs clone to have original materials (uv discard) - // we also want to respect material swaps... but this is fucking slow :( - - if (!ShadowCloneManager.s_CopyMaterialsToShadow) - return; - - _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials; - UpdateCloneMaterialProperties(); - } - - private void ConfigureShadowCloneForUiCulling() - { - _shadowMesh.shadowCastingMode = ShadowCastingMode.On; - _shadowMesh.forceRenderingOff = false; - - // UI culling needs clone to have write-to-depth shader - _shadowMesh.sharedMaterials = _shadowMaterials; - - // Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow - UpdateCloneMaterialProperties(); - } - - private void RenderShadowClone() - { - // thanks sdraw, i suck at matrix math - Matrix4x4 rootMatrix = _mainMesh.localToWorldMatrix.inverse * Matrix4x4.TRS(_rootBone.position, _rootBone.rotation, Vector3.one); - - _graphicsBuffer = _mainMesh.GetVertexBuffer(); - ShadowCloneHelper.shader.SetMatrix(s_SourceRootMatrix, rootMatrix); - ShadowCloneHelper.shader.SetBuffer(0, s_SourceBufferId, _graphicsBuffer); - ShadowCloneHelper.shader.SetBuffer(0, s_TargetBufferId, _targetBuffer); - - ShadowCloneHelper.shader.SetBuffer(0, s_HiddenVerticiesId, _computeBuffer); - ShadowCloneHelper.shader.SetVector(s_HiddenVertexPos, Vector4.positiveInfinity); // temp - - ShadowCloneHelper.shader.SetInt(s_SourceBufferLayoutId, _bufferLayout); - ShadowCloneHelper.shader.Dispatch(0, _threadGroups, 1, 1); - _graphicsBuffer.Release(); - } - - private void UpdateCloneMaterialProperties() - { - // copy material properties to shadow clone materials - _mainMesh.GetPropertyBlock(_shadowMaterialBlock); - _shadowMesh.SetPropertyBlock(_shadowMaterialBlock); - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/ShadowClone/ShadowCloneManager.cs b/BetterShadowClone/ShadowClone/ShadowCloneManager.cs deleted file mode 100644 index 4443efd..0000000 --- a/BetterShadowClone/ShadowClone/ShadowCloneManager.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System.Collections.Generic; -using ABI_RC.Core; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using MagicaCloth; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public class ShadowCloneManager : MonoBehaviour -{ - #region Singleton Implementation - - private static ShadowCloneManager _instance; - public static ShadowCloneManager Instance - { - get - { - if (_instance != null) return _instance; - _instance = new GameObject("NAK.ShadowCloneManager").AddComponent(); - DontDestroyOnLoad(_instance.gameObject); - return _instance; - } - } - - #endregion - - private const string ShadowClonePostfix = "_ShadowClone"; - //public const string CVRIgnoreForUiCulling = "CVRIgnoreForUiCulling"; // TODO: Shader Tag to ignore for UI culling? - - // Game cameras - private static Camera s_MainCamera; - private static Camera s_UiCamera; - - // Settings - internal static bool s_CopyMaterialsToShadow = true; - internal static bool s_DebugShowShadow = false; - internal static bool s_DebugShowInFront = false; - private static bool s_UseShadowToCullUi; - private const string ShadowCullUiSettingName = "ExperimentalAvatarOverrenderUI"; - - // Implementation - private bool _hasRenderedThisFrame; - public static readonly List s_Exclusions = new(); - - // Shadow Clones - private readonly List s_ShadowClones = new(); - public void AddShadowClone(IShadowClone clone) - => s_ShadowClones.Add(clone); - - // Debug - private bool _debugShadowProcessingTime; - private readonly StopWatch _stopWatch = new(); - - #region Unity Events - - private void Start() - { - if (Instance != null - && Instance != this) - { - Destroy(this); - return; - } - - UpdatePlayerCameras(); - - s_CopyMaterialsToShadow = ModSettings.EntryCopyMaterialToShadow.Value; - s_DebugShowShadow = ModSettings.EntryDebugShowShadow.Value; - s_DebugShowInFront = ModSettings.EntryDebugShowInFront.Value; - s_UseShadowToCullUi = MetaPort.Instance.settings.GetSettingsBool(ShadowCullUiSettingName); - MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged); - } - - private void OnEnable() - => Camera.onPreRender += MyOnPreCull; - - private void OnDisable() - => Camera.onPreRender -= MyOnPreCull; - - private void OnDestroy() - { - MetaPort.Instance.settings.settingBoolChanged.RemoveListener(OnSettingsBoolChanged); - } - - #endregion - - #region Shadow Clone Managment - - private void Update() - { - _hasRenderedThisFrame = false; - - for (int i = s_ShadowClones.Count - 1; i >= 0; i--) - { - IShadowClone clone = s_ShadowClones[i]; - if (clone is not { IsValid: true }) - { - clone?.Dispose(); - s_ShadowClones.RemoveAt(i); - continue; // invalid or dead - } - - clone.ResetMainMesh(); - } - } - - private void MyOnPreCull(Camera cam) - { - //bool forceRenderForUiCull = s_UseShadowToCullUi && cam == s_UiCamera; - if (cam != s_MainCamera) - return; - - _hasRenderedThisFrame = true; - - _stopWatch.Start(); - - for (int i = s_ShadowClones.Count - 1; i >= 0; i--) - { - IShadowClone clone = s_ShadowClones[i]; - if (clone is not { IsValid: true }) - { - clone?.Dispose(); - s_ShadowClones.RemoveAt(i); - continue; // invalid or dead - } - - if (!clone.Process()) continue; // not ready yet or disabled - - clone.RenderForShadow(); // first cam to render - } - - _stopWatch.Stop(); - } - - #endregion - - #region Game Events - - public void OnAvatarCleared() - { - // Dispose all shadow clones BEFORE game unloads avatar - // Otherwise we memory leak the shadow clones mesh & material instances!!! - foreach (IShadowClone clone in s_ShadowClones) - clone.Dispose(); - s_ShadowClones.Clear(); - } - - private void OnSettingsBoolChanged(string settingName, bool settingValue) - { - if (settingName == ShadowCullUiSettingName) - { - s_UseShadowToCullUi = settingValue; - s_UiCamera.cullingMask = settingValue // make UI camera not see CVRLayers.PlayerClone - ? s_UiCamera.cullingMask | (1 << CVRLayers.PlayerClone) - : s_UiCamera.cullingMask & ~(1 << CVRLayers.PlayerClone); - } - } - - private void OnVRModeSwitchCompleted(bool _, Camera __) - { - UpdatePlayerCameras(); - } - - #endregion - - #region Private Methods - - private static void UpdatePlayerCameras() - { - s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent(); - s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent(); - - //s_PortableCamera = PortableCamera.Instance.cameraComponent; - } - - #endregion - - #region Static Helpers - - internal static IShadowClone CreateShadowClone(Renderer renderer, FPRExclusion exclusion) - { - return renderer switch - { - SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedShadowClone(skinnedMeshRenderer, exclusion), - MeshRenderer meshRenderer => new MeshShadowClone(meshRenderer), - _ => null - }; - } - - internal static (MeshRenderer, MeshFilter) InstantiateShadowClone(SkinnedMeshRenderer meshRenderer) - { - GameObject shadowClone = new (meshRenderer.name + ShadowClonePostfix) { layer = CVRLayers.PlayerClone }; - shadowClone.transform.SetParent(meshRenderer.transform, false); - shadowClone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - shadowClone.transform.localScale = Vector3.one; - - if (s_DebugShowShadow && s_DebugShowInFront) - { - float scale = PlayerSetup.Instance.GetPlaySpaceScale(); - Transform playerTransform = PlayerSetup.Instance.transform; - shadowClone.transform.position += playerTransform.forward * scale * 1f; - shadowClone.transform.rotation = Quaternion.AngleAxis(180f, playerTransform.up) * shadowClone.transform.rotation; - } - - MeshRenderer newMesh = shadowClone.AddComponent(); - MeshFilter newMeshFilter = shadowClone.AddComponent(); - - ShadowCloneHelper.ConfigureRenderer(newMesh, true); - - // only shadow clone should cast shadows - newMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - - // copy mesh and materials - newMeshFilter.sharedMesh = meshRenderer.sharedMesh; - newMesh.sharedMaterials = meshRenderer.sharedMaterials; - - // copy probe anchor - newMesh.probeAnchor = meshRenderer.probeAnchor; - - return (newMesh, newMeshFilter); - } - - internal static (MeshRenderer, MeshFilter) InstantiateShadowClone(MeshRenderer meshRenderer) - { - GameObject shadowClone = new (meshRenderer.name + ShadowClonePostfix) { layer = CVRLayers.PlayerClone }; - shadowClone.transform.SetParent(meshRenderer.transform, false); - shadowClone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - shadowClone.transform.localScale = Vector3.one; - - MeshRenderer newMesh = shadowClone.AddComponent(); - MeshFilter newMeshFilter = shadowClone.AddComponent(); - - ShadowCloneHelper.ConfigureRenderer(newMesh, true); - - // only shadow clone should cast shadows - newMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - - // copy mesh and materials - newMeshFilter.sharedMesh = meshRenderer.GetComponent().sharedMesh; - newMesh.sharedMaterials = meshRenderer.sharedMaterials; - - // copy probe anchor - newMesh.probeAnchor = meshRenderer.probeAnchor; - - return (newMesh, newMeshFilter); - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/ShadowCloneHelper.cs b/BetterShadowClone/ShadowCloneHelper.cs deleted file mode 100644 index 797ebb7..0000000 --- a/BetterShadowClone/ShadowCloneHelper.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using ABI_RC.Core; -using ABI.CCK.Components; -using UnityEngine; -using UnityEngine.Rendering; -using Object = UnityEngine.Object; - -namespace NAK.BetterShadowClone; - -public static class ShadowCloneHelper -{ - public static ComputeShader shader; - public static Material shadowMaterial; - - #region Avatar Setup - - public static void SetupAvatar(GameObject avatar) - { - Animator animator = avatar.GetComponent(); - if (animator == null || animator.avatar == null || animator.avatar.isHuman == false) - { - ShadowCloneMod.Logger.Warning("Avatar is not humanoid!"); - return; - } - - Transform headBone = animator.GetBoneTransform(HumanBodyBones.Head); - if (headBone == null) - { - ShadowCloneMod.Logger.Warning("Head bone not found!"); - return; - } - - var renderers = avatar.GetComponentsInChildren(true); - if (renderers == null || renderers.Length == 0) - { - ShadowCloneMod.Logger.Warning("No renderers found!"); - return; - } - - ShadowCloneMod.Logger.Msg($"Found {renderers.Length} renderers. Processing..."); - - // create shadow clones - ProcessRenderers(renderers, avatar.transform, headBone); - } - - private static void ProcessRenderers(IEnumerable renderers, Transform root, Transform headBone) - { - Stopwatch sw = Stopwatch.StartNew(); - - IReadOnlyDictionary exclusions = CollectTransformToExclusionMap(root, headBone); - var exclusion = headBone.gameObject.GetComponent(); - - // log current time - ShadowCloneMod.Logger.Msg($"CollectTransformToExclusionMap in {sw.ElapsedMilliseconds}ms"); - - foreach (Renderer renderer in renderers) - { - ConfigureRenderer(renderer); - - if (ModSettings.EntryUseShadowClone.Value) - { - IShadowClone clone = ShadowCloneManager.CreateShadowClone(renderer, exclusion); - if (clone != null) ShadowCloneManager.Instance.AddShadowClone(clone); - } - - // ITransformHider hider = TransformHiderManager.CreateTransformHider(renderer, exclusions); - // if (hider != null) TransformHiderManager.Instance.AddTransformHider(hider); - } - - sw.Stop(); - - // log current time - ShadowCloneMod.Logger.Msg($"ProcessRenderers in {sw.ElapsedMilliseconds}ms"); - } - - #endregion - - #region FPR Exclusion Processing - - private static Dictionary CollectTransformToExclusionMap(Component root, Transform headBone) - { - // add an fpr exclusion to the head bone - headBone.gameObject.AddComponent().target = headBone; - - // add an FPRExclusion for all target entries on CVRAvatar (Experimental feature) - CVRAvatar avatar = root.GetComponent(); - if (avatar != null) - { - foreach (CVRAvatarFPREntry fprEntry in avatar.fprSettingsList.Where(fprEntry => fprEntry.transform != null)) - fprEntry.transform.gameObject.AddComponent().target = fprEntry.transform; - } - - // get all FPRExclusions - var fprExclusions = root.GetComponentsInChildren(true).ToList(); - - // get all valid exclusion targets, and destroy invalid exclusions - Dictionary exclusionTargets = new(); - for (int i = fprExclusions.Count - 1; i >= 0; i--) - { - FPRExclusion exclusion = fprExclusions[i]; - if (exclusion.target == null) - { - Object.Destroy(exclusion); - continue; - } - - // first to add wins - exclusionTargets.TryAdd(exclusion.target, exclusion); - } - - // process each FPRExclusion (recursive) - foreach (FPRExclusion exclusion in fprExclusions) - ProcessExclusion(exclusion, exclusion.target); - - // log totals - ShadowCloneMod.Logger.Msg($"Exclusions: {fprExclusions.Count}"); - return exclusionTargets; - - void ProcessExclusion(FPRExclusion exclusion, Transform transform) - { - if (exclusionTargets.ContainsKey(transform) - && exclusionTargets[transform] != exclusion) return; // found other exclusion root - - exclusion.affectedChildren.Add(transform); // associate with the exclusion - exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) - - foreach (Transform child in transform) - ProcessExclusion(exclusion, child); // process children - } - } - - #endregion - - #region Generic Renderer Configuration - - internal static void ConfigureRenderer(Renderer renderer, bool isShadowClone = false) - { - // generic optimizations - renderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; - - // don't let visual/shadow mesh cull in weird worlds - renderer.allowOcclusionWhenDynamic = false; // (third person stripped local player naked when camera was slightly occluded) - - // shadow clone optimizations (always MeshRenderer) - if (isShadowClone) - { - // renderer.receiveShadows = false; - // renderer.lightProbeUsage = LightProbeUsage.Off; - // renderer.reflectionProbeUsage = ReflectionProbeUsage.Off; - return; - } - - if (renderer is not SkinnedMeshRenderer skinnedMeshRenderer) - return; - - // GraphicsBuffer becomes stale randomly otherwise ??? - //skinnedMeshRenderer.updateWhenOffscreen = true; - - // skin mesh renderer optimizations - skinnedMeshRenderer.skinnedMotionVectors = false; - skinnedMeshRenderer.forceMatrixRecalculationPerRender = false; // expensive - skinnedMeshRenderer.quality = SkinQuality.Bone4; - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/TransformHider/FPRExclusion.cs b/BetterShadowClone/TransformHider/FPRExclusion.cs deleted file mode 100644 index b07aeea..0000000 --- a/BetterShadowClone/TransformHider/FPRExclusion.cs +++ /dev/null @@ -1,35 +0,0 @@ -using UnityEngine; - -namespace NAK.BetterShadowClone; - -/// -/// Manual exclusion component for the TransformHider (FPR) system. -/// Allows you to manually hide and show a transform that would otherwise be hidden. -/// -public class FPRExclusion : MonoBehaviour -{ - public Transform target; - - internal readonly List affectedVertexIndices = new(); - - internal readonly List affectedChildren = new(); - internal readonly List relatedTasks = new(); - - private void OnEnable() - => SetFPRState(true); - - private void OnDisable() - => SetFPRState(false); - - private void SetFPRState(bool state) - { - if (relatedTasks == null) return; // no hiders to set - foreach (IFPRExclusionTask task in relatedTasks) - task.IsActive = state; - } -} - -public interface IFPRExclusionTask -{ - public bool IsActive { get; set; } -} \ No newline at end of file diff --git a/BetterShadowClone/TransformHider/ITransformHider/ITransformHider.cs b/BetterShadowClone/TransformHider/ITransformHider/ITransformHider.cs deleted file mode 100644 index 4922adf..0000000 --- a/BetterShadowClone/TransformHider/ITransformHider/ITransformHider.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NAK.BetterShadowClone; - -public interface ITransformHider : IDisposable -{ - bool IsActive { get; set; } - bool IsValid { get; } - bool Process(); - bool PostProcess(); - void HideTransform(bool forced = false); - void ShowTransform(); -} \ No newline at end of file diff --git a/BetterShadowClone/TransformHider/ITransformHider/MeshTransformHider.cs b/BetterShadowClone/TransformHider/ITransformHider/MeshTransformHider.cs deleted file mode 100644 index 523aab3..0000000 --- a/BetterShadowClone/TransformHider/ITransformHider/MeshTransformHider.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public class MeshTransformHider : ITransformHider, IFPRExclusionTask -{ - // lame 2 frame init stuff - private const int FrameInitCount = 0; - private int _frameInitCounter; - private bool _hasInitialized; - private bool _markedForDeath; - - // mesh - private readonly MeshRenderer _mainMesh; - private bool _enabledState; - - #region ITransformHider Methods - - public bool IsActive { get; set; } = true; // default hide, but FPRExclusion can override - - // anything player can touch is suspect to death - public bool IsValid => _mainMesh != null && !_markedForDeath; - - public MeshTransformHider(MeshRenderer renderer, IReadOnlyDictionary exclusions) - { - Transform rootBone = renderer.transform; - - // if no key found, dispose - if (!exclusions.TryGetValue(rootBone, out FPRExclusion exclusion)) - { - Dispose(); - return; - } - - exclusion.relatedTasks.Add(this); - - _mainMesh = renderer; - - if (_mainMesh == null - || _mainMesh.sharedMaterials == null - || _mainMesh.sharedMaterials.Length == 0) - { - Dispose(); - } - } - - public bool Process() - { - bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy; - - // GraphicsBuffer becomes stale when mesh is disabled - if (!shouldRender) - { - _frameInitCounter = 0; - _hasInitialized = false; - return false; - } - - // Unity is weird, so we need to wait 2 frames before we can get the graphics buffer - if (_frameInitCounter >= FrameInitCount) - { - if (_hasInitialized) - return true; - - _hasInitialized = true; - return true; - } - - _frameInitCounter++; - return false; - } - - public bool PostProcess() - => true; - - public void HideTransform(bool forced = false) - { - if (!forced && !IsActive) - return; - - _enabledState = _mainMesh.enabled; - _mainMesh.enabled = false; - } - - public void ShowTransform() - { - _mainMesh.enabled = _enabledState; - } - - public void Dispose() - { - _markedForDeath = true; - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/TransformHider/ITransformHider/SkinnedTransformHider.cs b/BetterShadowClone/TransformHider/ITransformHider/SkinnedTransformHider.cs deleted file mode 100644 index 10a32b3..0000000 --- a/BetterShadowClone/TransformHider/ITransformHider/SkinnedTransformHider.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Diagnostics; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public class SkinnedTransformHider : ITransformHider -{ - private static readonly int s_Pos = Shader.PropertyToID("pos"); - private static readonly int s_BufferLayout = Shader.PropertyToID("bufferLayout"); - private static readonly int s_WeightedCount = Shader.PropertyToID("weightedCount"); - private static readonly int s_WeightedVertices = Shader.PropertyToID("weightedVertices"); - private static readonly int s_VertexBuffer = Shader.PropertyToID("VertexBuffer"); - - // lame 2 frame init stuff - private const int FrameInitCount = 0; - private int _frameInitCounter; - private bool _hasInitialized; - private bool _markedForDeath; - - // mesh & bone - private readonly SkinnedMeshRenderer _mainMesh; - private readonly Transform _rootBone; - - // main hider stuff - private GraphicsBuffer _graphicsBuffer; - private int _bufferLayout; - - // subtasks - private readonly List _subTasks = new(); - - #region ITransformHider Methods - - public bool IsActive { get; set; } = true; // default hide, but FPRExclusion can override - - // anything player can touch is suspect to death - public bool IsValid => !_markedForDeath && _mainMesh != null && _rootBone != null; - - public SkinnedTransformHider(SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions) - { - Stopwatch sw = Stopwatch.StartNew(); - - _mainMesh = renderer; - - if (_mainMesh == null - || _mainMesh.sharedMesh == null - || _mainMesh.sharedMaterials == null - || _mainMesh.sharedMaterials.Length == 0) - { - Dispose(); - return; // no mesh or bone! - } - - _rootBone = _mainMesh.rootBone; - _rootBone ??= _mainMesh.transform; // fallback to transform if no root bone - - // log current time - ShadowCloneMod.Logger.Msg($"SkinnedTransformHider part 1 in {sw.ElapsedMilliseconds}ms"); - - SubTask.FindExclusionVertList(renderer, exclusions); - - foreach (var exclusion in exclusions) - { - FPRExclusion fprExclusion = exclusion.Value; - if (fprExclusion.affectedVertexIndices.Count == 0) - continue; // no affected verts - - SubTask subTask = new(this, fprExclusion, fprExclusion.affectedVertexIndices); - _subTasks.Add(subTask); - fprExclusion.relatedTasks.Add(subTask); - fprExclusion.affectedVertexIndices.Clear(); // clear list for next SkinnedTransformHider - } - - // log current time - ShadowCloneMod.Logger.Msg($"SkinnedTransformHider part 3 in {sw.ElapsedMilliseconds}ms"); - - if (_subTasks.Count == 0) - { - Dispose(); // had the bones, but not the weights :? - ShadowCloneMod.Logger.Warning("SkinnedTransformHider No valid exclusions found!"); - } - - sw.Stop(); - - ShadowCloneMod.Logger.Msg($"SkinnedTransformHider created in {sw.ElapsedMilliseconds}ms"); - } - - public bool Process() - { - bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy; - - // GraphicsBuffer becomes stale when mesh is disabled - if (!shouldRender) - { - _frameInitCounter = 0; - _hasInitialized = false; - return false; - } - - // Unity is weird, so we need to wait 2 frames before we can get the graphics buffer - if (_frameInitCounter >= FrameInitCount) - { - if (_hasInitialized) - return true; - - _hasInitialized = true; - SetupGraphicsBuffer(); - return true; - } - - _mainMesh.forceRenderingOff = true; // force off if mesh is disabled - - _frameInitCounter++; - return false; - } - - public bool PostProcess() - => false; // not needed - - public void HideTransform(bool forced = false) - { - _mainMesh.forceRenderingOff = false; - - _graphicsBuffer = _mainMesh.GetVertexBuffer(); - - foreach (SubTask subTask in _subTasks) - if ((forced || subTask.IsActive) && subTask.IsValid) - subTask.Dispatch(); - - _graphicsBuffer.Release(); - } - - public void ShowTransform() - { - // not needed - } - - public void Dispose() - { - _markedForDeath = true; - foreach (SubTask subTask in _subTasks) - subTask.Dispose(); - - _graphicsBuffer?.Dispose(); - _graphicsBuffer = null; - } - - #endregion - - #region Private Methods - - // Unity is weird, so we need to wait 2 frames before we can get the graphics buffer - private void SetupGraphicsBuffer() - { - Mesh mesh = _mainMesh.sharedMesh; - - _bufferLayout = 0; - if (mesh.HasVertexAttribute(VertexAttribute.Position)) _bufferLayout += 3; - if (mesh.HasVertexAttribute(VertexAttribute.Normal)) _bufferLayout += 3; - if (mesh.HasVertexAttribute(VertexAttribute.Tangent)) _bufferLayout += 4; - // ComputeShader is doing bitshift so we dont need to multiply by 4 - - _mainMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw; - } - - #endregion - - #region Sub Task Class - - private class SubTask : IFPRExclusionTask - { - public bool IsActive { get; set; } = true; - public bool IsValid => _computeBuffer != null; // TODO: cleanup dead tasks - - private readonly SkinnedTransformHider _parent; - private readonly Transform _shrinkBone; - private readonly int _vertexCount; - private readonly ComputeBuffer _computeBuffer; - private readonly int _threadGroups; - - public SubTask(SkinnedTransformHider parent, FPRExclusion exclusion, List exclusionVerts) - { - _parent = parent; - _shrinkBone = exclusion.target; - - _vertexCount = exclusionVerts.Count; - _computeBuffer = new ComputeBuffer(_vertexCount, sizeof(int)); - _computeBuffer.SetData(exclusionVerts.ToArray()); - - const float xThreadGroups = 64f; - _threadGroups = Mathf.CeilToInt(_vertexCount / xThreadGroups); - } - - public void Dispatch() - { - Vector3 pos = _parent._rootBone.transform.InverseTransformPoint(_shrinkBone.position) * _parent._rootBone.lossyScale.y; - TransformHiderManager.shader.SetVector(s_Pos, pos); - TransformHiderManager.shader.SetInt(s_WeightedCount, _vertexCount); - TransformHiderManager.shader.SetInt(s_BufferLayout, _parent._bufferLayout); - TransformHiderManager.shader.SetBuffer(0, s_WeightedVertices, _computeBuffer); - TransformHiderManager.shader.SetBuffer(0, s_VertexBuffer, _parent._graphicsBuffer); - TransformHiderManager.shader.Dispatch(0, _threadGroups, 1, 1); - } - - public void Dispose() - { - _computeBuffer?.Dispose(); - } - - #region Private Methods - - public static void FindExclusionVertList(SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions) - { - var boneWeights = renderer.sharedMesh.boneWeights; - - HashSet weights = new(); - for (int i = 0; i < renderer.bones.Length; i++) - { - // if bone == any key in exclusions, add to weights - if (!exclusions.TryGetValue(renderer.bones[i], out FPRExclusion _)) - continue; - - weights.Add(i); - } - - for (int i = 0; i < boneWeights.Length; i++) - { - BoneWeight weight = boneWeights[i]; - - Transform bone = null; - const float minWeightThreshold = 0.2f; - if (weights.Contains(weight.boneIndex0) && weight.weight0 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex0]; - else if (weights.Contains(weight.boneIndex1) && weight.weight1 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex1]; - else if (weights.Contains(weight.boneIndex2) && weight.weight2 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex2]; - else if (weights.Contains(weight.boneIndex3) && weight.weight3 > minWeightThreshold) - bone = renderer.bones[weight.boneIndex3]; - - if (bone == null) continue; // no bone found - - // add vertex to exclusion list - exclusions[bone].affectedVertexIndices.Add(i); - } - } - - #endregion - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/TransformHider/TransformHiderManager.cs b/BetterShadowClone/TransformHider/TransformHiderManager.cs deleted file mode 100644 index 2c14854..0000000 --- a/BetterShadowClone/TransformHider/TransformHiderManager.cs +++ /dev/null @@ -1,213 +0,0 @@ -using ABI_RC.Core.Player; -using ABI_RC.Systems.VRModeSwitch; -using MagicaCloth; -using UnityEngine; - -namespace NAK.BetterShadowClone; - -// Built on top of Koneko's BoneHider but to mimic the ShadowCloneManager - -public class TransformHiderManager : MonoBehaviour -{ - public static ComputeShader shader; - - #region Singleton Implementation - - private static TransformHiderManager _instance; - public static TransformHiderManager Instance - { - get - { - if (_instance != null) return _instance; - _instance = new GameObject("Koneko.TransformHiderManager").AddComponent(); - DontDestroyOnLoad(_instance.gameObject); - return _instance; - } - } - - #endregion - - // Game cameras - private static Camera s_MainCamera; - private static Camera s_UiCamera; - - // Settings - internal static bool s_DebugHeadHide; - internal static bool s_DisallowFprExclusions = true; - - // Implementation - private bool _hasRenderedThisFrame; - - // Shadow Clones - private readonly List s_TransformHider = new(); - public void AddTransformHider(ITransformHider clone) - => s_TransformHider.Add(clone); - - // Debug - private bool _debugHeadHiderProcessingTime; - private readonly StopWatch _stopWatch = new(); - - #region Unity Events - - private void Start() - { - if (Instance != null - && Instance != this) - { - Destroy(this); - return; - } - - UpdatePlayerCameras(); - - s_DisallowFprExclusions = ModSettings.EntryDontRespectFPR.Value; - s_DebugHeadHide = ModSettings.EntryDebugHeadHide.Value; - - VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(OnVRModeSwitchCompleted); - } - - private void OnEnable() - { - Camera.onPreRender += MyOnPreRender; - Camera.onPostRender += MyOnPostRender; - } - - private void OnDisable() - { - Camera.onPreRender -= MyOnPreRender; - Camera.onPostRender -= MyOnPostRender; - } - - private void OnDestroy() - { - VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(OnVRModeSwitchCompleted); - OnAvatarCleared(); - } - - #endregion - - #region Transform Hider Managment - - private void Update() - { - _hasRenderedThisFrame = false; - } - - private void MyOnPreRender(Camera cam) - { - if (_hasRenderedThisFrame) - return; // can only hide head once per frame - - if (cam != s_MainCamera // only hide in player cam, or if debug is on - && !s_DebugHeadHide) - return; - - if (!CheckPlayerCamWithinRange()) - return; // player is too far away (likely HoloPort or Sitting) - - if (!ShadowCloneMod.CheckWantsToHideHead(cam)) - return; // listener said no (Third Person, etc) - - _hasRenderedThisFrame = true; - - _stopWatch.Start(); - - for (int i = s_TransformHider.Count - 1; i >= 0; i--) - { - ITransformHider hider = s_TransformHider[i]; - if (hider is not { IsValid: true }) - { - hider?.Dispose(); - s_TransformHider.RemoveAt(i); - continue; // invalid or dead - } - - if (!hider.Process()) continue; // not ready yet or disabled - - hider.HideTransform(s_DisallowFprExclusions); - } - - _stopWatch.Stop(); - if (_debugHeadHiderProcessingTime) Debug.Log($"TransformHiderManager.MyOnPreRender({s_DebugHeadHide}) took {_stopWatch.ElapsedMilliseconds}ms"); - } - - private void MyOnPostRender(Camera cam) - { - if (cam != s_UiCamera) return; // ui camera is expected to render last - - for (int i = s_TransformHider.Count - 1; i >= 0; i--) - { - ITransformHider hider = s_TransformHider[i]; - if (hider is not { IsValid: true }) - { - hider?.Dispose(); - s_TransformHider.RemoveAt(i); - continue; // invalid or dead - } - - if (!hider.PostProcess()) continue; // does not need post processing - - hider.ShowTransform(); - } - } - - #endregion - - #region Game Events - - public void OnAvatarCleared() - { - // Dispose all shadow clones BEFORE game unloads avatar - // Otherwise we memory leak the shadow clones mesh & material instances!!! - foreach (ITransformHider hider in s_TransformHider) - hider.Dispose(); - s_TransformHider.Clear(); - } - - private void OnVRModeSwitchCompleted(bool _) - => UpdatePlayerCameras(); - - #endregion - - #region Private Methods - - private static void UpdatePlayerCameras() - { - s_MainCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent(); - s_UiCamera = s_MainCamera.transform.Find("_UICamera").GetComponent(); - } - - private static bool CheckPlayerCamWithinRange() - { - if (PlayerSetup.Instance == null) - return false; // hack - - const float MinHeadHidingRange = 0.5f; - Vector3 playerHeadPos = PlayerSetup.Instance.GetViewWorldPosition(); - Vector3 playerCamPos = s_MainCamera.transform.position; - float scaleModifier = PlayerSetup.Instance.GetPlaySpaceScale(); - return (Vector3.Distance(playerHeadPos, playerCamPos) < (MinHeadHidingRange * scaleModifier)); - } - - #endregion - - #region Static Helpers - - internal static bool IsLegacyFPRExcluded(Component renderer) - => renderer.gameObject.name.Contains("[FPR]"); - - internal static ITransformHider CreateTransformHider(Component renderer, IReadOnlyDictionary exclusions) - { - if (IsLegacyFPRExcluded(renderer)) - return null; - - return renderer switch - { - SkinnedMeshRenderer skinnedMeshRenderer => new SkinnedTransformHider(skinnedMeshRenderer, exclusions), - MeshRenderer meshRenderer => new MeshTransformHider(meshRenderer, exclusions), - _ => null - }; - } - - #endregion -} \ No newline at end of file diff --git a/BetterShadowClone/format.json b/BetterShadowClone/format.json deleted file mode 100644 index a2374f1..0000000 --- a/BetterShadowClone/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "EzCurls", - "modversion": "1.0.0", - "gameversion": "2023r173", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures.\n\nThe settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too.", - "searchtags": [ - "curls", - "fingers", - "index", - "knuckles" - ], - "requirements": [ - "UIExpansionKit" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r24/EzCurls.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/EzCurls/", - "changelog": "- Initial CVRMG release", - "embedcolor": "7d7d7d" -} \ No newline at end of file diff --git a/FuckCameraIndicator/FuckCameraIndicator.csproj b/FuckCameraIndicator/FuckCameraIndicator.csproj deleted file mode 100644 index e94f9dc..0000000 --- a/FuckCameraIndicator/FuckCameraIndicator.csproj +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/FuckCameraIndicator/Main.cs b/FuckCameraIndicator/Main.cs deleted file mode 100644 index a0c7957..0000000 --- a/FuckCameraIndicator/Main.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MelonLoader; -using System.Reflection; -using ABI_RC.Core.Player; -using UnityEngine; - -namespace NAK.FuckCameraIndicator; - -public class FuckCameraIndicator : MelonMod -{ - public override void OnInitializeMelon() - { - HarmonyInstance.Patch( - typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.Start), BindingFlags.NonPublic | BindingFlags.Instance), - postfix: new HarmonyLib.HarmonyMethod(typeof(FuckCameraIndicator).GetMethod(nameof(OnPuppetMasterStart_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - } - - private static void OnPuppetMasterStart_Postfix(PuppetMaster __instance) - { - // thanks for not making it modular, fucking spaghetti - // and why leave it a skinned mesh... lazy fucking implementation - - GameObject indicator = __instance.cameraIndicator; - GameObject lens = __instance.cameraIndicatorLense; - - // Disable NamePlate child object - const string c_CanvasPath = "[NamePlate]/Canvas"; - GameObject canvas = indicator.transform.Find(c_CanvasPath).gameObject; - canvas.SetActive(false); - - // Disable lens renderer - lens.GetComponent().forceRenderingOff = true; - } -} \ No newline at end of file diff --git a/FuckCameraIndicator/Properties/AssemblyInfo.cs b/FuckCameraIndicator/Properties/AssemblyInfo.cs deleted file mode 100644 index 05525a7..0000000 --- a/FuckCameraIndicator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MelonLoader; -using NAK.FuckCameraIndicator.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.FuckCameraIndicator))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.FuckCameraIndicator))] - -[assembly: MelonInfo( - typeof(NAK.FuckCameraIndicator.FuckCameraIndicator), - nameof(NAK.FuckCameraIndicator), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckCameraIndicator" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] - -namespace NAK.FuckCameraIndicator.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.0"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/FuckCameraIndicator/README.md b/FuckCameraIndicator/README.md deleted file mode 100644 index af9d39c..0000000 --- a/FuckCameraIndicator/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# EzCurls - -A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures. - -The settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too. - ---- - -Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/FuckCameraIndicator/format.json b/FuckCameraIndicator/format.json deleted file mode 100644 index a2374f1..0000000 --- a/FuckCameraIndicator/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "EzCurls", - "modversion": "1.0.0", - "gameversion": "2023r173", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures.\n\nThe settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too.", - "searchtags": [ - "curls", - "fingers", - "index", - "knuckles" - ], - "requirements": [ - "UIExpansionKit" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r24/EzCurls.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/EzCurls/", - "changelog": "- Initial CVRMG release", - "embedcolor": "7d7d7d" -} \ No newline at end of file diff --git a/MirrorClone/Main.cs b/MirrorClone/Main.cs deleted file mode 100644 index 87afe20..0000000 --- a/MirrorClone/Main.cs +++ /dev/null @@ -1,80 +0,0 @@ -using MelonLoader; -using System.Reflection; -using ABI_RC.Core.Player; -using ABI_RC.Core.Util; -using ABI_RC.Systems.IK; -using UnityEngine; - -namespace NAK.BetterShadowClone; - -public class MirrorCloneMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ModSettings.Initialize(); - - try - { - InitializePatches(); - } - catch (Exception e) - { - Logger.Error(e); - } - } - - #region Harmony Patches - - private void InitializePatches() - { - HarmonyInstance.Patch( - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Awake), BindingFlags.NonPublic | BindingFlags.Instance), - postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_Awake_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)), - prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_SetupAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)), - postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_SetupAvatar_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), - prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnPlayerSetup_ClearAvatar_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( - typeof(IKSystem).GetMethod(nameof(IKSystem.OnPostSolverUpdateGeneral), BindingFlags.NonPublic | BindingFlags.Instance), - postfix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnIKSystem_OnPostSolverUpdateGeneral_Postfix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( - typeof(TransformHiderForMainCamera).GetMethod(nameof(TransformHiderForMainCamera.ProcessHierarchy)), - prefix: new HarmonyLib.HarmonyMethod(typeof(MirrorCloneMod).GetMethod(nameof(OnTransformHiderForMainCamera_ProcessHierarchy_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) - ); - } - - private static void OnPlayerSetup_Awake_Postfix() - => MirrorCloneManager.OnPlayerSetupAwake(); - - private static void OnPlayerSetup_SetupAvatar_Prefix(GameObject inAvatar) - => MirrorCloneManager.Instance.OnAvatarInitialized(inAvatar); - - private static void OnPlayerSetup_SetupAvatar_Postfix() - => MirrorCloneManager.Instance.OnAvatarConfigured(); - - private static void OnPlayerSetup_ClearAvatar_Prefix() - => MirrorCloneManager.Instance.OnAvatarDestroyed(); - - private static void OnIKSystem_OnPostSolverUpdateGeneral_Postfix() - => MirrorCloneManager.Instance.OnPostSolverUpdateGeneral(); - - private static void OnTransformHiderForMainCamera_ProcessHierarchy_Prefix(ref bool __runOriginal) - => __runOriginal = !ModSettings.EntryEnabled.Value; - - #endregion -} \ No newline at end of file diff --git a/MirrorClone/MirrorClone.csproj b/MirrorClone/MirrorClone.csproj deleted file mode 100644 index e94f9dc..0000000 --- a/MirrorClone/MirrorClone.csproj +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/MirrorClone/MirrorClone/MirrorCloneManager.cs b/MirrorClone/MirrorClone/MirrorCloneManager.cs deleted file mode 100644 index 035c9c0..0000000 --- a/MirrorClone/MirrorClone/MirrorCloneManager.cs +++ /dev/null @@ -1,247 +0,0 @@ -using ABI_RC.Core; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Systems.IK; -using ABI.CCK.Components; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.BetterShadowClone; - -public class MirrorCloneManager : MonoBehaviour -{ - #region Static Instance - - public static MirrorCloneManager Instance { get; private set; } - - #endregion - - private bool _isAvatarConfigured; - - private GameObject _avatar; - private GameObject _mirrorClone; - private GameObject _initializationTarget; - - private CVRAnimatorManager _animatorManager; - private Animator _mirrorAnimator; - - #region Unity Events - - private void Awake() - { - if (Instance != null - && Instance != this) - { - DestroyImmediate(this); - return; - } - - Instance = this; - - MirrorCloneMod.Logger.Msg("Mirror Clone Manager initialized."); - - _animatorManager = PlayerSetup.Instance.animatorManager; - - // Create initialization target (so no components are initialized before we're ready) - _initializationTarget = new GameObject(nameof(MirrorCloneManager) + " Initialization Target"); - _initializationTarget.transform.SetParent(transform); - _initializationTarget.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - _initializationTarget.transform.localScale = Vector3.one; - _initializationTarget.SetActive(false); - } - - private void OnDestroy() - { - if (Instance == this) - Instance = null; - } - - #endregion - - #region Game Events - - public static void OnPlayerSetupAwake() - { - if (Instance != null) - return; - - GameObject manager = new (nameof(MirrorCloneManager), typeof(MirrorCloneManager)); - DontDestroyOnLoad(manager); - } - - public void OnAvatarInitialized(GameObject avatar) - { - if (!ModSettings.EntryEnabled.Value) - return; - - if (avatar == null - || _isAvatarConfigured) - return; - - _isAvatarConfigured = true; - - _avatar = avatar; - _mirrorClone = InstantiateMirrorCopy(_avatar); - } - - public void OnAvatarConfigured() - { - if (!_isAvatarConfigured) - return; - - Animator baseAnimator = _avatar.GetComponent(); - - if (!_mirrorClone.TryGetComponent(out _mirrorAnimator)) - _mirrorAnimator = gameObject.AddComponent(); - _mirrorAnimator.runtimeAnimatorController = baseAnimator.runtimeAnimatorController; - - _animatorManager._copyAnimator = _mirrorAnimator; // thank you for existing - - var cameras = PlayerSetup.Instance.GetComponentsInChildren(true); - foreach (var camera in cameras) - { - // hide PlayerClone layer from all cameras - camera.cullingMask &= ~(1 << CVRLayers.PlayerClone); - } - - var mirrors = Resources.FindObjectsOfTypeAll(); - foreach (CVRMirror mirror in mirrors) - { - // hide PlayerLocal layer from all mirrors - mirror.m_ReflectLayers &= ~(1 << CVRLayers.PlayerLocal); - } - - // scale avatar head bone to 0 0 0 - Transform headBone = baseAnimator.GetBoneTransform(HumanBodyBones.Head); - headBone.localScale = Vector3.zero; - - CleanupAvatar(); - CleanupMirrorClone(); - SetupHumanPoseHandler(); - - _initializationTarget.SetActive(true); - } - - public void OnAvatarDestroyed() - { - if (!_isAvatarConfigured) - return; - - _avatar = null; - _mirrorAnimator = null; - if (_mirrorClone != null) - Destroy(_mirrorClone); - - _initializationTarget.SetActive(false); - - _isAvatarConfigured = false; - } - - public void OnPostSolverUpdateGeneral() - { - if (!_isAvatarConfigured) - return; - - StealTransforms(); - } - - #endregion - - #region Private Methods - - private GameObject InstantiateMirrorCopy(GameObject original) - { - GameObject clone = Instantiate(original, _initializationTarget.transform); - clone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - clone.name = original.name + " (Mirror Clone)"; - clone.SetLayerRecursive(CVRLayers.PlayerClone); - return clone; - } - - private void CleanupAvatar() - { - // set local avatar mesh to shadow off - var avatarMeshes = _avatar.GetComponentsInChildren(true); - foreach (SkinnedMeshRenderer avatarMesh in avatarMeshes) - { - avatarMesh.shadowCastingMode = ShadowCastingMode.Off; - avatarMesh.forceMatrixRecalculationPerRender = false; - } - } - - private void CleanupMirrorClone() - { - // destroy unneeded components - // only keep Animator - - var components = _mirrorClone.GetComponentsInChildren(true); - foreach (Component component in components) - { - if (component == null) - continue; - - // skip basic unity components - if (component is Animator - or Transform - or SkinnedMeshRenderer - or MeshRenderer - or MeshFilter) - continue; - - // skip basic CVR components - if (component is CVRAvatar or CVRAssetInfo) - { - (component as MonoBehaviour).enabled = false; - continue; - } - - Destroy(component); - } - } - - #endregion - - #region Job System - - private HumanPoseHandler _humanPoseHandler; - private Transform _hipTransform; - - private void SetupHumanPoseHandler() - { - _hipTransform = _mirrorAnimator.GetBoneTransform(HumanBodyBones.Hips); - - _humanPoseHandler?.Dispose(); - _humanPoseHandler = new HumanPoseHandler(_mirrorAnimator.avatar, _mirrorAnimator.transform); - } - - private void StealTransforms() - { - // copy transforms from avatar to mirror clone - // var avatarTransforms = _avatar.GetComponentsInChildren(true); - // var mirrorCloneTransforms = _mirrorClone.GetComponentsInChildren(true); - // for (int i = 0; i < avatarTransforms.Length; i++) - // { - // Transform avatarTransform = avatarTransforms[i]; - // Transform mirrorCloneTransform = mirrorCloneTransforms[i]; - // - // mirrorCloneTransform.SetLocalPositionAndRotation( - // avatarTransform.localPosition, - // avatarTransform.localRotation); - // } - - if (!IKSystem.Instance.IsAvatarCalibrated()) - return; - - IKSystem.Instance._humanPoseHandler.GetHumanPose(ref IKSystem.Instance._humanPose); - _humanPoseHandler.SetHumanPose(ref IKSystem.Instance._humanPose); - - if (!MetaPort.Instance.isUsingVr) - _mirrorAnimator.transform.SetPositionAndRotation(PlayerSetup.Instance.GetPlayerPosition(), PlayerSetup.Instance.GetPlayerRotation()); - else - _mirrorAnimator.transform.SetPositionAndRotation(_avatar.transform.position, _avatar.transform.rotation); - - _hipTransform.SetPositionAndRotation(IKSystem.Instance._hipTransform.position, IKSystem.Instance._hipTransform.rotation); - } - - #endregion -} \ No newline at end of file diff --git a/MirrorClone/ModSettings.cs b/MirrorClone/ModSettings.cs deleted file mode 100644 index 05c753a..0000000 --- a/MirrorClone/ModSettings.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MelonLoader; -using UnityEngine; - -namespace NAK.BetterShadowClone; - -public static class ModSettings -{ - #region Melon Prefs - - private const string SettingsCategory = nameof(MirrorCloneMod); - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(SettingsCategory); - - internal static readonly MelonPreferences_Entry EntryEnabled = - Category.CreateEntry("Enabled", true, - description: "Enable Mirror Clone."); - - #endregion - - internal static void Initialize() - { - foreach (MelonPreferences_Entry setting in Category.Entries) - setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); - } - - internal static void OnSettingsChanged(object oldValue = null, object newValue = null) - { - - } -} \ No newline at end of file diff --git a/MirrorClone/Properties/AssemblyInfo.cs b/MirrorClone/Properties/AssemblyInfo.cs deleted file mode 100644 index 611ba51..0000000 --- a/MirrorClone/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MelonLoader; -using NAK.BetterShadowClone.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.BetterShadowClone))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.BetterShadowClone))] - -[assembly: MelonInfo( - typeof(NAK.BetterShadowClone.MirrorCloneMod), - nameof(NAK.BetterShadowClone), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/MirrorCloneMod" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] - -namespace NAK.BetterShadowClone.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.0"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/MirrorClone/README.md b/MirrorClone/README.md deleted file mode 100644 index af9d39c..0000000 --- a/MirrorClone/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# EzCurls - -A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures. - -The settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too. - ---- - -Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/MirrorClone/format.json b/MirrorClone/format.json deleted file mode 100644 index a2374f1..0000000 --- a/MirrorClone/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "EzCurls", - "modversion": "1.0.0", - "gameversion": "2023r173", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "A mod that allows you to tune your finger curls to your liking. Supposedly can help with VR sign language, as raw finger curls are not that great for quick and precise gestures.\n\nThe settings are not too coherent, it is mostly a bunch of things thrown at the wall, but it works for me. I hope it works for you too.", - "searchtags": [ - "curls", - "fingers", - "index", - "knuckles" - ], - "requirements": [ - "UIExpansionKit" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r24/EzCurls.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/EzCurls/", - "changelog": "- Initial CVRMG release", - "embedcolor": "7d7d7d" -} \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index cf5c2e2..c602176 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -31,12 +31,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", "EzCurls\EzCurls. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysicsGunMod", "PhysicsGunMod\PhysicsGunMod.csproj", "{F94DDB73-9041-4F5C-AD43-6960701E8417}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MirrorClone", "MirrorClone\MirrorClone.csproj", "{D5E81123-9D3B-4420-9CCD-1861657BE00B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterShadowClone", "BetterShadowClone\BetterShadowClone.csproj", "{D0C40987-AF16-490A-9304-F99D5A5A774C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckCameraIndicator", "FuckCameraIndicator\FuckCameraIndicator.csproj", "{0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadowCloneFallback", "ShadowCloneFallback\ShadowCloneFallback.csproj", "{69AF3C10-1BB1-4746-B697-B5A81D78C8D9}" @@ -85,8 +79,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NukePostPresentHandoff", "NukePostPresentHandoff\NukePostPresentHandoff.csproj", "{77F332CD-019A-472F-9269-CFDEB087B3F9}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" @@ -157,18 +149,6 @@ Global {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.Build.0 = Release|Any CPU - {D5E81123-9D3B-4420-9CCD-1861657BE00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5E81123-9D3B-4420-9CCD-1861657BE00B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5E81123-9D3B-4420-9CCD-1861657BE00B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5E81123-9D3B-4420-9CCD-1861657BE00B}.Release|Any CPU.Build.0 = Release|Any CPU - {D0C40987-AF16-490A-9304-F99D5A5A774C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0C40987-AF16-490A-9304-F99D5A5A774C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0C40987-AF16-490A-9304-F99D5A5A774C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0C40987-AF16-490A-9304-F99D5A5A774C}.Release|Any CPU.Build.0 = Release|Any CPU - {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BE10630-EA6A-40FB-B3AF-5C2018F22BD3}.Release|Any CPU.Build.0 = Release|Any CPU {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -265,10 +245,6 @@ Global {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU - {77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.Build.0 = Release|Any CPU {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU From fd4fe2ea9dc68c5b761fe44b4c5a679bb5be629a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:19:20 -0500 Subject: [PATCH 024/188] AvatarScaleMod: renamed folder --- .../AvatarScaleMod.csproj | 0 .../AvatarScaling/AvatarScaleManager.cs | 0 .../AvatarScaling/Components/BaseScaler.cs | 0 .../AvatarScaling/Components/LocalScaler.cs | 0 .../AvatarScaling/Components/NetworkScaler.cs | 0 .../AvatarScaling/Events/AvatarScaleEvents.cs | 0 .../AvatarScaling/ScaledComponents.cs | 0 {AvatarScale => AvatarScaleMod}/HarmonyPatches.cs | 0 .../Input/DebugKeybinds.cs | 0 .../Input/ScaleReconizer.cs | 0 .../Integrations/BTKUI/BtkUiAddon.cs | 0 .../BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs | 0 .../BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs | 0 .../BTKUI/BtkUiAddon_CAT_DebugOptions.cs | 0 .../BtkUiAddon_CAT_UniversalScalingSettings.cs | 0 .../BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs | 0 .../Integrations/BTKUI/BtkUiAddon_Utils.cs | 0 {AvatarScale => AvatarScaleMod}/Main.cs | 0 {AvatarScale => AvatarScaleMod}/ModSettings.cs | 0 .../Networking/ModNetwork.cs | 0 .../Properties/AssemblyInfo.cs | 0 {AvatarScale => AvatarScaleMod}/README.md | 0 {AvatarScale => AvatarScaleMod}/Scripts.cs | 0 {AvatarScale => AvatarScaleMod}/format.json | 0 .../resources/ASM_Icon_AvatarHeightConfig.png | Bin .../resources/ASM_Icon_AvatarHeightCopy.png | Bin {AvatarScale => AvatarScaleMod}/resources/menu.js | 0 NAK_CVR_Mods.sln | 12 ++++++------ 28 files changed, 6 insertions(+), 6 deletions(-) rename {AvatarScale => AvatarScaleMod}/AvatarScaleMod.csproj (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/AvatarScaleManager.cs (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/Components/BaseScaler.cs (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/Components/LocalScaler.cs (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/Components/NetworkScaler.cs (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/Events/AvatarScaleEvents.cs (100%) rename {AvatarScale => AvatarScaleMod}/AvatarScaling/ScaledComponents.cs (100%) rename {AvatarScale => AvatarScaleMod}/HarmonyPatches.cs (100%) rename {AvatarScale => AvatarScaleMod}/Input/DebugKeybinds.cs (100%) rename {AvatarScale => AvatarScaleMod}/Input/ScaleReconizer.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs (100%) rename {AvatarScale => AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_Utils.cs (100%) rename {AvatarScale => AvatarScaleMod}/Main.cs (100%) rename {AvatarScale => AvatarScaleMod}/ModSettings.cs (100%) rename {AvatarScale => AvatarScaleMod}/Networking/ModNetwork.cs (100%) rename {AvatarScale => AvatarScaleMod}/Properties/AssemblyInfo.cs (100%) rename {AvatarScale => AvatarScaleMod}/README.md (100%) rename {AvatarScale => AvatarScaleMod}/Scripts.cs (100%) rename {AvatarScale => AvatarScaleMod}/format.json (100%) rename {AvatarScale => AvatarScaleMod}/resources/ASM_Icon_AvatarHeightConfig.png (100%) rename {AvatarScale => AvatarScaleMod}/resources/ASM_Icon_AvatarHeightCopy.png (100%) rename {AvatarScale => AvatarScaleMod}/resources/menu.js (100%) diff --git a/AvatarScale/AvatarScaleMod.csproj b/AvatarScaleMod/AvatarScaleMod.csproj similarity index 100% rename from AvatarScale/AvatarScaleMod.csproj rename to AvatarScaleMod/AvatarScaleMod.csproj diff --git a/AvatarScale/AvatarScaling/AvatarScaleManager.cs b/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs similarity index 100% rename from AvatarScale/AvatarScaling/AvatarScaleManager.cs rename to AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs diff --git a/AvatarScale/AvatarScaling/Components/BaseScaler.cs b/AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs similarity index 100% rename from AvatarScale/AvatarScaling/Components/BaseScaler.cs rename to AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs diff --git a/AvatarScale/AvatarScaling/Components/LocalScaler.cs b/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs similarity index 100% rename from AvatarScale/AvatarScaling/Components/LocalScaler.cs rename to AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs diff --git a/AvatarScale/AvatarScaling/Components/NetworkScaler.cs b/AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs similarity index 100% rename from AvatarScale/AvatarScaling/Components/NetworkScaler.cs rename to AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs diff --git a/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs b/AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs similarity index 100% rename from AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs rename to AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs diff --git a/AvatarScale/AvatarScaling/ScaledComponents.cs b/AvatarScaleMod/AvatarScaling/ScaledComponents.cs similarity index 100% rename from AvatarScale/AvatarScaling/ScaledComponents.cs rename to AvatarScaleMod/AvatarScaling/ScaledComponents.cs diff --git a/AvatarScale/HarmonyPatches.cs b/AvatarScaleMod/HarmonyPatches.cs similarity index 100% rename from AvatarScale/HarmonyPatches.cs rename to AvatarScaleMod/HarmonyPatches.cs diff --git a/AvatarScale/Input/DebugKeybinds.cs b/AvatarScaleMod/Input/DebugKeybinds.cs similarity index 100% rename from AvatarScale/Input/DebugKeybinds.cs rename to AvatarScaleMod/Input/DebugKeybinds.cs diff --git a/AvatarScale/Input/ScaleReconizer.cs b/AvatarScaleMod/Input/ScaleReconizer.cs similarity index 100% rename from AvatarScale/Input/ScaleReconizer.cs rename to AvatarScaleMod/Input/ScaleReconizer.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs similarity index 100% rename from AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs rename to AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs diff --git a/AvatarScale/Main.cs b/AvatarScaleMod/Main.cs similarity index 100% rename from AvatarScale/Main.cs rename to AvatarScaleMod/Main.cs diff --git a/AvatarScale/ModSettings.cs b/AvatarScaleMod/ModSettings.cs similarity index 100% rename from AvatarScale/ModSettings.cs rename to AvatarScaleMod/ModSettings.cs diff --git a/AvatarScale/Networking/ModNetwork.cs b/AvatarScaleMod/Networking/ModNetwork.cs similarity index 100% rename from AvatarScale/Networking/ModNetwork.cs rename to AvatarScaleMod/Networking/ModNetwork.cs diff --git a/AvatarScale/Properties/AssemblyInfo.cs b/AvatarScaleMod/Properties/AssemblyInfo.cs similarity index 100% rename from AvatarScale/Properties/AssemblyInfo.cs rename to AvatarScaleMod/Properties/AssemblyInfo.cs diff --git a/AvatarScale/README.md b/AvatarScaleMod/README.md similarity index 100% rename from AvatarScale/README.md rename to AvatarScaleMod/README.md diff --git a/AvatarScale/Scripts.cs b/AvatarScaleMod/Scripts.cs similarity index 100% rename from AvatarScale/Scripts.cs rename to AvatarScaleMod/Scripts.cs diff --git a/AvatarScale/format.json b/AvatarScaleMod/format.json similarity index 100% rename from AvatarScale/format.json rename to AvatarScaleMod/format.json diff --git a/AvatarScale/resources/ASM_Icon_AvatarHeightConfig.png b/AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png similarity index 100% rename from AvatarScale/resources/ASM_Icon_AvatarHeightConfig.png rename to AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png diff --git a/AvatarScale/resources/ASM_Icon_AvatarHeightCopy.png b/AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png similarity index 100% rename from AvatarScale/resources/ASM_Icon_AvatarHeightCopy.png rename to AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png diff --git a/AvatarScale/resources/menu.js b/AvatarScaleMod/resources/menu.js similarity index 100% rename from AvatarScale/resources/menu.js rename to AvatarScaleMod/resources/menu.js diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index c602176..471c504 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -17,8 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarScaleMod", "AvatarScale\AvatarScaleMod.csproj", "{A6DF0D98-428C-4FE2-BA7F-756312122B1E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKAdjustments", "IKAdjustments\IKAdjustments.csproj", "{CCD510BF-1A32-441F-B52B-8A937BF75CE3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustment\FOVAdjustment.csproj", "{EE552804-30B1-49CF-BBDE-3B312895AFF7}" @@ -87,6 +85,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartReticle", "SmartReticl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAmIPointing\WhereAmIPointing.csproj", "{E285BCC9-D953-4066-8FA2-97EA28EB348E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -121,10 +121,6 @@ Global {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU - {A6DF0D98-428C-4FE2-BA7F-756312122B1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6DF0D98-428C-4FE2-BA7F-756312122B1E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6DF0D98-428C-4FE2-BA7F-756312122B1E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6DF0D98-428C-4FE2-BA7F-756312122B1E}.Release|Any CPU.Build.0 = Release|Any CPU {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -261,6 +257,10 @@ Global {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 1ed32799a84a176dc8f02084591f64a0687be30a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:19:35 -0500 Subject: [PATCH 025/188] InteractionTest: idk never committed this --- .../Components/InteractionTracker.cs | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/InteractionTest/Components/InteractionTracker.cs b/InteractionTest/Components/InteractionTracker.cs index f543aa6..b5ea1b1 100644 --- a/InteractionTest/Components/InteractionTracker.cs +++ b/InteractionTest/Components/InteractionTracker.cs @@ -30,7 +30,10 @@ public class InteractionTracker : MonoBehaviour sphereCol.isTrigger = true; BetterBetterCharacterController.QueueRemovePlayerCollision(sphereCol); - trackerObject.AddComponent().isLeft = isLeft; + + InteractionTracker tracker = trackerObject.AddComponent(); + tracker.isLeft = isLeft; + tracker.Initialize(); } #endregion Setup @@ -57,7 +60,7 @@ public class InteractionTracker : MonoBehaviour #region Unity Events - private void Awake() + private void Initialize() { _selfCollider = GetComponent(); CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoaded); @@ -76,6 +79,8 @@ public class InteractionTracker : MonoBehaviour private IEnumerator FrameLateInit() { yield return null; + yield return null; + OnInitSolver(); IKSystem.vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdate); IKSystem.vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdate); } @@ -154,34 +159,55 @@ public class InteractionTracker : MonoBehaviour private Transform _oldTarget; + private Vector3 _initialPosOffset; + private Quaternion _initialRotOffset; + + private IKSolverVR.Arm _armSolver; + + private void OnInitSolver() + { + _armSolver = isLeft ? IKSystem.vrik.solver.arms[0] : IKSystem.vrik.solver.arms[1]; + + Transform target = _armSolver.target; + if (target == null) + target = transform.parent.Find("RotationTarget"); // LeapMotion: RotationTarget + + if (target == null) return; + + _initialPosOffset = target.localPosition; + _initialRotOffset = target.localRotation; + } + private void OnPreSolverUpdate() { - if (!IsColliding) return; + if (!IsColliding) + return; - var solverArms = IKSystem.vrik.solver.arms; - IKSolverVR.Arm arm = isLeft ? solverArms[0] : solverArms[1]; + Transform selfTransform = transform; + + float dot = Vector3.Dot(_lastPenetrationNormal, selfTransform.forward); + if (dot > -0.45f) + return; - _oldTarget = arm.target; - arm.target = transform.GetChild(0); + _oldTarget = _armSolver.target; + _armSolver.target = selfTransform.GetChild(0); + + _armSolver.target.position = ClosestPoint + selfTransform.rotation * _initialPosOffset; + _armSolver.target.rotation = _initialRotOffset * Quaternion.LookRotation(-_lastPenetrationNormal, selfTransform.up); - arm.target.position = ClosestPoint; - arm.target.rotation = Quaternion.LookRotation(_lastPenetrationNormal, _oldTarget.rotation * Vector3.up); - - arm.positionWeight = 1f; - arm.rotationWeight = 1f; + _armSolver.positionWeight = 1f; + _armSolver.rotationWeight = 1f; } private void OnPostSolverUpdate() { if (!_oldTarget) return; - - var solverArms = IKSystem.vrik.solver.arms; - IKSolverVR.Arm arm = isLeft ? solverArms[0] : solverArms[1]; - arm.target = _oldTarget; + + _armSolver.target = _oldTarget; _oldTarget = null; - arm.positionWeight = 0f; - arm.rotationWeight = 0f; + _armSolver.positionWeight = 0f; + _armSolver.rotationWeight = 0f; } } \ No newline at end of file From 7f4237bf9507d257771b04e437ced7c059f20aff Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:22:37 -0500 Subject: [PATCH 026/188] depricated bunch of mods i will never touch or are native now --- {EzCurls => .DepricatedMods/EzCurls}/EzCurls.csproj | 0 .../EzCurls}/InputModules/InputModuleCurlAdjuster.cs | 0 {EzCurls => .DepricatedMods/EzCurls}/Main.cs | 0 {EzCurls => .DepricatedMods/EzCurls}/ModSettings.cs | 0 {EzCurls => .DepricatedMods/EzCurls}/Properties/AssemblyInfo.cs | 0 {EzCurls => .DepricatedMods/EzCurls}/README.md | 0 {EzCurls => .DepricatedMods/EzCurls}/format.json | 0 .../HeadLookLockingInputFix}/HeadLookLockingInputFix.csproj | 0 .../HeadLookLockingInputFix}/Main.cs | 0 .../HeadLookLockingInputFix}/Properties/AssemblyInfo.cs | 0 .../HeadLookLockingInputFix}/README.md | 0 .../HeadLookLockingInputFix}/format.json | 0 .../IKAdjustments}/HarmonyPatches.cs | 0 {IKAdjustments => .DepricatedMods/IKAdjustments}/IKAdjuster.cs | 0 .../IKAdjustments}/IKAdjustments.csproj | 0 .../IKAdjustments}/Integrations/BTKUIAddon.cs | 0 {IKAdjustments => .DepricatedMods/IKAdjustments}/Main.cs | 0 .../IKAdjustments}/Properties/AssemblyInfo.cs | 0 {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/Main.cs | 0 .../MoreMenuOptions}/ModSettings.cs | 0 .../MoreMenuOptions}/MoreMenuOptions.csproj | 0 .../MoreMenuOptions}/Properties/AssemblyInfo.cs | 0 {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/README.md | 0 {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/format.json | 0 .../SwitchToDesktopOnSteamVRExit}/Main.cs | 0 .../SwitchToDesktopOnSteamVRExit}/Properties/AssemblyInfo.cs | 0 .../SwitchToDesktopOnSteamVRExit}/README.md | 0 .../SwitchToDesktopOnSteamVRExit.csproj | 0 .../SwitchToDesktopOnSteamVRExit}/format.json | 0 29 files changed, 0 insertions(+), 0 deletions(-) rename {EzCurls => .DepricatedMods/EzCurls}/EzCurls.csproj (100%) rename {EzCurls => .DepricatedMods/EzCurls}/InputModules/InputModuleCurlAdjuster.cs (100%) rename {EzCurls => .DepricatedMods/EzCurls}/Main.cs (100%) rename {EzCurls => .DepricatedMods/EzCurls}/ModSettings.cs (100%) rename {EzCurls => .DepricatedMods/EzCurls}/Properties/AssemblyInfo.cs (100%) rename {EzCurls => .DepricatedMods/EzCurls}/README.md (100%) rename {EzCurls => .DepricatedMods/EzCurls}/format.json (100%) rename {HeadLookLockingInputFix => .DepricatedMods/HeadLookLockingInputFix}/HeadLookLockingInputFix.csproj (100%) rename {HeadLookLockingInputFix => .DepricatedMods/HeadLookLockingInputFix}/Main.cs (100%) rename {HeadLookLockingInputFix => .DepricatedMods/HeadLookLockingInputFix}/Properties/AssemblyInfo.cs (100%) rename {HeadLookLockingInputFix => .DepricatedMods/HeadLookLockingInputFix}/README.md (100%) rename {HeadLookLockingInputFix => .DepricatedMods/HeadLookLockingInputFix}/format.json (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/HarmonyPatches.cs (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/IKAdjuster.cs (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/IKAdjustments.csproj (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/Integrations/BTKUIAddon.cs (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/Main.cs (100%) rename {IKAdjustments => .DepricatedMods/IKAdjustments}/Properties/AssemblyInfo.cs (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/Main.cs (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/ModSettings.cs (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/MoreMenuOptions.csproj (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/Properties/AssemblyInfo.cs (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/README.md (100%) rename {MoreMenuOptions => .DepricatedMods/MoreMenuOptions}/format.json (100%) rename {SwitchToDesktopOnSteamVRExit => .DepricatedMods/SwitchToDesktopOnSteamVRExit}/Main.cs (100%) rename {SwitchToDesktopOnSteamVRExit => .DepricatedMods/SwitchToDesktopOnSteamVRExit}/Properties/AssemblyInfo.cs (100%) rename {SwitchToDesktopOnSteamVRExit => .DepricatedMods/SwitchToDesktopOnSteamVRExit}/README.md (100%) rename {SwitchToDesktopOnSteamVRExit => .DepricatedMods/SwitchToDesktopOnSteamVRExit}/SwitchToDesktopOnSteamVRExit.csproj (100%) rename {SwitchToDesktopOnSteamVRExit => .DepricatedMods/SwitchToDesktopOnSteamVRExit}/format.json (100%) diff --git a/EzCurls/EzCurls.csproj b/.DepricatedMods/EzCurls/EzCurls.csproj similarity index 100% rename from EzCurls/EzCurls.csproj rename to .DepricatedMods/EzCurls/EzCurls.csproj diff --git a/EzCurls/InputModules/InputModuleCurlAdjuster.cs b/.DepricatedMods/EzCurls/InputModules/InputModuleCurlAdjuster.cs similarity index 100% rename from EzCurls/InputModules/InputModuleCurlAdjuster.cs rename to .DepricatedMods/EzCurls/InputModules/InputModuleCurlAdjuster.cs diff --git a/EzCurls/Main.cs b/.DepricatedMods/EzCurls/Main.cs similarity index 100% rename from EzCurls/Main.cs rename to .DepricatedMods/EzCurls/Main.cs diff --git a/EzCurls/ModSettings.cs b/.DepricatedMods/EzCurls/ModSettings.cs similarity index 100% rename from EzCurls/ModSettings.cs rename to .DepricatedMods/EzCurls/ModSettings.cs diff --git a/EzCurls/Properties/AssemblyInfo.cs b/.DepricatedMods/EzCurls/Properties/AssemblyInfo.cs similarity index 100% rename from EzCurls/Properties/AssemblyInfo.cs rename to .DepricatedMods/EzCurls/Properties/AssemblyInfo.cs diff --git a/EzCurls/README.md b/.DepricatedMods/EzCurls/README.md similarity index 100% rename from EzCurls/README.md rename to .DepricatedMods/EzCurls/README.md diff --git a/EzCurls/format.json b/.DepricatedMods/EzCurls/format.json similarity index 100% rename from EzCurls/format.json rename to .DepricatedMods/EzCurls/format.json diff --git a/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj b/.DepricatedMods/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj similarity index 100% rename from HeadLookLockingInputFix/HeadLookLockingInputFix.csproj rename to .DepricatedMods/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj diff --git a/HeadLookLockingInputFix/Main.cs b/.DepricatedMods/HeadLookLockingInputFix/Main.cs similarity index 100% rename from HeadLookLockingInputFix/Main.cs rename to .DepricatedMods/HeadLookLockingInputFix/Main.cs diff --git a/HeadLookLockingInputFix/Properties/AssemblyInfo.cs b/.DepricatedMods/HeadLookLockingInputFix/Properties/AssemblyInfo.cs similarity index 100% rename from HeadLookLockingInputFix/Properties/AssemblyInfo.cs rename to .DepricatedMods/HeadLookLockingInputFix/Properties/AssemblyInfo.cs diff --git a/HeadLookLockingInputFix/README.md b/.DepricatedMods/HeadLookLockingInputFix/README.md similarity index 100% rename from HeadLookLockingInputFix/README.md rename to .DepricatedMods/HeadLookLockingInputFix/README.md diff --git a/HeadLookLockingInputFix/format.json b/.DepricatedMods/HeadLookLockingInputFix/format.json similarity index 100% rename from HeadLookLockingInputFix/format.json rename to .DepricatedMods/HeadLookLockingInputFix/format.json diff --git a/IKAdjustments/HarmonyPatches.cs b/.DepricatedMods/IKAdjustments/HarmonyPatches.cs similarity index 100% rename from IKAdjustments/HarmonyPatches.cs rename to .DepricatedMods/IKAdjustments/HarmonyPatches.cs diff --git a/IKAdjustments/IKAdjuster.cs b/.DepricatedMods/IKAdjustments/IKAdjuster.cs similarity index 100% rename from IKAdjustments/IKAdjuster.cs rename to .DepricatedMods/IKAdjustments/IKAdjuster.cs diff --git a/IKAdjustments/IKAdjustments.csproj b/.DepricatedMods/IKAdjustments/IKAdjustments.csproj similarity index 100% rename from IKAdjustments/IKAdjustments.csproj rename to .DepricatedMods/IKAdjustments/IKAdjustments.csproj diff --git a/IKAdjustments/Integrations/BTKUIAddon.cs b/.DepricatedMods/IKAdjustments/Integrations/BTKUIAddon.cs similarity index 100% rename from IKAdjustments/Integrations/BTKUIAddon.cs rename to .DepricatedMods/IKAdjustments/Integrations/BTKUIAddon.cs diff --git a/IKAdjustments/Main.cs b/.DepricatedMods/IKAdjustments/Main.cs similarity index 100% rename from IKAdjustments/Main.cs rename to .DepricatedMods/IKAdjustments/Main.cs diff --git a/IKAdjustments/Properties/AssemblyInfo.cs b/.DepricatedMods/IKAdjustments/Properties/AssemblyInfo.cs similarity index 100% rename from IKAdjustments/Properties/AssemblyInfo.cs rename to .DepricatedMods/IKAdjustments/Properties/AssemblyInfo.cs diff --git a/MoreMenuOptions/Main.cs b/.DepricatedMods/MoreMenuOptions/Main.cs similarity index 100% rename from MoreMenuOptions/Main.cs rename to .DepricatedMods/MoreMenuOptions/Main.cs diff --git a/MoreMenuOptions/ModSettings.cs b/.DepricatedMods/MoreMenuOptions/ModSettings.cs similarity index 100% rename from MoreMenuOptions/ModSettings.cs rename to .DepricatedMods/MoreMenuOptions/ModSettings.cs diff --git a/MoreMenuOptions/MoreMenuOptions.csproj b/.DepricatedMods/MoreMenuOptions/MoreMenuOptions.csproj similarity index 100% rename from MoreMenuOptions/MoreMenuOptions.csproj rename to .DepricatedMods/MoreMenuOptions/MoreMenuOptions.csproj diff --git a/MoreMenuOptions/Properties/AssemblyInfo.cs b/.DepricatedMods/MoreMenuOptions/Properties/AssemblyInfo.cs similarity index 100% rename from MoreMenuOptions/Properties/AssemblyInfo.cs rename to .DepricatedMods/MoreMenuOptions/Properties/AssemblyInfo.cs diff --git a/MoreMenuOptions/README.md b/.DepricatedMods/MoreMenuOptions/README.md similarity index 100% rename from MoreMenuOptions/README.md rename to .DepricatedMods/MoreMenuOptions/README.md diff --git a/MoreMenuOptions/format.json b/.DepricatedMods/MoreMenuOptions/format.json similarity index 100% rename from MoreMenuOptions/format.json rename to .DepricatedMods/MoreMenuOptions/format.json diff --git a/SwitchToDesktopOnSteamVRExit/Main.cs b/.DepricatedMods/SwitchToDesktopOnSteamVRExit/Main.cs similarity index 100% rename from SwitchToDesktopOnSteamVRExit/Main.cs rename to .DepricatedMods/SwitchToDesktopOnSteamVRExit/Main.cs diff --git a/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs b/.DepricatedMods/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs similarity index 100% rename from SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs rename to .DepricatedMods/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs diff --git a/SwitchToDesktopOnSteamVRExit/README.md b/.DepricatedMods/SwitchToDesktopOnSteamVRExit/README.md similarity index 100% rename from SwitchToDesktopOnSteamVRExit/README.md rename to .DepricatedMods/SwitchToDesktopOnSteamVRExit/README.md diff --git a/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj b/.DepricatedMods/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj similarity index 100% rename from SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj rename to .DepricatedMods/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj diff --git a/SwitchToDesktopOnSteamVRExit/format.json b/.DepricatedMods/SwitchToDesktopOnSteamVRExit/format.json similarity index 100% rename from SwitchToDesktopOnSteamVRExit/format.json rename to .DepricatedMods/SwitchToDesktopOnSteamVRExit/format.json From 437e4c5ea20e3983a01e6aa22d9d0a862f475016 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:23:05 -0500 Subject: [PATCH 027/188] removed project references --- NAK_CVR_Mods.sln | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 471c504..7c513e3 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -17,16 +17,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKAdjustments", "IKAdjustments\IKAdjustments.csproj", "{CCD510BF-1A32-441F-B52B-8A937BF75CE3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustment\FOVAdjustment.csproj", "{EE552804-30B1-49CF-BBDE-3B312895AFF7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatBoxExtensions", "ChatBoxExtensions\ChatBoxExtensions.csproj", "{0E1DD746-33A1-4179-AE70-8FB83AC40ABC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", "EzCurls\EzCurls.csproj", "{9D39E900-8F38-485B-8B67-9F75D8FF2C5E}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysicsGunMod", "PhysicsGunMod\PhysicsGunMod.csproj", "{F94DDB73-9041-4F5C-AD43-6960701E8417}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" @@ -35,10 +31,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadowCloneFallback", "Shad EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StopClosingMyMenuOnWorldLoad", "StopClosingMyMenuOnWorldLoad\StopClosingMyMenuOnWorldLoad.csproj", "{9FA83514-13F8-412C-9790-C2B750E0E7E7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwitchToDesktopOnSteamVRExit", "SwitchToDesktopOnSteamVRExit\SwitchToDesktopOnSteamVRExit.csproj", "{F7F4B840-6FF5-46C4-AAFD-95362D1D666C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoreMenuOptions", "MoreMenuOptions\MoreMenuOptions.csproj", "{AB07B31E-A930-4CCC-8E02-F2A4D6C52C8F}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelativeSync", "RelativeSync\RelativeSync.csproj", "{B48C8F19-9451-4EE2-999F-82C0033CDE2C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptingSpoofer", "ScriptingSpoofer\ScriptingSpoofer.csproj", "{6B4396C7-B451-4FFD-87B6-3ED8377AC308}" @@ -71,8 +63,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeadLookLockingInputFix", "HeadLookLockingInputFix\HeadLookLockingInputFix.csproj", "{AAE8A952-2764-43CF-A5D7-515924CA2D9F}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" @@ -121,10 +111,6 @@ Global {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU - {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCD510BF-1A32-441F-B52B-8A937BF75CE3}.Release|Any CPU.Build.0 = Release|Any CPU {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -137,10 +123,6 @@ Global {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.Build.0 = Release|Any CPU - {9D39E900-8F38-485B-8B67-9F75D8FF2C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D39E900-8F38-485B-8B67-9F75D8FF2C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D39E900-8F38-485B-8B67-9F75D8FF2C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D39E900-8F38-485B-8B67-9F75D8FF2C5E}.Release|Any CPU.Build.0 = Release|Any CPU {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -157,14 +139,6 @@ Global {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.Build.0 = Release|Any CPU - {F7F4B840-6FF5-46C4-AAFD-95362D1D666C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7F4B840-6FF5-46C4-AAFD-95362D1D666C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7F4B840-6FF5-46C4-AAFD-95362D1D666C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7F4B840-6FF5-46C4-AAFD-95362D1D666C}.Release|Any CPU.Build.0 = Release|Any CPU - {AB07B31E-A930-4CCC-8E02-F2A4D6C52C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB07B31E-A930-4CCC-8E02-F2A4D6C52C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB07B31E-A930-4CCC-8E02-F2A4D6C52C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB07B31E-A930-4CCC-8E02-F2A4D6C52C8F}.Release|Any CPU.Build.0 = Release|Any CPU {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -229,10 +203,6 @@ Global {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.Build.0 = Debug|Any CPU {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.Build.0 = Release|Any CPU - {AAE8A952-2764-43CF-A5D7-515924CA2D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAE8A952-2764-43CF-A5D7-515924CA2D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAE8A952-2764-43CF-A5D7-515924CA2D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAE8A952-2764-43CF-A5D7-515924CA2D9F}.Release|Any CPU.Build.0 = Release|Any CPU {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU From 6c2b6d29a2ebcd59a7033bcd5bbc8a9bebe1f644 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:31:21 -0500 Subject: [PATCH 028/188] README: updated release table --- README.md | 57 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4271937..76c86ad 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,40 @@ ## Released Mods -| Mod Name | README | Download | Description | -|------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| CVRGizmos | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CVRGizmos) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CVRGizmos.dll) | Adds runtime gizmos to common CCK components. | -| PathCamDisabler | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PathCamDisabler) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PathCamDisabler.dll) | Adds option to disable the Path Camera Controller keybinds. | -| PortableCameraAdditions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PortableCameraAdditions.dll) | Adds a few basic settings to the Portable Camera. | -| PropUndoButton | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropUndoButton) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropUndoButton.dll) | CTRL+Z to undo latest spawned prop. | -| ThirdPerson | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ThirdPerson) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ThirdPerson.dll) | Allows you to go into third person view. | -| FOVAdjustment | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FOVAdjustment) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/FOVAdjustment.dll) | Makes CVR_DesktopCameraController default FOV configurable. | -| MuteSFX | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MuteSFX) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MuteSFX.dll) | Adds an audio cue for muting and unmuting. | -| ShadowCloneFallback | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ShadowCloneFallback) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ShadowCloneFallback.dll) | Exposes a toggle for the Fallback Shadow Clone. | -| StopClosingMyMenuOnWorldLoad | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/StopClosingMyMenuOnWorldLoad)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/StopClosingMyMenuOnWorldLoad.dll) | Prevents your menu from being closed when a world is loaded. | -| SwitchToDesktopOnSteamVRExit | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SwitchToDesktopOnSteamVRExit)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SwitchToDesktopOnSteamVRExit.dll) | Initiates a VR Switch to Desktop when SteamVR is exited. | -| MoreMenuOptions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MoreMenuOptions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MoreMenuOptions.dll) | Exposes some menu placement configuration options. | -| RelativeSync | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/RelativeSync.dll) | Relative sync for Movement Parent & Chairs. | -| LazyPrune | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LazyPrune.dll) | Prevents loaded objects from immediately unloading. | -| ReconnectionSystemFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ReconnectionSystemFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ReconnectionSystemFix.dll) | Prevents recreating and reloading all remote players. | -| AASDefaultProfileFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AASDefaultProfileFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AASDefaultProfileFix.dll) | Fixes the Default AAS profile not being applied. | -| ScrollFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ScrollFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ScrollFlight.dll) | Scroll-wheel to adjust flight speed in Desktop. | -| PropLoadingHexagon | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropLoadingHexagon.dll) | Adds a hexagon indicator to downloading props. | -| IKSimulatedRootAngleFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKSimulatedRootAngleFix.dll) | Fixes Desktop & HalfBody root angle issues. | -| DropPropTweak | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/DropPropTweak) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/DropPropTweak.dll) | Allows you to drop props in the air. | -| VisualCloneFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/VisualCloneFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/VisualCloneFix.dll) | Fixes the Visual Clone system. | -| KeepVelocityOnExitFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/KeepVelocityOnExitFlight.dll) | Keeps the player's velocity when exiting flight mode. | +| Mod Name | README | Download | Description | +|------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| AASDefaultProfileFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AASDefaultProfileFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AASDefaultProfileFix.dll) | Fixes the Default AAS profile not being applied. | +| ASTExtension | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ASTExtension) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ASTExtension.dll) | Avatar scaling gesture & persistance on existing avatars. | +| AvatarQueueSystemTweaks | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AvatarQueueSystemTweaks.dll) | Tweaks to improve the avatar queuing system. | +| ChatBoxExtensions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ChatBoxExtensions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ChatBoxExtensions.dll) | Adds some chat commands to the ChatBox mod. | +| CustomSpawnPoint | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CustomSpawnPoint) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CustomSpawnPoint.dll) | Allows setting custom spawn points in worlds. | +| CVRGizmos | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CVRGizmos) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CVRGizmos.dll) | Adds runtime gizmos to common CCK components. | +| DropPropTweak | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/DropPropTweak) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/DropPropTweak.dll) | Allows you to drop props in the air. | +| FOVAdjustment | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FOVAdjustment) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/FOVAdjustment.dll) | Makes CVR_DesktopCameraController default FOV configurable. | +| HeadLookLockingInputFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/HeadLookLockingInputFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/HeadLookLockingInputFix.dll) | Fixes head look locking input issues. | +| IKAdjustments | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKAdjustments) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKAdjustments.dll) | Allows grabbing IK points for manual adjustment. | +| IKSimulatedRootAngleFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKSimulatedRootAngleFix.dll) | Fixes Desktop & HalfBody root angle issues. | +| KeepVelocityOnExitFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/KeepVelocityOnExitFlight.dll) | Keeps the player's velocity when exiting flight mode. | +| LazyPrune | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LazyPrune.dll) | Prevents loaded objects from immediately unloading. | +| LuaTTS | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LuaTTS) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LuaTTS.dll) | Adds Text-to-Speech (TTS) functionality through Lua. | +| MoreMenuOptions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MoreMenuOptions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MoreMenuOptions.dll) | Exposes some menu placement configuration options. | +| MuteSFX | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MuteSFX) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MuteSFX.dll) | Adds an audio cue for muting and unmuting. | +| OriginShift | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/OriginShift) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/OriginShift.dll) | Shifts the world origin to avoid precision issues. | +| PathCamDisabler | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PathCamDisabler) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PathCamDisabler.dll) | Adds option to disable the Path Camera Controller keybinds. | +| PortableCameraAdditions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PortableCameraAdditions.dll) | Adds a few basic settings to the Portable Camera. | +| PropLoadingHexagon | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropLoadingHexagon.dll) | Adds a hexagon indicator to downloading props. | +| PropUndoButton | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropUndoButton) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropUndoButton.dll) | CTRL+Z to undo latest spawned prop. | +| ReconnectionSystemFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ReconnectionSystemFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ReconnectionSystemFix.dll) | Prevents recreating and reloading all remote players. | +| RelativeSync | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/RelativeSync.dll) | Relative sync for Movement Parent & Chairs. | +| ScrollFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ScrollFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ScrollFlight.dll) | Scroll-wheel to adjust flight speed in Desktop. | +| ShadowCloneFallback | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ShadowCloneFallback) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ShadowCloneFallback.dll) | Exposes a toggle for the Fallback Shadow Clone. | +| SmartReticle | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SmartReticle) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SmartReticle.dll) | Makes the reticle only appear when hovering interactables. | +| Stickers | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/Stickers) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/Stickers.dll) | Allows you to place small images on any surface. | +| StopClosingMyMenuOnWorldLoad | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/StopClosingMyMenuOnWorldLoad)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/StopClosingMyMenuOnWorldLoad.dll) | Prevents your menu from being closed when a world is loaded. | +| SwitchToDesktopOnSteamVRExit | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SwitchToDesktopOnSteamVRExit)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SwitchToDesktopOnSteamVRExit.dll) | Initiates a VR Switch to Desktop when SteamVR is exited. | +| ThirdPerson | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ThirdPerson) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ThirdPerson.dll) | Allows you to go into third person view. | +| VisualCloneFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/VisualCloneFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/VisualCloneFix.dll) | Fixes the Visual Clone system. | +| WhereAmIPointing | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/WhereAmIPointing) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/WhereAmIPointing.dll) | Makes your controller rays always visible when the menus are open. | # How To Install From a51d5812e74198b14b79e2b7cfe273fdce6d6ada Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:41:20 -0500 Subject: [PATCH 029/188] SmartReticle: updated readme --- SmartReticle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SmartReticle/README.md b/SmartReticle/README.md index b32d3f3..98a3060 100644 --- a/SmartReticle/README.md +++ b/SmartReticle/README.md @@ -1,6 +1,6 @@ # SmartReticle -Simple mod that makes the Desktop reticle only appear when needed. +Simple mod that makes the Desktop/VR Head reticle only appear when hovering over an interactable, holding an interaction button, or when using a tool. --- From a168619d19781f7f3577413467830efa7d5ce424 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:38:06 -0500 Subject: [PATCH 030/188] SmoothRay: brought back from the dead --- .DepricatedMods/SmoothRay/HarmonyPatches.cs | 14 ---- .DepricatedMods/SmoothRay/Main.cs | 28 ------- NAK_CVR_Mods.sln | 6 ++ SmoothRay/Main.cs | 76 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 6 +- .../SmoothRay => SmoothRay}/README.md | 3 + .../SmoothRay => SmoothRay}/SmoothRay.csproj | 0 .../SmoothRay.cs => SmoothRay/SmoothRayer.cs | 4 +- .../SmoothRay => SmoothRay}/format.json | 10 +-- 9 files changed, 95 insertions(+), 52 deletions(-) delete mode 100644 .DepricatedMods/SmoothRay/HarmonyPatches.cs delete mode 100644 .DepricatedMods/SmoothRay/Main.cs create mode 100644 SmoothRay/Main.cs rename {.DepricatedMods/SmoothRay => SmoothRay}/Properties/AssemblyInfo.cs (87%) rename {.DepricatedMods/SmoothRay => SmoothRay}/README.md (78%) rename {.DepricatedMods/SmoothRay => SmoothRay}/SmoothRay.csproj (100%) rename .DepricatedMods/SmoothRay/SmoothRay.cs => SmoothRay/SmoothRayer.cs (96%) rename {.DepricatedMods/SmoothRay => SmoothRay}/format.json (66%) diff --git a/.DepricatedMods/SmoothRay/HarmonyPatches.cs b/.DepricatedMods/SmoothRay/HarmonyPatches.cs deleted file mode 100644 index 590def2..0000000 --- a/.DepricatedMods/SmoothRay/HarmonyPatches.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ABI_RC.Core.Player; -using HarmonyLib; - -namespace NAK.SmoothRay.HarmonyPatches; - -internal class PlayerSetupPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] - private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) - { - - } -} \ No newline at end of file diff --git a/.DepricatedMods/SmoothRay/Main.cs b/.DepricatedMods/SmoothRay/Main.cs deleted file mode 100644 index 0a591d6..0000000 --- a/.DepricatedMods/SmoothRay/Main.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MelonLoader; - -namespace NAK.SmoothRay; - -// ChilloutVR adaptation of: -// https://github.com/kinsi55/BeatSaber_SmoothedController -// https://github.com/kinsi55/BeatSaber_SmoothedController/blob/master/LICENSE - -public class SmoothRay : MelonMod -{ - public override void OnInitializeMelon() - { - ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } -} \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 7c513e3..391758a 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAm EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmoothRay", "SmoothRay\SmoothRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -231,6 +233,10 @@ Global {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SmoothRay/Main.cs b/SmoothRay/Main.cs new file mode 100644 index 0000000..e5e2a12 --- /dev/null +++ b/SmoothRay/Main.cs @@ -0,0 +1,76 @@ +using ABI_RC.Core.Player; +using HarmonyLib; +using MelonLoader; + +namespace NAK.SmoothRay; + +// ChilloutVR adaptation of: +// https://github.com/kinsi55/BeatSaber_SmoothedController +// https://github.com/kinsi55/BeatSaber_SmoothedController/blob/master/LICENSE + +public class SmoothRay : MelonMod +{ + #region Melon Preferences + + public static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(SmoothRay)); + + public static readonly MelonPreferences_Entry EntryEnabled = + Category.CreateEntry("Enable Smoothing", true, + description: "Enable or disable smoothing."); + + public static readonly MelonPreferences_Entry EntryMenuOnly = + Category.CreateEntry("Menu Only", true, + description: "Only use smoothing on Main Menu and Quick Menu. This will be fine for most users, but it may be desired on pickups & Unity UI elements too."); + + public static readonly MelonPreferences_Entry EntryPositionSmoothing = + Category.CreateEntry("Position Smoothing", 3f, + description: "How much to smooth position changes by. Use the slider to adjust the position smoothing factor. Range: 0 to 20."); + + public static readonly MelonPreferences_Entry EntryRotationSmoothing = + Category.CreateEntry("Rotation Smoothing", 12f, + description: "How much to smooth rotation changes by. Use the slider to adjust the rotation smoothing factor. Range: 0 to 20."); + + public static readonly MelonPreferences_Entry EntrySmallMovementThresholdAngle = + Category.CreateEntry("Small Angle Threshold", 6f, + description: "Angle difference to consider a 'small' movement. The less shaky your hands are, the lower you probably want to set this. This is probably the primary value you want to tweak. Use the slider to adjust the threshold angle. Range: 4 to 15."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(PlayerSetup_Patches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Events + + #region Harmony Patches + + internal static class PlayerSetup_Patches + { + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] + private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) + { + __instance.vrLeftHandTracker.gameObject.AddComponent().ray = __instance.vrRayLeft; + __instance.vrRightHandTracker.gameObject.AddComponent().ray = __instance.vrRayRight; + } + } + + #endregion Harmony Patches +} \ No newline at end of file diff --git a/.DepricatedMods/SmoothRay/Properties/AssemblyInfo.cs b/SmoothRay/Properties/AssemblyInfo.cs similarity index 87% rename from .DepricatedMods/SmoothRay/Properties/AssemblyInfo.cs rename to SmoothRay/Properties/AssemblyInfo.cs index 2becd53..a87f25c 100644 --- a/.DepricatedMods/SmoothRay/Properties/AssemblyInfo.cs +++ b/SmoothRay/Properties/AssemblyInfo.cs @@ -20,14 +20,14 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 220, 130, 5)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.SmoothRay.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/.DepricatedMods/SmoothRay/README.md b/SmoothRay/README.md similarity index 78% rename from .DepricatedMods/SmoothRay/README.md rename to SmoothRay/README.md index 7c670d3..72fcbf0 100644 --- a/.DepricatedMods/SmoothRay/README.md +++ b/SmoothRay/README.md @@ -1,6 +1,9 @@ # SmoothRay Smoothes your controller while using the Quick and Main menus to make it easier to navigate. +This is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController) + +**Only supports OpenVR, not OpenXR.** --- diff --git a/.DepricatedMods/SmoothRay/SmoothRay.csproj b/SmoothRay/SmoothRay.csproj similarity index 100% rename from .DepricatedMods/SmoothRay/SmoothRay.csproj rename to SmoothRay/SmoothRay.csproj diff --git a/.DepricatedMods/SmoothRay/SmoothRay.cs b/SmoothRay/SmoothRayer.cs similarity index 96% rename from .DepricatedMods/SmoothRay/SmoothRay.cs rename to SmoothRay/SmoothRayer.cs index ae562af..55797e7 100644 --- a/.DepricatedMods/SmoothRay/SmoothRay.cs +++ b/SmoothRay/SmoothRayer.cs @@ -131,8 +131,8 @@ public class SmoothRayer : MonoBehaviour _menuOnly = SmoothRay.EntryMenuOnly.Value; _smallMovementThresholdAngle = SmoothRay.EntrySmallMovementThresholdAngle.Value; // dont let value hit 0, itll freeze controllers - _positionSmoothingValue = Math.Max(20f - Mathf.Clamp(SmoothRay.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); - _rotationSmoothingValue = Math.Max(20f - Mathf.Clamp(SmoothRay.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); + _positionSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRay.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); + _rotationSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRay.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); } private void OnAppliedPoses() diff --git a/.DepricatedMods/SmoothRay/format.json b/SmoothRay/format.json similarity index 66% rename from .DepricatedMods/SmoothRay/format.json rename to SmoothRay/format.json index 2c91941..4fc19ff 100644 --- a/.DepricatedMods/SmoothRay/format.json +++ b/SmoothRay/format.json @@ -1,12 +1,12 @@ { "_id": 162, "name": "SmoothRay", - "modversion": "1.0.3", - "gameversion": "2023r172", + "modversion": "1.0.4", + "gameversion": "2024r176", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Smoothes your controller while using the Quick and Main menus to make it easier to navigate.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\nSupport for TrackedControllerFix & DesktopVRSwitch.\n**Only supports OpenVR, not OpenXR.**", + "description": "Smoothes your controller while using the Quick and Main menus to make it easier to navigate.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n**Only supports OpenVR, not OpenXR.**", "searchtags": [ "vr", "ray", @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r21/SmoothRay.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmoothRay/", - "changelog": "- Fixes for 2023r172. Literally just recompiled.", - "embedcolor": "#dc8105" + "changelog": "- Fixed for 2024r176.", + "embedcolor": "#f61963" } \ No newline at end of file From 52315f5d5180c6400d5329d3b27c038d5a0d01b4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:21:41 -0500 Subject: [PATCH 031/188] Stickers: updated to BTKUIv2.3.0 --- .../Integrations/BTKUI/BTKUILibExtensions.cs | 103 ------------------ .../BTKUI/UIAddon.Category.StickersMod.cs | 6 +- Stickers/Integrations/BTKUI/UIAddon.Main.cs | 20 ++-- 3 files changed, 13 insertions(+), 116 deletions(-) delete mode 100644 Stickers/Integrations/BTKUI/BTKUILibExtensions.cs diff --git a/Stickers/Integrations/BTKUI/BTKUILibExtensions.cs b/Stickers/Integrations/BTKUI/BTKUILibExtensions.cs deleted file mode 100644 index 1e16c93..0000000 --- a/Stickers/Integrations/BTKUI/BTKUILibExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Reflection; -using BTKUILib; -using BTKUILib.UIObjects; -using BTKUILib.UIObjects.Components; -using BTKUILib.UIObjects.Objects; -using JetBrains.Annotations; -using MelonLoader; -using UnityEngine; - -namespace NAK.Stickers.Integrations; - -[PublicAPI] -public static class BTKUILibExtensions -{ - #region Icon Utils - - public static Stream GetIconStream(string iconName) - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string assemblyName = assembly.GetName().Name; - return assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}"); - } - - #endregion Icon Utils - - #region Enum Utils - - private static class EnumUtils - { - public static string[] GetPrettyEnumNames() where T : Enum - { - return Enum.GetNames(typeof(T)).Select(PrettyFormatEnumName).ToArray(); - } - - private static string PrettyFormatEnumName(string name) - { - // adds spaces before capital letters (excluding the first letter) - return System.Text.RegularExpressions.Regex.Replace(name, "(\\B[A-Z])", " $1"); - } - - public static int GetEnumIndex(T value) where T : Enum - { - return Array.IndexOf(Enum.GetValues(typeof(T)), value); - } - } - - #endregion Enum Utils - - #region Melon Preference Extensions - - public static ToggleButton AddMelonToggle(this Category category, MelonPreferences_Entry entry) - { - ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value); - toggle.OnValueUpdated += b => entry.Value = b; - return toggle; - } - - public static SliderFloat AddMelonSlider(this Category category, MelonPreferences_Entry entry, float min, - float max, int decimalPlaces = 2, bool allowReset = true) - { - SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description, - Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); - slider.OnValueUpdated += f => entry.Value = f; - return slider; - } - - public static Button AddMelonStringInput(this Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { - Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); - button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s); - return button; - } - - public static Button AddMelonNumberInput(this Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { - Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); - button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f); - return button; - } - - public static Category AddMelonCategory(this Page page, MelonPreferences_Entry entry, bool showHeader = true) - { - Category category = page.AddCategory(entry.DisplayName, showHeader, true, !entry.Value); - category.OnCollapse += b => entry.Value = !b; // more intuitive if pref value of true means category open - return category; - } - - public static MultiSelection CreateMelonMultiSelection(MelonPreferences_Entry entry) where TEnum : Enum - { - MultiSelection multiSelection = new( - entry.DisplayName, - EnumUtils.GetPrettyEnumNames(), - EnumUtils.GetEnumIndex(entry.Value) - ) - { - OnOptionUpdated = i => entry.Value = (TEnum)Enum.Parse(typeof(TEnum), Enum.GetNames(typeof(TEnum))[i]) - }; - - return multiSelection; - } - - #endregion Melon Preference Extensions -} \ No newline at end of file diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs index 65d874d..6660a52 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs @@ -10,13 +10,13 @@ public static partial class BTKUIAddon private static Category _ourCategory; private static readonly MultiSelection _sfxSelection = - BTKUILibExtensions.CreateMelonMultiSelection(ModSettings.Entry_SelectedSFX); + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_SelectedSFX); private static readonly MultiSelection _desktopKeybindSelection = - BTKUILibExtensions.CreateMelonMultiSelection(ModSettings.Entry_PlaceBinding); + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_PlaceBinding); private static readonly MultiSelection _tabDoubleClickSelection = - BTKUILibExtensions.CreateMelonMultiSelection(ModSettings.Entry_TabDoubleClick); + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_TabDoubleClick); #region Category Setup diff --git a/Stickers/Integrations/BTKUI/UIAddon.Main.cs b/Stickers/Integrations/BTKUI/UIAddon.Main.cs index a56f8f1..30827a1 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Main.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Main.cs @@ -24,16 +24,16 @@ public static partial class BTKUIAddon private static void Setup_Icons() { // All icons used - https://www.flaticon.com/authors/gohsantosadrive - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-alphabet.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-eraser.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-folder.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-headset.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-magic-wand.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-mouse.png")); - //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-puzzle.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", BTKUILibExtensions.GetIconStream("Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-alphabet.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-eraser.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-folder.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-headset.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-magic-wand.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-mouse.png")); + //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-puzzle.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); } private static void Setup_StickerModTab() From 958f07ed08c6246d9ef71761b16be6a44b697999 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:22:14 -0500 Subject: [PATCH 032/188] Stickers: added subscription check on outbound mod network calls --- .../Stickers/Networking/ModNetwork.Main.cs | 29 +++++++++++++++++ .../Networking/ModNetwork.Outbound.cs | 24 ++++++++++++++ Stickers/Stickers/StickerSystem.Main.cs | 31 ++++++++++++++----- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/Stickers/Stickers/Networking/ModNetwork.Main.cs b/Stickers/Stickers/Networking/ModNetwork.Main.cs index 64f21b9..d2cdeea 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Main.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Main.cs @@ -6,6 +6,25 @@ public static partial class ModNetwork { #region Mod Network Internals + // private static bool _isEnabled = true; + // + // public static bool IsEnabled + // { + // get => _isEnabled; + // set + // { + // if (_isEnabled == value) + // return; + // + // _isEnabled = value; + // + // if (_isEnabled) Subscribe(); + // else Unsubscribe(); + // + // Reset(); // reset buffers and metadata + // } + // } + public static bool IsSendingTexture { get; private set; } private static bool _isSubscribedToModNetwork; @@ -15,6 +34,16 @@ public static partial class ModNetwork _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); if (!_isSubscribedToModNetwork) StickerMod.Logger.Error("Failed to subscribe to Mod Network! This should not happen."); + else StickerMod.Logger.Msg("Subscribed to Mod Network."); + } + + private static void Unsubscribe() + { + ModNetworkManager.Unsubscribe(ModId); + + _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); + if (_isSubscribedToModNetwork) StickerMod.Logger.Error("Failed to unsubscribe from Mod Network! This should not happen."); + else StickerMod.Logger.Msg("Unsubscribed from Mod Network."); } #endregion Mod Network Internals diff --git a/Stickers/Stickers/Networking/ModNetwork.Outbound.cs b/Stickers/Stickers/Networking/ModNetwork.Outbound.cs index d3f20f3..241f566 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Outbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Outbound.cs @@ -38,6 +38,9 @@ public static partial class ModNetwork public static void SendPlaceSticker(int stickerSlot, Vector3 position, Vector3 forward, Vector3 up) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -55,6 +58,9 @@ public static partial class ModNetwork public static void SendClearSticker(int stickerSlot) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -68,6 +74,9 @@ public static partial class ModNetwork public static void SendClearAllStickers() { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -80,6 +89,9 @@ public static partial class ModNetwork private static void SendStartTexture(int stickerSlot, Guid textureHash, int chunkCount, int width, int height) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -97,6 +109,9 @@ public static partial class ModNetwork public static void SendTextureChunk(int chunkIdx, byte[] chunkData) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -111,6 +126,9 @@ public static partial class ModNetwork public static void SendEndTexture() { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -123,6 +141,9 @@ public static partial class ModNetwork public static void SendRequestTexture(int stickerSlot, Guid textureHash) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork()) return; @@ -137,6 +158,9 @@ public static partial class ModNetwork public static void SendTexture(int stickerSlot) { + if (!_isSubscribedToModNetwork) + return; + if (!IsConnectedToGameNetwork() || IsSendingTexture) return; diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index 8bdc480..ab95877 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -4,7 +4,6 @@ using ABI_RC.Core.UI; using ABI_RC.Systems.GameEventSystem; using NAK.Stickers.Networking; using NAK.Stickers.Utilities; -using UnityEngine; namespace NAK.Stickers; @@ -64,6 +63,29 @@ public partial class StickerSystem #endregion Game Events #region Data + + // private bool _isEnabled = true; + // + // public bool IsEnabled + // { + // get => _isEnabled; + // set + // { + // if (_isEnabled == value) + // return; + // + // _isEnabled = value; + // if (!_isEnabled) ClearAllStickers(); + // ModNetwork.IsEnabled = _isEnabled; + // } + // } + + private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; + + private const float StickerKillTime = 30f; + private const float StickerCooldown = 0.2f; + private readonly Dictionary _playerStickers = new(); + internal const string PlayerLocalId = "_PLAYERLOCAL"; private int _selectedStickerSlot; public int SelectedStickerSlot @@ -97,13 +119,6 @@ public partial class StickerSystem } } } - - private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; - - private const float StickerKillTime = 30f; - private const float StickerCooldown = 0.2f; - private readonly Dictionary _playerStickers = new(); - internal const string PlayerLocalId = "_PLAYERLOCAL"; #endregion Data } \ No newline at end of file From af00f4c6d0d6f7ff6cd6534c6cd42ce41fa74ee2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:22:26 -0500 Subject: [PATCH 033/188] Stickers: fixed shader replacement hitting stickers --- Stickers/Main.cs | 1 + Stickers/Patches.cs | 14 +++++++++++++- Stickers/Properties/AssemblyInfo.cs | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Stickers/Main.cs b/Stickers/Main.cs index 4b223f4..87e6824 100644 --- a/Stickers/Main.cs +++ b/Stickers/Main.cs @@ -26,6 +26,7 @@ public class StickerMod : MelonMod ApplyPatches(typeof(Patches.PlayerSetupPatches)); ApplyPatches(typeof(Patches.ControllerRayPatches)); ApplyPatches(typeof(Patches.ShaderFilterHelperPatches)); + ApplyPatches(typeof(Patches.CVRToolsPatches)); LoadAssetBundle(); diff --git a/Stickers/Patches.cs b/Stickers/Patches.cs index 4fc6b1b..4c9ff63 100644 --- a/Stickers/Patches.cs +++ b/Stickers/Patches.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; @@ -48,4 +49,15 @@ internal static class ShaderFilterHelperPatches StickerMod.Logger.Warning("ExperimentalShaderLimitEnabled found to be true. Disabling setting to prevent crashes when spawning stickers!"); MetaPort.Instance.settings.SetSettingsBool("ExperimentalShaderLimitEnabled", false); } +} + +internal static class CVRToolsPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(CVRTools), nameof(CVRTools.ReplaceShaders), typeof(Material), typeof(string))] + private static bool Prefix_CVRTools_ReplaceShaders(Material material, string fallbackShaderName = "") + { + if (material == null || material.shader == null) return true; + return material.shader != StickerMod.DecalSimpleShader; // prevent replacing decals with fallback shader + } } \ No newline at end of file diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index 45de81f..425649a 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.Stickers.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.6"; + public const string Version = "1.0.7"; public const string Author = "NotAKidoS"; } \ No newline at end of file From 35943bd709c350f3869d8242388aee3e21d49fe2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:25:19 -0500 Subject: [PATCH 034/188] LuaTTS: fixes for latest scripting nightly --- LuaTTS/Patches.cs | 21 ++++++++++++++------- LuaTTS/Properties/AssemblyInfo.cs | 6 +++--- LuaTTS/format.json | 24 ++++++++++++------------ NAK_CVR_Mods.sln | 6 ++++++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/LuaTTS/Patches.cs b/LuaTTS/Patches.cs index c81a0d6..07d4297 100644 --- a/LuaTTS/Patches.cs +++ b/LuaTTS/Patches.cs @@ -1,5 +1,4 @@ - -using ABI.Scripting.CVRSTL.Client; +using ABI.Scripting.CVRSTL.Client; using ABI.Scripting.CVRSTL.Common; using HarmonyLib; using MoonSharp.Interpreter; @@ -10,11 +9,19 @@ namespace NAK.LuaTTS.Patches; internal static class LuaScriptFactoryPatches { [HarmonyPostfix] - [HarmonyPatch(typeof(LuaScriptFactory.CVRRequireModule), nameof(LuaScriptFactory.CVRRequireModule.require))] - private static void Postfix_CVRRequireModule_require(string modid, - ref object __result, ref Script ___script, CVRLuaContext ___context) + [HarmonyPatch(typeof(LuaScriptFactory.CVRRequireModule), nameof(LuaScriptFactory.CVRRequireModule.Require))] + private static void Postfix_CVRRequireModule_require( + string modid, + ref LuaScriptFactory.CVRRequireModule __instance, + ref object __result, + ref Script ___script, + ref CVRLuaContext ___context) { - if (modid == "TextToSpeech") - __result = TTSLuaModule.RegisterUserData(___script, ___context); + const string TTSModuleID = "TextToSpeech"; + if (TTSModuleID != modid) + return; // not our module + + __result = TTSLuaModule.RegisterUserData(___script, ___context); + __instance.RegisteredModules[TTSModuleID] = __result; // add module to cache } } \ No newline at end of file diff --git a/LuaTTS/Properties/AssemblyInfo.cs b/LuaTTS/Properties/AssemblyInfo.cs index 435ab9a..13b932d 100644 --- a/LuaTTS/Properties/AssemblyInfo.cs +++ b/LuaTTS/Properties/AssemblyInfo.cs @@ -20,13 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 125, 126, 129)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.LuaTTS.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/LuaTTS/format.json b/LuaTTS/format.json index 9ec9f5f..75db4ec 100644 --- a/LuaTTS/format.json +++ b/LuaTTS/format.json @@ -1,23 +1,23 @@ { - "_id": 211, - "name": "RelativeSync", + "_id": -1, + "name": "LuaTTS", "modversion": "1.0.2", - "gameversion": "2024r175", + "gameversion": "2024r176", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.", + "description": "Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak.", "searchtags": [ - "relative", - "sync", - "movement", - "chair" + "lua", + "tts", + "texttospeech", + "tomato" ], "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r28/RelativeSync.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Fixed RelativeSyncMarker not generating correct path hash for local player\n - This fixes relative sync on local avatar movement parents\n- Fixed initial calculated interpolation interval when assigning a new RelativeSyncMarker for a remote user\n - Would calculate from time was last on a movement parent, which could lead to seconds long interpolation\n- Added Network Debug settings\n- Added experimental settings to fix **local** jitter on movement parents\n - These options are disabled by default as they have not been heavily tested", - "embedcolor": "#507e64" + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r28/LuaTTS.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LuaTTS/", + "changelog": "- Initial release.", + "embedcolor": "#f61963" } \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 391758a..64ab154 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -79,6 +79,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarSca EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmoothRay", "SmoothRay\SmoothRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -237,6 +239,10 @@ Global {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 59cec7e7d376e2784adaad342f4c0e9b8656ace4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:45:08 -0500 Subject: [PATCH 035/188] SmoothRay: cleanup --- SmoothRay/Main.cs | 33 ++++++++---- SmoothRay/Properties/AssemblyInfo.cs | 4 +- SmoothRay/SmoothRayer.cs | 81 ++++++++++++++++++++++------ SmoothRay/format.json | 6 +-- 4 files changed, 93 insertions(+), 31 deletions(-) diff --git a/SmoothRay/Main.cs b/SmoothRay/Main.cs index e5e2a12..14f8589 100644 --- a/SmoothRay/Main.cs +++ b/SmoothRay/Main.cs @@ -1,4 +1,6 @@ -using ABI_RC.Core.Player; +using System; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; using HarmonyLib; using MelonLoader; @@ -8,31 +10,33 @@ namespace NAK.SmoothRay; // https://github.com/kinsi55/BeatSaber_SmoothedController // https://github.com/kinsi55/BeatSaber_SmoothedController/blob/master/LICENSE -public class SmoothRay : MelonMod +public class SmoothRayMod : MelonMod { + internal static MelonLogger.Instance Logger; + #region Melon Preferences public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(SmoothRay)); + MelonPreferences.CreateCategory(nameof(SmoothRayMod)); public static readonly MelonPreferences_Entry EntryEnabled = Category.CreateEntry("Enable Smoothing", true, description: "Enable or disable smoothing."); public static readonly MelonPreferences_Entry EntryMenuOnly = - Category.CreateEntry("Menu Only", true, - description: "Only use smoothing on Main Menu and Quick Menu. This will be fine for most users, but it may be desired on pickups & Unity UI elements too."); - + Category.CreateEntry("Menu Only", false, + description: "Only use smoothing on Main Menu and Quick Menu. This will be fine for most users, but it may be desired on pickups & Unity UI elements too. When off it is best paired with WhereAmIPointing."); + public static readonly MelonPreferences_Entry EntryPositionSmoothing = - Category.CreateEntry("Position Smoothing", 3f, + Category.CreateEntry("Position Smoothing (3f)", 3f, description: "How much to smooth position changes by. Use the slider to adjust the position smoothing factor. Range: 0 to 20."); public static readonly MelonPreferences_Entry EntryRotationSmoothing = - Category.CreateEntry("Rotation Smoothing", 12f, + Category.CreateEntry("Rotation Smoothing (12f)", 12f, description: "How much to smooth rotation changes by. Use the slider to adjust the rotation smoothing factor. Range: 0 to 20."); public static readonly MelonPreferences_Entry EntrySmallMovementThresholdAngle = - Category.CreateEntry("Small Angle Threshold", 6f, + Category.CreateEntry("Small Angle Threshold (6f)", 6f, description: "Angle difference to consider a 'small' movement. The less shaky your hands are, the lower you probably want to set this. This is probably the primary value you want to tweak. Use the slider to adjust the threshold angle. Range: 4 to 15."); #endregion Melon Preferences @@ -41,7 +45,9 @@ public class SmoothRay : MelonMod public override void OnInitializeMelon() { + Logger = LoggerInstance; ApplyPatches(typeof(PlayerSetup_Patches)); + ApplyPatches(typeof(ControllerRay_Patches)); } private void ApplyPatches(Type type) @@ -71,6 +77,15 @@ public class SmoothRay : MelonMod __instance.vrRightHandTracker.gameObject.AddComponent().ray = __instance.vrRayRight; } } + + internal static class ControllerRay_Patches + { + // SmoothRay + [HarmonyPrefix] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.SmoothRay))] + private static bool Prefix_ControllerRay_SmoothRay(ref ControllerRay __instance) + => !EntryEnabled.Value; // SmoothRay method enforces identity local pos when disabled, so we skip it + } #endregion Harmony Patches } \ No newline at end of file diff --git a/SmoothRay/Properties/AssemblyInfo.cs b/SmoothRay/Properties/AssemblyInfo.cs index a87f25c..e5fddc2 100644 --- a/SmoothRay/Properties/AssemblyInfo.cs +++ b/SmoothRay/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Reflection; [assembly: AssemblyProduct(nameof(NAK.SmoothRay))] [assembly: MelonInfo( - typeof(NAK.SmoothRay.SmoothRay), + typeof(NAK.SmoothRay.SmoothRayMod), nameof(NAK.SmoothRay), AssemblyInfoParams.Version, AssemblyInfoParams.Author, @@ -28,6 +28,6 @@ namespace NAK.SmoothRay.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.5"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/SmoothRay/SmoothRayer.cs b/SmoothRay/SmoothRayer.cs index 55797e7..92d2873 100644 --- a/SmoothRay/SmoothRayer.cs +++ b/SmoothRay/SmoothRayer.cs @@ -22,8 +22,10 @@ SOFTWARE. **/ +using ABI_RC.Core; using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.Savior; +using ABI_RC.Systems.InputManagement; using MelonLoader; using UnityEngine; using Valve.VR; @@ -70,8 +72,10 @@ public class SmoothRayer : MonoBehaviour UpdatePosesAction(true); } - foreach (MelonPreferences_Entry setting in SmoothRay.Category.Entries) + foreach (MelonPreferences_Entry setting in SmoothRayMod.Category.Entries) setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings); + + MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged); OnUpdateSettings(null, null); } @@ -127,12 +131,41 @@ public class SmoothRayer : MonoBehaviour private void OnUpdateSettings(object arg1, object arg2) { - _isEnabled = SmoothRay.EntryEnabled.Value; - _menuOnly = SmoothRay.EntryMenuOnly.Value; - _smallMovementThresholdAngle = SmoothRay.EntrySmallMovementThresholdAngle.Value; + _isEnabled = SmoothRayMod.EntryEnabled.Value; + _menuOnly = SmoothRayMod.EntryMenuOnly.Value; + _smallMovementThresholdAngle = SmoothRayMod.EntrySmallMovementThresholdAngle.Value; + // dont let value hit 0, itll freeze controllers - _positionSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRay.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); - _rotationSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRay.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); + _positionSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRayMod.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); + _rotationSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRayMod.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); + + if (!_isEnabled) + return; // only care about setting being enabled + + ray._enableSmoothRay = false; // ensure built-in smoothing is disabled + + if (MetaPort.Instance.settings.GetSettingsBool("ControlSmoothRaycast")) + return; // disable saved setting once + + SmoothRayMod.Logger.Msg("Built-in SmoothRay setting found to be enabled. Disabling built-in SmoothRay implementation in favor of modded implementation."); + MetaPort.Instance.settings.SetSettingsBool("ControlSmoothRaycast", false); + ViewManager.SetGameSettingBool("ControlSmoothRaycast", false); + // ^ did you know the game doesn't even use this method native... + } + + private void OnSettingsBoolChanged(string key, bool value) + { + if (key != "ControlSmoothRaycast") + return; // only care about SmoothRaycast setting + + if (!value) + return; // only care about setting being enabled + + _isEnabled = false; // ensure modded SmoothRay is disabled + + if (!SmoothRayMod.EntryEnabled.Value) return; // disable saved setting once + SmoothRayMod.Logger.Msg("Modded SmoothRay found to be enabled. Disabling modded SmoothRay implementation in favor of built-in implementation."); + SmoothRayMod.EntryEnabled.Value = false; } private void OnAppliedPoses() @@ -148,12 +181,13 @@ public class SmoothRayer : MonoBehaviour private void SmoothTransform() { Transform controller = transform; - if (_isEnabled && ray.lineRenderer != null && ray.lineRenderer.enabled) + if (!CanSmoothRay()) + { + _smoothedPosition = controller.localPosition; + _smoothedRotation = controller.localRotation; + } + else { - if (_menuOnly && (!ray.uiActive || (ray.hitTransform != ViewManager.Instance.transform && - ray.hitTransform != CVR_MenuManager.Instance.quickMenu.transform))) - return; - var angDiff = Quaternion.Angle(_smoothedRotation, controller.localRotation); _angleVelocitySnap = Mathf.Min(_angleVelocitySnap + angDiff, 90f); @@ -176,12 +210,25 @@ public class SmoothRayer : MonoBehaviour controller.localRotation = _smoothedRotation; } } - else - { - _smoothedPosition = controller.localPosition; - _smoothedRotation = controller.localRotation; - } } - #endregion + private bool CanSmoothRay() + { + bool canSmoothRay = _isEnabled && ray.lineRenderer != null && ray.lineRenderer.enabled; + + if (_menuOnly) + { + switch (ray.hand) + { + case CVRHand.Left when !CVRInputManager.Instance.leftControllerPointingMenu: + case CVRHand.Right when !CVRInputManager.Instance.rightControllerPointingMenu: + canSmoothRay = false; + break; + } + } + + return canSmoothRay; + } + + #endregion Private Methods } \ No newline at end of file diff --git a/SmoothRay/format.json b/SmoothRay/format.json index 4fc19ff..096f71c 100644 --- a/SmoothRay/format.json +++ b/SmoothRay/format.json @@ -1,12 +1,12 @@ { "_id": 162, "name": "SmoothRay", - "modversion": "1.0.4", + "modversion": "1.0.5", "gameversion": "2024r176", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Smoothes your controller while using the Quick and Main menus to make it easier to navigate.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n**Only supports OpenVR, not OpenXR.**", + "description": "Smoothes your controller while using the Quick and Main menus to make it easier to navigate.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n**Only supports OpenVR, not OpenXR.\n\n **NOTE:** This disables the built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.", "searchtags": [ "vr", "ray", @@ -16,7 +16,7 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r21/SmoothRay.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmoothRay.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmoothRay/", "changelog": "- Fixed for 2024r176.", "embedcolor": "#f61963" From 70ade663bc347e8f50619dcf66ed7e2d53d09fd6 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:53:25 -0500 Subject: [PATCH 036/188] SmootherRay: expanded and enhanced --- NAK_CVR_Mods.sln | 2 +- SmoothRay/SmoothRay.csproj | 2 -- SmoothRay/format.json | 23 ---------------- {SmoothRay => SmootherRay}/Main.cs | 14 +++++----- .../Properties/AssemblyInfo.cs | 14 +++++----- {SmoothRay => SmootherRay}/README.md | 0 SmootherRay/SmootherRay.csproj | 6 +++++ .../SmootherRayer.cs | 26 +++++++++---------- SmootherRay/format.json | 23 ++++++++++++++++ 9 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 SmoothRay/SmoothRay.csproj delete mode 100644 SmoothRay/format.json rename {SmoothRay => SmootherRay}/Main.cs (89%) rename {SmoothRay => SmootherRay}/Properties/AssemblyInfo.cs (77%) rename {SmoothRay => SmootherRay}/README.md (100%) create mode 100644 SmootherRay/SmootherRay.csproj rename SmoothRay/SmoothRayer.cs => SmootherRay/SmootherRayer.cs (87%) create mode 100644 SmootherRay/format.json diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 64ab154..55d8b13 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -77,7 +77,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAm EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmoothRay", "SmoothRay\SmoothRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\SmootherRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" EndProject diff --git a/SmoothRay/SmoothRay.csproj b/SmoothRay/SmoothRay.csproj deleted file mode 100644 index 66a50a8..0000000 --- a/SmoothRay/SmoothRay.csproj +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/SmoothRay/format.json b/SmoothRay/format.json deleted file mode 100644 index 096f71c..0000000 --- a/SmoothRay/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 162, - "name": "SmoothRay", - "modversion": "1.0.5", - "gameversion": "2024r176", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Smoothes your controller while using the Quick and Main menus to make it easier to navigate.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n**Only supports OpenVR, not OpenXR.\n\n **NOTE:** This disables the built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.", - "searchtags": [ - "vr", - "ray", - "controller", - "smoothing" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmoothRay.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmoothRay/", - "changelog": "- Fixed for 2024r176.", - "embedcolor": "#f61963" -} \ No newline at end of file diff --git a/SmoothRay/Main.cs b/SmootherRay/Main.cs similarity index 89% rename from SmoothRay/Main.cs rename to SmootherRay/Main.cs index 14f8589..b1b1056 100644 --- a/SmoothRay/Main.cs +++ b/SmootherRay/Main.cs @@ -4,20 +4,20 @@ using ABI_RC.Core.Player; using HarmonyLib; using MelonLoader; -namespace NAK.SmoothRay; +namespace NAK.SmootherRay; // ChilloutVR adaptation of: // https://github.com/kinsi55/BeatSaber_SmoothedController // https://github.com/kinsi55/BeatSaber_SmoothedController/blob/master/LICENSE -public class SmoothRayMod : MelonMod +public class SmootherRayMod : MelonMod { internal static MelonLogger.Instance Logger; #region Melon Preferences public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(SmoothRayMod)); + MelonPreferences.CreateCategory(nameof(SmootherRayMod)); public static readonly MelonPreferences_Entry EntryEnabled = Category.CreateEntry("Enable Smoothing", true, @@ -73,18 +73,18 @@ public class SmoothRayMod : MelonMod [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) { - __instance.vrLeftHandTracker.gameObject.AddComponent().ray = __instance.vrRayLeft; - __instance.vrRightHandTracker.gameObject.AddComponent().ray = __instance.vrRayRight; + __instance.vrLeftHandTracker.gameObject.AddComponent().ray = __instance.vrRayLeft; + __instance.vrRightHandTracker.gameObject.AddComponent().ray = __instance.vrRayRight; } } internal static class ControllerRay_Patches { - // SmoothRay + // SmootherRay [HarmonyPrefix] [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.SmoothRay))] private static bool Prefix_ControllerRay_SmoothRay(ref ControllerRay __instance) - => !EntryEnabled.Value; // SmoothRay method enforces identity local pos when disabled, so we skip it + => !EntryEnabled.Value; // SmootherRay method enforces identity local pos when disabled, so we skip it } #endregion Harmony Patches diff --git a/SmoothRay/Properties/AssemblyInfo.cs b/SmootherRay/Properties/AssemblyInfo.cs similarity index 77% rename from SmoothRay/Properties/AssemblyInfo.cs rename to SmootherRay/Properties/AssemblyInfo.cs index e5fddc2..57dbe09 100644 --- a/SmoothRay/Properties/AssemblyInfo.cs +++ b/SmootherRay/Properties/AssemblyInfo.cs @@ -1,20 +1,20 @@ using MelonLoader; -using NAK.SmoothRay.Properties; +using NAK.SmootherRay.Properties; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.SmoothRay))] +[assembly: AssemblyTitle(nameof(NAK.SmootherRay))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.SmoothRay))] +[assembly: AssemblyProduct(nameof(NAK.SmootherRay))] [assembly: MelonInfo( - typeof(NAK.SmoothRay.SmoothRayMod), - nameof(NAK.SmoothRay), + typeof(NAK.SmootherRay.SmootherRayMod), + nameof(NAK.SmootherRay), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmoothRay" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmootherRay" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -24,7 +24,7 @@ using System.Reflection; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.SmoothRay.Properties; +namespace NAK.SmootherRay.Properties; internal static class AssemblyInfoParams { diff --git a/SmoothRay/README.md b/SmootherRay/README.md similarity index 100% rename from SmoothRay/README.md rename to SmootherRay/README.md diff --git a/SmootherRay/SmootherRay.csproj b/SmootherRay/SmootherRay.csproj new file mode 100644 index 0000000..d2a45fb --- /dev/null +++ b/SmootherRay/SmootherRay.csproj @@ -0,0 +1,6 @@ + + + + SmoothRay + + diff --git a/SmoothRay/SmoothRayer.cs b/SmootherRay/SmootherRayer.cs similarity index 87% rename from SmoothRay/SmoothRayer.cs rename to SmootherRay/SmootherRayer.cs index 92d2873..44ef320 100644 --- a/SmoothRay/SmoothRayer.cs +++ b/SmootherRay/SmootherRayer.cs @@ -30,9 +30,9 @@ using MelonLoader; using UnityEngine; using Valve.VR; -namespace NAK.SmoothRay; +namespace NAK.SmootherRay; -public class SmoothRayer : MonoBehaviour +public class SmootherRayer : MonoBehaviour { #region Variables @@ -72,7 +72,7 @@ public class SmoothRayer : MonoBehaviour UpdatePosesAction(true); } - foreach (MelonPreferences_Entry setting in SmoothRayMod.Category.Entries) + foreach (MelonPreferences_Entry setting in SmootherRayMod.Category.Entries) setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings); MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged); @@ -131,13 +131,13 @@ public class SmoothRayer : MonoBehaviour private void OnUpdateSettings(object arg1, object arg2) { - _isEnabled = SmoothRayMod.EntryEnabled.Value; - _menuOnly = SmoothRayMod.EntryMenuOnly.Value; - _smallMovementThresholdAngle = SmoothRayMod.EntrySmallMovementThresholdAngle.Value; + _isEnabled = SmootherRayMod.EntryEnabled.Value; + _menuOnly = SmootherRayMod.EntryMenuOnly.Value; + _smallMovementThresholdAngle = SmootherRayMod.EntrySmallMovementThresholdAngle.Value; // dont let value hit 0, itll freeze controllers - _positionSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRayMod.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); - _rotationSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmoothRayMod.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); + _positionSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmootherRayMod.EntryPositionSmoothing.Value, 0f, 20f), 0.1f); + _rotationSmoothingValue = Mathf.Max(20f - Mathf.Clamp(SmootherRayMod.EntryRotationSmoothing.Value, 0f, 20f), 0.1f); if (!_isEnabled) return; // only care about setting being enabled @@ -147,7 +147,7 @@ public class SmoothRayer : MonoBehaviour if (MetaPort.Instance.settings.GetSettingsBool("ControlSmoothRaycast")) return; // disable saved setting once - SmoothRayMod.Logger.Msg("Built-in SmoothRay setting found to be enabled. Disabling built-in SmoothRay implementation in favor of modded implementation."); + SmootherRayMod.Logger.Msg("Built-in SmootherRay setting found to be enabled. Disabling built-in SmootherRay implementation in favor of modded implementation."); MetaPort.Instance.settings.SetSettingsBool("ControlSmoothRaycast", false); ViewManager.SetGameSettingBool("ControlSmoothRaycast", false); // ^ did you know the game doesn't even use this method native... @@ -161,11 +161,11 @@ public class SmoothRayer : MonoBehaviour if (!value) return; // only care about setting being enabled - _isEnabled = false; // ensure modded SmoothRay is disabled + _isEnabled = false; // ensure modded SmootherRay is disabled - if (!SmoothRayMod.EntryEnabled.Value) return; // disable saved setting once - SmoothRayMod.Logger.Msg("Modded SmoothRay found to be enabled. Disabling modded SmoothRay implementation in favor of built-in implementation."); - SmoothRayMod.EntryEnabled.Value = false; + if (!SmootherRayMod.EntryEnabled.Value) return; // disable saved setting once + SmootherRayMod.Logger.Msg("Modded SmootherRay found to be enabled. Disabling modded SmootherRay implementation in favor of built-in implementation."); + SmootherRayMod.EntryEnabled.Value = false; } private void OnAppliedPoses() diff --git a/SmootherRay/format.json b/SmootherRay/format.json new file mode 100644 index 0000000..e5f51fd --- /dev/null +++ b/SmootherRay/format.json @@ -0,0 +1,23 @@ +{ + "_id": 162, + "name": "SmootherRay", + "modversion": "1.0.5", + "gameversion": "2024r176", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Smoothes your controller while the raycast lines are visible.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n- An option is provided to only smooth when aiming at menus.\n- Smoothing characteristics are completely configurable, but the defaults are basically perfect.\n- Pairs well with the [WhereAmIPointing](https://discord.com/channels/1001388809184870441/1002058238545641542/1282798820073406556) mod.\n\n**Only supports OpenVR, not OpenXR.**\n\n-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.", + "searchtags": [ + "vr", + "ray", + "controller", + "smoothing" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmootherRay.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmootherRay/", + "changelog": "- Fixed for 2024r176.", + "embedcolor": "#f61963" +} \ No newline at end of file From 31858c81eb969210f1a6171bcc08778e3f11d122 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:54:25 -0500 Subject: [PATCH 037/188] SmootherRay: updated readme --- SmootherRay/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SmootherRay/README.md b/SmootherRay/README.md index 72fcbf0..bf64836 100644 --- a/SmootherRay/README.md +++ b/SmootherRay/README.md @@ -1,15 +1,21 @@ -# SmoothRay +# SmootherRay -Smoothes your controller while using the Quick and Main menus to make it easier to navigate. +Smoothes your controller while the raycast lines are visible. This is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController) +- An option is provided to only smooth when aiming at menus. +- Smoothing characteristics are completely configurable, but the defaults are basically perfect. +- Pairs well with the [WhereAmIPointing](https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing) mod. + **Only supports OpenVR, not OpenXR.** +-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why. + --- Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - +~~~~ > This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. From d2c42391b97ed1efe145c2d4497122f19802b287 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:57:56 -0500 Subject: [PATCH 038/188] SmootherRay: update format.json --- SmootherRay/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SmootherRay/format.json b/SmootherRay/format.json index e5f51fd..a627013 100644 --- a/SmootherRay/format.json +++ b/SmootherRay/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmootherRay.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmootherRay/", - "changelog": "- Fixed for 2024r176.", + "changelog": "- Fixed for 2024r176.\n- Rebranded to SmootherRayer due to native implementation now existing and sucking.", "embedcolor": "#f61963" } \ No newline at end of file From 5f6a85984d49999a308579163c11f8bdcc666078 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:08:40 -0400 Subject: [PATCH 039/188] Update README.md Added Restrictions section. --- Stickers/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Stickers/README.md b/Stickers/README.md index eff471a..ae0f4ae 100644 --- a/Stickers/README.md +++ b/Stickers/README.md @@ -22,6 +22,12 @@ Any image placed in the `UserData/Stickers/` folder will be available to choose - Requires the experimental Shader Safety Settings to be disabled as it will cause crashes when decals attempt to generate on GPU. - The mod will automatically disable this setting when it is enabled on startup. +### Restrictions +- Full Restriction. + - To disable Stickers for the whole world, name an empty GameObject "**[DisableStickers]**". +- Partial Restriction. + - To keep stickers enabled but not allowing it on certain objects, add the "**[NoSticker]**" tag to the GameObject name. + ## Attributions - All icons used are by [Gohsantosadrive]() on Flaticon. - Decal generation system by [Mr F]() on the Unity Asset Store. From 71d780248f4a9e059451f498750958f865c4bad2 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:10:40 -0400 Subject: [PATCH 040/188] Added World Restriction Checks --- Stickers/Stickers/StickerSystem.Main.cs | 273 +++++++------ .../StickerSystem.StickerLifecycle.cs | 384 +++++++++--------- 2 files changed, 344 insertions(+), 313 deletions(-) diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index ab95877..0a5a4ef 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -1,124 +1,151 @@ -using ABI_RC.Core.IO; -using ABI_RC.Core.Networking.IO.Instancing; -using ABI_RC.Core.UI; -using ABI_RC.Systems.GameEventSystem; -using NAK.Stickers.Networking; -using NAK.Stickers.Utilities; - -namespace NAK.Stickers; - -public partial class StickerSystem -{ - #region Singleton - - public static StickerSystem Instance { get; private set; } - - public static void Initialize() - { - if (Instance != null) - return; - - Instance = new StickerSystem(); - - // configure decalery - DecalManager.SetPreferredMode(DecalUtils.Mode.GPU, false, 0); - - // ensure cache folder exists - EnsureStickersFolderExists(); - - // listen for game events - CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); - } - - #endregion Singleton - - #region Callback Registration - - private void OnPlayerSetupStart() - { - CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); - CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); - - CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); - CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); - SchedulerSystem.AddJob(Instance.OnUpdate, 10f, -1); - LoadAllImagesAtStartup(); - } - - #endregion Callback Registration - - #region Game Events - - private void OnInitialConnection() - { - ClearStickersSelf(); // clear stickers on remotes just in case we rejoined - ModNetwork.Reset(); // reset network buffers and metadata - } - - private void OnWorldUnload() - { - CleanupAllButSelf(); // release all stickers except for self - } - - #endregion Game Events - - #region Data - - // private bool _isEnabled = true; - // - // public bool IsEnabled - // { - // get => _isEnabled; - // set - // { - // if (_isEnabled == value) - // return; - // - // _isEnabled = value; - // if (!_isEnabled) ClearAllStickers(); - // ModNetwork.IsEnabled = _isEnabled; - // } - // } - - private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; - - private const float StickerKillTime = 30f; - private const float StickerCooldown = 0.2f; - private readonly Dictionary _playerStickers = new(); - internal const string PlayerLocalId = "_PLAYERLOCAL"; - - private int _selectedStickerSlot; - public int SelectedStickerSlot - { - get => _selectedStickerSlot; - set - { - _selectedStickerSlot = value < 0 ? ModSettings.MaxStickerSlots - 1 : value % ModSettings.MaxStickerSlots; - IsInStickerMode = IsInStickerMode; // refresh sticker mode - } - } - - private bool _isInStickerMode; - public bool IsInStickerMode - { - get => _isInStickerMode; - set - { - _isInStickerMode = value; - if (_isInStickerMode) - { - CohtmlHud.Instance.SelectPropToSpawn( - StickerCache.GetCohtmlResourcesPath(SelectedStickerName), - Path.GetFileNameWithoutExtension(SelectedStickerName), - "Sticker selected for stickering:"); - } - else - { - CohtmlHud.Instance.ClearPropToSpawn(); - ClearStickerPreview(); - } - } - } - - #endregion Data +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.UI; +using ABI_RC.Systems.GameEventSystem; +using JetBrains.Annotations; +using NAK.Stickers.Networking; +using NAK.Stickers.Utilities; +using System.EnterpriseServices; +using UnityEngine; +using MelonLoader; +using UnityEngine.ProBuilder.MeshOperations; +using NAK.Stickers.Integrations; + +namespace NAK.Stickers; + +public partial class StickerSystem +{ + #region Singleton + + public static bool RestrictedInstance = false; + + public static StickerSystem Instance { get; private set; } + + public static void Initialize() + { + if (Instance != null) + return; + + Instance = new StickerSystem(); + + // configure decalery + DecalManager.SetPreferredMode(DecalUtils.Mode.GPU, false, 0); + + // ensure cache folder exists + EnsureStickersFolderExists(); + + // listen for game events + CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); + +} + + #endregion Singleton + + #region Callback Registration + + private void OnPlayerSetupStart() + { + CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); + CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); + CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); + + CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); + CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); + SchedulerSystem.AddJob(Instance.OnUpdate, 10f, -1); + LoadAllImagesAtStartup(); + } + + #endregion Callback Registration + + #region Game Events + + private void OnInitialConnection() + { + OnWorldLoad(); //Checks the world again in case the bundle updated. + ClearStickersSelf(); // clear stickers on remotes just in case we rejoined + ModNetwork.Reset(); // reset network buffers and metadata + } + + private void OnWorldLoad() + { + GameObject StickerWorldRestriction = GameObject.Find("[DisableStickers]"); + if (StickerWorldRestriction != null) + { + RestrictedInstance = true; + MelonLogger.Msg("This is a Restricted Instance"); + } + else + { + MelonLogger.Msg("This is NOT a Restricted Instance"); + } + BTKUIAddon.UpdateStickerMenu(); + } + + private void OnWorldUnload() + { + RestrictedInstance = false; + CleanupAllButSelf(); // release all stickers except for self + } + + #endregion Game Events + + #region Data + + // private bool _isEnabled = true; + // + // public bool IsEnabled + // { + // get => _isEnabled; + // set + // { + // if (_isEnabled == value) + // return; + // + // _isEnabled = value; + // if (!_isEnabled) ClearAllStickers(); + // ModNetwork.IsEnabled = _isEnabled; + // } + // } + + private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; + + private const float StickerKillTime = 30f; + private const float StickerCooldown = 0.2f; + private readonly Dictionary _playerStickers = new(); + internal const string PlayerLocalId = "_PLAYERLOCAL"; + + private int _selectedStickerSlot; + public int SelectedStickerSlot + { + get => _selectedStickerSlot; + set + { + _selectedStickerSlot = value < 0 ? ModSettings.MaxStickerSlots - 1 : value % ModSettings.MaxStickerSlots; + IsInStickerMode = IsInStickerMode; // refresh sticker mode + } + } + + private bool _isInStickerMode; + public bool IsInStickerMode + { + get => _isInStickerMode; + set + { + _isInStickerMode = value; + if (_isInStickerMode) + { + CohtmlHud.Instance.SelectPropToSpawn( + StickerCache.GetCohtmlResourcesPath(SelectedStickerName), + Path.GetFileNameWithoutExtension(SelectedStickerName), + "Sticker selected for stickering:"); + } + else + { + CohtmlHud.Instance.ClearPropToSpawn(); + ClearStickerPreview(); + } + } + } + + #endregion Data } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index 2977dff..821aedc 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -1,190 +1,194 @@ -using ABI_RC.Core; -using ABI_RC.Core.Player; -using ABI_RC.Systems.InputManagement; -using NAK.Stickers.Networking; -using UnityEngine; - -namespace NAK.Stickers; - -public partial class StickerSystem -{ - #region Sticker Lifecycle - - private StickerData GetOrCreateStickerData(string playerId) - { - if (_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return stickerData; - - stickerData = new StickerData(playerId, ModSettings.MaxStickerSlots); - _playerStickers[playerId] = stickerData; - return stickerData; - } - - public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left, bool isPreview = false) - { - Vector3 controllerForward = transform.forward; - Vector3 controllerUp = transform.up; - Vector3 playerUp = PlayerSetup.Instance.transform.up; - - // extracting angle of controller ray on forward axis - Vector3 projectedControllerUp = Vector3.ProjectOnPlane(controllerUp, controllerForward).normalized; - Vector3 projectedPlayerUp = Vector3.ProjectOnPlane(playerUp, controllerForward).normalized; - float angle = Vector3.Angle(projectedControllerUp, projectedPlayerUp); - - float angleThreshold = ModSettings.Entry_PlayerUpAlignmentThreshold.Value; - Vector3 targetUp = (angleThreshold != 0f && angle <= angleThreshold) - // leave 0.01% of the controller up vector to prevent issues with alignment on floor & ceiling in Desktop - ? Vector3.Slerp(controllerUp, playerUp, 0.99f) - : controllerUp; - - if (isPreview) - { - PlaceStickerPreview(transform.position, controllerForward, targetUp); - return; - } - - if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) - return; - - CVRInputManager.Instance.Vibrate(0f, 0.1f, 10f, 0.1f, hand); - } - - private bool PlaceStickerSelf(Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true) - { - if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot)) - return false; // failed - - // placed, now network - ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up); - return true; - } - - private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool isPreview = false) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - if (Time.time - stickerData.LastPlacedTime < StickerCooldown) - return false; - - // Every layer other than IgnoreRaycast, PlayerLocal, PlayerClone, PlayerNetwork, and UI Internal - const int LayerMask = ~((1 << 2) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 15)); - if (!Physics.Raycast(position, forward, out RaycastHit hit, - 10f, LayerMask, QueryTriggerInteraction.Ignore)) - return false; - - // if gameobject name starts with [NoSticker] then don't place sticker - if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) - return false; - - if (isPreview) - { - stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); - return true; - } - - stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); - stickerData.PlayAudio(); - return true; - } - - public void ClearStickersSelf() - { - ClearStickersForPlayer(PlayerLocalId); - ModNetwork.SendClearAllStickers(); - } - - public void ClearStickerSelf(int stickerSlot) - { - ClearStickersForPlayer(PlayerLocalId, stickerSlot); - ModNetwork.SendClearSticker(stickerSlot); - } - - private void ClearStickersForPlayer(string playerId) - { - if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return; - - stickerData.Clear(); - } - - private void ClearStickersForPlayer(string playerId, int stickerSlot) - { - if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return; - - stickerData.Clear(stickerSlot); - } - - private void SetTextureSelf(byte[] imageBytes, int stickerSlot = 0) - { - Texture2D texture = new(1, 1); // placeholder - texture.LoadImage(imageBytes); - texture.Compress(true); // noachi said to do - - ClearStickerSelf(stickerSlot); // clear placed stickers in-scene so we can't replace an entire wall at once - OnPlayerStickerTextureReceived(PlayerLocalId, Guid.Empty, texture, stickerSlot); - ModNetwork.SetTexture(stickerSlot, imageBytes); - } - - public void ClearAllStickers() - { - foreach (StickerData stickerData in _playerStickers.Values) - stickerData.Clear(); - - ModNetwork.SendClearAllStickers(); - } - - public void OnPlayerStickerTextureReceived(string playerId, Guid textureHash, Texture2D texture, int stickerSlot = 0) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - stickerData.SetTexture(textureHash, texture, stickerSlot); - } - - public bool HasTextureHash(string playerId, Guid textureHash) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - return stickerData.CheckHasTextureHash(textureHash); - } - - public void CleanupAll() - { - foreach ((_, StickerData data) in _playerStickers) - data.Cleanup(); - - _playerStickers.Clear(); - } - - public void CleanupAllButSelf() - { - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - - foreach ((_, StickerData data) in _playerStickers) - { - if (data == localStickerData) data.Clear(); - else data.Cleanup(); - } - - _playerStickers.Clear(); - _playerStickers[PlayerLocalId] = localStickerData; - } - - public void PlaceStickerPreview(Vector3 position, Vector3 forward, Vector3 up) - { - AttemptPlaceSticker(PlayerLocalId, position, forward, up, true, SelectedStickerSlot, true); - } - - public void UpdateStickerPreview() - { - if (!IsInStickerMode) return; - - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - localStickerData.UpdatePreview(SelectedStickerSlot); - } - - public void ClearStickerPreview() - { - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - localStickerData.ClearPreview(); - } - - #endregion Sticker Lifecycle -} +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Systems.InputManagement; +using NAK.Stickers.Networking; +using UnityEngine; + +namespace NAK.Stickers; + +public partial class StickerSystem +{ + #region Sticker Lifecycle + + private StickerData GetOrCreateStickerData(string playerId) + { + if (_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return stickerData; + + stickerData = new StickerData(playerId, ModSettings.MaxStickerSlots); + _playerStickers[playerId] = stickerData; + return stickerData; + } + + public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left, bool isPreview = false) + { + Vector3 controllerForward = transform.forward; + Vector3 controllerUp = transform.up; + Vector3 playerUp = PlayerSetup.Instance.transform.up; + + // extracting angle of controller ray on forward axis + Vector3 projectedControllerUp = Vector3.ProjectOnPlane(controllerUp, controllerForward).normalized; + Vector3 projectedPlayerUp = Vector3.ProjectOnPlane(playerUp, controllerForward).normalized; + float angle = Vector3.Angle(projectedControllerUp, projectedPlayerUp); + + float angleThreshold = ModSettings.Entry_PlayerUpAlignmentThreshold.Value; + Vector3 targetUp = (angleThreshold != 0f && angle <= angleThreshold) + // leave 0.01% of the controller up vector to prevent issues with alignment on floor & ceiling in Desktop + ? Vector3.Slerp(controllerUp, playerUp, 0.99f) + : controllerUp; + + if (isPreview) + { + PlaceStickerPreview(transform.position, controllerForward, targetUp); + return; + } + + if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) + return; + + CVRInputManager.Instance.Vibrate(0f, 0.1f, 10f, 0.1f, hand); + } + + private bool PlaceStickerSelf(Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true) + { + if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot, RestrictedInstance)) + return false; // failed + + // placed, now network + ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up); + return true; + } + + private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool RestrictedInstance = false, bool isPreview = false) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + if (Time.time - stickerData.LastPlacedTime < StickerCooldown) + return false; + + // Every layer other than IgnoreRaycast, PlayerLocal, PlayerClone, PlayerNetwork, and UI Internal + const int LayerMask = ~((1 << 2) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 15)); + if (!Physics.Raycast(position, forward, out RaycastHit hit, + 10f, LayerMask, QueryTriggerInteraction.Ignore)) + return false; + + // if gameobject name starts with [NoSticker] then don't place sticker + if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) + return false; + + // if the world contained a gameobject with the [DisableStickers] name and restricted the instance disable stickers! + if (RestrictedInstance == true) + return false; + + if (isPreview) + { + stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); + return true; + } + + stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); + stickerData.PlayAudio(); + return true; + } + + public void ClearStickersSelf() + { + ClearStickersForPlayer(PlayerLocalId); + ModNetwork.SendClearAllStickers(); + } + + public void ClearStickerSelf(int stickerSlot) + { + ClearStickersForPlayer(PlayerLocalId, stickerSlot); + ModNetwork.SendClearSticker(stickerSlot); + } + + private void ClearStickersForPlayer(string playerId) + { + if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return; + + stickerData.Clear(); + } + + private void ClearStickersForPlayer(string playerId, int stickerSlot) + { + if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return; + + stickerData.Clear(stickerSlot); + } + + private void SetTextureSelf(byte[] imageBytes, int stickerSlot = 0) + { + Texture2D texture = new(1, 1); // placeholder + texture.LoadImage(imageBytes); + texture.Compress(true); // noachi said to do + + ClearStickerSelf(stickerSlot); // clear placed stickers in-scene so we can't replace an entire wall at once + OnPlayerStickerTextureReceived(PlayerLocalId, Guid.Empty, texture, stickerSlot); + ModNetwork.SetTexture(stickerSlot, imageBytes); + } + + public void ClearAllStickers() + { + foreach (StickerData stickerData in _playerStickers.Values) + stickerData.Clear(); + + ModNetwork.SendClearAllStickers(); + } + + public void OnPlayerStickerTextureReceived(string playerId, Guid textureHash, Texture2D texture, int stickerSlot = 0) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + stickerData.SetTexture(textureHash, texture, stickerSlot); + } + + public bool HasTextureHash(string playerId, Guid textureHash) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + return stickerData.CheckHasTextureHash(textureHash); + } + + public void CleanupAll() + { + foreach ((_, StickerData data) in _playerStickers) + data.Cleanup(); + + _playerStickers.Clear(); + } + + public void CleanupAllButSelf() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + + foreach ((_, StickerData data) in _playerStickers) + { + if (data == localStickerData) data.Clear(); + else data.Cleanup(); + } + + _playerStickers.Clear(); + _playerStickers[PlayerLocalId] = localStickerData; + } + + public void PlaceStickerPreview(Vector3 position, Vector3 forward, Vector3 up) + { + AttemptPlaceSticker(PlayerLocalId, position, forward, up, true, SelectedStickerSlot, true); + } + + public void UpdateStickerPreview() + { + if (!IsInStickerMode) return; + + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.UpdatePreview(SelectedStickerSlot); + } + + public void ClearStickerPreview() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); + } + + #endregion Sticker Lifecycle +} From 498dcff8b96e716ceaf8baa694914d68b99d1bbe Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:12:09 -0400 Subject: [PATCH 041/188] World Restriction Check Disabled incoming messages when world is Restricted. --- .../Stickers/Networking/ModNetwork.Inbound.cs | 496 +++++++++--------- 1 file changed, 249 insertions(+), 247 deletions(-) diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index 7a6af74..ad04daf 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -1,248 +1,250 @@ -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Savior; -using ABI_RC.Systems.ModNetwork; -using NAK.Stickers.Utilities; -using UnityEngine; - -namespace NAK.Stickers.Networking; - -public static partial class ModNetwork -{ - #region Inbound Buffers - - private static readonly Dictionary _textureChunkBuffers = new(); - private static readonly Dictionary _receivedChunkCounts = new(); - private static readonly Dictionary _expectedChunkCounts = new(); - private static readonly Dictionary _textureMetadata = new(); - - #endregion Inbound Buffers - - #region Reset Method - - public static void Reset() - { - _textureChunkBuffers.Clear(); - _receivedChunkCounts.Clear(); - _expectedChunkCounts.Clear(); - _textureMetadata.Clear(); - - LoggerInbound("ModNetwork inbound buffers and metadata have been reset."); - } - - #endregion Reset Method - - #region Inbound Methods - - private static bool ShouldReceiveFromSender(string sender) - { - if (_disallowedForSession.Contains(sender)) - return false; // ignore messages from disallowed users - - if (MetaPort.Instance.blockedUserIds.Contains(sender)) - return false; // ignore messages from blocked users - - if (ModSettings.Entry_FriendsOnly.Value && !Friends.FriendsWith(sender)) - return false; // ignore messages from non-friends if friends only is enabled - - return true; - } - - private static void HandleMessageReceived(ModNetworkMessage msg) - { - try - { - string sender = msg.Sender; - msg.Read(out byte msgTypeRaw); - - if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) - return; - - if (!ShouldReceiveFromSender(sender)) - return; - - LoggerInbound($"Received message from {msg.Sender}, Type: {(MessageType)msgTypeRaw}"); - - switch ((MessageType)msgTypeRaw) - { - case MessageType.PlaceSticker: - HandlePlaceSticker(msg); - break; - case MessageType.ClearSticker: - HandleClearSticker(msg); - break; - case MessageType.ClearAllStickers: - HandleClearAllStickers(msg); - break; - case MessageType.StartTexture: - HandleStartTexture(msg); - break; - case MessageType.SendTexture: - HandleSendTexture(msg); - break; - case MessageType.EndTexture: - HandleEndTexture(msg); - break; - case MessageType.RequestTexture: - HandleRequestTexture(msg); - break; - default: - LoggerInbound($"Invalid message type received: {msgTypeRaw}"); - break; - } - } - catch (Exception e) - { - LoggerInbound($"Error handling message from {msg.Sender}: {e.Message}", true); - } - } - - private static void HandlePlaceSticker(ModNetworkMessage msg) - { - msg.Read(out int stickerSlot); - msg.Read(out Guid textureHash); - msg.Read(out Vector3 position); - msg.Read(out Vector3 forward); - msg.Read(out Vector3 up); - - if (!StickerSystem.Instance.HasTextureHash(msg.Sender, textureHash)) - SendRequestTexture(stickerSlot, textureHash); - - StickerSystem.Instance.OnStickerPlaceReceived(msg.Sender, stickerSlot, position, forward, up); - } - - private static void HandleClearSticker(ModNetworkMessage msg) - { - msg.Read(out int stickerSlot); - StickerSystem.Instance.OnStickerClearReceived(msg.Sender, stickerSlot); - } - - private static void HandleClearAllStickers(ModNetworkMessage msg) - { - StickerSystem.Instance.OnStickerClearAllReceived(msg.Sender); - } - - private static void HandleStartTexture(ModNetworkMessage msg) - { - string sender = msg.Sender; - msg.Read(out int stickerSlot); - msg.Read(out Guid textureHash); - msg.Read(out int chunkCount); - msg.Read(out int width); - msg.Read(out int height); - - if (_textureChunkBuffers.ContainsKey(sender)) - { - LoggerInbound($"Received StartTexture message from {sender} while still receiving texture data!"); - return; - } - - if (StickerSystem.Instance.HasTextureHash(sender, textureHash)) - { - LoggerInbound($"Received StartTexture message from {sender} with existing texture hash {textureHash}, skipping texture data."); - return; - } - - if (chunkCount > MaxChunkCount) - { - LoggerInbound($"Received StartTexture message from {sender} with too many chunks: {chunkCount}", true); - return; - } - - _textureMetadata[sender] = (stickerSlot, textureHash, width, height); - _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxTextureSize)]; - _expectedChunkCounts[sender] = Mathf.Clamp(chunkCount, 0, MaxChunkCount); - _receivedChunkCounts[sender] = 0; - - LoggerInbound($"Received StartTexture message from {sender}: Slot: {stickerSlot}, Hash: {textureHash}, Chunks: {chunkCount}, Resolution: {width}x{height}"); - } - - private static void HandleSendTexture(ModNetworkMessage msg) - { - string sender = msg.Sender; - msg.Read(out int chunkIdx); - msg.Read(out byte[] chunkData); - - if (!_textureChunkBuffers.TryGetValue(sender, out var buffer)) - return; - - int startIndex = chunkIdx * ChunkSize; - Array.Copy(chunkData, 0, buffer, startIndex, chunkData.Length); - - _receivedChunkCounts[sender]++; - if (_receivedChunkCounts[sender] < _expectedChunkCounts[sender]) - return; - - (int stickerSlot, Guid Hash, int Width, int Height) metadata = _textureMetadata[sender]; - - // All chunks received, reassemble texture - _textureChunkBuffers.Remove(sender); - _receivedChunkCounts.Remove(sender); - _expectedChunkCounts.Remove(sender); - _textureMetadata.Remove(sender); - - // Validate image - if (!ImageUtility.IsValidImage(buffer)) - { - LoggerInbound($"[Inbound] Received texture data is not a valid image from {sender}!", true); - return; - } - - // Validate data TODO: fix hash??????? - (Guid imageHash, int width, int height) = ImageUtility.ExtractImageInfo(buffer); - if (metadata.Width != width - || metadata.Height != height) - { - LoggerInbound($"Received texture data does not match metadata! Expected: {metadata.Hash} ({metadata.Width}x{metadata.Height}), received: {imageHash} ({width}x{height})", true); - return; - } - - Texture2D texture = new(1,1); - texture.LoadImage(buffer); - texture.Compress(true); - - StickerSystem.Instance.OnPlayerStickerTextureReceived(sender, metadata.Hash, texture, metadata.stickerSlot); - - LoggerInbound($"All chunks received and texture reassembled from {sender}. " + - $"Texture size: {metadata.Width}x{metadata.Height}"); - } - - private static void HandleEndTexture(ModNetworkMessage msg) - { - string sender = msg.Sender; - if (!_textureChunkBuffers.ContainsKey(sender)) - return; - - LoggerInbound($"Received EndTexture message without all chunks received from {sender}! Only {_receivedChunkCounts[sender]} out of {_expectedChunkCounts[sender]} received."); - - _textureChunkBuffers.Remove(sender); - _receivedChunkCounts.Remove(sender); - _expectedChunkCounts.Remove(sender); - _textureMetadata.Remove(sender); - } - - private static void HandleRequestTexture(ModNetworkMessage msg) - { - string sender = msg.Sender; - msg.Read(out int stickerSlot); - msg.Read(out Guid textureHash); - - if (!_isSubscribedToModNetwork || IsSendingTexture) - return; - - if (stickerSlot < 0 || stickerSlot >= _textureStorage.Length) - { - LoggerInbound($"Received RequestTexture message from {sender} with invalid slot {stickerSlot}!"); - return; - } - - if (_textureStorage[stickerSlot].textureHash != textureHash) - { - LoggerInbound($"Received RequestTexture message from {sender} with invalid texture hash {textureHash} for slot {stickerSlot}!"); - return; - } - - SendTexture(stickerSlot); - } - - #endregion Inbound Methods +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.ModNetwork; +using NAK.Stickers.Utilities; +using UnityEngine; + +namespace NAK.Stickers.Networking; + +public static partial class ModNetwork +{ + #region Inbound Buffers + + private static readonly Dictionary _textureChunkBuffers = new(); + private static readonly Dictionary _receivedChunkCounts = new(); + private static readonly Dictionary _expectedChunkCounts = new(); + private static readonly Dictionary _textureMetadata = new(); + + #endregion Inbound Buffers + + #region Reset Method + + public static void Reset() + { + _textureChunkBuffers.Clear(); + _receivedChunkCounts.Clear(); + _expectedChunkCounts.Clear(); + _textureMetadata.Clear(); + + LoggerInbound("ModNetwork inbound buffers and metadata have been reset."); + } + + #endregion Reset Method + + #region Inbound Methods + + private static bool ShouldReceiveFromSender(string sender) + { + if (_disallowedForSession.Contains(sender)) + return false; // ignore messages from disallowed users + + if (MetaPort.Instance.blockedUserIds.Contains(sender)) + return false; // ignore messages from blocked users + + if (ModSettings.Entry_FriendsOnly.Value && !Friends.FriendsWith(sender)) + return false; // ignore messages from non-friends if friends only is enabled + if (StickerSystem.RestrictedInstance == true) // ignore messages from users when the world is restricted. This also includes older or modified version of Stickers mod. + return false; + + return true; + } + + private static void HandleMessageReceived(ModNetworkMessage msg) + { + try + { + string sender = msg.Sender; + msg.Read(out byte msgTypeRaw); + + if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) + return; + + if (!ShouldReceiveFromSender(sender)) + return; + + LoggerInbound($"Received message from {msg.Sender}, Type: {(MessageType)msgTypeRaw}"); + + switch ((MessageType)msgTypeRaw) + { + case MessageType.PlaceSticker: + HandlePlaceSticker(msg); + break; + case MessageType.ClearSticker: + HandleClearSticker(msg); + break; + case MessageType.ClearAllStickers: + HandleClearAllStickers(msg); + break; + case MessageType.StartTexture: + HandleStartTexture(msg); + break; + case MessageType.SendTexture: + HandleSendTexture(msg); + break; + case MessageType.EndTexture: + HandleEndTexture(msg); + break; + case MessageType.RequestTexture: + HandleRequestTexture(msg); + break; + default: + LoggerInbound($"Invalid message type received: {msgTypeRaw}"); + break; + } + } + catch (Exception e) + { + LoggerInbound($"Error handling message from {msg.Sender}: {e.Message}", true); + } + } + + private static void HandlePlaceSticker(ModNetworkMessage msg) + { + msg.Read(out int stickerSlot); + msg.Read(out Guid textureHash); + msg.Read(out Vector3 position); + msg.Read(out Vector3 forward); + msg.Read(out Vector3 up); + + if (!StickerSystem.Instance.HasTextureHash(msg.Sender, textureHash)) + SendRequestTexture(stickerSlot, textureHash); + + StickerSystem.Instance.OnStickerPlaceReceived(msg.Sender, stickerSlot, position, forward, up); + } + + private static void HandleClearSticker(ModNetworkMessage msg) + { + msg.Read(out int stickerSlot); + StickerSystem.Instance.OnStickerClearReceived(msg.Sender, stickerSlot); + } + + private static void HandleClearAllStickers(ModNetworkMessage msg) + { + StickerSystem.Instance.OnStickerClearAllReceived(msg.Sender); + } + + private static void HandleStartTexture(ModNetworkMessage msg) + { + string sender = msg.Sender; + msg.Read(out int stickerSlot); + msg.Read(out Guid textureHash); + msg.Read(out int chunkCount); + msg.Read(out int width); + msg.Read(out int height); + + if (_textureChunkBuffers.ContainsKey(sender)) + { + LoggerInbound($"Received StartTexture message from {sender} while still receiving texture data!"); + return; + } + + if (StickerSystem.Instance.HasTextureHash(sender, textureHash)) + { + LoggerInbound($"Received StartTexture message from {sender} with existing texture hash {textureHash}, skipping texture data."); + return; + } + + if (chunkCount > MaxChunkCount) + { + LoggerInbound($"Received StartTexture message from {sender} with too many chunks: {chunkCount}", true); + return; + } + + _textureMetadata[sender] = (stickerSlot, textureHash, width, height); + _textureChunkBuffers[sender] = new byte[Mathf.Clamp(chunkCount * ChunkSize, 0, MaxTextureSize)]; + _expectedChunkCounts[sender] = Mathf.Clamp(chunkCount, 0, MaxChunkCount); + _receivedChunkCounts[sender] = 0; + + LoggerInbound($"Received StartTexture message from {sender}: Slot: {stickerSlot}, Hash: {textureHash}, Chunks: {chunkCount}, Resolution: {width}x{height}"); + } + + private static void HandleSendTexture(ModNetworkMessage msg) + { + string sender = msg.Sender; + msg.Read(out int chunkIdx); + msg.Read(out byte[] chunkData); + + if (!_textureChunkBuffers.TryGetValue(sender, out var buffer)) + return; + + int startIndex = chunkIdx * ChunkSize; + Array.Copy(chunkData, 0, buffer, startIndex, chunkData.Length); + + _receivedChunkCounts[sender]++; + if (_receivedChunkCounts[sender] < _expectedChunkCounts[sender]) + return; + + (int stickerSlot, Guid Hash, int Width, int Height) metadata = _textureMetadata[sender]; + + // All chunks received, reassemble texture + _textureChunkBuffers.Remove(sender); + _receivedChunkCounts.Remove(sender); + _expectedChunkCounts.Remove(sender); + _textureMetadata.Remove(sender); + + // Validate image + if (!ImageUtility.IsValidImage(buffer)) + { + LoggerInbound($"[Inbound] Received texture data is not a valid image from {sender}!", true); + return; + } + + // Validate data TODO: fix hash??????? + (Guid imageHash, int width, int height) = ImageUtility.ExtractImageInfo(buffer); + if (metadata.Width != width + || metadata.Height != height) + { + LoggerInbound($"Received texture data does not match metadata! Expected: {metadata.Hash} ({metadata.Width}x{metadata.Height}), received: {imageHash} ({width}x{height})", true); + return; + } + + Texture2D texture = new(1,1); + texture.LoadImage(buffer); + texture.Compress(true); + + StickerSystem.Instance.OnPlayerStickerTextureReceived(sender, metadata.Hash, texture, metadata.stickerSlot); + + LoggerInbound($"All chunks received and texture reassembled from {sender}. " + + $"Texture size: {metadata.Width}x{metadata.Height}"); + } + + private static void HandleEndTexture(ModNetworkMessage msg) + { + string sender = msg.Sender; + if (!_textureChunkBuffers.ContainsKey(sender)) + return; + + LoggerInbound($"Received EndTexture message without all chunks received from {sender}! Only {_receivedChunkCounts[sender]} out of {_expectedChunkCounts[sender]} received."); + + _textureChunkBuffers.Remove(sender); + _receivedChunkCounts.Remove(sender); + _expectedChunkCounts.Remove(sender); + _textureMetadata.Remove(sender); + } + + private static void HandleRequestTexture(ModNetworkMessage msg) + { + string sender = msg.Sender; + msg.Read(out int stickerSlot); + msg.Read(out Guid textureHash); + + if (!_isSubscribedToModNetwork || IsSendingTexture) + return; + + if (stickerSlot < 0 || stickerSlot >= _textureStorage.Length) + { + LoggerInbound($"Received RequestTexture message from {sender} with invalid slot {stickerSlot}!"); + return; + } + + if (_textureStorage[stickerSlot].textureHash != textureHash) + { + LoggerInbound($"Received RequestTexture message from {sender} with invalid texture hash {textureHash} for slot {stickerSlot}!"); + return; + } + + SendTexture(stickerSlot); + } + + #endregion Inbound Methods } \ No newline at end of file From be05c04e7229f609485fcbd90113d962bc86d167 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:14:03 -0400 Subject: [PATCH 042/188] World Restriction Checks Updated the UI based on the world restriction. --- .../BTKUI/UIAddon.Category.StickersMod.cs | 210 ++++++++++------- Stickers/Integrations/BTKUI/UIAddon.Main.cs | 219 +++++++++--------- 2 files changed, 235 insertions(+), 194 deletions(-) diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs index 6660a52..0dfb059 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs @@ -1,89 +1,123 @@ -using BTKUILib; -using BTKUILib.UIObjects; -using BTKUILib.UIObjects.Components; -using BTKUILib.UIObjects.Objects; - -namespace NAK.Stickers.Integrations; - -public static partial class BTKUIAddon -{ - private static Category _ourCategory; - - private static readonly MultiSelection _sfxSelection = - MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_SelectedSFX); - - private static readonly MultiSelection _desktopKeybindSelection = - MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_PlaceBinding); - - private static readonly MultiSelection _tabDoubleClickSelection = - MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_TabDoubleClick); - - #region Category Setup - - private static void Setup_StickersModCategory() - { - _ourCategory = _rootPage.AddMelonCategory(ModSettings.Hidden_Foldout_SettingsCategory); - - Button placeStickersButton = _ourCategory.AddButton("Place Stickers", "Stickers-magic-wand", "Place stickers via raycast.", ButtonStyle.TextWithIcon); - placeStickersButton.OnPress += OnPlaceStickersButtonClick; - - Button clearSelfStickersButton = _ourCategory.AddButton("Clear Self", "Stickers-eraser", "Clear own stickers.", ButtonStyle.TextWithIcon); - clearSelfStickersButton.OnPress += OnClearSelfStickersButtonClick; - - Button clearAllStickersButton = _ourCategory.AddButton("Clear All", "Stickers-rubbish-bin", "Clear all stickers.", ButtonStyle.TextWithIcon); - clearAllStickersButton.OnPress += OnClearAllStickersButtonClick; - - Button openStickersFolderButton = _ourCategory.AddButton("Open Stickers Folder", "Stickers-folder", "Open UserData/Stickers folder in explorer. If above 256kb your image will automatically be downscaled for networking reasons.", ButtonStyle.TextWithIcon); - openStickersFolderButton.OnPress += OnOpenStickersFolderButtonClick; - - Button openStickerSFXButton = _ourCategory.AddButton("Sticker SFX", "Stickers-headset", "Choose the SFX used when a sticker is placed.", ButtonStyle.TextWithIcon); - openStickerSFXButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_sfxSelection); - - ToggleButton toggleDesktopKeybindButton = _ourCategory.AddToggle("Use Desktop Keybind", "Should the Desktop keybind be active.", ModSettings.Entry_UsePlaceBinding.Value); - Button openDesktopKeybindButton = _ourCategory.AddButton("Desktop Keybind", "Stickers-alphabet", "Choose the key binding to place stickers.", ButtonStyle.TextWithIcon); - openDesktopKeybindButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_desktopKeybindSelection); - toggleDesktopKeybindButton.OnValueUpdated += (b) => - { - ModSettings.Entry_UsePlaceBinding.Value = b; - openDesktopKeybindButton.Disabled = !b; - }; - - Button openTabDoubleClickButton = _ourCategory.AddButton("Tab Double Click", "Stickers-mouse", "Choose the action to perform when double clicking the Stickers tab.", ButtonStyle.TextWithIcon); - openTabDoubleClickButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_tabDoubleClickSelection); - } - - #endregion Category Setup - - #region Button Actions - - private static void OnPlaceStickersButtonClick() - { - if (!_isOurTabOpened) return; - string mode = StickerSystem.Instance.IsInStickerMode ? "Exiting" : "Entering"; - QuickMenuAPI.ShowAlertToast($"{mode} sticker placement mode...", 2); - StickerSystem.Instance.IsInStickerMode = !StickerSystem.Instance.IsInStickerMode; - } - - private static void OnClearSelfStickersButtonClick() - { - if (!_isOurTabOpened) return; - QuickMenuAPI.ShowAlertToast("Clearing own stickers in world...", 2); - StickerSystem.Instance.ClearStickersSelf(); - } - - private static void OnClearAllStickersButtonClick() - { - if (!_isOurTabOpened) return; - QuickMenuAPI.ShowAlertToast("Clearing all stickers in world...", 2); - StickerSystem.Instance.ClearAllStickers(); - } - - private static void OnOpenStickersFolderButtonClick() - { - if (!_isOurTabOpened) return; - QuickMenuAPI.ShowAlertToast("Opening Stickers folder in Explorer...", 2); - StickerSystem.OpenStickersFolder(); - } - - #endregion Button Actions +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using BTKUILib.UIObjects.Objects; + +namespace NAK.Stickers.Integrations; + +public static partial class BTKUIAddon +{ + private static Category _ourCategory; + + private static readonly MultiSelection _sfxSelection = + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_SelectedSFX); + + private static readonly MultiSelection _desktopKeybindSelection = + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_PlaceBinding); + + private static readonly MultiSelection _tabDoubleClickSelection = + MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_TabDoubleClick); + + public static Button placeStickersButton; + + #region Category Setup + + private static void Setup_StickersModCategory() + { + _ourCategory = _rootPage.AddMelonCategory(ModSettings.Hidden_Foldout_SettingsCategory); + + placeStickersButton = _ourCategory.AddButton("Place Stickers", "Stickers-magic-wand", "Place stickers via raycast.", ButtonStyle.TextWithIcon); + placeStickersButton.OnPress += OnPlaceStickersButtonClick; + + Button clearSelfStickersButton = _ourCategory.AddButton("Clear Self", "Stickers-eraser", "Clear own stickers.", ButtonStyle.TextWithIcon); + clearSelfStickersButton.OnPress += OnClearSelfStickersButtonClick; + + Button clearAllStickersButton = _ourCategory.AddButton("Clear All", "Stickers-rubbish-bin", "Clear all stickers.", ButtonStyle.TextWithIcon); + clearAllStickersButton.OnPress += OnClearAllStickersButtonClick; + + Button openStickersFolderButton = _ourCategory.AddButton("Open Stickers Folder", "Stickers-folder", "Open UserData/Stickers folder in explorer. If above 256kb your image will automatically be downscaled for networking reasons.", ButtonStyle.TextWithIcon); + openStickersFolderButton.OnPress += OnOpenStickersFolderButtonClick; + + Button openStickerSFXButton = _ourCategory.AddButton("Sticker SFX", "Stickers-headset", "Choose the SFX used when a sticker is placed.", ButtonStyle.TextWithIcon); + openStickerSFXButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_sfxSelection); + + ToggleButton toggleDesktopKeybindButton = _ourCategory.AddToggle("Use Desktop Keybind", "Should the Desktop keybind be active.", ModSettings.Entry_UsePlaceBinding.Value); + Button openDesktopKeybindButton = _ourCategory.AddButton("Desktop Keybind", "Stickers-alphabet", "Choose the key binding to place stickers.", ButtonStyle.TextWithIcon); + openDesktopKeybindButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_desktopKeybindSelection); + toggleDesktopKeybindButton.OnValueUpdated += (b) => + { + ModSettings.Entry_UsePlaceBinding.Value = b; + openDesktopKeybindButton.Disabled = !b; + }; + + Button openTabDoubleClickButton = _ourCategory.AddButton("Tab Double Click", "Stickers-mouse", "Choose the action to perform when double clicking the Stickers tab.", ButtonStyle.TextWithIcon); + openTabDoubleClickButton.OnPress += () => QuickMenuAPI.OpenMultiSelect(_tabDoubleClickSelection); + } + + #endregion Category Setup + + #region Button Actions + + private static void OnPlaceStickersButtonClick() + { + if (!_isOurTabOpened) return; + + if (StickerSystem.RestrictedInstance == false) + { + string mode = StickerSystem.Instance.IsInStickerMode ? "Exiting" : "Entering"; + QuickMenuAPI.ShowAlertToast($"{mode} sticker placement mode...", 2); + StickerSystem.Instance.IsInStickerMode = !StickerSystem.Instance.IsInStickerMode; + } + else + { + QuickMenuAPI.ShowAlertToast("Stickers are not allowed in this world!", 2); + } + } + + private static void OnClearSelfStickersButtonClick() + { + if (!_isOurTabOpened) return; + QuickMenuAPI.ShowAlertToast("Clearing own stickers in world...", 2); + StickerSystem.Instance.ClearStickersSelf(); + } + + private static void OnClearAllStickersButtonClick() + { + if (!_isOurTabOpened) return; + QuickMenuAPI.ShowAlertToast("Clearing all stickers in world...", 2); + StickerSystem.Instance.ClearAllStickers(); + } + + private static void OnOpenStickersFolderButtonClick() + { + if (!_isOurTabOpened) return; + QuickMenuAPI.ShowAlertToast("Opening Stickers folder in Explorer...", 2); + StickerSystem.OpenStickersFolder(); + } + + public static void UpdateStickerMenu() //TODO: add Icon changing, Bono needs to expose the value first. + { + if (StickerSystem.RestrictedInstance == true) + { + _rootPage.MenuSubtitle = "Stickers... are sadly disabled in this world."; + + placeStickersButton.Disabled = true; + placeStickersButton.ButtonText = "Stickers Disabled"; + placeStickersButton.ButtonTooltip = "This world is not allowing Stickers."; + placeStickersButton.ButtonIcon = "Stickers-magic-wand-broken"; + + } + else + { + _rootPage.MenuSubtitle = "Stickers! Double-click the tab to quickly toggle Sticker Mode."; + + placeStickersButton.Disabled = false; + placeStickersButton.ButtonText = "Place Stickers"; + placeStickersButton.ButtonTooltip = "Place stickers via raycast."; + placeStickersButton.ButtonIcon = "Stickers-magic-wand"; + } + + } + + #endregion Button Actions } \ No newline at end of file diff --git a/Stickers/Integrations/BTKUI/UIAddon.Main.cs b/Stickers/Integrations/BTKUI/UIAddon.Main.cs index 30827a1..e741432 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Main.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Main.cs @@ -1,107 +1,114 @@ -using BTKUILib; -using BTKUILib.UIObjects; -using NAK.Stickers.Networking; -using NAK.Stickers.Utilities; - -namespace NAK.Stickers.Integrations; - -public static partial class BTKUIAddon -{ - private static Page _rootPage; - private static string _rootPageElementID; - - private static bool _isOurTabOpened; - - public static void Initialize() - { - Setup_Icons(); - Setup_StickerModTab(); - Setup_PlayerOptionsPage(); - } - - #region Setup - - private static void Setup_Icons() - { - // All icons used - https://www.flaticon.com/authors/gohsantosadrive - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-alphabet.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-eraser.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-folder.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-headset.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-magic-wand.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-mouse.png")); - //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-puzzle.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); - } - - private static void Setup_StickerModTab() - { - _rootPage = new Page(ModSettings.ModName, ModSettings.SM_SettingsCategory, true, "Stickers-puzzle") - { - MenuTitle = ModSettings.SM_SettingsCategory, - MenuSubtitle = "Stickers! Double-click the tab to quickly toggle Sticker Mode.", - }; - - _rootPageElementID = _rootPage.ElementID; - - QuickMenuAPI.OnTabChange += OnTabChange; - ModNetwork.OnTextureOutboundStateChanged += (isSending) => - { - if (_isOurTabOpened && isSending) QuickMenuAPI.ShowAlertToast("Sending Sticker over Mod Network...", 2); - //_rootPage.Disabled = isSending; // TODO: fix being able to select stickers while sending - }; - - StickerSystem.OnStickerLoaded += (slotIndex, imageRelativePath) => - { - if (_isOurTabOpened) QuickMenuAPI.ShowAlertToast($"Sticker loaded: {imageRelativePath}", 2); - _stickerSelectionButtons[slotIndex].ButtonIcon = StickerCache.GetBtkUiIconName(imageRelativePath); - }; - - StickerSystem.OnStickerLoadFailed += (slotIndex, error) => - { - if (_isOurTabOpened) QuickMenuAPI.ShowAlertToast(error, 3); - }; - - Setup_StickersModCategory(); - Setup_StickerSelectionCategory(); - Setup_OtherOptionsCategory(); - } - - #endregion Setup - - #region Double-Click Place Sticker - - private static DateTime lastTime = DateTime.Now; - - private static void OnTabChange(string newTab, string previousTab) - { - _isOurTabOpened = newTab == _rootPageElementID; - if (!_isOurTabOpened) return; - - TimeSpan timeDifference = DateTime.Now - lastTime; - if (timeDifference.TotalSeconds <= 0.5) - { - switch (ModSettings.Entry_TabDoubleClick.Value) - { - default: - case TabDoubleClick.ToggleStickerMode: - OnPlaceStickersButtonClick(); - break; - case TabDoubleClick.ClearAllStickers: - OnClearAllStickersButtonClick(); - break; - case TabDoubleClick.ClearSelfStickers: - OnClearSelfStickersButtonClick(); - break; - case TabDoubleClick.None: - break; - } - return; - } - lastTime = DateTime.Now; - } - - #endregion Double-Click Place Sticker +using BTKUILib; +using BTKUILib.UIObjects; +using NAK.Stickers.Networking; +using NAK.Stickers.Utilities; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace NAK.Stickers.Integrations; + +public static partial class BTKUIAddon +{ + private static Page _rootPage; + private static string _rootPageElementID; + + private static bool _isOurTabOpened; + + public static void Initialize() + { + Setup_Icons(); + Setup_StickerModTab(); + Setup_PlayerOptionsPage(); + } + + #region Setup + + private static void Setup_Icons() + { + // All icons used - https://www.flaticon.com/authors/gohsantosadrive + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-alphabet.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-eraser.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-folder.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-headset.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magic-wand.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand-broken", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magic-wand-broken.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-mouse.png")); + //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-puzzle.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle-disabled", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-puzzle-disabled.png")); //Disabled Sticker Puzzle + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); + } + + private static void Setup_StickerModTab() + { + _rootPage = new Page(ModSettings.ModName, ModSettings.SM_SettingsCategory, true, "Stickers-Puzzle") //Sticker Icon will be left blank as it is updated on world join, AFTER Icon value is exposed.. + { + MenuTitle = ModSettings.SM_SettingsCategory, + MenuSubtitle = "", //Left this blank as it is defined when the world loads + }; + + _rootPageElementID = _rootPage.ElementID; + + QuickMenuAPI.OnTabChange += OnTabChange; + ModNetwork.OnTextureOutboundStateChanged += (isSending) => + { + if (_isOurTabOpened && isSending) QuickMenuAPI.ShowAlertToast("Sending Sticker over Mod Network...", 2); + //_rootPage.Disabled = isSending; // TODO: fix being able to select stickers while sending + }; + + StickerSystem.OnStickerLoaded += (slotIndex, imageRelativePath) => + { + if (_isOurTabOpened) QuickMenuAPI.ShowAlertToast($"Sticker loaded: {imageRelativePath}", 2); + _stickerSelectionButtons[slotIndex].ButtonIcon = StickerCache.GetBtkUiIconName(imageRelativePath); + }; + + StickerSystem.OnStickerLoadFailed += (slotIndex, error) => + { + if (_isOurTabOpened) QuickMenuAPI.ShowAlertToast(error, 3); + }; + + Setup_StickersModCategory(); + Setup_StickerSelectionCategory(); + Setup_OtherOptionsCategory(); + } + + #endregion Setup + + #region Double-Click Place Sticker + + private static DateTime lastTime = DateTime.Now; + + private static void OnTabChange(string newTab, string previousTab) + { + _isOurTabOpened = newTab == _rootPageElementID; + if (!_isOurTabOpened) return; + + TimeSpan timeDifference = DateTime.Now - lastTime; + if (timeDifference.TotalSeconds <= 0.5) + { + switch (ModSettings.Entry_TabDoubleClick.Value) + { + default: + case TabDoubleClick.ToggleStickerMode: + if (StickerSystem.RestrictedInstance == false) + { + OnPlaceStickersButtonClick(); + } + break; + case TabDoubleClick.ClearAllStickers: + OnClearAllStickersButtonClick(); + break; + case TabDoubleClick.ClearSelfStickers: + OnClearSelfStickersButtonClick(); + break; + case TabDoubleClick.None: + break; + } + return; + } + lastTime = DateTime.Now; + } + + #endregion Double-Click Place Sticker } \ No newline at end of file From fa050d8103362f8926d76829d7a0909f4bafae61 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:14:59 -0400 Subject: [PATCH 043/188] Added Sketch contributions --- Stickers/Properties/AssemblyInfo.cs | 62 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index 425649a..5107aed 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -1,32 +1,32 @@ -using NAK.Stickers.Properties; -using MelonLoader; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.Stickers))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.Stickers))] - -[assembly: MelonInfo( - typeof(NAK.Stickers.StickerMod), - nameof(NAK.Stickers), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.Stickers.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.7"; - public const string Author = "NotAKidoS"; +using NAK.Stickers.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.Stickers))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.Stickers))] + +[assembly: MelonInfo( + typeof(NAK.Stickers.StickerMod), + nameof(NAK.Stickers), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.Stickers.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.8"; + public const string Author = "NotAKidoS, SketchFoxsky"; } \ No newline at end of file From 14eeb1e5594756b4afe77180c1fd223aadecd8d4 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:15:50 -0400 Subject: [PATCH 044/188] Added Disabled Icons --- .../Stickers-magic-wand-broken.png | Bin 0 -> 60393 bytes .../Stickers-puzzle-Disabled.png | Bin 0 -> 38097 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Stickers/Resources/Gohsantosadrive_Icons/Stickers-magic-wand-broken.png create mode 100644 Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-Disabled.png diff --git a/Stickers/Resources/Gohsantosadrive_Icons/Stickers-magic-wand-broken.png b/Stickers/Resources/Gohsantosadrive_Icons/Stickers-magic-wand-broken.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e222ce9898da631639d8bb5ee10e89c53a0490 GIT binary patch literal 60393 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 z z_&A{Iu=%1ITD#J!TligY1kKFMrc2ncziPZijG-qxhueb!jqU#hsaQaUa2AyOfUSUF^!zys;PElH*R^<-jrLm4x{S*@B0Pgg zgzz&G55r!Gkj_6%AMbP;)UjxA*A>jlQGc%Z#oQ>A>$B!RJ_2?y@Z_v-=K~*sQ zVEr&V;)a>oNie1vjqmH~=p=5iw#ISCK@8(;(a{w5QG*O26?=+0D*>k%D)z?s28!@| z`DqaRjvG=hdOxmr_DjIlNZ#NGfWtl`lPqwIQ*JzSm*$8%Q6D2#yM~vO_TP5P+XAxL z5kFY)iC?x(rJLb`|tmJV8cA20Co(=YYKr z0efD3o+|W)U10XeAMLN9-?m8^@+6Rs2Z$wpn7LXV0Hcq6IF`-9asidy{pe z%!-(Y1Wul+-(xs=1|C{Tb9@}MQy|W303Mzje4T6=1zwp#3NF#n$?1CK{?O16@TP

c%0JIP6w#WjW(k44Bx*gFBAj(KE8R= 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 literal 0 HcmV?d00001 diff --git a/Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-Disabled.png b/Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-Disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..600038bb1dac280cd9895821559895eb819e96cf GIT binary patch literal 38097 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[er1DrqW~$Xh-B{{TgV Bq>cap literal 0 HcmV?d00001 From 4c582e3edf480f2c419ece4026614ef40a014c68 Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:17:18 -0400 Subject: [PATCH 045/188] Stickers World Restrictions Added Resources and World Restriction checks. --- NAK_CVR_Mods.sln | 506 +++++++++++++++++++++++------------------------ 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 55d8b13..01ced9a 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -1,253 +1,253 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32630.192 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CVRGizmos", "CVRGizmos\CVRGizmos.csproj", "{CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FuckToes", "FuckToes\FuckToes.csproj", "{79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GestureLock", "GestureLock\GestureLock.csproj", "{45A65AEB-4BFC-4E47-B181-BBB43BD81283}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathCamDisabler", "PathCamDisabler\PathCamDisabler.csproj", "{98169FD2-5CEB-46D1-A320-D7E06F82C9E0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustment\FOVAdjustment.csproj", "{EE552804-30B1-49CF-BBDE-3B312895AFF7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatBoxExtensions", "ChatBoxExtensions\ChatBoxExtensions.csproj", "{0E1DD746-33A1-4179-AE70-8FB83AC40ABC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysicsGunMod", "PhysicsGunMod\PhysicsGunMod.csproj", "{F94DDB73-9041-4F5C-AD43-6960701E8417}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadowCloneFallback", "ShadowCloneFallback\ShadowCloneFallback.csproj", "{69AF3C10-1BB1-4746-B697-B5A81D78C8D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StopClosingMyMenuOnWorldLoad", "StopClosingMyMenuOnWorldLoad\StopClosingMyMenuOnWorldLoad.csproj", "{9FA83514-13F8-412C-9790-C2B750E0E7E7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelativeSync", "RelativeSync\RelativeSync.csproj", "{B48C8F19-9451-4EE2-999F-82C0033CDE2C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptingSpoofer", "ScriptingSpoofer\ScriptingSpoofer.csproj", "{6B4396C7-B451-4FFD-87B6-3ED8377AC308}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaTTS", "LuaTTS\LuaTTS.csproj", "{24A069F4-4D69-4ABD-AA16-77765469245B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyPrune", "LazyPrune\LazyPrune.csproj", "{8FA6D481-5801-4E4C-822E-DE561155D22B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReconnectionSystemFix", "ReconnectionSystemFix\ReconnectionSystemFix.csproj", "{05C427DD-1261-4AAD-B316-A551FC126F2C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AASDefaultProfileFix", "AASDefaultProfileFix\AASDefaultProfileFix.csproj", "{C6794B18-E785-4F91-A517-3A2A8006E008}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginShift", "OriginShift\OriginShift.csproj", "{F381F604-9C16-4870-AD49-4BD7CA3F36DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrollFlight", "ScrollFlight\ScrollFlight.csproj", "{1B5D7DCB-01A4-4988-8B25-211948AEED76}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portals", "Portals\Portals.csproj", "{BE9629C2-8461-481C-B267-1B8A1805DCD7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropLoadingHexagon", "PropLoadingHexagon\PropLoadingHexagon.csproj", "{642A2BC7-C027-4F8F-969C-EF0F867936FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IKSimulatedRootAngleFix", "IKSimulatedRootAngleFix\IKSimulatedRootAngleFix.csproj", "{D11214B0-94FE-4008-8D1B-3DC8614466B3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropPropTweak", "DropPropTweak\DropPropTweak.csproj", "{2CC1F7C6-A953-4008-8C10-C7592EB401E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualCloneFix", "VisualCloneFix\VisualCloneFix.csproj", "{39915C4C-B555-4CB9-890F-26DE1388BC2E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractionTest", "InteractionTest\InteractionTest.csproj", "{7C675E64-0A2D-4B34-B6D1-5D6AA369A520}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", "KeepVelocityOnExitFlight\KeepVelocityOnExitFlight.csproj", "{0BB3D187-BBBA-4C58-B246-102342BE5E8C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartReticle", "SmartReticle\SmartReticle.csproj", "{3C992D0C-9729-438E-800C-496B7EFFFB25}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAmIPointing\WhereAmIPointing.csproj", "{E285BCC9-D953-4066-8FA2-97EA28EB348E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\SmootherRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.Build.0 = Release|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.Build.0 = Release|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.Build.0 = Release|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.Build.0 = Release|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.Build.0 = Release|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.Build.0 = Release|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.Build.0 = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.Build.0 = Release|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.Build.0 = Release|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.Build.0 = Release|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.Build.0 = Release|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.Build.0 = Release|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.Build.0 = Release|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.Build.0 = Release|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.Build.0 = Release|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.Build.0 = Release|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.Build.0 = Release|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.Build.0 = Release|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.Build.0 = Release|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.Build.0 = Release|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.Build.0 = Release|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.Build.0 = Release|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.Build.0 = Release|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.Build.0 = Release|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.Build.0 = Release|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.Build.0 = Release|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.Build.0 = Release|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.Build.0 = Release|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.Build.0 = Release|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.Build.0 = Release|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CD7DECEC-F4A0-4EEF-978B-72748414D52A} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CVRGizmos", "CVRGizmos\CVRGizmos.csproj", "{CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FuckToes", "FuckToes\FuckToes.csproj", "{79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GestureLock", "GestureLock\GestureLock.csproj", "{45A65AEB-4BFC-4E47-B181-BBB43BD81283}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathCamDisabler", "PathCamDisabler\PathCamDisabler.csproj", "{98169FD2-5CEB-46D1-A320-D7E06F82C9E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustment\FOVAdjustment.csproj", "{EE552804-30B1-49CF-BBDE-3B312895AFF7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatBoxExtensions", "ChatBoxExtensions\ChatBoxExtensions.csproj", "{0E1DD746-33A1-4179-AE70-8FB83AC40ABC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysicsGunMod", "PhysicsGunMod\PhysicsGunMod.csproj", "{F94DDB73-9041-4F5C-AD43-6960701E8417}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadowCloneFallback", "ShadowCloneFallback\ShadowCloneFallback.csproj", "{69AF3C10-1BB1-4746-B697-B5A81D78C8D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StopClosingMyMenuOnWorldLoad", "StopClosingMyMenuOnWorldLoad\StopClosingMyMenuOnWorldLoad.csproj", "{9FA83514-13F8-412C-9790-C2B750E0E7E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelativeSync", "RelativeSync\RelativeSync.csproj", "{B48C8F19-9451-4EE2-999F-82C0033CDE2C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptingSpoofer", "ScriptingSpoofer\ScriptingSpoofer.csproj", "{6B4396C7-B451-4FFD-87B6-3ED8377AC308}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaTTS", "LuaTTS\LuaTTS.csproj", "{24A069F4-4D69-4ABD-AA16-77765469245B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyPrune", "LazyPrune\LazyPrune.csproj", "{8FA6D481-5801-4E4C-822E-DE561155D22B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReconnectionSystemFix", "ReconnectionSystemFix\ReconnectionSystemFix.csproj", "{05C427DD-1261-4AAD-B316-A551FC126F2C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AASDefaultProfileFix", "AASDefaultProfileFix\AASDefaultProfileFix.csproj", "{C6794B18-E785-4F91-A517-3A2A8006E008}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginShift", "OriginShift\OriginShift.csproj", "{F381F604-9C16-4870-AD49-4BD7CA3F36DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrollFlight", "ScrollFlight\ScrollFlight.csproj", "{1B5D7DCB-01A4-4988-8B25-211948AEED76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portals", "Portals\Portals.csproj", "{BE9629C2-8461-481C-B267-1B8A1805DCD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropLoadingHexagon", "PropLoadingHexagon\PropLoadingHexagon.csproj", "{642A2BC7-C027-4F8F-969C-EF0F867936FD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IKSimulatedRootAngleFix", "IKSimulatedRootAngleFix\IKSimulatedRootAngleFix.csproj", "{D11214B0-94FE-4008-8D1B-3DC8614466B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropPropTweak", "DropPropTweak\DropPropTweak.csproj", "{2CC1F7C6-A953-4008-8C10-C7592EB401E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualCloneFix", "VisualCloneFix\VisualCloneFix.csproj", "{39915C4C-B555-4CB9-890F-26DE1388BC2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractionTest", "InteractionTest\InteractionTest.csproj", "{7C675E64-0A2D-4B34-B6D1-5D6AA369A520}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", "KeepVelocityOnExitFlight\KeepVelocityOnExitFlight.csproj", "{0BB3D187-BBBA-4C58-B246-102342BE5E8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartReticle", "SmartReticle\SmartReticle.csproj", "{3C992D0C-9729-438E-800C-496B7EFFFB25}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAmIPointing\WhereAmIPointing.csproj", "{E285BCC9-D953-4066-8FA2-97EA28EB348E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\SmootherRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.Build.0 = Release|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.Build.0 = Release|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.Build.0 = Release|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.Build.0 = Release|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.Build.0 = Release|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.Build.0 = Release|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.Build.0 = Release|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.Build.0 = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.Build.0 = Release|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.Build.0 = Release|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.Build.0 = Release|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.Build.0 = Release|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.Build.0 = Release|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.Build.0 = Release|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.Build.0 = Release|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.Build.0 = Release|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.Build.0 = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.Build.0 = Release|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.Build.0 = Release|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.Build.0 = Release|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.Build.0 = Release|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.Build.0 = Release|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.Build.0 = Release|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.Build.0 = Release|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.Build.0 = Release|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.Build.0 = Release|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.Build.0 = Release|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.Build.0 = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CD7DECEC-F4A0-4EEF-978B-72748414D52A} + EndGlobalSection +EndGlobal From dbc6341f9eb72b5e76a08dfe0a8c5897c7852555 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 21 Sep 2024 01:07:04 -0500 Subject: [PATCH 046/188] Stickers: cleanup --- .../BTKUI/UIAddon.Category.StickersMod.cs | 52 ++++++++---------- Stickers/Integrations/BTKUI/UIAddon.Main.cs | 43 ++++++++------- Stickers/Main.cs | 26 ++++----- Stickers/Patches.cs | 10 ++-- ...abled.png => Stickers-puzzle-disabled.png} | Bin Stickers/Stickers.csproj | 4 ++ .../Networking/ModNetwork.Constants.cs | 9 +-- .../Stickers/Networking/ModNetwork.Inbound.cs | 3 +- Stickers/Stickers/StickerData.cs | 39 ++++++------- Stickers/Stickers/StickerSystem.Main.cs | 30 ++++------ .../StickerSystem.StickerLifecycle.cs | 13 +++-- Stickers/format.json | 10 ++-- 12 files changed, 113 insertions(+), 126 deletions(-) rename Stickers/Resources/Gohsantosadrive_Icons/{Stickers-puzzle-Disabled.png => Stickers-puzzle-disabled.png} (100%) diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs index 0dfb059..0e9c0c6 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs @@ -8,6 +8,8 @@ namespace NAK.Stickers.Integrations; public static partial class BTKUIAddon { private static Category _ourCategory; + + private static Button _placeStickersButton; private static readonly MultiSelection _sfxSelection = MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_SelectedSFX); @@ -17,17 +19,15 @@ public static partial class BTKUIAddon private static readonly MultiSelection _tabDoubleClickSelection = MultiSelection.CreateMultiSelectionFromMelonPref(ModSettings.Entry_TabDoubleClick); - - public static Button placeStickersButton; - + #region Category Setup private static void Setup_StickersModCategory() { _ourCategory = _rootPage.AddMelonCategory(ModSettings.Hidden_Foldout_SettingsCategory); - placeStickersButton = _ourCategory.AddButton("Place Stickers", "Stickers-magic-wand", "Place stickers via raycast.", ButtonStyle.TextWithIcon); - placeStickersButton.OnPress += OnPlaceStickersButtonClick; + _placeStickersButton = _ourCategory.AddButton("Place Stickers", "Stickers-magic-wand", "Place stickers via raycast.", ButtonStyle.TextWithIcon); + _placeStickersButton.OnPress += OnPlaceStickersButtonClick; Button clearSelfStickersButton = _ourCategory.AddButton("Clear Self", "Stickers-eraser", "Clear own stickers.", ButtonStyle.TextWithIcon); clearSelfStickersButton.OnPress += OnClearSelfStickersButtonClick; @@ -62,16 +62,15 @@ public static partial class BTKUIAddon { if (!_isOurTabOpened) return; - if (StickerSystem.RestrictedInstance == false) - { - string mode = StickerSystem.Instance.IsInStickerMode ? "Exiting" : "Entering"; - QuickMenuAPI.ShowAlertToast($"{mode} sticker placement mode...", 2); - StickerSystem.Instance.IsInStickerMode = !StickerSystem.Instance.IsInStickerMode; - } - else + if (StickerSystem.Instance.IsRestrictedInstance) { QuickMenuAPI.ShowAlertToast("Stickers are not allowed in this world!", 2); + return; } + + string mode = StickerSystem.Instance.IsInStickerMode ? "Exiting" : "Entering"; + QuickMenuAPI.ShowAlertToast($"{mode} sticker placement mode...", 2); + StickerSystem.Instance.IsInStickerMode = !StickerSystem.Instance.IsInStickerMode; } private static void OnClearSelfStickersButtonClick() @@ -95,28 +94,25 @@ public static partial class BTKUIAddon StickerSystem.OpenStickersFolder(); } - public static void UpdateStickerMenu() //TODO: add Icon changing, Bono needs to expose the value first. + public static void OnStickerRestrictionUpdated(bool isRestricted = false) //TODO: add Icon changing, Bono needs to expose the value first. { - if (StickerSystem.RestrictedInstance == true) + if (isRestricted) { _rootPage.MenuSubtitle = "Stickers... are sadly disabled in this world."; - placeStickersButton.Disabled = true; - placeStickersButton.ButtonText = "Stickers Disabled"; - placeStickersButton.ButtonTooltip = "This world is not allowing Stickers."; - placeStickersButton.ButtonIcon = "Stickers-magic-wand-broken"; - - } - else - { - _rootPage.MenuSubtitle = "Stickers! Double-click the tab to quickly toggle Sticker Mode."; - - placeStickersButton.Disabled = false; - placeStickersButton.ButtonText = "Place Stickers"; - placeStickersButton.ButtonTooltip = "Place stickers via raycast."; - placeStickersButton.ButtonIcon = "Stickers-magic-wand"; + _placeStickersButton.Disabled = true; + _placeStickersButton.ButtonText = "Stickers Disabled"; + _placeStickersButton.ButtonTooltip = "This world is not allowing Stickers."; + _placeStickersButton.ButtonIcon = "Stickers-magic-wand-broken"; + return; } + _rootPage.MenuSubtitle = "Stickers! Double-click the tab to quickly toggle Sticker Mode."; + + _placeStickersButton.Disabled = false; + _placeStickersButton.ButtonText = "Place Stickers"; + _placeStickersButton.ButtonTooltip = "Place stickers via raycast."; + _placeStickersButton.ButtonIcon = "Stickers-magic-wand"; } #endregion Button Actions diff --git a/Stickers/Integrations/BTKUI/UIAddon.Main.cs b/Stickers/Integrations/BTKUI/UIAddon.Main.cs index e741432..327949b 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Main.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Main.cs @@ -22,30 +22,36 @@ public static partial class BTKUIAddon } #region Setup - + private static void Setup_Icons() { + Assembly assembly = Assembly.GetExecutingAssembly(); + string assemblyName = assembly.GetName().Name; + // All icons used - https://www.flaticon.com/authors/gohsantosadrive - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-alphabet.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-eraser.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-folder.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-headset.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magic-wand.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand-broken", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-magic-wand-broken.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-mouse.png")); - //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", UIUtils.GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-puzzle.png")); - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle-disabled", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-puzzle-disabled.png")); //Disabled Sticker Puzzle - QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", Assembly.GetExecutingAssembly().GetManifestResourceStream("Stickers.Resources.Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-alphabet", GetIconStream("Gohsantosadrive_Icons.Stickers-alphabet.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-eraser", GetIconStream("Gohsantosadrive_Icons.Stickers-eraser.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-folder", GetIconStream("Gohsantosadrive_Icons.Stickers-folder.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-headset", GetIconStream("Gohsantosadrive_Icons.Stickers-headset.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magnifying-glass", GetIconStream("Gohsantosadrive_Icons.Stickers-magnifying-glass.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand", GetIconStream("Gohsantosadrive_Icons.Stickers-magic-wand.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-magic-wand-broken", GetIconStream("Gohsantosadrive_Icons.Stickers-magic-wand-broken.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-mouse", GetIconStream("Gohsantosadrive_Icons.Stickers-mouse.png")); + //QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-pencil", GetIconStream("Gohsantosadrive_Icons.Stickers-pencil.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle", GetIconStream("Gohsantosadrive_Icons.Stickers-puzzle.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-puzzle-disabled", GetIconStream("Gohsantosadrive_Icons.Stickers-puzzle-disabled.png")); // disabled Sticker Puzzle + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "Stickers-rubbish-bin", GetIconStream("Gohsantosadrive_Icons.Stickers-rubbish-bin.png")); + + return; + Stream GetIconStream(string iconName) => assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}"); } - + private static void Setup_StickerModTab() { - _rootPage = new Page(ModSettings.ModName, ModSettings.SM_SettingsCategory, true, "Stickers-Puzzle") //Sticker Icon will be left blank as it is updated on world join, AFTER Icon value is exposed.. + _rootPage = new Page(ModSettings.ModName, ModSettings.SM_SettingsCategory, true, "Stickers-Puzzle") // sticker icon will be left blank as it is updated on world join, AFTER Icon value is exposed.. { MenuTitle = ModSettings.SM_SettingsCategory, - MenuSubtitle = "", //Left this blank as it is defined when the world loads + MenuSubtitle = "", // left this blank as it is defined when the world loads }; _rootPageElementID = _rootPage.ElementID; @@ -91,10 +97,7 @@ public static partial class BTKUIAddon { default: case TabDoubleClick.ToggleStickerMode: - if (StickerSystem.RestrictedInstance == false) - { - OnPlaceStickersButtonClick(); - } + OnPlaceStickersButtonClick(); break; case TabDoubleClick.ClearAllStickers: OnClearAllStickersButtonClick(); diff --git a/Stickers/Main.cs b/Stickers/Main.cs index 87e6824..80ebd6b 100644 --- a/Stickers/Main.cs +++ b/Stickers/Main.cs @@ -23,10 +23,10 @@ public class StickerMod : MelonMod ModSettings.Initialize(); StickerSystem.Initialize(); - ApplyPatches(typeof(Patches.PlayerSetupPatches)); - ApplyPatches(typeof(Patches.ControllerRayPatches)); - ApplyPatches(typeof(Patches.ShaderFilterHelperPatches)); - ApplyPatches(typeof(Patches.CVRToolsPatches)); + ApplyPatches(typeof(Patches.PlayerSetup_Patches)); + ApplyPatches(typeof(Patches.ControllerRay_Patches)); + ApplyPatches(typeof(Patches.ShaderFilterHelper_Patches)); + ApplyPatches(typeof(Patches.CVRTools_Patches)); LoadAssetBundle(); @@ -38,17 +38,10 @@ public class StickerMod : MelonMod if (StickerSystem.Instance == null) return; - if (!MetaPort.Instance.isUsingVr - && StickerSystem.Instance.IsInStickerMode) - { - if (Input.mouseScrollDelta.y != 0f - && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus - && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom - { - StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; - } - StickerSystem.Instance.PlaceStickerFromControllerRay(PlayerSetup.Instance.activeCam.transform, CVRHand.Left, true); - } + if (Input.mouseScrollDelta.y != 0f + && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus + && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom + StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; StickerSystem.Instance.UpdateStickerPreview(); // flashy flash @@ -57,6 +50,9 @@ public class StickerMod : MelonMod if (!Input.GetKeyDown((KeyCode)ModSettings.Entry_PlaceBinding.Value)) return; + + if (CVRInputManager.Instance.textInputFocused) + return; // prevent placing stickers while typing StickerSystem.Instance.PlaceStickerFromControllerRay(PlayerSetup.Instance.activeCam.transform); } diff --git a/Stickers/Patches.cs b/Stickers/Patches.cs index 4c9ff63..0bb5921 100644 --- a/Stickers/Patches.cs +++ b/Stickers/Patches.cs @@ -8,7 +8,7 @@ using UnityEngine; namespace NAK.Stickers.Patches; -internal static class PlayerSetupPatches +internal static class PlayerSetup_Patches { [HarmonyPostfix] [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.GetCurrentPropSelectionMode))] @@ -18,7 +18,7 @@ internal static class PlayerSetupPatches } } -internal static class ControllerRayPatches +internal static class ControllerRay_Patches { [HarmonyPrefix] [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.HandlePropSpawn))] @@ -30,14 +30,14 @@ internal static class ControllerRayPatches StickerSystem.Instance.PlaceStickerFromControllerRay(__instance.rayDirectionTransform, __instance.hand, true); // preview if (__instance._gripDown) StickerSystem.Instance.IsInStickerMode = false; - if (__instance._hitUIInternal || !__instance._interactDown) + if (__instance._hitUIInternal || !__instance._interactDown) return; StickerSystem.Instance.PlaceStickerFromControllerRay(__instance.rayDirectionTransform, __instance.hand); } } -internal static class ShaderFilterHelperPatches +internal static class ShaderFilterHelper_Patches { [HarmonyPrefix] [HarmonyPatch(typeof(ShaderFilterHelper), nameof(ShaderFilterHelper.SetupFilter))] @@ -51,7 +51,7 @@ internal static class ShaderFilterHelperPatches } } -internal static class CVRToolsPatches +internal static class CVRTools_Patches { [HarmonyPrefix] [HarmonyPatch(typeof(CVRTools), nameof(CVRTools.ReplaceShaders), typeof(Material), typeof(string))] diff --git a/Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-Disabled.png b/Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-disabled.png similarity index 100% rename from Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-Disabled.png rename to Stickers/Resources/Gohsantosadrive_Icons/Stickers-puzzle-disabled.png diff --git a/Stickers/Stickers.csproj b/Stickers/Stickers.csproj index de4a315..e4f12ab 100644 --- a/Stickers/Stickers.csproj +++ b/Stickers/Stickers.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/Stickers/Stickers/Networking/ModNetwork.Constants.cs b/Stickers/Stickers/Networking/ModNetwork.Constants.cs index 0f24904..5cc7a16 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Constants.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Constants.cs @@ -1,15 +1,12 @@ -using ABI_RC.Core.Util.AnimatorManager; -using NAK.Stickers.Properties; - -namespace NAK.Stickers.Networking; +namespace NAK.Stickers.Networking; public static partial class ModNetwork { #region Constants internal const int MaxTextureSize = 1024 * 256; // 256KB - - private const string NetworkVersion = "1.0.2"; // change each time network protocol changes + + private const string NetworkVersion = "1.0.3"; // change each time network protocol changes private const string ModId = $"MelonMod.NAK.Stickers_v{NetworkVersion}"; private const int ChunkSize = 1024; // roughly 1KB per ModNetworkMessage private const int MaxChunkCount = MaxTextureSize / ChunkSize; diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index ad04daf..e769006 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -43,7 +43,8 @@ public static partial class ModNetwork if (ModSettings.Entry_FriendsOnly.Value && !Friends.FriendsWith(sender)) return false; // ignore messages from non-friends if friends only is enabled - if (StickerSystem.RestrictedInstance == true) // ignore messages from users when the world is restricted. This also includes older or modified version of Stickers mod. + + if (StickerSystem.Instance.IsRestrictedInstance) // ignore messages from users when the world is restricted. This also includes older or modified version of Stickers mod. return false; return true; diff --git a/Stickers/Stickers/StickerData.cs b/Stickers/Stickers/StickerData.cs index 4b4aaa9..2ca96bd 100644 --- a/Stickers/Stickers/StickerData.cs +++ b/Stickers/Stickers/StickerData.cs @@ -69,16 +69,16 @@ namespace NAK.Stickers } } - public Guid GetTextureHash(int spawnerIndex = 0) - { - if (spawnerIndex < 0 || spawnerIndex >= _decalSpawners.Length) - { - StickerMod.Logger.Warning("Invalid spawner index!"); - return Guid.Empty; - } - - return _textureHashes[spawnerIndex]; - } + // public Guid GetTextureHash(int spawnerIndex = 0) + // { + // if (spawnerIndex < 0 || spawnerIndex >= _decalSpawners.Length) + // { + // StickerMod.Logger.Warning("Invalid spawner index!"); + // return Guid.Empty; + // } + // + // return _textureHashes[spawnerIndex]; + // } public bool CheckHasTextureHash(Guid textureHash) { @@ -132,7 +132,7 @@ namespace NAK.Stickers Transform rootObject = null; GameObject hitGO = hit.transform.gameObject; if (hitGO.scene.buildIndex == 4 // additive (dynamic) content - || hitGO.TryGetComponent(out Animator _) // potentially movable + || hitGO.GetComponentInParent() != null // potentially movable || hitGO.GetComponentInParent() != null) // movable rootObject = hitGO.transform; @@ -244,21 +244,18 @@ namespace NAK.Stickers if (_previewDecalSpawner == null) return; // uh fuck - // clear previous - ClearPreview(); - // place at hit pos Transform rootObject = null; GameObject hitGO = hit.transform.gameObject; - if (hitGO.scene.buildIndex == 4 || hitGO.TryGetComponent(out Animator _) || hitGO.GetComponentInParent() != null) + if (hitGO.scene.buildIndex == 4 // additive (dynamic) content + || hitGO.GetComponentInParent() != null // potentially movable + || hitGO.GetComponentInParent() != null) // movable rootObject = hitGO.transform; - Vector3 position = hit.point; - _previewDecalSpawner.AddDecal(position, - Quaternion.LookRotation(forwardDirection, upDirection), - hitGO, - DECAL_SIZE, DECAL_SIZE, 1f, 1f, 0f, - rootObject); + _previewDecalSpawner.AddDecal( + hit.point, Quaternion.LookRotation(forwardDirection, upDirection), + hitGO, + DECAL_SIZE, DECAL_SIZE, 1f, 1f, 0f, rootObject); } public void UpdatePreview(int spawnerIndex) diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index 0a5a4ef..9cd8fcf 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -16,9 +16,7 @@ namespace NAK.Stickers; public partial class StickerSystem { #region Singleton - - public static bool RestrictedInstance = false; - + public static StickerSystem Instance { get; private set; } public static void Initialize() @@ -45,8 +43,9 @@ public partial class StickerSystem private void OnPlayerSetupStart() { + // TODO: this can be spammed by world author toggling CVRWorld.enabled state + CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); - CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); @@ -61,30 +60,21 @@ public partial class StickerSystem private void OnInitialConnection() { - OnWorldLoad(); //Checks the world again in case the bundle updated. ClearStickersSelf(); // clear stickers on remotes just in case we rejoined ModNetwork.Reset(); // reset network buffers and metadata } private void OnWorldLoad() { - GameObject StickerWorldRestriction = GameObject.Find("[DisableStickers]"); - if (StickerWorldRestriction != null) - { - RestrictedInstance = true; - MelonLogger.Msg("This is a Restricted Instance"); - } - else - { - MelonLogger.Msg("This is NOT a Restricted Instance"); - } - BTKUIAddon.UpdateStickerMenu(); + IsRestrictedInstance = GameObject.Find("[DisableStickers]") != null; + if (IsRestrictedInstance) StickerMod.Logger.Msg("Stickers are restricted by the world author."); + BTKUIAddon.OnStickerRestrictionUpdated(IsRestrictedInstance); } private void OnWorldUnload() { - RestrictedInstance = false; - CleanupAllButSelf(); // release all stickers except for self + IsRestrictedInstance = false; + CleanupAllButSelf(); } #endregion Game Events @@ -107,6 +97,8 @@ public partial class StickerSystem // } // } + public bool IsRestrictedInstance { get; internal set; } + private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; private const float StickerKillTime = 30f; @@ -131,7 +123,7 @@ public partial class StickerSystem get => _isInStickerMode; set { - _isInStickerMode = value; + _isInStickerMode = value && !IsRestrictedInstance; // ensure cannot enter when restricted if (_isInStickerMode) { CohtmlHud.Instance.SelectPropToSpawn( diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index 821aedc..df4ee96 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -51,7 +51,7 @@ public partial class StickerSystem private bool PlaceStickerSelf(Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true) { - if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot, RestrictedInstance)) + if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot)) return false; // failed // placed, now network @@ -59,8 +59,12 @@ public partial class StickerSystem return true; } - private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool RestrictedInstance = false, bool isPreview = false) + private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool isPreview = false) { + // if the world contained a gameobject with the [DisableStickers] name and restricted the instance disable stickers! + if (IsRestrictedInstance) + return false; + StickerData stickerData = GetOrCreateStickerData(playerId); if (Time.time - stickerData.LastPlacedTime < StickerCooldown) return false; @@ -75,10 +79,6 @@ public partial class StickerSystem if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) return false; - // if the world contained a gameobject with the [DisableStickers] name and restricted the instance disable stickers! - if (RestrictedInstance == true) - return false; - if (isPreview) { stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); @@ -181,6 +181,7 @@ public partial class StickerSystem if (!IsInStickerMode) return; StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); // clear prior frames sticker preview localStickerData.UpdatePreview(SelectedStickerSlot); } diff --git a/Stickers/format.json b/Stickers/format.json index 8eb9979..b25086c 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -1,11 +1,11 @@ { "_id": 232, "name": "Stickers", - "modversion": "1.0.6", - "gameversion": "2024r175", + "modversion": "1.0.8", + "gameversion": "2024r177", "loaderversion": "0.6.1", "modtype": "Mod", - "author": "NotAKidoS", + "author": "NotAKidoS, SketchFoxsky", "description": "Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network.\n\nLimitations:\n- Image should be under 256KB in size.\n- Image dimensions should be a power of 2 (e.g. 512x512, 1024x1024).\n - If the image exceeds the size limit or is not a power of 2 the mod will automatically resize it.\n - The automatic resizing may result in loss of quality (or may just fail), so it is recommended to resize the image yourself before placing it in the `UserData/Stickers/` folder.\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/Stickers/README.md).", "searchtags": [ "stickers", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/Stickers.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r41/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Added Friends Only setting.\n- Added button to clear sticker thumbnail cache.\n- Added Identify button to Player Selection page.\n- Added `[NoSticker]` GameObject name check. \n- Adjusted inbound network buffers to be cleared on initial connection to an instance.\n- Adjusted selecting a new image for a sticker slot to clear stickers in-scene for that slot.\n- Stripped all unused classes a bunch of other methods from decalery.\n - Completely removed Skinned Mesh Renderer support as it required running on CPU.\n - Most uploaded content is not marked as readable anyways (plus it crashed consistantly).\n- Fixed nullref spam when clearing stickers when sticker was already marked as dead.\n- Fixed issue where saving melon preferences would error due to null sticker selection.", + "changelog": "- Added world restriction via `[DisableStickers]` GameObject (thx Sketch).\n- Added sticker placement preview.\n- Fixed stickers being hit by VR switch shader replacement.\n- Fixed Desktop Sticker placement bind firing when a text field was focused.", "embedcolor": "#f61963" } \ No newline at end of file From 379be57b846c7a20b9b66d934cf8ccaa0a2f3691 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 21 Sep 2024 01:21:15 -0500 Subject: [PATCH 047/188] Stickers: update format.json --- Stickers/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stickers/format.json b/Stickers/format.json index b25086c..5c28daa 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r41/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Added world restriction via `[DisableStickers]` GameObject (thx Sketch).\n- Added sticker placement preview.\n- Fixed stickers being hit by VR switch shader replacement.\n- Fixed Desktop Sticker placement bind firing when a text field was focused.", + "changelog": "- Added world restriction via `[DisableStickers]` GameObject (thx Sketch).\n- Added sticker placement preview.\n- Fixed stickers being hit by VR switch shader replacement.\n- Fixed Desktop Sticker placement bind firing when a text field was focused.\n- **This version is not backwards compatible with previous versions over network.**", "embedcolor": "#f61963" } \ No newline at end of file From d0c8298074c4dcfc089ccb34ed8b8bd7e0b9cedf Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:56:10 -0500 Subject: [PATCH 048/188] SearchWithSpacesFix: initial release --- SearchWithSpacesFix/Main.cs | 26 +++++++++++++++ .../Properties/AssemblyInfo.cs | 32 +++++++++++++++++++ SearchWithSpacesFix/README.md | 14 ++++++++ .../SearchWithSpacesFix.csproj | 2 ++ SearchWithSpacesFix/format.json | 23 +++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 SearchWithSpacesFix/Main.cs create mode 100644 SearchWithSpacesFix/Properties/AssemblyInfo.cs create mode 100644 SearchWithSpacesFix/README.md create mode 100644 SearchWithSpacesFix/SearchWithSpacesFix.csproj create mode 100644 SearchWithSpacesFix/format.json diff --git a/SearchWithSpacesFix/Main.cs b/SearchWithSpacesFix/Main.cs new file mode 100644 index 0000000..8de7baa --- /dev/null +++ b/SearchWithSpacesFix/Main.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using ABI_RC.Core.InteractionSystem; +using HarmonyLib; +using MelonLoader; + +namespace NAK.SearchWithSpacesFix; + +public class SearchWithSpacesFixMod : MelonMod +{ + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(ViewManager).GetMethod(nameof(ViewManager.GetSearchResults), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(SearchWithSpacesFixMod).GetMethod(nameof(OnPreGetSearchResults), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + // this is so crazy + + private static void OnPreGetSearchResults(ref string searchTerm) + => searchTerm = searchTerm.Replace(" ", "_"); + + // this is so crazy +} \ No newline at end of file diff --git a/SearchWithSpacesFix/Properties/AssemblyInfo.cs b/SearchWithSpacesFix/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8ba0f9a --- /dev/null +++ b/SearchWithSpacesFix/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.SearchWithSpacesFix.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.SearchWithSpacesFix))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.SearchWithSpacesFix))] + +[assembly: MelonInfo( + typeof(NAK.SearchWithSpacesFix.SearchWithSpacesFixMod), + nameof(NAK.SearchWithSpacesFix), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.SearchWithSpacesFix.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/SearchWithSpacesFix/README.md b/SearchWithSpacesFix/README.md new file mode 100644 index 0000000..1c4c5bc --- /dev/null +++ b/SearchWithSpacesFix/README.md @@ -0,0 +1,14 @@ +# SearchWithSpacesFix + +Fixes search terms that use spaces. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/SearchWithSpacesFix/SearchWithSpacesFix.csproj b/SearchWithSpacesFix/SearchWithSpacesFix.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/SearchWithSpacesFix/SearchWithSpacesFix.csproj @@ -0,0 +1,2 @@ + + diff --git a/SearchWithSpacesFix/format.json b/SearchWithSpacesFix/format.json new file mode 100644 index 0000000..f8950ca --- /dev/null +++ b/SearchWithSpacesFix/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "SearchWithSpacesFix", + "modversion": "1.0.0", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes search terms that include spaces.", + "searchtags": [ + "search", + "spaces", + "fix", + "meow" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file From 04c4a4590dcf5c3e434232ef01b53650f3d6f242 Mon Sep 17 00:00:00 2001 From: SurprisinglySuspicious Date: Wed, 20 Nov 2024 16:07:52 -0500 Subject: [PATCH 049/188] Issue: If NStrip wasn't found in folder or PATH, it tries to show a message to user, but it doesn't wait for key input, so it just immediately closes. --- copy_and_nstrip_dll.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/copy_and_nstrip_dll.ps1 b/copy_and_nstrip_dll.ps1 index aa4cb97..d4b95db 100644 --- a/copy_and_nstrip_dll.ps1 +++ b/copy_and_nstrip_dll.ps1 @@ -139,7 +139,7 @@ if ($missingMods.Count -gt 0) { Write-Host "" Write-Host "Copied all libraries!" Write-Host "" -Write-Host "Press any key to strip the Dlls using NStrip" +Write-Host "Press any key to strip the DLLs using NStrip..." $HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL $HOST.UI.RawUI.Flushinputbuffer() @@ -156,6 +156,10 @@ else { # Display an error message if NStrip.exe could not be found Write-Host "Could not find NStrip.exe in the current directory nor in the PATH." -ForegroundColor Red Write-Host "Visit https://github.com/bbepis/NStrip/releases/latest to grab a copy." -ForegroundColor Red + Write-Host "" + Write-Host "Press any key to exit..." + $HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL + $HOST.UI.RawUI.Flushinputbuffer() return } } @@ -169,6 +173,6 @@ foreach($dllFile in $dllsToStrip) { Write-Host "" Write-Host "Copied all libraries and stripped the DLLs!" Write-Host "" -Write-Host "Press any key to exit" +Write-Host "Press any key to exit..." $HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL $HOST.UI.RawUI.Flushinputbuffer() From 70a54b632c819a75805851a44de4edd512fb3fab Mon Sep 17 00:00:00 2001 From: SurprisinglySuspicious <100347264+SurprisinglySuspicious@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:59:19 -0500 Subject: [PATCH 050/188] Add it for PATH too --- copy_and_nstrip_dll.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/copy_and_nstrip_dll.ps1 b/copy_and_nstrip_dll.ps1 index d4b95db..0f2c7f3 100644 --- a/copy_and_nstrip_dll.ps1 +++ b/copy_and_nstrip_dll.ps1 @@ -35,6 +35,10 @@ else { else { Write-Host "[ERROR] ChilloutVR.exe not found in CVRPATH or the default Steam location." Write-Host " Please define the Environment Variable CVRPATH pointing to the ChilloutVR folder!" + Write-Host "" + Write-Host "Press any key to exit..." + $HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL + $HOST.UI.RawUI.Flushinputbuffer() return } } From db07d539716d76dfcaabd5cfd144aa9aec87be15 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:04:47 -0600 Subject: [PATCH 051/188] ASTExtension: Fix for latest nightlies --- .../Extensions/PlayerSetupExtensions.cs | 5 +-- ASTExtension/Integrations/BTKUI/BtkUiAddon.cs | 31 +++++++++---------- .../Integrations/BTKUI/BtkUiAddon_Utils.cs | 15 +++++---- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/ASTExtension/Extensions/PlayerSetupExtensions.cs b/ASTExtension/Extensions/PlayerSetupExtensions.cs index 241cd1e..7fa9e5b 100644 --- a/ASTExtension/Extensions/PlayerSetupExtensions.cs +++ b/ASTExtension/Extensions/PlayerSetupExtensions.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.Player; +using ABI_RC.Core; +using ABI_RC.Core.Player; using UnityEngine; namespace NAK.ASTExtension.Extensions; @@ -17,7 +18,7 @@ public static class PlayerSetupExtensions Vector3 localScale = playerSetup._avatar.transform.localScale; Vector3 initialScale = playerSetup.initialScale; float initialHeight = playerSetup._initialAvatarHeight; - Vector3 scaleDifference = PlayerSetup.DivideVectors(localScale - initialScale, initialScale); + Vector3 scaleDifference = CVRTools.DivideVectors(localScale - initialScale, initialScale); return initialHeight + initialHeight * scaleDifference.y; } } \ No newline at end of file diff --git a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs index 8c57cb7..cbb7534 100644 --- a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs +++ b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs @@ -3,41 +3,41 @@ using BTKUILib; using BTKUILib.UIObjects; using BTKUILib.UIObjects.Components; -namespace NAK.ASTExtension.Integrations +namespace NAK.ASTExtension.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon + public static void Initialize() { - public static void Initialize() - { Prepare_Icons(); Setup_PlayerSelectPage(); } - private static void Prepare_Icons() - { + private static void Prepare_Icons() + { QuickMenuAPI.PrepareIcon(ASTExtensionMod.ModName, "ASM_Icon_AvatarHeightCopy", GetIconStream("ASM_Icon_AvatarHeightCopy.png")); } - #region Player Select Page + #region Player Select Page - private static string _selectedPlayer; + private static string _selectedPlayer; - private static void Setup_PlayerSelectPage() - { + private static void Setup_PlayerSelectPage() + { QuickMenuAPI.OnPlayerSelected += OnPlayerSelected; Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ASTExtensionMod.ModName, ASTExtensionMod.ModName); Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height."); button.OnPress += OnCopyPlayerHeight; } - private static void OnPlayerSelected(string _, string id) - { + private static void OnPlayerSelected(string _, string id) + { _selectedPlayer = id; } - private static void OnCopyPlayerHeight() - { + private static void OnCopyPlayerHeight() + { if (string.IsNullOrEmpty(_selectedPlayer)) return; @@ -51,6 +51,5 @@ namespace NAK.ASTExtension.Integrations ASTExtensionMod.Instance.SetAvatarHeight(height); } - #endregion Player Select Page - } + #endregion Player Select Page } \ No newline at end of file diff --git a/ASTExtension/Integrations/BTKUI/BtkUiAddon_Utils.cs b/ASTExtension/Integrations/BTKUI/BtkUiAddon_Utils.cs index 3dd1c30..7b3f569 100644 --- a/ASTExtension/Integrations/BTKUI/BtkUiAddon_Utils.cs +++ b/ASTExtension/Integrations/BTKUI/BtkUiAddon_Utils.cs @@ -1,18 +1,17 @@ using System.Reflection; -namespace NAK.ASTExtension.Integrations +namespace NAK.ASTExtension.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon - { - #region Icon Utils + #region Icon Utils - private static Stream GetIconStream(string iconName) - { + private static Stream GetIconStream(string iconName) + { Assembly assembly = Assembly.GetExecutingAssembly(); string assemblyName = assembly.GetName().Name; return assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}"); } - #endregion Icon Utils - } + #endregion Icon Utils } \ No newline at end of file From fe768029eb5666340ad035066b1223e5e378f7d2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:05:34 -0600 Subject: [PATCH 052/188] LuaNetworkVariables: idk --- .../LuaNetworkVariables.csproj | 6 + LuaNetworkVariables/Main.cs | 58 ++++ LuaNetworkVariables/NetLuaModule.cs | 137 ++++++++ .../NetworkVariables/LuaEventContext.cs | 41 +++ .../NetworkVariables/LuaEventTracker.cs | 60 ++++ .../LuaNetVarController.Base.cs | 146 ++++++++ .../LuaNetVarController.Networking.cs | 214 ++++++++++++ .../LuaNetVarController.Registration.cs | 101 ++++++ .../LuaNetVarController.Serialization.cs | 62 ++++ .../LuaNetVarController.Utility.cs | 57 +++ LuaNetworkVariables/Patches.cs | 65 ++++ .../Properties/AssemblyInfo.cs | 32 ++ LuaNetworkVariables/README.md | 14 + .../SyncedBehaviour/MNSyncedBehaviour.cs | 326 ++++++++++++++++++ .../SyncedBehaviour/PickupableBehaviour.cs | 161 +++++++++ .../SyncedBehaviour/PickupableObject.cs | 72 ++++ .../SyncedBehaviour/TestSyncedBehaviour.cs | 63 ++++ .../SyncedBehaviour/TestSyncedObject.cs | 42 +++ LuaNetworkVariables/format.json | 23 ++ 19 files changed, 1680 insertions(+) create mode 100644 LuaNetworkVariables/LuaNetworkVariables.csproj create mode 100644 LuaNetworkVariables/Main.cs create mode 100644 LuaNetworkVariables/NetLuaModule.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaEventContext.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs create mode 100644 LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs create mode 100644 LuaNetworkVariables/Patches.cs create mode 100644 LuaNetworkVariables/Properties/AssemblyInfo.cs create mode 100644 LuaNetworkVariables/README.md create mode 100644 LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs create mode 100644 LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs create mode 100644 LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs create mode 100644 LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs create mode 100644 LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs create mode 100644 LuaNetworkVariables/format.json diff --git a/LuaNetworkVariables/LuaNetworkVariables.csproj b/LuaNetworkVariables/LuaNetworkVariables.csproj new file mode 100644 index 0000000..728edb7 --- /dev/null +++ b/LuaNetworkVariables/LuaNetworkVariables.csproj @@ -0,0 +1,6 @@ + + + + net48 + + diff --git a/LuaNetworkVariables/Main.cs b/LuaNetworkVariables/Main.cs new file mode 100644 index 0000000..d8c5def --- /dev/null +++ b/LuaNetworkVariables/Main.cs @@ -0,0 +1,58 @@ +using ABI_RC.Core.Player; +using MelonLoader; +using UnityEngine; + +namespace NAK.LuaNetVars; + +public class LuaNetVarsMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + #region Melon Preferences + + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ApplyPatches(typeof(Patches.LuaScriptFactory_Patches)); + ApplyPatches(typeof(Patches.CVRSyncHelper_Patches)); + } + + public override void OnUpdate() + { + // if (Input.GetKeyDown(KeyCode.F1)) + // { + // PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0"); + // } + // + // if (Input.GetKeyDown(KeyCode.F2)) + // { + // GameObject go = new("TestSyncedObject"); + // go.AddComponent(); + // } + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetLuaModule.cs b/LuaNetworkVariables/NetLuaModule.cs new file mode 100644 index 0000000..319fefa --- /dev/null +++ b/LuaNetworkVariables/NetLuaModule.cs @@ -0,0 +1,137 @@ +using ABI_RC.Core.Base; +using ABI.Scripting.CVRSTL.Common; +using JetBrains.Annotations; +using NAK.LuaNetVars; +using MoonSharp.Interpreter; + +namespace NAK.LuaNetVars.Modules; + +[PublicAPI] // Indicates that this class is used and should not be considered unused +public class LuaNetModule : BaseScriptedStaticWrapper +{ + public const string MODULE_ID = "NetworkModule"; + + private LuaNetVarController _controller; + + public LuaNetModule(CVRLuaContext context) : base(context) + { + _controller = context.behaviour.AddComponentIfMissing(); + } + + internal static object RegisterUserData(Script script, CVRLuaContext context) + { + // Register the LuaNetModule type with MoonSharp + UserData.RegisterType(InteropAccessMode.Default, MODULE_ID); + return new LuaNetModule(context); + } + + #region Module Instance Methods + + ///

+ /// Registers a network variable that can be synchronized over the network. + /// + /// The name of the variable to register. + public void RegisterNetworkVar(string varName) + { + CheckIfCanAccessMethod(nameof(RegisterNetworkVar), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return; + } + + _controller.RegisterNetworkVar(varName); + } + + /// + /// Registers a callback function to be called when the specified network variable changes. + /// + /// The name of the variable to watch. + /// The Lua function to call when the variable changes. + public void RegisterNotifyCallback(string varName, DynValue callback) + { + CheckIfCanAccessMethod(nameof(RegisterNotifyCallback), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return; + } + + _controller.RegisterNotifyCallback(varName, callback); + } + + /// + /// Registers a callback function to be called when the specified event is received. + /// + /// The name of the event to watch. + /// The Lua function to call when the event occurs. + public void RegisterEventCallback(string eventName, DynValue callback) + { + CheckIfCanAccessMethod(nameof(RegisterEventCallback), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return; + } + + _controller.RegisterEventCallback(eventName, callback); + } + + /// + /// Sends a Lua event to other clients. + /// + /// The name of the event to send. + /// Optional arguments to send with the event. + public void SendLuaEvent(string eventName, params DynValue[] args) + { + CheckIfCanAccessMethod(nameof(SendLuaEvent), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return; + } + + _controller.SendLuaEvent(eventName, args); + } + + /// + /// Checks if the current client is the owner of the synchronized object. + /// + public bool IsSyncOwner() + { + CheckIfCanAccessMethod(nameof(IsSyncOwner), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return false; + } + + return _controller.IsSyncOwner(); + } + + public string GetSyncOwner() + { + CheckIfCanAccessMethod(nameof(GetSyncOwner), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return string.Empty; + } + + return _controller.GetSyncOwner(); + } + + #endregion +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs b/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs new file mode 100644 index 0000000..01fbdc4 --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs @@ -0,0 +1,41 @@ +using MoonSharp.Interpreter; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; + +namespace NAK.LuaNetVars; + +public struct LuaEventContext +{ + private string SenderId { get; set; } + public string SenderName { get; private set; } + private DateTime LastInvokeTime { get; set; } + private double TimeSinceLastInvoke { get; set; } + private bool IsLocal { get; set; } + + public static LuaEventContext Create(string senderId, DateTime lastInvokeTime) + { + var playerName = CVRPlayerManager.Instance.TryGetPlayerName(senderId); + + return new LuaEventContext + { + SenderId = senderId, + SenderName = playerName ?? "Unknown", + LastInvokeTime = lastInvokeTime, + TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds, + IsLocal = senderId == MetaPort.Instance.ownerId + }; + } + + public Table ToLuaTable(Script script) + { + Table table = new(script) + { + ["senderId"] = SenderId, + ["senderName"] = SenderName, + ["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"), + ["timeSinceLastInvoke"] = TimeSinceLastInvoke, + ["isLocal"] = IsLocal + }; + return table; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs b/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs new file mode 100644 index 0000000..ed60cee --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs @@ -0,0 +1,60 @@ +namespace NAK.LuaNetVars; + +internal class LuaEventTracker +{ + private class EventMetadata + { + public DateTime LastInvokeTime { get; set; } + public Dictionary LastInvokeTimePerSender { get; } = new(); + } + + private readonly Dictionary _eventMetadata = new(); + + public DateTime GetLastInvokeTime(string eventName) + { + if (!_eventMetadata.TryGetValue(eventName, out var metadata)) + { + metadata = new EventMetadata(); + _eventMetadata[eventName] = metadata; + } + return metadata.LastInvokeTime; + } + + public DateTime GetLastInvokeTimeForSender(string eventName, string senderId) + { + if (!_eventMetadata.TryGetValue(eventName, out var metadata)) + { + metadata = new EventMetadata(); + _eventMetadata[eventName] = metadata; + } + + if (!metadata.LastInvokeTimePerSender.TryGetValue(senderId, out DateTime time)) + { + return DateTime.MinValue; + } + return time; + } + + public void UpdateInvokeTime(string eventName, string senderId) + { + if (!_eventMetadata.TryGetValue(eventName, out EventMetadata metadata)) + { + metadata = new EventMetadata(); + _eventMetadata[eventName] = metadata; + } + + DateTime now = DateTime.Now; + metadata.LastInvokeTime = now; + metadata.LastInvokeTimePerSender[senderId] = now; + } + + public void Clear() + { + _eventMetadata.Clear(); + } + + public void ClearEvent(string eventName) + { + _eventMetadata.Remove(eventName); + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs new file mode 100644 index 0000000..589c405 --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs @@ -0,0 +1,146 @@ +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using ABI_RC.Systems.ModNetwork; +using ABI.CCK.Components; +using ABI.Scripting.CVRSTL.Common; +using MoonSharp.Interpreter; +using UnityEngine; + +namespace NAK.LuaNetVars; + +public partial class LuaNetVarController : MonoBehaviour +{ + private static readonly HashSet _hashes = new(); + + private const string MODULE_ID = "NAK.LNV:"; + + private int _uniquePathHash; + private string ModNetworkID { get; set; } + private CVRLuaClientBehaviour _luaClientBehaviour; + + private readonly Dictionary _registeredNetworkVars = new(); + private readonly Dictionary _registeredNotifyCallbacks = new(); + private readonly Dictionary _registeredEventCallbacks = new(); + private readonly HashSet _dirtyVariables = new(); + + private bool _requestInitialSync; + private CVRSpawnable _spawnable; + private CVRObjectSync _objectSync; + + #region Unity Events + + private void Awake() + { + if (!Initialize()) + return; + + // TODO: a manager script should be in charge of this + // TODO: disabling object will kill coroutine + StartCoroutine(SendVariableUpdatesCoroutine()); + } + + private void OnDestroy() + => Cleanup(); + + #endregion Unity Events + + #region Private Methods + + private bool Initialize() + { + if (!TryGetComponent(out _luaClientBehaviour)) return false; + if (!TryGetUniqueNetworkID(out _uniquePathHash)) return false; + + ModNetworkID = MODULE_ID + _uniquePathHash.ToString("X8"); + + if (ModNetworkID.Length > ModNetworkManager.MaxMessageIdLength) + { + LuaNetVarsMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!"); + return false; + } + + _hashes.Add(_uniquePathHash); + ModNetworkManager.Subscribe(ModNetworkID, OnMessageReceived); + LuaNetVarsMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}"); + + switch (_luaClientBehaviour.Context.objContext) + { + case CVRLuaObjectContext.AVATAR: + _requestInitialSync = !_luaClientBehaviour.Context.IsWornByMe; + break; + case CVRLuaObjectContext.PROP: + _spawnable = _luaClientBehaviour.Context.RootComponent as CVRSpawnable; + _requestInitialSync = !_luaClientBehaviour.Context.IsSpawnedByMe; + break; + case CVRLuaObjectContext.WORLD: + _objectSync = GetComponentInParent(); + _requestInitialSync = true; // idk probably works + break; + default: + _requestInitialSync = true; + break; + } + + return true; + } + + // TODO: evaluate if having dedicated globals is better behaviour (i think so) + // private void ConfigureLuaEnvironment() + // { + // _luaClientBehaviour.script.Globals["SendLuaEvent"] = DynValue.NewCallback(SendLuaEventCallback); + // } + + private void Cleanup() + { + _eventTracker.Clear(); + + if (_uniquePathHash == 0 || string.IsNullOrEmpty(ModNetworkID)) + return; + + ModNetworkManager.Unsubscribe(ModNetworkID); + LuaNetVarsMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}"); + _hashes.Remove(_uniquePathHash); + } + + private System.Collections.IEnumerator SendVariableUpdatesCoroutine() + { + while (true) + { + yield return new WaitForSeconds(0.1f); + if (IsSyncOwner()) SendVariableUpdates(); + if (!_requestInitialSync) continue; + _requestInitialSync = false; + RequestVariableSync(); + } + } + + #endregion Private Methods + + #region Ownership Methods + + public bool IsSyncOwner() + { + if (_objectSync) return _objectSync.SyncedByMe; // idk + if (_spawnable) + { + if (_spawnable.IsSyncedByMe()) return true; // is held / attached locally + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId); + if (propData != null) return propData.syncedBy == MetaPort.Instance.ownerId; // last updated by me + return false; // not held / attached locally and not last updated by me + } + return false; + } + + public string GetSyncOwner() + { + if (_objectSync) return _objectSync.syncedBy; + if (_spawnable) + { + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId); + return propData?.syncedBy ?? string.Empty; + } + return string.Empty; + } + + #endregion Ownership Methods +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs new file mode 100644 index 0000000..47f6415 --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs @@ -0,0 +1,214 @@ +using ABI_RC.Core.Savior; +using ABI_RC.Systems.ModNetwork; +using MoonSharp.Interpreter; +using Unity.Services.Authentication.Internal; + +namespace NAK.LuaNetVars +{ + public partial class LuaNetVarController + { + private enum MessageType : byte + { + LuaVariable = 0, + LuaEvent = 1, + SyncVariables = 2, + RequestSync = 3 + } + + private readonly LuaEventTracker _eventTracker = new(); + + #region Mod Network Events + + private void OnMessageReceived(ModNetworkMessage msg) + { + msg.Read(out byte msgTypeRaw); + if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) return; + + MessageType msgType = (MessageType)msgTypeRaw; + switch (msgType) + { + case MessageType.LuaVariable: + HandleLuaVariableUpdate(msg); + break; + case MessageType.LuaEvent: + HandleLuaEvent(msg); + break; + case MessageType.SyncVariables: + HandleSyncVariables(msg); + break; + case MessageType.RequestSync: + HandleRequestSyncVariables(msg); + break; + } + } + + private void HandleLuaVariableUpdate(ModNetworkMessage msg) + { + msg.Read(out string varName); + DynValue newValue = DeserializeDynValue(msg); + + LuaNetVarsMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}"); + + if (_registeredNetworkVars.TryGetValue(varName, out DynValue var)) + { + UpdateNetworkVariable(varName, var, newValue); + } + else + { + LuaNetVarsMod.Logger.Warning($"Received update for unregistered variable {varName}"); + } + } + + private void HandleLuaEvent(ModNetworkMessage msg) + { + string senderId = msg.Sender; + msg.Read(out string eventName); + msg.Read(out int argsCount); + + DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); + LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); + + // Update tracking + _eventTracker.UpdateInvokeTime(eventName, senderId); + + // Read event arguments + var args = new DynValue[argsCount + 1]; // +1 for context + args[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script)); + + for (int i = 0; i < argsCount; i++) + { + args[i + 1] = DeserializeDynValue(msg); + } + + LuaNetVarsMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args"); + + InvokeLuaEvent(eventName, args); + } + + private void HandleSyncVariables(ModNetworkMessage msg) + { + msg.Read(out int varCount); + for (int i = 0; i < varCount; i++) + { + msg.Read(out string varName); + DynValue newValue = DeserializeDynValue(msg); + + if (_registeredNetworkVars.TryGetValue(varName, out DynValue var)) + { + UpdateNetworkVariable(varName, var, newValue); + } + else + { + LuaNetVarsMod.Logger.Warning($"Received sync for unregistered variable {varName}"); + } + } + } + + private void HandleRequestSyncVariables(ModNetworkMessage msg) + { + if (!IsSyncOwner()) return; + SendVariableSyncToUser(msg.Sender); + } + + #endregion + + #region Event Invocation + + private void InvokeLuaEvent(string eventName, DynValue[] args) + { + if (_registeredEventCallbacks.TryGetValue(eventName, out DynValue callback)) + { + _luaClientBehaviour.script.Call(callback, args); + } + else + { + LuaNetVarsMod.Logger.Warning($"No registered callback for event {eventName}"); + } + } + + #endregion + + #region Sending Methods + + private void SendVariableUpdates() + { + if (_dirtyVariables.Count == 0) return; + + using ModNetworkMessage modMsg = new(ModNetworkID); // can pass target userids as params if needed + modMsg.Write((byte)MessageType.SyncVariables); + modMsg.Write(_dirtyVariables.Count); + modMsg.Send(); + + foreach (var varName in _dirtyVariables) + { + modMsg.Write(varName); + SerializeDynValue(modMsg, _registeredNetworkVars[varName]); + } + + _dirtyVariables.Clear(); + } + + private void SendVariableSyncToUser(string userId) + { + using ModNetworkMessage modMsg = new(ModNetworkID, userId); + modMsg.Write((byte)MessageType.SyncVariables); + modMsg.Write(_registeredNetworkVars.Count); + foreach (var kvp in _registeredNetworkVars) + { + modMsg.Write(kvp.Key); + SerializeDynValue(modMsg, kvp.Value); + } + modMsg.Send(); + + LuaNetVarsMod.Logger.Msg($"Sent variable sync to {userId}"); + } + + private void RequestVariableSync() + { + using ModNetworkMessage modMsg = new(ModNetworkID); + modMsg.Write((byte)MessageType.RequestSync); + modMsg.Send(); + LuaNetVarsMod.Logger.Msg("Requested variable sync"); + } + + // private DynValue SendLuaEventCallback(ScriptExecutionContext context, CallbackArguments args) + // { + // if (args.Count < 1) return DynValue.Nil; + // + // var eventName = args[0].CastToString(); + // var eventArgs = args.GetArray().Skip(1).ToArray(); + // + // SendLuaEvent(eventName, eventArgs); + // + // return DynValue.Nil; + // } + + internal void SendLuaEvent(string eventName, DynValue[] args) + { + string senderId = MetaPort.Instance.ownerId; + DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); + LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); + + // Update tracking + _eventTracker.UpdateInvokeTime(eventName, senderId); + + var argsWithContext = new DynValue[args.Length + 1]; + argsWithContext[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script)); + Array.Copy(args, 0, argsWithContext, 1, args.Length); + + InvokeLuaEvent(eventName, argsWithContext); + + using ModNetworkMessage modMsg = new(ModNetworkID); + modMsg.Write((byte)MessageType.LuaEvent); + modMsg.Write(eventName); + modMsg.Write(args.Length); + + foreach (DynValue arg in args) + SerializeDynValue(modMsg, arg); + + modMsg.Send(); + } + + #endregion + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs new file mode 100644 index 0000000..e68170f --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs @@ -0,0 +1,101 @@ +using MoonSharp.Interpreter; + +namespace NAK.LuaNetVars; + +public partial class LuaNetVarController +{ + internal void RegisterNetworkVar(string varName) + { + if (_registeredNetworkVars.ContainsKey(varName)) + { + LuaNetVarsMod.Logger.Warning($"Network variable {varName} already registered!"); + return; + } + + _registeredNetworkVars[varName] = DynValue.Nil; + _luaClientBehaviour.script.Globals[varName] = DynValue.Nil; + + RegisterGetterFunction(varName); + RegisterSetterFunction(varName); + + LuaNetVarsMod.Logger.Msg($"Registered network variable {varName}"); + } + + private void RegisterGetterFunction(string varName) + { + _luaClientBehaviour.script.Globals["Get" + varName] = DynValue.NewCallback((context, args) => + { + return _registeredNetworkVars.TryGetValue(varName, out var value) ? value : DynValue.Nil; + }); + } + + private void RegisterSetterFunction(string varName) + { + _luaClientBehaviour.script.Globals["Set" + varName] = DynValue.NewCallback((context, args) => + { + if (args.Count < 1) return DynValue.Nil; + + var newValue = args[0]; + if (!IsSupportedDynValue(newValue)) + { + LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}"); + return DynValue.Nil; + } + + if (_registeredNetworkVars.TryGetValue(varName, out var oldValue)) + { + UpdateNetworkVariable(varName, oldValue, newValue); + } + + return DynValue.Nil; + }); + } + + private void UpdateNetworkVariable(string varName, DynValue oldValue, DynValue newValue) + { + _registeredNetworkVars[varName] = newValue; + _luaClientBehaviour.script.Globals[varName] = newValue; + _dirtyVariables.Add(varName); + + if (_registeredNotifyCallbacks.TryGetValue(varName, out var callback)) + { + _luaClientBehaviour.script.Call(callback, DynValue.NewString(varName), oldValue, newValue); + } + } + + internal void RegisterNotifyCallback(string varName, DynValue callback) + { + if (!ValidateCallback(callback) || !ValidateNetworkVar(varName)) return; + + if (_registeredNotifyCallbacks.ContainsKey(varName)) + LuaNetVarsMod.Logger.Warning($"Overwriting notify callback for {varName}"); + + _registeredNotifyCallbacks[varName] = callback; + LuaNetVarsMod.Logger.Msg($"Registered notify callback for {varName}"); + } + + internal void RegisterEventCallback(string eventName, DynValue callback) + { + if (!ValidateCallback(callback)) return; + + if (_registeredEventCallbacks.ContainsKey(eventName)) + LuaNetVarsMod.Logger.Warning($"Overwriting event callback for {eventName}"); + + _registeredEventCallbacks[eventName] = callback; + LuaNetVarsMod.Logger.Msg($"Registered event callback for {eventName}"); + } + + private bool ValidateCallback(DynValue callback) + { + if (callback?.Function != null) return true; + LuaNetVarsMod.Logger.Error("Passed DynValue must be a function"); + return false; + } + + private bool ValidateNetworkVar(string varName) + { + if (_registeredNetworkVars.ContainsKey(varName)) return true; + LuaNetVarsMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}."); + return false; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs new file mode 100644 index 0000000..57221a7 --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs @@ -0,0 +1,62 @@ +using ABI_RC.Systems.ModNetwork; +using MoonSharp.Interpreter; + +namespace NAK.LuaNetVars; + +public partial class LuaNetVarController +{ + private static DynValue DeserializeDynValue(ModNetworkMessage msg) + { + msg.Read(out byte dataTypeByte); + DataType dataType = (DataType)dataTypeByte; + + switch (dataType) + { + case DataType.Boolean: + msg.Read(out bool boolValue); + return DynValue.NewBoolean(boolValue); + case DataType.Number: + msg.Read(out double numberValue); + return DynValue.NewNumber(numberValue); + case DataType.String: + msg.Read(out string stringValue); + return DynValue.NewString(stringValue); + case DataType.Nil: + return DynValue.Nil; + default: + LuaNetVarsMod.Logger.Error($"Unsupported data type received: {dataType}"); + return DynValue.Nil; + } + } + + private static void SerializeDynValue(ModNetworkMessage msg, DynValue value) + { + switch (value.Type) + { + case DataType.Boolean: + msg.Write((byte)DataType.Boolean); + msg.Write(value.Boolean); + break; + case DataType.Number: + msg.Write((byte)DataType.Number); + msg.Write(value.Number); + break; + case DataType.String: + msg.Write((byte)DataType.String); + msg.Write(value.String); + break; + case DataType.Nil: + msg.Write((byte)DataType.Nil); + break; + default: + LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {value.Type}"); + msg.Write((byte)DataType.Nil); + break; + } + } + + private static bool IsSupportedDynValue(DynValue value) + { + return value.Type is DataType.Boolean or DataType.Number or DataType.String or DataType.Nil; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs new file mode 100644 index 0000000..7712781 --- /dev/null +++ b/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs @@ -0,0 +1,57 @@ +using ABI_RC.Core.Savior; +using UnityEngine; + +namespace NAK.LuaNetVars; + +public partial class LuaNetVarController +{ + private bool TryGetUniqueNetworkID(out int hash) + { + string path = GetGameObjectPath(transform); + hash = path.GetHashCode(); + + // Check if it already exists (this **should** only matter in worlds) + if (_hashes.Contains(hash)) + { + LuaNetVarsMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}"); + if (!FindAvailableHash(ref hash)) // Super lazy fix idfc + { + LuaNetVarsMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}"); + return false; + } + } + + _hashes.Add(hash); + + return true; + + static bool FindAvailableHash(ref int hash) + { + for (int i = 0; i < 16; i++) + { + hash += 1; + if (!_hashes.Contains(hash)) return true; + } + return false; // Failed to find a hash in 16 tries + } + } + + private static string GetGameObjectPath(Transform transform) + { + string path = transform.name; + while (transform.parent != null) + { + transform = transform.parent; + + // Only true at root of local player object + if (transform.CompareTag("Player")) + { + path = MetaPort.Instance.ownerId + "/" + path; + break; + } + + path = transform.name + "/" + path; + } + return path; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/Patches.cs b/LuaNetworkVariables/Patches.cs new file mode 100644 index 0000000..ce861fe --- /dev/null +++ b/LuaNetworkVariables/Patches.cs @@ -0,0 +1,65 @@ +using ABI_RC.Core.Base; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using ABI.CCK.Components; +using ABI.Scripting.CVRSTL.Client; +using ABI.Scripting.CVRSTL.Common; +using HarmonyLib; +using MoonSharp.Interpreter; +using NAK.LuaNetVars.Modules; +using UnityEngine; + +namespace NAK.LuaNetVars.Patches; + +internal static class LuaScriptFactory_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(LuaScriptFactory.CVRRequireModule), nameof(LuaScriptFactory.CVRRequireModule.Require))] + private static void Postfix_CVRRequireModule_require( + string moduleFriendlyName, + ref LuaScriptFactory.CVRRequireModule __instance, + ref object __result, + ref Script ____script, + ref CVRLuaContext ____context) + { + if (LuaNetModule.MODULE_ID != moduleFriendlyName) + return; // not our module + + __result = LuaNetModule.RegisterUserData(____script, ____context); + __instance.RegisteredModules[LuaNetModule.MODULE_ID] = __result; // add module to cache + } +} + +internal static class CVRSyncHelper_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.UpdatePropValues))] + private static void Postfix_CVRSyncHelper_UpdatePropValues( + Vector3 position, Vector3 rotation, Vector3 scale, + float[] syncValues, string guid, string instanceId, + Span subSyncValues, int numSyncValues, int syncType = 0) + { + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(prop => prop.InstanceId == instanceId); + if (propData == null) return; + + // Update locally stored prop data with new values + // as GS does not reply with our own data... + + propData.PositionX = position.x; + propData.PositionY = position.y; + propData.PositionZ = position.z; + propData.RotationX = rotation.x; + propData.RotationY = rotation.y; + propData.RotationZ = rotation.z; + propData.ScaleX = scale.x; + propData.ScaleY = scale.y; + propData.ScaleZ = scale.z; + propData.CustomFloatsAmount = numSyncValues; + for (int i = 0; i < numSyncValues; i++) + propData.CustomFloats[i] = syncValues[i]; + + //propData.SpawnedBy + propData.syncedBy = MetaPort.Instance.ownerId; + propData.syncType = syncType; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/Properties/AssemblyInfo.cs b/LuaNetworkVariables/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4cc49cf --- /dev/null +++ b/LuaNetworkVariables/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.LuaNetVars.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.LuaNetVars))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.LuaNetVars))] + +[assembly: MelonInfo( + typeof(NAK.LuaNetVars.LuaNetVarsMod), + nameof(NAK.LuaNetVars), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.LuaNetVars.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/LuaNetworkVariables/README.md b/LuaNetworkVariables/README.md new file mode 100644 index 0000000..c78a56a --- /dev/null +++ b/LuaNetworkVariables/README.md @@ -0,0 +1,14 @@ +# WhereAmIPointing + +Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs b/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs new file mode 100644 index 0000000..3576155 --- /dev/null +++ b/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs @@ -0,0 +1,326 @@ +using UnityEngine; +using System; +using System.Collections.Generic; +using System.Threading; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.ModNetwork; + +namespace NAK.LuaNetVars +{ + public abstract class MNSyncedBehaviour : IDisposable + { + // Add static property for clarity + protected static string LocalUserId => MetaPort.Instance.ownerId; + + protected enum MessageType : byte + { + OwnershipRequest, + OwnershipResponse, + OwnershipTransfer, + StateRequest, + StateUpdate, + CustomData + } + + protected enum OwnershipResponse : byte + { + Accepted, + Rejected + } + + protected readonly string networkId; + protected string currentOwnerId; + private readonly bool autoAcceptTransfers; + private readonly Dictionary> pendingRequests; + private bool isInitialized; + private bool disposedValue; + private bool isSoftOwner = false; + private Timer stateRequestTimer; + private const int StateRequestTimeout = 3000; // 3 seconds + + public string CurrentOwnerId => currentOwnerId; + public bool HasOwnership => currentOwnerId == LocalUserId; + + protected MNSyncedBehaviour(string networkId, string currentOwnerId = "", bool autoAcceptTransfers = false) + { + this.networkId = networkId; + this.currentOwnerId = currentOwnerId; + this.autoAcceptTransfers = autoAcceptTransfers; + this.pendingRequests = new Dictionary>(); + + ModNetworkManager.Subscribe(networkId, OnMessageReceived); + + if (!HasOwnership) + RequestInitialState(); + else + isInitialized = true; + } + + private void RequestInitialState() + { + using ModNetworkMessage msg = new(networkId); + msg.Write((byte)MessageType.StateRequest); + msg.Send(); + + stateRequestTimer = new Timer(StateRequestTimeoutCallback, null, StateRequestTimeout, Timeout.Infinite); + } + + private void StateRequestTimeoutCallback(object state) + { + // If isInitialized is still false, we assume soft ownership + if (!isInitialized) + { + currentOwnerId = LocalUserId; + isSoftOwner = true; + isInitialized = true; + OnOwnershipChanged(currentOwnerId); + } + + stateRequestTimer.Dispose(); + stateRequestTimer = null; + } + + public virtual void RequestOwnership(Action callback = null) + { + if (HasOwnership) + { + callback?.Invoke(true); + return; + } + + using (ModNetworkMessage msg = new(networkId)) + { + msg.Write((byte)MessageType.OwnershipRequest); + msg.Send(); + } + + if (callback != null) + { + pendingRequests[LocalUserId] = callback; + } + } + + protected void SendNetworkedData(Action writeData) + { + if (!HasOwnership) + { + Debug.LogWarning($"[MNSyncedBehaviour] Cannot send data without ownership. NetworkId: {networkId}"); + return; + } + + using (ModNetworkMessage msg = new(networkId)) + { + msg.Write((byte)MessageType.CustomData); + writeData(msg); + msg.Send(); + } + } + + protected virtual void OnMessageReceived(ModNetworkMessage message) + { + message.Read(out byte type); + MessageType messageType = (MessageType)type; + + if (!Enum.IsDefined(typeof(MessageType), messageType)) + return; + + switch (messageType) + { + case MessageType.OwnershipRequest: + if (!HasOwnership) break; + HandleOwnershipRequest(message); + break; + + case MessageType.OwnershipResponse: + if (message.Sender != currentOwnerId) break; + HandleOwnershipResponse(message); + break; + + case MessageType.OwnershipTransfer: + if (message.Sender != currentOwnerId) break; + currentOwnerId = message.Sender; + OnOwnershipChanged(currentOwnerId); + break; + + case MessageType.StateRequest: + if (!HasOwnership) break; // this is the only safeguard against ownership hijacking... idk how to prevent it + // TODO: only respond to a StateUpdate if expecting one + HandleStateRequest(message); + break; + + case MessageType.StateUpdate: + // Accept state updates from current owner or if we have soft ownership + if (message.Sender != currentOwnerId && !isSoftOwner) break; + HandleStateUpdate(message); + break; + + case MessageType.CustomData: + if (message.Sender != currentOwnerId) + { + // If we have soft ownership and receive data from real owner, accept it + if (isSoftOwner && message.Sender != LocalUserId) + { + currentOwnerId = message.Sender; + isSoftOwner = false; + OnOwnershipChanged(currentOwnerId); + } + else + { + // Ignore data from non-owner + break; + } + } + HandleCustomData(message); + break; + } + } + + protected virtual void HandleOwnershipRequest(ModNetworkMessage message) + { + if (!HasOwnership) + return; + + string requesterId = message.Sender; + var response = autoAcceptTransfers ? OwnershipResponse.Accepted : + OnOwnershipRequested(requesterId); + + using (ModNetworkMessage responseMsg = new(networkId)) + { + responseMsg.Write((byte)MessageType.OwnershipResponse); + responseMsg.Write((byte)response); + responseMsg.Send(); + } + + if (response == OwnershipResponse.Accepted) + { + TransferOwnership(requesterId); + } + } + + protected virtual void HandleOwnershipResponse(ModNetworkMessage message) + { + message.Read(out byte responseByte); + OwnershipResponse response = (OwnershipResponse)responseByte; + + if (pendingRequests.TryGetValue(LocalUserId, out var callback)) + { + bool accepted = response == OwnershipResponse.Accepted; + callback(accepted); + pendingRequests.Remove(LocalUserId); + + // Update ownership locally only if accepted + if (accepted) + { + currentOwnerId = LocalUserId; + OnOwnershipChanged(currentOwnerId); + } + } + } + + protected virtual void HandleStateRequest(ModNetworkMessage message) + { + if (!HasOwnership) + return; + + using ModNetworkMessage response = new(networkId, message.Sender); + response.Write((byte)MessageType.StateUpdate); + WriteState(response); + response.Send(); + } + + protected virtual void HandleStateUpdate(ModNetworkMessage message) + { + currentOwnerId = message.Sender; + isSoftOwner = false; + ReadState(message); + isInitialized = true; + + // Dispose of the state request timer if it's still running + if (stateRequestTimer != null) + { + stateRequestTimer.Dispose(); + stateRequestTimer = null; + } + } + + protected virtual void HandleCustomData(ModNetworkMessage message) + { + if (!isInitialized) + { + Debug.LogWarning($"[MNSyncedBehaviour] Received custom data before initialization. NetworkId: {networkId}"); + return; + } + + if (message.Sender != currentOwnerId) + { + // If we have soft ownership and receive data from real owner, accept it + if (isSoftOwner && message.Sender != LocalUserId) + { + currentOwnerId = message.Sender; + isSoftOwner = false; + OnOwnershipChanged(currentOwnerId); + } + else + { + // Ignore data from non-owner + return; + } + } + + ReadCustomData(message); + } + + protected virtual void TransferOwnership(string newOwnerId) + { + using (ModNetworkMessage msg = new(networkId)) + { + msg.Write((byte)MessageType.OwnershipTransfer); + msg.Write(newOwnerId); // Include the new owner ID in transfer message + msg.Send(); + } + + currentOwnerId = newOwnerId; + OnOwnershipChanged(newOwnerId); + } + + protected virtual OwnershipResponse OnOwnershipRequested(string requesterId) + { + return OwnershipResponse.Rejected; + } + + protected virtual void OnOwnershipChanged(string newOwnerId) + { + // Override to handle ownership changes + } + + protected virtual void WriteState(ModNetworkMessage message) { } + protected virtual void ReadState(ModNetworkMessage message) { } + protected virtual void ReadCustomData(ModNetworkMessage message) { } + + protected virtual void Dispose(bool disposing) + { + if (disposedValue) + return; + + if (disposing) + { + ModNetworkManager.Unsubscribe(networkId); + pendingRequests.Clear(); + + if (stateRequestTimer != null) + { + stateRequestTimer.Dispose(); + stateRequestTimer = null; + } + } + + disposedValue = true; + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs b/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs new file mode 100644 index 0000000..30fddc1 --- /dev/null +++ b/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs @@ -0,0 +1,161 @@ +using UnityEngine; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Systems.ModNetwork; + +namespace NAK.LuaNetVars; + +public class PickupableBehaviour : MNSyncedBehaviour +{ + private enum PickupMessageType : byte + { + GrabState, + Transform + } + + private bool isHeld; + private string holderId; + private Vector3 lastPosition; + private Quaternion lastRotation; + + public PickupableObject Pickupable { get; private set; } + + public PickupableBehaviour(string networkId, PickupableObject pickupable) : base(networkId, autoAcceptTransfers: false) + { + Pickupable = pickupable; + isHeld = false; + holderId = string.Empty; + lastPosition = pickupable.transform.position; + lastRotation = pickupable.transform.rotation; + } + + public void OnGrabbed(InteractionContext context) + { + RequestOwnership(success => { + if (success) + { + isHeld = true; + holderId = LocalUserId; + SendNetworkedData(WriteGrabState); + } + else + { + // Ownership request failed, drop the object + Pickupable.ControllerRay = null; // Force drop + } + }); + } + + public void OnDropped() + { + if (!HasOwnership) return; + + isHeld = false; + holderId = string.Empty; + SendNetworkedData(WriteGrabState); + } + + public void UpdateTransform(Vector3 position, Quaternion rotation) + { + if (!HasOwnership || !isHeld) return; + + lastPosition = position; + lastRotation = rotation; + SendNetworkedData(WriteTransform); + } + + protected override OwnershipResponse OnOwnershipRequested(string requesterId) + { + // If the object is held by the current owner, reject the transfer + if (isHeld && holderId == LocalUserId) + return OwnershipResponse.Rejected; + + // If theft is disallowed and the object is held by someone, reject the transfer + if (Pickupable.DisallowTheft && !string.IsNullOrEmpty(holderId)) + return OwnershipResponse.Rejected; + + return OwnershipResponse.Accepted; + } + + protected override void WriteState(ModNetworkMessage message) + { + message.Write(isHeld); + message.Write(holderId); + message.Write(lastPosition); + message.Write(lastRotation); + } + + protected override void ReadState(ModNetworkMessage message) + { + message.Read(out isHeld); + message.Read(out holderId); + message.Read(out lastPosition); + message.Read(out lastRotation); + + UpdatePickupableState(); + } + + private void WriteGrabState(ModNetworkMessage message) + { + message.Write((byte)PickupMessageType.GrabState); + message.Write(isHeld); + message.Write(holderId); + } + + private void WriteTransform(ModNetworkMessage message) + { + message.Write((byte)PickupMessageType.Transform); + message.Write(lastPosition); + message.Write(lastRotation); + } + + protected override void ReadCustomData(ModNetworkMessage message) + { + message.Read(out byte messageType); + + switch ((PickupMessageType)messageType) + { + case PickupMessageType.GrabState: + message.Read(out isHeld); + message.Read(out holderId); + break; + + case PickupMessageType.Transform: + message.Read(out Vector3 position); + message.Read(out Quaternion rotation); + lastPosition = position; + lastRotation = rotation; + break; + } + + UpdatePickupableState(); + } + + private void UpdatePickupableState() + { + // Update transform if we're not the holder + if (!isHeld || holderId != LocalUserId) + { + Pickupable.transform.position = lastPosition; + Pickupable.transform.rotation = lastRotation; + } + + // Force drop if we were holding but someone else took ownership + if (isHeld && holderId != LocalUserId) + { + Pickupable.ControllerRay = null; // Force drop + } + } + + protected override void OnOwnershipChanged(string newOwnerId) + { + base.OnOwnershipChanged(newOwnerId); + + // If we lost ownership and were holding, force drop + if (!HasOwnership && holderId == LocalUserId) + { + isHeld = false; + holderId = string.Empty; + Pickupable.ControllerRay = null; // Force drop + } + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs b/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs new file mode 100644 index 0000000..76b495a --- /dev/null +++ b/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs @@ -0,0 +1,72 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +namespace NAK.LuaNetVars; + +public class PickupableObject : Pickupable +{ + [SerializeField] private bool canPickup = true; + [SerializeField] private bool disallowTheft = false; + [SerializeField] private float maxGrabDistance = 2f; + [SerializeField] private float maxPushDistance = 2f; + [SerializeField] private bool isAutoHold = false; + [SerializeField] private bool allowRotation = true; + [SerializeField] private bool allowPushPull = true; + [SerializeField] private bool allowInteraction = true; + + private PickupableBehaviour behaviour; + private bool isInitialized; + + private void Awake() + { + // Generate a unique network ID based on the instance ID + string networkId = $"pickup_{gameObject.name}"; + behaviour = new PickupableBehaviour(networkId, this); + isInitialized = true; + } + + private void OnDestroy() + { + behaviour?.Dispose(); + } + + private void Update() + { + if (behaviour?.HasOwnership == true) + { + transform.SetPositionAndRotation(ControllerRay.pivotPoint.position, ControllerRay.pivotPoint.rotation); + behaviour.UpdateTransform(transform.position, transform.rotation); + } + } + + #region Pickupable Implementation + + public override void OnGrab(InteractionContext context, Vector3 grabPoint) + { + if (!isInitialized) return; + behaviour.OnGrabbed(context); + } + + public override void OnDrop(InteractionContext context) + { + if (!isInitialized) return; + behaviour.OnDropped(); + } + + public override void OnFlingTowardsTarget(Vector3 target) + { + // ignore + } + + public override bool CanPickup => canPickup; + public override bool DisallowTheft => disallowTheft; + public override float MaxGrabDistance => maxGrabDistance; + public override float MaxPushDistance => maxPushDistance; + public override bool IsAutoHold => isAutoHold; + public override bool IsObjectRotationAllowed => allowRotation; + public override bool IsObjectPushPullAllowed => allowPushPull; + public override bool IsObjectInteractionAllowed => allowInteraction; + + #endregion Pickupable Implementation +} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs b/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs new file mode 100644 index 0000000..4941aaa --- /dev/null +++ b/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs @@ -0,0 +1,63 @@ +using ABI_RC.Systems.ModNetwork; +using UnityEngine; + +namespace NAK.LuaNetVars; + + +// Test implementation +public class TestSyncedBehaviour : MNSyncedBehaviour +{ + private readonly System.Random random = new(); + private int testValue; + private int incrementValue; + + public TestSyncedBehaviour(string networkId) : base(networkId, autoAcceptTransfers: true) + { + Debug.Log($"[TestSyncedBehaviour] Initialized. NetworkId: {networkId}"); + } + + public void SendTestMessage() + { + if (!HasOwnership) return; + + SendNetworkedData(msg => { + testValue = random.Next(1000); + incrementValue++; + msg.Write(testValue); + msg.Write(incrementValue); + }); + } + + protected override void WriteState(ModNetworkMessage message) + { + message.Write(testValue); + message.Write(incrementValue); + } + + protected override void ReadState(ModNetworkMessage message) + { + message.Read(out testValue); + message.Read(out incrementValue); + Debug.Log($"[TestSyncedBehaviour] State synchronized. TestValue: {testValue}, IncrementValue: {incrementValue}"); + } + + protected override void ReadCustomData(ModNetworkMessage message) + { + message.Read(out int receivedValue); + message.Read(out int receivedIncrement); + testValue = receivedValue; + incrementValue = receivedIncrement; + Debug.Log($"[TestSyncedBehaviour] Received custom data: TestValue: {testValue}, IncrementValue: {incrementValue}"); + } + + protected override void OnOwnershipChanged(string newOwnerId) + { + Debug.Log($"[TestSyncedBehaviour] Ownership changed to: {newOwnerId}"); + } + + protected override OwnershipResponse OnOwnershipRequested(string requesterId) + { + Debug.Log($"[TestSyncedBehaviour] Ownership requested by: {requesterId}"); + return OwnershipResponse.Accepted; + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs b/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs new file mode 100644 index 0000000..76d6281 --- /dev/null +++ b/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs @@ -0,0 +1,42 @@ +using ABI_RC.Core.Savior; +using UnityEngine; + +namespace NAK.LuaNetVars; + +public class TestSyncedObject : MonoBehaviour +{ + private const string TEST_NETWORK_ID = "test.synced.object.1"; + private TestSyncedBehaviour syncBehaviour; + private float messageTimer = 0f; + private const float MESSAGE_INTERVAL = 2f; + + private void Start() + { + syncBehaviour = new TestSyncedBehaviour(TEST_NETWORK_ID); + Debug.Log($"TestSyncedObject started. Local Player ID: {MetaPort.Instance.ownerId}"); + } + + private void Update() + { + // Request ownership on Space key + if (Input.GetKeyDown(KeyCode.Space)) + { + Debug.Log("Requesting ownership..."); + syncBehaviour.RequestOwnership((success) => + { + Debug.Log($"Ownership request {(success ? "accepted" : "rejected")}"); + }); + } + + // If we have ownership, send custom data periodically + if (syncBehaviour.HasOwnership) + { + messageTimer += Time.deltaTime; + if (messageTimer >= MESSAGE_INTERVAL) + { + messageTimer = 0f; + syncBehaviour.SendTestMessage(); + } + } + } +} \ No newline at end of file diff --git a/LuaNetworkVariables/format.json b/LuaNetworkVariables/format.json new file mode 100644 index 0000000..654911a --- /dev/null +++ b/LuaNetworkVariables/format.json @@ -0,0 +1,23 @@ +{ + "_id": 234, + "name": "WhereAmIPointing", + "modversion": "1.0.1", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.", + "searchtags": [ + "controller", + "ray", + "line", + "tomato" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", + "changelog": "- Fixed line renderer alpha not being reset when the menu is closed.", + "embedcolor": "#f61963" +} \ No newline at end of file From 7b73452df6ae9e785875f5f2f270449cc881d6fb Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:06:26 -0600 Subject: [PATCH 053/188] ShareBubbles: initial commit --- ShareBubbles/Main.cs | 94 + ShareBubbles/ModSettings.cs | 51 + ShareBubbles/Patches.cs | 1731 +++++++++++++++++ ShareBubbles/Properties/AssemblyInfo.cs | 32 + ShareBubbles/README.md | 43 + ShareBubbles/Resources/sharingbubble.assets | Bin 0 -> 1293156 bytes ShareBubbles/ShareBubbles.csproj | 33 + .../API/Exceptions/ShareApiExceptions.cs | 66 + .../API/PedestalInfoBatchProcessor.cs | 112 ++ .../API/Responses/ActiveSharesResponse.cs | 22 + ...stalInfoResponseButWithPublicationState.cs | 13 + .../ShareBubbles/API/ShareApiHelper.cs | 236 +++ .../DataTypes/BubblePedestalInfo.cs | 10 + .../ShareBubbles/DataTypes/ShareBubbleData.cs | 80 + .../ShareBubbles/Enums/ShareAccess.cs | 8 + .../ShareBubbles/Enums/ShareLifetime.cs | 7 + ShareBubbles/ShareBubbles/Enums/ShareRule.cs | 7 + .../Implementation/AvatarBubbleImpl.cs | 103 + .../Implementation/IShareBubbleImpl.cs | 13 + .../Implementation/SpawnableBubbleImpl.cs | 122 ++ .../Implementation/TempShareManager.cs | 230 +++ .../Networking/ModNetwork.Constants.cs | 11 + .../Networking/ModNetwork.Enums.cs | 38 + .../Networking/ModNetwork.Helpers.cs | 27 + .../Networking/ModNetwork.Inbound.cs | 200 ++ .../Networking/ModNetwork.Logging.cs | 32 + .../Networking/ModNetwork.Main.cs | 36 + .../Networking/ModNetwork.Outbound.cs | 130 ++ ShareBubbles/ShareBubbles/ShareBubble.cs | 363 ++++ .../ShareBubbles/ShareBubbleManager.cs | 424 ++++ .../ShareBubbles/ShareBubbleRegistry.cs | 32 + .../ShareBubbles/UI/BubbleAnimController.cs | 230 +++ .../ShareBubbles/UI/BubbleInteract.cs | 51 + .../ShareBubbles/UI/FacePlayerCamDirection.cs | 21 + .../UI/FacePlayerDirectionAxis.cs | 22 + .../ShareBubbles/UI/HexagonSpinner.cs | 71 + .../ShareBubbles/UI/ReturnOnRelease.cs | 91 + .../ShareBubbles/UI/ShakeSoundController.cs | 85 + ShareBubbles/format.json | 24 + 39 files changed, 4901 insertions(+) create mode 100644 ShareBubbles/Main.cs create mode 100644 ShareBubbles/ModSettings.cs create mode 100644 ShareBubbles/Patches.cs create mode 100644 ShareBubbles/Properties/AssemblyInfo.cs create mode 100644 ShareBubbles/README.md create mode 100644 ShareBubbles/Resources/sharingbubble.assets create mode 100644 ShareBubbles/ShareBubbles.csproj create mode 100644 ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs create mode 100644 ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs create mode 100644 ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs create mode 100644 ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs create mode 100644 ShareBubbles/ShareBubbles/API/ShareApiHelper.cs create mode 100644 ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs create mode 100644 ShareBubbles/ShareBubbles/DataTypes/ShareBubbleData.cs create mode 100644 ShareBubbles/ShareBubbles/Enums/ShareAccess.cs create mode 100644 ShareBubbles/ShareBubbles/Enums/ShareLifetime.cs create mode 100644 ShareBubbles/ShareBubbles/Enums/ShareRule.cs create mode 100644 ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs create mode 100644 ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs create mode 100644 ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs create mode 100644 ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Logging.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs create mode 100644 ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs create mode 100644 ShareBubbles/ShareBubbles/ShareBubble.cs create mode 100644 ShareBubbles/ShareBubbles/ShareBubbleManager.cs create mode 100644 ShareBubbles/ShareBubbles/ShareBubbleRegistry.cs create mode 100644 ShareBubbles/ShareBubbles/UI/BubbleAnimController.cs create mode 100644 ShareBubbles/ShareBubbles/UI/BubbleInteract.cs create mode 100644 ShareBubbles/ShareBubbles/UI/FacePlayerCamDirection.cs create mode 100644 ShareBubbles/ShareBubbles/UI/FacePlayerDirectionAxis.cs create mode 100644 ShareBubbles/ShareBubbles/UI/HexagonSpinner.cs create mode 100644 ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs create mode 100644 ShareBubbles/ShareBubbles/UI/ShakeSoundController.cs create mode 100644 ShareBubbles/format.json diff --git a/ShareBubbles/Main.cs b/ShareBubbles/Main.cs new file mode 100644 index 0000000..72f3c3d --- /dev/null +++ b/ShareBubbles/Main.cs @@ -0,0 +1,94 @@ +using MelonLoader; +using NAK.ShareBubbles.Networking; +using UnityEngine; + +namespace NAK.ShareBubbles; + +public class ShareBubblesMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + #region Melon Mod Overrides + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ShareBubbleManager.Initialize(); + TempShareManager.Initialize(); + + ModNetwork.Initialize(); + ModNetwork.Subscribe(); + + //ModSettings.Initialize(); + + ApplyPatches(typeof(Patches.PlayerSetup_Patches)); + ApplyPatches(typeof(Patches.ControllerRay_Patches)); + ApplyPatches(typeof(Patches.ViewManager_Patches)); + + LoadAssetBundle(); + } + + public override void OnApplicationQuit() + { + ModNetwork.Unsubscribe(); + } + + #endregion Melon Mod Overrides + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities + + #region Asset Bundle Loading + + private const string SharingBubbleAssets = "ShareBubbles.Resources.sharingbubble.assets"; + private const string SharingBubblePrefabPath = "Assets/Mods/SharingBubble/SharingBubble.prefab"; + + internal static GameObject SharingBubblePrefab; + + private void LoadAssetBundle() + { + LoggerInstance.Msg($"Loading required asset bundle..."); + using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(SharingBubbleAssets); + using MemoryStream memoryStream = new(); + if (resourceStream == null) { + LoggerInstance.Error($"Failed to load {SharingBubbleAssets}!"); + return; + } + + resourceStream.CopyTo(memoryStream); + AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); + if (assetBundle == null) { + LoggerInstance.Error($"Failed to load {SharingBubbleAssets}! Asset bundle is null!"); + return; + } + + SharingBubblePrefab = assetBundle.LoadAsset(SharingBubblePrefabPath); + if (SharingBubblePrefab == null) { + LoggerInstance.Error($"Failed to load {SharingBubblePrefab}! Prefab is null!"); + return; + } + SharingBubblePrefab.hideFlags |= HideFlags.DontUnloadUnusedAsset; + LoggerInstance.Msg($"Loaded {SharingBubblePrefab}!"); + + // load + + LoggerInstance.Msg("Asset bundle successfully loaded!"); + } + + #endregion Asset Bundle Loading +} \ No newline at end of file diff --git a/ShareBubbles/ModSettings.cs b/ShareBubbles/ModSettings.cs new file mode 100644 index 0000000..22f708c --- /dev/null +++ b/ShareBubbles/ModSettings.cs @@ -0,0 +1,51 @@ +using MelonLoader; + +namespace NAK.ShareBubbles; + +// TODO: +// Setting for ShareBubbles scaling with player size +// Setting for receiving notification when a direct share is received +// Setting for ShareBubble being interactable outside of the UI buttons (Grab & Click) +// Store last Visibility, Lifetime, and Access Control settings for ShareBubble placement in hidden melon preferences + +public static class ModSettings +{ + #region Constants & Category + + internal const string ModName = nameof(ShareBubbles); // TODO idea: BTKUI player page button to remove player's ShareBubbles ? + + //internal const string SM_SettingsCategory = "Share Bubbles Mod"; + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + #endregion Constants & Category + + #region Debug Settings + + internal static readonly MelonPreferences_Entry Debug_NetworkInbound = + Category.CreateEntry("debug_inbound", false, display_name: "Debug Inbound", description: "Log inbound Mod Network updates."); + + internal static readonly MelonPreferences_Entry Debug_NetworkOutbound = + Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network updates."); + + #endregion Debug Settings + + #region Initialization + + internal static void Initialize() + { + + } + + #endregion Initialization + + #region Setting Changed Callbacks + + // private static void OnPlayerUpAlignmentThresholdChanged(float oldValue, float newValue) + // { + // Entry_PlayerUpAlignmentThreshold.Value = Mathf.Clamp(newValue, 0f, 180f); + // } + + #endregion Setting Changed Callbacks +} \ No newline at end of file diff --git a/ShareBubbles/Patches.cs b/ShareBubbles/Patches.cs new file mode 100644 index 0000000..4f705e3 --- /dev/null +++ b/ShareBubbles/Patches.cs @@ -0,0 +1,1731 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Networking.API.Responses; +using ABI_RC.Core.Networking.API.UserWebsocket; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using HarmonyLib; +using MTJobSystem; +using NAK.ShareBubbles.API; +using NAK.ShareBubbles.API.Exceptions; +using NAK.ShareBubbles.API.Responses; +using Newtonsoft.Json; + +namespace NAK.ShareBubbles.Patches; + +internal static class PlayerSetup_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetSafeScale))] + public static void Postfix_PlayerSetup_SetSafeScale() + { + // I wish there was a callback to listen for player scale changes + ShareBubbleManager.Instance.OnPlayerScaleChanged(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.GetCurrentPropSelectionMode))] + private static void Postfix_PlayerSetup_GetCurrentPropSelectionMode(ref PlayerSetup.PropSelectionMode __result) + { + // Stickers mod uses invalid enum value 4, so we use 5 + // https://github.com/NotAKidoS/NAK_CVR_Mods/blob/d0c8298074c4dcfc089ccb34ed8b8bd7e0b9cedf/Stickers/Patches.cs#L17 + if (ShareBubbleManager.Instance.IsPlacingBubbleMode) __result = (PlayerSetup.PropSelectionMode)5; + } +} + +internal static class ControllerRay_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.DeleteSpawnable))] + public static void Postfix_ControllerRay_DeleteSpawnable(ref ControllerRay __instance) + { + if (!__instance._interactDown) + return; // not interacted, no need to check + + if (PlayerSetup.Instance.GetCurrentPropSelectionMode() + != PlayerSetup.PropSelectionMode.Delete) + return; // not in delete mode, no need to check + + ShareBubble shareBubble = __instance.hitTransform.GetComponentInParent(); + if (shareBubble == null) return; + + ShareBubbleManager.Instance.DestroyBubble(shareBubble); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.HandlePropSpawn))] + private static void Prefix_ControllerRay_HandlePropSpawn(ref ControllerRay __instance) + { + if (!ShareBubbleManager.Instance.IsPlacingBubbleMode) + return; + + if (__instance._gripDown) ShareBubbleManager.Instance.IsPlacingBubbleMode = false; + if (__instance._hitUIInternal || !__instance._interactDown) + return; + + ShareBubbleManager.Instance.PlaceSelectedBubbleFromControllerRay(__instance.rayDirectionTransform); + } +} + +internal static class ViewManager_Patches +{ + +private const string DETAILS_TOOLBAR_PATCHES = """ + +const ContentShareMod = { + debugMode: false, + currentContentData: null, + themeColors: null, + + /* Theme Handling */ + + getThemeColors: function() { + if (this.themeColors) return this.themeColors; + + // Default fallback colors + const defaultColors = { + background: '#373021', + border: '#59885d' + }; + + // Try to get colors from favorite category element + const favoriteCategoryElement = document.querySelector('.favorite-category-selection'); + if (!favoriteCategoryElement) return defaultColors; + + const computedStyle = window.getComputedStyle(favoriteCategoryElement); + this.themeColors = { + background: computedStyle.backgroundColor || defaultColors.background, + border: computedStyle.borderColor || defaultColors.border + }; + + return this.themeColors; + }, + + applyThemeToDialog: function(dialog) { + const colors = this.getThemeColors(); + dialog.style.backgroundColor = colors.background; + dialog.style.borderColor = colors.border; + + // Update any close or page buttons to match theme + const buttons = dialog.querySelectorAll('.close-btn, .page-btn'); + buttons.forEach(button => { + button.style.borderColor = colors.border; + }); + + return colors; + }, + + /* Core Initialization */ + + init: function() { + const styles = [ + this.getSharedStyles(), + this.ShareBubble.initStyles(), + this.ShareSelect.initStyles(), + this.DirectShare.initStyles(), + this.Unshare.initStyles() + ].join('\n'); + + const styleElement = document.createElement('style'); + styleElement.type = 'text/css'; + styleElement.innerHTML = styles; + document.head.appendChild(styleElement); + + this.shareBubbleDialog = this.ShareBubble.createDialog(); + this.shareSelectDialog = this.ShareSelect.createDialog(); + this.directShareDialog = this.DirectShare.createDialog(); + this.unshareDialog = this.Unshare.createDialog(); + + this.initializeToolbars(); + this.bindEvents(); + }, + + getSharedStyles: function() { + return ` + .content-sharing-base-dialog { + position: fixed; + background-color: #373021; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 800px; + min-width: 500px; + border: 3px solid #59885d; + padding: 20px; + z-index: 100000; + opacity: 0; + transition: opacity 0.2s linear; + } + + .content-sharing-base-dialog.in { + opacity: 1; + } + + .content-sharing-base-dialog.out { + opacity: 0; + } + + .content-sharing-base-dialog.hidden { + display: none; + } + + .content-sharing-base-dialog h2, + .content-sharing-base-dialog h3 { + margin-top: 0; + margin-bottom: 0.5em; + text-align: left; + } + + .content-sharing-base-dialog .description { + margin-bottom: 1em; + text-align: left; + font-size: 0.9em; + color: #aaa; + } + + .content-sharing-base-dialog .close-btn { + position: absolute; + top: 1%; + right: 1%; + border-radius: 0.25em; + border: 3px solid #59885d; + padding: 0.5em; + width: 8em; + text-align: center; + } + + .page-btn { + border-radius: 0.25em; + border: 3px solid #59885d; + padding: 0.5em; + width: 8em; + text-align: center; + } + + .page-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .inp-hidden { + display: none; + } + `; + }, + + /* Feature Modules */ + + ShareBubble: { + initStyles: function() { + return ` + .share-bubble-dialog { + max-width: 800px; + transform: translate(-50%, -60%); + } + + .share-bubble-dialog .content-btn { + position: relative; + margin-bottom: 0.5em; + } + + .share-bubble-dialog .btn-group { + display: flex; + margin-bottom: 1em; + } + + .share-bubble-dialog .option-select { + flex: 1 1 0; + min-width: 0; + background-color: inherit; + text-align: center; + padding: 0.5em; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid inherit; + } + + .share-bubble-dialog .option-select + .option-select { + margin-left: -1px; + } + + .share-bubble-dialog .option-select.active, + .share-bubble-dialog .option-select:hover { + background-color: rgba(27, 80, 55, 1); + } + + .share-bubble-dialog .action-buttons { + margin-top: 1em; + display: flex; + justify-content: space-between; + } + + .share-bubble-dialog .action-btn { + flex: 1; + text-align: center; + padding: 0.5em; + border-radius: 0.25em; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + margin-right: 0.5em; + } + + .share-bubble-dialog .action-btn:last-child { + margin-right: 0; + } + `; + }, + + createDialog: function() { + const dialog = document.createElement('div'); + dialog.id = 'content-share-bubble-dialog'; + dialog.className = 'content-sharing-base-dialog share-bubble-dialog hidden'; + dialog.innerHTML = ` +

Share Bubble

+
Close
+ +

Visibility

+

Choose who can see your Sharing Bubble.

+
+
Everyone
+
Friends Only
+
+ +

Lifetime

+

How long the Sharing Bubble lasts. You can delete it at any time.

+
+
2 Minutes
+
For Session
+
+ +
+

Access Control

+
+ + +

+ Users cannot access your Private Content through this share. +

+
+
+
Keep
+
Instance Only
+
None
+
+
+ + + + + + + +
+
Drop
+
Select
+
+ `; + document.body.appendChild(dialog); + return dialog; + }, + + show: function(contentDetails) { + const dialog = ContentShareMod.shareBubbleDialog; + const colors = ContentShareMod.applyThemeToDialog(dialog); + + // Additional ShareBubble-specific theming + const optionSelects = dialog.querySelectorAll('.option-select'); + optionSelects.forEach(element => { + element.style.borderColor = colors.border; + }); + + const grantAccessSection = dialog.querySelector('.grant-access-section'); + const noAccessControlMessage = dialog.querySelector('.no-access-control-message'); + const showGrantAccess = contentDetails.IsMine && !contentDetails.IsPublic; + + if (grantAccessSection && noAccessControlMessage) { + grantAccessSection.style.display = showGrantAccess ? '' : 'none'; + noAccessControlMessage.style.display = showGrantAccess ? 'none' : ''; + } + + dialog.classList.remove('hidden', 'out'); + setTimeout(() => dialog.classList.add('in'), 50); + + ContentShareMod.currentContentData = contentDetails; + }, + + hide: function() { + const dialog = ContentShareMod.shareBubbleDialog; + dialog.classList.remove('in'); + dialog.classList.add('out'); + setTimeout(() => { + dialog.classList.add('hidden'); + dialog.classList.remove('out'); + }, 200); + }, + + changeVisibility: function(element) { + document.getElementById('share-visibility').value = element.dataset.visibilityValue; + const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.visibility-btn'); + buttons.forEach(btn => btn.classList.remove('active')); + element.classList.add('active'); + }, + + changeDuration: function(element) { + document.getElementById('share-duration').value = element.dataset.durationValue; + const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.duration-btn'); + buttons.forEach(btn => btn.classList.remove('active')); + element.classList.add('active'); + }, + + changeAccess: function(element) { + document.getElementById('share-access').value = element.dataset.accessValue; + const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-btn'); + buttons.forEach(btn => btn.classList.remove('active')); + element.classList.add('active'); + + const descriptions = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-desc'); + descriptions.forEach(desc => { + desc.style.display = desc.dataset.accessType === element.dataset.accessValue ? '' : 'none'; + }); + }, + + submit: function(action) { + const contentDetails = ContentShareMod.currentContentData; + let bubbleImpl, bubbleContent, contentImage, contentName; + + if (contentDetails.AvatarId) { + bubbleImpl = 'Avatar'; + bubbleContent = contentDetails.AvatarId; + contentImage = contentDetails.AvatarImageCoui; + contentName = contentDetails.AvatarName; + } else if (contentDetails.SpawnableId) { + bubbleImpl = 'Spawnable'; + bubbleContent = contentDetails.SpawnableId; + contentImage = contentDetails.SpawnableImageCoui; + contentName = contentDetails.SpawnableName; + } else if (contentDetails.WorldId) { + bubbleImpl = 'World'; + bubbleContent = contentDetails.WorldId; + contentImage = contentDetails.WorldImageCoui; + contentName = contentDetails.WorldName; + } else if (contentDetails.UserId) { + bubbleImpl = 'User'; + bubbleContent = contentDetails.UserId; + contentImage = contentDetails.UserImageCoui; + contentName = contentDetails.UserName; + } else { + console.error('No valid content ID found'); + return; + } + + const visibility = document.getElementById('share-visibility').value; + const duration = document.getElementById('share-duration').value; + const access = document.getElementById('share-access').value; + + const shareRule = visibility === 'FriendsOnly' ? 'FriendsOnly' : 'Everyone'; + const shareLifetime = duration === 'Session' ? 'Session' : 'TwoMinutes'; + const shareAccess = access === 'Permanent' ? 'Permanent' : + access === 'Session' ? 'Session' : 'NoAccess'; + + if (ContentShareMod.debugMode) { + console.log('Sharing content:', { + action, bubbleImpl, bubbleContent, + shareRule, shareLifetime, shareAccess + }); + } + + engine.call('NAKCallShareContent', action, bubbleImpl, bubbleContent, + shareRule, shareLifetime, shareAccess, contentImage, contentName); + + this.hide(); + } + }, + + Unshare: { + currentPage: 1, + totalPages: 1, + sharesPerPage: 5, + sharesList: null, + + initStyles: function() { + return ` + .unshare-dialog { + width: 800px; + height: 1000px; + transform: translate(-50%, -60%); + display: flex; + flex-direction: column; + } + + .unshare-dialog .shares-container { + flex: 1; + overflow-y: auto; + margin: 20px 0; + min-height: 0; + } + + .unshare-dialog #shares-loading, + .unshare-dialog #shares-error, + .unshare-dialog #shares-empty { + text-align: center; + padding: 2em; + font-size: 1.1em; + } + + .unshare-dialog #shares-error { + color: #ff6b6b; + } + + .unshare-dialog .share-item { + display: flex; + align-items: center; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 10px; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + min-height: 120px; + } + + .unshare-dialog .share-item img { + width: 96px; + height: 96px; + border-radius: 4px; + margin-right: 15px; + cursor: pointer; + } + + .unshare-dialog .share-item .user-name { + flex: 1; + font-size: 1.3em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-right: 15px; + cursor: pointer; + } + + .unshare-dialog .action-btn { + width: 180px; + height: 60px; + font-size: 1.2em; + color: white; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 10px; + padding: 0 20px; + text-align: center; + } + + .unshare-dialog .revoke-btn { + background-color: #ff6b6b; + } + + .unshare-dialog .undo-btn { + background-color: #4a9eff; + } + + .unshare-dialog .action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #666; + } + + .unshare-dialog .pagination { + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: 20px; + } + + .unshare-dialog .page-info { + font-size: 1.1em; + opacity: 0.8; + margin-bottom: 15px; + text-align: center; + } + + .unshare-dialog .page-buttons { + display: flex; + justify-content: center; + gap: 20px; + } + + .unshare-dialog .share-item.revoked { + opacity: 0.7; + } + `; + }, + + createDialog: function() { + const dialog = document.createElement('div'); + dialog.id = 'content-unshare-dialog'; + dialog.className = 'content-sharing-base-dialog unshare-dialog hidden'; + dialog.innerHTML = ` +

Manage Shares

+
Close
+ +
+
Loading shares...
+ + + +
+ + + `; + document.body.appendChild(dialog); + return dialog; + }, + + show: function(contentDetails) { + const dialog = ContentShareMod.unshareDialog; + ContentShareMod.applyThemeToDialog(dialog); + + dialog.classList.remove('hidden', 'out'); + setTimeout(() => dialog.classList.add('in'), 50); + + ContentShareMod.currentContentData = contentDetails; + this.currentPage = 1; + this.totalPages = 1; + this.sharesList = null; + this.requestShares(); + }, + + hide: function() { + const dialog = ContentShareMod.unshareDialog; + dialog.classList.remove('in'); + dialog.classList.add('out'); + setTimeout(() => { + dialog.classList.add('hidden'); + dialog.classList.remove('out'); + }, 200); + }, + + requestShares: function() { + const dialog = ContentShareMod.unshareDialog; + const sharesContainer = dialog.querySelector('.shares-container'); + + sharesContainer.querySelector('#shares-loading').style.display = ''; + sharesContainer.querySelector('#shares-error').style.display = 'none'; + sharesContainer.querySelector('#shares-empty').style.display = 'none'; + sharesContainer.querySelector('#shares-list').style.display = 'none'; + + const contentDetails = ContentShareMod.currentContentData; + const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; + const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; + + engine.call('NAKGetContentShares', contentType, contentId); + }, + + handleSharesResponse: function(success, shares) { + const dialog = ContentShareMod.unshareDialog; + const sharesContainer = dialog.querySelector('.shares-container'); + const loadingElement = sharesContainer.querySelector('#shares-loading'); + const errorElement = sharesContainer.querySelector('#shares-error'); + const emptyElement = sharesContainer.querySelector('#shares-empty'); + const sharesListElement = sharesContainer.querySelector('#shares-list'); + + loadingElement.style.display = 'none'; + + if (!success) { + errorElement.style.display = ''; + return; + } + + try { + const response = JSON.parse(shares); + this.sharesList = response.Data.value; + + if (!this.sharesList || this.sharesList.length === 0) { + emptyElement.style.display = ''; + const pagination = dialog.querySelector('.pagination'); + const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); + prevButton.disabled = true; + nextButton.disabled = true; + pagination.querySelector('.page-info').textContent = '1/1'; + return; + } + + this.totalPages = Math.ceil(this.sharesList.length / this.sharesPerPage); + this.updatePageContent(); + } catch (error) { + console.error('Error parsing shares:', error); + errorElement.style.display = ''; + } + }, + + updatePageContent: function() { + const dialog = ContentShareMod.unshareDialog; + const sharesListElement = dialog.querySelector('#shares-list'); + + const startIndex = (this.currentPage - 1) * this.sharesPerPage; + const endIndex = startIndex + this.sharesPerPage; + const currentShares = this.sharesList.slice(startIndex, endIndex); + + sharesListElement.innerHTML = currentShares.map(share => ` + + `).join(''); + + sharesListElement.style.display = ''; + + const pagination = dialog.querySelector('.pagination'); + pagination.querySelector('.page-info').textContent = `${this.currentPage}/${this.totalPages}`; + const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); + prevButton.disabled = this.currentPage === 1; + nextButton.disabled = this.currentPage === this.totalPages; + }, + + previousPage: function() { + if (this.currentPage > 1) { + this.currentPage--; + this.updatePageContent(); + } + }, + + nextPage: function() { + if (this.currentPage < this.totalPages) { + this.currentPage++; + this.updatePageContent(); + } + }, + + viewUserProfile: function(userId) { + this.hide(); + getUserDetails(userId); + }, + + revokeShare: function(userId, buttonElement) { + const contentDetails = ContentShareMod.currentContentData; + const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; + const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; + + buttonElement.disabled = true; + buttonElement.textContent = 'Revoking...'; + + engine.call('NAKRevokeContentShare', contentType, contentId, userId); + }, + + handleRevokeResponse: function(success, userId, error) { + const dialog = ContentShareMod.unshareDialog; + const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`); + if (!shareItem) return; + + const actionButton = shareItem.querySelector('button'); + + if (success) { + shareItem.classList.add('revoked'); + actionButton.className = 'action-btn undo-btn button'; + actionButton.textContent = 'Undo'; + actionButton.onclick = () => { + actionButton.disabled = true; + actionButton.textContent = 'Restoring...'; + + const contentDetails = ContentShareMod.currentContentData; + const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; + const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; + + engine.call('NAKCallShareContentDirect', contentType, contentId, userId); + }; + uiPushShow("Share revoked successfully", 3); + } else { + actionButton.textContent = 'Failed'; + actionButton.classList.add('failed'); + uiPushShow(error || "Failed to revoke share", 3); + + // Reset button after a moment + setTimeout(() => { + actionButton.disabled = false; + actionButton.textContent = 'Revoke'; + actionButton.classList.remove('failed'); + }, 1000); + } + }, + + handleShareResponse: function(success, userId, error) { + const dialog = ContentShareMod.unshareDialog; + const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`); + if (!shareItem) return; + + const actionButton = shareItem.querySelector('button'); + + if (success) { + this.requestShares(); + uiPushShow("Share restored successfully", 3); + } else { + actionButton.textContent = 'Failed'; + actionButton.classList.add('failed'); + uiPushShow(error || "Failed to restore share", 3); + + // Reset button after a moment + setTimeout(() => { + actionButton.disabled = false; + actionButton.textContent = 'Undo'; + actionButton.classList.remove('failed'); + }, 1000); + } + } + }, + + DirectShare: { + currentPage: 1, + totalPages: 1, + usersPerPage: 5, + usersList: null, + isInstanceUsers: true, + + initStyles: function() { + return ` + .direct-share-dialog { + width: 800px; + height: 1000px; + transform: translate(-50%, -60%); + display: flex; + flex-direction: column; + } + + .direct-share-dialog .search-container { + margin: 20px 0; + padding: 10px; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + } + + .direct-share-dialog .search-input { + width: 100%; + padding: 10px; + border: none; + background: transparent; + color: inherit; + font-size: 1.1em; + } + + .direct-share-dialog .source-indicator { + padding: 10px; + text-align: center; + opacity: 0.8; + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; + margin-bottom: 20px; + } + + .direct-share-dialog .users-container { + flex: 1; + overflow-y: auto; + margin-bottom: 20px; + min-height: 0; + } + + .direct-share-dialog .user-item { + display: flex; + align-items: center; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 10px; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + min-height: 96px; + } + + .direct-share-dialog .user-item img { + width: 96px; + height: 96px; + margin-right: 15px; + cursor: pointer; + border-radius: 4px; + } + + .direct-share-dialog .user-item .user-name { + flex: 1; + font-size: 1.3em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-right: 15px; + cursor: pointer; + } + + .direct-share-dialog .user-item .action-btn { + width: 140px; + height: 50px; + font-size: 1.2em; + color: white; + background-color: rgba(27, 80, 55, 1); + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .direct-share-dialog .user-item .action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #4a9eff; + } + + .direct-share-dialog .pagination { + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: 20px; + } + + .direct-share-dialog .page-info { + font-size: 1.1em; + opacity: 0.8; + margin-bottom: 15px; + text-align: center; + } + + .direct-share-dialog .page-buttons { + display: flex; + justify-content: center; + gap: 20px; + } + + .direct-share-dialog .user-item .action-btn { + width: 180px; + height: 60px; + font-size: 1.2em; + color: white; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 10px; + padding: 0 20px; + text-align: center; + background-color: rgba(27, 80, 55, 1); + } + + .direct-share-dialog .user-item .action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .direct-share-dialog .user-item .action-btn.shared { + background-color: #4a9eff; + } + + .direct-share-dialog .user-item .action-btn.failed { + background-color: #ff6b6b; + } + `; + }, + + createDialog: function() { + const dialog = document.createElement('div'); + dialog.id = 'content-direct-share-dialog'; + dialog.className = 'content-sharing-base-dialog direct-share-dialog hidden'; + dialog.innerHTML = ` +

Direct Share

+
Close
+ +
+
Loading users...
+ + + +
+ + + `; + document.body.appendChild(dialog); + return dialog; + }, + + show: function(contentDetails) { + const dialog = ContentShareMod.directShareDialog; + ContentShareMod.applyThemeToDialog(dialog); + + dialog.classList.remove('hidden', 'out'); + setTimeout(() => dialog.classList.add('in'), 50); + + ContentShareMod.currentContentData = contentDetails; + this.currentPage = 1; + this.totalPages = 1; + this.usersList = null; + this.requestUsers(true); + }, + + hide: function() { + const dialog = ContentShareMod.directShareDialog; + dialog.classList.remove('in'); + dialog.classList.add('out'); + setTimeout(() => { + dialog.classList.add('hidden'); + dialog.classList.remove('out'); + }, 200); + }, + + handleUsersResponse: function(success, users, isInstanceUsers) { + const dialog = ContentShareMod.directShareDialog; + const usersContainer = dialog.querySelector('.users-container'); + // const sourceIndicator = dialog.querySelector('.source-indicator'); + const loadingElement = usersContainer.querySelector('#users-loading'); + const errorElement = usersContainer.querySelector('#users-error'); + const emptyElement = usersContainer.querySelector('#users-empty'); + const usersListElement = usersContainer.querySelector('#users-list'); + + loadingElement.style.display = 'none'; + // sourceIndicator.textContent = isInstanceUsers ? + // 'Showing users in current instance' : + // 'Showing search results'; + + // TODO: Add source indicator to html: + //
+ // Showing users in current instance + //
+ + if (!success) { + errorElement.style.display = ''; + return; + } + + try { + const response = JSON.parse(users); + this.usersList = response.entries; + this.isInstanceUsers = isInstanceUsers; + + if (!this.usersList || this.usersList.length === 0) { + emptyElement.style.display = ''; + this.updatePagination(); + return; + } + + this.totalPages = Math.ceil(this.usersList.length / this.usersPerPage); + this.updatePageContent(); + } catch (error) { + console.error('Error parsing users:', error); + errorElement.style.display = ''; + } + }, + + handleSearch: function(event) { + if (event.key === 'Enter') { + const searchValue = event.target.value.trim(); + // Pass true for instance users when empty search, false for search results + this.requestUsers(searchValue === '', searchValue); + } + }, + + requestUsers: function(isInstanceUsers, searchQuery = '') { + const dialog = ContentShareMod.directShareDialog; + const usersContainer = dialog.querySelector('.users-container'); + + usersContainer.querySelector('#users-loading').style.display = ''; + usersContainer.querySelector('#users-error').style.display = 'none'; + usersContainer.querySelector('#users-empty').style.display = 'none'; + usersContainer.querySelector('#users-list').style.display = 'none'; + + engine.call('NAKGetUsersForSharing', searchQuery); + }, + + updatePageContent: function() { + const dialog = ContentShareMod.directShareDialog; + const usersListElement = dialog.querySelector('#users-list'); + + const startIndex = (this.currentPage - 1) * this.usersPerPage; + const endIndex = startIndex + this.usersPerPage; + const currentUsers = this.usersList.slice(startIndex, endIndex); + + usersListElement.innerHTML = currentUsers.map(user => ` +
+ ${user.name}'s avatar + ${user.name} + +
+ `).join(''); + + usersListElement.style.display = ''; + this.updatePagination(); + }, + + updatePagination: function() { + const dialog = ContentShareMod.directShareDialog; + const pagination = dialog.querySelector('.pagination'); + const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); + + pagination.querySelector('.page-info').textContent = `Page ${this.currentPage}/${this.totalPages}`; + prevButton.disabled = this.currentPage === 1; + nextButton.disabled = this.currentPage === this.totalPages; + }, + + previousPage: function() { + if (this.currentPage > 1) { + this.currentPage--; + this.updatePageContent(); + } + }, + + nextPage: function() { + if (this.currentPage < this.totalPages) { + this.currentPage++; + this.updatePageContent(); + } + }, + + viewUserProfile: function(userId) { + this.hide(); + getUserDetails(userId); + }, + + shareWithUser: function(userId, buttonElement) { + const contentDetails = ContentShareMod.currentContentData; + const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; + const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; + const contentName = contentDetails.AvatarName || contentDetails.SpawnableName; + const contentImage = contentDetails.AvatarImageURL || contentDetails.SpawnableImageURL; + + buttonElement.disabled = true; + buttonElement.textContent = 'Sharing...'; + + engine.call('NAKCallShareContentDirect', contentType, contentId, userId, contentName, contentImage); + }, + + handleShareResponse: function(success, userId, error) { + const dialog = ContentShareMod.directShareDialog; + const userItem = dialog.querySelector(`[data-user-id="${userId}"]`); + if (!userItem) return; + + const actionButton = userItem.querySelector('button'); + + if (success) { + actionButton.textContent = 'Shared'; + actionButton.disabled = true; + actionButton.classList.add('shared'); + uiPushShow("Content shared successfully", 3, "shareresponse"); + } else { + actionButton.disabled = false; + actionButton.textContent = 'Failed'; + actionButton.classList.add('failed'); + uiPushShow(error || "Failed to share content", 3, "shareresponse"); + + // Reset button after a moment + setTimeout(() => { + actionButton.disabled = false; + actionButton.textContent = 'Share'; + actionButton.classList.remove('failed', 'shared'); + }, 1000); + } + } + }, + + ShareSelect: { + initStyles: function() { + return ` + .share-select-dialog { + width: 650px; + height: 480px; + transform: translate(-50%, -80%); + } + + .share-select-dialog .share-options { + display: flex; + flex-direction: column; + gap: 15px; + margin-top: 20px; + } + + .share-select-dialog .share-option { + padding: 20px; + text-align: left; + cursor: pointer; + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + transition: background-color 0.2s ease; + } + + .share-select-dialog .share-option:hover { + background-color: rgba(27, 80, 55, 1); + border-color: rgba(255, 255, 255, 0.2); + } + + .share-select-dialog h3 { + margin: 0 0 8px 0; + font-size: 1.2em; + } + + .share-select-dialog p { + margin: 0; + opacity: 0.8; + font-size: 0.95em; + line-height: 1.4; + } + `; + }, + + createDialog: function() { + const dialog = document.createElement('div'); + dialog.id = 'content-share-select-dialog'; + dialog.className = 'content-sharing-base-dialog share-select-dialog hidden'; + dialog.innerHTML = ` +

Share Content

+
Close
+ + + `; + document.body.appendChild(dialog); + return dialog; + }, + + show: function(contentDetails) { + const dialog = ContentShareMod.shareSelectDialog; + ContentShareMod.applyThemeToDialog(dialog); + + dialog.classList.remove('hidden', 'out'); + setTimeout(() => dialog.classList.add('in'), 50); + + ContentShareMod.currentContentData = contentDetails; + }, + + hide: function() { + const dialog = ContentShareMod.shareSelectDialog; + dialog.classList.remove('in'); + dialog.classList.add('out'); + setTimeout(() => { + dialog.classList.add('hidden'); + dialog.classList.remove('out'); + }, 200); + }, + + openShareBubble: function() { + this.hide(); + ContentShareMod.ShareBubble.show(ContentShareMod.currentContentData); + }, + + openDirectShare: function() { + this.hide(); + ContentShareMod.DirectShare.show(ContentShareMod.currentContentData); + } + }, + + // Toolbar initialization and event bindings + initializeToolbars: function() { + const findEmptyButtons = (toolbar) => { + return Array.from(toolbar.querySelectorAll('.toolbar-btn')).filter( + btn => btn.textContent.trim() === "" + ); + }; + + const setupToolbar = (selector) => { + const toolbar = document.querySelector(selector); + if (!toolbar) return; + + const emptyButtons = findEmptyButtons(toolbar); + if (emptyButtons.length >= 2) { + emptyButtons[0].classList.add('content-share-btn'); + emptyButtons[0].textContent = 'Share'; + + emptyButtons[1].classList.add('content-unshare-btn'); + emptyButtons[1].textContent = 'Unshare'; + } + }; + + setupToolbar('#avatar-detail .avatar-toolbar'); + setupToolbar('#prop-detail .avatar-toolbar'); + }, + + bindEvents: function() { + // Avatar events + engine.on("LoadAvatarDetails", (avatarDetails) => { + const shareBtn = document.querySelector('#avatar-detail .content-share-btn'); + const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn'); + const canShareDirectly = avatarDetails.IsMine; + const canUnshare = avatarDetails.IsMine || avatarDetails.IsSharedWithMe; + + if (shareBtn) { + shareBtn.classList.remove('disabled'); + + if (canShareDirectly) { + shareBtn.onclick = () => ContentShareMod.ShareSelect.show(avatarDetails); + } else { + shareBtn.onclick = () => ContentShareMod.ShareBubble.show(avatarDetails); + } + } + + if (unshareBtn) { + if (canUnshare) { + unshareBtn.classList.remove('disabled'); + unshareBtn.onclick = () => { + if (avatarDetails.IsMine) { + ContentShareMod.Unshare.show(avatarDetails); + } else { + uiConfirmShow("Unshare Avatar", + "Are you sure you want to unshare this avatar?", + "unshare_avatar_confirmation", + avatarDetails.AvatarId); + } + }; + } else { + unshareBtn.classList.add('disabled'); + unshareBtn.onclick = null; + } + } + + ContentShareMod.currentContentData = avatarDetails; + }); + + // Prop events + engine.on("LoadPropDetails", (propDetails) => { + const shareBtn = document.querySelector('#prop-detail .content-share-btn'); + const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn'); + const canShareDirectly = propDetails.IsMine; + const canUnshare = propDetails.IsMine || propDetails.IsSharedWithMe; + + if (shareBtn) { + shareBtn.classList.remove('disabled'); + + if (canShareDirectly) { + shareBtn.onclick = () => ContentShareMod.ShareSelect.show(propDetails); + } else { + shareBtn.onclick = () => ContentShareMod.ShareBubble.show(propDetails); + } + } + + if (unshareBtn) { + if (canUnshare) { + unshareBtn.classList.remove('disabled'); + unshareBtn.onclick = () => { + if (propDetails.IsMine) { + ContentShareMod.Unshare.show(propDetails); + } else { + uiConfirmShow("Unshare Prop", + "Are you sure you want to unshare this prop?", + "unshare_prop_confirmation", + propDetails.SpawnableId); + } + }; + } else { + unshareBtn.classList.add('disabled'); + unshareBtn.onclick = null; + } + } + + ContentShareMod.currentContentData = propDetails; + }); + + // Share response handlers + engine.on("OnHandleSharesResponse", (success, shares) => { + if (ContentShareMod.debugMode) { + console.log('Shares response:', success, shares); + } + ContentShareMod.Unshare.handleSharesResponse(success, shares); + }); + + engine.on("OnHandleRevokeResponse", (success, userId, error) => { + ContentShareMod.Unshare.handleRevokeResponse(success, userId, error); + }); + + engine.on("OnHandleShareResponse", function(success, userId, error) { + // Pass event to Unshare and DirectShare modules depending on which dialog is open + if (ContentShareMod.unshareDialog && !ContentShareMod.unshareDialog.classList.contains('hidden')) { + ContentShareMod.Unshare.handleShareResponse(success, userId, error); + } else if (ContentShareMod.directShareDialog && !ContentShareMod.directShareDialog.classList.contains('hidden')) { + ContentShareMod.DirectShare.handleShareResponse(success, userId, error); + } + }); + + // Share release handlers + engine.on("OnReleasedAvatarShare", (contentId) => { + if (!ContentShareMod.currentContentData || + ContentShareMod.currentContentData.AvatarId !== contentId) return; + + const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn'); + ContentShareMod.currentContentData.IsSharedWithMe = false; + + if (unshareBtn) { + unshareBtn.classList.add('disabled'); + unshareBtn.onclick = null; + } + + const contentIsAccessible = ContentShareMod.currentContentData.IsMine || + ContentShareMod.currentContentData.IsPublic; + + if (!contentIsAccessible) { + const detail = document.querySelector('#avatar-detail'); + if (detail) { + ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => { + const button = detail.querySelector('.' + className); + if (button) { + button.classList.add('disabled'); + button.removeAttribute('onclick'); + } + }); + } + } + }); + + engine.on("OnReleasedPropShare", (contentId) => { + if (!ContentShareMod.currentContentData || + ContentShareMod.currentContentData.SpawnableId !== contentId) return; + + const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn'); + ContentShareMod.currentContentData.IsSharedWithMe = false; + + if (unshareBtn) { + unshareBtn.classList.add('disabled'); + unshareBtn.onclick = null; + } + + const contentIsAccessible = ContentShareMod.currentContentData.IsMine || + ContentShareMod.currentContentData.IsPublic; + + if (!contentIsAccessible) { + const detail = document.querySelector('#prop-detail'); + if (detail) { + ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => { + const button = detail.querySelector('.' + className); + if (button) { + button.classList.add('disabled'); + button.removeAttribute('onclick'); + } + }); + } + } + }); + + engine.on("OnHandleUsersResponse", (success, users, isInstanceUsers) => { + if (ContentShareMod.debugMode) { + console.log('Users response:', success, isInstanceUsers, users); + } + ContentShareMod.DirectShare.handleUsersResponse(success, users, isInstanceUsers); + }); + } +}; + +ContentShareMod.init(); + +"""; + + private const string UiConfirmId_ReleaseAvatarShareWarning = "unshare_avatar_confirmation"; + private const string UiConfirmId_ReleasePropShareWarning = "unshare_prop_confirmation"; + + [HarmonyPostfix] + [HarmonyPatch(typeof(ViewManager), nameof(ViewManager.Start))] + public static void Postfix_ViewManager_Start(ViewManager __instance) + { + // Inject the details toolbar patches when the game menu view is loaded + __instance.gameMenuView.Listener.FinishLoad += _ => { + __instance.gameMenuView.View._view.ExecuteScript(DETAILS_TOOLBAR_PATCHES); + __instance.gameMenuView.View.BindCall("NAKCallShareContent", OnShareContent); + __instance.gameMenuView.View.BindCall("NAKGetContentShares", OnGetContentShares); + __instance.gameMenuView.View.BindCall("NAKRevokeContentShare", OnRevokeContentShare); + __instance.gameMenuView.View.BindCall("NAKCallShareContentDirect", OnShareContentDirect); + __instance.gameMenuView.View.BindCall("NAKGetUsersForSharing", OnGetUsersForSharing); + }; + + // Add the event listener for the unshare confirmation dialog + __instance.OnUiConfirm.AddListener(OnReleaseContentShareConfirmation); + + return; + + void OnShareContent( + string action, + string bubbleImpl, + string bubbleContent, + string shareRule, + string shareLifetime, + string shareAccess, + string contentImage, + string contentName) + { + // Action: drop, select + // BubbleImpl: Avatar, Prop, World, User + // BubbleContent: AvatarId, PropId, WorldId, UserId + // ShareRule: Public, FriendsOnly + // ShareLifetime: TwoMinutes, Session + // ShareAccess: PermanentAccess, SessionAccess, NoAccess + + ShareRule rule = shareRule switch + { + "Everyone" => ShareRule.Everyone, + "FriendsOnly" => ShareRule.FriendsOnly, + _ => ShareRule.Everyone + }; + + ShareLifetime lifetime = shareLifetime switch + { + "Session" => ShareLifetime.Session, + "TwoMinutes" => ShareLifetime.TwoMinutes, + _ => ShareLifetime.TwoMinutes + }; + + ShareAccess access = shareAccess switch + { + "Permanent" => ShareAccess.Permanent, + "Session" => ShareAccess.Session, + "None" => ShareAccess.None, + _ => ShareAccess.None + }; + + uint implTypeHash = ShareBubbleManager.GetMaskedHash(bubbleImpl); + ShareBubbleData bubbleData = new() + { + BubbleId = ShareBubbleManager.GenerateBubbleId(bubbleContent, implTypeHash), + ImplTypeHash = implTypeHash, + ContentId = bubbleContent, + Rule = rule, + Lifetime = lifetime, + Access = access, + CreatedAt = DateTime.UtcNow + }; + + switch (action) + { + case "drop": + ShareBubbleManager.Instance.DropBubbleInFront(bubbleData); + break; + case "select": + ShareBubbleManager.Instance.SelectBubbleForPlace(contentImage, contentName, bubbleData); + break; + } + + // Close menu + ViewManager.Instance.UiStateToggle(false); + } + + void OnReleaseContentShareConfirmation(string id, string value, string contentId) + { + // Check if the confirmation event is for unsharing content + if (id != UiConfirmId_ReleaseAvatarShareWarning + && id != UiConfirmId_ReleasePropShareWarning) + return; + + //ShareBubblesMod.Logger.Msg($"Unshare confirmation received: {id}, {value}"); + + // Check if the user confirmed the unshare action + if (value != "true") + { + //ShareBubblesMod.Logger.Msg("Unshare action cancelled by user"); + return; + } + + //ShareBubblesMod.Logger.Msg("Releasing share..."); + + // Determine the content type based on the confirmation ID + ShareApiHelper.ShareContentType contentType = id == UiConfirmId_ReleaseAvatarShareWarning + ? ShareApiHelper.ShareContentType.Avatar + : ShareApiHelper.ShareContentType.Spawnable; + + Task.Run(async () => { + try + { + await ShareApiHelper.ReleaseShareAsync(contentType, contentId); + MTJobManager.RunOnMainThread("release_share_response", () => + { + // Cannot display a success message as opening details page pushes itself to top + // after talking to api, so success message would need to be timed to show after + // if (contentType == ApiShareHelper.ShareContentType.Avatar) + // ViewManager.Instance.RequestAvatarDetailsPage(contentId); + // else + // ViewManager.Instance.GetPropDetails(contentId); + + ViewManager.Instance.gameMenuView.View._view.TriggerEvent( + contentType == ShareApiHelper.ShareContentType.Avatar + ? "OnReleasedAvatarShare" : "OnReleasedPropShare", + contentId); + + ViewManager.Instance.TriggerPushNotification("Content unshared successfully", 3f); + }); + } + catch (ShareApiException ex) + { + ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}"); + MTJobManager.RunOnMainThread("release_share_error", () => { + ViewManager.Instance.TriggerAlert("Release Share Error", ex.UserFriendlyMessage, -1, true); + }); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Unexpected error releasing share: {ex.Message}"); + MTJobManager.RunOnMainThread("release_share_error", () => { + ViewManager.Instance.TriggerAlert("Release Share Error", "An unexpected error occurred", -1, true); + }); + } + }); + } + + async void OnGetContentShares(string contentType, string contentId) + { + try + { + var response = await ShareApiHelper.GetSharesAsync>( + contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable, + contentId + ); + + // TODO: somethign better than this cause this is ass and i need to replace the image urls with ImageCache coui ones + // FUICJK< + string json = JsonConvert.SerializeObject(response.Data); + + // log the json to console + //ShareBubblesMod.Logger.Msg($"Shares response: {json}"); + + __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", true, json); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Failed to get content shares: {ex.Message}"); + __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", false); + } + } + + async void OnRevokeContentShare(string contentType, string contentId, string userId) + { + try + { + await ShareApiHelper.ReleaseShareAsync( + contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable, + contentId, + userId + ); + + __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", true, userId); + } + catch (ShareApiException ex) + { + ShareBubblesMod.Logger.Error($"Share API error revoking share: {ex.Message}"); + __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, ex.UserFriendlyMessage); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Unexpected error revoking share: {ex.Message}"); + __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, "An unexpected error occurred"); + } + } + + async void OnShareContentDirect(string contentType, string contentId, string userId, string contentName = "", string contentImage = "") + { + try + { + ShareApiHelper.ShareContentType shareContentType = contentType == "Avatar" + ? ShareApiHelper.ShareContentType.Avatar + : ShareApiHelper.ShareContentType.Spawnable; + + await ShareApiHelper.ShareContentAsync( + shareContentType, + contentId, + userId + ); + + // Alert the user that the share occurred + //ModNetwork.SendDirectShareNotification(userId, shareContentType, contentId); + + __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", true, userId); + } + catch (ShareApiException ex) + { + ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}"); + __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, ex.UserFriendlyMessage); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Unexpected error sharing content: {ex.Message}"); + __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, "An unexpected error occurred"); + } + } + void OnGetUsersForSharing(string searchTerm = "") + { + try + { + if (!string.IsNullOrEmpty(searchTerm)) + { + // TODO: Search users implementation will go here + // For now just return an empty list + var response = new { entries = new List() }; + string json = JsonConvert.SerializeObject(response); + __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", true, json, false); + } + else + { + // Get instance users + CVRPlayerManager playerManager = CVRPlayerManager.Instance; + if (playerManager == null) + { + __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", false, false, true); + return; + } + + var response = new + { + entries = playerManager.NetworkPlayers + .Where(p => p != null && !string.IsNullOrEmpty(p.Uuid) + && !MetaPort.Instance.blockedUserIds.Contains(p.Uuid)) // You SHOULDNT HAVE TO DO THIS, but GS dumb + .Select(p => new + { + id = p.Uuid, + name = p.Username, + image = p.ApiProfileImageUrl + }) + .ToList() + }; + + string json = JsonConvert.SerializeObject(response); + __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", true, json, true); + } + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Failed to get users: {ex.Message}"); + __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", false, false, string.IsNullOrEmpty(searchTerm)); + } + } + } +} \ No newline at end of file diff --git a/ShareBubbles/Properties/AssemblyInfo.cs b/ShareBubbles/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..06006a8 --- /dev/null +++ b/ShareBubbles/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using System.Reflection; +using NAK.ShareBubbles.Properties; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.ShareBubbles))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.ShareBubbles))] + +[assembly: MelonInfo( + typeof(NAK.ShareBubbles.ShareBubblesMod), + nameof(NAK.ShareBubbles), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.ShareBubbles.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.2"; + public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc"; +} \ No newline at end of file diff --git a/ShareBubbles/README.md b/ShareBubbles/README.md new file mode 100644 index 0000000..27e11cf --- /dev/null +++ b/ShareBubbles/README.md @@ -0,0 +1,43 @@ +# Share Bubbles + +Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. + +### How to use +Open a Content Details page and click the Share button. You can choose between placing a Share Bubble or Sharing directly with users in the same instance. When placing a Share Bubble, you will be prompted to configure the bubble. Once you're ready, you can then Drop or Select the bubble for placement. + +While viewing content details of an item shared *to you* by another player, you can also click **Unshare** to revoke access to the content. + +#### Visibility +- **Everyone** - All players with the mod installed can see the bubble. +- **Friends Only** - Only friends with the mod installed can see the bubble. + +#### Lifetime +- **2 Minutes** - The bubble will disappear after 2 minutes. +- **Session** - The bubble will only disappear once you delete it with Delete mode or leave the instance. + +#### Access Control +- **Keep Access** - Users who claim the bubble get to keep the shared content. +- **Session Access** - Users who claim the bubble only get to keep the shared content for however long they are in the instance with you. +- **No Access** - Users will not be able to claim the contents of the bubble and will only be able to view it. + +Access Control is only available for **Private Content** and serves as a way to share content in-game. + +**Note:** Session Access requires the game to be running to revoke access once you or the claimant leaves the instance. If the game is closed unexpectedly, the claimant will keep the content until you next launch the game and connect to an online instance. + +## Credits +- Noachi - the bubble +- RaidShadowLily - the particles +- Tejler - the bell sound +- Luc - the fixing pedestal api endpoint +- Exterrata - the loading hexagon model from the hit mod PropLoadingHexagon + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/ShareBubbles/Resources/sharingbubble.assets b/ShareBubbles/Resources/sharingbubble.assets new file mode 100644 index 0000000000000000000000000000000000000000..339e6295b2715b05b3e24ed6ca24f1734cf77191 GIT binary patch literal 1293156 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⁡8 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

8F!{4c;V@O zgxTE88xH)mbw2NV#GRX`epi8RhE_lM7FV^bA&Iba!eTiVt-vCq3vV5W#9cmqOT93E zl|nXIt^~D?B?$sDda7hv4M_c*5_Qb?9SnEcw5K@jVvricG*1)VgiaTA_Z&M%B*+yP zwGJ?^d2%l~4L#RT;G7DmbLF9bS6?G!DWryX%P_X>H_(gY0hn#a_3ZdA4hcy?? zUTA8G)Qx6_1VT->!{WpJrN@_WHpF82Ildh!%wv>vhFM9KB5FF$^$$OvB14!>E!DucEe zEtqk}7FmG|&U;E&x3Dw}o7w^+G5r?e{p_`oEot2@s=wF+1c$x9%*DokZysj6yty6K~T!Uc^025Z|MpB#=CYd2q_@KO(nNT};V#uV-s~2vmR&NJbTz3c>espHA-}X676*wG;roN68k zbShxS>#OWe6jrUMI~ta64wRJzl?`Nih}E)r8QXinP-&+A_7-Iusd6Yctzx7G=EmR^ zwtJYpP+Y<9zMWvtyT~*e`cr;gvnRp@nSytpZ>gddL1aAF?7Pq6%J%WqJ+IfVl%RVH zM6AVh>{*qymd?--A??+)kG1i3uF7_TqhxyGfs|7*aSXYmY0226-+2&XoLT&xUN_1L zG<+{QQUxRqqhSX&sg8d8-8a-?3YGRk!x`A8kW1e7-7C>$Vaj$trM^LFOdY_Zs!m9+AS%vw8o{oem{r<~ zFB}gjh4ZOi+ zm(jRrCRIR=RSDip1sqwcnD?GY;+Ra19-cz(q-%s?8W=ASsoCy7em&>h%s@=mUTLO1 z8t*bbo^oqOi0~SBAM>$YY2qAJ`~?A=F*JOED()CxnRf1o*+m@kEUxKO!JlL6=o`Qe z*4v&*w+Ez4Y7*XR;Ad)q2nG1(FbIvMbNXTGkux3jP9~+>Qv~yGLJ&J{%H~dh{ zjA*w9(xmo{fNILW>6PKY$7Ua3M}Bcb6R+5(S-x{b1pa?sQfFQvP zJ@gd3Uls{z#rB|H6q6~uCY_qMmP9vc)|8D6INWZ?!@NQ4mIL4;fuOF=WXiwG^%0xh z_n@1zSYg=+EiL1)BwGZI6bn{6f%3UB_sOvtKql=+ZE?!Yp9Zey2M#W3G&UEh5Y{E~)pM6WsND3cU%7BPmh(dVp ze$DG80l_?9nKdM}AXf%`l4GrGkdea?jCGPKK|TdlQfgws;4*1MsEa2m@=f6krVOJS z9c2P6a!d>>5V~NKep7ZFfJ(;F$7mzaF6-PET!$?n zZTT1;fhpKI=1y2b5cv%%9?DvFeKPiXF1fdZ_zrUp+GJwAxl|Ox+;LCJTA6CysiE#k zLbfdH)L-=|`!SxTfSO0JjnY9NF|O8TM#xZE$Z4dJ8 zjrmIq)F*`g{jHF&mf{YeSi1GoI+6~cV~{l+pkepuZmN|KzGOZ)-7iVH*aIHBtX>ry6j&ayHa{9~!g$S`QZN&pw<1%>Fh zQZf&`Y`R%zku-T^>(_FtQ*`c3zO#eOO7;TcxMIu;I&_-v1pu4lgCgEJDS-rZL2eY( zxs2W(K8c$g9%F#XG}%icVRm$!=h%T*G zUuKdiV9WRIw=OpxS4w(~)zql#p#?vvwC2 zMd~ns*Fdl;`$b0R23CMQf0u1kSQ&tKY3=Emz?PBZv1;Yl?*`AVL06+&M7h(~%?F}c z%9tpoj&k(#N63JfCu4(=v1$m2hUeEEitlek1qT6LhYy?1+`$$M= z8PVBE9cXh>i3plqbn6tAR!gV!8cF-9p!O@HL<1pj{+CQcIP7s;j8~gYgY~=CE=og_qj`wZs=nqi!x{EZ2;)d;I##@g9F#}KEhz* zf{UFU zpZir)TOg|ZK-!Zb+}u-Yin|BYu0l||l_RD;{wekce(eVQV^PJR^QZezOi>L0nZ>5L zp{4KgA|OJNP(I;kb^?ZpoV<<6QTDH4!r~#SS<= zn1Unk+~u$|@=kI+}ti2N7;{I70jXEyNV^Wo;zeB#L0cvwiRA0Xem|u z=RV0<)^bIvCjyal_sU-88xa^4N0L<26rs@Q#1})MT1Yg&9_)AH&`$#@PI?B#N zCSXAld}Lxzj1aTYtK{;MuVLtBEaUfux&1~WVI0o@A!Io~$RfwnYah9#Hvz-{Hi{&o zvu3}V9g3}YT6rOK!juAxGSRolb;&RRr-CXnnOnN*!wI#XiWAZM{iImJ(3989YM${9B5d!^OY>wSF;RR{(gC-#F_R!f`X+)IJ=XsF72rqhcia0^>l=~Cc41|qa&GDnn2>4}!_I#7L| zA}>Yi4l(}Ce)(D#Kn#AWIr^W3k3U<~Uvm;&=-~ZC&`r504qizylFxWN{}*j7u^t&C z&l2;37?c7U!r&ybYPXWp0*lE4EhSiVpC%8MAojs`XE*&egkIwsU1H z)awHm^7lH8t5;Xj9$Nuls?gQH7|t@1m#Gf9|2gn3U4kLnId7P#(1!p#Czpy?*`fzI3rk_ZsjRxYURS{DoChK`m#~Bedgq7)?6Afaw z65m;YqDBKaEb>`f!3`JkcU$2$k@>6&*?%3u-8T=XBj@q>S^3^C#yj1$srCIyO@6$M z#r@Nb7~E5W5ro^Tp9^nMOLKvn)}PHt8%S*Nb~LtQj$rJY1~gK0QsYNkeK?M~+)C$t zxkXG#YcZdmChe{Zl$S14@RCQ++UtGICf35yrypnaO=NRmi(R2ty4hVl^k_I{x4irAuT%uIDd25$D~eL@pA4 zww~z@aZ0B+{)cCw)sBd$T4&I)67WZV20CG-svUD&E%IH zy&wr+@R!4#6p+U>PtdIK!Urr$S0Oe*-LMr!<=-#Ifx7oUQJv1n0;%j-d3Y8Bc9WSI=3a^N1-)ix&iB73=s@z{b26z{hb5K{@h*x_%}2|s zJnnFxMWs!Q&;e1pbawtSwF~7lx2Wsw`-tYz6JU-7!CgufF{l^E3cOfo2`D}1>pfqq z!U}NW$jx0w1GofOG{rEBt9@*IfYRNoisgrh@+e3Mi`t^8!>eip7q6ewrGX1X<9>t& zIM9Gy>A4}93{1oQ>o&^r&b81jra3&4DGU+f6>gTXfN|p?A5Yu3FNBqto1nkII~`!6YagX2%F`3UfaFRF36Rxt(&kr3_KCoW zQ22$&E@A{QR;Zx>K~&D@0`|N!aUp@lYjFir_AuAwM8)$h!RG9L^LE2tIT~T>PxLTO z{Nna8MIxszf$@f*`N3Sh;JJ&{5tR(kzI6$itxvcZSA zPM4PR>+)cjT)oWr+K`Y6U6!!rbn*p*ymNtG(7vW?QEDRSf0S14CM*o~Du`pKI|frG z3+R;IM;NtFcP`cP;(C-q^-P|8S+`GNjJOiJR5G%dB8aABt2yaIYJ*Iriveza&}KG2 zdNyQZQVddoNsnp6Fk_do<34PLo>+u13yVx5eU5;4O{(#I0TfNCXlRXkh}OhpBv0Mj zO9Y^BUJ9Bifg#8$jlKxhTUC*MWfh%|>E)$bL~0b~k6i?x@W^e+*?D8GhKR;PH>r9+ zAc$k~a3(C%YlE6h4|lX~(O&WuwM5N@Rm;)nSHH3J*adIabjElT+ZLa(+Cnv6z`z@b zeqM8sT+-2NhiXrk+u%Wd7$E%1KGE-X1QNm7*MosA`N&ja3?e`Ukz#;xYw;i`=mNB= z#a|CyPKl!AR}ky+AEh8Gql$Y!1tppdsPhMkTxig3HgzErtR8V#8uk=lY8@LlE>9fc z=y&R%+f7EJze65nLy9-j8$fziE+i6Jy9vkGnXqiXQ2r-hDaRE1V9<^p8-$$_5B#Sn zO6ZGUxsS|4T^~7QF5G>!$l;u_M`SOcRn+_3YY%lf`l+%Lv>x9XwI}P(K`|q-*qsBu zjq>(4R+ltG)Y!=OUw>s2!?eIr0ZBHjrl5QyO!(jkIav>jilQjnZ^q$3E{K+(gQtd* zQ)I_fQXrWGKxBI@=miPhll;Lvg<8K zYBHba7Uu}V6}&dxUQ!p^Qtw0^6m|?=bo?uJFjx~+r&et|vbs{p;yh`maZa6Uk;ETq z=0k+?l3SM~80`5FDCgHH8kE6js2=dS{seQ3B1^OH0(lo%Qm)~gn6`md^ncx?QgndW z5VIaa{54vH-&bpsjcm&;>7I1mmV5ZZbVT}+!Q-EGb=|2#?2tpuh}K(H8RdP24xy7!Vvzf+9Jc_T#H#)V;M z{Dj3WICBj$WbTMNuYq_Eu9K5B2ZX&M^Jd|59}lCzA<~#GVLIQH~$6fy+d*) zQ?Wj+=qTDb%!8jW=x|gbnTB=trjYtLa+}=EZq%*qHOA{GDMonpuJWzpk&neM_Q{X- zXZKmna(Levo9S1t*;8#FBRK$lhhX>_nAkk5>wY5M5*uQ(LRC1K zgvj9s#jQ(*#IhyK4u952i>innn!6XIl5#mn5q~hgse&Z>UC&cQ$fLaNV3Y1*`*X0G zsU0#cI(F%S{~4PSPl2G=v&<6s{dxmSAQ!^cp?iTsW80{#e1!&ydUp8~wU|j2cIf&J zW+vZ!w_G`oXyTI~w)4UQfp~O}WrG^56?dwh?sw!+N`|>~_*tUr zKLw?9gwQMMN&X^f$y^D&gf11urjq_jSbq=B72cu8-#EimZiBbz$D#ZNh;`7CDcmdb z@Ekitv8PQ^CY6>zJ^xtzC&&A|WM1oRz1@x1rmA)^UVF}5ubKx>B#^rL>OS4w#2sRV zmhz|fJBH;oU3u72091!gV!~LOQ&CA`j1IkptohE07-JtDLJ;5Rp%M+}!k7f_2mM?C zNjKAW=n~}xvDt?Hj25oyPx-8oV0d8zr(`G-NF2Krn8CC=NqFwiuBrrFu*jmA2tjP` zVaPw!WOF9;kAZWh`tv4j4=uF)ym8YE(rI^oB`)vt&ORoDVaC+}SzQNu&6sbyF-Ps@ zaOpdW6FqVJUS_P(|J3{50%z0g4Vd-z0mv!b z4+#*~=$eV&YgR6n4Q-0lo`R){Xg0_HTN|cBnzGz18JPulxA35WdFqjtSc)4LHQCeQ zmjN-{c$Q2|NG6RCEYB2VU#Kb3Z=>I8S;{XXPa&K8D4{RWqh;3_%*A}R%Lbii=rCL9HnQUg{Ec`9(=VQj4ggsiSdJF3 z#yQBZjuXDRXc})rd)I^~oz7Lu&v`wa$8?GJF9);+$=bJfv^&CyK>s79bK38j8m}OnP^LOPMu78rt&R(Wi zu~U;qp4X=1)7e8~c1Hx+uJ9D+g6ndG6CMSECL^({$bnuQWWp5DqQ*7j zuxLeJC)yP50$xSmdgO|e6WimadqI5j=wI}+{#y)E@nrLQVSO7{NwVBvS~sxKB3kT@NnX1Gu(Xti^kqoARiCUmfjz{Y2(${@$uz0xKHCE|25W2|x+o4a zvLhj?@hSegiFCD(+szkkJN#?1wrIY5=ob>D&m<+_Ez2bBRTCEAiRq(ceDsUoA@Hpb zco>lV*u)#C+P4fb>dC!u;F*rn7p0UYjG8jtB-4hx( z859hvet{qs56Z=_F7>n1wL^C@FEOr^(*%vz+OG;~X;Oe}Wq?;`c8t8o)3RtI^%~f= zv`~@mC2Tge06?>pa<4c;1Wbb~eNrwu3TIt;47h6s0%0AUJq+jKO7VdD(mD`xC*}h){3W{I~xct6!GxeA73}+TV zBsn52A`$o|bYkS~<{Row{)73-3KI*Zkyjj~<2mIK0{POxBV9gp>4pkJC^|K)Fo^_XUNv{VLSdj}h5v3jUQr?vqpJ{)!; z7D_U4q+i|^1FI6rCS{<*fhTmN{>n_)VT1-e<}9ygn*suiiJRScz>pViePNk*7=zjg z+aV;S6S!vMbrKn0sd08GV4Rn&OkW!Hu7F+!eO_f`7XpuGdB6uYjjBlEm;Xv2IOblU@7O3BADRG(ucP(6} z&{7%-x+Q{3ond3bzS48}Z9Uss>Vf2{DLa;)xea2h+KN#g2j?HExY@1B0SbetEu95kb8{gF><<Mg@HC*r8W3pk{9I#Fv$$%jxa9y7Cd z%};3c5I9ZMj^t--d z17Sjtk}3z9$S^q;VIef>_wB1zsYBnkWjkCr(f!RDTy@&qDJD9{YbGq?eR7>YqqC#u z^p9H+K#8wm)~u({y${Q)X5*?K`cI>Ci2En{W1K+J;leMp?8*Az6tZKCAe4tQ=H(zY z|2>Q~3AIx@;Gd6~mw`kA&6KgkeT@54N&*fShj>i`>~rzf3KmB&p`g)~H~u$jM- zM`j~!+Y27|#j4A*)lm%9_BbXzYNtN5SCPW=J%Z74nJo2tGiXTWe|KQ`vV&$u67bz^ za>O0EncENUzs^JZBUVtPuE!ii5HRV89Kw_+g8Tgn-=(i;g_qAk^H_@7wAO zC&?71>R6<=9Z@oQb5Y^-SFQi*SaVixUw5mb?J0V*L^cR>eF~!cit2cFgnY=VgR}#$ ze8r21aI#GvfRXhUrea1ipkv-u_E!IpyvVFgPcxQn!rDL#vj;cR7!c=H?&74_23>Pr zf}ClEQ)==2K(9sS-Kwr7kT502b0EUHR|2k*&m7;WJGO@COQSmY3Iu4?CeC;l_u8K>3?79 zj){@|o)OgfckDmM3Qwc6&7ARi2m5Ck{e*u@B9AAAJjmnq`M(^3YuhUMtcKi8g|%S` z`)&-e5u+kS2P3?A@Ll`2>b)il^Tmbnbl-ssd!m!;$1BtvZ7#0}ph+HmB1)tNA~he3 z$}L} z#Gx8ag{9bS&l_#xYBHn?3)_Xx#CL#Ng%tTNSWaw4y@8&3nu!r`8dODgRvtOHoQY~6 zv!+T!khm%Fv$B6a92mt{sG@;01m>iAiuNe6Q` zkA!ki!{4q4?FaZ$=tHOsnUtw}ryLf%k^@h^3OurAKAIJ{=v>M&1#x+|)nPxglNrx;O?U4GIFP>H9=rz@#>As44X~bnYi>6adqpd zXC@oPgXQP;(GQ@PPc$!}Y;Od_K5oqyq4L)QR*gO?nB=b@gdNKsWNmOa33s~CWwexG z(Fy!9k_4Y!^j38~W0Y9|}A#3-j&MzAxhAf~m zeL{*zWSt)!CtN#e>KZOAmBB->bVXclmOFrc3(?mL44eG(eD;Gfvf?p0h(tyg6?Aa7 z@grtloNQR*6HSg{OB#w_IHPG&TKYnGqIr)N|5R0qfuN!Twn8;(I-AHBW@cXwgs}+h zO_bdXFF~yyxpK@cC}mRGy)1DX zh6wv;)qsHp?j#v$*ZEpT`o^L-Et$aUkU*pKG9Jynd(4NkX{JvPSN_9n--lys?HB0# zE`gzo@as(-H6I(6^0zm@ZWagXQRhRo?}{-P&nyudBuNsCpPfBZ?h!u-)hDu)@~|&B zyYN+`%oimrR`$FTnM?;l)MCe_ttojkTjcA{48*Cz+fy&USNPEGJt|Bc0EEkEupx?C z8o^1$s!7Z{GGd_}*URw6#Qk8`M5St>YeSgcJd$Lrz<2u+3PvVeZHG5{eVY8%j6J2_ zN1QvE`fI?_0Yb-cC#j5k;#uPig9I!0UsCLR$M^Nd7Wr3J!ZPV9yaXI9N+nTFYHjZs zpEW=&pc2`z(Rb5jpvgs0vag*+|AZ%dF7B9&yfg!-7c47fJYQmF)a0C^=lyE zMF2D@s$<;qLdyb1!K%>|qNL$Iqx_)ao{KKUy4&oS^Rq^eNV?MRGKyw`;|7-X_T>E` zwI@dM+%vXn!|OUX^+kt~pgK`mP@|ZyT#*|iTNA=sy@y-K>iKIEj{mO|(jYhQ<5c_Q zT;ivkk`1@|KTZ9YX}1U$A$qn7-pjxCzh1A>CQ6M}Y-`Y6zBFpop>wfsr1PEyL_lop zA6aj0I`iWyp|Z!Z%Fj7;#*tWW2vuJT!QiyoaWW=m2K;_EmGLPvAx8)E`=Y-!*eJLM zTh%0HYcXW=0rJja;!C7%lkf9plPf7)7J42$aGCu2m0W!-LPU@aGwWOcx|tCoy)X*s z6;@t(K}7njcM$U&Enx_94KD(6a_Hr{ffC;o5!Jxn&@lLF{Fjnhb?%!Y_xL0iQ{f#rIV0|L!JzVHQyD}$DGQaV9 z^!;7mAn9;p`?JQaukOupZRgAwEX9~kwiN&J014-_T^^hbv}b?^2f~w4<9TN9HVps; zg(u=NMJ5-R-cu_9a*Qq)uokgonp(h_;bw`{I-_iNA(%?SfoJqb_g%Q=0m0&@B5eyG379IEx z+gyC;h3Z!ZW;M@KkPYD`nsFk35$b~MulChH?mC*oO@7h{jQk&~7$jWVSBbGY=VQX0 z$%phi${$V6b2L=H&^HA?snPbBlsqK_GQu84D`Yr>qSnJYG$7-qQSKZ;afIvvwPylP zlyf60-K|iji;h9(Dj`tq?+qS85m&P8v3dAv;kOfSlKjBD?TF-V4O%=sYwv75JgB@w zN>a2lM}i;IJQpfg=AP;yiIy?KUT-WX#N2sS1-3Gemk<^Bjqc!Mi`6R_Yr& zH1n+Py+u%~C<^NEI`*J&@X)D)9=S1$E5*D{?)gjgpVwR?*nJh#PKp4Co;X+pz)=n_h~KH#aeSgIlGp=zDnq& zmv|-a{h+rg&Bbsu1Eq958&qOAg}!tXh>@^_t=$Ga5@gSIsUN&uRTQD4dyV_D88n=V z7$Ys+<^6aeL+B%!=MwYj=r@W8v4OC;zt6CDnm#rYA|%#k9Dmzg`eKnBoSpdDwSAIm ztG;oy#8IW9CTJCQ_#_vi(NHhK&&r1L=f%bXDG35r2|9zujp2>cqQvqkz7KV_)Ep20 zb6c%$b{SL=BP9Vc^7M9(bFB@Hz#j4f)IPafJ!@fTx@Qv!#@iH$&hR>@4h-xHG^e@PL8AJ$I0Jr+ zq;DZ3ozoe3J;w6LAa|MaYhJg18XuJ5NX~FVf8&Pr^xVtzZkd~o6Vim~*VIRS8qP^W z#|rqQbzVJ?@kSorE)g{`T)sS z?4OGZ>E|&Y^C;z(CBitenH7_+-A;hJ!M^`?2PT^zPZ~W-dw_ySQE?$YN7)K*<4xGQ z1r#;V4RQ1gy#{z}yY8vfv<-OuPP104&r0RdXTgZeMAppl_Ij(7jMu4lz$QG9iSe*m zj--&PjlR{5*-&G$V*&cYyMo_vTili95{sNDt*>+T>_OINwj6Esl~yyky0Xjt;2+futdT3d^LYXOx&g;_I6kjuHyAg>Wb+9z1Tg_LC9Zlw1eVfu7w9+AeS z!W8+bGNIgeC1}BBiZgazT4G6nmkX4QhIFK_-ii(4MS(+nSs-{6hiDipVDSOfJx`A0 zJ~;1fb1ZMlCiqeYtjbtq74-k_V&Z%N<3EkzotxNe6XO5Lf@MpPTP%2b^F6={7Hl109EQ3UR+% z_*GO#R^p5(E4v{=(b^%JIxl(nX`?s+&g*~zm`UM{Ic@FPArh%wY(|GI)6@>v5l36} zrGJ6NlmNn*s(?$Njxl!tU`|5_W?ZnYTU=;A)6D8Y=#tLqKBhHmF}}0C=Ju>-@LE2a z1T`xnT>5ZzTDfV?UPFUB>v)Q~QeLS1RmcymzcU|!wtF{b*xFjV-4u5@KwsnGkueRDbbE1Km)F+Ie zI>-_xVjbDu^N8I9I{^f&vI6y1G{JraLL=bm(<^5!1??U#3EHmIGhi`9dDqSkn>_{A z&Ck+$rJgCvjPXE5eNsQ)^2S}mK3mLrjJ496nK>+nKaKh$zHv_Oa-lV*yhZT;xah9s z8_cEOB^eTjlo|>!Lhq4fVb?-dif?RXZr2X%`g{=FgTtyPx=Y+X&R6tk8mmOV4t-D{ zW-xoh|Km2yKcR91uZ?NZfh-23`-9%7g9(goZMR}S++Ts312Uan62S*-`v&6D-J(Oi z6E0T9ierv~a5>!ottG;Pmf10rCi58^2iQ&#lLY$&qvE_VrqQcceL!h6S^?9#3A_99 zC8KD43r;HGeQS??S6;(cS`ICcmf-j`_R4)lM}2mK`Kmrtr4JMVU0c6RrS+VNeE7~l zO#113lt0DpuGUQ8xaLYVJU$=T-2@a>>PF-i?gjA;9`U!D$h7;J$P8BpJ!vyoYtC_h zt68&wBHVLA=z{e}y{eV}X~Ux#q5J$OwjS#$?U`2!ef9-egXCwPU_6YTAlw-rnKG}xGW09+Ea0!k8bH_pPvJ4BCy)ssS&-N4Ue#7 zVJ}q@3{!%cFd=tqO4m7fJm0>Bz5QYDN+fjJT9e{vpgEZw-5}NsNNa&wURp#L`ifd$ z5{fQv-MCdqb{#W}?x3L;;aqgY=z+>n`G_`;T_dS5`DgpPuFtW3M^NZX9z671?D4JF|v=jxtF6wGge zyTLIN_9Q{UoE4oUlzGq2wbX6ToP?fPIQ(E?Yec4}x^?>KL9%Ien0seVnZt!eN*&Gh zuJD_TEX2j;1hyZ`{9g#m=#vW0_4K|GZn)Wq3&H^5`dzW?fNEjQbJ|9thZ4S{B#=Ol z_hqJ6oZj#n;`RnD#fgR}-s&8N#pOVBhAwjO4l`H4=IRpUHK+E1ff>d&PKR!D^%-sK z@wUtNh72e!nc%V~H*JO;H>!HfUa5j<_%sMTvn|FLSW)PoWYVTr5YP5{SR8Yzz!KYw zmOR`hrH;}<6+gfp*6XtDS?UU)=|RRNDs_+l-R5rq0h(5ykAJTXo|zRTizJN;DN-l0 z>gp?IqY3P6@~tQt;qA?so=Q}UYHgn(;ud9_(#JBIok^$l^>0EhXdJF;DH|#?Vg8sc zmwrMH2SF)q$&o^7&geLGhxuO6U=5@c#P{IaG6oZPl>CRTV&epN%$)j14H4Hy$3=ZM zdjIDTi5|?I)sG5lC?XZ`RnqgwaefNN1GtypuC$(BJX-inJQOxSo>PN|4JpMJ%CQa< zDn5bA%gW+u@tgL1z&$PY1nQg8WFW=V?0H#9xQhXRTp}PzNH>Gkcq0a-bAlD9A(ucP zaOg~_po}mx42f$V>i0K7nygcsY+>=7Pu$5pG2}vHWUUm&SGpd+hm*?wt8f!^%zMB< zEZQ#HyD(Z(enK+R^KzYBKfE_E%+f-FFPHaEe8d(JWl5u|D>b;2 z_2+>3TR`P+OP=uSdQ~<5DMOvYaati#4Wzg)ItqOX80Qvqo-Zu|t0PSgKDzPKAPz14 z=RfHia)0oo-EVxhOkpaQ@+jc9)JBPstmuq@MNfipEhR1VzScVf4$@{l>g@0?u-x99V<^oi5W5WN4Fi)txJ5 z)=px;uy+;N_pa_Ws?x^w4+_4MNWjG4*pE<$f$X=F8%Uoh2o0>~FlMTo0&5S1^S?R;O1R zImx(0p!NBcI%5i7Kh+3Tcq%--5CbP-;37@qf+zw+9{2F-3VBxQH3Q6&kS&tbSQRYt z?Vl`S{s?yJYXN_(Y(I;qI(N=hLxta5PCNo#$hgE3U6yGO8UDraH8qb($hzTaiD(bb z)K5yD=ka5(5X5nri7(<=3YFv$f77#_FL-U=7GEx+onk;6OD|7ML7aE=RVHs`_LPE* zY{=y)46ZJ)9WyTT=q>g>!Uz-Yh1QG3eH0|7^^%vFOio)kT~mVRf!e~Bm!!0|@MhQ$ zYHE&07jDj1Cs~Dqy{g>*0!btcxwL$3*0Cb14IZr^$6n4VeY5(-=0!hqvB3SNx|0AK z3p!q)beNky!O(C(hVYHjxKz@?A$&9M{mkxQ41Q_*lw>qd9K1H)wmV&_o# zQaJVS<>gNjexS?mBWA`!qBin(6lEpBKs?k%5`@mjz4ecv`w}uo6}xM8b-a$NkvCSvyA`HYsuJj8s@TKMMq^$i;}Z*Z*n7WvKQ(tWT*LaCGTp;cKVmB^Nv(q}sG z;@7^V$e^rM>kzth>s{ZSYxN9A(PrdunCDhZ!>UnW?M;QCG*%LXq|qjt(_W~m>b`#y z;dPBb&FClk^h6}63sxvZpe-N`Jw+8j17L=g6exDRg==ByE9?e^4iqaD9m-EodX?Y3 z97~T1|5@Y%z6!A}Q(ils#N6rhG50_-X>Dyqs(|IVq~B=l*gwq4n}f8Z-OW0tdYiN{?#v=n77cxUpKi@Y@TmsC(WU!wg}yaS%gA)=nxkDvhs_tx!fWIA}ff^wv~ZqMSur!j;?Ipiu?olH{+eH zT!0gZ@Blh!j46kWZqpM@A!$w71QHA&Y?j}%*P#Li)fe`Z6#M3{un< z`kp9q!<_0(QWSQ%2QnAeyjae@Im$+25$9W9IbIH%^hpbz%Ug%(>kTV~qW5n8Wd*JW z)bC^KBgqLcN*UE_y4zx{3$Ve6*0pA?gO>(wg9IZe*5mlk0pfu*0Ei3b2v@4Y13m;i zQeW45oznh0lY9bZ^IsFl-08T6{s+DH`IZnkIUj#yBzrZ@v7xW!Bi7gR!H(}DRTbaM z+2Uf4%&padYzbX@wO=CqY%P^gc@QX4A1$^WF1Sosq```66aP8YZSmND{eIB)u`sl- zpG21HnX`fmT;pJ*%f)m+zCxcMYz>L(Ps%e+ElsrPC)Hr~c!tXd9f*{Q8qrqTiXS{g zxhegWKY>;}f_uIO(zt?cZxV6ps5#_1#YZ3NnQ|@?7N8zty1KGXXLrKo!%5?BQ`UP9 zlo2t~ab_Q2LQn8o9Bw!vuP)C*QW)Qt(*eFRm1Z@N0O;Vy?YmDEY)4)`9B!aq0N57>M*|- zi{Fv9@4BvDL=mKX)3Dx>btS@Q$i1lK5Ze|)|IvcMI1>yiy_eq96;OURkoIn32=;}M zeF3#%QMlU1$|dI(~1w&lWhs$h`FL{ zaSrYA9jz~7=0kI%qlfqpZ2`V=)ifHw9QrZ;icy=PVf4OU^YKxPsJUGWep=TK zsW6TTh~jn*K;HF;&RwVtBzCUGT5Q@G1r>Zv$UGFr!YmIGtzEr);FNyXD>hfJ$K;58 z%^Nnr?3#mWWn8Tkys}ER)Fsv|Zb;A`clknuYgLpwRc=8^l$$;s?*FJ(p+KT(asBO5 zoXBEDw~xQZrcTxj@_M_QjR39}t0VK#jNe<@lh8!FZ%GnzbQ!IaKG{4%*t z-0o+9h+*iO__fu>sXLcL+FO=NAL^fHsx4i_lh=H93BYyh|o7?nJ#uxvQe?tSIZ87VcJreND`u~z=hv&F1n4#T0Wu2kJ zFLoq`Al6)*PPCk`DJHS5r2rS>NHuX`vyj5(U<$hhy*3npwtx`&>HYXt09a;IOV##z ziwVuH2Q{#2`wTgTDIUkm7hxrGn z4#E20kx=hKR8;GQAm$`}e9{J_RFm*1)mF3Bg7w5%PH|lcxJ=M?yk5u?TbBoscUil; z$zdKfYfHjN;O3Mx0m>l@Optt91cHXaA%g!>4pvKj;;kVoEkm#5)0O|xcF1Z&Ui=QO zLs_~rtH3+9CR}~q>~2ZA9NZf53YuDYakEkUN5~??tq}UOL6f0BSB+$2IHY|C5rjj` zO&x^U+Z+WX{x{Z`E8$VAf2!mJnaXW2vEg&Xgjsxx>{E;(p}OBLH^OYp>WUo{RyBRn zK-mrX_HCxU^aXIp9jr>70V_51u1nMSc#(_9 z(n3Eounyg)C`8EIw%3^Y?x(4i-|QQpn8HaRjT}$)@QE92QJ#@|xg5gWX8~F!nXnzU z4-LvFo@a$Nq}OfDNb4W?{azt>V-}jqbw4=_iJl7dg!% zFpAKhYI2`ZF#zge^F*XZ_*=fh5dg9-JIBKCS|b2$d=CsZ(`Z&FA`)@ftMe(C;qN-c z#)exP&Iy+?Z8h+~euqa;>3KS39nNmK zWdQ|K#%SJQ+atm^cAQrZhMFslc@?)jO$`!0H1)APLv4DF0eUNgT^M*`J&B7S{-zK9 znO5Dma{o^M59;t*&{h6f>V%tkMSPN8Cq*ZD{IlOjdPZklKwz`97;3+Xv4fi@kIM!* z9LLO%aP2$uQD5Uvq?R{e_u;vbup_%gRD!HRWejWk(~|gVo6SGC4kEuFT1TL)dQof| zAxykMw$-7O@e4AdFuHK4oi+}?sd~oLFl_RFRw80dhch7 z)Yw4T;Eh^fPpzr%n;7d1+iWDiIvJ%bAVS&tS^Ma0XlT_zlA0_BWG@GaiTlBE8z2~Y zjUeTq`uG8{)`0b=;Cqbv{L`8UKh|NvU^0+jMrA; zb$ye;GSGl%7rNXKaNSqNEy$C*4x=uY#K{5Wal1w4woDh=`y2>5 z*lBwgnp4Lva3~wb5+`1dcStM~rtvU`qxNNdZ5aiDYXq3z0|!yXpo4i)?fNZjNqNLL1U7mM)%d@XM~k2k9@rjqrl-hl-{e*15M1R{m2?`V8enZ0bQ|o5!*mrS4tJ|c&ggwx?+{qwNvW*u0 zLWj{3Ym)=@+d|h%ozI~J`|g3zw+!U+#zG)#+Q=W^34>~93t#6O`JNVDbSc-eWpm?# zh?v?k*I%}YQ);J!uSOjC3?fd-lx?~uC^Pa+d7r3BaxO5c{4Dw$M>k_ez~7rKc2u{1 zf3hegrSnsN3Bq0VS*E%D&&~4MVmeyHP{@OqyocJ{fy} zKnl3Sub|9xjR<8->1NV$qZR98OFpngWl6K^$6Y#JJeSyx>o4AC7+9hNKDVsDmf5^3 zD^HuA^U%Oejss_)k))e_V8(ErcsO;(y+!lh)92@vn8#~~Wp14)RIrYZ*|_QLOlZOzD{ zB@2)=-{-)3ONOCAkK}*DRRc~nm-;G;ghk_4N>&UZycNtLZ93*v z#Pa*6h`Evuf_b+srW+l`RTR0M&}?H2C2$o3qwCB2_eN#V&P((1x=DphS>rvYN{WguFNIgg>W-N}WbE4}fJ0MuDBp)sV69rH_FHcbmSD zP4FyNv|u8uoLanig#F!lm24}qaTBCI+*(=L@Sgq?>%zTnj)`>ruskYEPo=HK*RIW8d`!hv}siqEH|A`vml?@P&<#Fb`@T6W+&|-ytheaN>GXUTMqly} zpMEuilR{yVSsI}`(yM>E!(i(2a0!QG0n_97Usz9wBnkAHNZ7Y8v|Ay+O~2Fv+qJ6b z%;^u8mht8`-U0R3rdCigRNhUsI=mI1NAxvVZHb4MJ`8cxgCtaBrs2RXaL@~oA4wlK zlFlBJ|F(nICybyNR$cAMX4_6IQb*O;1s-xDbUb~WSV6unuh_fWTZ|7&fBmK=+@x@NedESMQ1>8j)8pEm-1Wc0UHn zQ^Q;_3UQ|xL;d01JlQ_|CRdvdY(V-Fo1M{+>E60G$XpWH{!RDK!?G3g9HPom?mSuJ zEf!iIkNL0_D)uBA%!*+pFM?oBYLjzK_7)ZoDWR45N4Sr0LWH?ymHJfJFVwDSk!A3? z(nH|qA10VBRXxwa@84**+%#9c=gU(PnfmJAI<=- zjmP^bb)-0*E!1k-WG?cuAR23;M{t4M4( zVsigNwz?fkSS_NBHd*CmCJt5E0PE$l$m^fum8?Z=;)@xB_8}i!18F9qHx?F$kSior zPLZWfT0f)h52V-G5*K5v6@7K5Dz!=?QbkrMylDB^4ZCmHHWUJtDE|c;q8nd(%cmw! z)^UyxWCb$bQ%8N9ZSV(;rlnJsY)sgBRB{UEBTLgKN_JY7-AEoq%Zb@JweF|yk`I-M zK3jvA(cv6dNn_S)9l=LB$tDqoXrt81xA9^QUe=~`nd}`u;^eXD93_cfnD#ceOT|zi2d7EI>nL1XT0mPp}hCdG5XT&4G98#2N@&f-FvID(!9XR zk(}BlggOS@m3qn%X2HW%R(sv<9~71Q3}pzA9Fqjouve~49GMWi+FNiA9fH{aUBN8F z_Cp9TN5eoH1Qb(RQ-DuTF}+7qpsJ9{|1q85K+BY5ba3JiA&=7t?0K zEkzim7k=&-mq+_JVFYONE=+JXd^W>+(b=!Rd2nLuX*|#^<3<^=ZVyl1ezNH2C%tYk zWVcx*Y4Z5PKpl^dzg5_{1CuHcceA4&`H>(cMQen@ur~JRz=SoA0>&{te>B|Xpbk}D znSfUG`meU@d1(^bgh3h+hYx~764g>iP66pPqj)Q{o93~lgx?aaoFaPsn)F#(<>kdv zJJBQpKU{2c&U_N!2USonYq{dY-$R`&4xaUy8}dFT6viB%ZnGxr0VVK62T79w=H7&& z`Ha2$(wdyN)1pT{f(2mQY-OA-(dw}S6~lI={p3LPSN3|HWHE6^?rmSPotjliaEerS zx5=`eM^n~zuN+F-JUYZ=$8+hWg8o7V@S?>=IJzYhj2+5$!&4%wQce+g?WZo4Xaar3}&FTIe8 z?UyXE{6XbU;-4F;VXx1ehLV7^r=cTLoLNZK5n3t_zJT`_dL11RrLBytUqy-07ssI+ zotb*q6(NS&%OokL)ZAwsc$BWa3rc(2Q7pUi6TI{nTTIOvc_+VCQv@sWd9|M11f$dE z#hDNAKWdCaEB+E#hYx|_F}oLCCmKy8&0)@)E>oQ#C1+J{iLawFH*k=D2&`JuK{v~G z4XxT#%+4)TDv*S)ridi0ZDlfN@%vxx0K|&AF++ zNfX~KPDe}GAbn}AtkZMxLr(;kZ~{!avgH^;VxkY*-RDX$sKW1Y(7p+dp}7_Sz0Z0l zO-m^U010+%#?vWH-Vaw{N*{`}=s=IG*R;|NE@qC=gOp{{m^8yUB?vPjUck#z-RnRx zP>-@);$XA$?kp)ZhmA+|XCEIL9-yIt3ayRjhMg&9?8Te9Gz}zsnIT`=M>US@i)Xl6 zk%-TJT|6CB5PXUiCrkNn5XxF#N@OgFT>UkS+tcpRJa%&-v^ zv>oS!(wIEd$ZTCjI_1qNF4Cn0X8y>8C*9cn{gn@^&Zjg7p*1_^VkdZO`tK!aY2sW3 zzHsk3`#3v>zM0ddP^B^~<)cXz`Z0UYW_gH7TLZgHDaMlX!r2)RKzxLkV4lmFskQXo zhWFX1+AgZ|3cx)H&j-VQT1l!_Lm+3$Q`3tT+v^OwuX+ctZ?ENMFATxSc_M2N6C-_s zaKcHQ{~}H9bT5!k^D;i00SQQ{huGO;EKytW$_teU|+19*VEso{l2oH9|XVp+4bdV2cRwLcXoPvMHALmbQkSdICe< zv6k~U<1Vj)PZ|_(Ji56sl3*REOoGD*U~Za=e2`0yn$8!-}Alt`=F z(4|lWlq1EFsh}KQ+XNaxfuO(+p?g4V_bVk}pz`+|4}3B>U!JcJ+bk5{W&SOE*eh8? z&}!m&b?5dEoB3s!JZy2|_~YuE^Ppi{J4Hm#r;6kj=-91#FKyk)Mw!;12@G3~(Qi|m zmW^C&AYlh_Yq-Fok%U$e3Uc;X{c{XMcF)k8#UEoRKsUv!oN>2!C2msbnMcD8452X` zFd3UV!^C8l7wsB9>)ysN;~URvnc5I)05=QS8CUN9%_t^XTrG5e*iMW`B_?)J+!-%T zt7EHZUA9(U`ZWGQ$&GIY&H;0hdJ>-TL;{mM$p}QyhaGXe&M|^20L~U4aJD6Ki)qS2 zHTx-siE(>g+PyVn^|)e~E^Lpz@$y-il95v-tHpMyhSVjbjkx4G)FQ6VXH}@wln0&M z{@I-d4xGu!c}_7eWmjI`ftim^TSB-kYR-PoWyBple5@U?GNgFdDC8H{D4Cd!p_(jl z!W;YZ4C*aJ{bLG%$OSfhu!8nrJfM%TsDy7Oz3^U!7}yVQ(acSt;< z&^u1R4i_RGY2QVAod3!sD(V@eT@fDx9Ky(SdnwSRFQS^wWyz0>?U6FAQiL2Y$+60i z(7%l7qD-bgLbvVbsMIH7)WhV0h+EbDraWzWtD=kT{-Rc`C{&NmeJFMnVy1P}Evh_& zy=;q4B+8`rW!HxDM+gvCp_%FaA*A5JTlvY&uS($dm9a05_SJ?-<2mB6pMOc=oCYC% zNlyIO!6FVPIIc1Jg#}@S-S>={F zA6Rg9^T^5kO!1dR$oA-B%a0H8#K#1GO<5&=QnBmqh41F09gasBKIH8r6;XZ?prDNX z#nnjIL&^DVRCU5X1a-)e6SScId>6e4y?){?`VpF8+@}e2^gz#hXnpDQ!FDt&nI$d= zrfY}+8%auCI-m{2AkRB$!Np2-F`l$IQMG@tKq4u#$N$qGAcm8%12yx2g2>WuG@EcJEq z>N1#lcljI_cna@k=tPSATUv0$C5V6rB|dsdAODgs5G+tj`(zgrI;@_V0_TlDuXQHg zOU}B20$g8DOII?G|FWgCh^50~fot7F?{H3fgwUyWp>VrmV)p$rl~(OGopIZu)`TvfuxOg$+TzaVnUsf<#0? zaN9<}G*j_iUL1XDA2b&MI`1Z{yqW`c^(kiv;eY3g+c6^E?BIKJ;erS%Nu^=R5tI9N z!}A!?2F_VC`y>zN05^3(7BsU*qO++N6O6P5)_WF=D71I>_w3vHM%6#oJG#;Ozvk*o z0Hk6)G_)LD_&LtD@2X_w15mUqPT$Y~;U#Rd(; z-<@Mj&#n1(%YY_vt;p<2MD_`)kO1T^`zL^z_-Y7raq2+x<8QKp@{({wGX_5vO({M> zHX%w!1vZmc6{9Qn4Tgp}@cmsJLKY4#um)AOIpl)!iYv4b0^y9#KQ&J%QlZ%tprJl4 zn2^J}4MNCf4nAI+&6`_pKz|(0Kc-Ypdj_r8x3H~BB8}@#CJs4;wnGS3CU~ETFTF5mAX zVPUW^0P~`|uB+S-4}Q&dXK-FeV=;dMvTqc4YPJYHT>RqdM4fyXmxZ;K-t((|k|2mn z=8C!VsUcd_e~--)-^h=#v*{%tI-EFhS;Z<|L@NvK+lT<^L=h z9`c2}Wx73d22y&y&u5{=Kp06BkhNAd6=ygAoODoJ(o3?F5wV>FhQ+TCPo zyNF0$Ss+LuvmWjAtvbfknF89J-Nw3dq4|rB!DX>9oRrJ1u93}-mo8DM@1(Ot{i#;F z`*|rATo4f@k2*SGCYS8S(CCpZotlBB1$|k`A{L~PC8A~k$8QtO10s+#NVYhOe7&wH zMLJ8$iEA#{JT8`oW@!->L^FEIBd>ns zNb1otUt{nF575;o7<6my$N3<5vnD!_>3IzyzmC}s5i!ucU@DAzAQkiR#-0sFo%?&? zjt|l}P-I2Q(Z?Wl80vLU}cE(x3o6^M)x>;&zy7P8dH4&U^s80Gw$iv(wsf`eLwEkFB#=qB*4)x8G ztQG|ciGVm|g1#B?M=+?d{^C9OnMc-Vostpc$)Tbz|6=t#&AzkUc{7PE0^w#I1*jz) zHo3{-HNt^D8u_UEW=D}z773f+cP;Cb^ zl(I~@Qn!2(c|t4G7T>eqwM9sb{k~d?d9I}&qyG-EIz!EzwjZ|z9fy7jB zat{$nmg?J0x#|LA*S&l|UgCZdR z)yHP3w8ZE-bA4w`jA z_N;(NPdTmYBi{)3udFb4DHGv4Q$8>AY~W1(HK=KN@`y z)qi!Ce|x2}bCCfF6HZBUXGE)1BC3e<|m{V%9N>;?OSg3j5V zD6%`iFIz^T;^Zxz;<4F#mq*pr4E_qcLAw#c`9sQeMP&9gzT^!HFTYN)EIlpU?eOk@ zZT8xAU1!5EDbL#Ty=|#@!dx~xau>s2k7RYk4lg7f*uHYr4aQCocJ`mE&%U)3V!d}a zPb#B$u1k-F3206GzW%iwHZo>tGX6t{GxHlUyqC1}0I*+Yb5k%@Cm-RxqwIiU(aGFO z<)=>h*X4>dZD>Ze71PcSPph-L>>~wrY6CbcKiQ1j&hB3QvFoXl0*wv559=JNywqN9 ziW^Z>8Wt1NnGEhEj7C?CZY?Jqvb$=yN&8DE8}$QV>zU0DyC0(v*i;Jmy8YcC78Hd_ zb~7^0<4pQU$WT}R%3NtOy*CEugEJqD!6>gzmF3`pCk`9%c`KzL*to72rLF_@v!m>I2i2ct(_`d&Hy8SW(w zbA4XhE4!2v3)472cmg&Z$g4(pYNF`b_vbkAAus@$Oxpge^Bhk=9Gc^lAjmR|Q1v7n z3a>%Qt$Tr7=<83WQIn+D*#KcCsaJO`crcbb-OB$!v*Nxq9YJM4_c>CLJTH;-$q(-ki|4Zb z%~@*epRvdDrFcATLg!aO5VcC*g;u0|*AdyK19bBrU(+EZDgPfZG%seDLnXk2D}-q} zKw?Vd=*55b2f)UNrH(D<5hRVcj|~jQNF=x(gnTNQaouktuTH^UE*?(U0NqxFUG#z= z=xNm_-wt|PjmF-o((_W^^Vx2_nL@3XbS|n9$6IH43lcD%qm9Z6f8(kxYEYfe(yxFzLDAw8j6Xx0ZokGe9;W;B{V zo-||^3Z~qiG%B0x02=yK@qWC-1|NU272y_^6 z8B!J{u_vw!T3_Nbx;vXzHQrvQ^+w#AN^n$iZKe zgqzryZ7f#j(Endq8d)Z!4PUDKl%mrn9om>bR{1Csi!l{2;T<*>OV%n@c)0xN zK!du7VM$31G8v}vPT6^yOzigKXj{o)qDXv|4(fg1JcgxINsqFG+)k{SxbX5zTarch zEi{rM2LkeHQ=Y^dKjK{%mdtc5b`2~G^D?uHATk@b<9}`)k2#<#&eOnJn)2}M)r;hz za!nN`>iU7QOiFQVle2t16!sqg63f0fAGHVF8o|>gBWPn&l7h82<4uVDsj!=qxctH=@0D+WxDqbuJWMJ-Y%K zN7$?MVjks_H$LH;@fTuLe9A3a_T+gwg%TvIQ~c;apcT5!YJm4LYVjiSR`pCz#`qlW zcF9p#MABwX*MrGJa})d-+%PQG<>$uN?w{_<4?=-UqhI_-q@_o+D3ugF*kJ9@Aa8`El* zoX9tD9ib$RX#MrLg{4Bz!tudgD4#2_{H9Q6g-aYyK4Pr~)b(Ff8Y?q$sikdxusaF% z;9@&OjA)MkWHyk%DocyD=rG^?L~t@j9TbE?*7zLMgnps+*8Xb~7 z;2vQ}l0t>PV_y_jUHXmnynrM4t84$Yjr5DI&`pQ`P1UhGBA&AyG_yZ+lxD6y??4k>q)B*Jf&_8Y z`lapj)8M`A(^JL@sE1+cYh1}V zrGrR%!7tT({~VC;kK|f$6sVHvw?S(6RQ3Fo^&z8qAKXnKAXAa!)aGKcl|H}Q{y%t{ zUiAd8_f#RMw1ek6ce1R{acfxm1$E*95(ridhFeN=D~Yx90UPU(L1$O*_FCoHwWdY7LD zj}!*W(7F*adpt{2SrOQ@KNz(VE^XP}2OPI%;QVO09l`1dLRH-q2S-?~PCR&gMisrg zZkf+fd)=^``g%t7kcd{)IseH+HBRM+(iuC7hUr~A1^O4iKbzmpe{_v}H^G6htrJxf zM@p<4T<;lcortX_ichO841;6o3^w<)RCS_d9Jz<34^)O>Ik`8$v9-Yt+Ysd)%fai` zu9mLmDB0#dRH|cAT{8CJJva31b!$n5H53Jm;IUi>@q$s#8e-tre`}osZ=C9oNV~~n z^)(e|T>99QX`H8^0IP=GxoU3@HkH`4znG)7rX#&;rQDw%c23}D)yL=(Jhs=~*R11E zwY20Vejueww)%-rCeA(^6*?W7oDLBh+nfztYAhrezrU*whQ|a0b;Z#qLl@aIn9^5!Xbyk=-b#+gV{W(I?)AhuxKLMO3RN4>zkd`O#P#zPmbS8* z+C&iW1ge>|B03d}Q$nNqxVbYjTYh8UrY|TU$yRBL<;t8>3XEy1@!b_0>NF#btTEAO z#X{@e!`;-2bP_AWvnp{`k>ez7!|e-ylK*qI?GhFla%GWrI2pIKA+wfvdSTglW_j;j1gy4W z;dR7>o1od-!ldv{2`a9~AF=aqSOz6&CdC(0a*W29+~3(Uo4ea*zIEqp8C#nA!7#|n z-muftGbd7(7z#t(yR~#7F$-tQYQRBqx8pR-#Y~f5!i5ToLAAO?`IYCHLXF_9asCqf z*SOChP=#IW-aS<9`%U4k62!|K0w%050g`YfMcy@8+weu#ODTn1UJO3r=y}GJcWxdD z+TUZv$Zc0lKRR6Wrpx`@F~ob4OK>@&#egiuN9HHuC7yyezRyQJf=(}Tw*Bnf_Dk45 zosw1dSqU|1^1~>UyOlI4c;>=HOWj`7f==p}g}fe^FbAQ+Y835 zGT_I`R-cw;#p0Hz(kyiUEZT`9CkF%Guw6@;2XN?Bh-c?DYOoUs1tLYR;_D_l2$w-0b%SeE(9Em18b9&~NPc;ce+<>U=2kmCq z?k|ejH54d9p4ll#qd$`z;OJtm#|97ezv0h%cLl@6vA2o?IO^SKl)qr|<dM)T1TLo+_8ll@fE1?(qS}XOO6B)zxXSEbFk-fZ>Aw4A_KOm=l z1zp4d%ZzOQCZQl;3bR#!C24sjr;C{YC^GoUzD|>@Oi)4ol6S=S0)_zgxw0|Oqa#cc zuCt*4a~Yy*EC=WGtspJD#|`0(Py#}T^9boyh!6`+Q^(ip@xiOf$1^U44GlEb zFOz3-DKuRsv@fG*AM*?=!@tA9I$Zea=Y8<5nvOnW`H|n97ykWkg#XPibA}@>&&NN_ z{p1|0Ox|}4LuxYBJ1>lGu0ez=I4e+-qvslH!;o?fo$-)nZX&3&lhWk;5>P_|_3;HK8^X^e`GH{#g&_O7;b=ClMy`{Q!& z9bp7yCWfTys-kMp(Ffeyt07(}{A&xDzWfoHVCMa@_b8i+cUzdV7JHSwWQPa~LWY|V z9UD#uYGcc!%xq!v8VHXN`-@*2Lf;pdVWNBZmT|{=Eg0$7AcUG|Uri;ay{z zQcqe;uasdLo41v!RaU$qF?z75*~`s-JlUYWSCri-u z0160`U@uj3aQ`g8Iz;|v?G*p& zl(Ud*UjWP*xqzBCG{Q63QVx2FxeyDAYR_ZxtTX^$XQ%M&W$*3zM=YyDp}F+B5UW3G z%%n*h1mUNrsv_5|rlpiy{>qJZL|`G$mGuD{g~DW+l|Cm}Gudk@w4GMNqs+s?AJU;c z3^`f%LpPZE>kf12E3?qrq*a zymB|3Zd$BZSP`~L@jLjY5SN-ceizG$hv?^>tIxdzG){qYQO2Crbh&ttZ!`GJ`2R zfDKOy5BxoY=a76&pf8Ld!?(bgdea{I!6X=v(YP3ZV5BG5m|IIQMgcU6yF?Sui(vIf z>B>#Dg1N-8W+>DyM}4oZ>LRJ63xgkB`POH7pO~TJ-3V};%M|bn@Ppm$>JCTIZ27fr z^XHJQ@>D6;h+&zEBMsVj1zNXP8V3#l{M(NyR%nb*DDP<4P2m|BU>3MQ#fRG8GrcCa zk}73)?guw9+S95&WJyB9Yh{}{3+W_MgZZ>O(%DM=M}jJvK41+AT-^$!fP$u3J3-n# zxttD!WFeVe#j&vgo~kKK;EmC3P$L%;YVXdrqYPVQSO`<6;ld!y3&KTS=r_5uN%XZ; zw{Wy%D}vz|nXF;M-iu{Zmv+k{g$7)f0T!cRkBdOUS%xS45pdpX8+Zc@6X1cbX=L+q zXk=^+EKoHu_!o}tQ8D!1g3nFgLjL*=naqs`F|GMQlmkCO#zth_>xxCF0%I2m3-0aq z)HtuBsE9j9TwHD+X?iIvP}y<#=RShkroOKVM`$Y*p1s2`*)3igBWQ*J|4RW=)qUzXA?k(neWdtVgy|fOwG{G+!}Fr(CfvJGS^tN_VX1Xk zDQ6X1vpxN+9zGb8&#SzeGmDD!33+YdiGP_5vKs~F3T;^ zh)Mcd1xdVQPr|YWj_$4>bk|r1=%WR~=H~9_qoBU?Qeh9>olp~bdoy-j-y3w82xnf? z@`SWd)s#30@hu0%QAB%he&!K-FG*F{dz=Ddn zATagXez!6~mpv&t_)!=?fyunYs>V^SRzN|;lnxgz&jL&^w0h+gjRt)qxoI0!z6B8h znpN_bFOOdLo|w-=WdvN=+$3N}(r+^Is2E9~-)fVj?;nUbK2iD%O@glcV+HGZ%2tU4%vObJ3B&zCzm0mpmWJ=8jS3euc#E5E0v7?s+%|D>&8>F)e%CPMa(^S0N^GjtH{8-=Qy3HHN2;WH)U=A^pPEb!l4O;QH*hG3~Jf{-)}5-XTQ zQ`FJz-mcT9x}2rdp(CLdko0QRs<&&PcatX-lWRZMH?$TP%4vyBEUZ3f<@fCegUI@{ z@We5JzOnIxI&K||fmRt8(m04t3IFa#AeMp3|64-g1{Fd?GW$qy$U0{2bjR!%;hzGb zG&X>l(SEQMbz{m|T1GkC269L|_5fimGaW9~e2Tx81g-C?7r@l0Y7KF~(1xUmi&24Y+;0IpqZMf4U(g}p zjK&sNq;wp1R#WMm0RG>I5R2lnO%p+60ou|MDP6#Z8`8)kYcw6}al4PVyZLJT$)cz! z=i9?8C2uhgA!t7X$$~Vf*(7KS(vhi?z|=TQ7jYFql5mCCUOcjMIWl6_PCQ$ z*t#L3ArG6calZ#6Zh0lljti8B$ zDa_^(MTjq~vXN*Y=Xn4_?_Jg&c(o+w|Cy~DdGtP<2f@LF1FB&juhYhwK$~INlz=C7zacJ0rdwkm~9qlF+7xb>oOm zq3DO21zsJ1UJXHW>FZ;@?ShATo!^MJ%LkB36iM#3PWqT*K}$Il%jNzr(CqA<(2}MR z+&Em~^JX{=FQ<4kYNt34p#FdizcL4t+eD-uTtt-M6ULk7T>B2Qhv>1g>{_ zDdr8$Lpl{~CJ_#5&Yg5osi8w#K~BFKJEtfyGoJ<3TdKayOcvv5ZYstN72{454jY=^ z>GySjOtI<2#(`*_(}<96MoAAg1^U3oWzth6T7jK>FH2c{8I`AhQ^Kjo$}m}*-i(6D zaBu98GHhu3&{UG-zTg||ZEVQB8%G27kC~I*A!#v{Q8o{K1Dc+ktv}Ja+aJh=CjND{ zOz_(0rJMx^C14|qqFw3uF-#er8P4&>z2A#$DX-DxsBY?k1ukRjFwTaeRU*e}iB*vJ zi$=^K>7e3NmwN*NY&kKVJU6pjg&$L!Y}r?GLplqu-rA8M`~Zr2qaGN8xc5btD#qI5c7h|3`ZeA^$|9P-Rk`#GwU7I8@@mIKF8YyWQi^tv1v*W5;%oH>~Yv&y0 z&a6K=;#cm>qR%$ifCbEE9GgSg1n-)^qb&@OurS;qPR!%QfVo)@j9Z(_)n|{TkU6rc z{-06L;Y3x|_u~nrPT?lZ*Ai;^YiMwrAMbitXT0?)Mw+SFtx^G=Qj5H0t=Ktlt{m!% zsUp;`=)$12Ux3Xc?`Tl@J8BZ$u>5$n6?jt(GTidlR{rX-H^Gj6?(c5dh@RnuBbjhT z_zNX-u(-(l>+8YhuN`M_036~j+{Q(jdZ*UHEw3CjfIV+~$-UalJAwFa zd2rcqSeCBjEz&FO!GvObjI~EoGN#K$?Tla6Qs*mV*?NTS5ZaC^zQd5#=f-HHs>8Sw zfh5!F+clo3jRp?cj-ciHh9yQsKf<}7`bQy55M9u)aaWpuev*+A*mf(su_M+=OXZwo za@Z?6l#B_FCH95d%bv$_nWd66hYP^7KeN!uwo6to0Tmiq4#%FjcIo-t;wAPi;K7*) z^dHj=99ZFYSxbC%^ZQ|44Lk4RU>C;Z1~O#tsi-}wLu091!K3!gpwN~(L`OC%8>h7Q$n5K?6^yD-6MlGGx3CY*TX=|vtV$J=3g-wj z3LuoDrQ|+;{#&1`?b~_N13&uPKSgq~r^=M%OI+x7FqXox`QUWRV*}DJ=Z%zy-{O$t zM1EuZP%3pOTEa!9!}oU#!k!gC6ZvhoqH9OMq$3!t&Vu#sr;K$f9N7t>QmY+Y7`ZPt z2RM(~au5<`A)95FNaH572{&}N_O6WaweZd*gvJ{<;Epg*Jh8?^?qPIe5XVUsMt0SD zSR4{8>K2g8RkpHy$zj%g>F>Uk$<_o&&RG+}HS+Ng8nQ-|yu`T^Py#5O1i7DKc&a10 z|D_IsnE>5OCJ%QmQHCNRouYTo#=SQve?=h-N-<9=kqiT|TnF^9)e(t}m3mRnQiVxg z{*8m~bp?yH(012y2nQxPVxa}#S@Is;hUHyFk;8(d$Jfb#n`cd8M8wQK75Q;Es zR#t6;@a7u_a*1Xp%aMU2L%Ft4U6^hiXd%beNM2X?dZw*}Ug)TF%V;{R1f6we%mWO~ z$q%$xjB#ssf>qeiq8sN~De+{m5PIlg%CrTN4gjXmreK zrubBAB}zkA@e9-Zhg5CKVa&H9L}$QW+Qf>Wc2fI7AD~Vnvdw8>$NTNa&dZA2fzdhF zi5?v^che2Kt^#Snaq6ackMZYv=ip9)?yIyCV}WKtU`uoShF$3Of!4^;{afH$Rpul?PWoc-)!>##Wtr}lKz<4| zl&a@?#d{vXZxF_RlSUHdLeRCu3H6e7*Sf3y$9~(`;;PYe#7Zx?CUdDAaZ&#Ioy6@@ zRagChRZhC5^U3(OfPBPDn5m||`MTqk3F!{z`*{8(k$J&qxluD_E#7=w3#E4xkQy~G z8l&pimO6EISesP`{Z##UAAt3&_RdJZj4p6%x{eT{y|8#CF(v3HnT)PM2E6YYkAmcK zqikZ#UA^E>HXHd`^tEZ=R|xRBCD<(qvI+OYfzRK9eliLIj94DjYv;yhCUk-VO_syDFUbTHgV#Ilnv3r()Fid&uJ0`u}CxQ*4w z!VeL_%$%fggMvaQaG+uqfG-J?1}xFJdj*=qR)FK<>7cv)7Eu1Gw#=4}%D&sFrz{rDa?{Y6|;k^Cs) z{MlZChgwmQv3A7{UW~l5#|WwqKIWP3N8PDy=^S%vJTge`(WNru%`AS!Lc3;uXpas2 z4w{ec<*?#b2hZnvfXlk^y*2qyVO^WUS1uwepKUQ3J6loe$t?^3jYp=6g1+V4>rzja z!`U@eGd6vPH4}X&UD0}>rJa6P2&K!OiOfp=WmJTrt#~=>BIU~2iwQ3;io8Q%8c_Tb zU^uf+H^G9BgSol>)@{v%O~>kt4~dh;IjG$O82zO;B8I&dcIO*VG13oIHJ=QVxb52W zZr6CJ-c^TG0-LVP!X|a1DyOIOFAK7Sqy5r(^YtLElL27Q4{q z$VC6#Li$%J;q1{90X|NhDxGyGn=tuNqPbx@zFPH@ z#mbc+X$=s^?RR|eD))ia2=qOde$jCEQ=p$=l5<0zl!Q(q{J>;B$Sjnro^k4zKaK?v z-BK8X*;Juw`f9q+tnc72Y2_lPeX!liIzBf!)=@raWVAXlfsiX{C2f_y!*YR zkHC_Bss+gN_L2uk7@3B>Rz3nUy3iEkn}Bq_ANt1oTh{tCq9EJ+^a2y@oRqFeV16dE zc~;%xz%jH=NMAqYOqa&~B6_8M`&aM*H029z1{;+4a)7q&%U%>JVYk8urr+28ONDatvj}|(>`uD&qjPh{4~6}o<3@a zsgkqd&gV7xpA<-VQ8#39!xS+HhTw7h1Z)_?wLzP4feX1b-;__q2+rBf9wLv_k?CZI zP!Ma-xfx%x0vY|5UdC`tuGjSxf1QB8RjC3FnR0tJ6h<6y~U9yEh>`5|c228{2|*Rl^10Nr9FtNank4KCVuzG*~@v7AXK2 zSxLlx$Mb#{fVS)NFnITrNc6LlZ%XhkywaIgikJCH3QwU0(|oC~&OES^pZ~g^Aa%e+kAZ?BZgtAG@@MlXx_SyjlM{b+Ki41Ut&MqJveZrO;iHQ=>M;w0RT-oH5_ZCxdz- zDh}DWjsN=F!jDzoqOug3smEVaKmsyXyu>#(ltQ$k%P97XG z322ux2AvEZ4yUy8`hPg&RL2ko&?n|hR&KDpc))d(0|(PW^LJJ~5l%{Ti*Wz7NAmvE zlN55;BZf%Ifa00=Aj5_L$2$)HT7Aag9OkeNeQwhUYtyK>9gg@EFoe_O@sJPxJi4vN ze_qI`F@oL96$fJc^J|f|MjI9hYiN&jk2Ndp3m(sapzc%r+wTE3&ua!M+*IN)JXX9fY5?q_|#ZB;jl1jqrf?$_G4Kevh%v`hiR%+EZI=b zn26ErUfOHoYPTvn??W#XTVl8)b%xJw+&m?*bAs{SjD3yzLG${UiTZgGBJ7W@t5ZM5 zp8jA1IUkGrjRN&RYf=&tW@J~3*7Gc!BT;=rfNcScvZzb{v0e%}yW7+CST;}(x{`_M-Xl|qB^Vl(P&Zw|WhxR`tJP@_P zw!!c7ma++^srGUzo?6vNiUP^e&lfb_%#$=w|NfM@$BP05Q+dVGV#wWlu|*QK#t86+ z00xQ>t!>Do($?)yvwbgvqC;TvgJ`YpXqO9l_EYseH)_YU+;T{%I!Jmo{~pO?eOim#%clhf(Z7Y~ z??2LZgti8BqUlzfc4YCSb~VyxSe{y$ffa3~d8Q7st+K_9T(ihCb~NIQ0;*it?py4o zqwzoV|CQf|o1G4kW`s>_B+Z;hEec^9K7G7Bx(9hIK?3nPn3u1R!3&FHKLVLi@nw%M zYi{iF!eyj6Q2T(+KC(Uq$V>$Q3m6g_gfHPC^=h~W4Ghw+z0?-ht;AYqS%d3tRE%_Z z%w;)n6Rd1u=z{992D)k0x*8gC8f5&k2>E76b;QVS!Y){AthAVB=C+1a_YDMR8ksm@wNwow(l@L^^vFC zknLwHWw{Q`ao4ZHN*?e)mW+8dWwaZ#UHP4l@mwX}%m1t-3S#kcr39;$v{x(R+63`N z>SVlC6>Lmy3dq4vESVF-bTC3=Bu>GF*9e`|kUxW z;ZjcG`!SU+DZL1<@TW9AEHJzHV7Eu{YN#dOf4Js#{+DXY*VJdpaZLjNMD44;!~DoA zDVXGN8q1<6Pt<8adHT~ba9Fk1(wi#OSHoa=O7S7y@5i&sb9(H921#>rwePEMun@Z? z%WyfT#DL{QHs}E(mCd?o9;o8rAr*H!F_zjge|GNo^|Mcyr_I_%g*Cr4dOFRr0k~-O zYo-4~m5Y~w*<~N_0l5nKY4T!9OUct;v`n`=MU8F;bxDP={)mE32zp!-Wdv-7n*`_z z6pJ%5Gp9cMgN%vaY{S3dN#7>VbhTc;|AU!AHfparRm&LXmhiKMq( z!-*C-4Q_S1QI8}-gO$51D|me+P`3o#&Od!bpfAd{ZM*7cAz3sQ{MFmB1oqXW>bWVu zfb6A3k=xfY>{`tZZTC@BI#ex?vhlpI*FAkJL*H(kdjx;MjNDnOdLi%`U> zFxgCYsDxxhx=B{3u3z8efC3RaJQnC#gfWqv2{XtL`iEK!)P=>!Eb^a;=bP+F1Za$= zRKNU&&~MdT+b!p!5Yh1_CY9Rxq_IjqpM{ZLyl`Yb#uQQckZk6!GUJ@?=4rA5p-jDD zlL(nkwWQpjVyKr`c#erl@)O6=f0dsQUi2IBU#|wZ3bYF1UWtaaUFv!)hT#isf6nY2hsQ9h zxZGJ-q|M&_DaS7kvGvEag>0QqsUSAL)GH;qAbPgb)JYc)B#fw71DR7ojE8T6XnGfU zV=!hX9^S0;;f=65atG0ks=AKYj7fwX&>SY#TYchTvq$LVBYO+H(>nFbbNa8Eg~67Y zd1*<7e&A)6j@?r0{zNJGHwUbT;uQ>94F9)|vD61Yu9Gx!0-ZnYUQc8Z&rErfQdy$! zRI&V52p#fYf2B58B4Ch$j4fc^udi5Y75)!N4E@|ZTS$H zzRi7y+GU>@b$~gXiIKTb;WXa)EzIj5EqxGq_hGYCtH5(xxOEu2-JBrX#i)6XR=1kH zPmtciObE{mv3)86m91>1E}H#{UcL%;l(;E}-F^ZiWK~SKjyiu!XRHD~E4tp~b}}{~ z(i^mse#OD-FcCwBD032_AfoUiZZvSWPO-Q|<9hhq4A->};CiXx>Z8kH5gKqOT34Oy z(p)t_1FL;i0T2t(?gvCM?Yr?yDEg6nwu>|R!^tnab+Sj}BQ3pm2a5!x)`)jKko8&9 zfkI^cT^W@4A9QfGD`=$s>!`>D_t9z2-+#~>IYB_H$I6w?BCY?c_IaCEk4@Rqkndr; z4dGk-g^cZYsh7?eA7%C>ll^O|m#xO{zGV{#0R>@zm;87iuICP8XRDL7R?tX)1O{0; z8!JTH+7hi>PUg1#R@}M+okF4!wg4`O=USL_S!!|E7QR1Lnb=>=>!o|x+U{EZ6JFQX zvRKgVuZUigxTWfxJ#0z~Gv}f#p;t0uUFM74WdUzPTc{M3PGLB$D@V9K>Dr$;r^VT3Sy^pg<*%c%LyN%I>s1&? z61J3yEgZv2Kgqx)G~{aT0)?3xY2*+FNxd;^?9}V?4h3l@Os#Z1jzR6mAWhLyo;%`i_$~K5wG|h{caI$#(r3efd*MSA-Bsb+f($a+Fuly{S*_ z*iT9qw=;K&p-Upt5~kPQUD{g%93y*v0z(z__a0Qib3PP4Y=6&pSoLbR>*;>dq~g=T zFDpZ0Ye(|TnhO4HX+EVLfo}azS7NqH>ClF}-)Q~s@Vv53U0Hn$hlN)LyNhO=jgHg! zS3^VQ9tvLgAeF@dePR{i`2Qz0F!%aMh=4g*jD0@IxhZAnhnQnLte%xxrS_&DHUUGy zcM?I_u64T74&kLeAICMWuy{@bY2qGVa_H04P|&CiQS>=PLjo|V{EQBKCgcGk{n1abWVdOs)D8@Dc# z3drm^rOvGGUVZU;U1G$F?*HaDLLraNvNGcg0KRwOwngaEvwifQu79AoUipWG7Upv6 z62iZzFGiTsoHr8Opl*sGV|fi0^78ui3UszuJ)m~*&HHRZ8VY$QVqr>2-ne?S%69(D z^te~2C8agW$JIn*G7_s#6V_0Ae=-0@o{^O5xS>`}?=%txWkj@E{jZs!vc5N3kzoo; zPJ|rY@mo1HUsRQ7um7<|vOZ0tLZ^SJcE@@HY+@w8caza0&YmX~HfGCh9VhEwAQCrA zygLE^vNX*dN(;QC`W=t3w_gl|IXg$dw40qS4S&GHG<8!PUS#fkR3ij4g4uM$XT_#F z{+3!kFl5<^<|o)gIpOK=eSqNf1FKkA-&tG+j4WWb1Cy_&C#a!mTd_no@*tXB(Vj6E zvOjSX-{I@1lqRgJJc;}MREm_uUnj}~h3P@BfRmE)XEB*oQDMPxZ58)XOWHV^YIL%j z7q{BR)$|-I&;B*=BwI^&l31TMo2>t_KiYcZ-O(3-HCN6wCtfb2_4-$Wv0nemrOomE ze3{9QE zKk3iK;=&)vAvnV++>i*Ab121uI0H;=Q^8LPk_J58`Y%+y>9Ut;=c~3>95b&3e53Ps zs`A(k)h<@_WyR1xxLEEdwrB`)5MJ*sNMZN_Tg0M;OW&SPnDGE1RL-r#%-eSvwo@FA z*#$!5bxM>=Hh+8HFad2YZ6In1jtvvi!B)KB;9IUY=wD8usw$9CrOAzL${5m`C4x)ullKR z|FtrJm2g1&SbNxhI-{jITY$`og35&FV5)Nxi6|yy=~c0fp@9gfQp-EY76S`mdEtA! zs8&%R1csO3vowrM3{G>;nZJ<0<8@>M2!cgGA_EJfI-wmROm*M=i_CchwZTvmm^)E@ zh|UcK`fle=!t)p{Ur@2`P*A`2*P^VyHTda>{DK)v2BQV<+Zi z^mV)#)_tn^EFvkLZ&s2Yger7YH9_bUS^eP0T-A|kOh&zDXdQ8I=L5e0Dd@gpzrtK- zXA_3k-`TR2W&CJSoKp`?@@7l_GH!~m_z+2uf~z~QHhuBjCaa`Glf9Y|&5Q-5#NfFx zy-=Gw07u|^k2py}1D&C5pu8dbuq$Bf(xz3=iW4Ci#s*^wOsy=u4Pj=ly?hf7k^yWk zi0w9N zDoNojP1|GMANiQ>`FxxgCEFe&Gs^fb^wFw|J1~gX%%zSu7ph@+11=Qd>Vb34Te2D% z-c8(iz~v)?&*{qyz?gQxV#1*VL|UI%9`wF^cgZhjl%TL#fW^fJ-7)J_VaKuTT7PWW zp!mpdQ%==SY(@zhGfzzC7+^q2$P|M^N73%ZM6+-9EPP|K` zg)NAwp+bRCEPDQP`2jxVU=>Qer3B`V4B^tf!tA7ZRDZXMOhCsgr4C=iw6!Azl+_bG zihPxaozBep52GI^V2%FL!w3dnmO3~8dhtXY0r0zNkZMc4Y)*7Cc$2s9wK(oMf(_GS z2+EB-=6Nu4&M!ng>$xg;SfWRIO-xxm#muX5*&aJYIT((^kKmV@vZ7l@(l(6~{>C~7 zX?x$dVkt9BFxQ1f#H|6WbCrIQT?;SrA^6~yBEdnz)ipVV#XD1Y#DqOo$GEaz^c_;%&;mLj(9C-jP(t-s@&nS;}>S4l-|bA>24Gx(hY z%(9;ct|G8wj~k(xy`TKLOzY4_3?ekb64cF`Vpo+-D9ve;Bpfxh5bOl{+m{0do%)*7 z31`#)GWmq2oFCC5$_ww#<9> z5&WCZh|m57)pLQ<7Dx@0DwNCBBiE6rL7Omndqw?*W!pIgu}-`HZNWZKIwYd zW`JC2hU#3FIpK4^kHZZBg$V7m!7Nv0VAd=l@G8+xeVwLe@JyD=UGOZ+JhMgi-3{*( zen*fsVnN1n5NYY1C6Wy9G5kzm`#!`ZV3;ihuy!Dxgwv40qR{v(|~kb@1@Ew`Tgbpho5H+5oGsP_`X zHNXZ5XgWG3)6dpIhW`g2v`X(`5Wv*zHt2eF_jO+&_8A2#M$`F8&_D%wiWrn5G?$6v zi4s@*Xx%GDO|k{_6MGKf>Rhj?lVUGd6Bjv zlt5SA5HaSTj-7O#;JPXm;-zdRw>T@SYfg64$IQc~DNpS+;IGtWx-*WYQfjV7_N-Pi zhWm*KYt{aP2G03%6(NqHbbx!QE?_t&@V{5WeS&mn!*1}b%NmURjFXHbZqT#JjZd@U zgzfVv#mttgAwrSKBawl+f> z9gOqJr~wi6b>KGXF6$GjYkx)1v>p9-FoQnPNUf#*(P!D>0cM=V7{^DbIYbQqmEVw? zkF-P`6lY&7p{&nm_TrGHG0D+GiL&ePGL(#DRCdUB761SGbH3l=oLaO_p(xnRf6KB1 zqRis24Sj(}_@}*}Re>#Pz#CemW=qyh@;6Gob5#mTVd!&DeofE-qRzr*pMM;BbL;cz z^tPYqxM>PQ%Syr{a@-0{05`>bmye2VO%W*G?66mVgByv<^|~9;CYSy*FT91npnUVk z&BP=ye39Z1=M({sr|h$hI^zI#s0(dQdUKkin)CIE#?P-Yn!1Z#N^}9^_#{ zlRbVgYHsgp7e^OB2WdjIsj2z6F=9wWX^v8JuVZz@abF|0W9m&aAFtZ#B3-V;*V!N# zDv=m988NeUc-QV7K;1Ug%sE!f4H%$_b9{fp;XFSpe|SY0#cHtbINxYBUNE%*Ocu

E=hye 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≶c8B#?wT#?}D`K3G*~?>_ zm~$vjY4Zgp(!e&C=HlZ9F-2a6)h}&R-e=*_=}bz7B*r#tvvxM`rm0e zS~qJ}b#<0$qMaLvAu#!C8okJ^`p+GoME(H-^58K{)CA&;j?ib+|2k1}5&>yGHbF@7 z^6;rTBJF7cE%0U!WKDj;QcD~Xs$5a=fYG73?7GT+8y9&?$%4^H_=Uq)5qf0-R3f1U z*lZN;)?KnE(Mqq&f8{;v1(b188szL73?@PdPorP^WzvbopHQH7B5ZftQ^_TV1Cs^d}`<492>cx}Adz=wAo}&y0cpu3O`+NUM70P!>gdEZ*iJ&7C%s{k?YuQte8M z*b}o^lWpe9%mSGCm44Y9j*lU{MAnwd0Q${nhE(bNo~_ya!CFSBEo7 zgst}|RnkHjcL9f|+*MzMfwm&pTI`fYE z^dD&p3<8c%o;+AtlYT3(?PD&B6pYFV(JM?r23IXWROejoTXIZ9$c7m}X4~^;+krIO zGrCv(WHG6!<}H#^zozF@JR@HPiV}mY{K|w@f_+==(Z-=&w8TSj6hF@{F~l$(#tfeb zs3Hk+d9s8oP243%e)Q~bhxJC#WDVsSydHeRrj`|En7{&0eK3&_jZ*c%-l7MHS_8tc ztxwL*)OyKMTCCA}>o6uC_^d`Vc4Ke@iYVZ<#9p)AM=b9LjGnqfFeW{|ZU&)%!1O33 zG)LwzpPr`a(ZWjBl`d19Pw2SVCMZeAyS8`XI=6a$YPpzXJ!0y_$GWxxM(TDF{ije; ztRpqz4d_ic_V~Ud-x7jYnVqC;4t_{DdQId10A1JSK~I{A6kD&tGj{z z4+=G9OJ16@`@ULZ*$zF=8tgBz>eLIExQrhjS7KW77~$4c-=}qyQR3&dGZ|>nxtLwC zNp(Pm>idK-Gy=DUSubHjiZawvo3<8@d*_7z<4x{XS?`6NKHR;~C5}VI4I8As_ z*BpUNwuJ>)b@LoJ3M)Lmq<%GGlB<^${{Iq`;*;s4NCfkK^4EOrM|Mt$1cJ1%r%Vr7 zb5D5KE7=~32GOI>UKtMADFyUt6!Y_WhgpiG0HbeJn)vgH-uTGYX}3D<{RB9B3o=m~ zd7dqag&E{4Gh69nWG#O&zYrOxS_{J#a#}z@6mK*&^ZNFj!K||@c$44#6`g;co=mYJ zarQRYJBY9~SU}lHX=z zXP<}CCw<*X(13G@SZ~9d13t);>kzl^+s1Dvl3S46514 z4l5wZMx*MFxB2Si5LDBe>!weKJNjhE0$Z*%Ch`Sl7*B!G3mIr{+pcg%8T#U8n%>G_|vVID&Vye3ZsLAdiL1|NufP8hB z1aP)wm;m${e?jA&>MmZPs6e+lXaCRP(5?{iWm?9(Vz@=pzaj>>xdZj5+f`j){qlOi zUYdYypTR+LZ-R$WgIjdP5JO|AqrkbWIc-cXp%tgK(J5m^(Q!A*v^GrTsJ)I#^M=%}(lUiN7*b7`#OIJr z)Tw|XZ%<%8ffo#jNuO;U5O$N%7iLcy~B>)o~r-HwLPI(Xrf4 zR@lODO+TVY2mSIP8>Y`T<6;@&1qcg=!O5f4OVI)H(4!0m$);bONSoHQ`3GoG@;4fH zi4ns7g^Vl9F2K#qR@1FHOXP!9#9gE5*g(aQt-;L~9pvJ2A4;ua+kRJ1NPy*V6!IV3 zhQkRlpYFIijSmI~iF-b$m6!x$*5!IChS}!4inN^JUx%=L63Y69fSlX5UjvM!LjVNx z6+{g`t8+B#o$!jV25PU~X-3uQviViQFETAvOrxji2`77XWmh5gz~BHfHY~c9=7SY4jJY#HHBCR&dOaPO@z~0q*xn_5aHLuP|4bu!VR~ zdhP(^IYKW57>dGf`jPZmVsp72kiz=9S~F~bIw%~r+2v)k_)4)zDC~(BuT{*mwyyg& zeIK@ys{`mluB6#i$(@nF$|Zjb2!cWTMqCJ||9+i%k|B4|*7qT0W#(X za_8`A9``qw4_+MA<)yAEguY8(Wjg(jz@cGrvfF~v08S0wMui^a|6yw8temL#z||sU zqNMQT^uz)T2ax{}88{(XhB|KAXhMcy2HLg8t%)rTw>o$*J^D)LpL?9;WXV{;>-C&W zOoqcUM_+*a`}lP46w;Y#);T0I-Fl&AsAU%@;Z7{4Ktn`&Cq~zRC-llW#G#-Gz=_}N zi{2_M3@`Ys36m$n_6(AiokZO#?N33*`W9QrzCaUiA|tpkAQdNnl6FVwi#-$7+PMj~ z$hz3*)4IBr99G71Noy4 z_8?H-*b@iDHXE2s&)*OHKOekW4nkMMB}4jSfi#I*Eep2FPllK%!?paJxY=*#^IsM3 z;=PQnc|cllIgch6_Fb|uTsrnD41sYNC1@`S|#UJF)MWEVkJaD-+n67>78ieqL!wQysHPosPN(&@0)>P7|De zEZ~zk=sI16#&d+C4~5-Oj6#thF9Xv*SD1s)06*o;Y@>gZ*eq=-LBI%mT>Nn8?8(^R ztkmIe=^);n9c?YvV!ISwDJ)W8@T!pYp*7<~rl_)LAR_37te z`B-kdt~nUJ;Kq$%lt}m1XdXNS91@Sz2E|bUsmcM*aOJVnhMU}1`CRC;2riaVe3tuF zTKW5DAS(sT{z(49bo%HLYVt3wd^Xz9%?h$Cq$ji_&@3va5tsm$#r>K^jH8^NfzLw| z+BetENECyD^8qb8Gr0=^N!fOQkwzi@kh6-43H!7=TMoC1Jud@P{r20T>_8-t{=B|8 zFJ@SMc$c%sno#f4?metBV5Wjn17CwhN?CUG`a%wCDB9h)E?V3Ap`PAU^<$RQTkjywr zS&#I0g(VTF34v+FjjCf10sD1NJp5o&{xKNoY3zrsX!YBQWn+_NcP}~1AGS5;DBYl0 za_8jrfQygV>4lPEshP~5pGRtxGkXd&prhkvIo-JQC~M)lanxZZ<&ynRQ@Wd5OW-Rs z2teQYlvH|X10cXMg$QqPlh0!i>SdkHgfHxuBSzgln_CtulOdTXi~*}y%(|P;ey#}h zZ{!M=^Xw{m>7_aq!NPSjaZ{pnvYQpJ_iA#llEQZ$c!%5Pf3C|v^G*n7GG&?hQ<`2V z_;UI}5y~=YzHOD!PdmFbYC?3r_Q7VBEy)GjnK(EZnV)ISgpWBZ8u(*4M3;^@5Q3R! zFOr1j08voglu$O`E4k7;iKD}Q=Wo2r{5_#*P|n7<0_KQ>27+(Up+erh__itwu&ZF4 zy!l+}oo>C3_bGO@{;mu74=iPl19minUU2ExfO8ek9gJZE_M{Fu#FNDMkM@|3AeKpv zo*=$iaYMLtGSr$0Ddx_`0dvc)>G-aFuPRtHcm=E`_Pd}`y;N4Flv$emTN3L^V+fR^ zKo#)9domjJ(_DU7iv(V<0v?+e$k7>bN&8MkQYFCl1nM&FfLPTK>Wr6|nH5#1p~?c> z1jio=#1Y}xtng+zo6|psnuV{d%a!CxA}Xn|V8p0Iq2-mb!Fwf(30GB9$og?7{irr( zn}20=(G)IaZN8fppN6opg-&#m^tOvQ3UgCkKwb0?Ln9K6OgV`mgOm|>DVp8ObFb;$ z0|;zQ7Un?Hqar77{6X9K7QNmi$BgK^D8>2y%JFfz-qVR_X5-QYyR(ZfTt_c@(}_M7 zTD!Z7Mul?7pr-}(AMk(32#)bf#Y#u?%QxBc=?mAQw=b^gyDygi2KpVqlm>q}1u}%D zwV1j?jf`}9%+(*9s*!0yowqAoM7wFYn=$$(t=_v(h^jDVV1SO+SL$GP8E(I$1XTu8 zSr$2$p9f1+Y6tjEg95ir=TfSFC?M$;q00#LPj!K)ObE7ztC$f=W>1zz0-SBXB;vil zG^OI<%fB){7q-5QkLOquFLx;gwa14r(%U{s1rN_x1XugyhSgM2a!^xm2p|#G0N7 zA~p+qmaj!V%~3fvU}3`bpm^71wA8{)j%iGMA^8?6K&lmAMF5fRi$G)@k6?Lk`$c>5 zmZUjpW!@MuZ$Ywzy{quwb$w94ZQ&M4>m=}mkB`1tT{Dxz=}74mu`~eNk3YU0Z8DQG z=?SJI7(D*Uu#O%y9QE1Q;TuMYSGB0nC^MTG;*>%TY!#$oGYmXmS`k%q8*kKQ3np<< zXHlKi-~D{Nk>e-5{QM}c$uJu2>paOh88kCQs#}Iz_DJfkZ>S%8M6bjsSI6M>6m77S zl_$=QmI_0mqIHVg^0RWa7nL#!o4qF(++KSeo0cC)=(me_-q3V zfPJJH$m0|QPXGua5!qP%9K zzvB3IoE6Q9j;5Asnw+Sgj@4Q_%{hPmPakEF)Iol~@s!=2AC&do^^Q<$zX7NolW$Ql4@JRiRxX;Q zr5S=LMLJ0-jh!L#Q_9`PacFreE02do9_`sNZD~ZyNeaesNU@&l)=j z##pRJuh`taLLjf)Ge#$Wq2-8y=5*hXys{dWsY0z4E}gIBjSvFYeDHFo$(wL8DDR2I z&_d4XIwJ?qnu!hjdEUyK<2VA3#qDO*c}8O7@P%DwO)5o5O}XiDnv^rQVzO_M2yJHl z89EzgKc{C!fJ>d=sc$+{Hk!NwIHMI4{1yA?w~W;9;;w6~mJ1;gy1bXy@W7Jd#Zq=S%-c9As`fA*CGQl|! zyfQ~J%9JxKY-ZGlWEN=Q>c2y*GDA64WxiB_@~^b<8VTN!po|fI9O(BH*4iWK`jrRW zos(WDfVIBJHOENW5Ud;-!~QIMP{;<|6%IBX?|c5%A&C9itVSxwQU&(UPhdKj+wxY} z<|Hi+qfku_Ke=JC;mE|-)b%%(0r3-9Cae{6LRn9=mw9oNH@k!4pc9s-#5ZC|X8ub$*!!=fc}T|66U+U#-SzwQm$tzsq& zT+7PfytJZEW^%#OCYu9OH;ccQ=*U9G-Nmf*Eok!;~ zZ8375hmlkkE8<$~-;m2I$~v`jQY_NS+f!45O+IbrFpJiCV-PXQIX(FZK}=+LHjM96 zNY#GM3TLv}O0)gH-_ww6JqBp4s~fEzrf^fQ0TQ6Pm^UFjtuI1JAsEoLvkHthVhXgR zq#O-0fciVfX*-F62v7+!y5Nf2Xj=wwG{zv-`tsd@j$4V|Y=r!9oi%=CX-fOmuySYL ziK->8S6&baj>}eM@TzPgd6MwORfs24^`P?4GCCty5u0Ja$Ony$^R-?MK==&Bf_0z)T@qgw_V4LI* zbX}tBkkvf`ky~`yCEP5BEe&K4gB=JWs%JVWq}FH~QlJGP|LdDK8PDSZ9x9{~hcog1 z#nJx_BS!VYFEn@eeC~;ALswgCrQT3=9E7%midy~g<~6T9hvB6u6nISKn4K=|NQ!)^dxmU3@$QlZ5`id_1h15IBe{aJ6ZZF&eab?bzPU!SJzy;Gq;JLQ(CQ_dUjk`B5rA&DiBlAL%VdPVRO?dnteTEl`OA3}AfN>2n z(ASB@&PPs72IuMbmiuNDeksG9uPM%>AB~CmY9CZqTpGkv$9$x#bl-!Vo@Wxi~#(!bZY2Iy~ZMrXP-w z;Yu1`a()to`>1LV+=l!K;*{5l!(nQ0Ts_fd`OA-NLO4$wDN!mV^p}-l?3~J{0Mn8a zR(Xt}<~6%yqfPO#Z$*^?Vt)knnZ;27MNpM9l~+8ZqWZ;KWM=Og7|0Vwg2x-RtFwMc z-R$^2oEWrknx2lAQZXe5i8{WQ!*e|VXfM>DeefPoEN(p?@>$@x&4E}3l0S!g_gL4{ zI}c`=1*9KQf`)12(AFsSUJz_em3*UA1S5mE_`Or0C!I2lX<&k){R;hgy#4$bOZSL@ zVhR0XB-}djioDHAW-pWMVZYX>P?`@pd+i(B()8^4%nH?k<|}+mWijXC94yxfM(Yye z104{D)Ug@`Wk(%UeD=!lQ^WM)EShoO{G>dH82kMf{gS)J3AV(QQ3J6}@$taR8UkU(6l zTuQwV8_iX{d4l{_cG>gx9CyfXcsAt(!Og-79<_voh$8MzT<6EdT(40?iKX=jnqb+Rj zy{f)x7ZyVnVe`9douuD2_0f4X>f4xIw+HKw;Szu#(BORxQvmfQ9Up!Vnxr(M6KCBs zI2rlN_@dZAjM+#=))rV@NEqbxbI776dGD!0TgJ zI~bMfS6|vrurygd;Ni*~g|h0)O811pGnQI#uHW40Q3qIbo(gXY+6xGHfnoA4A zMGWdKPf#_{jJIrVrJGN+gYrp3LR!k~0WT~RBU#5sz}0Jz09GXw(eD60Teu77C>|5mig*ES zw<@7c5x{%8hOO6?IEWmSmGyW$l+?|MOWpfl=|Fz@3tmV9W%NHS*0zI6cZB7C_pLSm zchYeEBjz3z>z>nQYT4!wGf!zZ7Y~Nf+&iBG8bv(nS#RZdRLg2+8MPl@{kfTgY97*K;zY7Sh|;4V=p4UCTyFN!p4r0s<9ZSQf|b zK+1nG6I@gGti9wTsVu2CRo!G z#JJ!6X!6bOQ7_51l@HD0rd*tyTbUk5gBV9Bq>XjhAj5Y>aqrGO6wg%9EF=^RMRqx-SAmz4Bxr-|UuXc}Tu(2>1YtnvK zVY99Q!Fb78vLkCPK8f~K+JI0)iI5p%0V7V)NvZ_6){x7H=gibLx`Q$C&!s-DBg&@-xkW#y*E> zw^muSP0uG+ruwy}0+ht4GSmhhxt6TDZu|yH&ETiDZvE~tbSytI=bpfG^^Y6(+}~}B zyxl*=(q9Z3yLul4TE4u%64r^MFT%|>(ac=} zJ`@{gLsuZa-J!qb zxC4l#HQ9H2lf6Mg8MRiX#@uMxA=uDl{BtH+o!BAHBVxS*nDe&w4_1Z$a0Gp76_@Y#KSp@S>rEXVL=tz>(_pyGAAK_(^Trw9A?nz`spe>Z&&Z`nP6r7M|vPdqT;&Dsqwf5<<*J~iiIA*Nc7$I=qr^jGnoEmktsg4;h5XDX?DQPOSp zt0~hHZ+^BFr;#awe_V?^b)D#XQt~K7#n9c>Q6tP|nC3oz6 z)sTcPDH@?0>BJw~b?NJvzjMZ^chpQevXMegq1k;AaXiPuh4br#;P=6ZWdvZ|03HBc za#DFc^WFGj3;t9j$~m`>g0}3|1ZV=FY~aNSS~O93mV%rLy(j1D!YI=`L;0DA`JroF z!w%&peRB?6u)8Yfts@hga3!HtO!#oVI#|bp{Wt?)$)dOD zYdaS~Gu5UF(fB+!F9c*jm=LcoGAFJ$@n__y{JU#6#SQ^;ak++W@z2I6beC{rcWx#c zV$|D=D@Ipe1tT`3o6g<#5m$~Cg$=A|37oy@6*xXepFN!*A@7Vbr_))BPB>9j|064y zWg=RAbeQAGC6elcWtz*m34ktO<0}2dPmlk7z&kZj9MeFj=Bpd81sj$w>zAV&_tD0h z3>E)5yCz1Okg93!tauetN7Lk~#t$ zV;bv*m1-9>L2}@(b=uwI1F{n-%e&RY`fp(>XnQOQZKnSJ_r*hLml+5|fap1Bz%C={ zltcBX(~3|D0VEeTI+*Q?CK)9{h#C)UcFvYCsX*?60e0K$bTBv@6h=rxeTNNBCGMVq z7qv>)zZ&Fqkd?#m%4|0Xp4~|{I4uwpBkz7{@zO)Tl7)xvKOv`k3AR%n61v0nBy+-^ zhoW4xhCMIskbS}K%BV2qUFe|;AC)2$w*9r3ESqECQ%F;7iKCYcl^qBcmD(oMXmHk! zus3P%z?XDxf9s;?KQWU;Es0c|#`DjOvf8Q$hKd3GSEV1IrikPsH zjC9%vsuW8wO3uvHFzc8@u`;!sVzd3;c3>{pLVcM3c%}02|v}M?aM6*u@gjBe1&80H++#&OD9V>0zE{$&O9IyLAG@IVOrHQ83>chdfo)l#W z??=%8bc}bXN4(1gN1^z`XpWgveb9ncZo$$K@&S@j#YXGSsB@#FSp&e&tS9fd1iHW- ztsWxtFND5euJ1qo8ML0<4nddEq^DmgFf9gs>5@Wy0KrCbmFE+&MHA_-Uc%X61L&f7 zV@~yi`p`?6rjX`g&f%<(ynRt7u7h9U{VN;_@GhzeW-7C20tl;mpX^{bzCi8eDSE`GF&VDIT-6`Y!0=!s_|!C240}XRx7bwf zp3ZXPll8GM-#o_%^tCZS_M$r53pW~ZSPgO1u-R+NT7B=2H*z5P?7M0NK&mrk6I4IJ z`6Pr9C<_`3m%<8(?{1QVit>1s1^mE#M*wBj;d)*HM7vHJa%ih|-j5|-@q$nDF&sNyvT(&asgb0loj z1$T5AFRi0p`~@jc%<`DGqthU8sKAHF`Fo2Pw|<^pK&(H3}Qm-IbK7+j#jQ* zNX*sGL$3-9%|6A~bxu63prPzy_A#GGgiN29D@`S(gU%pP&yuIPoF${y3jfgNJXivL z^KqxcbYZ#(LYn&dixvKt#@cjFv0*d5N)%?QDZ2}3EDIb$`E6lF(@&IOu?zYZ3xL4# z>INIXk9dzzsmon+kAEWSHnRUL5h_w*hfF~nPbISgM|FI6a#xp5v!#eruL zXzs_P^{?OqJ$_Bg-q`3)W=n;W@e~FeYVg)L+}iCeY#;+e?&+o6e?{DRwTb%sb7_my zv6|_emzW)JH=buwHiCznAr1q6zK_yQ^n1gLb6{@92c+qapGh6^8NW`463m8V15Vcc z`GUmDOnD&982|PWE(Et;wB}q6)2y15##6j_4BN@t@a^CHi=@i8!5XJ?rFOb1XgAA? z9p_C=s@JSK7#GOILhKxdqsTDbGIs+=37gE9g~Y8e^yH{&5lE9vVR~oioX=`=d21qd z?A@pzcSv-vUu13iuLL4_VqCe4@Wy*Mf%SH~zcH|Us4U=(d@<9-ZdaYBe+^a{51zsB zI6cDl)c;^J-9Yi=M)$PeUnr@AR}Xw(2=V?_*7>rjf2NJPU#*aVlc^z5RuBrNH2Ady zCKQ)&@at(%p9LhtZuT0CSm?Ih#0vvCkr+fyeI4n2ZRo`@&M+o&#*QAP*}&J+4Ig1( z9)v(h>Z!Nv1$yuW{>?}^1WQM+uGYmNMst!V9HQ^`&;{Ey-kyo3+xNmmu^jf;>+qJ( zx^;|CWouw&E{K_iSsfM>5ruJl>a{YD`)L@Dup-30AQNerhT@MNoIm4dcR$KMt{LRe zn3snJOArIIn_^?(|BM?9cT7vOq2k%zHAGsEdersm`6AIiI-HDy-kQX;N+v>R*Q#^2 zj35{K2F+;$*r%m#>sF%m&r)=KCrr;;oOdn}CkEGDC=a9vC%(g=;GLU99>jMxPef`_ z^$>9u+mJ%gBF*;_=cnbfhie@7EwF~h#haTbng{i8QvoSD*4Y9dn4C5pPszqK?aO+hgK+})d*U_nS=|b;$ z-{_Q*&yIiTX)s(A)imSph#)kLZaZd`DI1JCBNuJOGD(! zw6Hux2+XxrVWa2A{Pn-u6axQmW9y8-!ZNERE*44d;8jgU%qri`A!SU ze#Tsvwy0-JlaY{m9`BWVn!;0(kwqz8*fok@a-fL|QlAo{&*2sBXDMxBV&E&@n}-^h z6D1xmJ^iyU=8Q6lOfs8&u>2KL+pFkBizd4f0?>87SEyEf6MvkqKEj{vw};hv=X0U< zLq$16s3Ldnx~8fJSGarbp}^h;=j}M5V|c@R8ANV+w_)dNQ~sSedc z097^kG7Nt-5uUu9rIfYY8;?e&Ml>e^o2;ySxM?|l3a&^VY`J3~EYl=7d+`~FMSt|b zIPZm-s74EAwMkqpsQiLJ^_XsqsYb%dRx6fl<1_J~@&$gLGu zn0QzAtFv*#p$d3~0m;!RwaOn2;3=4EUM^(hL?$-A0*+hTNED#MHB*}i^JoOhwd#be zdqFVbjM_X9Lbro=x1@o%<~KiL05WMz0HaV#L0$^%3us6hQ<*-md()B_N%9T=C%z@q zd(PW+KXL|Lz0rg)sxw*nT94&+PBe}i8;%uz9MI6yw)qwU#IQvxS=06N=c(uEc8W7> z@IwPWq9CiT92m{RC~0YtM-+KoPtAG7FCTn`z~hdZc(V6HWi-Ko4AM{nc}zjt4<~$Ed_yS}q_XG5`06B_C7A5PgoxC%(Zdz~`Py zjnC+RKEDKgIjAfR2UN@>6xEMxywtCAQ6OVJwHqTgoJU8tUmez?^l8(vD2@=)^^zap z0smd#^6w7@*6awmt#69qUEk>hxSwfx38kLlBhcU%=l_@Qd>=vwUrgyUK98(4nA6%u*~9=V#ryIFKRUA15AWG67c1}v=zw?%N6-SS(lT>b_3(kiY_j& z*Kf^?)eojS^;0XwB5g6{ky#U=U;yqFK;{NA%lIB_LKRG{_UlX%`CrcVQWUH~nm<{* z`Eo>#4BnD@Je+DTmM1S;B4@eNUNFWwh$M{IYI33tK?P4aGWd)_hRmydi2=?YwAhj%g87)KqRZ3h@42 zgF)+*BuR>{e-%wu1lo!M z>Dw6rZ-(wtX)`j_>9%2nOO|Yn<400--o2<#9&;4A;<^aZCOpO3Gb*6|imHO8hNr|! zms{-rt=%EjI<%Nj-lqIpl<8{z{ujT4NNIhF%;d4|=_VB|dH~d*2s8&U)3Ck>k-tHV z#3SZ@I%Z6fAsl3%e$lWFf06|(y88ATt{ zM}Z9G;X8hkp*^?`S;-41*4%PB_c)lJAy-&24%0ci3E=G0b_ce+WXKd1h%mqTJ2*(T z;5up|T#w3VIBhJMNFAs)2lPe>YKA3|UY6+YVCNkhTG8Uc$RsM-rk2}7vkFImoS{G3 z$mdDHdEjxmuCE^IujtC(e+y9!gl-|%)GrKgwrpG2<#r~?iPS-Dk;M{40rPa87p~6m zv1;01`C2|+tH~W~iz0szq^D$LL`z~vROgwWLy4nB_#hMzQyU*OY|Iv3PggkXxHYv% z!md^$&XlZSxq*dL30u5_?UWrAL}@dt`4kKH``a%3)y{QmV@IQ)x6N^I#C6~>gcU8= zEK}rio_nM(;m+P>n~{aV1x4y6fxO$66!z5eSKW(GCLV8AEn zzWmCa&2iy&5(O7)(Gnn&f}wIPOOkGZu!RzR3=DgHDSOEpAq^hZ2VzogMm$hd)i5_` z-bF-yelVPdvbUM%<50cha@n{@ps~n_D5O(8it8@MZ$1tHm6>=6@IHG;1G~qv-QYdSl50Q|_$^*cw8;T%*Do{D(ZFJj1c__-WYZ!OrLxN^SK-WM z!;yV4*ynpF37uq67x+4eCzRPC!E_g+K-ME@R!(d((H*k$qa;K0N&;pId2^T&_~+!5 z^E?>T^}?^-ibMc*k(o2Y1om*D{bZODIoOWNAe(&@78GERoY7!{e;SGI^7)-sr?nMF zV*)-o7YvF=ogr^gTOBT8<);$;Wf`pz$7*gL65)Lj!kK-)QbfR$jU5+xhGf4KJTF_D zh?!L!K|S$mx12aH0Rb95^EZgNBZn2M%U9{!WLVAhW|6s3TehU|ix!dp{%7da+Va-v z$_w6=MReyg#h+k%fvV*ffFH#k;ti0ht@-)26SRT%2>J=2#PPR*gPhEbf9C>I3b4|i zN3cA<3txEv(q>|9A>AtJTq)$fh``MkzX{G($~sz85mcC#r~mZe?W;a<%Msgu@P7F+ zSc{SMU#VPgQ@*f-KBB0I91Ia{JfuT!YgIy!nrq-qHS!>i_^jq_ZT~h!$gRy#1$@<7 zQqu>!6C!6e7%{{|HpkT@vJ3_b&EK(DLKr z8T`>s>z@H_(WY3An|X-f;p^^(x5Nst%W1D)#kZaKi|a2E@HiTCND=Fjxm#~!-IEHv zI@ULM^j8C%9XZ3xWBDD1Rl!o;y#&0Lm`3Se9!|rO9tA<-F@+#@#ju%YAYAT*YbY`r zi`bm9_wm{rj(I~vE)_hmeEa_kYTWwGa_fZmMDCS!En3NK0En#5WI@iTGW(D&siu4l zUR9+moP3Y##>$tp*@y_-%UlW`Hc@FYt_-we-KP_ty^(1=f%9Zq#9hbi&1gdbx4-{u z0;iC4$)TPXy-gF`i`1`r-B3!vjomkgGqT#90v!t-GcS<8%TW zlw}wcyE`oQ0#5G-HJ@N?AG_|>jDV+9zuWdPCYfed(Ctk{tr3g}meQ7!S-x6DYyBG` zPWd=Ey^l^U-^i9nqT%wrd$2TMZ}W`+U9OqEdoW%^XWa)8n~^mXn#urk_0`&cfoU-m z^`G!TT6$}MiMp_ZkyVRUo*1vQ!9YzZ+DRO3UIw?e^aUCtI<9yWCjMUs7o&2Ds7>K? zDH~@(427qNVbLUEUUX$Se?ePmcXIW>8)D{93ZMu*L+z^V#JWybt#!S23GIcjXZhYj zp{O&@#ple%@HHm9Sm2e%nBC%Nv^>l}0j-wDxWnLSUB&ld5c+boq30-lZWu%Qsuk0{ zoo!a#$M_lx7a}@^PJjts4R}7yv_z!9RNvTT1 z*+1(vr6aV4`CR0H(t948dU#7l{F+LT4+XR{Fgigf9Vov!#3!T`RJY1~Dw7}GM2qRr zX=l$8U$RCH5M_BC3*#G%n?tl@?ILde%!u?fB# z5p(fzL57DffqI8~c=92dBVnc1qBj`Xk?~qqR=q@oPl*DWmiV)aBVbCyHn+Z>j~VNX z`jwCCUTa_9AG*BmV9W9EKgK&Wp4E3u`AFXFM46K%4Z2T^fII>R-U4W52I|8xH{EM8V;C3CG4jWf60N_0!dq~M zRI8UhVKi=&AMx?jz4L^k#laWpO>~ha2`zAgi*1Yt&(3a}+=_!2h0&S~=>DprdSlK;< z&0$7{cV9=`@yQp-x@M0>y@K>QVc#zUOFao{Ik>O<*1=zB@jw(ueDNQ+6-4N9hJ&59 zkN)}P)VeysXL9YxkTy3~l@Ls<3~kI4qH95H{2h3|ry~gs01h-3oP8mLIJhVZh{|Z& zuJ(pTfp*JO+YtK-53D9BoX*^pV?lDS3MQ#OOs%b|kioBNGrSl%d1uo|_G_7+($XgJ zIEMS;N%FYWXx-`aJB+Q8&fX9K#Kb-<7&9?-)0BZsBNVSZf;cKo3jTgY{eVYh-$ut8 zfViRSA|L^{8g-~olpv|vQQE2hOHR(6fGBGH2+i^LiqyQY-qd=*?umHaJbxI?RWKE; z*7pQH$^pt8SC^d|VmX_XVR;EsA4XSSb^H|&`TI=Tw90T_>n|h#-*_G*fjHURo6Afg zSB`p>?v5^KFK>`a7ZcF!8o*rZBaT8An=?Q}@5f3c5AhRSf zy}@q*l_Q_{ne(;?K(AG0dhf%GCTu-kI}m4i=5Fr>1c;Hyjs?N!2o$djC|O$Ybg%5& z)q!Do+P4Y_6)^YnR6Fd&vQMZn?2-Zxzv|7D3SK<(iOH2x4{@Ikl$zrw|FNh8&j~mC zq&F_0a+?;b4>aV+n6H-Dvp4aUC3w(OQ@#Qv1;r+U1)k(y+LzAz7pBAFFnLT~JH2d0 z(pPaDrgj(ZJ#2-2}$8irD8iR!|0Hv(5G?du-b@G zyn2+xf|<|A=f|T}z9Uma{~C-<3^p`hhNa3m+NVttO1iHmxW!TUiXByxzb%o)Jx)HjEXh zc5F8|!xqy}ZaE+*O|?>8L7WSJ{aw?ph%_8>zyprH#KN#Ila)NwairY|{}>x(Vnw@h zzFw>XamAtcS>#Q0atvJO`s@nwzs9;(-jq4VU~Ekg%jBbEFp~4mwhZZ^95*dJYT0H> z*S`d18Q)BfEkku4rrsH;hQO!omZ8&T_t~hJDa(sxLZGC)C@GM!gFE+wcp5s{q~zDn zB1FeKb~KaNn?9&tg5`p#h>UT@bWa~i-{Dz$4B^WU`Hr`AgJto?eUUD>SWgm~Stey8 zz~?C6ItNCp_;+a!_x_7-VZe%7&1;{J^#Qn&C2~X(`P}3Kxmp0!l*+W$(^c#K6&V1J z8o{}_jumazboWgH`L`z`6}uc@kl*zx<>h8``n*M;xGl_Suj>tz0;Sb_S{16f6u7Ue zbTyEdPc1Ylmhz7M0;+@3Fr|ea;A`*vPoI8iSq8ZVf+@3Q=@oOEQ$jhxx7vIeht|VhRo{|1F}iVeg6!C2~(2IuVk22k=3y z3VL*|Psc(wWA0k?v%m~z^yt0Dw!r_RPJ;O?+Y8mLr-MNg*s+0yKhul9*R@2=byka0 zrfbK4Is)Mkda@d!Ts6c89m8OUL$!b%FAV`YD^b_hmoBCucY*e{!011)haUE2n^+qOA{+QBwRxUuc&{ z6u1=dO$zAk#vz-*mohLqu`HuA>0iYJ$uS#34XPl38wX^iw{FCpDWda@;#XS5MonK! zIR&*ek|fOQsxmGJ>`$TNv{#jK4mP5C_;S*tX@v$CCj0+SK{WzRiso*4h^!`C1#Hvl z6QABvHoP#~p2V8Yx~ zXZ#fR&4^3{S{lT=TfbThaFH$qU z#wC$Y7TSpB{~tR`CN3qRXMn27!%6+E1r_kD>yK;5Vj;{kM^?m`0G5EIycMYm_R|&L z*9Gvx5D*N`56KiD(@J^}3Mz>Y2v*z6H@16O67UAE=m&4Zu5TRNMLy1IEwD6PV%`X5 zbrOqcq*nEabe!fUyBED8!Y^bY?fXAh^QNe_>Q70w>h>$$zaTz1?=x%i4N^1Bst{EzhUf_> zalH{QWz^TWlp!=?7}L=!{ra2n6jIL+yc8on+MfBF_}0XsL>XnrQ^ko#1UC`fKnVT@ z?mHOOPTB3y+TaC&#j9}%ad^!bL2OAjeTld1I_-$lM>O|j=X}*sdEcnxhTSmOLcR~)Ufk8^@ZoPEjlEQ`RW(*&4JWh|Dz_xZN~n}}<-(U3=UQ*NkB zKbbNZb_js1$=Wi!a8*{|&69q%Aza0galSO?s#rNa0X}VBUg_&bR6ynccN|%5D3un- zR;JPF=?oXAdVa(NcUO!WN9j32gn$79KfeX#E|VQ*4luwxjxA@HXr1l^RiC5pI9&`a zefEWw>zHkA&W5YGSoEiMI=8096>8XbywalI&Vor13`(UbwBs{v(Y^o0y9Sx8UVVaa zPZZ;Rbw>1CLEz~c2obNjXbEtEh8j@TlE3U-2i=?^I;*VgZNOf>KXb21CSsOJyr+=l za|ude9S#>&PZ!|~Q_(*3rvNuV$iD-*-@F)XUROd(N!c)$ye7^S z4vi7=6IG`511?S*qVz{Txl`rMcfK0@61bWUu&Q}A%fWDYiutPqvy3!6Cx#f7Bor|f zFl|gAb*pDJ%@}$8xtx>4ZU5$9g5YEb_~C%$7WMB4!%*@kBuo1xhz>(m$ zb_>?fkjnqjzChk|R%LVz*I2rTgf()ieqdimm^o%(1lptkn!W8~1nWtf!*ky!^{2R; zqGTT*`T=rOl|XwzVW3a)UDq^*%lu=LdqGA4s~y2hi_#oY))@@HPRXbOEsS|uKvcN} zI)+y>jv3SpoxZePk#AGlA9@*MoTQvT9;F*QmIM{}4J#2PA4?w#QmT5bjeS=*XJg$R}nKVWI52o>V>5 zKl&R-=SxFkH8INgoiRe^YJM(mFfKmXjN0zp`cWJhdpx-47iy-|x*t>M%!K*ut|ZBctN7&Ea)187ICK0e`lEc*BFt#=HA z;w7*nG2vLM#mgxxIJIIIFKuhO~0I- z7t2j)dDj4d$ri~GAb|x-dP6yvm%3|5R2jIohd~IFFYWB%|7)_v{lb~l5o42+0g$mu zJ|dl%ukB~U;P0*K77Teo^b4Yre#A1^|HXeyxM+Rncf|%tK;CSfWTlIrR5+E1B+ZqO z(;tQmm?|ZKmJ!il8ER}~T%7N&g#5D0s#MSr*>E!WYBBD3IRq7FA!sbz`J&y)seNPM zR|&m5CVXY2eWTY-UmhO3aB^;Rbw$9cKr+aOO4ejN>t_T0e$h}7de8|yfqnYOmc5hP zYu?coaF%z$^#=mYotbAwl|)@C4)rb5!pirr^dh_HSj1TiCXi3-4(RY=56=jB2kRQ7 zeE3f6uF`F@c1(CDkau+|TE?Myg8NaDv`AcfgvNRzf$lVy@9_$jg+k1I>bgD3Xr-*(2PR+*6h=cU% zL^a)rDLF0pPI{Ezj%emXtx8rmU!RR@0{Rmchn_wE1A`m%V}&oqX&i*n1KvwsnBUa= z;4PW_tr^R5SrG=Sj=ltY1(m8rZ`o?>+H6rAPVKR-?3^!2`nmx*)A~fzj`?zr!=ARo zyekQ(F^I*mg&Iky$_0t}&i!BV}~g z6PQOsozW#~*Mz#UiG%qV3=>I zmpB2yN*$p~T3u}rtTgS^tVUm(tx1)TJz0-!D9hS1v2HiYVIylr!}+Bme*v9tEJ%~a zm(#y3M6U@+uZ>*xA@Z7UYJDQcTc0?bke&~qn{uqnBKXS%LnZVH-JNvN$kk5+>&GBk z94g6P5cN8i8)^~!0Ime=B8g99Mx6gcm1LLy8)bno)*ptNZn5z?Oa46>RwQlU8>ZI& zFqWp+U`k&T{xm?mK;7-0<56cBj(nE>sHxDI0eRZq(^>Ac>0BZR2TN>v#|hjsNvhY3 z_ggg}iu^Qcimpmf11Citjb5Hb-<4Zg3;Ox5HN^x-X`lSpi?zW2VY#VOq07{LS-S!9 z(TDqR(k;scVba{YCl~ik0{-obPzj$bdlhMt2QM^)a$ZywT7@B^5;0& zWfbK=gYY;KCa2Z>WUQHo(vvfpE;7`eHy?z>4h2k2Cnc8!s_bSyea1hOoaBc*IH+R@}LV#+i*>fffSodO_p! zZ3_#_Y*{IyXro*ADxoGF0y*zCg)le9dUG^PA?Z{*!e#+GGig&X?awN~XbKYdjfTF4 zNa^gf{tgz{hfljeXmiKs37;H=zqoL!(PP3!2r!hvDHHdlPcaS6W%xMt*yE!~_khcw z=&Ks`RoWfOqa&9K$er~HozfaG14ngWl8L)(xq)8~iCPXp*SU5m$+ zo802Cz@J#j9Rz?3(K_u=kZS~7m&CZN&3?9?mY;M@$h+C^!pPhwvrJ|RAeu~L3-{Xh z8u(Ou`C#(TvZLK_goMc1Pk&h^KJJBVk}8u~xTNTvch=(LV>X?bBVCY>yPc`(t5Zxfd!womCQP*DNy6Y zXK^BCo3n|OL94{5136t#Ykp{~)9}GSl=Uwoe{(xIRj;l^Kfw5;h~b#D#)Q4R0Ek@qktW@ru4eFqtwX~&^FosE?9!b^{Ch>N^m*K~BBowzXN2D?2I&%`x0uEK6@hKdFye4{K{u^BZwwDHSfouB8AudROg(BPBRwomtwVeIRjM2b#McaLi}^hVwb#<^oA(V*o49arV|8OAu_ks=uK3bEEGM6!qez z%kh4U2VHy_-1d|jcHnD8GtOpAsj%YJUyP=qe<<%`)h-#Q?ci~ye!NoN7vW&rt?CC<)beQ(Xd53k_Uz#ffg86RAQC;h zLj~Lrv2YDW5%FMettIE)Y|>B2_z|$G<|X#cB_x3$`5***K)tVE4priCT@&4yi);UD z>Os9D3xTwt?}(H~#TdLK6*k?#<*XcJ5rFBAs)wR5YjELGR(cMIb9;Xg=Fwhz7p7~% zeQQ}Nr9`VSr81|rzhcd#6XL7WgN zrQ~tccN@@n!2A4{iv*l8jfyK!pK8A-3oo1;=q3+CB@1R(*1~`E_wD&{o74B`2DndK z0=GFU-R~_t#!G>XpaxTLF!MAfGh>nR;%vih88e^8UQ6-p&SS6|cl{IX#rlG}Wn%&7 z7aPU?*sd12S4`@j`jG-!zIpWkm$==|^6h3rv%DR}INl?9zc@jgeF|zLk-^loA5tqtg#D|MX9CRV!5xbN)aKQbEMhkW_u07 zb)48iq){Y)Dtjvte3MI7of3agf$AS^`7MQWw;cseK)iT+xN-z;SMgk9RFn5HlewG# zsnI#T<4UAgKf#t^R@>e^FPfnGsr&{PA=1wykC(EM5(e7jd+ik0oHU-an20d~3Lx(4 zj1fl3itd;gqUoqqJ7>d1Wakk<`=me?Z4o9(IbK{%*+PkJ3%guuF^j+P|3tcb%4hSO zDKzq!kd|Yin^kp{>E)))Gc+-e+)kKrU=L#rNS9Y>b)GaIu#7lf-5v<*`1c^zfF1*( zQC_Mj%Uff`)wV>inqn9fl;wqY872wgf$!7!xITI*3UqpQcR(LqBGb;(c zl-WhFnA*W=x!+$Xm!OeClX2y9YHtjhAK6U<>qixweaJhf)$kHMDqr3z&|#=PJ`9fN z71$>Wy6_H~p^2C^77_S}u9;xYZ|;XD0RrDrccC!do|z62qdDTG*Ws8WGzeDW772<=Sc)sBGYHM`hn=P zQa$d092a0|1xaWDckm`%#!K3giwR44voOX`K>Q&P%6@o!_CAihC_zFX=eImZe|}XJ zK3q!;veVpD_XqA*_U=NuojSV82sTSs2l2DVom$GO>@}AcCa-L{H*kY9DW+FzR>1XA z-K{&qs06hff#6V0Pr&nG?Qon)6Rb5z{moj~oUokrvEG>C4-9N*#EhroLMbJ8dCHfe zKgTM{=hC;?GyCuG_HAF4v$76;a3#Oa;BA}f?@eV6zA056PyHPJX1~W=MYEMd z8QvA-D@hVLxNYV2v;HY2b%5rY8YQ%m3ld^MOIHEwAb=sgA@H-18>tm9fR^@Sj6Dc~ zM_Pd@615E~85oa_Aq2TL)-+2m9D523r;Bzq18%w~Q@NpKg9y{;xaq8f1mqg?_tP?g z+X%)|ce!d`>{{E}qyHm~EvzB$_rhY843X)3;&M7g$tBrTCZJqCJc3*>7){3G7jYH7 zt~_PV<5Yf4fyUZxHl#Hr0qP zZCX#?a-nyM*3kWuA~rQh*iSBa$MhoBchBkR%??MSP{B7O2U_Fh+uMquBIbr1k`E~l z0Y+RTM;q%tW7s=7vX+f7r6xn)8&K8{dh}3$Yvl!VJr*;uZXe|9b1Vnr%RxUMuW z<758Dv2i>~YVs%GU12;zV<}4^d4wdy1-<1rC~l8+&1|Y-N7wK(8odxGlxH2-zOzr!u`&-#KXBQ4b*%C?QoZWRwgDMMCfzW3hmxT!>*B*heXk2{d@vBzPG z6jb=T@gqW+&F?;FD=f0@l0;}tUkeCia)n0v<|9 zOb-h`=`nH&?Tfx9TBxOum*Vm zJb%(NvaPr5 zEDrkqEKH^rYOZf3t7Ri-RrK<|?9|cV`fukP56*a@eTM6%_BY?{s&#xKbitZGZyC%_ z)OMS~{-k$BU|>V#pEjUyn?isuo#a(x%pN;U>VwVrLU{V5%=G}6Ul}Kl-OS57ioh=k=$aaZ4B2IrTSb2&W3X3tu=^oDdWtt!2I&&u|KDNWnRxeJ{ ze-4GpR*XxC7>6f%epbAz2-Q1T|p3H2y}AMpd2#lD)qeXK4@+ zuB~JxjQt9O)tVlAFza>V>~*UVH%qyPX}f$(Wo={irOXQH>xSI|dN}0W)irfz#YGv_ z%wqj6cn=1#qW6Z$-}Lr=SH;U?!6qF!t`})O?m&xFRuqnfUbH;e%!-T{aKO*D(nMtc zXd_~4nH!w^o$aL2BiVkA8ru-hU5B&@j&}vMvO_T(YG^|;NB z-D=4_2;T7Iok9k^jO|4(&aE|sOM&czSh_fGU_j}>!3zmdZ^Xm{i&3cFGa~@%+Wx=g zOD{~Is+To_kwLT#HIvjV?L1!hpanTC1@+|mWVo{*ne(jRW-<1-*=c;fH{LG`B=5=C zk}+D!s1Ear`S%LUKfQ(P;UjMG8!`{ME;>DD_nWcVji?w@;$( z^L81vW6o&UPY9~29JIqfW#v*`O?%$Dx64Pb>gw`&$FB=Al32!4Y9i^j4f-WNgr>f2 z3s%vIv9p*IsN8|9HGN$8orjTaeb7ixAlJ4+C1nt8hH!Ki0HILd{5V-eS;-%@5r^7j zHkoHt|BEE(zH~#p-7$5bNco7V6;RhFXrkQT2qJEJ3gCZWcyPtkH*x{2*hMEL1%4y! z>KB2PFRbhePMu8e@e$koj`mR$qdj5FJ(s$%k$(m?B|SlGFMH7aVKMfygp6?Xm!=&9tTo(g?{@X$ZdXRiTmPcnOrl z5v*~>tn*GzW28*hkwE?8+cf<>2OdtZxYQ2zGzsswhq1|0!6sy;ssXp(Z9n^dB?Xv6$#9!jh1IqUSdCQf#qUEmaw_%TRv2T)RE z{1Bi^;ENDlI>&^~Th1O}nrCKR5;GS@p&yjq46@4kq;W9zBel%UE4^i=1 z-z?X0gjP&C5CRxUOA0ik)kLiHecEL4uEPjAr!SY$AL)%3*pP)R%4Cekao(FH-O!y9 zV;PCAoQjhKdbU2Z<9B_v8QAvlU3s6-uE3O=E-|%v-S?FkJKBy;W&=Sds|TX?f>Jz0 zqZMYLK>3aUY*b+iUvOOLr0o6d7&R9vq$%G~N6DM_n^lIM-_2A^0fW#~isNEW=C%td zKn%jQ`dGri%3p*s9;pPh&$1Rk_x~Y?wB}FZxoWgbBbd5rg$p+_;j=R_=jbC}uiV6X zB(D0&tR}|FG`4kdsKVhdUo8n&jm}Hq`Z$l|vlE*GcPtD_gmqWmnxP1V8)k9D0p|R%82=V2WW^DTqq3HS1T?1gPzzDt|b;f7Eo> z*$2Ku*_-ML7LB>iF{7E^SPUP6dQ(cc%bkCX=*U^|F`bULm_ZzBu$q@3Dl+JG=_@Bn zvm7{JB*YM}@E-%GNJyP^1GyzkEV`RD0svGkXIHQzGngG zpxaoui7XbeA0JeM_9%pbZhjP#>~A8ie}x*s!z+>9g~jpt#9nOo0wU6gfwx5^c_6UOZ1a=mw;Qdh+2(m-j z{V0EIhKC*DiJ@v6HLY(6!hFNQ;GrGy!WMj%9f_YPgECU_Yr)T56%~w3KmMOENH5^_ zRY=k9dWak)I8U(!R-$_9UNELcFMxV37GMKH{c4Q1d zol*@KK^Y2r*z(An^t*cwq^$4xh8=X%HjB{4BLp*p8R)v|ePfIcyi+02D}CT!=W*`$ zyI`FgQ+0f3OTaAh^*6yVwP*JJ33`tZ@lPe038_^6hsk4Ri$zf~urHYh0sQ16qBEKX zRAG?|nncG22iWy7!7AWO+x4Ej?lcA+GvNYYIkvKYkKOQBK~O6XNE_*})1|0Ei@JX- z)`Lt;P1kpoGi_{v0Y!h}HET@dSPsSF971L<(^ZvM*~QNK*QVkYv9)BJ0hbeKs^y@l|M}m)x^Os(hq34bev&3VVFxY$R6m5$8F z|A438%9ibme&kd&r5q$$@WDIat)$bsk@*PKc=k&!;Cte~ns#|rly^n90Qcv9ZSSmoGI;^~tVA8 z$Sn?uQjOhKC!?P$sL;thLk!tzor<`3&PR><@}M$1pzGkrGTMMsGROgnJ!C52qAMF-eN+yg+OGT!_-Ju` z-o}c343`t_$X7>H!w>qAaLFX6D6?ipA#3!66J1K~3RbHXhDT%Ei=eRN(vm<3(~z0& zDFYheyi(n2@5?o;J6~nG8+xI^(Z#bme%bZ^nf~K4!?X z-BmmKW5X%e-?4OW+GC{^cP?rf8y0m8ixAc|TbJA`1bO3MNm#C_9|__-0sH#?B9W02M3NvBG5L>RJs+AMxb~?_R#S2D(McnrlRU4ME5WE#<4TxgH9uSwa#t_53oEb;x#>MX;aC z4#5&$^bpB7>XWARzC(G@2@GNxfj1LTOVm&Tmct!$oxG1~^=f4}GM?Fa4h>U5VMoIo z$IxD-ZB~v6VH@9;i9k>TUyw50H(-Vs$~%CVw|UubD0tvhO|{|ZCMRo{V$|*N$u%a$ zq2e{o9WZvaF^eVpDuk`)wLj}fgZCSCAChirxt3^WkU6g^5zO0gGSNPs%bg%*e;i+A zFi7&kT%s`sjpGL{Qo!^P9P5^x2ByPhR202cF9TD-OU`DRmZYG(H5U7AI;z;q0LHBD zPO=q?pJ{-zLyF?YVFC>`U@TQReFRUnr`>?9Ny&nv(|2m;P@5y~Pq{157LE3;$s0sU zsV@BC9cU(ocO}RVvJ}k7JXKE1B!F>Wr3m&0&hb_JX;6Mq1-c($FuF2hAZnIVrq&BY zhc1xdJsq9HBt(FdaRx0LDDV#pTv+6QYP&_C0uyo>_cp z!&%0CTaiM7i}x0hoY3WoWxW}rcKz@f8{pnP_6dVHQonx^sR(cTQq4AC2%EzdorAE% z%h4rn6QjQJ0^+21YbCto0+wh5=}!5%BQC8ZRZ>Gf?X!So;dgrVg62~cre_`Pc%Yeh z)8z!^+=4&)StylYzTt`{_N=c70OLL&I~QiCt$fuFT03ggIL3?;>^E#S!3pEcr!_Fw zrm{5ZyZfmxi7;ddUqdL=(fQLVFo3}%BwV>(Gj*nns;+oe=B0XQH!u1tR)W6FJVOrH zYQo+#ywi*ao8wJK8^^1e!2k~-nbCFrqT@@;{vq1sZXJ8z2!6aT;09A3tKEX;o$AS3FSIxx z%Xfkza_6oVMjz&9d%)k$;*e?;<|jHrTSFRGKb@D_`!0hz7gF|T9z^RatjBu|VNKAiE@@=A(TtU0w6rV)EHmQXo~%WWF>)}7kt z#B%dVkiDeO6LmxitR-ti0ui8D-Xf_%a7i(3HiK|ssI z6N4_T7?8?=VZNSEJy{c@QJ4=IHa6AJrO2nG>c>MW=&>0-v8JvxsTEyP9av4YKs5)+ zdg&VqLJWN2OIB&n%dr&H#+QGcsl*Mu!ANV-q->#u!%`Vw7NfaQaX~R0^8M&tOd3oE zqj}m@-c~kvfEk*=Mh+|59o@^$zXrS=&~2wEo_4MlJC%*}$e19yaA|QZ_e?0HgX6|& z8*WKo01oxb#-WK_K&RHeoU7p(j9xSa2N!=U%Z4gB=Cip?5Q2$T{m>+&2bG+(Xg;L19!#2{qMcg7rBF> zen;ZHqy$DDoR-Iepl+5<=YzXI@=A~~FwyW|w&LxoqA0zePIHG^Lkb__jbnFeh#u*7 zo>nYu@)aY2$y=%B6U?t1q8c$jhlR)Ga=^Lz*=M}tEndWb+|*${?zT>CoS+9b1J11o zr4uge`9b%f?kTsf`L!bssrUVz#H8$mI&Nd&tLl~ttrOvmqdw88gI z*60FmIX*I$2pVCfws4kLbd;0FmbU0LnyKO z?FIqXpm~s`&SAp_UgmB0LMvlE2R~pb74-Ske2HY;Jl&8d5&vqr1?i=zwCx4yGCg<0q_tlJjuuODto$)V>zP7eLccL5V{(g0!bM9@9j1E z&K?_p7woGZhruz6O~Q4D5&rxYIlvq2TMVMRd6HBY$&BQvUV-jn1S+MbB!JPy?Yn?L@NMrLyr(uT38yQW;%wHGWCp+9$o2j6!>=f`S(pmW&q;f@a zkT7u(ib6zrseJY<;9(Pr~$iT1?FdZkrfH zNC|AX`-t4Q(AiDDzcG0(zQl!tB_VdDKpF*Ce$Ca(FllHAG&G-LxF6JCaL%DWarOTV zM0O)2mZdEEzmi-uzAJa}{G^`6+Q1q;B;2(#TQmCcI~=|eeAGoP6|#hUmEK0Iu5{gh zfm9hMy@S^j&(=u`z1bvcxyfKp__TF<1mWdO%RxBJt(Xdi*2WFH)RW z`Tw}n8bcdM+eg<8gt!DpLIpEf-F-3kEv>&_YW)F3O z@Dj9Y`2U&9lQ!Y?~HkwuUD|r+q$Q>L;cGb2tKOw znhd>>Mp=gK>tMOc1s7qL5DsP5P$6D1&yok#mfpOr=~{~s*#$5XNS0!o=0U*kk3bJ& zK+mC*^fEsAl7EzsXn`rM>5y11B#uW%P`3;8^ACP4JMh;sS0GFW=Z%zVG>!REFlrwD zV}Is2xGr%ZZteBlJEL*<8sJO05tE?yQg|&EB9WLwl{L*|Wr)=PwxK8scvO9cvRs67 z97l5CW)g^TW`qT0#fZH`ViA4t%EQ(F^!y`O1(0w+<+c8bN~)n6({^mXFQU@qLr?YdB+V)xt=i7AOm^xdlpU)a!NtFJiTrGO^Ky}L`P zHPt%!!=g4o#S9~)3RUSA$C^GYQT96;l<4c$dcC&hYrN>+owpr20XQZ&GC$9>BGRx4 zu3Tq$KHNcu%DK$Q1Itv-KB~70Lc}Gc%;0; zW>=H(P(@f&gZA!`EI=OgI#S7>5xRJ%qV}S#vQ%d<|Rc$;xqiiVtbOI@U3t z_8y)+fJmv_p(y=nyzY+b12z>4O&&0(*=In|a!{+n*eWG-mhFC?bpz#EX2_6ZI`F+5 zC!iN!IbX{73$GpsTlyrap4f>6?NurGSkZdbSWDS;_WW(2POrrD8d?1X8R{7IdwfAA zTc`kaGh?aktEcLa!GtskrS!lW@T3>D_57HO)^lJyM0~NMj^a|X{l=n}0?q}vK_bdH zUq<=fg~Jj6rbROgb0a4RP-H{O#j|$0l;^&r9d%IhdcCC}yj@y8wEO^)exh8{LpsD@ zmU`QC6)letAkUM>aKE=+#QMwSI6IcXJFJTU936h{uhY{x(1tzq$@04bD72DroCyl6 znqT=tA=rU(>3gI~|BKLTZd%uIU zmZ>9d&oU{(HX4Ec4uC*twh8`?9%^8S#5)6Sn>UHi>H6tjnmU4q5YO! zp?gSSS?$`vX_vWJjPR)2p{e35;B_A8f{uy9$r=&Jko)LmsNIX2O<699)^4`7OF*_5 z21eePGIf*zlF|ZiixpV>tA3@M`FA)GZTA4^9Kh^q=4Lx>6~*Rb7LoWPqO{haIZsn@l4qEsP*iXax8>q_@{Zm)>McWy!p{FLj_ zc{E*5C_TV{N+q8YL2rXg`;Z)J-z5oBVV2`z!&k*LU0m+sp3fcqNoTr=Br)&C7p4_V#*o|*jmaWWJ)xAdc_78o!CTS1D#F#OLU53o zvKKEgp^wkL&xu1E_vRARQzRN`@82SSsXP8uzz}UYCCzWre3=%z*z2L7kirN*vRV-@ z1|p`h?S#yB;Gj#*YzyJ}BqhsT;u`)=9L;*5(D9APuj3D|yk??jS~hlg67kNaCOGrSc?6ybU{C3*N<@`#~$X9*IDrn)A) z--*200?(=#GzX4L?+wQpQXz|(A?U+n@$BQy{ig4IbLPqM0AzDv0s^udk zFirVVsHH;OqlSngs*p&)^9kwCPwbG4GAy8G)?Y#`T?_2$3@CC=F+h~|<6R};`M86m zR(vN$zmO%Zle{9XW2WEcyE*hJ!e{qEchPqfgYBr0na)dwDWW8DIcYnF6~|kuRXr;Kn?ahnpY`+zyeg{Wk0PkW z{eR~dtt;B(US|Ex%w#^pCA!^RouJV`-ij%_R>vVd=C0gpPFV7mszbe^hp!yl8?)I0 zjpi5p%^Qq&n3&A@$KqisWs+Dd`2DW{u|zU- zk03`#w6Y}i(4rKh!7)H<4dC0_F9AxGOy5a}OiH8ZNDAiRpb(r8qe~Sf;J`=>1;O+# zk|P*7;BPt~3#cAt<}MfE`F@?wZqdWi1XJ0?K{V*-3Nw%bE4D?OqTpbd6fp-iA}55u z#UcFDBH9qL;`XTE#*{ep6k1p&wjJZNhzz+nXEs)C8FNum-});1*k#3f{N#jJUn&*3 zvn~0&Yj*|EQwN`S3!xYNZ`|cGoyYUFN8V-F9^apUM5zaYK}S2Sj>uljB$rN{_@NM- z0R|lH4FNvHz=q@wo38DY&dlo>sVo^MIrTxK1T&kTyOQ-Y4bP#Pjyq*NWMK0#Kz=IonWCO??QsbHXuT(Q)u#(uZ+H`152*^vik1^ z3jag=$HTT(^6lkaP!hx--MIJtznl-{`$RAzMac2-3u4sV{ELH3Y}jJ=T}n5Aum3Rz zHOt4f6ML1^dPdVop2bsbM4bJWuw9f7o?+!e$>vP#BT+0Y+k78Iq}5Sng&XycGQ+)X zz&UyjhAFc7VN`tFHyHF+__5M7y&m+z3VI9%?n$bfT=uGke`Psiw~#6N@d@a)$lzCf z^<)Z04hYt)Y0g<-%vX-PJP=*7n@z9aYd|fx88{_2de!6SNY^iz_MOpWi9LB3e)#^Z z{Q7}P4yMhPtoMSZ2hwk;_FxPLc|G*h;1t!E?_(fbXFw3&<>|PWU3OweLfoLK2+{bB@at!^Rmv za1eJL0vHs6s$nKTXf&&GjIg0PMC8;Wv-+CWW77*ibuAiIC%r{bHT+rfw+G^W7<6a# z;a#Fpm5A~s5G3CmSw7iqL=0AD(uH#$JO`C3368ET*JI@ zY*;}qeyNiNe*SL{*DFGm>bVG<%o6GSL8gKCGBy-!t^L&g3>{xgE;Z$DT8kbGaIass z8aCI2LuRi)c&CN>FY2v(>XO_{U`*DPud;PEtSq-6LRhzQSxZepxm8I zv@}wZio$Vf#V_ zJ8>rKm5809C#c6Fl-&z*elpm{>N)H3J-gOm@<*66@jCtsG6^`z{#J4$e}r!B_2BOS z2uaL=k?HrEn{WlSn4r!hgz$iO??rpXdDc`Cz{77)swwi6H498Uga933(r8yJKI>I5 z)&h+f3V2<$meg;3z03FLl8X;xwpAv9-6-?@pAwRmpl9EpE>OCg9m0{u?|pYb%JPDt zy8WcqG(6;{|9F>5#%>Y!0cTVke`sc{C7s&s(UV~$B}&aui!c1T|IX_4%)y+a*{kFwdG%T4jce8zOkTVqfI?NH>;WOC>n{rhZsq76^`z0j|RZM*SE08&k`H?hH zQM#hY@Tq)}Qv)Z#*FAwjwZ?AU*0Ct>P#kTN{E<}$qr1a>r`aL#tilt4*mD9nF8K@36f zR%zLdfW11}g+Kem-pU;f;z#uWyM4Rsk%Uy=lrkD~o7s~ubg#$*UnQ7$^-hZzXe1_6 zQthV0jfz8(kA0g=SnZ1vFvpGOuTi_C>CJ_6T*YoU?F$TJe~@E!$%>|Zxz6g0Tg;ip z%;`~M#Zg*F2-3@Wd!iBV9~hxRJlYbu zR?Ghx3SC@CmiET1MWa7lR*zK`vnj|q@zqsV9B{*%h&G{#A?4(%mn_vRTp^ zX}Ret>8A9~fA@O8TJG&N8&;@7Nk#dSh#A|>BW)$THO!$Q3|W9wlUY|77@~+K5)ULF zmS_UD6(~R1&{w2YVIWUw31^)iL7CJN38U7UMdfGM_ltKN3p{iL-v)fc5!p&_{RZxY zZ&TqvGm4(v+wd#~O+Wz)g>eY;F)M2d5L_0x9X`cqb|XJ-98;D89y=4m--A8jmvJLd z?I+f7)00%Z{k-h*6S~@%RC!{O?sVGHS-;i5c39;bbR%L~vu^K&i?1S9~JrbmW0E*J*jg>5Bnxg67PdbUjKb5CE%6F&k= ziTnj!JUxmTLv`1ysMR?-Z6s;Zh{EJ>)j6N7NI=WNmEnaNmuo0mffl%I>l)2~Xcy?6 zFNIyC8zBXvhe^iqu^N(Q>7)h9G3z$+O!HHs#K*gA(rf^zzbfm-J+(Eky(>D3SWZXu zLTt%c)%z3`eT#1wF*~t^bB31pF4ppC`h+tD6Y}2N0M8$Lwh+9_vd51pm1@< z1jUJ8`0VL*I_5i5AD6+)D@SgZ(nm=ZL z(YHS;wP>1Jb3l|*CQNI4JrV^_hw2T=q*07 zKcwA3i-gT6$#bd7ErjW~%2nn!SftNTGSVje(B0FYAoV z{FD;82m!D{FH%nPn3%Dj@B4t+dvYZvW$ms^=ev2f$KvDJq!C2cZEFHDs^@CIS_AX= zfGzLyWVF7JiCKxp@UP$mOB;j9w=}KNf%sdjio;$35nhJpO77({Cq{c9q1F_#VrCT& zr+`1%<80*%H#)Hfi})1pFKEKCG5*vZDEVSQ7sY}p3C4$D)Qposy3krqQdeZ(lVLx3 z-cRTAGx_F9$>y^EvbG@%l^ZrLld@!J8Tt`_ zsvP21CSKD5#0x80=5g-AceFX87Yl!KFT6P`3VTZYj%p?jt9PMdJUnEn3zd@&G$P#J>qNLBYz4 zPehmB%A^o~V6l zTvy%LgvR-*p03-$iJBg&2PLh$gLs;7`?LyroCz{XqFPE>Q_0DhH8;$}UmJesg;ba` zjOXl51!4=`b=bUW`ZaYw zm1*P028#g%XDp`+8h8!C8EP(z99X6EO+#Y`ffM4?$DFExm71g?rxp2HvzwccpmjD~ zBpt}n^M(;Z>hv{1kYXr4T6f(v8$W!46>%au$Ge-7-qTB+ix(8xz1H1F5lINkA|EqW zyZ6y}aZ#~v8&5s%)hHusi~b6Zz!lcJ&xrF!*AE7c3wGNMPGEWOL&zHo2A&k{7OC~| zFlXduGvm1@oPj3RO-RA}r#p|Kt|w>pmY4!Sf93+23gH|3+}l!yFBUFxX;k)bj-IR`RMW5l)Cwt z?6(wgnM%_b-}y@K)GsyXY7NiJ50{N7*Pk^S>>q!Q+7F!fHqM8z2|ln8|GKu16F}Uz zQ>>(;1~+2O4$NgK{OJT1IaAH%hDu753zlk5N?tXeVOd+po&-8paRaY>eGxNw)o#cA z@;f`gOx3PEwfKIi)UlsCL3vIU7Y)AZm~_?KB&2!T&1#jEPOkuC;*SZyi=bRt5M{Da zi@i~v4|?xMq`wH)=rrmjuMoFNZ+gFAfIXDg!kx~*^Vcu!dW#LJ&cDoK_VtJe^F7c# zJ*lKf$5F8Ks*f1lpM;T@h!KiqYAV3WQ1j3>%uqcFv__cCt~}65Zl`vw zmc<__ml@daqn}s+vWsfTL#?r>lri&!3s^7=jxF1gWy9TU|6eC zo7NyshUIdlKfvCtD(t&-jn!>KK8cCabX%~Lq z-$P#}^Z#(d4Wn+CZ)G@50jd7qH8}8TKa}LJ~1uGVKgelI*(jPG&+U4mh#U$%yXPjOmm` z;>U`Ptt}tdT9KsSW3=7-K6wp>9}KQK!eno8pbo~-1Y%-PY(HJ^sINBAXR!LU3cd?V(RXG zmBbOOw(rag_CNa5!_XoTT1t>l2mWW&H}fX(jfzO?Z6lS{9j1jDsyhvEo^ZAhK^N8u zRD5wTuAFcJOBJd@F7~qJ(?m>)82JeMBklduq_ABNYMxz8I=G3)HH2UI1n>E02Kybw zCOr&>=2f}!Z|hS5G|W6vf+Cmwzt3`gpzNRTtd`EP|GQ!-8t01g9M>)vt%O+I1g}Q$ z+5NK+nyQAk=Xu0tsbJf2LO@0K{xwHYMrpU1gDQ}^h8K&Xu5aUN81tdf4mOY9>mldZ zp4T*hdb$HR4zlHG_=voC`>&~0_?ua{>Y|Bre4+W+I9vg%7iunAf*m279k04VEPdv} ziIF34Oq5~tkKS6VzNO92itCEv5Ke)iVKPvq%JD?dHDT#oHoH%J7#QTQ3QU<7tbMMksn_X{e$mTF;@LtP8<%|_tKEa&waT=2cT zRM`8^f^xiuFB$LA@*{KXCJ!_yruc%IThv2Yx$Le}l83?fg-?42Uax;{@kQArGGI{M zfduxHyP3v@P>fJ+68?Fz;(r(S@bJX3UC8i!UGmC;n5QHnp+MC-?WFFzqw&l~;xAZ@ zter$U`;rN5*bEUC0tTfIkHCaAp?JPzn1F6ES#;pp#vS@hrYmlmi!OKqI$X z3*iXtaK@!d`S=I7B?Mpt{`lStNdh&Vd`0U=bPCvMvGH1d4hg6~XRIDXuVcR6OzCUmFuFU(hg*itQ!Zc8*(52GBoB1z;+gm&Y%g;!H4!Y3Vdoj4%e66 z(OPt&&FfuMUxsbjxGn&k>kf5{G7krIvCqY|2k|a9%Ep>5XLG33fABm>eMV1d+~AG_ ztoPz-=V%^C;jlf{;whr~{Neg&8HSoUjM;@`!AJiF=FbmE&Vuz1I@yDN8b>l>RvG10 z3R>MqBq}c>Jmw9qXxT+?PMC$#g4Ke`)dX-*m+&B=0Xwi8%TLz4tiI>J?yu*YL#<4j za;x35^AO zbVF4|Rds@c_#8fOSiodpu?LooTRd7|mhc`n#EDY3g&Jg-rOmvfwEaEj#>Yb*7tfDc z0YswI&zM#_!S&JVcapMrUFkKcPtgCaxo6!@Ltf$W)x zZCfD=bTC^E$S0r{o5kr}naD$Nrc<{2Hn!120jX6Gf6=C*LhI=| zM~=Afx56cK_K|!F1~1ag%zT-zn4Fu?%kz*v#2L_EffpqJxo1j7B@-cym5blMPaMcrE0NH= zgk#&HcG7_D%_h2}x?%KGS+QkX{{-D0u)IJ;;##o?yrv2b&|ZV(X99=z_B)3-`BfH&WT3A^arZKlH$M_L1bMXeCT0wyajo)??+qY~N;}pvO~t2zA_E zCR2pX$~s&Xn+=@W9id(Gt6Z9BXb)WMV_f2t+8n*MQU`JCch05F++jBS$zdT{U!L}} zmp@fKDb^p2{D?o_Yx*|7i4=l0B+7Cr+UBV!m!x8so`Se5FV{DXoz|Fp8R=7z zhGLi;Jc#kxl(5E$qLG8Uk&*izTwlA5!-gF!gc{@dx2--KqH06_Q5;Hx1nyVYJFAh{ zipdYY21JXD>_elye70zBq-nnjmq&(*LM_Ev|B~(ZAsb|d<*l1GRl>{POCaX~>Fj>A z=jFnE%IWvnv>!vdG_vD!J#O33mn`Aq?Jj+UqJP!P$`IMtN1Cwss+azVjc#u|SR7!b z&&Emxg0ZkVmnIF;XQwx?#n66)-saDm*gD0oWAo0pGF=Q|3#IM z4N1LbCgEatzh%J8VOXv^$lv@MP#+{x`ZAUiQ3Tmb7gpKq0SuHg`=-4^k{Z}w#!{1g zHAVsg9+!GkPtbRZp=rCSzwJstc^ZzW$n$($o9o_Z5DJ!S@gGK6S)K#wo;;(LB`mb% z0AdE0W(tZ1dFOiP(>owKNJxhrJJS*V^;S2L;n*w1B@RKPk-yA!9_l|u%2iIONe{RE z#sM}(00~blKJ9U530B2%eW@_*M-}0!LYgR?x9bI+9pza4#{g@{qNQJiIUA@@$Ri1L zN@+jY&7c9&1pi@GakdjSp|X$w>Ji)(1Pwr{-z)^1n?@uwbJ;u6hK_dj^(E1vu9m=J zo>}0aqvgcRM5VTR%q5$%)V%a(^`C;f`60N7$L@8eat<}`Z!X3~*>LA~vF~#anKPM> zjwl%Cp-C@OQ;JC-)~tmHkDeLuN#X^W;BIoj{<+KJ_&+H_NKa%&t zJZdu4q_=NzB}6(fc5GAZ(Ggy!YnMvCJPz0ZMq1Kmu zAyUcdZC`Aeug}G^Z!sp;f;d#0ZP>b@wb8TOFX{MSh`>Im(1e#C;uoyvdvF`JWyv4j zvVVZnE6?SK4APKTF4>frki+c6lacsW270dMZkH^LSWYgOLCxv@#;3I_H%i$~`Isbu zbQtYMWEb9<@K$91V_{k$8`%DR2T5@T%X2BZ_>Qq54H^GKA(Ajd476@LnnfEdl}$8g zF9XIF&dN2hW|`m^!MU~(`1YkSESM*Lz$n<>!xxN|m9fkMXhz`lI~jP6ck`)R4O`w$ zQPj6wfBC?@s_*2NWx#S{erZcz*FMa=5;Ov$O*;@JCgDK<0f_QOI`YDZXG81}SXv7U z{%#V5Nh4QXs9W%~`tPOih<-W4LBRk{3C4-JG>wEc2haxH>%lfqK^sz|1O7PnnVk^4 zR_Jclqjs`E6QqMV*L(@zljr9iWV!>LY<0X6_mGiDqO=8I^GDL!3vGyY__D3J0#H@l zhN7e@%ZRrpLPB$@ohkOe0&grsFCr7fpPPIiY|cBMpw1j_Fccqi$KNd%q1?{Y3al4M z(mNkaRm1^FGBy^1w^B4OO~Dq9t#6jiLn|lHGl;KS-e(bjE8a zp$c=kWjS#jiBveoZ=8eVuPrzyGI$~|G5JD;O+6MIrUY3oBN#FF-6X<<5(PaIR(2`j zm{KZF>D`w%w`YhiIO8SJnnOJ+G5u*9_z%s`S-7CvJIIfyg;|{_Uv@+ean`ia(Q@CR zl7_>-PKI6=+Gbb%Eb$FZsq_Dx8N?J*6AOa~L@43qkWzeuQ074z;k^MFsS40WCG@q15$78~)xNW$}&J(K_oN~x`S9bdDeV~P7Z&5 z<((xJzl+Rb!qx1z4bi^if16cKwm&T6BG|ncPz4>Y{d>@PrAqZ~J+16ZY(2pKoU9-n zUEPE5;$MJ8iT%(z=QAXm2XY$S%qxJAqb}77*?}I_IR@3Mg$8eKVO$pyb8Kn4lach& zLvHx=k`xC$nQpZFqCTy8oqP5Xx9Gwo@Bt5WZ(NV=LY>dDZ+M-6X!sO1wfZpN)#$EI zK--u6DjuKvM*7zeXYEQYa^u(#FT#lt@b9aHy+eY3RGCml1Oc$z@1I3lDw;nltA>a8 zH9_UPp9DNG>|%)l`A}F!(Ww$z}Ci znCG*>|L{{25S1k}05=+BpMEpK5xAj~9$n{Td)y6|)+ijj7w#A}|94;mlbuN3kv&ds zD&o3R+rUGM^>FUpXMz#vh2cK)KmZ`;10>;2;eM9x96?E!{QZ|uy%UD4?qoF$}K)Xe3gMtKEETKPxl1xpTHW$bT5VR{T*xnz*am54YYSl^|tne5paFp<+oo z=sAqx3r#>SgSTl$E%I~RIq%;R;_f8ND>5gqI2lRA4Tvt;}SU zbvjsievy=N8?@^C(TXrf7Yzz&9ba=Ci``zN*oO0@)3rs7rWwV8psCuR zXi19g&L?V1EO!fL7R;A9zrdE&U!RqA9etfrYV!6i7OsV{TLfac3Tg95e=!MdA(6A-oksc~^*;J%}T2=^$BNM$y#?QUufVhTl zDkjH*@%+Pxui~49r`cYIEdl`iC4%LH97{zK!u4b~EQI*1yjnF{6>|+Op=E9wD$k+B z3`49$dskJN?hmR$5yEvpFLGSXKKd^7y5mJT9j`WGf}!#l=yq7w;-Ac-X^Hw=b%~;^wF0Qzqh87$s6gzQ0Fk7tTa0OI*Ldwdp%r#2Yhn*$3h;Q+FK{( z6W^Wuud@LAF5Dxr^zEU7vP6DhuJ*=5mZ|+QpjbD!pB)Gv6?=H((gUrdiFx*}d1l!~ zATeUna2m-E>3++=ui8t#(5f%j^J(BfQz0224>!~|yiznEWdwv}4(p7?{p>{CyQRsr zcbda+5jYfjFsIRLlu1fhh=X1A3##(@1inK3CPxWo(}~p`w@p-SizLT@8H>F32g}gb zlMROYcW)1{QLr>C`yf;sK;5s~wh8ddmpxT99n(wb0B{Q;I~J|Gz4lxiBiBEUfBHdY zkTNiE=M#hgbO!<%ZqB#ez%V>F@GTE9(6Q`{N%AkLpzpnPWR%4EtgOuwW}HVMWbP&~ ziaP!H8C^frt0fv})o0ZsEgq}b%hQwat35bwuOX$g?hm=byUKnO_TPv%e|`!(vW%%9 z8z^dZ0(^F-Z4Wj5D|~(dpk4Uz?T_3&fLlwR@={XC-^nn@2Q8C8W>pCcQ+v0LOVUC? z!HL>2I-n?lZc)ad6+7yD?js_CE9Cs5Mf8e*Y-31fWfnQI{}NGTgBX=IEY>@ZRwjeu zC}E%LI%{EY-`-zQYK4-$`=kLU9pnt=i5mz95O%&86oBZ$O5)U#EyY;I>PhS}oi%tt z#VgF1*aoReC2=asHO?n`LNjqd+nMPvo)vW!oL5m?J0ODTt1Ywsn$w0dVrUD$Y8z{j z^Ftfg{sA+aYVO`%kuqhw-zhs3%403SHAg|u00@|@$*)YF+)`Lfn@I2F#0hb|pg^ET zc!c|B;`Olc3j~W(Qlg~f-Q{O*3NvEDi1d;C4HBC_gXkYznXi?!<4yEQ+irK~KBb0N zbkPGww2xRx#;6L%OTVP1OtR;3v9<-a8sOc$NnA4PuZuJHM}aJkF^3eR61-$kKg%$TL}wF9Y|X(TdYCcgRU8~ zo%wMX#T;`SwAkQC*|t-ptO_Ms{AV$DC zON1sM{$9ISFR{SQZXs$VN>yRQ`kfZyu1&_~N?DP^_bsxmzkcL=eDrV;`53!gkr+f> zWO2zBqeti)vn<@R1{?2(@?YWudBTm82dgVdq#E;M|rq6YA5On}ZG@tvrs;pF$aV4l3<<>tRZKn|t zOJ}jNsyc;)M?Ns|Erlqw7wDZ!;mIt)&QP)80Lka+zozsAJeH-|n`$&`7}i=4+njFt zaG~2-`_}Fmro+~gq!2-_#B9d?#HTLy3I8hI*MA+k=V3fj7Id#%@kcM2Z$b5wZy`uD zL3W3HXO3o^+;Jrd>bO1~c$$Dh=TJ-LoK&%M=|_WvA7c?l(fh$nr5-WrwxJQ6P#n2^*aAbuDB*?d@zv zQG_N;fRdfMc|-8*;m~SMv(skA%0#N`XPr8KH-8<`O+-kR8h))8Jm5;*Re%d2KhUX zqF3mwTZieBIN{30;>d52S}DcWB{$WdsZE$UY@X!QC7=!i+vt?q*us#^aDb52P6S zIf&!sR1|wbh~IC|lmUn-3aMzn6H_)CYsKxBE9K!xU}v!3Kc^fh~v5z5vWcz1j}mR zsL}D9eKBwd*5z_2mG%CgR7Ofe#G7qwB$kgRyVymh>^418Qqinh@W+Sj@ zeBfEtXJ8lBe4j0f=%0d>#4F-2gNDo;EQqm-G_7Hm!}1)vYon7tsgsIN5m}7qjkaSS z7ukGqd61G@Y$rV93$ero)>;#D`1y-Slo!QTjmxN&2GBymj!JA`R;E>%E)933Z1G53 z4O4r?_L2qg#r)Pxc}o-xHSx)MbJA4BS5ezNyr){O<%Po9B0Lv0HDQy;CQ#lWc$$`9Mu0C< zPgA**miRJHMXuFNxU7q+-LE6#l&+``vN`LAM$S!^EZDDHrPZZED}o@Kc7S83P{gJa zyNaAqa9#0u_k6PH^AgMz@OpXPedm*ZUo>l1;7wBoUmyl*?NYFb5oBdj$sIQw=9`p+ zE*)ou48~o$g?~^WoPR&z&)_zR^O-^Yw1v5^#AHN+b9fBCfXiF_Ai42!LVqKY8orAV{o!SZb zj++~cxS4Hut%LLjrOR#3Vq!?B&g%DKO~ zZFu|tZ3-q6EFWac=8gv;2l_|VnB&n1^RT>}(uJa@mherKuQo%KPx7yE-1sdwGy_2k z`LAMG9RG|n2byOkRk2$Bz5;FE87>!gAYXZb8x-71oBJH+tUr;5>36WLHQDSaq{r zaXR0=iY{jVS5QoqHMXwA;G)U;W(U};XugfEvcdja;}f5&s40_lA*Y~Un9 z5!Go_BXfHOkGYB4`yR5?|8*bvgtB|O`G>A9XhH5$Ym<%?`CCVrTFLoHQ~ZQMgYDpWr8es!zwn#VX>4dHE|gIYz!Z z=eBaacIWXS@D*XWh=(bM0UO% zEok7xX0$TA89k$e&9*reBYv8wg-kHh+F+@cb8ct{WMp~F9$tOm zkW$CuhgWOs+0Xy7L}s^tV8rLz&6oqn(jgk?>p|-`oXRvLZ-e6T{vl_n%q%0?>gdsq{=c9ZXghS{Y+$?2MUx>YfTCE3xW=W@&NaP-0lo<+ zV=wz5J2Bb?lHHWAe}*vn-=N)+3)U8#%=(AubC-zjNT>WiM9Uza)~Q86=v@&F934w{ zmst*w_hu8`^>1swEVu&SaqBDfUaN7hX5t)?mGQLgXFt(ocNk$DC)6?6omZF!&7|O) z#sv~U#3@?^@#FzRt2b7C$PGn)+fuU-$Znw_;6H3uHz`pCGuYiU4<0=K8U7op#|NSZ zvY1dr@IY&?1VeY7IV~8RHPL~9_C6?VBrAU|SA92;qNDjADWOo0Spjs;AjS$N$E9u6 zOE}`LAIN4&EJ)s7oL8q83KtQ@UoVoaI3bjo&3r=nzq;9G9`1HH#E33cLAb z9%l@#_y_bCUn;Dx<8C!QMFiAZ^ZF$Lb}|^NUp5Os16uW5;8j{9BuXQrOt?`zIpqBa zzBU8v(pa@q*Uq$}c6SQCe|;k?qwp2NDtH*~z<8AvENJPnUFEODQ#~O!#Zq8DjkJtR z266Ta557$|ZjX1ZeUJpPneY7d9nky1!z|X(ouL||5l@^^G7}x{ayrFyjY}D$6yF;A z)+N39|GL|8QxdXcoP3k?-6|nCCFP5vC*N`a)_uCfcOc;)K{UPAno0m*s+%;Sk3Lj( zYUGheCex@3Cv7@8WF_g?hg=AJn)Y}u;vB*sG504}>QP24CcMPf)YS>6-@&M_Oh0c* zJ*O&kX2O?2zScQ&jBIrQ$1BJuBIJwCxYClWUV4L}vag)zsGH;LMJl@lMbicT)cz*Y z505MuUd!H|utvjG@D}c~FOBmcmrFD z#D;h)2PxuYO^P|`5UoBYC>x`LqUJ>gCI}elKorJd4oGFbR6J0?B?iTAL70BoVg)FZUTaSc4qLp=I0@TE5VqyU_vTuuVJ;s(QP}E`h;ROruHF17W5{A2x zQAlOr#P8`;%+xFkvOR`BBfQT7zG$!}!wJjuuVmd7Y5tMw0yp=yHo$hdioZQtxAc4# zBLse|$ViER$Mu0tVcO{6Gj$wNQT5D30S?pGJH?X~y;PN59Rx>i(K|JpvL&E=b~gu* z$~{__XsQ@*Eo)2rodD7;M`Y&b8TVbEdbo|k3CDM=04?xP9tNhsm_hW5g=K+XH!bg+ zc~gf_y_*9HimsNubFaLDzFM8_=w6Fb~iYTOl`bCbI*t2 zzEHV)6{4|j-;_^l2`M+DB@sVvPsYemxPQD#PdqEunMLG*q3!5Nl1dUTR{%5L_3UuX zUU$4=Q}7c0FdoWL%e}Q@=-zWT*_DUB%0WI#I{>8c|J{TJ#cF&Cv?9t> z0^gbP>ThA+-|ARM_rHmPKXXf3g14)I^gppZ>`*@N3$4GFUUz-kvjMr z>mj_`P`oj?LP^swFF_QBe%sO=bSlv7^jq_0F}73U+RncNr~?B?j4iAG39h9o`4Jc? zpy3gCi4)N#O#tKZk+rT0wlk6un?Dx;QnyJ92a#DrQ4(I+%YJ2@IbN>*yPk*`>i$+` zOhi&59n0`b3xKQmnC10{|29qn$-VaztlL>Ag#h-s6eg4qPoVZ7E)^UpJx&IuhwB@N zY+}v+brujBSaItNvH$lD_`P|Pz^`VM)zw2BQmBbK%1|Nvis(U^XBdscK`}!oxl*Yc zp6>v@-R(~z<8$U{l`QE#9q8Yb{H0fPU>T#;Jgopa1MZ>%F;1zRKf_p_6Bx7h*- zggw_MM zDQ*XM#7h&7b18e^JKE{U9xx(`lhR-&rw|p?XER#NybMZUVv~;DL`rz|Zr&t?M?ONB zwF}iZgqUTYAVoE?IaS74|07lA*7B16S+*{kw+2z6(7!5n?f>Xhp%x4~>(Bz!>-%p= zZWNC(M^hN#r^53R>;!MKrhLoRQXh-|1TXhHsJ7+I}44 zGD@x=j4nSZS)*hS_?u%LteFu?C8{H5XRk-m3_hMZ2RCLSR$gzIkWH--%QZz#AUH&= zimQnXvlS=-egnw6SVuBG3B|kWN;Hud!+Nnbx32VbK3m{~&KNuazS~kuwWse`h6`3u zAqlz2ZJlkt*CTi}McXYa=EpWiK%sPDDi&=-ZKGFEB6J zZA7g2P~3L%?F2T#|7xdjAxYYJBhx!axzE?yEb$#B`>ro+cR=+y8LMR353S?{K@Pc2 z=SR`s&hTjeKI-Vfpv^sF&+)itwwX=!nrETZ_tj(Dxv5dtMJq!&t}<4AU7`QQ%W^-L z8OGHZK@q-^D3teom8$#2l!;^xs4|%_QrPtGQEHM0i2+k!bTdCIB>Z$Mn{B&|viK<^ z*-k`LgxG|cV=4a>wL*qVD8OwZNycel!(q813y^c3m6)w zWwaN|FQIwlBk!~`;dX98hdQ0563}qJH2yYtzwnjC?792?Mn`rl_Y3L_0BEN(_tP72 z&Z@syh|-E8r_yZYGttO@ zk_JCgUv>w>7Q*fR>^Zg3ohAiTtS>^$CsnIwcHD_63sXnN15+qmtFt`BVoukDRyH$q zBICM_o-WP=rd{?JBsp2K6>nb?z)f$Z=vp)gcOYr|L4G+L?*aB5N=PZ%1oQgAPz-c7 zb>xMV_T1DY)^i6oP#*8;2z0-ZWOqx$^k3jVk^ak(5kR@;YsstwGDA}<{U*fp8E%so zX_E7p&Wk2F7x;lG{6%w)LUKcQfwt1@wj)RCUXl%TG(POX5&xSNfOCa8-xHQ>(=Y!(kn9ADupDP0y)%a z{<}yblB*{QXu8Z}-TBLIEmEVJxMG^kfmTHJX2`mVi#bo|OyK>3-uG6?*Q1Xowtd~0 zm0di6E#kp3B#-n8qaHh9CTeKrr`=o-I%&v=!$~c{n!w#52(*I>;&+3bb?T6jl+onKnvh5C8#L3!#_Or9y4zOdwr7$>ycTbE$rdTLrA6ygs1q$<7- zhk+sQs;(sf{Ds5~P%Vu!vqhicDgH0$}^}UMD;2j5>IifDwbH@(^9ZX3M+cWB8 zACKy>0>|P{mtJeuIGHZAIMvQFQ{vU1s&!P64Z7(&)?NLL&5c$bfar-o92 zjI?~mKVs_Y*VcOLb-O-x&o^mXGIbBg+-(JSt|S{K+oIGG19Su_T)^>*HrON;Xnro$L>)tfTzn<<8Zi0J9Z6sgLOIFW+k8qizt? zFHEmHTf`om2d^@z226NZUYb!#9cHTh2t$WczyKi;(ksW5c2w(l3TCzYo|~^tDt-Rd3k;&U1ogw2&=3TJ(W1o-83 zLgg_Xmu+EyhXk%0<3OTu3Q>f#G(BQ4ut&>}1jn6(3DPDN_}v#fWs2>kwAy| z+>PSmP6enwFfIBdlVud6ndmcuE1a*i_O@Ox!lKf;Mr!R-Ue>+*Uy_c^&KBbK`d5)w z?S|Y-ycQ7R)ayxav2yyPlGC7&RdZ@ z)~$qOna|K#POVH5k1(KEn9)Ik{w*pInil*xK z5tf&#K5kGgmdKqn;GWe(_Lx`PIDZ+sn_AtGQ#0dBqG%Zo-3KwWo_Dd>>Xa3{ZiXaT z$WGtEP@Yx+Jhz831)Y|P!o4qP$E^3(1EQhR5|~!p zk>A3e4qdX+YL+&3-s-ZiW2VtRG}fNPt_#f}CX6Z>qCkROtjl_|woEBoY@^i#S|bkQ zBjJq?ELj0?a*q^EuIVUL-v?E!CNYUeGY63R3bV`gRsN)iRz6t@;1MJ#ZA7Dl{%h-jnj7;dzRwl_CW?WWYFgDj1hfGt`rkkymg-u< zBb4S7HMW=e4y}7_3p=+4L#=M+-WZ&@So7K%=A9|TZr5mqSV*@~af`eRJPN2uAhq(z z6Pp~q`Ya7%u2unyt&ycd^?WQQeKe=RjMn@&p_p1A=AGMxyg3%dxrH))a?4WSZ#p zY4A4T`rFs~hrsq*7V3EZEUAdR@eTN+^x z-Q+Y(EQm13dJosuLLsJTO@^x9I%XgsE&Q0uref^F&Yn;Fh|);FsV>OL=okCyxKpy6 zXYMeFywKJOHAil;_B}-SZs8W=sPm$W`p43Qrmv*RBKD6eO#>|3^FnoXZr>xHJ))-F z*r~+e=D)&r4>2U{ThRQm`TJ-hYi4~d0N-~&%rd8B$d%2 z4JPbwpLyJT|J;IHKUtYN>Wp+*VJ!3>!!A*PHN0tU&TkLJ3K>Zsy=Jg*c9z*P#;1J< zUezLD4zSI;=p*_)u)}6yjlWkFA|%(lB+GSRXv+Q}O}x^!l061ZkCGM%B^$ZcgXIAJH@6eaS|A+{d58b5hT@$3t!7(tOn!>HG8gqM*mahu^6$iT6nsfbXX{P#on`UEj1AEjv zbY_w~_R)WW@)|^ml(1KJFfuQI`20~E;-{-@%#zg9CB%y$H#n(nWAzfRlJx~1!qtSa zZUNURAqmj@`~nyBB9A~nT8q5If_xMpDrMaxru}e4w4&{qtG2y=;n`zYki9sYK|UpA zF|QxAeTwM4<>%euYv#ibXx2T`%Ad;b@kbIy^|c{zfq-AC`iH30m^&mXW8nWlM|ZRr z=G!Cf6UXoU|Hc&mNj&3ZCy6*ei~c7N`eyOn9k8+AYhXCrv6R5h4iS`u%^9TsaHUMJ zmnF8XDEbpGDCLlkh}*gY2`6Up|E&=^8Aa?fn3a}A=?hbN%`5clESGp=1Tk9vy$$L%DV$#N9}D6M#SZ>xYuU5c&B;Ss$bey z5N}~0_-_3K2w+0w)>$6Y*ZCcQj;K}(voBwWv9CQSgSir*HJ``!=Ygn=gTmK4;P87` z@tH=&7V-K~HF&$oDXe3Tj;dix$!Zw719LN7)*CvQvDzcPqGMT}6796wv~73}<5WG>GJ4G`8b&cIQ)*CcOn+gs z?++{P2(eXhtJ=hQE48p*KtVegRb-2xq{`htFd|5A!fgoRN|1vvUUGi3x&EX?N+51l zh{b+dUWhl?i`>T7Gp$1sBKtR4Cm~n@#SWcjbPTbegxVlB#g92&I;MZNLuCDpmXWr0 zA-@6A&;U0;$iH%K(}IaPw980+N)S=Xb2<;XjvTF89OJlh>TVs84~@YBkmwV-UB9>&xT6p0w5%Pq)D2O?i{BnJ9)(0 zS7R_%V>UQ0?hHtOADPz%Fo3Jf|4j`skd3>|vlBmO-xyC6%Mj_3?-QqH?x(l*?_;&7 z-00Y!$NkV^*Dc7gDl^W__R7q}F$QfyXvZKkMCd-V=pNo=<{LWZ=G^G@&7>vC&mBq+zhL5d#7hV1n zT1;%~DB=fMp8nyqm~p&OSqD$}PMe#*_pZAp=Z;iu;IJdn}ZO zZKziE!&*VL_EDW`PGvpEaTTj`n@8Tebulc>Ia2ALky4Q_*J(%eXt3d{wW}rW77Mr1 zDSO}TVL+?`oe;Zw{M%t|Uq|GYaA{(l5rph{239Hj@wIPwsnE7WMdAWee6Tk8WS|7} z&0#C$+SYGx?jU4@i4+$lQrtOmMTDX=4g@7eM3Mfs6o{Z9%gRu5t01t4%A9boKeVy8 zBHhW(Di&dQW%2i#Y2S_n`o1S_luYo|xvI0-RgQOQmC4D9<#!C4m|!&1>vwUo?NNes zohuR13?F-(V*+tS@JF^zaQKJnMn(QqpnPB|(DvpDO==X7LhFy@8XISib0K0NNM5NM3NqF!Jz8jXgB+@ z?E|Q0Gdr!6x;)!KFqI@WPN%RkNDMJiIkKqUfQwbz-mN{L9o!B7LwsWDOl5ecF z)XGn1WhreSy`>`zl0y%htBO7B0T#EWWOrJbcz||uxS%f+43Mulx%#AQ$JYR)@2QlB znG$8+;)0h#F=O-C7d_bOXTm2f9NE>iR*YsZ2kdFr7cr3}C2QRQvReJT{#8|xVRWkY zR}&J=VL*$e-|8%kBzgr@s<3QDmPwmaz2#2g;S`)+9#2=u+*=|Yc38nJEv>zFN}M=$ z*_Kv^$0mFt+2ky{K%n+tF?x^85OFDCS>XQM}+z7bKg*;uc5LS-9U;8W7j;@UuGhjbLArZ4-WOlAexGPl!$dvY!%_&<9qCA z3BL)QH6|Q13TnLktnoC%g+Ue)3F~|KIs8uVo6J?LFS+S8kl4~xwi(UHqnbiM8^bQ6 z~EaqgH}w;WEAZuQy4M&%DS{bjZ3nnVs%inIPtPw6L}8 z!9Izkpjo_{B>6SPb`Zxusdd>QR3Ss{x_iDE5O0YmFPL;4rl{FE4pL+KvA6%;DzzQmCCQEmjnwO(Scu)HyHL`vC22uQP|5K2I-g9Hu0PllcB>XAw_G=we@-6eIr z36dW=N6HN^G1{Prf;Kl?n=_nn)<+9MISBd_PH3#u z4kalSvZE#^<;?&f9T(vyMjat&u9DZ48Czzp0-faym&J$}b`^E)oyS=Al0WXp4;c*q zd=@2cW^Z?n9|Lweef({OoA>$Zsw|mjzpB9=5?nCBux6e-WRb@0kg_x})DGgJg<0co zep`@(Y^UNVD=JdRA2k_Uz(#RZMoZ?y@o>cX9F4PPkM12j5?&@;{0rlDlU!M7Msp=Be}A5EpOn zLlVB=O33=cjY_?qHZn>tRZm${tagEw6Pk>=2apgq26r~M1;?C^BAEttP%L}FBc56aJ zwxq)%PZyrWYN|M8NK|WPS5$M5B`2$ z#SJxq)Wk+()Wx&PRWWh??`~fAZ!N5)x?0}k+_|?VN72hH>FrV$j17+#Fk}fO_FhHk zm)lE&E?pm|AAqd+$gk;4fv#H&hAZ%}fi>GWQlyNwCxd%exKF&P!|0bY0BT(w9aL)@ zL8xPKjJHr5{x9zOSCMN_8936`d#UaFZ+wfh-T&f3zf*jSAj;tqw04~cITl4J0mcTloRGU2RFtgY{J_|M~o)VJjcD>eu?t)t~^Ya z&56gdoz7M|eoNy9pJv1OzdY+g0DHc{r6agYq_@6#g)w93T1k!FLR~HrY zyRywZiZ@BTfZ$@tqnguO4o%Ye{zH)5{;6#d<5BmBSZ4=U`Zdu?PWxHOC7KEyb9xc%m+=w0W-ZYNObg% zs{U2y*F2694xX#s+IeDBFTFc|cpxTAjV~Ihj2&+uMKf1#rv;iwllAJWpk>ZDa4L{8 z0q9)|IR@ys@8s{@lhIh)53Ex>aKT%&uV~F0+Bwt{LwBo9JxWYVC z6pk>M!rt6Hn)4Foqe%rqk<`HUWSeQ(MzO@@LsgjmV?ye-aJz@ZERE!!_BdcQe@>na z+2YenFxNxI4-03-ZMixm%8BSVrI#mY9Cgb4718Wd6m2QoHLo5I5iRjY70O-^7=hT* z;4`6FyZ_gZR3YI#`-=e%;M4&5RLC7TwMTmI6Hq$P%qy+77LSLsP5-B&ggLBuS{Jm= zQf8>>z=EkkcEt7+ksE&`hVv&ykz7j|8TTFV*CT(-L-P1Kx7CbHHMxvtkrIN$~uY z(y0{`c>*R@6cT+8jscMz7C|IDVR(Y-5R|3)!Gw^tjUH**C)_X2>^DBY(-0Rzt%m&9 z{jxHr;2Ma285s{CKfMmt-VzM+%m$%Ye@ml8ggR`03SUSx>;rJUquF@g(vaK9S5KtD zB}mX7at-C(*HiuodaUrBmdu$J;8+ZDz@2p2DE2!jRYG7ngUL;ZPnjX%HXPkng((3t z7@KXbRg$hw79yO?cZ-)8|1)No@9{805+55&qe93%`p@Vfc;QRW=|cXv0F(%^$Q?^z zc;yh7uigUf5@+_F6Y?iMc|L^0h;X6@CbS>xrLe=OcCUePs3O3G(H^>1J4^cQCsd&@ zj1S3moc{}>3mFeIpcQ|2_FI5%Z&4~~Mg?Qp+V6&lj|Jnt#rJ{((u$tGdPq96CsbX$ z-~1>N^Q}8F7DcH8NoNbesXK8Q*#ArE)el?$k9cQ8eVo9dMm%vQREHBVPZehWhD{h> zUn!2Q#xW)t6~M8}i_=^hz^&cqM-h|*XQd`Sj8%Z!hS=c7buCGbA3r2ADl^-_80$s<=ZCLvF% zd{hFKVCT&UDW?QFQy*3(z1ctci?g&s)PU<__QJ}wJY!&8o1*VYee=^4JtKHi%o6?$`(9rB4Yr1oNh*2U|=HE%oVhmRc^M`IG^1`c8o&f7BU&J0rSJ79aeeN>s|ms>jl(B6Tz> zgg>uwpr__Ky40)oRGFFH?8-_4e5+d7kqV6TWIM@oaBtWR_!Rw1CEA21fuaj0#+0*3 zf|74)vsBY7;YOUXOo4j#Wv;D8vT0Jfa#=R0^FZ)7bHnQsCdSC5XB6^QuHW`)tvj(j zy2Dc<0i4l#K;5JJq%%M*wjz96f1MB}eMQ$3cMWVWN}KJnD@wvL`OCa@l>ftD45jp2 z23CfZ4j_k5Nn=F8JY9+Z%2ggw=|KOl(foQnU+5>apd4&$8dnGWxh1ntHlb_>*tfq( z{^GAqI62xdthIWCkME9?xa283qs^z}X}eF}gs6Nm&&KDcidyYO;Nn1y3UQ?B27&?7 z6B4PSF>(AAb3 zg74jZ@wkOHe)m|7KB84SIv1Gxj8=<;&YxTpid`qoaLNr=>eyusv=JybSIs!4d30B9 zZb)EvwqI1x#_f|JJavl9!W_q_m1Lo9Ku-)v$F4CVzKaj_oB}+L{i)?{QVhx?2awQY zq^N)~fzmFM%-O;R82$?o(#B)PL_64PoO~}}ZrgZ6Q;q}o>pPrx7;QXttM;I14cR9f z5RhQr=}jJoR5dVo#jg84WjapcS*qslmH;bLeH)aP&W9!z5~B z7&$$k9TH~jgWkwkuetYmys(S(b6-}d_FCQIjf=&Y&FyVF&{TJSfFWo!n`zoP<+A#W zOL;aEOWA~LzM|3BKSk2$uuWsU(R;~SE3Up0#7In7&RL%>{K-2xD4(ayuiQ4?={phs z-A*y-29gPBNjDdDGJcmNfm!Bkj_Dh-&Skh3u3AFbYssx2o(7esB?Jr?2~(TysVrr@ zan<0BIos2YUnnW*{7Yp|oB8`7oZYdFMAydbqQo*mR!9t&OtUDs0b7?M7_nUOj2mM5E`Cm!j6a^i-R0SxPV!BtbRSTPC$`6&4Eg zkou8^Be}~8FOjt8e_uXeKyBXYM6!Eq7yR+hR`w-LmJ0L9Y;NU@{S? zl%mk6&k_=lBn(1ju{cC`am`RQoyrUaV%sy2(CuLK-nC4&_IK&2B1y%H_y=*HmG0rk z>N2(&I<&fmc68@X`R#nYT)h1cPmH%3`6NT^_J&R^teGed^jNx85Woc8^I~=R?#KXN z^haq3&dO|SKf;0f+7e%Nc`GknF;ejV&9Iqz0*xa@qz(FW$xyW`)?GmWP#39q9CLMe zrH-|ofW+OQc+Fv2Jpu-wGTWR;g0pNu11AFn=3LSKWT%cTOnNIWyRt-)wx%?GmTr4p zq8EKei_NO52DL7{**Ed5fDL{8Q%O9GNV$7aGuWYbBo#Kn2p@;dFF%>8zv~xT4p?&4 z3y`idnYlmV&823O7%%QCOr(ser<~Wg%VTy#9tYJ^f!5y@&^Y>Ip+Uu&Q?blFDr=vH zF!S7mq$@5Jvu)A|zWMGhZav266wTyeOsw$S{sGF6y0qhwtIpzER$$F=j8GodSDgto zWwd_vUMnIU-KO!}M0FOy-$0K*!B?{Q?i-U(oCm?oy~&G}QNx0>%~a+cyeU9O^Qa~R z_Ak@-{U`0lZlND8e2W7SYe{zz${XL^{|g->S^J%TimsVNO(^2z`+AkC1^e*tK&<;n zXFN>vkQwWX&MZ+7lJ1=(Jub>oB#4 z3hsJD9GB*7$YPimxe*=yok_MJKu29GA{)m8O_>eiih(ux=R1Q&57Na-V{sEeLG)9# z{lOM5-+_(D9#_=T3u4uY7oXNTAjVhgg>*Auwab~wOwf!XA&P5s6gYV0mJhVJlk4aW zE?v0`MRRtw*HwkK|Jk`iE zUvFQ2#U+gFBNixNJA#wm2(Ik6JswQ>C1wk;x)6Ii63#0@24)FGDXQlC9Mb_+$LqC= zJK&N!Mc>dpnC_<*^Fk%7E%ZK35u^cXAip?rF8(Z}3}U4#1@p+^6; zD6IlgXIT9n?l+s8Dw<40YcBz5%`CdfS^|V+;P~(#rJ_A_idi=Ai$j0ak2Q01q(JnR z11Xg*H#FTB`J63Sx}Jna!Q&-XY6IL?rILnY`NB!dYSXviei&B{aD#8FE3~iO5I#+% zO@!tDNrYGJD)xSql7(4i23dHnGY@f^O}g@SlWCpOpYYc5tm&8`O>)2eHRR1Tk<7Uqr0!24mray7Q zXB>LPA1|vgkdiec-RA@7>uh1jeJhc^5YW709wZ;T28-CqYU63ZAgx}?0 z`D|?KnVTj&O*t}*i>8hL9CFjo5MBCxzhCW(4SOF`mNG>Bzq2$-n2M(1%+Dy2-z}pC zt&gY3N@c}=g#qPM7S?9vqXfsn6)20a8d(LXN6I!(mK`hw80UMl+9RW|xYoowW~1VR zh|nP*Tg3-TY7_8zk&lWGCFjv+iuccOGZWA1DYS((WY3HZqz6kaEnn}+l!OS)V=aL=^EgKLya8T^xcN6eWZaUZUYEnkAeDu+@&eMrMWcu7k%zB zJ-nOk(KUBc^%=!Gw1Dj+73=B~J4+zHizH{O8DkD1gRuAy11Bz`LWF9P(R0A4LD?{| zsOOW^QkU*tj1{JqyP7Cqk+yxoz6&m|mXU_9d#r!1R0aQKAV|MM9>1nEAg7g;^tl$%dBSDRSZ9j-~9P^9`kveA@V<3V*Bqmy_JMR$(v} zecNb|kpz>09L3U&k>MEA0c9O`=h?iABNJ)SOON7 zS-nV8Wip-j{`Fh$&YJ#>H1}p?(ksM!+P{ike|8bVx0?!muWd}Gy-t_j_3T#{_?^Q- zJN2xFe3g2tK1WX9W+hfRvHkJ0C&ip@muVMA3C5_+N~QP*I1ICtfeQJwROK%5 zmZ(3(&5el?=VOm-Hc9zVU$){-2hu`Tf(oDiWFDe`q^#lneItc@X3QX_9uI(x2h@>i zVVCVvtwpx5WkZE;`J;>+F7q}!swE2fP3N*OJ#?;|8GRPOdH2wf=STV@-jg;Ro!+cy z9V?nkOI}ilv+cdddXE?Y{Kr%!SHZ-^TY=V^MduKNTeT%N|i$DnS4`&g^Ec^u zNtQ^zFiki27W4~4rXpLQ?v}**b)J9VpPc*e2_X(uvV<)9%y5`kKQ%-a4@mMPZa*YZ z*MZRnrPv4ar!fm`8_uTb6KqEwwdzj4p8wQ}fMHPQkvJl#1)%*>#{KzR4t7 zE3=wG;;W$!{yhj<8B1V1qG%HD2w20IZaSFI0ngY86vc+8P*|41&bAT3+FvXV{x zUm`g!`%$*ift|jl&|Ra&-&J&YwYr$FxYWMx`b+EPvr6^+B{McaM( zkbMY!WVSKk!il5``dp|!2y5JvcFf)K@gAV@+|fN;?7~BvBLjwhEwq7jO9J4O9qd9) zYO^kGoCBy~6*S)p$|09>)LbYZo2|e1@Wm{-<6iWJVYcU<#Y)4_E}w|~CW3n7KstLg z-IL%N1p(+y5ms#_YAC<mAQD0D z62rJo-vRc*D7rrPY2cPRHy$2+CZsI@B@R8hk)c6bn6al+d@E^@?S!sJ^=gsEF{S;l zm_nH%7QG`dZ-US0PSVUyXd-gUFYh%~nLmElH$piBsXQNqgmSv-)5-_*P!}qO+1`v> z+euV&u~We&Kt^v|Gj>0-fG{+5VZ93uFSFXp4J420$<;dBqe!mCkY8%VYiJ0Ca0K`F z74X`(OOZ5cw{sj_wchQ*`JdJg4xPT)UOgGNaD9kQ*Ep!_x{K6RRlMcq+P0TBOT}vk%6f!QsIG{a}r{0WU;<# z>4d9C)0}84ccfd1TTgOiyrt^;sFkCkm1m1c(EA~J!W(9WGU4-PjW}XYGO$NrLxLw5 zWQQi$z`JD~vqzm}f`mE7QG-ou zd*-n|LY5JKN!eXUo)8Kih_DSh;ntg}6!~2P)g$wjfRRiJ1P69sv=u0&<|(D^PnELr zAaiqB71ZJDj`#)!b2$#BD@`{WG6FP!9@0_99&>_tA!<-P;|-AC&z#8ZXd`58UReT9 zm;Siu-nX#Wj2FPXOFt0MLg4&l1Jo`My)fCImM!_p{PW)GCBzU@z9Sx2 z>05-*SN?RxbUqP`rNr1F4lNn^+GU$l$Uprl&5U%yon7NvUH<(@ADbcwuK*~JA7oab zOwIcPrg2#y)qrcf`qW&T9vaoEkv86fv&*U_24%Pemyf*@5xuJ0OVyp@up%gT}-z|FHL4ED>6%*Bw>hTCC43eZm>6QNCJjZ6u6-X!T zrs$5Qud_qUK5NlbTE~;XK;T4Q#`tFZ6!G%QsnLZ}su zKMWm=F@Zur#CN*J9*29HG;k-@r#1T;oe!pHTQ)MwfVoS+zjmuyneT;-RFPPl>isRO zo;p;R@_|QFqQiF}sR6KiZ0Hc?=2p{0Lw5_A`6Bkvlwi%@7_0z>p|xu~{6c6*LOq@VGXhMMdDg& zxb2wfPZ{5WPk04F+WaX>rVeyhcxCz8a}N{NSX;xL=cV3Y&~c=N8#)Jomkup*gyYH0 zaF)(x5((mu$OymZrN-1J9Kuw>tWdUBw;sCbO6Tw6cyfD7#sa_Ud+&xsHN-ZC4x0H7_<>3zg5ue% z25ai%if*baybwNZmRQ$L@rqa;IHMjo-(OpvbIO8nGg3*HI=R@b% z288_SkOO6Z_9Cox6HOo|O*hDdw|UPI`K`S1+RUs{5*+OG)4}_wifbE0gi*63A`t%Zw;_4AlrU4-1LyO0!Ii_=}Hv&Br&@#$2y{MqJ zPwA-yx&U}i2tfDK^E_H=*cT=ik-?d4o$>V=9`{?{|0Ai|DHmY)du2LA>x^Hn=kkwu z9;v=wLEiHe_V6sqz*EMTpBf;DV45X^Q{9{ z0v$k4Gf!UdRcRMMi7UHmn;78$#!5j9j*X-HONr{&8y@xO5cR;^XuSsP2hC}u#tvio zAEsy&WIyEW5fW74l*3Q+`U7X^Z(_o4^n4TYAq;pDlQ*Kq61t17LFeIy1luZ|Ld9OA#>sua&(D3sA@w z_2LPD#|A1F!<1nKeE6*Cy~v#F?Sqr@&4td6DMSn}I!^6iv&H;|3t0nZp++t+cIM#)=wSeG7aQa;Q^n=ZX3@il?YVTb&G(Rv4(z`6gk6|*R z;2Y)4C^Z0yo*#&ev5fAjef-Fm&YHT{@mS}*M}XxUnbWTtQb<79O3?30-{ee`j!>?Z zHpRw=lj9sWLXW2>;ct_w8a28-j2dIpgiDIqdNf3swWood!jc`>YTG;_8Oz!w1yL!O z^r&Z%y|d-}Nu+z_G2NLzF{xGtKb z@HM7*x5pg2$>0a1&(oSmMUkh>v0ku*_f|Z?Y9N6>VduB@R+4m2is%)cuVIK*593t9{BUtE1QG-X|}6ww^Zgq=IUw*qUkN8Q(JX(ux4q2oNCx| z=#(Omu{D?YYa{ApPQ@vUkcdcPYMzg&+z74-XY0Cxh5OSg(_PTqJf#& z$kvy^(HLQcU!5@ya;9kmWw=%3%K|(lX?RO*eKtPiVGYq+v^X5k8d4%D0u5oTqKL?n zp6&OdJX=Obu)l@+CuIR8eLP-oqvr`^n`o#f;)K_q{g$#X*H@5tng<`vNjNY80eN*| z$0Nl*d=%gnnYkel=_Bt+r+hQ^!LQ%O@#<*QaZ4^^9DpQBH_It2dWiimos1*Q1Xln! zwKQu-Gbm{Ph+6>Vr?D17-!-i^ev9-2-x4RUOL;AG(l&+Vti8iS2GretOPk{{AnCy_ zmph8SJKXr`2~;Xx%1>}ch{J;rBEN=i;kSod{2|0P%$`72@tlp`4@Al`Yr(az&)$sO z06uT%bx2&-e>Y;75y=hF?A?^gLFqDL%~TXP)2}HXS3)Ql@4fp(bc3FEhyw~yp!aPO zwm`=>B#qx>SRUdxnx8R=4J`?s!rY8(6Qt?F^FWlu1TnHUaDSqK^At9y*C3`? zAP3X)D}*}6BA7+&dP31OYw%F4O)GauJ5z&c5mQ6qqTW1Lt36wQVoxcS;Axt}`W-T# zF(%Bqi-Y~XqRzTc|0JSYt3h&V`UAEmVzcyCjS$!kQt}Ed6jh%`euqDLS-96*K|iMC zm&N>%S+H~O2Vsl#EI%?)vj$b`j2;gJ5vCr8qnhV~iHGv7VhOtB3s6UY6@yws@yH5Qsah)Q5CEHZ^QBUZX9xL+l^ zT=3HkHJD}Hbs`H3$;>QlzR5nYs= zrkda>eJI(1Sed}dX=$C&z}P292Hjhk-U~?Hd^*PG+Vt3SQiGeZH8WENEojle)Jxgr zI>Ft)iW;O$wB^}la_};wWL3<0Q@Mw=xRC1j_-a9qHfB?IJo9|?n)%1d_MyL0+g5|@ zr9qv;GTm5@fBZ7#GBRL%#}NA zB){Xq60(i4b661P{!gei%hL*(ef|;{ZBvkrQ8)NaMKG8W-K-ibP6mpRv{2yEPR4N$ zq~fX2Mt7z~fQ+C=S;v5LlI~Rv4P7Y9w)fGntY| z&&?Fv?re*te+>Fz4lnwYVcr)pShFOsCKiQ9R)&O|Iphq)1msAatN%|&NAL2&oL0~R zRMk00gIueRxN?ll0ZzUa&7tEcG15NLu4gD7MtFjgaru9rO(c{y{(d+bx9JtyllO7E zBL^aIU@|&!xy_|%B12|{t z0$G&#PSSdPn>{#CHM>4~K>y&8RJyQNJXlSeAg}R4mW-E=yGMl`909k#g&e-A3u=^-`Rw7&FF#R!;N^ zA%9-Z`N08f3lc5YMz91e2wM})7>f&M!C=MRHwn%e176r|n1nQrE0~beayD1lcTR*L ztf}=U0Bb0nDnMU5&PZM0RDcC=YwF0%D|Bcf)jz&<05y!)N-^v7g!0^#X4&23Ybzsy zuor!a54({8C~BHhj(=ivoZ56H6q|oQSi<d>s`k_cd&hq$caM>O!i)SxiM4mXYrpwZo#aYbtG1 zt~wC~{1#{IFuI}dpyOQFY8{Y}MI`b98lu^sb}KtFdBNB0NjuDV7wlRBkefpr57#1d zo}AhjqqEt|kFcATv*pe*{Dl#4Y|HBHVLrDkH<%ELQUnQ)VD?EH&WLt0~6(}!e|Q4y-mJN}ySQ(k0&1#ByD*IkP> z-kLd(iMHb}^L)^ZNrutHIH*>SA~G)tHNbdH|qhp7?Lk3daEEHu>hy6-o?eo z+GZ@blkXS_O$S=COt%sGafjG+_R~BwyeKo^Ar2^<>h^Ui&Z8!=ZwB9Y;4>(vM!`Em zu76hts{eb-zNzyg{8I=p^@~*aqwNTUv)lGdpm(;h%{4C6$V8>NUhJcg$KPwD4~)UY zVjiT~3vT95RQu~eMbzCe$9^VvOXRRC+Z;UfZN$&kxLoO`!)*MZfc4Ns^oNzlTPi=1 zsCdd0B}?w3BGz1Y#iSlsQGM5(QoLTOwq2}0m{7G;%CyVs#FDhzEIy*XdMp}7Fkd#- z0da>YlCOK{>Od#W=?=rObbDG1iVd5|***^!cGWc-*6dk@aI)sfm_NbR;EMyO@n8X~ z(jtOq*CPnIne1N;#2!~+@XV0>cmzG+z%_!(rx3BE43yJR*#S5Tr+`j05SbH2hpb$b&_yzD$OtI`6m+VLA7BIjN$c?B*L|k<0Kkm(=)iu zs$y7R!ttqltQ4w_x?K)KkE&T#s0Z0D!z5u1QgAq~jsuPiFi$SF@yC$A<0!Ah2W(O} z8B@n+N;4>B?^lH=0`wyP4E)PUt%n!hsA1XffPro zG~Q9%Z}O7la&#(oG<7^vmrgNZU9*d~YPMe*#9Z{N+DRdhk0AA}b`2E9sgKk0St6m# zgzUY7O#P6;FQuu&Z(+Zu7ixHycTLMdYoPW@zJ$#Q7D0h0QU$Mm{w^>jqV=Bfls5G8 zv@sYIAk#!S&Bb0Vf6@z$eDIT_&zo zdQg_d#}$QvIIBd;ovv5Mz}ry(Ej+4cTC|#f^Q?GT8v!>Pr9B>nh$(zhEz>vJ_pgYy};3(|=oj`yL8-Ee@^a zJ;ea6mEo;I<2|c{YLrnjLZ9*K2TNSFMnu_h>+;UF!5CHB-)Nq#?+5t32cu4a77k7# zl{|Es8OxK46hw=eU~B^;6oL=cbVKc{N09Joz;-Jdwv_Ds#kVmrGD$X|gF-wwa=kh< zsjV{pXbH2J_E)8~nxl6(L$IpHwcA&24BBe zjp<^n(XL1CZ~~o^XS-fp;n2=Uw9F`B@EEzWD{lB)R*%u6pJY##We0-E2lal2WYFCX z6vrCy;bhBsFgJWXJ7-&Qv@)mT&9ceB^%G52yK@5OBJ}5m?^ZTa$I@qpYb%Rv*j6(X zd0(Yx|0jIYR+Fd_42f1H>g z`wxFvGEXyW+Cy@u`!BpdH=Fbm-i5mAYRxT_dxKSHJ6`~2tjp^zVLnL_t(_S~P1~*X zIUUr!E~)sCAAKLn&welwI}JO0Acp{+146SXbCiFYr%Bs>TxO=6Dih78?Z@jgy6E%~qet2gE*}j_w z*aCC+Aa4yatS+8^h9UCQbkzliPt$r9!1yTQkcvVELG$3{->JyF7KBsvN}ITGdKi1B z;`k6)}n$eD8FHP2KJ>G)6i;c|sabHX) zKRDM6yS>bg{HuDBUhtv2K?D(UBEo*-9m@pimRkO^1+XJls+Qm>SQsj9lHHnDVl!e2 z;}wF5cD>QIfAL-Z(Eo2}&wv_!oae=TGIV=xTr8e(+iWsB+qZ=8MTh7G{Za?7z0j#P zce~jH=~_Swf40U31CS8ovuZqu`n|919V|5@5&WWRvc0*LWP+4VyYSEBA{W^V4&<2* z&A#hA;e~$ECHGszh+5DJa^C=)6P%&Y6M`^r4%=>UO&9VGUiT}m;u&2w3kuVAFi)giqn4%g7?CCM>pv(8Hh*nc1dbZ;X6!oCco*Yh9-^3# z!;Vzp8Sk#RnXB1vtPRHqSY049-D8ipDc`Cyq*)VS(fU7C5QLK|RPQat%MC4gkN~lu zOQG{t2(;Q-4*V8@ydty+Lm$)>T*NY-%FsO6^2mTN9K;k_X&Q>D=JG02t+wdT z*84Z74GQ5AdH>)l23DKqXPXbJkueZ94tay)eBck@v>exo59Zx}))4?a8%$T=rAKoC zo5sR(V)u>YCtae5qR{JDf+`D-M%$lYcOnrwLxU|zsTHbT5E|Z7$(nWkX&Py+@iFPS z?^SR@zL%i4FGvU#e}}tM_y9>jw!a3#-;rthgKL{>KJ={;hJCN07PKDTdeh6o*;i#} z2OrGFc%fyI;G)96h*_^0()(M-Z&o>wDGCmp@%1OoLogIC1q1_akB@zHhK@*+23}ub_Zl{+LAe-oeV7?>vKIjgkf#{|66bz(|E0{$`u;Ut@-P7rormH=spFO?OF8jhWMFsM5kGNz*uZdycTTQ zSiZ&3&UkSBfb^%&xTfeVOM%s{*74w=S_|s?jyAn^CRYOeH>IY+2eyu9(>%k$SDeT^ zURf~+?Noj9Ix-1yEHNTgM4-f`CdCDR!M(I|C_jzx2%g2is}t6-MCaCI5R0C^qVr;~ z&`C!<;C5(b*#AEC!jFqrk;J0R1?mbT%{VRJ@9M<$d8tSaaI@gJ z*OG>%&~Pbm0`jb!%jA#f5p!<9n)-6XcF2b;v5}SZd0om;|3u`Wfd19uc<$dXMxVV# z2R(b-jghQA*;@-5g&s`J*nmn<7ZVIF^RkCT-@+@SfbWtTan|sp!dpg{6HYSM)Q!PG z!z!@8ko% zz61(n2$Qr|2D51IR3Q%f?H;UoayXfXK6jrIGfI{98+y2flRFi0p2OFt@-8_w z&RycfzkYKs@ZfSXhT9B!4tlw5_(eNeE{(Kz*NyVbb3aU;`c2EJj4m*nYGdkn*Vd{7 zpv`g}TKFv%5kKWDZjGlv9O(-eC zi*fS$90^j7Eh)f{gd!*YW46(c0oIw=XDK$!br1C5qHE+8gF6bicu1B_{eNjPTN-MP zLnJ_4qV7(bij!*ndHc;?yssf0J|x`u>ECD~UJ0!Z@=RgTH1+D&^B_q#-j<&H`J>$a zZ|AW(Ct7>kX?eGdUG_WJD1_t4r{+<$g$B~0CliZy(!V{}$RL^26Tx3Z<5x-LsBn_Y z!aWJ9u!O*!U{GfBw0UJVQVbKLhd@#Sixyi`AYk!^qd8twjYYVO%iN4g^Xg@Km!cII z*=KcMMiQ0~z*%{l=(1Pil}-}X2{G3lw>Csw-EfrFwHxaTvQ_G7aqbrj4@sv_U?7;+k!)a;BK)}@!HqHu zHLf7AnaC^I@_ii8UDL`19r2{x=o=PVMU@Jd2Ggz@2G?~=m)o(vj5kogyIPp1qbu7q zcp#>`-{+;Bs#+FJ!#VuQDf;JudtCu;O zb62U4Bg*wm(0odIQ14+J^o3#=A5WA#OHnUH>fZd1WZ#*-u_rS*Jf}n2lx($ zf=8}DM^vY4-PB60c!ya|8O&z@K(fk0f`?vQl`}&VH$0PTMuLOjuIrko7jRaQ*|)xr zNtdWpr701;zEM^#A_Qu&kt8Qh@MycVssL5VD{k*RqoXMj2<8Uws5sa=`GFQi!8Ww;>pM&>Une{ACPr2czF24$sl=G{(yz_uNxE-3+b;ki za8H-2JsZuV4;|8pRDZ%7rgtp-2}|*EDsP-v$(oKfF5@?OXO3~TeIIAES$964HyLUh zvDq8*WI;%B9^z`&Z%3T{?rpdn$~8#ZlMaxd_sVI>Q>C97H%Zj`5%o|xLD5aE<)pM5 z9$nVO^iZX0iAC(4gmPmdo#3A91%y3j7G<$kw5mq}kPE@26Ur*>>Tg>iu*bkoQDYAz z6+%(I?kKMoSPyncmp#l22<(qZbxKLe&jR}rqb4KI{9 z8p=LbFk+}&Cl=-KXLEIU^lRSvZ`dTi)DGq~Bb&Ljku18Lz^Tv1{WaAR8$90qcKf_z zXiSOD{ueyUJ!7Eeb#1%N+X8fR(LBM;hjnc!O;qo>5 zv=y%r=!(4)`d&TU<hZd=NGQfa73syPLl-DkvXagKCJW_$V($G#DpLn!_L{TI@}= z3_%=-JvYf16-YlTPHl*x&$y*^iF|ndC6f>ESn7Fpka8d;bN&$F7$jz&D{Hba8?%1| z*3M-r7#b=^r-(;^t!EH1x;x3t`s{|MxB4l1?sozi_umgj6b&`HILdcstWMopq~g9` zi6u6aVlUacnkIv6j6{-&>iw29#yUp^9E$R`{>WwHCfd0=0Q})mOr{tJ)minog%K5l z0Jeq7p|ZiU$I=XOVU2;joh5bQDajJb9&RO4yB0Ljg0ql5$}tN^@sSu)bLkPTR$SOT zmX2H;`MCJFmW@+y* z;(nDpo81$=Z@+|%uXEy+lGB1yKSfdt%5Ok2H|gIMx}nNt!Q)TsPyC*$J1fU}Z-%F4 zf-gGrznh4$-X?rYuqTiBKoSLnC7|f4*CQqc#+k$ftj(Y1B@TbcXZ8}>9MwZ<9xo0) zqX8~V-DW79;9(;dL%^{VpEhY8fo{px=Gn^9x2%m?|3Tj41H6!}$XQe?Kn%abSv=+4 zaO5c%RJkFO=(>hl`fASLCLIG;3yXz>af0e#HhCOVuIHi}vYQ|w$>$Q}*ZW5~<21m%Z>mSJ zU$p)AJYq8B3B^lDkKJEi@0CZSA^@Y)@awG^__B~_248su*i(+FD-E3i^Z=&Mzn}0Z zB2`94yiJ)Pq^|JBfi*et^d0TT@ja#2U?An5bVV)F$F%0}oaq=vTR45RHooW!tZ8^a)Jz->R@D?`-+o-G$ zPdDd^n}$Eq|L{k-3dT+C@;h{PKXjaY5WeAe-O>=V&NAeov|j#HIzU~4APG0CKaP{w zO%hczuAfs6&e&6kB8>V3fRgJz7Odbw$%rm+Al(ot539_M03=Ww#&q1fN_J+~wzGi$ zs$N#Pf5urp_1MD;+G#g-v2o_IKA}jO?__lMK)jWu8>P^-(+v8BuFs}?VJK0cbU&mq zLzb~F5W1@6`QBc1$=?v>{UjTH^Z3raB#wXS&9{{$Zy6fvIaS_=!ah=|MR&HRsA|<( z=I9KL3|}t*Sb$d4>RL&c#w7=strN^hAMIE)>SYgG47nQ8jeD&uC3W^LY0+~B!aHF% zX@jkmRtwY)w-0io8<(L8W5DucdSH*K@3vADvM2QQKh@2E)Rt&4SDyo8sQ-8%&9 zGw3&Sl-BaCivA^pbv?J3M)uv4 z`ov$z33p;adUM-c8sA@hztybh55w&eJ}YesEH)p(GQJaYYyg+{3DNH=MAase5V6eD zv#WzhNsk&To}I3)MVOd;-~fy71nxV{x)F7)Y=Y;hR&3`&1V3srN!sH~A_FUgu}FOU z0d!4b^;0r*?}*{^`*(s_o&)Ka>-w77a!)0<>@r~kkU4Pj3W?xO0QP)DH=FD#s_s?Rii6c9-PCX2 zN&_~V-`Px8R{t=9IAKu1)8KTywR_w?AL$svm6_|F7|_okTPD}AvOBa{^)?tA=MSm$ ziCtFMt#8GjmTV&)q_Z?<%;3^B*zDZw2^%;fH{E4VyC#I0TPdkiegXjR-Igws9yxEjHzO{1CH(4;(^r zVsuBm=xgWxr_kF^-V}!&XGvlDa6hTI9yLV)gtXDdaOr6b?zf{_$DZ+#$r;bBx$4&W!-{3}k?G+aI8E5ep3hbx zOfW2tm7yUOdOxOUU8aP)A>1idYxJ5ecgfrGM>wOdqx^)E)-zzF#DX1n)m59sMSEx2 z=bwyz9K0I$6n^IdyYRFSxMTyB1UDCb4B4UV%xo;uX~D!kiLnddWPAF&I#auYC6#zG zo;|yT0b|l>pyO^+vNo>t%TPg^SuQ{lTA=sXT5Io|_ z-ciCyA%+?2#eX#hy_H0g0D;&+T=kW+?XiqfN~F&K)G6aDSpm z9J!*Wo}3CtvKFe$^TkoURMx^{AH1P z=zmGj*AP`#xrQDk^Yk$BVU)c6@4=oSEWhE$f`DfM<78HF_;l9Opg!vO#~(1b+9qK` zO4!p;brGu(!6!pIGvFN~svhsb;TunUB?bli&FL*&!r9Nl4%TZw9WIpQV z{10>>E%_X2PVktzXE%AD*05QH(o8`J3d$TtVcgBcI$x71`}mY0N^oTC#(s-xL_vtJ${PZ_xo(lg%4>V(n`>R?R&^~YX0f6aYDbqgZf zhLUdY)eN>@(oke62TSF`@2!?nq7y$^){&_~Y9X2%2Sb>j^SO_b(Wv`9`E%k#kw7JuMjv;1ImB z4%X>FiEH+>d3~zPv+ToxV0wI-CWwazw3vAp8R^_UDRF2zy0)dfKB@-*e4iY?G4K|P zGpoSoyfFvnQPl16TOkhWNS8>MvJ$Hm;8C-D4I}vA)yFWg-qrHe4}wQeIGu7Vp*dVB zJ=ldfj&z`A9&cFD=g$sU>BJ4ImF-TMFemU`w_PrFt|kl!31#p=&8M-bnAC9vs8=ph zAGmfE+eapN4zBby24LF3t>&u@Ey+~}a07&x5g?c_l{zCvjE}E0LXvne+`v2bTqgb#G5S>5gtlBQu2QcZ<++= z!kauTdr=46w@ssj8wBA~sK93pzQYnZ39KJ!;q47KZtZnEa1BgUI}%>_NF}#Re^nAR zyc)9X-1yLM6nEM%d{x_Pc&!(oOC*=oIO2qkeU@>Q+M?l!P;zFd#odW<{nCKL_P7G|TE+D^L#UH2JU84;-24ee=MtQ=J3N# zYeD_nTysT7W6Lvp-=)^eTF#MJ%!~r~p*u)CGCJ6RMTk@2c*d%|qMo18)SHw{UYXhp z7O>spb`?R18uLAX_A1o@>>~=8l&-SX@6@Zg=au2Wk#Y z*s_kG2>EV}b6bx8FzRD!?ma5geLzg#eDNlZ;HG4?KUeG@|EjRK-=mh|3^9S)+xAq_?*9uFb$% za~qVW8-dwhumFSCT}i8Q{$+A{EE^Qftkr303?C3T{8rdXXKdU-j_aT>(|i-IcB7KX zcgNP_Fg2bjU^fo6>j&0Z!`d6iwUb=&{uQ347i^KYN1yNUmg3IbN70FUYEod~KIk}? zAQDl1BdF-g^>1jhB5BCKL<-+_Ux;+l7WOT>sDs(>XO!jyL2wxpI6Mk3&z3Wt8ddoa zABRSw^$j&?FY^(VEn|o^_d;s_X>YwwHfNdieU%E2yAOtS&fttJO0g>cg@Z=`xF@Z- zcx5P%Gm%vxmN)b~lgo%i+Ru8n=$uQxwoG#9a8u0z^*-Unh{mjY?>3Z%L6KjddfZ_# zsx45+IS1h^8GHLhKN(HqD@=QhEd4N#iyJgF>f8L}=N=h<{p9oeV#h*qO>?(;hYs$L z3j_|YDJgRj><_9($r78YWBCuQ#@?o~hv*)Zy8`D$vJ7-?jfrv)^C0NVBi!;S<8|kX z!(fN9QnRZwUtrd^HZKk5N5{(gNan^17c5%2{+kA}!U`-H%GXn!on)0c5id0^5c5XX z!uh|s3(qOVmIkqwy(>A*Qmc%{%i|4+nI+=}C)w7Ld2X2*F9szCT0OUml-?5}uzgn% za>#jJ(whNwM)QJ-v(#N&%zNuCoHew~0aqu3kq<}rxG;V^?)cinGS0NX!mMyyqHNTd zE1%_$@Y=fpJR-k;T@y^do3tZh+9m6fqkB)p0U{=cO^yh;WpKBR^}|jsymRN3h-Z?|ZvYS7c1s*d9fpUY-{mflcT7;sj3I zqEDQ4q#F7plh?M(r(xLJG#~Z=uT=FT1c=cji6<;*&+xj$jKCO4=F%kfu3rVqg2jwQ z3n@$k1OzbbzWo6gZ1u-3Yd7V__|0TJ%X16fW4AUp+h&f+{y{;Ifs#WZnIR;+LSN%Dr_tF=*%RS3J7zr|M!A1_+ zK9stKIT=ULIh~cu%cDEJNi_9cVVfSM)=P3v#Fc09uS@W4AMcgyG;|+_6TqG9dHApz zVfX!^7(g1mwC#{BWIsw>V94>Laxic)&F!=GvITm@g&)0lUQcs@vm1Iul)g==3mj>N z147+C*Ii7{qZfH**~vEglro+DneF-zlizEyR4pNCF|7Su+?I#v3@YM2L+@~w&?YX^ z14_rdCpvf@opuNtM_270jzJ7xw-pd?$m=dX)1BS=gGp5JccZnw>7%b%E-)D>lE!zAn z=|W!$i@Hc|vKC&4wGec8T+I-#kf=fmRgOm{RW`1!6nZ7QPwQHUWtmevt#`Lif zv(}nG2Mp8$z#=*XUR0@U7Gj0d|CFH;_c1ZnMTxdNZx;4s1H0L~As|zlnCRTsz0%O= zkaiUa;c+Wh&Y=8C%T4zct`S@Ok+Z~7E>r5Ir-3Yb;OpjAwN_@jg>g~!0r|R}(&O4+ zN=Z*>dZ}&M27QlRajxQKnpXqafFLRR`C#2RGvcYVE*jsK*z2EW}Yc2*lt$WQ`U@$C;&sF5TrNOL=C6$ zq%DTD7_bigSA2s_(g6pJ>;?Y#)Jr-}N>RX~4|9J!Cm`J0pr~z}7z2_``LGlr5s^xj zL!TIAx)3$}ul`w}j^qilaZBc`2!-+CqsCfbI)u&`r^h$0XF!HECRSZmv^BxJF&vgn zcuVINnLCgdI=2>@k>$PKBIVT$Ako)%+_i5YM*R=)FC?jO#-WgbXh!*fMl{IV|a3&q~_s&v~ zI|feCjGxB~H~V#ZqdTbor{Jl4x#F-OU@ltN_OSYYsfT_4mwI$t&h4gkvnjDY@>BHO zL|F>&r`EKF8F^%}EgpS5v$E53DWPi`NMixPEJDEpP~Ga(QcznECq85p75B}O@=VI? zN&f8E4r_!rT>RB1f33k!$!G{_1Kr!5UEW-XhsCgEtTFd^A@G2F0;d5pC{C$(y9B;h z4!QCiCLr;^p{>J_0H`PCBb;!FAFxvYlfO@qcYOa^F)cz{0uyS{M>&QjGp}zuFsu1M zjI4N7h?Qp)BPR5_yoqR4?GGuskP3S@^6`2yHI6SE3%(vj0fDkztf@9NHM%+XPlhTo z(%yeMN7VC5;b>$*?)NE6HiWe{!gD$Yr)u)A$cbXB;#K_puTYr44shzgI~sY#LCSg; zA;{FeM@2_5HU~71rLHC#qu<|VaQxE!;FMcR=h|6;lEpsqF=ay12}TwXQtBTb^~p$J zib@6%Hp4Pzn70?7tOk}mTV6{cXxz>uE?nsFbP{X*zRG0OmpHbX8c0Wn*>VdP`k-l( znd4#Rtq2gM#mT~?(WY%87SWi38PlQ_qdXgoi=6}tmfJ5yCr+iOv_G7(SCPnhHMmir z1%AiZXa57e5neVo5T&A}-0e?$qKy{$7Om*=C1?GIZZLV0nL}87Z`D=7;JHqvEHI0! zCX4H1Q+)Wdqn@$1veIoZJhAoEJTJd%Q_yb&LXxHZ#_AK%O1eId6v?X16v_=SI{a8KV@ta%1`(QJ>9;ZUX!vZd zV$0rhI5FlA^-oHnQ+m%Y7#LGSoG)prGxBca&Lcb1H(7gUGxQP`6w?T0(Qxbb^?%4# zaE$+GO2{R72Xg2zgeXPsD6d-lRefeGR3bPp&ZgRAC4!hJCa8kw`z_dO_~FWzA&w2j zxWlUAFREv4mMA1U7uO?QE;-GNKf1cZV8>&Hs3Fnwo$7qry%lJf_sOXixhb#oNLTnR zUb21uXZhLyA;w8p!-sDgIAmmSg!;wx#56?6oEY5u_ z5%AEi2jhRvv$%{0bbF5CInLisBcPxTru4oDaR)kA@Es^Yy=w(W|M5j8; zd%^oIKOG#4(|WaT9@>I>VdRVg?!gnQ=!xwd;7 zfQBqTE@0%CT5~c-^49djeE=?({}ky3IweOe0#zcGeB;{O+=FO!7pYIOT5*Rr#XnwE z1eN(??^vWT^;JwFp#R=ed{I1Vp;2oQ!U7s!DH|I;0Dvpm&(yodc_|wMcCGYPXeQP5 zhI>>R#aaGmV@7WYCd|};|2da50&?kcgv>&mV}7WZnnuqT80BlPs1oZY_`V%Lx3zxj zB(|}|q=MXpwKC1^G+OdO7Hr)0zE~buqG5(|55@VWO%^bH6z!s~N~8-tp=Oi>vUA^2 ztct-0#P1BXyDfjo;QxfX3UZX=t~f>+XqCSj0plZ4s%a;R5h7UQtZlSkqLZ`BcXRY< z7$GPTF>Vr9i>A4-s;HMTWA=~s+bqzopSyA0)h*Ud(*cJO%5*|^i=oLbI+#gg%-~tC z;46IQQEWsScB{v<7s zNxQE=R-rc!UFhEq^Ig7@IW2>h@CzvfIG$VB>o0JFQK8r_m7-Mht;bz8#qDt*13h~W zD3|}XHOZ~mYt)EqT8`e^&6T2>Dy{^NzMCv64#Hyn^YTE(3|7X1Fd|XSjy4qeU|)$I zbGqy$>78|1;Q7AOJ>MhEORN;WA)JsLG^8ycjn{&1SMC7vIu;nmk%PLk=;oS|T^!1p zke~>13q1RaZh>hYRW_?Y`TY+hv2!+`R(C^p2orRsRI8uc#Y z1g;R2iYku$nNGgsvJB;$u($fSrI%~3;UCu?&q`EncV1V0R*17q;y9X#gn?Jhr zqgyt4=T^{s(z)%x^`d7xEP4lNjslY`b`@dm2k*m<7ybWD+nJ^w{f$k1ZaAxh_T131 zKQkD~>eK27v^L9vE4lW|4NYa?*Tl8CTUt`Akwm$pdoBFdjXm}GF;Jn&V?uyq3?;j0 zVECaMOVHUK$0iRNWG*#ip#MFut3q)=pVXBW{!;<*8su3G>DLI)n&#wu#)WTIssOg_ z#CydDH>!s6r_x1EfFzNsVpj9wn;C?6Kbo){LqpIT)f;%$yCS{22UZk7W1TLk_1pk< zyO80&>N)DMiEIqEzt9p7imDK9)?CI#n4?EfmKHL->M#QB*QI9_9j{&We0aPvDuLNj z6ZD6VH)PLd~Ga;oajjC>?+lco(`M z{wTFS{#F6muVCissFM5mycyM-h=rl z;XU(6LYHzwmr@>%IyWp7NsEeVNcHwYu4G+9+L~m&KWP)}98CUt4Pg(}0Anuw z+2<~^w2}?sA^oW3Jvx1g0Zv#am;5vZxmYYywD<_F@-}ZQkWtUfGGVOCC`v3@^rqEl zuopR45nQq)p=)m;wMykdE%<{DT@}`@~0qf;F;f-+_a2n5KGsyR0&$YQwWhq*+3-Do+`8s@ctl@w~? zXx{=gi_98Ms~xjr`DeQxUI>h5B+CRIQcl4&M%M~n`muE=+c_cZt{Cw#Vr>h1$qdhR zOOC~!0|6JZ^A`_)&0&Sf;yIWNswb*GMBOcf0yc~VY)dAV8u{%oRX_mN?J_S8qO^@L zTr<^05lV*-^NrDTOP2}PjB-V|T8BZHa z%>ua%kw*{&afMnGxYgu0f7rt%UG5p#`;LusHrA+%GB*F|(`A?K zVX-T}GBJIx=l_iKuxM+881b46f5c;O4n3Q=(b5bh3XldBN63yrxPgT$j^8|h*(djI zdQDzfwOY%2?Vy%r;PvrJ!rdOvC>PXvOnYSwtRb!RofE9i`1)no#?3giv9y3IJ=K|e zXvAqQ4MR`nLvYgwf2JSA&d4g^dMEnwB$wGk`M_BfrB41CJbuds?V4a6%E)|N$WKt+ zFjKu?uM*oB1E?ghrr%+fr~!=o+4ONGr_D?8x*rO~YF=^TAV>EzcvyTHI38=&hvRkk z_ht%aL{k=ZYwu#>;4pG8g`w&yfh8Tw%WfenPog&re)?yqU$c0a%r6&a=$qT4p{5$x%IjZ^WDaw}^P z(=0Nm3#vxXg>V@edFDd-Vdw1b<6Y0cm0LzQIbI$YJ^RnVKP9~SR1XsbJIZM$`V69K za2y&ymCu2>Y!r+BLmIzSHc#&3uMtXbIt3g>E9AYiN#9d{niK^W6b@;ThxNH~~rE&O4yc==1q;}2>rggXC8BQ+kzQ$kE z@-Ja<)3t(I`_sDCN%n1sE*Ya41r|${2bwr6?X@9 z3MspQ@uzGyoGWyF(V|WXgh%LBkf09$-@3d*xXQBDegaN$Sl0MH)!on4^40e_e}Av6 z*o&&eQM2O~*&2*;U5b`Y3jB-*N&ZO{sl`*H#`ZZ$p6*5Yid&x3p4HHF-ox%jBcnUF z568oL#`u4vp)>+hYi!DH|3hAzlkn=7hWS5>?X$3qfbJfdDXhC_U5GS6u6d^?09U9&BsaOrXnmQ`|ckg1CAxS ze_!!?e}TOebGoT%|2p4s;9&E{CR4@jF5Q5Ex&h!mY@!e;^x)+m+E_N}Xeo^ew~GQK z8A*`LHj9R#qdL}61I!1^+EDu1RmTPB?tDf>^nhb~1NZ?5$kCJWJ@ zWGw;s(8Q?r{tHCy)1MpY2W}tx>U+`Zo@h_tyC*rUjLDogutr`D4>{qkrR?>N7e?-8 zjP)`_yn9360@1o^bd4mYXkAOY6pa*v8)MVBt>(S4WrYh#6x{E_84g9`bN5lbks(rz zwiCm$e=;6LwV{mN7Dr9jZ69<9_?5U7t>T&6F6`&@mSJJL?nKUB#m>S+RDiEE{*`h| zlO8ro$LxEzpc%i6F4+3{tpH5U&J6*{TT;yllAMH+-HSp>9T<#jxZ4Tmw8kkO=PEMK z_}Dn+&?y27Bpk@0Zy(L;XloE8LT;e_f0&q4EAdqsWpfR$#?;Oy-CD?j5|I`lcHH2WM4^SViCctL7@FXuXbkqYGkl(74&bIDd;qW^e+Yu}euv96!v%s`*=g7M%Lw(H7 z&wE$KV>bzT|44frexh{DQku(ub}C{$$muw<5#FlglAF^mE10Z#i%HaKz+ zv()#u1SSUvAuJ!vH0nZ{lTYSf51ERw-I6}Mz(mkgKz@W|8$h>p9EU|H9m;)z!_5D- zZA*%rvV7fA#=~apcrPwsFLdfomR|v0^W{_;&P!F(^NXIrqSedYJbwf-KeYX$VeU)p z?%L^o2KZI{o{AQ;U7r-iRT^+M(jMk6!@^cxHF#n=^)-3A0@z6+gP^OromZ<6MURet zRK*m+xcZ-3t&9#zm&Lu-5~`LjBac7dFycb7`IdQIeT!5IC%Q`f4lZlrGQ6mCR40vK zj!P6xSL433DtuEZ?UFUhG(VmfVs$m58RcaS%W#4glD4f)(GvZ7B~4)rj;!lkF(~6GsCMh#`S;YmepQ=z zF|Sx=9-0CirvT)EaZ7oy_I3n{VuW$w}C(vfsV~{=cQxBe$%uQdKH$|LHEnl2Z^zTx8XnW7Gg+~PanKp>M@#NM;)xY1#P)WkY`O+iC-Ud zc$HS`lUXN%;`+-qQ|_ysLsT<~Qu$dlbV;M7R(arcQpD(q)}u2#G~PLe9}t+`)hZvr zd4ZPeQtd+=!)BBSx^q13yWfTd`C>9kc&W^iXKr<3r*%965OJWU7o>Ju{^g) z{)`KCLhNW=0Ss;`LMc5()|a=4v&dj$vf|AW)@`{}o}dUaU7^!*_m6~7)20jKY(A{h zTn0$+s8LVw`+l6d$b7EZal~0OPYATIm*1sa#mW*hUCv*UN!!wYyDKB7RT9QJb5!xx z=friC98AKvd)FsY@_=tUGkQ8c2D*$%gdF6`!|mF+YGksqn~i~+O~PVGAH~WNS65wB z6^aE3d-E=Mj6Zw6<<4nbb`|K}oKi|g`+>4oA*{e<}deHcE^U$P1!a`%OmV08T+{?u;%I83$lC`O()(vQQ~;$Tr@? zRjwX+T2uvtasjTzJ1Tjp_lBs#t9T&);^ju;&e|O z?dd@dsnS0-`?1Pe7`FRv4xG9>0~OFjOUJ)`xVmo{KMgly9J+Pw9px0F1$*?hCIw{} zeGc))V8W56k`54jgicYy2oGn^B}Ss^YKNEab=!p($;Bv6z`=!MUFLd%ENnB$L9Q`| zO2f8<_vLC!ZsU9Vp-Ufo6ybPeQ^oX-g8VM=TMv)R+0vM=GNHd=cq>CMc9y!us0SM# z#89^e(hv8{>CziuYU#kO{o&ZXB#%+4U~>`j@)jD#q$p7w8T{gi9ND4ft1?m~yiCZr z;F$*vb@fN1FjCh8rG7>$L(U^=Sql4t)dMTNcg={*p{>SHupZXg!H9G{&tkT%TWM@_ z#00(Kp~eDtf4A+SyA;Y$n95He@#=V|OJdUog^x=7BtluVMizV`MDd2k_mQH_1Kci`Yj&bfUyZWZ@5 zr7fFHp!I3bp3Ux{Ia6R2nP$u+(_u-r`}JbzUL#bigVlG9GR{T^mrB?d6s5!ov`(}| zZKXa~Kmw9tLAWB6-Bi3TocAIZyQ5n8|6M<==-E(@0DPhu<^S0a0;dWXyZOX}08*); zC)&-ex#_~tAr^%J`S#$xlS?Nr*yu8umB#0 zAr9A7&VfK%RaZiQamigiB~5dj^8N{vfG!X*{KEa!X#A;ODtI9Lbsp-- zL=WDA>W}RB&oMvZb>nV93j{TZVvTc6Ki)(m^%qzVlA!N|UF!=6pULq%)0_bBvg&dPui3=1@Lo`tgq`b&J@-=pgnu>M^h$7x-*|r2gOa ztw3bH5t{w+@E@`4;FrN{#`M($L$xV}4!|O}J-=&37YI?%(}3tYckcz_lB3C@oKH%& zSug-sV9J%BR}+qZ6h+hBftt3D@Er=I@N!2OW%!K-@ahU;Qgh$8%4~F>2nfY zi&%zV1p^#O#0!KEzKmgsj&&ks2MSgt#cnZn%aE8~y-V2lBVUL0CR~MKYDJjk_e#Jl zOvT}0QxA}GCH9`1?@XP4RfXPE|Nl@QZf^hruv+&KJ1FGKD0sNiW)23C?TmT#CJa*c z`jtAt)Rk+jj3j9+fCCXB0DiTx8Yj_cr3=w_5|6DKMLr*laXMnK7J=v}Id`VT0SS_( zUeD1hV|ZH#3@3v}EcUT=7&KNRnO6z`Qhee8J zWCL|349&!ck7CqdE@dK(!qGrUN66r#!CTZ7hWh16&RB$;NLqKNLaIysQh(&@+#}K8 zNJ@ePI><%Kl^PG>iQj$$@nq8gsrPB6k^s!~JZ_%OJl)n;q5z(vv_ryeMccD0KnFzr zf^tka(U68*OKQ^=;;y*ngN)cmhO~FeoAlu<+sbn=g)qy+wI@VIldNN`!Nk zDxq{e>;P+?t{pBJZa^i%D67b!SrfBUq$E#+wkLhD-z8GZt247p4C!>>? z=`QJW8C#4|%!D8|&FEil$*C{GUFd^RUT{JAY^Jy$$*!0uI(s6bGs?=d-yxw8Nha#9 zK&*%sE}H;*Neu7+Q6W(#>kA;ut99WHD~H|*f~<8t5RzQd31L*jx7bH3NdtF22|qp# z7dfLl!qom&5!i^>7f{cSm9QUA{y7s$Mfqci=IVR~D|1KUAuXp^$^_Ii=>v`Ua(F~@JcFd)$7+{wU`6-^emEpfG{W) zEdca*q@|{pz%Hy4kO<~Ku9ws!Sz!@%{WDxtmb3c3kfP?*Mt!ocSb(_<0+a3ypFFp3 zU0cm-7tc(RxMbYq{lWT}2pgV)TRkm47W@tTNJQ;+{IF@9JLGrjNUsq1gUCqsItAlq z92VoxV+bijiUS&o5cV4QBYXAEM5^HK1HV_C+zZeqqD}t%jA`EQf$gXEYpV@%`x0Ut z6_zL@#I$tQ|HhJGn4+g-)Y*yKEQSxtWaFUv%{U}FO!{9e$KJy*9Q?Hh`cVnZCc>X( zcHyNHZ6s!wN(<`dsCQtMmX$K_c_kFrnpc(s5wpd@h-*gn9c;ZcqgQknl?eLFIJ(t> z7~D>l?UA*0WA(|8Iw9I2OP<}wAj7=nutSar4jTGyvyNg%ihK5BaHs0cEDe8E={OBl zRU?nQ@R{#(yhqyTotRM>-#@`5FEQaH3r|_kFWru8uvBYoCk*vW66h(hgx1W z_tfpEfUFsOZOVN=g5QUqF}CU#^k!5#Y$;(-w?dJwEAL^9AlL3i(JzWhDc>m8OR-IT zjr2*2X^(cae}x@_=m>S&Uup-5SfAi@>M4CnR_EN{1{&WoR`UQ()8wPDr6QJRUK>FB$tw>eBhiicAPm##t^=-DYS)b9UCz?hh;P`wBGYTaF%yK$ z#_iZqHPU-DN{5@HW_4Wlg3nGhN7^%9rEQZ~^ZX7~FV2%9?AW^m6Umci_Cgh^*K)>tL?cRd*3*E=2;J1?W3}?0- z0V3&9?4JeX3PcF>Z!L9DApxrZ6~Ta0)|Q0Z-cvr;?q0~V32-`?w)wu+VnDe7Q9!Q0 z(`EAT>b; z4gD0UZ2uuF$TKi7e@v6w_@)R%!iY_#g^x0UlX)P5mx9l_d^Pxqa+ZPrL2xp(do!kZ0{jD2~eBF<+XYnJ(K4^aSq zYHbb&dCk>TGw>aU{cp7A5qe)g-i5#$(PJ1A6(`j+R1;a=wtuEZFSn*}h!WXl*o*>JFP!HD8@ zl$ujMPn&e939N$e+pZDcG^948FeX?ATs+cq=>E~_ii2|;GE2-o{#h0Qj)Rduwr;4G z+?>ugXnG#tuG4DZT$9QFZXzW)%dMjKztug^E|#;beCUaAz_q(`liJbAGlP1cMQC&7 z@Bl_@Mp(oj84~$MhY85ZKQSqWYzT!fB+%}08Gwq1@}Kq!K@G|0^CIqS{4P-YVDOdZb)*kSoRmMJW8&K#%8P-Dq5s)n zkQ#vC5#K;}g(-I0d;`Oo<*|R%d4S{VN2=!Zj!&Ac-(l(gNhe3ZNWc(j*8%V+Q*xnG zpmi8XL_KmC2tV3N8Fk0*fSRUVf!6=BV4p4|pieQ9WOe_xS~`Z^f*~vK1p{BfglFS{ z*21E7K&L=`)2MWxm$3*^iu_rg`=(*6KvgBeX6*kU+M{$VOD^rq3&~-t6V#60ihj!s z|Mq^JQFJWk+eR-ct#iEjR6e01I>R9S>{9*lH3r3BM*-AD?R^|S+1vZARK}R29Ba5H z-GoD2yS>U>_1>dVf7+kON&;MEG>Rq3t_Eq6`ymm{jef6O~)T`)T*d1@9&957k=2g^G~&# zI(XK~OWEWyB~00TH;G@$5+`pG01HJvagA(|lT!!ay2wXR6maLweD2S?%-QY4OnV7Z&~hjr3m|(%c1J=6J1=e!%Qn`FJmz<~!b0!;1*Q)$QhU8qa%-Hl{P>f*b5;CG zG)nI3R)~TITSv*o87fjwTa98oazK?{_#{!*5`g){F0A=W`Nu$H`JTI}3~$!K3LVzb znejC5$aK|5BZOA*$7Y8W&53zn*N1eA5$sdf*H7{}@B)d$gA zC2FrE`V!geLF$7T5`$yLMkubqKo1LIqneLS9c9#x)U#%%A1kz&i72es*~52mTqdOm zE>)1sp<+1Y{4p$htc}_rv#ufYPftT@H_OLvWcbQ@c0Qe}AkSmdB{u zpX~+a8TLfEjkVUSAT2;(Ni-&DF8O<40NCKe4CbQU^ql zRJ^)_&oRVhdhbkPuKBuhu$rvk?LdePvVxYVZ(7NIMg-F}&`h?uNM?pmHvcKq&V45X zF1V1P0wb6i7~=HrjR;-LZ<1G>X&>ngj}Q%2c1V9$H92e9*Zo|?K+2P>d)VWujA@t+ zBhBb6S_hP#mQD~^mohy>@5RtaI5Av@C^hE}P2HaM&~9<5BX4X7uFqpY z9fF1n+s5*qRmq+behknTk@Za1KoF47iOQo|whz53tp<^!gei-|P4dU}d+TFD!w;26 zY?grjtZ{+1-(Q|YZUuNf+IiMt%2EPmuZb;oO5X?u+n)Mie8-({p0aK7(F^KA2n-0t zJCFD@2V#YO$9YC@BTK(dpeH)gdN>;yll}pSU3&Z<_HfS9gZcK@k%F@ZR`)72Ib|mA zF7AH!=H@>~nd@T`g>$K_n?}t18qs(-}b-=>k^Wj-Yd=np0 z_t9OW;)pQG7kWz-r1_-n^fT&88b!!m4W6Jx4dAQSt4+yN$^sJ61%L^oad?#evSKBk zst+@uBh=tkB2rU_C*<{}xPnb${Ln8KxQgwsCY1up+2%7--k-}!zQJv|gZWH6^2!NJ z)Sy$K{!vR$QOuKPlkP)YT76q8fRZE&X+{Qh#Lg)XPT| zSd>61%y+huXmiFml^R3bc&ByufzIM1-A3(peL4?Nfxva-H&jnOvVh9up9<05v;xg^ zS-&Zxnm8R=?T8nJpIPr+*_g%d-@dlLr^Rl{^O_elw!A(Ev$sXof!`lk7|RH5hnPSp zFKp9a+)jm;VtAMY%o8TrhuOE8a-#e91b2j|YIX~{OvkX1{)0o3E76bsqK6}G4QY2L z-L-6LPN%-y(G9b{UO#@}XUo|>>2w6Y&qC>iQ3!CeBJPX~_{qC&m)O#;f_d0yNREGO z)f1&%M*N>v;}?sA6U1eF)*qb^<}@>T@@y3=d#I6@&GLGa6cOpfK4zBcJ}l-rjnW*q zC{Ab@Sid{&Y2R5seT?gml1e6RpNR_v25hA2i9zIscyKVK;v~}9>6jBfN=!-ised}m z1$m?p?LDx1(m^{*RgG6dy?&zZ`dP0HL*wo)N=0zgQQB-Tu=qiP|kvcz&3dSfp2n!Apc zA!&)s_GFtwiXjR_Yp_6Y-*r0W*imf@7$UY|DMfPZc83EA z*y7`7&g0=$4o1`{nghP9F6WOiqV{JLT3ThD6HTda`3Z?_50+YD;$tnf^QL`B^=zi0 zJ#b0E;=`l1WkH1_LGv`jBIu_&_0e)?bV6ut30g9oHwIMKNROF}fXRusRd0J|d=PR4 z9OP+OCL0)tID1aJ>24djwkA~IK%ooy%-WR+YN`2&n`k)pkxJO2X4)v%4drl&7b0?q z;RJRA8TgEZsvZvwe@$lQ1xF2nsXX&4&OT^Y!%C%%x8hon2)!}N-?%5LQ&6U z#!oDqW|y@1#I-}r8Q@20^E}Npn}I$|HD%iEbh2WcOPhyg5C|Trb5JFM56^&AEp}B47!0 z)>8S9A)yY%)Ym(+a7*W7BXrF8t(Zyj1F=O)^aRaiP`$HsLAOaL&ed225)&^2 z(#lb9_u_wI(R!0b;FBT?W|=vZ7S)5FR1 z$W6B2rdv+#Xn$@QDrBB8Wlx-sO?@HGHD;i}GDlWpYss&-6B*umNW~P{`=P0CT^bq^ zR?5>*U~@~XDEyK9HmW*(i4L=CNxO{o1uQg_5{5e13vI!g5i)G+fZUGfP|K!gwzdeEnzOG5DvlU# zP#m+YFoO`;b6-mk@`e)fiG)(x)c3L)#YM^gWJKmn(~F)m|FL-mH4`JU8%jwx{LId! zn?wgT;h~BNtRb5M2#WHJD8!V%xSot_p;#^9x#^D*9Bn0NTI6CFn2(SnO@xqtT0+Zv_btPRmy(zK|o43@#s|tET0Z~qT2aVhO5C3 zOf0hQ+n3sym3k|g+v6zhtE>982R1ucbVB2ii!7aic=W2Bf2`0VQ%-DyZvnJk6B#`x z69qkXDkxJLRuy|gpnvx>HNB-x_hS#Of)p7@Hx0ZCWo>Yf*ubc`@Ic5CE5VI+$xJs(-JdC=|-ZFGA#z1uPYQ54ni)mPc%Pv6X zrK{wxO|$R_R~kiW;nX~@WlADCtxiF+lD|oM6@S}}-8gu(dTb75~1ty=h?XUvA~1T#TY>S|Ctt`#Zs^I^n5f-GEMZ*__-8oG zJ|G0CIT7Qogm7G5QnHoJFdDR1cl8km$U8z+FMclEB$Hv(Yg{nWx9cBDJ;3~S^ASG1 zZ+^dBpaQ?fxEmn?SJmH zB;Stg$iY(ipW~C!LXtG2{yz^=y`|U^_$cd4@5Erl9WfCl-j*^k;LE zpfyCA0OEo(hminiNwz>cqiol72Z0fKu$ZLazi1 zT-d2~nZr`R8FkOhjY$(IN$Bxmd|zErDQO$}hN+yv1|op(4VO@+krR*;Af{Vums8D3 z`;aRwK&%!v9Gsj+=cqzB?8)Ch;jHWqDaLv6WFDadcj43r>^x3XTK;+NIfus!G`AL( zxL+SE3oZGB^h}&Cv~cc~l``AHw?QyhJ>T|uDIW+?@CbgbpF>{Z9MYash%=yw`TE>o zT=1+zO_18x%%)~zTFm%@Ym}m}d1Ux;u(Z!hE&NCdx6&Oe%{DQ9Fw{XCG^6I;?Sl99N&vLdwL zEiU8@%`x}pqEsC^sF^Hwxd+tuX!bI=jj<4y)(z~8S;@}Z-Wf5=eN`1CsyCG4e5E*) zogWHK^tB~Rkm0kB*-m%84p`ad{!FsFUY$Tf29Z}6L%E2~e$j6OvX#Xo!|BbKL!loP zOcbXunc8GPK>ED%%f`n9nF;{S2&nHQ`YB7-of}aHupv&f_G!LWvjkp!4!c3};NUS8 z57N0i465?`Ty=l=rOb>mZ07B^Ah>pQ!Sbv`NQD~ELt9%y@(eWEY_O!qbBFe4m(-=D ziHb?Znh(8I18U5MxuzJn3B7OI0DD04Qnn3zbwC6w1|3XV>Av)6X~znF4w70(upvPu z>>U!j4Ruh*m3tUTb(epG2p=RCPRqZSjFaNSErsJii)|fZC3uPt>Mh}I?6?aI{;A9m z6#5^cLuC{%8Vlu%*jtU&wQTAaRpbA`=3IU+)f%XkFP*VyLX7w6$l;-jE)+!g7Q|2? z8Go-cuk@G{IL*PNf0TORC!akaXy||w`VtUEKyd8901E+MCp#Tz(ka0QCJg^feG_r) zVq>8h)F?hOLR=3}G2S{>rb@(Sp>=@kkQG>fen7PjCxfRpeTkp7u_!9O281UE^hK}P z3}u~_gjpau_1 z5XxvBIy_!|PG}t3CmejCUz%Ibl75E==cc;8eZ;Vgr~66}=-Cq*%X1n0Kh4!eG`G46 z*qt8S!v-FRx^H`!a7hZ~l{D@D^uX)W>a5oU5@uyQf^P%0I{YXQ8Y6QIAOb)xR}Qt9 zJ%DHqNJN}Hve^ffM9Z~O!csEQQl}P2H-X3Z)wG~h;qh0BFIBS`w;m`iC>{4ls=owa zvb? z4gSybZuc}Dh%Kj@NO?+r9j#@`5Sc+NW%Q6Rig0lCSs!T1t1-TYrGH%&Q<65(h-^Z= zvVqG+MI$~(5>bzcB_D0mb*OW#qLFGyRnNEd9F*~ZjjzTg#*!y6>#C*Xm+~9vzZL?P z9TiL*^dTzf1@9z-UUt(N$ce5n+Bt|uY${U=sPbMAVyCZy7Y0X9CrKlc0x}YJ9X{}O zA!iEO%>e{7RWy^8=_Hcv5^dP6A(*@-Y?|K56p-xVTolOf{W!sf0p$YDB+h4h02Bu8 zAQffAQimRWxwu_jQi3JW=!b86PMJ>m@mm$@cV;IiprOfxy3;uut9_-vS9;v&7mRuV zE8;l>PSaUoVns4}w~wHt;Fl??{?=f^=PK~fh;0fKMpj;<+vLl39{Kb#=d?)A0}fOy zX^7IeBLk_8`aIRbogXe)49nO7!&!r6#qH-Ba^xrbIe_UBavojt|1Xp|(gvCJ1#Yz? zRDokRln8pTeUOr;{viAe18;nU6mVpG|IV2P{)utqoV4=3mYPv|?sU!lEvl))4_?JW zEu#WM{91r+s(6}+AOYqN3i;fLB4Uo!GCH=xS4rg2d(z+C5n(|+eJkTJlXqLt9gd-(Qpm&hHbNYQrg0y%pYxRWsW!!#@DFW{pWDT2e4Wk#$ zt!Cl9>Kk*XtrR;40O+~`pa^R5(UZg(HqtUz8duR%hg-X~Q3I(ha|prE?SvI}EYUx( z9vcv%uV@2FbLC1R!!6`IW#Lv`w!0~RnDNR^s)YiLvoB?ftD{AA?PK1MM941VBybcL zl0DF$Ut*zWI_L()9J=NXgiP&*5?TPG^O1vBXc2<{$X2p@c6UMjQD&wye%DN*_!kX! z45w=CtU(4LC{AHD&D7DQo94`0(7z78BRPC9Fkf7@;g>r8x98iLx!@^=415JaP6)bXqa6i3vWCS zYW*X9wp0ez*Y>gu@BB=X440JXBl1S5((Mdq$DI1I=r9K9W8^%^@Xzs=WzFll^l}(X zwVlmA=5Z;s&~a4YuRfX47|PDKWHOzB%R!MCB{~Z6rv~qR#4En#|L6V?$@_T)0FJWr z3$AXxVcd;9N|85Eu0!D z($P)G2C__SPcY)>D={MrJlgwR^Fs;)9e8CJYIyFYrezclK)nU<-HC6)GIHOwc9YJ${_onK8=7$aY8H53n- zrWu1}(6QKdx{>wE2|}V11%=$pfr-Ho|0{R_8{q!)yT=-+_qK!GU7}kZ&DTTYw};C6 z9`Fv!(9C((geY1SxBtwv?`SEHBGuxuH&@xCC*`z6TvC=g+CQB3}fBk4j87L`Wm8)vT;bj5Jx@ zBtwlx=MZuTLJ3fCjvlLCHhf*7s8?@cQ*7N1Dn7 z2n!8JXPw#zVXaeH*ltSrDrPA0bf-%#+-BNhEjPb`A*}7)gBI(qfc#r;=q$8XGxMDt zS~--md$I*N^8ipSax5^~df&Z>%=nH*oBH|q;X|P0#El|d>@-gl|nPHc~U7j28;d6pk6`?lmz>q zh80{JS00WoG(pqi;nJp}I%B9GD)szsMPqCvGD@far%n62?}zURP6DjD<6-LY9 zA_TQL`k_xqdgsD@R*XdFO9Kq;ED9qXin<|P85=M`M8gmkF>gLP1NTnN3|d-#WGXdq z(6lfU3o;hjOz`XGu-oPD$7Y(@qucicU6rp{7|4cv6~HVn39 zKFB_$!KJNe*Td;mPKI~0aKPPo04n^4f$zT{5Q}6sHcu(6yN`%<6>vwta^D@C>V?2} zg=%~vQk5`tv%A7hT(@MlEt2-7Dgp1bKOxyqi&#ktfZ(#lLVrvqaHJr1c{2OUuS*|_ zAE-qQ{tS$ha~vd&$wl0<6gK)QmtW5#$#Uy|N{+PpAKNAU1|F-4KsTpNR|8OM_@b8L za|Lubodkhw@xAx~%^RKOQegu~e?=TO`Xr?FtgX9O(dtE^v3BxI|3xgm!|AJ4>|Xlw z&?Ei9j{ro~FA;%a&4XJ8jH_9n@fK-Uv}~IP$*m~S_7E}5Umk9nl=aW^P`kiadw*hF zd4Rdw2{A9yfThjvi#HtJ)UmXlvx3DBH&IIw1M3O*z4RseQR8X>986>)7oL$vaR-4pk40uJc9z0f#b89{_+!-BIRyZ9Wa1`0 zrHhx=K!=^Sf1O3;)tmO1cL#u~yIhi2&`233k9Qrf#~!SQU27OxrsXI9e17@a-!}@w zB+|QR&d;)H&)eT-cEW3K$qo+p8YpVfmupy?v)`{7aN>j;)}FQj#7eUbZBhV`3L7by z!RfP4qU->M!GJtH+R$N5{`@Gh20MVjYV(2Mp!`SJdVuSQdK%G8HMvE&oWiI{|QcZywA8DG=1>-zjfbz#cS+UU5@rV_o@G&Bdm~lr*mon2m zD!kkJpz^yI8Sh((KSQgiy&G_^PI7PkeIA16@Pd0cX6>r&Y8YrTjVj~4%_zEf+^1|5 z(0=7f2gUEEB!_81*TYSG

yFK7OyGJ?9qPlg-0jp|5Mmz##MNY^?R>RuO?A@)Zp}f6T?h!@YWq)mmVdot!)=K6X`=;wvZ^V{vusHIGX5 z@@+%>Boo=&$7UPJNr&mse9D=LVrM_Mrl)oS|FlVC6qkHxds~Fz) zJkz`(NrhTX_;GLZ6?%+X4YvA|foAeAL-wt&DlAge|CsYa*I?E#}St{i?4YF5K}R!-`SPs`)bEesK15uWTe2WygNBhAtV= zc^>U|D6W8>kP=pduO46RFK|MR%dGtX^Worxd25j*jmkoY%FNrpDsxV?TV4w41N^Mcd@-=0^f71Y;mQMXWzvL4Rtk#QpO%rk+Tv404b$iwu+p(;dCVnFtN zD|@7e@5%~c!V+VQYxrK^5YPD~9a}+h?E|+0b&PFZ7n4FCqJ9Yl_(=jDw@<2y{Q<2! znv3p(f1i=Gn`&TROV(;IDyy=t{@^|qPrPu`y?gPup)z-a`LqandQ7{Q<{O9Y7F=#u zA{#yvhgy3O6|IY22|#S>j7cfN0|68mp-pK&jv5RF9O(nofjDyE5r%0v~LX1-D$0tvBQJZQ;; z&fPc{PT_eTvisUgh~)MvY`|2f54p?}CmaK2mhh<4gsEI=(;9;(`I^s^E**}#p%a%4 z{0(di;vI|AlP}w{x`4hw*3cSP5cCc9*8*RBV`N1^&!e{kzUnJ&w$J=GKwk^D^$-R&<)z z#4H>TCt*z~l!-fPqh3?s5`CpCn9GU;RV8E%;>3NsG8>#*$^Th;KO>Di)d~ktTFM(2 z=ovw(->J?oZT$ual-FRWp6v^%>$)Z=X@>|10E1{qL&ZRJiA^Oy3FNUGh4cO_7ODt- z*`k3%vU_|{)ZJFSYt##DHw$8<$&NH^vi$TO{#5-|=P##EMus6!|=a$)odDkO6X(4J_t@Ic@ zRI;O1wuLPV-oDe0K~Ih01=)nOJBFE@pfq561XjFThRReB#_I%G<-WGDAkKz7T5j=# z77;WemZzsQ<-AkxUr9|!=2a!@(iN0A0H21qC_I?_uDP$^vU<4?3n z4|Xwt(Hn#4g)XatGNIx`ir6Q;rA<$Hjo4Gu0}vV9jQpEj2iiKgLU>;QPnK7x%7B8l zBnvH6JwU@rE-&BdETiyJg6z_ue;Y1z>R;Sk_GR|!7I((ep9%2&=c7j5;PIPUXPwojfSk-R}c{}^SH z&QzGLd+bpG@Hwj!VdBUB#l!Jj zba0+18*e(X1DHZzIDT3NI9jT9l+yKBCVbE82>la&NVV(1-i+FPpu{AlrIxp6u0kjn zhxmrpzVBW?SuAp+B~o15NEZ+ds^ak6$*8=T0YhZ5sf|Ws+jB`%{yWbA@<}nxU5A@r zymeCADjl*ED(dCLOA5kR5p{OT0T+)u*g^n7B|{u{Cge!dT&4+>>|J{oI>2A5ha>v9 z@j|DKH;$=>Q^jkzk!Bxd?~8gexUxd;_6FbWVHO_@W;7#Xbn%+uSNP?W*G!QAAy@L$ zWdj_3{7Cy#h&8Xvj7uF-(Q*KC4UwM_2w?p#6osd*f&?NE8R4`B#;N-hK^}*dY3$n- zP{aXkz#5vsKNBi{jCp3fJbbya*Ed1|GFj#(5)Ey6adoygER7FR1DY+Hjm-YqH5GMO ziGHs%^Xw#PMTn;z?jG&&O^duFNQU4C4`x+g3nYR9ijSFOoC3@xgi^)rE*Xr@My!vt zG#XdQ)|qFBM^PE2CGX`WDDkHOBh*A431kt2%dYoC`>o5r6&D;NTDLY6)<*W!fk1!i zilcv8)&NRKBco%99*vN=o(^(oQv=_F3r{@l&SRJ(=F#}(ki$Znl7q!4XxAQ5h14bT z1Q9!I-`5zrFMPS-UYxrzzb=kTE5FB6we4e?auI}Yk6^gpC=Z~}=UZJULG1UBx_)fKKz`%iDti%xBvGQ3@b(i@uQ15}>>p)a2Gcw{T<^hOe4YCDh_vBET}yLn+R z8LWPSY8qJiDy5itHGU>Xg!!{`dBpOhIF=DoRi-L<$HR$k- zlC{{9KhsJCONLVIk2U(DX1}J3;<^ytp_%p=O{fx|zf*Eu=`MHTEoD+Yop<0<7$!OV zea7}dA@W^Vk#6(|XYYeaGBp@WX3oQ&xV+;mzd&ih^whoRHu$ISg2c3TaC5#68TFjL zWPT2lvJu1c{=d>`CbN|ZyW_X2hUBiOoRAifFg44=+g{B)qcm@|jGi(bEcIJ%-G zO3>#t*3)r7#xQaWEBK>s$^A3#NB8lDqh`fxnXz=I^!qB8$E%6z0J*X`@}vbq_Z%SU zp@<~J%aMi=C^dzzQRxm6pzEh(uKDN~9b=0=q#wxrFtI-K-GU0E#ID_EY@GU@4uvM1 zUqn3!xf_ZTa)OccpOe$7+mA+CIr2?hV;gp5bP$!ox-hd!igP{KoUmCfy2c@uvMy3K zHK(Ruvaag2(2XO=T}BbzpaIY3F`C-w_{%ce-Ip0;6m$T~RB%X;43< z&S~Adl_{u?`x(=tjeS1=E4|VpeeViqE0d(dsO`D*BM_d%(4KY3jbV!g32DHBMsYqz zW1fVP+Zj!<1AUn!bCu0by`FLGKqT*9JVXTbO6J5FA07(ULsBrqd;op6vC-KMgd$!> zC!Wr0QxEWQ2=CTI8 zp7hQa=+5SQI8Sh$8Y2KHkLBM$#G$IQG&pL!T4oN&WKYi6>=*3UNSVj*> zT`aYN_$+sT#m+bGte;+Pcxsc>sU|Nw)_))L5das@arBG{uTv!Cb>A0ik6)XYoS5=> zMO=9N4Q}>`TjgZD^v-yvcdqMg8=n==O6w^b#NR&%1C4=5&8+as^SZCw`3^%Wq4*a$ z1Xp&c_B+BXvWR5ZrQ=W+i0-A8ljTm)~DoZl=wdFE@wve%X1di14DN8hM1fDYixAnhet;uLy0`@R5 zaVB-2pgT2yOGFv-sfo_n{blcN`wOy1r1kf_91f)xT+{f*{#1$H{lu_|e#zFPf;U$= z6kJ^uta4puc9}3tb;4h5*tmwHXO!%#6TcYt9vz zCV#M~G_)^h4}VFo^o$ZB6Xu@x<+sZSkGo)2kMlkogs1jwAPJTc3G6F|0t}Kg1uVO9 z#Vx=Xa9mhs83W0Bw(sF>Z%CKFG78H8Y>9r)bCDUlFGN?^S^*!P$*yq- zz)TXOv%*Hk!jYH!oDZ%_8%0P%5wcP5m&JHFykgW?bTF&ilrFasZY$t9xSM3oruXhZ zNW`NX(`4@8z=YwfF=F>`!JbIh!Y3ln3%hvh*4Wb~C&I|~@h=^;{pjb6nU0&IHuk4O zt0Kfawh%DgUO+4y;>vNG52k_VgDU5=8Am~>JV=Kc!Q(v`IiFehQ7`bW(a~N)<95n% z&8Rirza>YlzGF3MFNVTsaI8}Yd+j`LxdHa}K0}M~jpOX9Tp1=Wrn%Cx;yI%cGof%=Nh=7)?5vj% zaP*b3U`w_3|0D1xS_Ci7Rxmlt3|*a=Y#X^VLXdI?Fji#_yhc_^1jd&BX}!t?+mn& zr|O^ksc-gS3>B%)jXR$nNjQm2E8RX1E|l*-mSkoV$j^?Z&rjPvze4 zmZixliFOk$!{inZ-%Mr2-A*mwv5aS(mlaHbJ8)76b2{m1jO)aj3~Y!|M#h8~5o2G1 zMY>Rj^ZH|6;W2m~$bx6*=A5aB1yzr+Nv_BV>oT}D`D2hz8)WJ1gwy>B?a}IO7)`Pe z6pDwi(H`)HxoNzWtmA3kSD!Z*j6<79#g1wbZA(nro!Iw#%*|+8a`a|JB z%OW7+yQ?~S$-x=5m^kKyq63=D!ECk5T{jatW77sgqw6E6MuzL^S4sC8J3Mw*r+WUh zM1d`>{BKBSvwNd|lO#?x`xoIR2W^@uOy%Xp7hn5l46j8z22q}phnUzomMp0|$`Qa{ zhMOOQ<|<-r^}C{8w$C+D982J4=GY0W3GbsJ6@d`zsTkF{0wr+&X4=V{-@0CV=RjmX zCZnnM1V-^3ExqK3p^?tN2V;ls!{{g%9mLy6LwOXxww*gKGu2JXtP?H(A|6LHYpQgV z&sqasD-|l6I{69zyL_+KMcy$>ftry-VP>!raW z-a<8lSuf_Q@RhxUn%6!S3BztvAX@?umsmS}3k~nb8|&JxeqVg0q|;f3Is+QUb7^X1eT)eqUai2%RKDx(+^mvF!_2 ziDHfuN0vO4S}AE~h$?H<(xY3A_|d!4Ef_@8uGAa$k@WAWGv;+-l&6#;q26(VdCZ?T z@Cl0U->#N}CE#;3WqW+~x~yRBfo%>><$8R?`adLgPo zg11F(jQq_BU|O!a=dB#vkJ0jA;XnwVQV`dz@fm9epz4Ab$&};I2gO0OS$q%DOJ6oRGiqu zLlsmL%tz9rog`J?n;)-TgGP^Wv$z4HB%>|gMn9h{ucDz@jO=ZOs zVm~;srkQ|Fg`-cZ(zL>Bfhb-XQ0eMkYz32Fg6M+a-Y_P>e+V%uxLkwd+)C~`PD2X& zVLc7a-6LQbHop-$CEDSnF$n)2keRAp;I4~9{FwlS6U$d0{n*Ol@e@Nw!;5#Nl?nN_85( znLoqVHzDkekw?RgiLz{iY8 zU@7`k3I=alRj>ofEDjV90re~Uq%ib^;WIM2ViVruaeYGKrg5aGy{J)?FfumI&Cp0sDy zf`Mz#rnT0OiznL$Lh=vNjodFfAI}6M1a&M^>V#nv%xuk9Tzzn&^e+e)AF*4Kjd1X%6tKlRh935-fFw|s z{8P!OpWLI97CY%Z06piwxqq75Hbe=pRl>vIw4n=d7C4#9llqSAz6-hOmORY}Cizn7 zPAs*%%3m*dSk28n>_`L62wpI<3+=sBCrT9x5qd@%8%V)p3&kdz*q8oz(Wzx%V47OnN(h*tH#5L-qyoGB%?(|_9*O_ zg!IvR8p^9@#-6zc*L1Fis@MIACh*~TlfaLlHgD?fraCv5YL*;?yi^ic9ybj*)lFlm zF{&HCP2cAH)0u&KWz*x+&%dlXKDhkec38V9ll$4G0c_u75!LpB&D){0<010N;;HW_ zvDqoMh?8P^EFxdz6LbK#oH2~O3OpE`gA9aFU}246pIyZM^O<}@gd?L$M#IYSd?g5+ zpDV(6Vk@BUI(5!8rjf9t$e7!Mc`|(C?LETziEWTa3$=yi;b~Pj-U)F+p}Fmm;@`IV zz|N;<%-d0Te>FW~LG03P4*$E5Zox?gaI%8l54UdvU(K^yG%QC0q3w`sS&*O=V55B; z%_u*ZS`D7lSC$Or)$6E<^fdT+5b3fEi5a}=KnjXM5=j$)`JE;AO=K*$u0jG+MaH7y zRXEZ~5`)$hFF!5OHy81dic<3B^qUzSWB7o}nSd$OFo(HzgfiG;gmZdnu3n&>ef1NCcQ#!-W)VSZ21B;K~>3YTB3Xk8D%Q}S9S5*gw5754K#JVmNB z2)bNDKGG@ww7ZFLrUz3@NIQ?}OYdi2;9Xjj&k@S{bW$j(C~C71NU_^~{qg4>?TvGH zZ+H~NUEXFvggtCoH{G1?=uRQuSWaQXcJyRSo1Kz>cP@*dzGY1X*^!pCI}>tR5v124%by!fY`pJr}NZ zw?lg0@9_~eSE4EGUU&iUy3bR9b;F{?qZP7}Iu$`P2FKEM-Vn)!&XnEmq724BRFs$% zCIeSJ&g|1WIIkwwCm382^-!mi4bYhi6L2dHAA^cWS1wcU`**nwRNkkWu4f!QHy|d6 zz$B~}YbA&C(hCgbX&wp>odiJT8FQLt$S#j-fgC(oQz$I`M0QAcmI~{jQnIJ68Oh*| z;Gp=PBT4na9c9JoJI*a}Wjh=BzL-_QE|j+MmQ>Axf%Kx^Rieih@qPt(5mBTZ<3M$8=y#_}3j4{9m<)$qW`F7XTLPP2XSeSktYB26 z0u<`=sC(*XZ)W9lF35U$3)Y8ghb#+VF#){<3j&4$@R+F}uM5oWulsZC!0yKM|7nlx z&CM>Rs>u_}QOmAkQri}F7*8~jli|avH^8{`Y92NfvFARXRZ-?A1jxSO-;ebw{|(^p z+>BalX#d8O3P)5?wsAfIu}6;qk+v_ZpU+(^i|(HDptl1IAPUlJ+C=$jrQqUZXvtno zL)zIm<;*Uq1wqKoY@jdG4Uh8?joUPeczKvj*t9l#JZ^2n{Ou z;k){!yB?bFwph&+Q+~d=Et{!wI^17=R85*qh3)uG)!>CXUi^ca1%c-=8p*r4jkkZ7 znuFLe4`n{XUwPGbc5itM$XWC@dhHyK1z43c|DD-|df8In3R{y_Ber^j_M|)MQ!o6&}|lT4rX_do#i@yQ;@?QB~(_Q zwyT2^l@~@cI?EX62sn|VD-q>S_*X&9M#0M=?z#yTxIDB6Ca!w3T7aHB;fMi=3Js+x zV;EX@RNHHsxJ9eSfj@8lSugJuBmva=r_Yu@WEVkr?vEZIBc>KQEPD@ zz!Wv7{$LxYhk!>6m0KIq=8rP?Zt#}#^QCg$dHrnaawz@>+j1YIUMew=7pvOvh=?0% zrpbFQExA9e!FChDlM(YK{B}i0C=uI{`d+Y*_6yG+qrEkhqz9c>F7oZf!PC?Q68&OL zF$aV>Z4*3>K;luYhja7dJfU7qLaMW@j1>C+=i1IJ3WN}V(zq(}d#vYq;9Ngl5PXsh_cWGQfmILX0 zRf(AyfDo7K2mB=h#}aUa*_tW>xKG>$bSEagq=t^qC)aBnc3t0- zM^-;f>EPM8Y-x7b0`!Fo z`WW{&tBxvh>KFMqY$rN0gs8Pimhp3H^HS53(^(71aY`+( z{49-+j!_>#oOj%8;(6II^;tT9_D*^y*15b`n$i4?KuVnY8ESs2-wGeGEXu!ZMAene zs)y;TEPbOynE-G${mQzOc1Ruu^Du!!wD=At@Ac3Nr6_N<>e$2psdWjrIqRHCj!?}6 zeDR$#&uWQKy9l^5bcs^$R6|Ps;e~D1pm`JEaa%F#x&TE$y1yqIr`_g9C3%G<9A^7> zn?M^VQ`ztQQD*3f=2AzIdp>OUP!C>C+1~b}Jh-EJqUVmw&i6-eO2LYX0vcHAFmuA* zUxBxMu{ew}_Ur-yxj_oOQT$*46mX31jcrgkKRj*LJNnAK&MerHOb!n#`L6wX%AVkh z3|BIuBwU@(?v}eHCa04LcwKWgfeDfGUVP%vZSqC(vB?Tph5NU^Pu)D8Lo{6zx&LjmDC8U(^X&Qp7EewtDb^)mY5vbHQ-36_KZY$M`o-uY2 z_}xw&!@=uObpAb|&n;`4VP4mkw&L}iLaiYT{-X8?J`n5uuJLzIK~WWvbGa0Rzylhl5Z<~lkYmdvlRazM_9 zUeE&djkTZA$yuUUsjVlX8e5Q(&FJ_7Xmz?pByRaU`q5`ZG2AIGv3;z&=VRJ(ivwAG zmq6>ncJTiaPMZmgzgA|7jM-hWgtxrW5t84>M!3O!negD}N ziNca(&bFW^Q}jT8=T1&F;_*%l{Dt*BOvHtymWCYwQ^6tOPyGeJGe8eTV&OT&*YoTJKKE&-~4o zt<=yS?-E@a1E~N_0f8UJywG>bn_mepc%4Nfqv~s|qB?hRkg=m~&ex@V3}MckMbyLr z%@||g!S5Par}~fOb~1SYCue#(Cm+!7+_$z1XsAf~t_0>=zXDymd*-2ovf)g8*;^J# zKfb;;DwXhIGNN7qsgf@7C@A!9iCuWqRyqeWkS761qbsHhn>#lbI2hlSLSPpLyuPAj z7nbfm;Qg6a_{fIH-!zpm7dIiShHxKVwlD z!bIMwT83sx9O&H3OUNxCq|~q6T+ZpNsJWzq>oxZ&3k?n1RUri7E>89H?T%_v;6j7} zCX1cPb8VUt*zA+|F>XUpa37laM%E?(*DV*i3|yZMP6kF|J!&R{9w4M~k{FshCIgAf z3+SX=T#0+=qjdSHVB_Sl7OY;u5WT+oZE%@%(TSxKzo0G;K zS;6wfC15HIrqbaLo@62E#z%@(P0`Q8r5R(DM+r(Om7Slh&1G=QpcTYgn5k|darrQN zn}LD>R#+HIWiRq}A}Gvy-HJgv*g?$bNqh4?`7~BNl4GMY=Y}jPv!?G*FI6H+-x6iF zI^OhYGgnSs>FJPsuug=S6S1g1;F#XEYyP)Z3*zwYOlCR6n95V z--X7Am2nMvh~Vkr!JFE z?dwqEfTS2IK0ezNg?7&8lXaCV3z`pe%MH^|&y?(%AE~2iCT%T2FvPEVNG#_-V5KZ0 z6N&!Yd94c5_Y%KI4$5uB?%4c(E; z4#x8m3OSDu)$fo8ZSs{28qAmkWU*}oqfA$JVulifgh)RHM?ke8{*0GxU1|l-Y6eCL zq{8l^5J?ZBt19fs`PP1a6 zWL7{5GN1wc?K}clbd+^rh}$Guq3YS>OqAy+G<7+hW~=87Ho)uR?5_If55Y`Cj;VlO z;|slKbEVo{V)~VvGJ%csKGaO~n}O&h@NPx6&BX%7v4QaX;@x&Bx9BhYK0hLMkSzOD z)*~71_{R9~PaH+Fmg($xi_xjUH~8v-begz=V$+6_--$S{X` zB;11t0DMFI9R!ZhWyMs-dUMkg;y^2vH=+uof-ddqA+}@nb~3|7vlyQ^q{TxmdM}k$ zzI^rZ-(K+FkN%bhx52S8IL!`T%m3I^QlQ2E6_1nc`9D2^H#I$dKMCGc;OyatKM^F5 zyDP)Y8djetL9(%oP3vfsO@p{igSe;!NZTPbsPb(~CgN3d!+G+t@`yqV&}Nr(Zl936qRr~Ma=EiV1vkbO2&Jq|s+Nd6ns z7)#Hlcm}mi%$Qu8P#g7BYYh+tCIy9vF<5+FKN|Sdq%5?^_W_$=B)26nrKQ$WR^12o zpY{vStub-qg?$#JT-rhBjC7`cmgi;*9V=5#bZI~?R$QC#9W}lJ6#gJFBT1{*vx)G; zDm7t=6Vt~EPtT^#Hglo2@3)k6As8XF(9JvhgncPh5r#n_#h{s1aJk|wQVBjvC!^9} zHZ1ExTX7ZtmeOGjZyV5T0s^RZRpz8oc-Ua$O0u)fI7FBD;qk7p3KoDtC!xNE#DsIS zMQT^I8(zn~DY+IDANmhRo@)t^4Aog2L?v*RsGzGJ9BX$i4!Y}lWvDpZMPNR0*3}g` zrsvc~But3jf)d}CkV>wz#u^aZGo%7H>ttcaMe8GHXsSR*;zC$3W3x)WVJJ&Mx@BPG zk&`_VFf&U9?qo_NeHeV@CV+z|nnaeuvJi^Rgg!ss3K%eNwetFFL|9PmGp-B#1y~8^ z{f*cROx`}cF33Wf^GTL24}KvNj80wCOvX$k&z!>ou?rde^@y#=fK<|0(xSzFkOyks z&)0b%R}%K={HS1#08S?61hwFQDl z9oM%QYn7LRTftEDB8JTy?lK5CPZFtTqRF)tp}mBLglYcaT)edLYHoapVmCOekSPmFjJDXdPO7aPtHga>IZv#h)v#uL+cFbTBB9{8*g|@zK z{Gs4$sSGP0#sMSC9ck|p#spbQ>~P{#k+puwMq*&aTGJQl8GYIEjJL>PLr>ua0eNT> zkPJsG;OvqtNA;nR8E8%zQ>-yTf(B~RnNs|9=0R=VJY@m5;Iby!0I5WRsxY#hrN+Wt z-to!w1B0vKIfl7Q-~ZeRPkhf|TW*4t+=F=IxJN8Vsv9DP8TiwxE>hamb}w<-#$xW~ z2kuBiFy?3<1Vn}zlEfi~pOR zt|S6~`rkKYrvbn4{}8QLzjnI|gD`)X(VqC4ubfk@!~_BW?(>9;=VCeE9sa*#0X@TZ z>|uGSEaM%8(G7)_F)d^w#_Sw2-?w_~8n6H~k~d0S;)N)K=6vUg4M15Up#!t!vWoCg zlk8IkI6=f0?ofgs?kB)Eo_Rs}@o87Y+LoR6{#skd&)=fiZ=xYW0rj2~xL;1uC=P06 zCCKoC4wWCEh)PK`pGv#t2_A=r=qZ_2vvIDP-&4`Lu)W$IXD%!G-4)66RG{WoW3x6D zKs6Tx?Mlwucy}#GvIRE=x6kc`nE}`j*QueD4vrKcir)JyOphmJ4FJz#E9-lGo~?jP zD8A4FBj@$0Vc(9DDfEBJi6;(_LTE57a~a34)Q|*uWN_SH9JmvkRTyx#PtT$w78ZwqM0KAO zsbF5&#tVT7>UYr^4pLfTpa#4kj7tD?(7gLqX`#501wGSy7b;sQ^{Y7W_bqx7eaw?! z#ROKmD6AXZQ!r{Gz;4uNHhxF;SLMF0$huN<8cJJ``wmyG&TqL$S`LOLIC6u73}|r+ z5QUlaP422tOdgjtdO%E#G*#tiffl_-|4)9~*TL*r5i(-0MFTJaAe{1Y9nx7A5ip z8HKxg2TOIwkZ?%)Vcg2tyy7r%&#hX1muysz-ns7pUJSHEBL zZxUE#)UJ)jwlL@&q?->us5AA}tCAGqQpKy~%aE(Y_EFXWVVQ)%q0@?7y90}pZ+t9- z5-HXv*;w-ZY&*}v+czQa+lVvva!i}21vnq`r!dIMQz@4nSwHS^h0;rp-jPk4>-PF* z`>39`>|h90-j;YY%mC@S+DX%$j41dAhL}VW>tSJd-F2iA{X@-#UW-ftzh7K5TU#% zIZ1rx)EsXJ6`95gR?s}b#Uk$=%63*=Av0$k*LOLd#N(sw`0XKQB4Ou9Plw_*EmDc~ z=-_)uw%T~0#1>h-C*z6#7l^}1spq4sUSoKkRT&!uj|Ay(BI3Iue4u$evV4EWkNKX)KWZsxyOqOQN3Zg<+DDMp9vrj@(U` zYDKZoA+m8DR+;_GRuQ}j-pm&L~^R>H@{I0`Ex z(Z{+Q8Ws|z+A)|w$B?Eq;K;42YSixsjhzbg7gHc>Q{{;g7TK5l;C#JXFeT&i#^N$9H~qd<9ROqob`Q zF3H(~qE|dC#GH!1YM()w~fX7vvR)JQOqOD!w(-tA3%96&I!N!1NN5^>Co~)k(f@ zkL%$ZKbA*FmkRj+nSaT-d_dxxSZKAg!L?p_ld7Tr^{n?}ch&HL0AtVE+l+Cn{y%l2 zJnb;-Pc>i49*iR7@9^Nz*II`+{Md<%u>0H^*t(NAr=EXbY9 zBbo;FNDq8As^U`un#kySVP!2JhrH@mT}woCg=EMm;6vg{ckRpEvKIB?I=aAeKk3#( zOo2#M*rHzT7BNF?0BY7-%-oKHR?FdiDu zeu<;a_gokk_;GSRjw=U zN&9WT8W))fWo8vU0ESt4e(1(G=cGyp^0wITF6?-c!0%`TX`8lKYmn{)=EzHhMPvyN9KCs>R)@*7 z95QSQ;X$ZIOP<-k!HF)#F559uege3eVVuJs>St+>OVP=MsDjb|!!SryJs-#QB|G8u zJJ56QopX24WL{s8_%bMKCDDueItpjRW*=bOqF8v!V`0ZJ12HmACnE56Jw1*0ilY}+ z*^215QrOGT8#~SXY&u;zfWk=nwvmVC1dShlBY7)^{AG9UV~O)10V~zjn|+1ltYR`( zU!a~=q7&bhJqvuBaYr=(2)Ym&IhkCi010Ve=lcWLddEp8f##p$NyDDP^%~!m6H>HpAiuiDm-UD`SZe~T7LP@nAbW^%2CGb ziJA}))5G^r1s0Ye+$qQm#`w}GEYuFz)Nv9H(U!<|X&-M<>%SNRf+Z`B0Pe=rp;0G& z(D{D6km!K^eRgjWG~qbEGN5Ym>Wy8<^DJm%D5s~y)LDo5f$OH)vA*5w$WC1vM8>i? zh8V;7z-9(df)6HuckM{t)&Jxxl(~*g>#v{h=;HX+rhOtVX(7Z$PSk{}Yr3T0Z)#n@ z!E)77(OQPyA`2+{5=Q!IF>n!jVkYwX;YHKDk(AVu-2sStyX-SCj`A3KlpSiT<#MH4G&Fui z6P0jB;H6rwlJ)tC?6pe%)^3*AHKGCBaBVG{>QS%+eT*(2fa9+NzIvzZ_8{LOD*p9a zFswk%Aa5v1HuAZ-yH^&SuzxblsDF8)38DC2TkgmrHB|S7XU!?~a>|oB` zy>Fm^a{%2Y96#ozoUwKg0o0x{Mp75|iP62e2b!g_)YBZ$Pc-r*7KQz7px%ez-CIlIEHD`k)=n`}ZlqxTIILx}n339^g~=6R zg;#&(tpmp;Nd$~u@p=yPn^^iOFS%r7&1C42zl0AT&l|)eiEeLp<#M}t@dcr&HCg@) zM3_}oT#*sd=FzCSX}srY+Ksi4Xg(mQbUUvv&wt4ZL`dygImm8|L z9v1ZAP#N425zCB*nGHBi7Ca1P(1e~m+p`@%ccg$%y+9Ajn5~r+OD0Q%o4evC=jr@n zz!cJ$V!3%iWGNkWJP@MxWxVwB8i01VuS`Q2jES230vehe8{wT$-u}p$@ysz$^!JB2 z$;og(k!DCQ2bK^-uGY>4Afw;r-7MBaH1Yur%fsESzL38paFipRhUF1n-=)D4 z30)Bh4!y5NGply>b!BTg>H>JfY1=qm_{s!SgbxMHU}}%8joiaF#dmwfcT`lc*1co# zr=R(%N&SB9LhYINL%;uaABg7mI`Bd_RO7DNT`|KiX-4keIGxepy(am-;h2p!d_3(Q zhuPLHNyscs3DmfPz|sZP3O>P;naD}b)F74^`6v<1(X?7Kut47_JH46mS>@{)k0@ae%n zh*?L5A~%&(^D#&PE(xsGN-!2d%Ql%sMI*o=f*nrCVmvXlR8<`2^^}{-5*$N__rY@| zb}egjJs>dr1U&ibB^lwWWCvDfICmOLCt!8y?@Bhat=%F?{Y*3 z1E*y5scNeptch^mPErE@UTM%C0PjK2XTle;Tm(RZcG-{s_Le&{RvpgYguS@PFK=D$ z{3iIlMFT$=mEH_h@F3m?>`I&Ggu|@0BL|)`G4TbPyX>qGaeuyR=tNkKy&DSeNC#=cwG$Y zE$b^Y3WOT+>wh!~IwB;RT8(b8eSLn6;wT-oBlKgy&GSqZ6<{!(>+B%WX>Z}^P9dQ1 z%!#~M-{P3b5P7~PZ@*(}&( zl|+at*y7Aj!CVrMIbs;N8XI+qkP(2^`)WAbWtM7!@>}15ga?WeSO}hL@Z&p=bBMVM81U6{>{ zf*3?KumN&x3BKO-sd4` zA_68bA_~}ttRuWYrIpQ;qh92`L$t58N8T8y6Y;?NBEm>DoLfV5M0CcSrVGVcE7A-` zO@i?*Scyx^dfuHGZUSwsQH1%xcm9S9c|m`rMv4P^KRy~7fpmU2qIbazhe>(}+}az! zij79^VN4?7%NS}WKy$-pA1oEw>s|mh#)e~(A%{1e}5Ccb7*Ubsex`A+W$M1*k1Ma8rnD zvV+}>H%-+Og5z&(6w#;W$>XqP;^CRVUzKEt>&)J2?}v3(SCnz;g!S7KJj;lPh${ZN z)nua4e1K{rKa|(?k#EeRSpjd9 zfV)+ks{}n~+Jz2}GhG}_B}KXy0}TqOSv96=8(?K)6pLwFekGLtEaI?l(81FfNgaS% zJ~3Bw{;$Wr#*X3+C%80m1DCqZtITvGnX-?@9)A zHqzRrEJKT&?6hQobRCQY%f(%*-O#X}%q!2Lc4YQ?no0{jQf} zq5?pU6uUT=ky%72OM9)V=&JD{FdUA>5wT@3uF3e+KfBDG9_?Rdx`x&qy2e}v(!;-F z)vvsC$|82mMzweatQ_?LUW`}MK;68DERF{7|wda>KlYx!7rJGtI-MwUGL8r2jH(w$G5Shr&I zu6rVW926Ms(Ezi{X>+r3Z}VblMTXdNe&ZZ9{vKbTRE9?~3%&`7$MLPC2@;Th3L^h+ zr6IrC-=MRmCk+JZf`>Dy2c0HCGR=A`cpqGAZTCJ<`5{}7+l;Tj3idisnQ|W#!*PE* zIrr`aWM9Tsi_$BN;%dN<+p=RKjjZhhq=j-C^e^gyl~qo~;_z{I+QkGwIJ#mP4qgvK z3~1=jDy>eO?SpH0Vz1>QefVK5UcwCU`3%{h_`4A~o?uxe09%c$=tV`dd=DueCxvzi zPx-XYz5P(r)_|$nE*lgnn2YW$Tu9mn7!E6;MTEdpmojs2_Y3V~Xer}T>nHxi=2mbc zob9fpbeo4ObT&l``1r;XyiTbT1dOqmz*G_mX!3EY7XM|t z#Is*opg5{W_*2iUlJ2&BChk7p(SnouUiBUM zrQ7<6fpAgEZHzr)%(o3>nVUN1R659qe{waJ50sHGlS=>Jf!a68EjvwnCgO|dti zmpbYH1srum+FY(2m5sAYL*Q-7oN^(|ka-G9TOkjuh30L>5C6q36lWgB09vsT(bvWD z;=qZ3QRrk#rn`Llz{8c|TqIptABXoY+k&GDf|KkX;~ADh_B+xAGIt=p{eO->-xum& zRq+Mic<_TPx^EW(td2jjeHz9eS}9|Oim=t-b5V9}pR1Wlo(nJ~X>}$T(h#LPxgfQ# z_ImMhjy^{(+&oLRCyU*oA0^H1#Jtn1{NEs@@}{xa2>sy(Ak7k9&?-Oia2dZfr+CfJ z-)clz94-icZ5}mYyaZu@BWaraWbKahcF*w7Ei-UJ(IulAK;YBYB<7dR!exC9pl$w**lz+ z*LS6%8B^=fwh+@RHLMDj)L_rEJ*aCwXe*cD(tC7IU_r1iP6sQ79xECWj1d%vU3!qA zkMZ_Qk&Y)cCD3#c-Swbkf&m~)MLKJ|52#1C-MXQxo(ED0`O_8@2t`m)iAhNZ4;B_0 zM|mT-A~71Lk4PgmKZ&qj=NgDzZt{`Mf{3ae^DiXg)Pcj}elm<=9kK`US#Z@_L-94Q zoP$`JCuW`bTcpj^l6{Dk!GvI8z_0!A5gVl$@h>5yN9WQnEYb%h2q7iGK&{NRAS-Q~K&iQ3{|9oT+#K5Gqv_w#oOSeN- zR>&22P|aUvvo@)UaJ0Igx&ecMNzX#i zZfm=ylW7W8x;o&5Tg^skc!LE!jYC;V|NbdG=&Zs60UE3Qk5PQUoZyOJg=6HNp~EKi zjsMXLf3eS^l56 zN*tV0+8qrrHlIupvDr)_5@JPe*+@cg;RUYaQsFftL?bD(ul%TRZo7$r`k{=3I%OX9x$-Hcu0QR9NE3E%C#WE!N#7#=ioB zpmj*dvBNp@Krk$ZXk#Lts0X)GzDLk`^a?otBIpMN{Z#GM__b)VtnbJ7Mr|zI=A?nS zcyHqEt_bOTgB`Z3BOTxW$;9$MIjTuo9oxiz{iHX6*h-#7c zMFIHUAD?<$B?a&-Mzi50E@l1!9*l(q=!&zbh3Ri>8dy|Vz9Y%zoHd$kr_ME6FiDV9 zb`P07Hz^n-rB-oAZrJKr7abZkC;<+IvPK`oQt}lm5uW+ZIeeJr>|-e|(Mm}J)Py9$ zOSe~8-u3T5^IM>?QaGO|$GCBb%mw{C*Nk!XUB7w(?E((hrX+XBq`$maS_@+_?Rs+M z9xiC~Pnc*Pwwq7?pEDvm2gl2)KGbnMTMiBeg2Fm+hS})JxzCbL{O}&4HFJ7Xz>6R2 zh5TJbHNQAkHrOTwH{@ogh#EWQY^P_8E~-FAlJ$)A-d@=z86B8`YG7s-l6Pb1uZ(Le z&{p0JR2^)*O5`ZrrK~CMvA!YJ?*6Qp=hL4s#PpZlf>r=v4XvZ$Y?Y+oekvF)2o*vaR_E0#jKx5lS9H)Ui z_@s|3##3nhELmIgMC4Jlmh$#n;;&*c?d_r%za*rhp-1_psZpjEz{CO4Xo@-&f!>fQ zxh!H2Gt+>TmJ2!J%2GyCv2&5{j;OFw!IV8D?@emqFJ(N^;zz7c^@Nrjt~ zly>H;@`J3~h)qV5Ep_##52;z;`B{GLq9x9!zASJO!~E9ktA!iv)ZNw5Cn!WWLTLk@ zht2PDgGKG3tKiY_YLIcz^9%U2C!IWh)SHrYUU?nWjrDZOC3I}K@*;L*?Y4)jb+5r} ztsOs%R+UWI6Am(sBvl6ry9!t#j12eHE)4K*kL%4U#4g^E)Gro*AhbXU~H`jvRagBkLT0=la3Q{k`dGWB+uG<<_b=t_sAJx ze0DLf%=CM5UbVvm!Mq`tc+z1OLIQe!R3JZsfDk)E&%Ry=8E;3 z;}wtju}UXvu&NPg#x)8%J;4iX&m*VAFV8wy472|k31R64LWt(c9vhXK=KK+eh{fcf z#rP!XtSLUEB^;OX2_Z9YT_IJe?B0Yk_q)r^{#y|esEAP4{{j$=hWxPns)Bzxq2f3A z$6KuaGxDmWh@@vt6Di_rJcfKSzB7Mg02xU65_x=hlmuI%2s@BQFv)xKh;*R#ru;C& zD-HNiX4A&uDozY+js+rxWce$uv@CRaT=vAEK$;A!T#{AVs!FT@IpLx?N6R0W6N30W z>{o+Dm?SqZuCG-IfyF1wb$;<8cw@)?BXo7tgsN$%<{c=GnqW^=Yks*V-t}1T;kQ1C z;c1{RhO1@_b6(M|rmTk-BeE9&S<_rw$G9V(zJg0hb8NzYcmrgX^9W-%XIiz)u*b`vadU)h}@U#HILY0 zs_!yFlp=FD1d1a=DjCl3)qmRaCGWW=BCmRZU?&+Z@Y2z-^ZiSCFmnM60!~LEvMOlF zp?}Y`L!wzZ3`JdvW#3D>8^mNk?QP!9fskuYP0e7h`+|hjX>mGn@^I)FUg+14^s0uU7U%EqlJ$+=*?oZcOkbHu@T@D6^fPC_kyIb zy==pRHY#(A&X3~E+El0<(M6c`ABbZ_U~c0*wWT(XCG=+-coNMuquVxFks3`?CE?-o z6K!Ff1R!lYvln_IwF(%8Kqih{dZm7B0*=1mQ#|58fkty_jOAQNQ}Cg^M;{_7F+{eO z*XN;=?~C@*{3MkrW{qs$iFWU+QD1h(&&`&IleZEl$UTAst4d2LTn5IomzDZ_uX#>TzIhnH^g#!I1R+YJKW<<=whvZaOuD=J zSs^LlaNW3Zxr*8ZvTj1b9Lfjm`k2rugI4BjD$^ViI?hfAIaQsMfC=_{qv%ZXiR61t zNSOC{`F3X}T&|8Hz$#fIJHXV8N~jz*n##7&+Ka1EY@8u+K5=AEffhZV3T1qIcJQGrJgyx5rxh#HQV1 zDpQp!ge$vs*v9FGy(!f*tC9i_^Dd~ zvCt?zvr507#vU;`bU=y^44dv7yzs(1tX*hX3UpF=w#+qiEhd<0^fqd}Rgcl)-S#MC zIeJFt8kOc-9w=Mja3m=`huGcje*R3wV(d?+c%5#6fH!M&%_rjPlnd&;3)%5tu2_y& zh-lGdm)R2c;^5vfjd%iwCX9nd@LTWtHvlc^wMGL z2`q|liXXmfl7&hi#|Ii4CzT+bv+&XBc-XoN9JmoJWw?qHN z(8oa*^sJZ|F`GR8Qc0tHIn6Ac;H^3v>uz$m-swx<8pWfh%#txp12>j8X z)AE7zYp|k!b0l!IBEE06Ps%qmCNSQca7I_e3q?wmxRTSm2NrisxsNcIV;L&E3^{d$ zP%l%l##F#zrc`($Pcu9itKuA8tGv;19ykH+q3B9*yp zSUd??r;z+3Hg5zkVxNuzQCU8_PnJZayZUw{9&0Nk%YtalTM09`!Q!ni!r)}HwA_yE zsHDAL?e`~Jwy&~2zc5ekVKyduaAv98QxYpc_7yvwfS)zWsEqb`V3FFWNcym{$$*72)&PGqsPw1}vM=CKSsA9n1BnoucRese&u4vr1~Gh{u`X=jEUL%^q} z^8Eeo6?LdcfBdDoLAMJOd9MmOquelHh$N-2g?IIj3#}vA-#{X5RNJ8tI z5p?*VXU|{O4t17$LN?(G(k4M;^SREAqf!c~`09h#@TgF%^a;T?=*wJ*v`bXF9<1w; zh&q0(i7$lSOn*|j(t@-5p8WeblSQw_5W*Yu>TwYr*0)UM^}UJIg>PM2ucW=qNs4id ztqm0LwGJpB&U%-KgB8*RG|_nOdozmfPGQ%~JW(0ck07wD0Wi8~;{1J~fF(x?M(<2c ztZRNOyyPa)qO?x?RlQymiUo|RYyfD*ttvvN{XA*sj)Efry<{`r50cqmWZWXv71RQ$>Mza2Lpsk z(#{2at;Jxh5331r!=T>ea-B0N}!Ij{EN zD6!q=2|U8lX)W~8N)xsYAT+Rm1}MO-06AukG+icyW~03aFy%DgX{N3`Yzc?gl^-ST z2G`!<0Y_H$6SSf9zjsP{{h7Lz6|YN;=<08YaLoi9F<-_xB=jjWAJ)@2i=2Dmmuao- zIQw6Db=3ccm*W#QC=$es*iD~lZcWz-8p8TU^s-!8amN%l70Q-MHDV;1rD4mVfvjgZ z|)y=*{WG{7{2PyTu*b>F_`r`NcW;2gASts$gVuKwW_;cJXC)`6!=S zTYERU3@Mj^+7M`WWwCMIY}R69TLDMU-ztSnB$eFfMfdWZ*zx;%Z)r-cSVd2u`u` z5p8qi|3w`Hy_Vki`t7ymx^Db`unT5MRsKEm9L*;cJZ3%Sy^w?Qfg9vz%eZDkirzEF zAMqJdU=PzfmZ$(;U@1CC7MKVf2VoUBG=8c;#{w=*zDd(EAP%Ghd0;2&h+}wFZCQN> zv_$?27>W8m5K4^gRn&42LG3~>s?V?WKryTKccZ1MbG@Bzm;SiPPz$McvKJ1CZmw<# zF5_jot$sB#G1spIFWr~Y;-Uh7Y!3bVoMQ$&etD?Ie=H!v`e3Ju1S>+Ya-=Z*Q{Gr3 zs1nlx7-PNE;9G}~nxN%~^ll-^y{<4SCAoT?J3pE4ljF_u2D8+?^mK*9e9@vz)=V9A z;T6CYQ7Sk7vByV9LS8>pNzH|s%_iAoI_?=Oo*;|t;)nNB^p<_M)g=lE5?=~?YI4Nk za-rtky?E0jRgsy33e9y{yfa)6TB{iPeg}RmDZ22vvwroM6OCctvyWEZWq`x@y%uMl za~voYY4+LCoL1wA~cFT3A|%xOAf{}(d>XwV*=)DaAx7?SOrq=oxNpytp>a42t@H5@&4*HmtuaQd-IM(x6;n zMXWKJKBUg_jkR zf1Y(oJLr2zhc5PQuG?Y;{IIyNCk2#9xccoV{}87}YAPV<&zMzfW1U9_t%X9!=b3{oLlnHGY& z?*}4!PE(g}4ik!f7JusTI0|eXz{{Eo3pP*J3VHTl&LsHqGC5B6{f1K}TWcg=75uWl za{U^$TmhoE9SjTjh;B@dxHNCN)4yv4RlH;mEyjtUqqVSTxsR>W6In*0f=uz5>mev( zl|k(SzB8VND1BU_DXboRZ%j)7cwYxyIdgf1u_})JnG9vF;(4#b2Gc@UNCA+*LN?LP z7^I=9C0B4z>!|mn>8p|+-1f}HC`g%-M#no$c1;)_0d3iWja?pO~I zF&mxGOI)W{k||m|J5)-JK;!?<`1g0F3S?lQ#P)yXj-$T1WbA$CTRzf-VQ|5H zN$Leq$iwSq$CxR^LRw$(Ygh>0imZilPpl2T4k*paJgJr7g5P1boHG%T ztg||%sn4F*ZLJo$HuyF^8*vvT2vNBFevQ%LVvZyDaYK=*KHoRd^jgUm?sXqwYLdkK z;k%&WZ7Hkvl9!N)oQwh~LN~;DhD-@)mymx_UP_L)JP@99gyJaaq{M|qVQTpTyxJxV zfQkt;gps_JFzfpQhlIbQ*)?dwsQ$> zKy<$B1HTByDqn4J%L3FFH{n=}fYRkDarv5YgRq6}=gbm^Rq)9Jb_|4YpB;!Y$>pyE%k{>JhzrJV(1!w>!XDF2OzrvKwEFH>{I zt{0r}+JvA(5?;S80bdC2CsguMQu?NR3pwh2A76jS-RmoNjP zg?*+lBMDPsL;jo{Oe-s}fUYWj+p3y{?@g1)i4OeuE(nGB98hXDtAqD1k*XC&>L-Dx z^1iy+xhYHk-=nwrWd|cVfFnb%#$zWnXc{-2)g>il^pL0?GshSESAmRCIJA*m=D;Mz zY}w@ZabNtbOT1?&-dF-`L7lf&Gq{?VFz(&HDJh=VHA#g1sdL%5R1#a|x_gu=64}R! zVz~gNepZEuV0{%0ZcyUojnk}lbX0gv6YAwvm$Y)=ZI?;qfT%?l3XAz1CY0&}$USmzD_z;EE}h<{ zH)%?+4(1=F8QwUF79eLXbEpg2fm5gjZZ%V>c^d!pMqNyBEN`$%@p+v<%mMW^)J3vs zVRubI3#z0F*)VnbJ(U=HiW?u;2nrPw(?2u#~)qZ@lW=bFcudQ9zzC@6>ZM(>W9=z66PTVc6G))RdJgnaDpO4hXz zxAFsLlL|*0l{xSIqJbeZt`$!)axq!YajuT1=@y>3<&nbE{hQDRey*XB?jMxdo)b*# zp2RYyNpCKVGpW6B6+t*H zniDz@^KCwKsI=7^V6$}GY_%g%(^}ujNK>dcY>)LSGRKjlD8!$1=9Ghm8ydfll0SQ$ z3pYx4E)qfRoeDU~!P~irv|4IXeoW^o%UP4)49oel`ZvhAPPaKzg%#H6Ro_ykzC3_dOA=1HiM5{c7kyq?2j5J$ z(NUuepqhLDH$ce0a@bz(?dwaa6& z#*S3gWut?}5bTjeb-46>aK}k@*JYfWf%%U7@utNNoP+f^)h_pe6R9wq~bXrc^Rpvazwvl0)Pl|XmNv6cg7nm-X)FLT!|KKJGOtG3RD)-9$%!=QkVZ* zvL2YS8b}g!!(m}eMganHP|uioj|-R1hrBoTI)62>c{I4218lVz&1|5VXKa=9&*1xl2&=px()!y!QaTK;R|n|F12oOG-i~punO1= zn0;9+$kNk@``IeLfsLfzj4Eb*`u1ZJU8it-UK=uXPEd_UpFOK=Z+2#)#X9hLZBQzLWaW_wn1rcW@7$PlHC9OEa--aboY3ZQJhxC{{|c_z^QAG6uAZF#z!XcJJI8O zM?$2YbFQP0ygKFY;{r3ttD6_omibx$5j<~+4fzfyR zqmhZs@&3~+TwZK_`w9WWBkVb2Web6hcq*W`uTJZ3zJA?5aU%YLnq zxW+HjKa%BGQ-acu)S4Z*_XOS>bz$jw`Jzr2zQxlnN|OLce>-H+d7vvw)PyE_9ZLc7J7@z1oul zyL8PdBLHye$!5O0;A#~v&%Ame?;XN-fs~LwKs0?&ct)6xOItU6BF5$ju~)8oZ0<%X zzhdxGfNDSp@9K-HK#SSU%<3K8vMXd;^eAozF*{EV9UT07d%D307zX04*M}6c{+bYQ zCTt{7Q^mj4uU0JdR$K9j^F!vN4X#uWRDSHy0?NcjqHYx9KwoOKVphPV{3{IW+!Vs1 zW>u_TdXh9pdjIOfU%;ysbPHEA*cWdjwI#dzd*bXWMDVR)Ym!LZqb%KDP^Lh^IzKkq z6z)qr;DB%6^55NIYY3FHEtCFlay0+xM^br;)J8tmE&mxUYOJ{Kw5Tl)V~O|v==xX= zT6<}Nxhf~?U>tB_Y5a%0-YB3zG$8gv67SEgn{aRvC9ba>C3-{NIZ<533pQ5M9mpkx z1RrhYGKT1zv5B@M5SpOk^W8VeIS+k zCE1ldrvU)=4X>xGAZ-+Bcqf9_=8=#qYn&{JUDNQzg8vWCNk=XTrVrHeqZ`>B2VoDY zUdHUIKtSbZ^f+2SP#dWOZfA`OK-CX6>}sqHFfBrI6{AgN2=)jTxmRbc&rcsi;IxBT zwo`AkDNFsRYZ8sx-p5SF(fPS-nzU{U3+N_~_M%gDJ%ddgkgjfTf9f}AW8%B~DwSmp zMW9@b^d`OdX#nYd&Ex=Q&n39ynY~ry)}A=5INQdTtl&kNVPJ+*6vm+N^Zo%-S*m3bK|iG45pI8kuMzU) z<<~@8;KD$ruii<-gnOGkdxKI|6x#^3?XO1UINGrNg3zHAZ21{S=OaY3 zBp=GT>&3g+Fi~CY-phJMM-ALSj6lac9o83I2_bvJ{(al_6qx*I_-q5guiJ~12q`SV zWJ(}q!an+CC?}g(EKk-$Ti>S+1YvyQoXT>1gI?`zF+NuA@YOjy$xC2-B2^7k0Vo5s zfg}K4&Y=mrs+yoeC9SUzpyLWW1|?}rVxFZi{jwSGrkNwW*f}Ot^R8y;^qT2VQE<*G z*{r=jHA~k4U(M>*I2&5743VbSXokT=?M@pUbA1XQ?}2!h9jNQ}WU3H4+~0&fF0Qax ze4EEMTq`N~m>6%I^9W|Y9&r=K##9JHOLptgubaZRX0|=J#$1;rLHh+@34naYewBOr00Bga_xCeI_Fx)tB<@l9lf<-0 zTQQSE(f@cZh6SS0X8!iLlrD_``{RvFGbiIT^N3kN`SIv8A=F-KCQ4U-y1nBD$4Y1i zQymZWO)mh*7(2wVLDSb-Ep*Z6JLympLMSOfd~uES#yVC zV^XTRgy7U$wg<9&gH$_5LV5@ijLmMDUq}EFkaoV$B?Rj9M5zE71N1d_>KV7l+nb(;PQxth zhp>T7TSR!KBw|nqqi+F^6T4>z@9Ny?QNc|7;UQ|@RWw`GHTdmliE4$}U{OYlBUPfK zOQQ3~zNl|qiT^n8tVjqpS9-^B1!GPN$~yd4ps(Iz>>ILZGeW&v8ZxIlO2(w>XhB1^M_bAQ{D&@;^vsa=2S{hnNL zH_^9t-vVbf(sVp8D!MK_Dy_XmlkwfSa_iuQT@Kc1r7M5ssgtr&TC~sw{7bnwu)5~L#koy6dXTA{FOE;88_c0p={eO==7EmVND*iUG6u~|cg}h$Fb5by)U^i!IbApS z;S~SBiq{+fY(-k727r~npy;C$s0wHQ(B`$;lafD!E*{IQl57>dGnhT)mCSt~YPfBs zh0_D=RITX&aAFOWi(1tRYjt{7=4%dEQTEkrN|hJC=?6h#IlY~{y5PJ_LNh@eo%4{Po;9)GnH@fXGHv1Sga%VhQilDc7$RCDjFc_aPVYdXW`|y~ zjlro?FGz62KQbmF40lY5iQ2hYbVX2D8Y`FU&ht>C7|_~}+dnE|Hchm=HvDvh`aGa> ze+tN?Y7m8^&_3h_pc+sqpwbImQU~Lskz)SZ!|g_G9w41|9W1WsKSYBSr8b?(NGd;tq`w}>e-}@RP33q z4=WX_&JKy~xNw|o{i*>@k#uXErIe6CY@Glkb7C4|@Z7;l3$>ABva zP)tR+Hc}{DEhw^jjXZvQ_Ov8R?Et+AFgID2u7bVOMc_xRfwS<1A%lKOW&KCclVv?b z^IFQO2*I4M$!-lgddHmv#xw7Rynxw@jiJ|3&f?C!zwRm~5Qzx=eZ^`d+r{D?QHqi} zZ7oWm05A50CRj)RIO>_WgINh48|}Q!wwI7yjrAPp^}%dC&dkUXoGdDp%2L+(qJ@+v z=bV)u&}084@x@qf321C57e8I~A%xhv$mr77^1mPKz#zca_u6IZ?AQ$uarA6fdCSPt z-tlBqJw@g(UzqK?aP{Dkb&BrHV%J_xrS>8NAUwUh=5`@FR*nM&EZP*RDzhRc3~^}I zyDpm0$*_7;{jNp;Rf6zJ|w(7TD*TeO$vC6nxo53 z{{K*fMqad=im@Y$!+w~opXxLoyze_#&AHECvwZN136!IJR>&hu8AKyc1&!nhtgg$QrbY7m5H8t*vVcr{GD@DA}lKfTP7L z!hc-z@0J+KOqJ?UR|4QLjXY1*{x8pDX~Xzu5I0Vl6JPEC^=Vx8(Y6dfxB?fiPDhLq z2{>&OzY9!LJZU`Cv$!-F5)$c2B_9mlD*E6Khr~jY@^v9$!xEM3H&)Q_Ok6_r!mO zUM%Muh+J~h~*EQpx_h+Z!h3g;VioKGvU{5!=C$Fh7mj@S+En^S*QU)#Y9#3mw> zVu5e*|9uGMW|iP@UI$2ASH(AUtFpSPB%$S4^LUZ zZUbaAbcM8Y!--rZk>ld!nWrJXBjDBeP>WWzPF4VgU8jW*^V1CRm##eHlJ&j0q5oKHfa`Wb`|wPQ520xTV##uuw1DV$}n3ZkH{Z+2!Z`J6CrXA@S3 zdpJNUB?hyEp7Cv&4A0WF!Y$-2nXBrX0x0g<(kJ1@vuGy#gGIuh!rDd1PD6Wn!_xq| zyhqtR7K58md}HA{YT*_%;q;7#ca9((mj|IKzcJg;S9=y)#+Kf7gg2q4^%HAFvIHOKntCgUL*+1XmtQHJM(w*H88}?2-zMK_w z{`ob-B+TD6t}T`S)|FcWL#L#ki*V0^Q7+>Zwk2r^oMi{Q&&^+uR}C=d+JndHJ~(|F zjPzo4&B>a_WgdtRMBs`qQbS?)(Ez-bbP*$q9FUGlwn9t~IZOxG8UwtW`1RXWnzm)8&bfU?2%P{NA%1(zH_7a%qQJ#-COU3pi6`>PmP~8t&05ky}>UG z|LU}6>ASA^nVu`=O>=t=vnn($bzO$-xWAqp&a>z9$$;Cv#Kj)Dgna1+{Sb*x8Hks? zNsJ_)#l(Kx+ILd?tW1}aZ5KvJNfQ+sBIE0<6Q^T}8cEI@ z1CbJ*R?s#hT{pML0wk|3fc9L>DMlvw?AeOD_!R7AN_Wz{qY}M=CHZ|HU_Kh9LW%rcas>+qSas>xL(y-u*@#(8;pr|jBXb-% zIoR~5itG`?s@tS*S=wxpP(XGWqY`Cn1E2c*|76{T>HUzYj@4sWb+>8Iqwv{=2?LN+ zD~cQOuWDnTPR3+!^|Vv>UD0{{@LQ+YNx(~^Z2A&+G#VlUFBP3IClDS-yXD2I1XrkzK^}>XFi+9kGu1-4uj})=qsrHl)6sgl(RMWVL z`eIkvUhQ0Rye*^3L)*S5(?^mK`QWTbU~NvWWqNdckC6x_AhMCrE1;xiPr7db8qzc0 z)b};s4n8%7{;2H694`z3ABzs3gbAJ;15bxn9S*YyA8PVnNpCosLk|!u#?kUDEP}Q- zF7y_l>AY%TW8uB@tmvjSF*Q@-nTAnU)0oboP zFr#u)FzoFA5QB}3!)_97cytV-b2lMnqE>Ks5+mFovVM0~XNj#6uCcOQ5243L;>hyn zg!IrHAYtq0q~m{@MvMqTznHbHXchwLO}x0^&Bg)kZNCl06=xVh2<-+TC=WsW&5R|K zPa}hUGcxy??#tEg2XtzHUHqh;*XW@Y9=1=3`Pujb#&?Od)tt>Fi*ajTu_l*zx$EP< zf1Lj^43#apap3bC=FI)g);Dp;i8EZRaWoR15j>T&V`&7zZ+mRe}Wpg4KWZvuyWzNLO{ ztYKF2B~(zXWhUs*}Y`MsX)Fq~t4lgTS3h zKn6<|F<`X5%Ute6ZOhH=A>n$=v$MthIFKah18pC&sUtfgcE*1zT$=#mr57toqed4O zk>O^{Zrtjc3p5j0=@xDau??Y#l<_Ie-msm{Jdfx<7`R|Z4Jpq|V${gv@2}aS+a8_S zY1W_S=K_TB-y0d4iG_oa!j;S{`!3L^xg$ci#_7Jr>su`;nEQ{OPwz~xDArj|u4mzf z8%dNsp9hz#&C5&Q3TYS)ua|FTG`WVNJyoURATvwPYcVH+L*4oHclmIi@j)O|31c#Qy9Tp0!#F=6@foqY_Wjh>3ptP@SDg_T?}RDq48aIN%` z&$Yh4C}3OvL&i#z*x`#yR_zNVo^Vqu`=HKxSHp+N1J zjHNBDE!I0EMIid_L8Zs?zlo2F^2S>Kk^_5DiDV1AdBU~0atgKc=nt_h@wOI|I? z&uLZ8HCD2dx##h&vUV_Pi}q4MtkGE_w+%Xa_0iGAnDU#8aGFl_oMTp(Dd8@raHC}L z-F%Nj|7g~f_dI8Kyfe8s6J9l)%3b!%I{*6~%T*qEha-bD_KX1i_J8FfMRFHrf}+w` z0tJs`r1f~CBZE8)30iA_yx%b(L7GHY4?|ud>jVE>8e<)Nx@8CPr5J{)OI0S+Y`aW}Efx zkus>wISXQI+KcIq21IH5y(INkw)=uBm1|5_J)B;=-rEFVo2PqeA;T_@Bg)}NAyW)A zl@Jbsi}=i+95cg!PN*@i9HyMU!ZN~DGW{jmb7k?MHaGx+GBw)yYi>{yA1Gim_P%HRNlLKXs`U$mbu%c3oj-`P@G|?&T`MJzGxMkR6*Q7Ia_)^_7)qHKv)^O&P3zp5GU|tUA+l&!`EgM6eaV%z4QRm^CmiuC-``;VDYq05Mu9N~ zT#6*Ne@&1#(FELCzFznX$BM%)DX1PLZdC;Xzq9sp%ncQMJ!iUYZ4Xj9Pk@R0W)=r0 zTLG{v%4u2`@)CCq41@)!FCQ*o=n&<*^QvLM?yyw!U+56k8b<`AVv^ib&yZ-$CddHe zr8H2TgA#f%`uWt-Fmh|4AGBZKDf^y)k%`3wT0?O{Rrz6+PSSaDa4Q`@2#8=0M$w%H zBqp)S?3s{Ir=>Wh zR1T8~Hl#e}vf6Z9WReK%&hTQ8>n8u9EJ78}8r5AWD1{x1+^xPO(HU?hNF=(#&bQJX zh+f%9r88UPnBlC8Hh`!we)KYUMbGQVz@Tn)bcU)#dpNEZTx^=lYPZ-??!2^0zY=cDhK@iFg88Ze#TU<5N=I$7PJ zXhGLtV=WVP)Gu*@Y28rpJTrs3kGlr@B8x|vStlc^;QL5>vX$<}jTpSr zoY^7f$9bu)+6M}sBY@q0v&IA`B0MD@?ddYQ7N}QW--WhXUDY%4CUdf-tHAA&rysTC zFh}_X*JCKq#NAnRBPN*jKvyiKB%01#zwHs)YJYfhJ5Gx}u+7GcQ?a!=%NVKNVsoZh zU%k8H?HN55T_QR$qgB;K;xb=8CU}a`iOis5 z1URV~XPH|Rh3j-oP8~E%essQuT3Z#@=$7VmnclrOl)VbcU)j>9d1OjSr!>z|QM(f; zU>k>d9pforEm+Y314V3Qh?)nze|*|XkUl7G(W42t!ubDxsE@5-5nPl~_?Zzf3+J~k z+0JavR-mitZQ|vQC%~zj#Gtpx2E1%@9&5#E%DmX@BG+@xy@^mqWtCdB0k7W2o`_x( z&;`JYIyLI0Of-Lzrla(*3zH5{c$_oaa>=V_=1xeb?_oRtmbgTm3_SV<6K(VFsP<_2 zVR*`AQGiB{{0693T=zU!Y^_z~>@|57EA|jIK`r&MdSkB69Y>}XRD`Z;;W=B=1TI!X z+M6k`8U@VFBzQQ$&qL@RacVouR6~;Ae5e%*ehz4=iASOByJH63tf^`n&_X+ZGnS_l zpqoXzwqhiEG5Xoj4_e<&u$ot#K;HBfhPr3&>orTN$=w-;PvB~zR=CXlar3*k)!J79Gd{f7 zP{=kWRN9v}rvX~C+3=@ap$?gK({6w(X5~xSP&b^MvW!+*>9W0q;=lqk5A1(O^u|HK zQ@rXLi=Zq_CkBxy1FL`^MiIoik+bZolMGtKbyxMwWpg9AF&<(&-phFz(~7y&VFK=b zukW5!uzcFa>(pXoWx2X1r#nf2;DSljp&;}Y;Yw=ehc>nc`?aI5hCQPOHHjDf2u5SG zoSa86ixiGpZ0DB}^MK_K@C~rHeGi5=K7RS`z~o+wcDci3rpuNd!fME(1028L06^%O zR+Xi6%@fgq;%yOld|r@0%xA?O5nu#Ax+%ITxO@@Hq6(&kJ33rSD|6xhuR{!sUy+s&GB+T0#zNPd|EF}y+2WvY+kJ(h;z zCPK82eM!{ooG9Gh&!d44fOrcU-7QkAe9C+)C0ypz$tNbqP8Y~Hm*itY23&zHZSU%# zi>b%3G|naf?a@arDm|@;bb=H)>70rygCkZ?C?j) zLG$2}_KI9r=Ky+P)Z+gqBWNalg3g=t0s+Yc;Pk$Hwqto(=Gs~Jo!sFH9{XT^%9Cuo z$h{2e5xukDLr}bdiSNHW{`AeRDwtgUb1jy$GQ>lV6}H8Bl{+@XhxD@W zAjZaceYPw`H%cr~Oh#6)KbBi8jl*e7X8#-7-QB2D8zPuP_hq{NTajHe_u(5U6|U~; zsa0>Nm_vqOqxnqaf4v7`2G&kqIp&@Ql&{WP)UE)34;@#O zv5y*~Hcb{kbsnn9+D1_O? zH&2Weyt)B!rom9K)u|{T^-@dsL_@vJgR*SG5pIhj+z1{vAXszy+p5ZX0@Qqzl&3^k8|CE zKRmdQNr7Xai;kL-k5!m_Un4~{9Sf94Z*|(0XqC?O&FkfIHaELF%`t+s( zpEj6 Awk94J7RrP4-}Bp25|ti2IF7-UK1r`wWZXlB%i!lZ%Sahu)1T?<`{9xsB6 zWn5K#(lyyJI{mtZYyGd;5jM^e`eI_&arK`gS)H;x6CmT4dsD)WO-z3Hwg&yK3uoR< zl3tHd&>KrpAdOBqj+E<(2Xsw66)`42m4wm0l8a&M+!+}- z7s<7MqW~EM`{;i999j<(F)OjD`Vv`nmAF&Ng&X*ktW(ezR)NfCemZ~@JZ;cSXI6y6 z_zV!voR6)GIAn2-^Wew~sqisvTV>mp4V>@%;B#7*kg86hc#kr936z}jk3Z5EY;BuA z5-ybY&W+7W1pyAFk=Oq}dtI&PA&BZnF}%Ga(I@zuD337YxpA!ROo1=CTogaq3v%^a zG2O4zy?L|yv34*zJUKmR1tP2lDo?8=m+P$vSU_hD>2q(6dc@_FOq7V{C=*TZ1J)&y z%?y#`Lwf#p0XcQ_Mx>t;eG1j5Uh8h5^+S5EZ#>9<`` z$OiS5h$AozsS?}>M|y^FB&5?2r2<3Bo9x*LitZKE0&H5PKUqDF5$xnr2bkg}B>(Ob zAFiz5)vFuMPNzGym_7lu4kkCb+t~m=DAw@AtuD>C%2!FcGW>hC{CO;Ka=hIOudyNf`Jqx=C;~Y z7h#`plO7CD#G?d0O(0o>tQ;6bL-xTjB1;p?S5{dNNnshP2=>rdhkt{j7w_Pr`XcK& zXRWw{e6gr4Ly@3r`L5Y^xnAcWv;>IyKjtR*mDYdr=Byw>f^AIdrGP}KvXE$da|%eJ zU|&2F&GJMkv#|5M@lB5hR}Lul%$u)(p557I8_WJdAPh=-shv%(bl@Yx7lo()q#I)QS^*?M#XfrOhlBD(XAX1cLZLK#@qtsLenAPn3N=rFI*Q1ED# z;uT%_)abKKmmIzO^3}2$K_hq1Er!AtgN5ybmn@)VCECCNbR9wYYIeyDY?1m2Z&Hgp z8&2As&$P>;ZstkgK@|w`-3wK{a!dH_^36tJymDKX`ol&yb$z~8n6Fif-OW3~d4s31mkq&|dy+g*(8=TC=WF`8B%wzvZ6&F zB!qV0)C3QMgog`SiQBM=4|}E{{Vi|NqU+!3q%{BPLJ&bvL;%~+ita@JjsS<>FV-s0 ze8?dWt7ZLa7hBsTrp2hGijdgekT$&EXMRJ0`7EAmuN(zODv|VWCq#-;gA(_;5p%f4 zQwZ?i>`T~V+v8O)Ca3VD*(@qw;ZVX95o1JGdF=yn+>w9G9eXkwwdFUB}9|R^@cx}9T&0WGHhsaQqB>1;R4KoL zX_fDV+Gv1TxnI2Bde51tUW!42qd_F%>qJgZmHqs2CC4t+5!S_bbw-vjx_Yi|-*Q(; z$4DKmsoGf#IdSYxrg?;2KLIG;CUsh4V)>^&*P75{-#8E>>}NI=la4ojJO6BHQHn(! z-O&_Q0%X0y+zJOs^xmB(Mda|<*El<>&a`U3!mPhikT9p*exNYmV*PGR?ozC0FE+vN zV(|nV90FsjN%&G&*q)cpN~zbn3aeU~Skn`Lw5>8K)8M> z;TGSInkF%RJuy0MXeW>1IP}PZW;sHoz-i`|Xu+f`R56Kjzx0~2=1qG!#xAwe8}vAK z<KRG|sq%&Wu5#A_wna))Glj7y7)E!!O|^6LB~|K;nN?V1yT_>7o+C zanys^=d8OGtmoi3@-y{WwCK+Mu2OPYy^x2e(oNEusO;_aX#gPU(ol!#Srw|i6g7&7 z3Vhc}`z*Inc&7^FV1^4&d)B9s#1}DIf{{!o1Nm5&wrqM#E}zqh<=7&W*D3LmQO)zz zm3=2tV$lAijg*Ct0sitQ`u^)kBH%-#%6vDD`7gz69Puz*2Nv%02{NDfcy@U6`n6_r5id65GlOUolgw5QPHaY29;cBmi2)+EGSAb$MEsG4nm^Fz}yze-+ zCdk=uwY2kNjhK56oU>Y3is$BY;l4E+OpVzmGddi>l?g6LCE;;L{=_>bO-t3-O8hF_O=4`zxHoli~YoRA7XQu zDG*{F68wHI@fQO@)|czG9*z^Zna<$Og6HQ zfgUCymqWETHNZo&#b>6#&6_)g_U0O`1Ik%!MAu@hP!v-w_%;NT}R?^9j_g4B6B2~F#hv$6PDN(DlWWD7w{J7I;U;de3dOk zRQx71w6t#>7X1omNSJ22nN>7O^ZJWBArO-aVfd}DGiLh zp#aU&mSJ29cZ<6HT6s389TEgA{AF+oqe(TpOe{D-`OkfJ5hE49|D2 z>mSh4)CszI$t;WYNPTb$XS(c$rplo%HFo=S z6}3VdEVs*sg|W5o^v0U-PnvH*SdCxY$+M`Oj%mVO870W-b;vvyVFz8acCoY({JoQB?^2cd- zHbcUIYPuSg^UwhHDgpw2kY2tW5!@#O`1vnJsQ)9?j@Sf2e-z*)T5J6Odhe$q&mcsi)d{{0v;A zKgTRjX>O3E=_`MbwE0^&)9pWF{uT%flZd8RUfwlGfsS93`amECIE>JBr+RkW26{|D zijcD?;yxYFk`kip%RxpK;;}g}gcxo7jwstl)E6G{tz5))Y}(6J&M25)*-T+%zrX5sib1IJ6TVi4L0RjDlCbkBC{E zt9x2X$i9{&O1f2CU6snuD4vz8e%VS#Ube{O&gXxx#hIpm`2Z!!R4DTyK9H zHX(HuUH=1caVF>{0xt%Wx{cW(^9GtZb4=v^u!*NizhSBbKqB@y7I>T~e807-F%-w( zbx-L@A2)kpQo(m~SZ&jsx&+6#xAN&sqScaFlQ)1Y&a{dVh%DzlQAgzFMi0nAp#P@O zD)+1MJ%`67-$}=Z+sQ(GAKXQ7ETZ`4%NtpkKhQ3leq%JkxTR8zyt}-kvSJG=_+ryw zaD7crcTJ#@l4nl^9QlY7(82$R*de4aY{;BhMaUljP-*@8k+0y|ekwJkXY9gUJ?LZ~ zhSOFmc(!q=1tBHC2n^17RV~wO05kN)cZ-1ziThweQF+dM5FTsC5|WxW{RzEE`M4EM~-l-ClIt~mv-8+@0E1Mhu3kNQ~tlx#Qpe`U|Qp8 z=F%Yn9(ZSt+;xnFKM3^vXufUJGak(D*ms7sWktQ;sJc6(B4Y0OpSE}QfIkxSA0|iM zNXuUuaiid9(%wtBy8?~rrX4k2YfJ-<#9M1HU1&2 zA57fEm}bHl=XSrs#p;I=ZxR|yNrcT=8nlMRuTAQ>Q`V=4N!jBLOAK>ip4~jZ#Oj_P zBcNc{^)13-fjiqd8w`np9`OUx!lrA@UMue2t6W@y%fTyUns{v+qCiYXQhcE={V}U5qD#6o%++~n_*JcmY zf~LwsU`{P+IW-@_iZYT&os^P_LZOz15^aX~jwnC1N{1pRfvsVw`!a8H-Ra;hi9s{E zJGmW~)k;Gn3zPH-sfNSRs)JT(${?zl^f`Kz(j%uC^_fH6->uX0VJzA1Tt&<|Kza4l zso10qy3NVZcMiwBi430SFWAl)i#R|J;vV&;B#G1Jl!cfLno!FK26f8_oxs8_5Lv{eo+Q?`5OGW&@Pf~8CvS>D)M^K01H5?U@l;@6g zc71Ji5$4Q>PLF{sXxm`l(jxiqk zAK$3on z(mCe|AAdwV{U9*rS3NPQgpjk#I55S>>+fgpRR#ROhA?#8P*rFf;!$$71&4l9>z$G4 z^{Z3!V1@CCl76laCh^@=K^{>+5OA#U4d~TubY${3{T2oEGdi-6$ICD`vwpguI&q!{ zWDc>v`91*UW=Xiw)q2?^hQS{xsEZ_@PtxaTAg-!RYs{n=K~gSg_Yf@UI#9lER6}_M zUXV0up@9Q?*N1y@c8%-oefcNJA8n4EGje21xBE22Po$4*?rsMk=wAX}@`tIi*V_`c zomTbOf9XoFbDpnHFV5$I+<3?k>|QlY0`w@!#x-&qhNLrsS=3d|`r1_OomkJ>wbfnYItt_z54#*7 zM{}9jIHvjkTd9d_7U|V4 zO1l4z(T6zD9PU3;6}9oEV0|$1Zzs>GbBqszJ~eb`DXH7YmCmo)JJn#Qq!(9z`ud)_ z(S<*858%EqnHbSyNT>>!xnm}#hj&RJACnGlTh6S+#H2^!#-7oKkUX0nCadNLo6W>N z33&DbQQe4+bi)$rxTUg!5dR|@mdV<~>Ma=081-LFZu=73aSTOhFqYj0#925lsuUm9 z*d&c}W!7CuxZ~O?x~1W2 zVjm~&2U^Q=(W4Y2rDhRdfn#=uuB1g}wa*o?@1+kjPC{Hlsr#iZPMBly>NE_fkxDy8 zhB}&DCKv>C^rWQV9yAZg|HzWmt6H_H7K*`h{8$lKMeMS?F&JH!7E{B23|!TRFd-cy zEh#HD-v=RIOLKdAhU911&|;oc9K$G(VIYuhY;$qbi<#(|OE7Y{|5mNzq!-_CfzizK zv|K07`l`aTL~>Er4?wN%WGyi3XZpom(1k5pmi<=MXA_y%cqF4kt3pE7z-UUNd{eF8 zGg}yuknjt25*=Mwr+j-6+P#hdNI;dVmj-|lkF1H_9jhhbOF3-7WECiL| zQ%dS?i_XuE*#B7ueQHS-Mn~$fsdV##c*l<5BU;y^uYAiE!GQ1Uo>WTfF_)dnZ1dB3 zMHHR^W&Gy$<$0rz2rENLw_2ESFMkE;w1$z2{Eg8=$@bw3h!&@28F<=weKwDVp9+JP zBG+UTS7j1>$?_;xB2aeAzUdT26`Ja= zZmVNr6;V<38ZU*b*CT@xfgzClnf);@ReMRaFrpaW;xe5hyf^;Kn( zh>bj!Gs?{Ph|hO-ubvwLX~*MQ4-%o*Snp&!z21US&szOX^Osz0_TX+SaQ`R90}Vv; zFj0a_9#@}fG9{Hj^(3fU+q3uEI=Eh^4FXISf17Wr=kV{;O#4|RF)$rD!A5Pc2K(&P zqE%w0Q63H(HRp;}oAD(F9)XOp38sF+edwlpU66V<0hPpz3n`*`9llQtpt`ws2Gcjq zqcD{>(*=yK5lHyyx@(P0C;K$=I%!Pi#X@3UA20v)6wotbxChrl4lOd}OXj)u59dUw z!G>?d458xC-ChR!KBA%LB9>HCAfy5EpW9y( zBQ!aS3Op%i(J@%6vcxkc+#rfK-96{!e`L|pki)-DXDd?(S$2fnt408y3f66DfR|yN z^4lqBzx`o6>Q{Naq}n{#N&K6`J=jft>A#IqgkBR9(*cQRS<21O)Iq^PU0FSx*?DfF zgO5)0%uEb=1C>Xn;2RKMbNS?>u2^Cy^`+{&hvW124y5VHE$SoJg|&#jHEQ<-<8);0y*qg_3D|-n|z7s z=PYS@yN$nvtAP;MWeW^jV^>>x^6zD^aGh)#mT=N&aXlkUD9AbTh;#k}W*hwV&Yn^{ z-ihHA(P4~WcxOi>*<^C;y$WR(89Q6sO{9dZE5m@krJVhB8|UVv2EDmiZVP`aj^20 z)Jj*7Q^MABpD1i;hkiu46MMPvE}dwSbYJd@g;Yg2aQzW>rdTL!qpPBGS+U5_kTys* z9Qo=xLDCJGRomc-roKwP9=VwwipMKvtMDcR!-o||D?V+1K6BA7Ge%f{Xj=~ptHg@!0QA`x~$6;vEvYJG7=ARmE&m zDn}7<-2XNIcX>I3hhwqTA7aY~5{u<7v&RLt093==V%acEBmA(cKx*aKrGi72ud>or1F_$3Zn=w6_xI*KfHgnQ1* zKU(qUlW7aTF$cPCbYhA)g;iT!;Ib09x z_-rL4D+~M|rZDB00_gCD3HP2fv!2ETs^6g@E_N7ZgaS$W)J41!$)8^QWgIr#?fw%+ z3U>yO!2n?v3#Ku$dtBSLJtg+j=qsOrJFQzD+xM2`$I=i_q++^dZLgkkg4F{fi9}P| zQp{Dg09#{93989t_iCQPkrtVM_pQ~?kekbF&*%DD_5ranoUELXPpX>&R(!BnyV&u| zo5wxJPZN} zML73Gt()7QCXo}i_@VKEPQ>4U@ij#-TxO+B`_smY0|pkaVLOd78RdeF`oPf1ytawk zLQbg(*zxH1Q!KU65k<p;#Ka$F6 znB6e2GM0Ur_;j`l>uO?W_h&=;dcmR6mW2Gy)CPNZdKpd~2&(#V2Kfn)aY=GUEsVAS zY<&VIaBe+*xX*!pWleUO?|<6`2>}hymQ*DZARB@L?RS3p0nXB#nc8v~*&Slq9)vUjKvWE4yY+4I7&c5l@XjKU zVYvM^xC<&2MX16A)TWTZ8D5c;sbxuRM=n+)_y_XYG?Feq!61&8VDOMmnICZE4;DO(R=BBK0>17!_5~RJxzjWLb4s&Y@TA$G*w^I9-*PZWI9% zF*yCHUA57cGl<+_`Os_L58;NeD&j^O68l=$cc|;>;B&LqKXm>i9(m2hD|*PbjmRYh z4SIx*DZo5Zlv35EzUJ>Ny#GZwT8GSiZ5GX0+U2(Dh)9a8LDFD<8bl?6e7Fn&Ue(UiJDpmK;&j>mNO4wxNb;M}N0 zrtxp@nNaR-xl7a!fb*2Hw99;QEVRU#>Go!!BX8{zicj$kRb2u@`QD$ulp89EtZ>RUL)dVMSLqg3W9RL+Sz zz~B5VGtznH1}b$-lA&KxU);=|WL|Os-pWzbCqH6t4^|;$Psz1>-ZQZZ+;Cn0prUG^xOoC`90&>JC5Ouz`1w zyvTYa=7+1Xm#K8*7ppsMeKlej&q+#Nwd`%@?na}r9`#WYOM?fWn`GP6U5~9$szitC zp7(ah4tY%+eV~i;=O^B~+A{U~zN^|Xp7rFSVdHbked#i=URwQAa^pngW2#d1K*&F| z({LTNK8XpVcxsP`d&}0h)p@K|W_SEnUE@g-=yHJ`*4g1GbnmPWC{zt**c2Cy#^a0E zpUW^abuP2vhbmp}e=+NS$!!(56i!@*h>(s$;=uNv-`KnG_B9-Q8L$?ph!l9bVf9|2 z0#>ewj=9@)d{|>m*I40@2+rBx*`PZd8^MD{D_O^$>Y|A+Vm?@z>NgFvdLI)eTF%8n z1@$qil)Wnk{;?KC1C9YtL#TUVwa8Y5mNI|G3jOCkC(fE==kv3nB!)1TR4}W^nhm@3OZ}(Guc?Rvs`9VS_q zJ5;TEVs(q_Z|X>LJ!Ek0mLmd0+Yg?(`!zY$m#6FtE2^(%O#N>lWRP+KzFHEpUfZOK zLu-oP2vD{1o@5nLx3z>I6e)#yRO|qNz=G3fU`?{Q%s7}#j-=nN>1Yj-7w}lwsU3n{ zGlR8bc(_}92om#*0PT$qlOIaq=rXsRnHi+bAzS(0n}B$1c9{SA*kUauS`7EcMWgb* z1P$bc0Tps2SXk&e+v`293N{w7WGfG8BcrsdWnOM8j%7V60^&BjCXR+;U~0;ey~`OW z59Gh$|DoHDd7LZw^5Ox^O|bdz{>Wxqkxk5w$N=f!kKrX5j!Nq zV+F~xdQ`_}SQgPk5)mqh@?yh|2K@=_SHk{1zISvsYg;=!m#6o%w8Nxerr(|GUB=?z zg7#yrZ4)v(xYQj!7vL~tuhIA* zh(uo(Kn!N6uigr20SdfqKtS()M5GrD8rIo!#-5^_N|^tcO9;VcrLHX-XEg1RR&W+u z#!GH77ZNbuGnWfJ+fcxyGp??vdr-; zF1D8v-Yjw>M)?bS5{Nin1opEjVqQzp<6P6~s@_=}iN!Oi2jn%5gShlcl=TK(tV?fj zX2cK41%Va`{`K=-DQN?obx4Y^I)re*Y@{wpWm^yOQ4kzm=bpt&!+S$o7XAiv0Qeas z$JzVx23nNSIDCNb_8_ibS^tQ1De*GI4-u>jTh#oB?v?Zwna<4vA3?lY4ojwa8wks;w@M|h+~X|BX*lsSCu--XR#`2uS%B2^ zA&^O&SoE3gP&>>b5>}aNWB_FqK6a`_g$Q|@!kq+1Y&%2)j~lM|Wxq*FOg1*lw`65+ zb>jG^pPv;F6vL1#J5GV(ALt3*C)P!3Q~HV>2<<(HC%%WTHOfl4J6mO>kZq8s)9{_(+yi8qPNen)Pcy>Pu{H_)G-UomrUk68MwNKWLs-#{Ui27`if# z*(7Ais=->+zBN;#WB_Psh0S7BVosV?$4=2e(LE)*9`2fLREuq||JS>A=i2uTCKG{s zar$Y+V)2npaKl4Y=P+@;r->%5_X{`p>dY+XeME}-+9+WzjNJPlC-mI8I7z&m|$MGgui|%r#!>G z-_bLHsyW8-_N?VY@dP<@$NWT6Kj@xMq`USk3*D z7)i$Mt{+eY&Pad)OF%e~orK4;1 zsciYHBn3sQ-CSQSG}8X{QcuXADRr0(P5oKCMnJ|Az>JWxHi*>+)fh#tc7B75sVB0{ zVQG_LzXTWXbA;6hybn7fMk4WCXcNO+RSKZMeqx|q@UBe9HZ4F;gwr_cpV)SDk*cp+ z^=-UpQ67s2>FQwOnJN`cWL6C_!(2Cj5nZd&-qs(*7WP@Wsp*`stE{w{RRInqg*%Lj zv3`s7{sDyW^z&}ZQhj>Wf1N9Xd}Uw0LF~gOOJ%%1$ok}9&BMkQO?~X%niuoX&5IowYT5QAOE)&!qGmq%A%=Ns?udc91rYSv;pexcEm{07~H80$+y@m zn(rW}`N}_(AJ&+B!9G_whvmnp6lh#oY}iFtij%k_uX5^e(OJKEUCVSc2P?sW{dQx6 z1n=&07YSL~;e(&A!P67`LbxCi6_r(S21TZ=ikq32r*R9if0b!4EFDpZVPpK*4)IJx zOay$mRmJ_(c!`|irW8TeOxmd~FmFcw;m-zwl+ZZgkj}^dF1jHKbku(A=}n>Xw8+zp z=Spegw|$6p)5i_`PR@ieEp>>5WT9lQkUJ4{C3LeJW#C6REuVV z+c&dPgtaEt7_d})_sC*Kw%x;mh+8CIC#;jqhxQ)5J7utvghy}9ExiJ_Or=A?bqEhl zV?oy_yhr4z+vq>VrSS88708Yrz~zS#6arBK#uB(6hoRao+k=W4L;(85X{*eR)7?G{ zJ&hoUZHp21qXaG+2jh*85c)pM}TY*<9u-`17 z$F3I&=EP*AJ+R@>ljr=rN3}~j3pBg^rdEFrTBjjEawc14GPe^2Fn&Yqg>EOF`t6sK z!9vWp&0;iQAxD8|&8n1@C!Lm~I^y1XmZxNLFS+$V6w0&XLx$Vwo&!9`w_9 zGXyf0JkWp0T!aK$99gxKz|sMZ1PU5X%H~v-4`oG2cUB8b3tyFqAu9TPprM1vI$2&k z<(KU(EP2~>*75a-#=38bY0t-m|M{L;6B8Ypm7o9c{`k%4@*27Ub!Lnl6%Ot3Dr23Y z05(`_L++q5waMvFYiGn+reASH#W|5RdIyd@nuA-`qm?sW;RX4Wkgsg(`2)?V#14-W zLj~533{47D07p`+QQWNL9QV3W-N`1qsM)tNN6$xbukyM0I=yNX?Y-U@*e{0a8Tz$3))hcGH`;|~TX5Vp=$L-SQ3o1$F%2HfH5!>E-w zB0F6YM)Z^xYmz$-`P<^;49522;QrzN0}!+aDD<4BKl{Td6`*e*>ob}&={y!&$hs@J z!b`fmERkgPJxzfG&EZ3OGG_G{uqzIGPhUJTIEqZ4aXUOhvpsuf11nwvn1x*{jdKzQ z+TT4z$$=xwwKm}b43=b5m97;HQ;Xp){{Q$13}YA;v>N&Wq{F$Jr(el7dN)DHa#sw< zsuxKEni?}Z2CnDuPk-B@TR3z)!++xY;PP^f&7xnYV0d>1P!o7-MVEi6Cu#<5=DSyh z8xga>Z8}opZ1<@(M;kbGaP?l@=jEWC-;~Pwu1>lybG<0#(dMlK4E0ksR*$PbQ8Sh2 zO#ViuW`kYHb;mr7&yMYmaXnLERb7XNo2kOzHx7#~JmebMX6i%5?SA6D=CFD^6Lira zY-W4tOs};`_YJ#RL;&N;tspHq?^(hzkx^S5dW3MmQRi=npl;W2vnD*b@L6po%zhFA zetBpppW`5a-A6m=e#v2cIsy{hY5YDDlWhd6!}m1180{83t`>A3UGI7oH=Y#iBfdbs z2sUQ~?%o(BY@G+@Zg=KFz~5z%PSq_@&)GFOBqm{Fm?XAEpjc4w%A8KLtgU!)+Mqc#tQlroYRY!R%Rx4&M z#6BqK)Sofj)P`0mH{tx&vQ#KxK1rF1Uez70i|cYNfJn0F3BT8MthoK=wB4uk3!K}x z4M~LLL&o`y)Dt*Ud#~=gY8RKk_Ye-lP}Ia|d76cdQ`oaO!-ABEo<$6ATRUP*ZbqI` z6@8tS-*L`nC3=e$312KksX%TU0To@zBvB{xkDzr^NK}+Hh7#qMv}Khz8MfoPQt_3{ zs;(mM&NXyiIQ%3^Kh2D&Q-gn~wyYPwS}Fac3a)GBstKv)^#Ecnq&SKx5tH%00d`ow z#fRnELA0w`1pClRS99o+y}YPtl`z6T@{|h2rl;Sux;T=WFDB)s=_~#5dRs$J5>Ib= z2%HiZSBvDu=9u0!&rijG=wh#P1OiZ{%n(X$kdK;_$E-7m)geSazF4Pllq?g(+8A6D z>9l77WkV5v;dS&|Z00V}G;cAW;Rj()q06HRWzc;!f|YixO+-JilP@=JxKpNVXg$qqV1xeR z;kw9ej1gq*6#|5dR*pNj#JcA0j1$bEw9RO)!M(~R4-r?F0vA&%|3CFvz&LH3S1=NC z%I+c#>im0r6i~onL8^!xUYslsAQi2zK(y>Onuw^pdGAUph=L$%@~W5eDdG?WxP?N| z<>I6qBBk6ckKM=bVQ95IPR*CLQ_vb}Gk zO1ta3VI!-Ap|MfzQE{IgaI2Cx+Ep33EA@^?tU2h@VUDdBtqTaOxd$1bM3yY$78R|T zELb9?e(MnAChgVFY_n$w(o^MsG7?MUUi(-?3^X=rL}IZurc1Y@uEGEG@?l<1ybD(X z5_D4qyahp}#j!-)FUIWlmCKHQ+~qG*%c~Vg+#Uz)dvNC}<%6YB3A=^@cAe-LN?b`V`4v^Ta;1 zcp~z-N8Gi7x#7HS&Albyy246KO9YJtpo#mHxL``VU>giB?gP9Y1c+=L7+_l$O6+z{ zNJqEOb~!YMf)WfVq%v5|3laZ>B@;t14xJX7#yoa{Pxk1@L4hX`i9Nz+zj@Qcg1D+tUWP8RNk zpAb)1K4{;h6OoSH;c{iDI6t5ZO-X$EVlVvqSo~Ow7@8o(-gE?yFWIf*1}n?dh;%Pw zudoxnBSp*-jurAarz__8Q#r>Tmz$KC4byBoiF$o6xfyF;I)f&-s_O~Y0!6lR+}!Xz zotp6E$Djf33f@e$=2Ic?0D~-@&)O^{x1DXFpufl8WY1%@MuJq5$}d0=_;?<*ClcbBWUoBKOwWHRk;X6Xq7CL^rh zA#Xk?p$2pq=ju|}F01qYyG_7>5SyU)`ao|;?!RzZ+EZ=M!zkfp17d@pPkw|{h(KX_(+;FCUy7|Si? zoA?oZ8W$K5Lk$uc>epxYI6jCHJ`s?Ktnye9?sk@Gz*cwzD$v;xp>9SLmIsdyOqm2+mu`%pAC?q$=Nuf9!d#qzrOW za(B4N(n?6FLss^cP`35@{FW!$=cWec=2TLqnq<1)*7^Z%y?sGD{e?N1J<(uI5uNrN z=nAT^joGYk&$&1@@g>`%?TFKvD+j^&1X^q(Pjp*eP-r=O5=HQNLCzSnza=!wmSxCJ zN;k%bEltpaI|EQ@CHXr`!ver1R&P#UW9mdjc8D0U=2=}VkxOKzCPdBz3flz(sGyu^ z-|=AVh%ez`_aH$E_Jy^k-M!Rv@(WKMKzUsQla`Xbl``emc~7xE_wmQ17>GMvLtMG0 zmUZ57qHk%w>aNl)4|7jQekuwhG&ju;U$qpJ9whv7NT`Dfr4_hBde4B2VIj9SL$hhG`vE-(i{UW1)Uc1o@@Wi1uSfbO8`0IlNkCt6GYYsy-pw; z7~8rXTDx{(BP7=i9qWhG$~7s^+jf9_<%_2q7F zFq#j^6eeje~`-7?I3F2eSy?c!SBWv7u9U);TBLlU+8& zVs)ebJ4#4PP$%f2O?-!vuEUX{m%uycgt+JNe&1eO!{LhT0y~J?$#>chG`leoo3!nJ zkY2R_c>ghXN*VVs9yretld_C?T|KWviF!w6Y{v0zQt>&=hhjc&lBDw^t0=WMh0n2>yH)f12g}K3nhZ2sZ^8Nc(`z&#x$N5kt4`QjCX1k_!;U!$c~H*cv5+w^lTi zui-A|SjUKv=(~BX-e)%lPIipn}yf zq1*|i#xGsY8M%zXX>ruLQ+t(6l<%f1W5;AXBd)-{)d|7_+a-UP9kxFgrAP6#%*HIa zq+=Mj?sgI&Y}x>4s9V+<&X3k6xVdNqXh*mAJ>LzgE>g6M^7Mq05kM$S`Jf~(f_Q6Y zBX?_Dg7@2zL*nW?Ea??_`n*CM98pC++9sp+e>nD=@`yIKka?+`q-K43n6jdmYVWaJ zGRPW79#IR2L0D_%)x%xKn+63-rVjpt@rz@HY^xNXx>tozG05m&?G=8X8F0&yhp2S@v}amn(x!YAwgcoz20yLr>5mp=_IGdmb(l@DFm9A6 zFjkNhi&nW2>|XAs5Y;x1;(7o(w|`Q55ROk59$!`(RGeJ;pw=+W)1mnz><GdGT&$ia8b@+X|4Tv*y5{aYE7qj|WsLH%!P$#iM$o9GJMI?msJg1PeRyg@Xw>M&bilIp zMt}JrOyQMEZss_8pgBO>AiF1*fW+WP|o4Lg?|T2HGi z7oF!BgxxB5ZM86fuZr9oN45bC&+p{4uZ^1^GxaG9+K<-u29-Lo?kkeqhN92po0YZ3 zAWfoIhHP{3fOZ%+Olwt6>`izbY{Wm&!k)KGS7KEl~E(oke;? zP5@P1a)pHzph`8&&V~T)-Vy{P!t)wtPd?W&kb7gho=GOlG>$@a*Ji!I^dlFIAri-qxNfKzq-i^64?1Vi`F}tr>SI9uw0>Q zswTGFJP+i*GWcNZl)qHYo78DnZVYVme$%`kbKgZE8IYB9afAc8Nk4m{Bp?Ov*v|mL zCb}6XPI-lB95g|V{w0Li>{BMX1=S8NmEA$q+8uKBOjhht&;^v0d(-+!A1sha`}XDK zip@yBb^jHg1YK$X3KGrgOt^;-M*0K0>ehWgHcQZiAW7&d+++!XYPxrtzZnG*oKI) zy#AZ>$V`w-M6K$Tl*d#!aA%1+%{=gAjqf}F*+-}<@r_IiAEU$$h$ez?fDwqF>Sx1B z`J1_ITeGZg$LJ`HBhE&kMOuS3L~0g7uT~#e@Dn-#-ZC^~FtnKY`LV_K(FU$N4f;gv z`s9{DWpe61Mv6Nu>q`NsW4U&Fc+n(|$~uy8Ha2>kj>1t-X+5s?#uK->3TXoQlN@YN z2m+sIRX>FsQZ;r5R4(%=9^QF#ngH+ zEAs7sNHNJG@+A#={wfj^X=-r)cKz0Z(Fcs1K3H96>0q<-q00h8ZM1Ll`9l-lo}Qb- z%G3DTm$9%%4!fM;Qd~cXs74X~+Bkrf(*|9gHNhb(JXmuJvQ=(Jf*9G~b;;Sq6(z&+ zRlX#&6-D4=@V7K$eJnO5*6p|8QM(dY(<`K$Wm{{IN&O51m^557OW2IjLrV~Q@inr@ zSsJ-0#jp|;kMP<|+iFy5?2?PNHCHko)i(cOh3mT%P%BOs5b$5HH^Hdw@j4!iq?s)lC=cJ){} zNB^q3523#TtL!sQ04pdQBP_o1-tuK_!_Of`^yfuif8}M+`*9h@+FFzEc|9E zMw~_0yttKdzhxb2YHbeD%e}UktIwRb;U=wb?7c(OP;S4Pd5jI6zGw&0VDeW=4&Wd` zjJE_Z+y}h2|Kp_9o#=@$yW#P;bkq+osMH+L=o#|bLt{vrdF{Q+jI1%@ot>|cjd z!@K03Ozn+`yQulT`s3(xO+wOEzD`BRN=ip7H}F7AN#m&XdZQT~Jg(El<~$k>1?8|; zYnddFYtpA1*0c+Q#tV_I=A-!QZX~kJa4vnb>o_mPVDmw(;UirHqHI1yl*#_lv~`Gx zrRVeC11~m3e4iDN4|M!;vh^F9T9czV94ZgvkWOQowS`T#t4(tU5)ss7uUnHVFmqRE ztW;VJ-1r3QaSCEVZv~P*;Jf<WA;st83555HNw+pVE_cSnOiH3Xoa|XoAfwGx zL1w=r%Y;^t^UqY&Bz$4RQ|!J) zW4U;qnLi)nfUgWO!G{vd@?nn~-K7jxH@M)xiFUfms7%xPk9PXw-l&NbdtA=zueNFX zB3nEtGJbr|)#U1AfK5v>6a;TsVIH~ZPEnya#Qn+hjqTNwQvMw$m^XPx*7nSz3UR9V zuh9EEmI&^eKk*8wKW=OB`SBy^lN?nTT(5MZqX>C!82uAjr^5YedJV-xOe-^<&6;LD zmPB}F7X%*r0+Bq0MQH}k*EFPv*v~zSk(6@k;+Xq#pQj>*+rC|r`d$k^A^f4Uj0GM! zb3Z?ul7h896b?y%rz=4x)mas}$+m>@Y%32S5^m+wp2&!BNqDY6gHMTi2Ow>Jw->CA$J{c243fITLnbywyV=Y z-3U(KI-RTjBzL+nnAr1p#gv2}Ort3^+d2K#p_c@onlo08+X61PSoeM8R-7fx16X?J zpQpHelAfG{^IZG*=T-;*^M*Lrw4W>LMkWTw_~pi$=@;(gBHYXPU+Y z3RWwl*b27&Onbz^l%MrSA;ATY>S`)HD2bbY+ihtFd@rvC)P&NlAxR0Z*E3s zbN`PG~L1VB*xG}SQ!NIEFnTDJ+n(|y^s>vF>g3HfE^P3;OY73 zKi!WTr`P01@P}~&EcRPrwJ5bjQX{gBm(rZ66HG?uA)r@rRSLV#L=l-oy2A*9jVXXy zkJun^>orYgN9C)dzwfcIQw7!oV#B?!a1m$H=B)Qwa;HchR1pqe6*6q)(2NwdU-F`pIeK09ry?z-!eSNc|c_5_<<{Vq)0)H<+k`fWKM==)y(0O~UxJsX#a! zk*B-$4bf5ggV>h9VjoPrv<-@8xWR1wOf~FN(u-f>{b;4sC!n`zM=i}yG;Qn%%fojr ziy>IxCF*>xEmGRoo3E_{BFPjg4t{s%Rz3Yn!SpA*4Y2GcR%Vad+nY$Ks3Ln#ht{4( zb=uf3v|8XHJ2yC|EV_0`?WJ-Gu1 zg=t7A_t#(eLM(Ngx99j;v!=b(=)U8(&Hg4zEDh~QiJc#o=8|o)m z;p2jF+_CU%{Qq4ME{iNi(IczPY+2F=-*&;hCj9g=r2HseU73$fMQC*z-OJXx!cD1_tefS^G5z zR**_mXmw#C_SGR8wLb|r*CE^Jf)CGyy?5-+C-oI*F)}#w@2D2$JA<#P4?PcdJrN=U zu~v!@LEvm6z`y?dYQqub6_?ck?Iz_U1z7PRkgIDn*M~>Aqnhfd-$=J*vj{^SDTkD) z2EA1<#jXJ_?e32{)G+;Ae?n*GtePK04hr%HSCn<^=*mnfjoyw4k}t5@m8(CnxOYEi z-d2qr3U{lcI3X76p|O+AcXjQvOur$9*X+p#`CgLxabomHLd!*XH{A1Uh@b>Q0`ofL z&?$cpFKG724SS`bDJ3QYoSuB72AsFGL+=Z-xL)Z00CL=Iol31Xk8j=eY6*{du}-D+ z9fpLPizf9m3s-J3x_@@4xe~53kCNL^RN+CJVpXX*D;SvSP=+9Nm$#ISwg-S6HsESw zy$?uh0vT!VgE`;E9`nscuIuO|s-Qk9=2_i}Wh63ecjgx8qLJ-4(aG0af=Y^CC|wTG z73O+Wpz=JU2A|UBEJ{{azN9xj(K{Ck&l0$~U4m5JN(Sdrs{-cY?#gup(@gHAuaP<8|OJ6}$(o|*>gdZfgfOH!po|ATe zB36Pj(E?MxaPycoKg%+F2I6dNg;Y3Pz$=+v<@W$iH4t;D4nT9(5&hxciPd5kmdC64 z@?i*ng;YLV+gwUEQI5O|qL&q6iDwv$r|Q5?|GPH$U4Z$X2_DOwQH!<%nOCT`>ONuu}x|MvNu&2%Lgr^chFrE zh9r~PLs@2Dz>|4D=Qm*&KidRw6f?Nra9yrH*~=o=7d%XEJ+CJ42gT?)WUuiguhn|V ze~~f=?a_)Skv(3M3U{o(jZs0b$^b|3f8@Pf^Ajh_zX7Bm;`3qw*Cc5H;`}6w)TD5& zKHHRbj0{R?e$2c!C`aF^`+KQ&87kf^<18F_1g$gkaaP8d^txw;=!Qwb01P=`R`*7B z{ODFb2MHa;&pNm-`2e=ATsHdVcqJC*900uUBTEU+un+E8CJgjXz-&Ah(lZb?DcBMSfE@rY8&dUgX|Zuy>-@vo!sl`!** zaUp?C49n!n>QcK4eG4p=3@p2$ng>vzpD5jlI1LRn>bW%D0WT&?D-e`NzF__u<`vbH zTsN>49O4p8UYvCa{v1P}igeQ4f8Nl|;vvx;nmm~}oRUh@@h&2`Sg&E=AxMdz?rMuo zzjn&L?BR3<+^xV=iGF$hOtF0aUWyZ)7rW{y&=)V+X#uB9!C-ks<-S~*V7Z!qw|>>> zJ{G|<5lAkO*9Zy+ral_BYwwu^IEB_1^rTNwLRI6wPJH9e-UDAz0^P8hU0`*e5x4Iq zBLOqSvE)TBnmGyOTZSw{s!tWw`k(1kS;sNjNS)G})2GU^1KJae>vQ!XlLR&ai({brkB+FRi#fL($Kpqj~@sWxg!-g@oy7Z z53TSHK}s5d-+|(eCLuKY*an=Xb;`JK>SXTxjQSV0>o^t3=fgW0!A#;NtF0u5-*Oq= z4KP0}>hw={!jt42zpPTzOJW4f#7sYje_A=-X$1co{VkKafy?~F46;ZHJ!%A=;4F96 zHMeyq_LPOAqgj3b!K7OZe&qni>f7V&4t=ZU2VnW3dL_b9aVpe6c2SfQX)3!$5tX3P zZqLjBt&4$0Zkd$lYw0dAzfk`kDK|-Ho>)dJAD0hDWdhRCV((EZ()g*~l-jDFA~v3LT;MS*Rzx3D?+De4;zuR_evvS)@l|*p@S< zmhE4c-+)H1Zl(px6$wMfE;K6{+0cUdN3)zn@>zF$$-P^LohDV`NhmYIo(H1bDG?x@ zz)LPM4Z8DQK3!;Q-0-c}o);~LQ?F;a=H%lk7i8j6$Laht8gg=IiUWW>qxU)murY@a zVe{tm$9M6cLcT;b6AN>lVZhU32D8AFDj}%ra;Q9ZGt0`4Q@X90P>{-M-^n7^%(WU-FMi<0P36)> zuTZ_G0=bgv?lBFMF}K-{XV02K4m2D-jweU_MSTXyOX(mMjT&xmWRp5BGT{KnFsOdZ zvkq8$4mBO9KOzr4f~NJ#3HfSS_V&?!zZ#^*FW7o-}1gMS%iIVNvMdNJ~eYC z{AQL&Ee*RDff+?Y6*!a74|%K{8!Ge9R6kI#9$_p^y}@YhaSaMc8x+$Vt!gb0Xazb! zPhypfWa+HN+~qKTSnQF{c)zEZ=t-lKLDK~>rf+)cVKEM8dyt1P9UF@zsb7c?!}aOr zhqo%<;6nUC#VcuYymV!0S>&5(gUi`B~rbgW)?nrVM8C6|B=;0t>I9f9lKfz=gw^()sfu?Vi)R_7` z3W=Q`BOW+@UrX^UP4hG}dkK2ha#^W}4k(2gs}G_(wL0g60aIK!H-6zg(@-Qo3bz-J zw>#71=tKM51oMY!WjNT+GSw2-O2)VLeid&v1XqA^2HkkN2{jgvYh8IE>@je)NbG0C zEgu8|AohFHua7jtmAJUWd;<0)G9}M)0Tk5A5IQybZQCn|&2yC(C{g(kx5)Fw5et`c zk#6VGwREWN6NID}c(HQEZofRAepY|}b;q+?EdEFnV3zc1F}V2R2F>qJ54n2|xZz{| zRM6|{?#b%esh_Ycv2Bu9!%SFHt3&Cj5-57_uNRYBP+5RSB24oM(09CD1Y(|z&^2Iz z2CcdMEFBjk$^8K(h_GO=rBG0cte5RDh_$JNP!={OF9P;XWmXY8wt%y)RX&u zg(euT?J_!-d%*yy#yub%l>@ASV8SN(y_RQK$$V$aB5|l;Ad*sL-wr@cQJMxdz|J zp3XmdqL^m%gOxvigc<`P=mQ8Oa5?=X}`KQQ9wvpKqFr zBU=x?X5)E(?WX>;$0TYy0L)+J00fKHMw^W|M?#XDO zh!CfqXcrQi)R~I53g*C&Oh%UUKiq0w7$>=fF%Yes9!Qmyu+INilF&NKi0P&De_i0H zsh#Q1o=Au&v>b0+MzJ*V5;fZ54e*w#*1QRF_=-dhfDn7LuYeZfv&V!dC;pva;d^!7xo8_!s-#&PTZp@ zjQ^J+FhKQkmE(#14^+G1RO44K}jS_Sjg#;OCk zt@%2C`rCFvO)*e0+&KT}a(~#%4{VZkU1Lc|(dgGB67xdcFt5SY>kK2by(}#&^Dd+m zYxI!{H(K6If3#|b*Rb>Yboma+WkU!s-?UpysXwNi#tg+MA=?xk*6JZ*wDH%~>Ac^K zgDdBq2@^w{?`s+fJf$_%Kiiwx0&EXw9-w%0c`iZX08hme-bUW2{EJboEk8|3uirOkNG$_uSa8_dG|AlAPUF*I) ztC{m-#qzWAaH`dj`zKBLFyWq@TDA)tUIKUy?}F{z#0L6&8;=V(arON zlDMq9Qja0x2?@JAabetuv%E>v*GWa)7T_q}q}#e!GXd|0BQpy5x%9_XIDe#DCRhfy zoshv;pNrA^8BhIi+~b^?%Q+$hdW|?2q~xs%x2-O_t1 zl6soL-|OheRtKZLofo1zUd;Ion&~Ka9!_K_9xzXKDMz#g6tw@wKiG)h{e8YsZ^bXL z0uD4w9i_zx!hvy$^r|kWOT!Tj?H-tBEIt>+4|vbGn{#w`F2T9nu%E_4K-=nY!5%k{ zLUr2UAo?p^hPzD;6<>57oM-8|9|X8k1nqC%{H*wEYDzz9gUr5Fv;8+Gq5sP^?D3Ys zu)U4zjLH&Z&N5rr1w^RSvrRlUYjqCc2TIVZvQHdJd>_Jz4Ax435OHlnWN{bgoKAFx!t%V*dhdPa#J z6;sTr3E6|bhBlTj?-b34Z_3=@(lEK;U;5HOpveG z>iJ783142MTkvmG2h)a5ep=_irL1{@7x@I0VOuZns13wzV&aMc9}BYzMjI%X2mlW& zyLbBs&msy+d`u4W8;+}eYrZldbxgVusM-845*a!)6z3&+We(!vyo8Sth~U#DdB{#e zvSqpy9de=Je?7P4NwsS1i11Ap(eQAg@x`^FRQ=T#nw|I003j=Z z(o;v2L#Y>K_9pUKN@c#4WQ++1C8Je77|}$wM&0j+iI66T4x8i#$AOt6jtn%`aw2R% zI;*~coIsva-uS0XYk&_T)K}%sy&6mhDGP33#_|K$BWeKDpiwIa>^byV4PCvP>#E{< z>W*{8R{TqR?qMXPUkf&(CIkLMPoK z&!lAQs(@f|Wo;HvL4nJyXTTG9@zb6rp1sEs7QE1KUm9t}ceqxeXJxsyTz*g{UhN3u zft7bj1dZb0&5T?C`AP=)E<&Ngs+25=;$|pB3W^l>=jPW2JIW-$F+N3Htht)!`=+Jy z!o&&5Ui(f`mFpIL0aU%ZEjRt)ia;j<@zxAo?06 zQwO+}D1=z#yP2wr%JynI7I1k1j9baH^^1T19DjUlDv<+Ske9Aajv3e_nfKWX&OxY- zy0KQIAbF2cFyPwkbB*E}h1x?Y&0>~t^Kf`feMv%+lM(c=*jHGT|E#@N;5jZOdk4Y(6_Cd8zA)=oP_bG{TS2OoGRgUhL4sV)|dO%4-0)ZgrK0PXBHurJ6z7!0O zlTCVprF3bictHJ7xTY1%Z=JD^!~I>~;`>X{1b)Ushr(PjK{Bd1+4JGGgf%{w=5>b7-Sf#C!$@Qv6_yrWZjkP!1mrIzV?c z7W->~MIT+AGpP(*oNuyclPAA0&l>nD>cTP{@~#!<_}Yf#C*a% z*&gn0U(w2D3ox}krm071!GI+Sc4Kg(rrl`5U{!vSbiSE|SB%aiDn5i`hzu~|(u!5~ zj+lnvKD-Hmhw4zqf}Sm=lZW51c-{t{i3yE*htuJqSVY6-6iN+z4O>8@Hr#<_uF89I zuC;&Zc@!aNPjw$8`$PU@?Sfw~GAi_XW+?x_K(2f?0*$)SC+k-x(QfDOYw}=U#odfo zVrnm#g%M#xfv!-c>E8RzS|xh~asZ*|>Y`zlM&UxPlG=B1Zvj+p!zrg^uEV@!F#pF# z2O3Za-g>ndGZ;xFHS4IS)C5SZ9AktPL4XW+J`=su&)%wK>qF<|d1v@{ZgIXysH4<= zix9&ehq*MN3MJlNj4!G54&H=21pdCQ&mY{T6}>OHW5gJyH!%}28A3NxDVSIYC3=ok zc?%~0%MC`2Q7amFw9Gdc?>Jg_^j$;MF-q`pF#Ut_dBgUsux$(v^w6ortk9#6}rkt_#?@iIp>15*mKMBnw z50-@tDdz`vKDuUDqQ8|Gya}&5(G~)Fmy8iLF&}WTEIWQ`A)e>Jjqx+0JkLSHnEKBe ztO+vlA_2&ZRBDESytA|B-!mMJj8@R|c>Xdua=)W}fS(_@SbCrI6;BJu(9TXtUgt6p zsvOvtYjt?}X(yeMb$?0E1Fny8s^+MtA6qFSBI}^=Sf454==!&!9Vu4irOSo4^WLlo zwBt)L_$Ve7g<@Jf>Eq%ZKbJif*E}pvWlq^`)JZaoP`Z5YIu9PsBU;^tu@mAU?F<0J z)BxWJp(m@qsQ+9Yob%Am)mbGt5UQZ?mk>-5gglf5d8?qm=ub_oE_>BLF*8vZM03V5 zAJ68@A=E(fz{Y3B4jKXIj-YL%gMgaW4^?hEy!N7t8{R^ujq|{)(ucvQAJCEBbbR#; zG(;%+AYwC{0M z+L-xBkXMkB&N^2P%N~z`J6>p1Z>Hf-w8}&{3at#Xk_xU8tjgJ`FZpw-CWBHjuOkEx zGm**rsQpC7?d*M%$$=guRemtDT`)R2VjHYG@jSU_Cz-fmDq3^Ac@;HH9Wc&Z)wDDy zO>-b3Ksb@9{-J)qDz;5A{j>()2A2|`$_%8*Y6(MpLSFBHaE#{mbvNM7J#}>=!L8>7 zwMSM5h8-@6z^*Fb;pT=TJ!Ta4M{$DwOZ}Tkm?i2*80F7`bCyBFMxq1_k`XYO0omqNp4U%! z5H1ZOj!EW+=J|X6=JIFpTy@ZP#p?*gd_r1#7Uv#R;S|@L8)|}L8}uEI*c;+-gYC5r zbhrq7b=@eWM&>AWhZW_W0A2Nx1AUnA&y3L>zFkCS3xcGf8>wIkOXZ{vD2#TIt;y_Mh|?O-mA}g%7m4smxDMUxP4G3+9>M$- zEIiF8;z^~QEl_Io!Pd635vOAho=kU^xhsH!P-mqgV zy*_T#R-ur*^AiIN))enr*u^)fcC>S+F^MhE^Q(}d(96%VBhz~((y!rv7dxxQpJMB_ z;=f1;%@ROnYib&r-*rAN%s&3px)%a&_LZc~fXrL7n6+3}DB3}eQnn_3CU&IyT9ISr zk`SJ)?aLi~D*uoXgjbJCF0H)}(bp$NWr{?gD(1rs2T#dcrx7}@xOP98nTQJicq&eX zK@Mkh^XX6k^K|Uy%mp`n8XGopH^=$CH2=2W^EE!ug-hjEc-4?C?V{<23xDg&qU4t( zsraWqi19Yi>s9^t@$x}i?k+=kgkfk9`7FJVKN`*jxE%T-|IDJ8ZL*+rqE)4}jU)EQ z{%!O0-4-~l=iAfsl4GP|CSd77L0&YiJ=uuCZUJKMbe+<-@j8Cm##9-=LO$1Z!TP8* z2Xu1az$(&McdNoW2w{C<5Pbycgo?;z|-U| zFi-8wY;>vX@3WJ=Yx}`DSx1SNiF}p?DAi?Ffxf}}m9KcNw+;&~y^iY*hCR-?q4~Uu zaAp_K+&5A@a2}Y_ZKn?5EDXC?5XI=v(Ai)e;~99MY;BYvbxIgu<_~r4{>GIhGHKdF zd9anNy^yFWV$Ck&;~Dm3|EqV@ zef$sJl?u8>Ew9*>kj;sEgHnQ9m3?Hj1)S+CGGcilMs?DnPOQB-14VlmVy%wa+13Bc zQIAM6BdBG{3t1?NlC$Ixhs9DOU%MTh@^=bucay*&bOIoNaD{Ro{sdUC?P$1uX-VMB zKFSne<=u_S1HH5kAc;U>mcKtzH2QG_I_TIZx0++Sr+FWTgwfOED_yYNpN4~v=q1ZCKlw`H^)tf4hUt__q32S7+rDxiigr-Z(?z#i!!wt|; znFQdQB~E1B9s*}}pqkMDnpb%O=d?#HMry9ER2ME6doOpR9_Gq@K&clZQnv+A+-L$W z#*QZ~XiXF^!6Cgs0s;-qCr2!+OV8^L{GquCTVI3sll;l`ry5h zV<)pGgQ+=nzF^Vvln10`Dyj$|(`J%`A^IE-IQu4hM@awQ5<(l&NM{!!gPfkYREuu$`+b6>Ji|F$_A^>3&c15RE?g+SxqkPfA7*S6u4&l&ANe7pLPVAOEldf2xQi z^Dh@>)A%8;mVjuI>{{_S&QX_FqkBJQgtV!j6)S27^-S-*Ql2cW`!iGF4@Qc+Otw)^8# zmMyH-X$CeekO?nUaHn@;S~oQ!e5UT3uFqXz`0WVyo$;?i*KJwjSC+-a+WVd(@o}>& zcVDt|TH(;>WvR)d-5nAQ6p!5d{newkG_0KmyL%GrTbOlnM8_2n<3S#QH;%#EuhENq{~aC$6cuu2F#CBIFddM74U+7>veYoB{guSG~Bd-(9th3d{~n@TDze zrZU~rvSuf&vX=>NBbD`s-lFmJw;*l8Lp)-@hJS@u@rthb^oA_&5_=O}33b45mIc&w=AP#l)qbXeoR-;f?jiUmcMhk3#FqOu7Y6Bd z0TKmlot@|Ua&^|#wIjCWzC@S9eL>8L(+8Qwc;p8$0394h{Guj0ojFMZJcvr;%x6&Q zhiLok^3+|YbTZqeDqcN;bg&I1OwFhIJ>X|z?E)-kcdnT^qjB_qSc8E?r zQgEr-m`meqY&2a;eYgpbrm%rb`KDL=wP_G~rnr?k#FQ1QKRkh7LUEDSY=;6F{Y#Ze zGQqy_0AL&+%^7l^8@l*eYgwc}ud3Jqx#|w8nXB65&NJ@e9Y=2NgF=AzznR#rHHvDE z5n!r5o#-}J%NM7G>JV2m`?h`4I~m(mql4|0UvmtUdgQ=^2&(1gh6yRdy;kL^9!eyk%^pRyZBqVIdpZuonb#X6i`S35TxBEdAs3u;5f7 zumWm5NatQYD6d;5I9YIETP-8{y&~*sdD41ik}5r-T@VcF zg!jB@2T52((i&V+{A)&(69)6*hkzot2KshkGE1dGST2t#Q}BM>+W3Xbzw~Bu_Cv3K z;aI9#5DaH`d;v{{JZ~3-NI=pZaXrp;E9zwvd@XaC3MtU};Y)uR3e%PT?9Z%2O_p(-!KY5b#DvBPh|-_o~H z&i41_IAhIHm0SavBt8wB0W75}df4c3`bg1N>}t&yloan;{|X=(#Pe}V-d?`&DE^F! ziTM07E0>pavyjglp5S&U{O+Sk^G4jP+CTwuKMJY{-`p^JPGOt__>XAjKmuHQ3jFS8 z^R1pm!H#bMJ;Enuc%A}usZseBF2j6JATu^s{Db^QFESh@cUwUgP=YHe0p?|tiLXH3 z5AMSuSn-SVvZ?&~R&L*5o5f0W;LXilM<`@VPhAb0QleO6Zn3$tqnWyap~xT^pHwz{ z*pRUxBz18FTebqvcq`}JJ+}8<5Oy>_haqn?YR5H*2HBxP1gFecI_fTc6iR)&&iqol ze~_cdJ&Oq+p#^qneZ6ouvitq^Z~IO5f-@9M$YKB8Ycd`WNk zOnFoWAYr)=HQtEDQo@k zx!YEn0Cbiyr482+kI9@~MIeRL+P({8kdlKmdKtMbKc=HCH9wx&qJ9EHfa>tq(;-uV z6(HkWgC$~bvG$dF--0A~uL1R6t+=1)I{!y!7sv~rQa^-IGhK6BRR?uwL8SQcH zw^vcQe`K~ato{V%z#5koXA9PFQLrk-cAUoIwxN!}*UYr3&HVO$)-gvApuwU&zV?>) zjJ2(b-mesWPNDmc!*3kNFGj)HFwB6U!9uRqy<`!p*{U0+~WLOaC@rc`x=W5hCQ zq7baf7trJWl)+-{WRg9g1IvK2#N-u_Xw=pmu=!OLbx$3*V262!D7b<)Pk1O}svPng zpft=%sgi4IdlnuLVGp{O!k;w~eu9*O*aH=*ybwcfvc;+)ssnU}eL6_XQdSP*d>WeJ zr|Gwzw7ddWA!crJaNUuL(TAc&S!tY1-AK zoWA#hRzxv|B%8Pys?JpEhGi5IfvHd-%v&esadT2Y+Mkmq4a;3pFC1+Z`xB9owU$29 zc_TuM`#+myM>Hkx>$>qVw0^dc0E~VjUOYRV{u>3ezh`As7_a?AGPr z^m2R%0|ML<^MT1=!Hx-cF_?0Y3SvNk1LuGkPMUicDA`;6TY@etqgUEU?)Q7!rqJPU;nZg}`WFDmV z`f1&OT2Q8)!@YAoFIRqWq3%$VcV$8-{cERCOEC!)!F z-M;(>(K1+htp%5Xqz&Cvd14QXQKsLok>vlrcCJ{|m_a6y5Ujb+LP-n*d^-MQ94l4_ zOgY}PGxtuOXavogSMY^&kQdg`+hRK=G1yM`&0UxqdRxCP&t6+*AKi>MufMKoDfX4| zS#0adZ*r5h5Ilw=Vzx&_hW%ZqO{CGW9`)B5Fu@W9!Y`JBGA5?VLjfI=ue?kr_~GXa;vpUJmev)N5Jjp$oP7FXBnM1gPT^O&@j z0007vfwouWHg#$9a7Am*R+#DlYe?W3J0XoR^#@;W1F{#+ZX9|bfz{!MgUJE$s!HI2 z%2%4|6{*5zzQJSxOhk> zJuX(pMK+Mx)gbE-ML+xSeAwz5g`s$Gb&lhx&XX*TyX?0h^N0}#{UZ{27e0kk+a~Jk ztTZN$xr-vPAzHoI*`|{kRGNr+fBgOOsg}>DN^F-Ga+>kFdD^C^dmmJ-Z8{Uzl;mVl z5CTrpjymGUSkgx^nNW~YpY6(AWE2f`!b2bS_FD>XsOO&fxj`DNBVo(w+UmqN2-v22 z^-MqfZOo|ROaKVMtNPyzv2K-)1=E{MG9;(%lC zo%T=~KORgcQY8S_cOw;pG*?w-PE?$uw7tlX+iY9+A|bifI}}oy@rP_J_<12sq}X{v zKmegAX^?4M4I6t>+tfByr-VDTBUih2=j8~Mda>!6V!OjqWC%d7*MfBH*T*y+bx{;) zbnRvn)o-;b8c}W2!h_2rZA_CU{M2hFfdQoRM#KD#+;1ApNy`w`3isMucidU4=HJ1b zKV^yS+fQfPs(u4w6!l4<7b-0A)T6Uf2XkaR7+Hc{A;^P~e{@Tgq=IQMdcP(k$kEeO zx5gh85-RnJbHL9WRJT@Yb;j~-JIuzwq5tkJ2V3@(XQcF-htQ_Vw!&;)I%yJ(Z^by2O?welxOE_oNZ zse~?sh@V-@%DQvMF=y71Osy}Vii9>qAhzd+P|J6O(8y@-_)Y)tYpyfkkGjg|KSDHJ?DD&xwEP1{CF4Bq+G6OH8Y zC%ArE_Zgf4%tSKlo8^)N4T7R@bk6nwEeMLH(o#T-OCSWCp0u z3!0#xj5XvaHjHJ)`lx#~OOh~bg>Q*on1*$75M@0$tcP7$^)i0TH|wgPyintJ6--jY zRvOgakJ+N!_9+WZ`D7RbBmaUd4)Kw?k#|}K7Q)H=k8jyQOsq5;l6A1VQ|_%QMhE%i zEs{LxN2H6Z3tEk18ws7wf%iZ#lY??WWEu#Hq z+*E0bC}rsL(yi;~fS|)JDm%PpY3ZqU+*Bm;$bRgWV(}zOPN#5%pVJeh-@ZqomY*0Y zy0jDeVNwYaMGkjph}(j0M517`Ir~_Ld6o*?jNHOghStEF=dDwROm^hE{=Ebvw3g7qAwJzPc0W?qVd$~UjZj7BI_|}clWF^xo?KRsACYIo z&TsyiW*7ooy8nwzPN^4XPV&s7PMIe1cQpJT&1G=0TEaGD3A-6ku0|un)}O-xz9PwJ z6~IPW5?^uWZ-RuN66Ur*Vx3j{YX`e>W4m>*yNklKuv6g@I4@Sm3|i$Ex5z=W6?1kY z)$?c~vd^R82h9{t9i45R%bquSAUlbTks*>EEtueilx@+p4IR7u9*el4w(Vi@94Z-x z**kQ2ppyZC@3a#AF>HWcL|yQMgE_~kg87=e?;z)WO}ErljfxKsyG;*aDzV*Drf>lVv$Z}Ca=!p-?y^Oh%${EMT_vWs4qR&r@+WD zoORdSmMz@p$_2;5ywR+E#Fs%UMOLktUON|cKOuaa?+}^Wa1S{A#z_iyz6;&+L?IQr zV1xum&Xrr5t2*aEJ!RP~PtQep@E1!dm;E9&XZQvf$A)*0i=5xSp_ta@)4qMwfj3t$B|}m=K|3reyT)>${$^fZlWi|DI{Mplw}Az z%`W_-MAyI}q(S9EMcPKdfeX`F@|~tE@t|O4rjZ6)X1s2v<(gIn4whXX+yV)AST;-< z>2LPWpk~ohY3Qp19F3&v_s^lQ)Mttb!GN^CZ||C`n4t7?-bKN|L413zP~$rY629WI zYJnxW6g330z~X&5Fq0a?)XI+^A2EUm6!YPzSt8q$A~NT`9!Uy15>*%GTbYM#!ksvW zdQH@cQ>>7pe)<(tFQve7>1*ye<%jPYlM`tH_O|V!^uhpX2LE4Y1_ft*f-8B2N9YVK6cz3$zWc*Kx6I_m99l1g^R7dwN{ZeRW=_Of zomRKi7x!aDXb@ZiY2n)zeVz7%Y$h)UQm1Fa4YtGDquQ4{)5FNd#}H5|Ofo7VYcc1k z3E!U7(UfN9hWQL5Xes6?YV2HI!tZ}AG2hNJ_jme*RZI@#55|#Qb#mMJb@dED9q7r} zZZH*|TX(ODIwKu&txbB}$S}Zrzq>`05Wn$q9j2Ez z0JsB;IjuwLpC{W{d~|UMd7htsra*S9s%A~sS-du^u0nLlcS^{BH!cRg|A4Tv#&(4z zbig7>Q6U~`Ea_jJFx~eE%ZjB|KaaaaTs_$JOk_vA!BBh^S}Pd0U_Q!?;cH+CawN$< ze3n)6Cfx_&K8CVIhw+wbiNc@p|G`*5QBH1cM$JaU9M+O_TLWwARcX(ki}Bu$Q!RDZ z%!Q$SGXoK3g5fA`KdxnXy=$Lg-wSfGP82vT1TJYivk=wktd84SWFcd2O4(VPqzREi zhNOsRewGm-_TvS%B-oMBiTOZUE?S6WVPse6ord1Qp4TcY7(J>GD1$2s zEv63?ZVdG1UcM5LdQ}_^N*Jv|)Fk(;$H{Al;YBSMg++d3jAk5vbw~-#1yH9;+lcUX!mPw zt|Ryl#jSn{V=kelDuo<-U8&tJy_X^I)eV)9i(K$zhnWqMIuJDs%OTHjrXcw8rt2sC zMkyf%|s*Q6=&pV&t{m z6Tbz$6{DPfF67zLWE>%$P0f1TM|Df$$JP`Wi_({d)GpTM2(}*?_OiVDO}kWWM>*yn zLjZlkgqEq({#yoboYe8JGw78%+eG-gOlG9 z7mGQgTMHC3|TqHQ=Cpfzy1c9v)QHPUiJV=~ZioH0QQp4CwqKGi~TOR;0vi ze4wslhC0O}XT;C#)yOkiA~mtHzw=HsJTwXa@9r&pO#kU~On;1~VKU4f0uLRVB%10U zCAbD?Z5niFn+7ShjbS@cfCGuM8s$7c3fi4c_Qv5@30>V1i@W-Y`G^I8G263UZ`80l z+*6WHrcZg;A)OQ)**Fey=1DiG>z2|tbRG8du4Ob_B=3pw^kTAWVlAPsdiqsc6#8-9 z{Ye>S;dnv$5$NsmeOUHmgx{2}`8}{6S1ScM%4*Yxf9XvHmL_z<9DQ*#aM+(0w(#&P&mmX8um}|YQpU;DRLf0 zYGS71v!5E?R?4K%Qb$zF%(1l%r3{(UJ*o@bB8mi zZ|*eZ^>_40!NsXCYLdGyyrf9k{70O}O*AvZJ_%po1&Dj4oSzTJHOrlB;+FsW%3$z4PKgl8-;s>g*1)c#lvU(H`IT%X@xa+3J?;6Bjoi%P5_@IW% zeR-tMf8~8=(I_@ctBztvq2X78>eT++(>R@3WPDQ@EO}?NHMeVBDL)4tJJdkUQ-Nl* zWy~;KjhF@O5PQsd#mOVGx<$*i#Nh9$xLjn3kWHeEL?i0pT+SY@vjaRsPN^TtuWvJd z$r0+alIwCcFK;G_uxJP^7Qp=-0}322jiVd>Q&fCdHoPuGegoZr8* za?D)x)8Z=n8ahBJj*9~_U#Z#jKeofqL0`ykmx3q=C{;41w&R(e7`k5?;YFhs^Pu@d<6b z@mgU;o_7{B-l1UzH;CcZM!u5AY^k5EnGSgLjjeL4HuxdL@Gm|wafaIeP%K%6f}Uj#`-ty ztm=ZOIKjSPeFMBTBk~wpgvjz8%sfLFk}>IIpZDZuB~dxxpZjNWuxb7z{ENHMpce1} ze+BD49gA$IM$3^-T;KuQc!AwJ?d;q}mo8l{aJ_d0NgKg9X3&lp`eo3eI1H*W%j+IP z`9f5^zuv)O9Eo<`Q6MRTiqs9rZ3>PI&bEdU$8&DH*@m7(F5QMrqu|WyAQLYZmTB62 z+7SHJSm>oX&No|ychWjA77hnGSF?Dn4_l2bF)U|zct>$Gr9{-7VO?7y7K&9+rP9k! zJ2qtB3O?vG-*R^U#r_1Hn^%7Q9^SDl1br5?S?XdQ3yz+-jeG=LXc?T5jQJWNov^?T z2omXIt5`)TY99N|_nx%oN&KrY;=Q%d`-F%erwc1A7Z114aC`I6T+|H&DQ`#q+YeM7 zJ{@X~P4Zl3v=UzD26Jn(`IzpO@{+ZY=`{c9lb_mIWjRiE0=8w zb?TV{bPer-AS)gYb6}D2NnR;4pg#5W6z%oub93z|y$5lWkc@O zvfSPU`^a0Xd@*Dw*$O`%q|#+xMMS}FaeN*yN<_Wn(SgKSx$@ek{aiih0`HNjLGFRn zQ5@);RF6$8qPe)ZE)M*-it6oOGrh)bD(al6%aXPq_jo&<9E7Q}==EYBGDWUY&h+yg z_u_ty?TKVoZwXX)B?(Vae+D`rLl8*bhPQZqc_?%sjxnWTxRcbLyC2Lxp5)oo7^Ha6tE-U`e|<`y#AQaZ$7Z6>2*zWoe?0b^P*5HTK^fA2VJRu| z1#0zsy)zAy0o(O6z2kXn#evAvc9}CKbppYN2@i|HMj{ioAYB0!kCC$=!zr+jJFT8& z?J3Oux96Q5n?V~OC7-7&!Hxtt8RFyf(_}J;y{c#Wf}K*`k_&{i{qr@}vV=K)oY?eK zs_zo^6v=j!rBa%?aSC*&TK3^PMlFE44-}3os_K#zE@p-;O7$K|zT?%i9R<6_%h}pB z<_9@N#KPfrn-u-a3V?$1dB|;mm#&1Ra<(QbnNH>qp9Ba}K*qUVJR;XM$e+P>KNrl0 z$hDyWE|n@A;*ic{`moYKHvB^Dsm@wR2?f7v%Au#rIr;Y~OWlCRb zM@6Aav4siCM=t}G+{-K^SXVrsZ{0NtB$!tPA}3SA4YVR^IL#SgK_xKOD!S<48SL;M zr-wtQ86{pOY)N{CMrOLWx15?#gYuQqHq>Y~>6^MgT$YeI>BXbG32d*7hE(zn|LQi= zF3U@}OgIQ$7-SPWmG3M+7TeX_heTb@L>`QpH&ylIWkZ*)@e$-ty+N9anwEM)s)Es7 zQ!12UWLU;v@CLEMO{~Ep4WJqX5Z4+8a(_VvOS4;PhzUwiU)^`&U__%;j0Pp zexk@LcUso^Q<)c$zl6t%04q821u5r{Vnlna;Mf;8ZKA38cE3^0GM-a}2D?+-2O0rMP)&%aZaB@g$0`Iz`d#}r8AOVVjLJ(9JC-W-lP2$gWm51T7 zEvm%?_C^@Ks0lO7usQf}oI0dOfZ#H`Dj1O8FZ1ML9`NfyTy<{W#=(@KSOtPl-i5|j z6!g=a*NOLxiy4#MB`w;-K+-D#wN>8yY%&sdrku-ir@!SWEntcHoI41NY_JW`d)m2qnf*#|Gb{ z*iWsd5<_vM?JC!dtd{)?F(TYGB3o&wo*b{&6!7Ef9@oaqjvE?L>!-@eT_%|`pe_~Q zs!SI}g-kdw!NQd3q5AM>&8+pD!OHv+Z}fW>qUMu{9|On%6Sv%pb-%5vyde!>B0_2| zg{scPy{qIJ*EJ~O-=V8dwAWjTO3?1x*BaPzR9LHdc7iBF73V`lU}w<#vujCP@dyO9h2kt6Dl@bQ z=6#bwlx_!(tfZ0#>>}OpnfMIfmtg3t-B*&U*{#6XvlO(Rq{%E;-xRxTIB(XnGQA1RGuL5O>uwtlD;hOC7-K64I!WVAi3vvd zuv0RyFc52-;ID?D`dHo;2YVg<7F{H`gzq#A+VYY|N!}yWI-6za)ljm9e8bIvW5m>7 zV_Z#r(=!=(K=9EFY9@_j36~F-4Yr(;FmfyN$?LyJW z9K$N+$ZC`O1$k<JIaN$33OwpGyEiK)%1^<)g>}8eVp{`Bfi)h`BDU6fHj!fc7f3{k9tI>O<*kZXJDk z-Kl9l9V6l^Qw5}0S7hg~b`d|6C%|-v^^ioI0~S@_MaJMtaT&4j?)WL`F9=bDG6Z-| zo&$4=mBEGxxG1EVfSA99>546lbwVkQ|J^-KZ~nQo4mD&yk|Jy+gEJLjaa1pXth)f& zuaA@k5zMDo`VY6H302Z)Iq;lka1P$1B1?%9DkKbl4>#1ODFHp~jkH^y2=i|r`Tx4* zAQ|QTNDvj-3CQQ4UtL_#7=2Puru_G!6bp+449E_G*Xpd4yu{k?z;Hmk&}BD};$ZPg z&?7CQVC7+s9BkY)ML`)!h#}w^Yss^k02Wo;@Y{dquD?{a!qrV z{?wyr6l_cF<}JlrQu_}__>}e}D4K{oH7GYmS;Nfu;A$QA5d3@e2a(H&^68s?3TVz^ zZ&@S0$ta77fuL2L8hJM~(7X?}0SPRDJb#_>sz34`+L4S(CN5&}PjyARyzQ4ooZius zjYY}sI8csL`_getQrE8&DM)xw#DN{MAX&X za^D?CoFBws8Z7w370y#4f@5+3HIm}vt&U0>^PLPH*Pb#J0!%%qS%O?|dtbKaf48eI z)%z`;>wHUdls78_pj)HyE7!r!pFwRE$PdqA@5J0nf+FEHDBtyfeoi(}b^E(xEt?>0 zV<;X}s4^6iwrH-J4kADF1#lu=qF%jXvyxRM49|6RbV@xx1ir6Dx7-aCXV#Ay1pn)9 zv(CJMyU*AY=Eee%kr54M`g^(T=k7}D0uRU4-#Qxd&=N~BlG>Pxgw5e<@k3eN{BAGmu&a4f%rVu`O@_K&gn;%>ia?i+~YjG1`0rp{_q+ei?0}kOzLt&&7c+3bh1R3Jw_>_}1zwJ@} z|65lMb~5%nwF^E4QYUJ>xR+g?1B`)g{7PkIFYd9!LkOgNTcbi8+LrMxj>M${r_^o2$Lgf) zIz{lFw-x9D2d-3{q(bVBCGvkFDg&@^ZTQ#oo?PouN>GB4@ugC$~_rML6gz|V~*x}m#%>WpDidoe&=YEQ_*A*rJFSKlJ|kYHbC z+2Sc$5>!fy4DuMdl@Y%5Mpp4zLt^d?-CBp+-X2a?gC(y@O@~>+vjnbUsM_iyXlC*)Y^k-P}ZR>I9y&n@mZ(4@*ud0S@Tg$}* zw@QKcp%?!U%m_7ecUA`9vQ^EqoVI-hSTiJBsJSy~B(8;K5C`coBqyMBn5* zBnNev~&Q4bg>%2#5)f`kW8!4&wO~H+{o?7xTg)#qEjPbqKSXK5LR~R#a3wWDI;J`Jder{hl<1mePw}$3k?5s@j;>At!j`jT^0HJRksU;*UqqW!vrs(zac%>wOZ8Aqmt0 zwDwDQSc?V3=seKCl z1EZ&5wEAN8m$@oty=D_HjD1*174t_LEGb*8LP!noVABy%&e8Qa@w0ZzrzMkDue&(! z#I;G5yGe!xPD90+3l0W7n!Abh8#g$ivG=sVw5;F}z2qEW&);rfNl~OH7F)0)5e1zl zUh&-{HOI?9oTP@5EU*4Tg`w@%eenAksgmxB$JsA`*taSJ&Spyx<`cLX{8e1c)$->? z&c#<{=VWe;22IpzL6$G3;WoM__$}TvcYymjtnD$d67rz|bx**1vSTh8h`*1%?amWC zP&s|#=Ce6jm5mPrge@}hwXAJ9$Mp76ipl3^yvnSGnOr(!g4Hk{v$UPx_N10xGn%(B zCt-(QA-O^k3cxG2=vr>?)Z~b(6ZD?YE3zRLc(Eilabhs*m`7U^B4^unVGL2@g-Uz8 z8_C?D5(K_$@s2p~GQ>~TmE=h6Xr-MuGh%G*kk zd_FKy9Dv7;bos2$JGl`P!(Re4smM{J&M{kXel$)MK}B1rF&MTUHr2g3oSwbnjb62| z_Q~!CW%sU~3kcw)8^77x|AZ%IaNObu#>=7p{mYH2SuumaXqx&|#SO_YAQ+1_qktC? zqwb{mnR!5-tK+Vw7-Gx>no!0FGbNaKZCIus)JgfZ)Z??Z~dO@@_}&2&|g51(8_VWE1MDqs9A19ObjaLM~? z?zB^(_IExIsQtugv8AK*wV1D$ZV#B)3g{2j9|JXvtfQ~LS9FEsS=-atl@O2{r-2@z6o_t&4_9;|_V|?KGQX?3QK6%9f((()EB{SVKMV1R$ zpv!6~7@!!Ma&2AC8!B<6cEZ5CQ@W)&y+0 zVn4P=X4d*gs_v^a%L;dpqtlW5f~b*2MNG$nA5Jfm9bRcg5a1$0-O{ZcEs~IXz$lrY%OL4qM!3&a%Lf#upP=MRjqA1e`NS*UNf@W zmOtR6Z_xews0VRMw#*L2ayvW7{X^Hd>Y_yx(&hh%HqM zWcZ+~uVRQ%y7z#wtJGK$&To|@8Wg)x#?f@W4-_Kmrhs3jY*M=jCj6(datfsGo;n~X znldOJcIhU8%dgqyvAVeykDgDm(bt7dW?ETv0{&h%7Ti1Bx%8(2@;Im4zEcU#jk>AG zX2A+BJU+W&&2j5@3dvC{$z$0iv2+HF^*VQ~__?Z?V=~0X61WHnR|ii3Fd@O;$CIs0 z*8)i55J9DT zZ-W%Y;o;~c4_@H-vwTAVFDPIhvM`wCz(f~c(5A4RB+)o@JdBqfTsQ~^>g5-{7}ry^ znxTJO^)z;}k$HXix948kiT@P8K-{7nDL8J(2R>Q|f(kX#XeHhnSFO8cTIu>>scAAJ zE5i3c&CgFSK?+kK#_q^|q2wnaMFOz{mHPO$BGe}p)>HaNI3cER* zn#0Sxkti>w#Sph^xS>BX5GruP0JtrvPt+gW>>-LqWoHE$yI?_v086wy-a9+qAQHgO z=HcpZtKW3iMyC$D?b&-RmuI)#xw65omm+J=K zO^3GD=gIsoJ|aGcTXfPU!Hb;qumhNOdhR3W2RmbNII=0A2r&fq7`>h(b`}=d`%A*3 z%()W){QVA{l>! zTIA8>gs)Pm{Y6(USP0wBZY%smP37zPTGUOdUg=k}v{)kPOIpcc6V0o-`MH_kLb4dq zcxM$MS}<6?Q}luZs_GuijvRdY2LH4za6?)D+kOf4zLFXYk27OUIb%E6@s{Hc(*&XX zCqLq9SnoWda%4%FUp`P?Qi2XB;JaiVhAv4i+BS`C$f`!vtn@-O{Ax5)Zx9xR_#KC^ z`%y|`5*FR4uuTL-OCrdHhafApw@rQ&ZlK=j|06Us^r-3iDeib_OA|Ov$)jO6;16T; z&9+$#6h+>d?Z>?!6vihuR(6Ml6avDu>w+HxZuc=;%V;W3UoCoCk}U>($I&8PCN>#~ zB+^P!xllnf#Y}+;A;=GhK=A_&*fXfXA?9JrKURS|T`I9agVa^4b#z5tqM`g}Bs~{M?^S{;Zt^KDw zo^miNoLB`=7l!Z)G=15WIi*_=wf#$U#@@zUHgLWcG4%)06>O1h97-bEHbgP$f-vsl zJ*VSF$%w<><{FRVvONo3T6`KPh;&O7DAbN4U(fHY1`g|HDwrhTosSP51y-!JEmera zja!3EMie>s!U)uSho-9!_)OTLw3MhyLDEohEzOY6(HdY?((zSXe`=t?RDQ(t@}8E5 zPaKt$f=VG!9l)t$5t&vox6d?|8j536EB>*@xU2D zdYIw9*asuSu~i-YprO6(bb4a&8_!~sL>MM)=S2>jh?wHJdBLNShwBG%l}Qqg!pZn* zbauj8c@Qqgii!6Xl#1pu>6a0F@&Klzls!mhn7`~Q?RzPDB&cgJa)sRPP@0q9ML|{u zI0y=|@X=2&#nJP{lCYfVLpFX~|+}16=e}tV1Nv7&YVL+u6lt{w-<|RkLdl*W)%i3G)7p2pIp_GoL*~ z>Z6`C!6ebV%Pwy5rWLzjLNymidIx5hop$56*u9yKe=l&VZ)aT|J+RZM?uxB9IO<2% ziSh@Bf?>~LY=jb7pq+8*3d6!ZCNOD%SzG3V%yqn}?`I0<%IP!3Z3-OR%#K%7<%*`^ zgMfl^g=icUAX&HonPAZ5-@`_oVefM&Lsw%=6T?fr*vUX0D+y|CHY#Ou*FWs14*LMA zPLJR$K0Zhx73R(!wya#1cH@vXV1;M$+r7yC15(9`+SF($7>0pf2hQ%T1~_3e%R6-= zEnvvX@}vJqk#ep84PtV>%;Idf&H% zJ7e?ev_oHxF!%orSjzN356cYWqEU5lpNg5&e;DON9cNq0^*3dm?gMC>5`22pRmKVq z^#J4GwQpoAH6!&K%J8;|Oc+ih#(LOPp^K~O%Q;>@spuM9m&n?+I5YPQ~D zDF|KyH94Q0fI-wsK3CPgU{(hZdHIZS|I+TbxqFFN$^44Ce4w$y7kI4y_CEAy> z^EO2NmZ+kRzssa%y@RPM=GSGIn6c}}8FwpffAEp=-Q0eiaDMkqY?P}`zK2K$7!%zK z!ClszBBMj0N-Ch7pImh#S~HPC`(`Aj-HJ+ja!A$G$S#)Nyn=hq*Q3HnwSZ+|`GsXH zgWws3tEj>H>$aCtzo%ZGJ&rbTwZ-Skw6fuN6Dpb+4_E;RIXQI{UnB*Ik};iwr{Uny zqZO~E|7)}n2i3dr^d;AgLm(fL(isX8uYg_MnF9k<6X#uY$FS^}B}*CoN+hH5d@Z!w zdt=-zA2y7~YB^I?j4}@!L&Y*BP6sQbtPZ1QXGMJXx*Z;;rhXPmdGY<-nr)Qu~q=mb$@00Ho%N|Ds-t<`1T}Gqn6GAJ2<|G47L!b+v~S z{yEurDDFl6*OU2*J5AWXifN-);Y=S{dB9E+9|z-neeChXP7EV;`6VZ8-YA7RJCYuH z2rX%8dClg}MCT@xm{(2L;;A6`<0Q3xsA>3hn>m4mtBlzIypXP0w#AdsZ8M-1JO@5t zID__zu0UZCTCsf(UJ3g^D74WW0Vz+@^Umx2uAHdDK#r#26=@Kw_pVgW-?}JFSAxWA z9w^jQg>r!pD2##+rWJ`;TJdd4tUVfu^~WWDZBk_cmEtN)Y0Mk#3+&J&hw7g@3KGS_ z94_xSj?_3%T|Fe8keT+=I}*f`ub!RNC?i<34l(E3>Lj(WWKA;+M{qOkMI80HfHl)_=|De(V znJ|jzq*IJK@(>QZIQU8_jQ1!%Q=o|okpc3Ao-y^%M`85&Ac5vrIA-aDvSD!aPH*~+ zAIj%IUYS8L2w3|;FJ(b;C`ri^GiX)P?7oSVFE6UR%0YDy?ME{2oL9+;sI3j#Kx z!wKCC-jl0u=b#=zi;%p4pISTbxk56qvV9B7patWmv4O$NP_16F+OE|vnez4gy+n%T zd@a_pmVmulF4mCC!m4Dp0;26&wjc~o{g|gXy|ip!&FB4G?#Oyt2d&p%q7J$au_3@` z*jCr~Jr@j77Q_+Z`vmF0IrXuVJJ3O)h3_e$9Wbi%ooc|wD^cVlQwLa{gcg+kM)4#z zPnaVpf|M|aeb{7zO^9@!y_poKZ+UJj6_X!8cma35sTr(3%?nr0uHj5l>iwZ?52&;V zs7GNSe6=+GUsozsrYCa^cVYz&j6ltvs7(JX8RVpO5!`T54+LI!ptrHjJ zZ?9!u({vVvs=k51@6>xP)1W0-;^IW+6WoLGn2N8ZYT@blEGh}Em|jaP@w@iwKHJ0g zf!!xa8{{QL!9oZKv5RBa2>QVC(_)8Ca|Cd|g*X0u;)dHzEagN?1H4JPwd4eZ!)PlS zJnzFdf>t4uF%&Rht0U9ykRH*X3!s-s*_UiLk(RK23_E5*z5J`ClQ*N7=bcDKtT0le zEkI@9SU$JTTg?~yy4`w%_FA*@C5P=fZejddF#ZBgTM=Q)oja&6GD+g6Soxc zpJz8`8BW7C!p1f<+JTXbUuEJ>28Gc-oFUK%w;k^E6g~pI%i#S()_VIT)O0BKwZZ#g z48^$4_6HO4JF$B#Gf;CnPZ%v1WDQ0IMV(frs|?GKMV{+fI;B}m_!;X?YR zH37jQQ{@Ai>64L>?Un5C@r!?U5N5Y)a=6TY(ZN_2BmDio}9`7wa%RFQgmmRX{kJdKUYT2@_*4)%dEF^|1Zd>Zw7(9+T|MsF! zJTqp=i&xNP6!IMRBAQ6-gNF1oWO)?&81S2G1>@ zi!EjIbQ@Bv2qEoSsBPnRZL}`|5AlWjVBP{?;jNsyVv<*)|y#D6dSq2 zThvkWkQSS~*i}*k&Yzk4(~0>@40?*IoT><5ngorj%kVZ}klkd><%y5pqKPDol!Q*0 zg3}1IiYXkaB3M!n9HJp2)1e&0OV5T1FtNQ(#|_&JW@b&w5Rds;%tmeQLmpNCDd1BX z6;j!67B+y|ymk#5b-oushT{pbLm5TCBWQqbF|nAuQ6Rbo;#=7Ob19($N`Z-+(q1e7G7s}s;roBiT0BWff6%R80&IVV0YfAS zWc53J^jCS>^C@xFu(?-)8{M^}*R%lY)%^_%aI~I+n)+<<2@4o0d4_zj6n$_%Ai}a^q?37H?#!9bF@hs;&+z9Z+1P7!xM7b1F#Hr<>7!Z`b)kDDy zE!WpVal8{NJ;1x_f>gc~_4odBq;%g{nfx|`_1{}jnf7Z!bFtWjt zc;qUFl(=DQ`Yf<@9Nh&pVH#w=g~cNS%ra! z=xxFk4yhn(I5@n^#b$VV`8;XY+$)II7mx^qtU>AwZREp10K^sujY18cqE+Y;uibdO zs{rDT^U}n}J|`?)fI6-03+wsbjYFr?U3}0PhjwBoF@Dh?455bLB4@u9N{7R3%1&-RTqruc?@QPIq(=QF;-;fnC7+M z=G8scp-W5F-ZzKU{%;sA)hNMi4u8#^7~oKTaEkYL{kUk$qH%gp#FYbAO`t?vrO5}^ zZG;w(&SXZdn9S`^#xo|SwNX|QZ!E-a`;Pb#M_mG{!!juVcbA2`Y1Im4f-gDpBGwvu z&9A4+gk#b6XLxx-JgFYiNoqr*<5?zjG%#nU5u5T+!JyLm%FR`c)DL>16Ma=q7$Z%| z$!EuSIJtMX0Z^7~Z2&D_lQ3KXTR0D1tQ$K4ih&zYW5d|R{tl@RuHC($wHvJ34f*3? zIN?WLz6S#w@Y3(P8Nn(+2B?(Nf6WP^g+{DLhfeF~M5TJWD|&bG2G)BBl*zjYYsbXR z$?}WMR5Q5R+09b>8k`nt_|G_ZH78rhMRuT}lg_mHUBDa!&C9 zF+$bK5TRcEVy2LfMhP_kL8OL}v)X~*C@@2!w*j!$5=&<3E?$MQ7Ij**{j_P#oeE`D zTlmE3?alRDMajnw)yrzbwdUQ^OgVi3RYCC05g3TJs-&kloT=37k{ECXgEas753`+V zNq_ZY0U^!M1x#0r)2zNuq!IlQ6Aa1A7GIFP)*&X7#u1W>aA;3{4LV z{_BHIGW=p|*_l@b=F&UJM8>Dj0{twpIAibIu3Q!f#-g~6QN0fr&Hp4~!k8>wOU}5& zg*6aeScY%-e5gSjeTG6G=1MsngVCz>ln{s}sSV(2ipd+o^<(@RtMK1CZ@uzbGcGNK z1Hfb?WSYb1_5BTt>)+GW*j6*&h{Q?kuYDWa5yI8xBKCfh>J~neDPB}#BqA$T9P|S1ihh_~@^Ou7R%e5UBirSj%;Kt{YmO}2emxnbBy^HTp zh>A2f5K6w$Z}M~{F6!Hn@HBiQl8LU_09ODj%nRJ}0vStQ(rr}&)(Ph87_H5x1z0nH^k5#7Jp2a06 zxcDms#seOQVh`T3PXz6y#CFaui0252{(9Q=R;~K0gsswqK|SIAVhExU!hMjga&-`s z_~ex!zl7MuSJu{oXJ*ArN=vehS1Q>fMR`Dpyu6~7QX>+*Xiiu!@G8Xf7<~s{<5R70 zdCbxSOIx9pMtAlJQu4F!D22f-L7(x!a%5wWv0s8m*e8(x5j&m@y^tiren}{HUi`_e zP;u8-mhTk2W1z$7Oa6hn^2HF2|3AztoMl${|IlvMK+p#@1_yaXHplh91;xBs)dQJQ zXlmjG*UCKSYRBC%L?bD3Xf1Y~EE|K}1o(DsAT79NkR`|>vBDn0i1q=1G{OjE;1!o^ zUB*0oSTR!AB%cB;jcR|>ZL06OdDa1`mWduNj@qP3h^!5?Ti{w;3>nD8u!t!vvDF*oPy|sb04$P&n~Hp7FC3$z}IhOFoqAZjGcdFe}@K`D^H+W1|4V zE0(gPEWtMiPAfJQLSn9Tt?~3*D$*gwpeCeh7oA_~{?hUXbAJS})C6A`;fW3bb(?7n z*9x%m{>#+ryxa9c`(bizk}2nfGc@fv(q)x1?r3;IX{(ATRT~Gz5i_K4&s91C z8#J!i^R~CcqB~0E`5(@s;wt4o*38YsQm6$7mT|rivCx(<6$lz()O-q^V7dYNdQ3O- z`*T|Fz7b7(M0~e4eS?013AqYMLId)YsH0ah=V8R?N@urnDmC9Hmb2V-BMZ!ul)MN( zE~>*NY9BTE=1Vf^l@*T_bZTlBm!+aDRxqlV~aDVELmbA2`w2|9oy5ETZ`y+X%QK zH-7i=<|W97onS2rLe7w_8LPf>BJUd&Q*P>bT_EmJ^|%G*i2~*zw}6bkf7a^9yjI?8UK>w!*8?*UMru^37Wa+0&EK-5?imI>$0*(4 z!upO2dSUC9pN4hlWS`tOr%ad(+pw=IKEOmFp_w-DwguTc;(U*Cnr=KHikZa%fU-m4wmo zap`|RTzRT)d98b2tjh#M$d$Y$rh|pHcMuNr+z51c;wD<@b}}n7j_ie=$5>JZC4dO? zckZh!{=u^VlvlKQP~V|P@Ep*w0?^wEmfFb@>a2xk$nv{#6NNHZnM7ZljGC{oyblC( zWr4!=^IGA$FF&I%@oAmD&Im6199O)Kx;I8ro&W8^FD1rj;P_OpQclsDJpywsY7%n- zpF+s$UFy4hozcnFf5;bmu_A7xV}6oM}tn#5P#_X@EYRF zQ!-lhQ@p&Du1LM~6;ern)1rz}HpG!_i8||!Q2O9$*yJ-nWVYOw*fGHg9g-uS446L{ zVT)MWGOTbJ_TjH(iF^_B?(~TWlDn^{64W**7mDJ;8}A&9$x)k;I&Lli?Rdl_x(t2v z0((ud&uOdP*5F=FLu5m_2OXH`9oF@^)^N3Ny06n5zi(sjdj=)za&{H!4!got7}b9= zA@L`DeyO9!_3dUi%e6drq$KjIspzk4cC4jAtowZkrZ^P7G0Yce3Yf^#$K~}-g85a% zfM8i566pD%UJ8Mrwfdukd4!?eueXMBuVc#^tOE0z8&;NEtDqTHx(WC z4p8c+0g>~MqIM}>NCG}W)Q~p36nvh7LtjmVA)#xFB}bUH83E>Bk8lHHP964ndGtES z#gg6sWtp|N zGPJecE4yME+b>F=4RnrT;c{Oeq9#7hzJI-d3I4S8!HcJe%HRxI_UC}Sp#nmU(ZL+~u03ojjBYC^9p?Qp)uH2^6r!){wSa9~u%+68D+$)l=AI&$rMc<1Na$REtqMwU^>Ej z>Dp8FIe|)`zn`9q&{F0FT3;e3iPsq+5q9|-jA1$%sA&f*l2FVlmg1Q_NU@bX63!-o zq4gLRP3@$QqmJ#q@^Og*#D~jJzDPUdT3q_16(f2b+-nAbRc^$r1F=X5G%Hz7l?D3a zP4}ZRv*MQ4znOcFZ@=ejwM9K1U3k_m;2v34A`jE6Ru`Ait#-Z&IIK3XW}n^9R>g9L z9lfJJfH4iWI4t`5a5WI>@fO32HcK#hU%K_w8^R*QhwYP%ml82yg2@@fQD&EQrV&h6 z-MtWp8}}@f#_>Sd&%&(#K{QqXnXFLU#xN`Hrot7rWIP_~J{!6{{ zl6%!nFtnE0ceQo-SbSl_W!$7SoE)lV2Bjd?5b+Q_(axaQ4&<`cH|jcy%Ci3PXHww)4PDKI_|O2sM0BElh*=V zFILWtavs_Qg7v42qs^yElo9P8D9#ptg!g7~bA;Bu6;hdSyBp0uJZyCE0#LX3_iEN}d%=Mg{^5<RAyaWyS$`$d%sLHOLD_Y$u& zZ7I$(Q0(5_wpbhs`Olx(AUHIE0^nLuIMzc?vD>6%xE9k!J|+e3VZ4h%C8~WYv?A5? zI_UlmZ8RKa<_`g3n&u)~IWbL|N3dc<&|Wy3^g|Wi&%hNeRY<@Ik5Pei^9y*-C^z4~ zvFvDTx3S~kzDpBvV@-J* zXhffLQnZ8sPl`snohocT5KrzAoGd6sx;%@dj-h&|Dtv zU&9MN^Do_5B?0iC^j31CA3-pAkij`Bnm+4Mucft`ZD`$hNeakjk@5MMpzDJE;tJO2 zldie!*`8`WhZ37&KoPWM0DYm?lk2(94eJD90KyGF@v29GV2uPA+m;%q(L!H!HQbJ2 zn4!;4aYU%W#SsRlGOk`*>iKhQ6{n8#ISurj`aFPwY(Efoex#7S3n1O#3fg)LBh~4s z4LdatZOJsRf$;}LhtU3ghUsi`9*v0H$||Mmkmru&I4}^0edqBfB!pqbBs<8mF!CC# zhUQOi{n!7MHE)~=mx~fqDfvg5T2B+1QzhmL@?3p8^+N(N`?l(2cd18~=bXSd3i*ZS zU-*mHd(%vAz{tKR+IAM%B(5wKMDA5ljKcQsXE?AT<2qQBGlCvwYFg1@mgQ|@&F5-R zqt0b+HW-N_Pk(>0A}!8WG7-EvF~U;L)Y!bgu$P5JvGp2Sl=(wvYe&kluCT2|twK)S zc1u<-$vDRroMOH+pSG{APc{Ol2UtX<&tMNan*yy$%kqM7Z;o-GUMGMHxnOkjlQrM5 z}3Qu?5XWZtkAPLraC$No}RQ1CQ`_0zzKh-lm|=dlZ*qrw_C@ zV2^nTTe&WYDc1FcYw#O(TFKnbtidZe0q>ubP%p3m)uqx6O(-xmoaTJ&qtEoitf-c?>O9~_%uZl#o~72vlDrya>E?$YyiiK5>-R?9U_+* zg)UY7WRwTH@crQ0`jY11n_%E`lw4TAeAx}1W`FMms$bWFFnj_x6$s#jgPeri(<2=E z)_9)5C2paNi`fm6>y<2T3Rayan%FeH#6KRG3G8XU?f+Tjk+ef>EB|vz$5ImlB2Q5q zyvag~kDXBuj2NId=!)0zZe9wehT`m-&78cVjLSgN;y_NdbsDi2@3R_-#2s*k6OQ9n z61P#@6fwkPP`24rtO0c*gKrUG?H9h5-MO6)7yfrSx8**=vu0wWOKJuVpjt?yIos#8 zs;;v|qRzz728mD@Kk8!rYYkG3OrCW#pgDdK;{NYMII9nozue-59YLrHHAWe1JX_7u zG`(GM?+?N3NaG}-VQnqvw7^#dGPmL9$Uf32v(m5;8VesI$dtl^wUBp~0E)xX9fHpM zZis2Q2S1Bk-Cy3gnIcG}uf>l&Tde%s!aC$dSP_@M|BXRK$|3}SXo9UBzWe9^vVt!2 z)i32Y^F2~x9{=6`cC{FV{XuAG;xH@f5#%u$y!0=Vx=5s4n}W+0?~ljjzDW8y&BS>| zyvD}^6q>|70c3rnps^YLyRGhqJ}NN1S;}951EtFMB`YSif;~{z3OD`-)d%vZKnQH% zvSCqwmT<=GSVM8z(pq_ar-Lbhj`Aa<;44S^3A+GDM4m!V=y%GTPKuQ0>!FA}dOU_| ztdcpMWBDMr(IiPB;|W1il1L?p< zs;>*I3^_WUW|;n*^AA3U23eU>N=L$|Vk#aWgoU4sT!*2kc=HeKw${-~`%`J}%n+%3 zhxc07UCRQ?Z?&=1s=w~??(X|s70_!GDJX1G5UwhlcXyiW;2~V((CIUiw%B$gVb*Va zA*2xbuerv9kvA8C&TPz&!b8upL3Xa3s1SvOoKmNF53;^#J%{8J(r5A_s|wWojTN`N z+lOF(r2%_$qmpd;=Z)r_D=7J!99`ffuKGFozDeE<=QX|~4`y8kqmKNaZ-7@I`$>F4 z&GF~$%E;kMIktR1;QERVzRXs9^F6yzF5YfV+X*xF!`PQ36-FPNK@OuC>gzTrIjl-s z?#ll5K|KSrO1AW6tf%)r((R3r*X`zR!~6BGlxk*t%SAvint557-USGedh9kFY<4Xm z(A1Q0W1Rx5l^A{bZBEo-MG?r1YQaLf-X%2;QzQ#5c(@lHqwa89*lJt?<7hR{3Ub#2 zS*e{+-XCf0tR|hOJ5w=F^7+wrjskE4uGHF8EPK!pdMlBf)QJ+Qd)#m;odHXc!V{Upw6)Ds(Uc`A51B=$!Mwcw};QJUGRCIA^f86uUVuZOabG#OUsEQ zNwMQ~#RjiVPpDie!Pf`$8hJYrY#D$GrpMPO75e<)FXg*pU3WF8JrC3u#w~6TXbp*r9W@h8Zi0C z2%a`lYFQ@+l%tpUzyuQgw|HYa4WtYXaW*0M1mvw60^3Yf4zIKn$*h;y};2n8@GDVG)+_qnqMyD9m-ZI}m}4 z+oFt;G)vyh^uj^&Nh>cV(S&-6*;@=c zoCm@P!=$qIHi8D;O^l03K#427!2?TES-oc6Q4xM(;JMZ%u%`z7vG5l00~kCr+ljVd zL&qk;kU&+C602$1{pY}quwnwX9>j}=6g2W-Yj!gg9KyOT9Cam}hvA>iAyr=Tv*VPz zPeWVziakXj=iuIPcKCj#5RB2(Y#3J-?X@5GZyCz`-YkiLr?JLW!vdIqgIo()jG)Jf z6%(Q3U8bR1y~xYR30sD!IU30A$jO;6xC>F487|%v$g!5b2RXIph^{>MCt%*;1wN4d zlZmfP>lOHU;B+`-jQ^8Ep^hRK=#$9kI3=|uS5eQyAkcxp3Q;#g2&@)Ww~-_C5fA5& zhEJkq$y8B3r*gI&n}wsi?>_|PEqhK^upCq?=rDes06jp$zn1-+CY_Cklew>m@QgLy z%YC!D5}&;{!K|XR&Gnz%OGA-vy7(H~jT4Bq$X_wu$Hf)rO*&unq(Y~8?e04jtj`Kl zYH?E5q8upDq3Oviv#O2&V+4FJypGw= zsemhoQDSzi1`Nz_2wb_ zgZZGG=7@aYqO^0j4mqHN0VmE-^GhBgM|~5TubD_@3aps(ZPjY33*l^VrQ!<5Ydd#J zkfa2l$Sm`kqsF_RTC1u&l+H<3Ogn>(@Z%qHVgHuSEVWKn{w|(SnaK2#1nC2u$s}Gy zsRGS^OE%_yn6$A&N#Ifqb(0KRxv#^VU8%7D9MtG&(Ue7#id4UG3UGe%*5dreODy&K zwc7;fz=zU7_}H&piOqN*%54b?u#&f;h-w@Z*S$J^Z=Y(f=_I;3Twk-iBk&n7KGHsZ zBe!ge@LyHzquX#3l8cr2x;TZfMs*kf2I#G=U<|Pu%q1Ed*lrtOIK;(8a%&MOt_%=$ z>s}*PNE^Xy$Ok+pVRMMgduPU^#Y2v1=TYJ1Rt(RpX~s45goW@Px-!G z@eRe@zN*$(0>W1l{6-7V&OQZ6djRKEHWJT?!p!a?&aWTd2#hwLUan$Y6n-C+O}6+u z5P=f7{r{kOz=VL(-04@S`HNXYqqbQPFe%qS${~~nK$u7|{$M8_B%0jc7 zrV^d;PKm)X*9 zYv#)<*2Xq>&WHQPuq91qE*7CZR1<_ss0IK0jI}n0n+Y4laBlCh&`B3Jm$A}is5dA) z9HW({{m7K$`#l;UpA#TuLu`zh$MtZ4DBAtBfvqvNBWjn=4>(4oQtY;;m-jne-i5kv zwjYb774GM*Enh;9+83pIc=NFZP{28hSnPFyM*f9VWmcJFa!YIqA62RfA36FMnbXx{ z)48gim83-QoLmF$+Af9?hl|?tBM`ZLS0+!Lh^97CwmQU*y7eX;Q-#m;YG4Lii{by> zD02Yr&!b)7oan2ntm$N3;4jTM^yf5p=s=})6Mxdu-8Z=qvH)YkEG@8zYv?+jRHIy= z*+Cg&@k83%B`;Lu_q`lU?Z}UqiZa3Hgd|VUg5M@$dzgn~v;`8+Xn->>TQM_jB#GJm zlz>@kaUX8&}wMC4iz*)Q<}9RS*C1L+L{z<@E z%fOzb1+>^r0Ji*`OCb(tG&8Nmt|?K-SD+e+CQo}eba9}`i-{T;X%Q0GiK)bv&23So zKYYb#4XM}uznzNQTLj;zM!o+e4-ugJRZW@OrCmei;Cxy7`q<(67|m^K$`S4fukdUu zSGgHxxa-K0NnMz0RRQ`gChpM>S|z`_ZU=qf!PE%bFZm<5wJ6kPK`4L2dnKX4p^GSCtnR^U5&9myw+FbD-sbW;deV0g;WjhoBGZb7J)bYia^b z+hBE^(5%kE3{B=hUkmM&cEo=e2)oO3UtGvU7?yWOiC^hIiw*7#?|d*C_+Q-_cjlKD zwLRjV%amh1$6Jy?|2&ui@sGn3E>F~1)#KYU7pXgURd2n|VdTBoyV7r60-#+S%y z6Ri zEYahuG%*UW*}obA7LS29K}mvri(}f}ctCWeJ`9MZ=Sef>mj<-%HQ=HlFQ1C^%i@v6 zfA}MS1jE_W1dA)li|<9FxTTEOwE@c@=9zRn;hC?D@&{>d1F)^_4`cd(ynlfj-=w2W zr^cSgAh?tyuQB|sk{<9Rp3pll?NoSjOMsbz*odU*X0t#R10>bg@n*C zs>5GLxAD}5GLvNWEiEU{c$J z&I%$Qig+z#4tB?XDLNQ}fN?m*VkvPw2NA<(*K0c#n;ma}g{OiXd|amq!}rR~39|DO zP!b^Y;Qu-nF1sg$tf!t{XK&mnMbBUE@dIBmAXtkswPL1o zvSofQ02w3Qu!t)}k&p90pZpX)YqJOtP>&~Rq-RJ}zaMs#|28XKsfJjp%%!%ScTioF zg@jvH`e-n|+tBOp&?O~8oCzGaCS99#vKE&+1nHK%gm{B^$QgQ6mZaINvfF_wJ#zNlQyqAaR^%$rJlOg!W@ z_z!uz%A^EP9j<%T$UbI^9F}FLw3GRFNXTv4?Q?8$y7ZWPO@GG$Q`;ONl+L0t{$@^_ zk~x&p;^Fiw($_w*>U}@Nrf_h2Xr|c$cf^8m``9n@iT_dvnM=mbMS8s#V&yu74RzQE z>dU^CNQEQT=A_K_1!sqe%iCB;x@e@Yy2tmI4dpA*7zkI?M-Y)H&N0htimjEjoG2Y#*&%XBA-tsXYDK zl?vw+w%0I)sOwMZYEgB!qm3fME$&qk1ax7~i<-;7-2 z6ea>u<@pgED|vH0CPk*Tqg!!}qbTF)%cA8o!D{l*lb>~rP7j3v8uguPyA~dZMCs=4 ze3H9cg-m4WrZk*cjRK+YHkgWy4F&IdIv>&G>yc-ww1uO8z~!njk`k5==<`f!d5#IU zX}M&BA;^#9B|p>6YM$l9Yp`6Lo<*3P!4q?IrsAT&ZIn2!*Zs4MYN+GQF@4Co6?;ZI zpgyR;ry|4y?OZ>Wox?wrQF*RicnVFgTe6Zzf|k>UN1el!Jee$=f##GATFndYw0jG~ zfU}8A2{gFu0sGvGls_3&vM6zV@DUn87Mqm7a(5ryft&txn&cXLp|FweVT!$#)D&{1 zvgvPF(6PH=0X}`_V=9jmX9;CK6(plgjf`SpEXcAe_ZGO2#_8=i?p3M$tV8_ssPJ)= zM4YBn+rQHgeA>R*B*0C|Q+i7He4s+-NzpwmAd2DN)C@c4^yXEIi?G=*bxolS#omBmCT8=zroJO}1)7)g_=_;a?K1+b@xKGXQsVNfcamkAB{s@Bsdz4O z`tFKutWDoUzW+;n}{*!iRNzkS>&409kA4@m2g`w{4X6gS;gt7q_&88#1i% zk#}a5T@60c{gObhPE6gN%MKk_m1!pYaqnMmYO!fSdl0VUrReQr)Exdzj!P9Vmv~PI?sFOEb3P4F zA~}0sG>ZD|j3R+43n;M)%@5=5^)AgZvSec2$EK-*GODnHtQp+|!)<6cV~0#x%146G zv|I%C{8m^^?YizbT6#!4-^1lA-{O;RSZAPVK8~-T^h97bkck=94em*3J#CpWzhG)TOzX z96N9gI2iJ8>+hk#0P|DS{VqV%EfZq=?mlhwWy> z+-1!vv_vj_u6EM4=@cv%7Kf|*WV7jbfUIZPs@kRvGdRi*FciabqWZxh>LHoBS(2?t#i3(53-je`gcIP=wp&G zr&Y{D84~O$ltefBhs_0%K6%Z3+DC?<1k7iii*_jf^)u8z84Zca=s(wFr3VQ&y?kQ) z*^f-xR5ujoT<%rnBy{myh5(hWn`dNg_J3wQj><^If<2CWyCAQL2=XsUhENs{e2YP) z0qotiIRG4ztSIQF_LL?CzOXSkut;KWX2h`|%d&^etUe0-7?D~yNj{2-2*E*Wo2Quy zjAP`6^eM7A`u?B64w2bS>bgCtjZr;bR-CoS;W5t~Z*-CoT#=NhQ@hB00kAC%)>IDd z2(7P>{Qc%>rk1=~Q=9Vc{fj_@i}^qPef8=Df^<+7K5=b)F zcq9=MAJ9@!&UXmL3r2mB%sh4EK3{$+Jp_FDyzQT8=K75;k=egqe7hrKVB_9p9$!Prw^iE{C-QVjyD455cSAWFJzp@dhsK*X4sYYH*zGM>w^-o ze|q*vmF{UQIxdxV%)87rS_oJs&04qyL@^Cshc=FA(&Pg#A{tq@K*x~zZ4Bw-K>63p zUV}*bEn+B{UWEjyn)M~-lT;W8D8|fXeXi&?n=Y;P%`JGD?zEqtBkiEj4sZ`hB?ctR zfx{UjeOIt4o!jbN|L49{5C>#w`^HlF7X_wy8=QA#(6N`kei~I!>f6KR?t^ z^XGNsymdi@_|Z<#;KKeWu%TzRMRSBG(+(VySOYw+3k&r zq!mnNcVie@AbLjyp+ckRJ0K-870rxxKOr3wBNl!f#klsqN&KLggoRot%LlWAtY$8i zEaQm?Pqo-hEr66SkQ8Cw|G3To3NAqMvyGMTP%gE3XhA1ylhFL!BlN$3(T6*j=`@~| zA=O%WMvt8G!Q0a?v@#XisNLSE z?_sADaIPvhOy!y^jZyWgT(I!;cA*fHcxh+ZQe0I!7mBS3^3W*F#y+RHwKK45;WSBN z>3E{*YiJe@y9T3wd04Ml$EaQbLo7!$b)o&UzqeGLWg1vx?lQp}YPWFf$Hh-%DsN|G zIcj%Hmmu_#)RE+&!6l!IVA7QMe^dD2#zpPlZ>iZ|GMP)PK+wC7@qmAsWQ3oe4kjTc z0S5k5_Xq1)XoFVm*GR}5ANZYATQ+c(-TMr!g0Bbj6jBzdQQ}?FH>t(si&~UMXQV0G zVN&${NRm<8B*uo({aA=Xusx{2Gam02DQ6?3P`xJ%TzYcv1T#tzSd~3@Mi%0u{^F&t?{w~Fz}phd**?jKM7cwYPRAvez~Y0E#pN(xgKd~W5pvvS6u(h z5h}u5Uw-!+{wjSzt_-tInT1EB&SWgxhz!L{=>Ey;1nugiI0Yje@pE5Me$cUp6bDfo z>A()59%f_xWsf1<$(n|^(2=gAa-XhA=@0{Tb8a-4f^Sy|*!9vP6ENK-3H=H41$o&$ z4p`CyF0p_R&+D-SWJts3hyze)&P&s^`($`iR)3LJxeRSmR8qKfs^vB$tECLRt=2%a zS@Xhu{3)l6Pf-7$#iHp!8KG&&xl7cH?Z>9MM-#Slmy}(JLdOokGjVsbO)3dySGWEt z4L-Y;WawhTw!vql!iX|tl8)pJUgNz@h5_T1w|Yv=kES%lVm8`i<|}+w$E#%p6E~%P zH95tN%P-vON1p7D13o!~T^Y@4L0KH$Zj|)cQj0f_K4CmRQ0(7u4C)_Y@qcI|kTcJ+ zU%*emeMBzhP;ZbTWFF`%0Hqf`_`ME@V^O62KN%?y44T9t+k#`qY0Khq|8W2B>py&H ze`KCsU&?I=+$c#$Vmi}Wm{;@~!6~e_FrlNcH4XI-&-z&6lv-!Z41t0IQc0*Eo9M)A zX!6hR!>C=i{c}S5OShC}xFk9n;m!jU#uWNqlJszq_c zZfjq)Bykp8Kx%3HlCU5=cf#Yd&uXwjowUQp*jkAIm6+J?K`~W-DAlw{Kk3jZku_W zv*vfg4P~Ma00ht~#z6l`8VJql_4)m@VA*0MS(4k(SNu^7@S)?yjlAAtSx0Y@x7q8` zfeL3CrF;Qb5X?Ub@xo*qjKPaGVKH0{a|T7!CtohP98Ij|O*Of~!A+!3ON2?fgvu2dbtZ11j*xX0JUhGZ=|3yPbxmi*n5Wk7Slu=Y~!F9th8M+g(;h_m@x#TFEb(^>pAcD&EPSzI2c=O z`K{AE04F*EMqI{8X9Qk&_;9^!&_~u;3LNLZ-1YPTt6 zuCQ+?YOGUz=!Ncv_E0cMTG*q;3mW4H-akPy-fC%%UdvMZ7#Z_}WML5;S;PhZ7C)z2 zyy(4}HTxL&)E5Nft7(68e|3Ol1>DhGTO#qL!@q2cVNE>6+IVgiFD}0*CF-bcJer>V zRNf7U{DS8&%h0dRiKenyJ?e$h_d?BZnU}SRlBQ(!>XeW?6xgAh2FewNCD_9YoQdtS zHF+ePy{;-=Hg&=7VA23VG}ULT7PV3{kv{?n5>_S1;j#cT@jHh8ZOY;dmOzKN%lV^S zRV>3DnDZ!W+e=$>W#C?`%{uYdrRJ+dS|xUPj>-KbGk>TVxFt%;7(d_3mUJoR z7f~SRxvGTY86kJ%82o5T&F>0t#$i8fS0|KlUJtf}7eNZde$X z1*Ko8y5!`4ZdD55b(f8jYii(>TK*}mx#AJ6DALl)B(HhjqpJ1r7$%e)2|u0ZLKPUo z%%Q-F)qd!`j3bhq*7nPZ`l$&n*K6uEKId&6)K2|XU6^%#Ck#nzb0p}WAX%%}`O!*V z`WNrMvt0_~<-EoIFyT~6Is;GM`>x^b;LT9Qn5eBAYN$hDCN`Js+YoJ5jwl+@kD62J zxofcc89LCKklPvq9~NM4*t3e5OFfyZQLML!&1NpwKa!4lC>!fOjo9z7<9qa>`O;Jr zcS@#yXiKIizCpI%)>d+u7Y>r~@N%;2@H(&wk8y{g9mn(@mf}$do#r=^GH-7Tx_kGN z3j{O;!*QTLh9{!~dV32^Gt$vyyHfv_Ttsm?HI#@3#F@s8ew4P}Y~+2>yFRrGTP)D~ zCGx6jmNo9%!9aGX(RHwiB=)>~x{{3G1cErw*aIQh$l8#-uMQP@B`Vkrmf;*rQ|4_y za_6mw*__Q)FlXSF3!Pu2RZ?h5>ZWrRXlj;`B7U)B|Dm=oqS$JxX39&QG;5o1c|AbA z+_p|Gv0^0J5LeR;&A!nvnu4p?w&rwxK?+HT*$|Kze2P;DGl8t9X}!a<$nAjwCYndswf{lw8Zsko@X&tkUEKB2$+5>tzGF{Kb&V9wyCF!0%+pkfM z$Y8j5W3>+-A~zR%Ja>$%wL}tmSpLgvNeupzfVa870ev4g+G+a7>Z5t~4wQrdvwcX=X&NQnc(=h_uI!$cF-ez|$Szr_sl*|JwJ? zQXPSfV`gM;42?bXqK2RBYJce97+;(9{I*a669_}4K_nTg+pNzeLe56XVov%*ftl~^7PeaVB%(w9ynWMoP9sVSzYo%z1}4o*WDy@b?Fn~P5bk7z@s@sa zb(bLG4MMdOX0R2mC`tLJG+;F2eM=C^`euy*%yq+VAg3a717yoGydVD6?UmB?fI`re zwrnmHz`!a_{ekO}<@rZ-W(m6nY!u zjBwkXyALdf*9Y%pz3=r<>G6{IF|`JV=Dw#e=@_o?ST{myd1&hrprn=ZP?aucE3Yd8X?+^cGi%@xL#xZ= zbQE7BAUzH*Fw8SOrry&nf+DX1kvx>gs&Q%b}gP`H-@qJ~Iy8nM9bI14dDcmg8_U!aRi zR~2px7U}{=ze4wSBCtE51@No2CxRYf>`rMM$z9ub#T4u=4H5|{81wxp)A9;z~<^IgCk2eHpM`vypimoLtAT6kib zl{S$(1~Va|HwQn8pQxI{E9EKwRk6j5Ed$c6>asxN{4$iRb233+lsBEiuycDDAKi=K=LEE29P8CVHw=Bm|aX*KX zD1F@ecYogl9UCBgGMVgxb`2+Vp)%Ewf(Tbf$q$ zB8O=pibK2twT@_1mz=aS((hK+sQ62~4|3fSjc53`-|B%p2)FoCBx0OPXk5|B=we1o zNm}8&GvVI~1W8P~#073gy(7Ou(bF~cx$~cMcg&JJj&4dW7CKpSFSGOJcTE!6gs+uT z9eDfnDVdAN@vkV6DpUl`0F~*qZp0F4T&e_VWwD=wN$dsbl239F`TkIk^CD6g6A%5h z8K!%eq!QQ^W`Y$h5g2bi&CAledWWh7Ay>0cNg~FFm)c7&3iCZq;e< zz=zJwcvA307QJ7dBxiR;3}7=eMlJ;p_^i>|65><}OW}#S_J#lzxH-27q;aSTP}D?j zYHVh=f^#zjU%YMhD@u_HvQc-wHv)7CQRV+MrRoBMhiMwBcRq4FG?yYCKTD&BXRH{% zn~*w$>apZYhsyG28kkxA6*+@}c2*;eA8kIt!8uz6LTfJoI&%!5mj+!9xa27k;*wv4cvQwnzOhN~& zWW@aceiP;lcO2xO`<>D@cAFFpJtd93`;Oi&U#`vq5iE}_)ZMu435&)qtK_An4oDIlm|rtfLTqO76fiv7~8?>rFdnE`tliAQcmslx_* z0xyG>uLY;NbMMqSjSF-)Tp0y3-A>ScIu`;S@H|K8jfl{|o>(y5)8l&gSa#p`S<$DJ z#JEpL{pj=JzgMX93OZd-BH%w|8bP^EQ$BEBsWvR1D*Q@MG^NOrU91a-8aR1Y8t<@P|6|>Kxa1goL__q)X$3_Cl4G zT^;1!5o|BzXm27*)CjYg6ZgKovW5d+et1FKr+=8R2Rqv(!X$ zwt&}hBDQh-BJmS$o|1Kr*gM?fz@T+7Ao07E(HDi*_ofgn8j5adlgQ7^eaFANLOthc z$G#(!m5wPEjFtMdL~Re|3P9?>8Ce+mXkr%%7T~Z*7khGSYxqF1%*|})64xP7>TqG* z2^hYYb?KJhDF&xfc0MH7Vc8vZNH2$r|S$h@LypKyhhf zfE#zX1IKOm+@y+tJ24T)rNZ3Uz;tb+^xKcpr^hV~ynuO{eBCDhC3P3hzyKcTN}*FULB*2y2BZj(UGB9@GH^mW~> zHAcR;r52P;!UFsrWD!=*e5TaL_qAE0H{Oj zMv3R|FJ=RkD-<`6FL!rTU$Fo7mD0~7Lf{mBfBn^!3{X-v@||!81_MiK2m+YiV#8r( zm^I00Uy(*g?D8901R!s|iLPJb*SIWWpb-*uJpoKemp%z}cFE|WM5*5M0b;#@@TddK zdw5XT3`F6eT9LiLIU0f3_IfI*AX+G#CjsN!88VDK* z^HW}dm-oExciYtZ3lWJb_qju20P>1?ws@==k?0$;bn(x1lQUFovb_gwn(3 zdBP7#j6S>t#ZxI_%WAmrSjx_a+8$2>b^jQf6hoT4kbNt3v$$9bTvE2a>OlMOZ}5lS z$vfcafeZ+apw7E*^ZejZG>;-64exCjMST*g=_PO4bltGVv8hdV8w}SbtgJq|_ek5y zStPa#*PV$Q44L%UnqU^u{m)?5e9dT_Y&LYjW#iSpVpYYeY810tKGo4CwKr^c$I$KtnY)Fq@URjj|elL)oI2k3nZw}Ta6aODbiIeuoy2{e?%!;1lfad*aSja zv|$(~z%7NZ;ol3p`F6P7&N?AIBV%dmUPBOS(~1lB zbVyn93gM7jbWd4IILWLxj|RHPpPK9T?B|&N0{rM}>qi{EBV6NZ_7do#$izqjk$vbF zI(+IH-2Hxbil)Xw5U8EiV{eIPiZJGhI2a9DrYNoF{39gaN+c-e?@qyDusUdm=W6#F za_YlxWbw*rqVfeygwovlerQyv1gZ_^(HEuay9}}avl>By&0$$ge5DlbUUlRDk#tQy(X05 zNGugDdrnBkrXig(*q~i>k(}Z7T;b4b!S(m9D9)u6h zRirU%izLwH4B4b~ZRAmqhf1Ne(_9A;8cC7H{(78#vw5G*AGs;ymAPk<)LXkMQ{Wtw zm=zIn0y-fUs@2xx;VIHxBBA7nFb32th!YP|b73&URK&DMwZ}TB+HRu&2P|cJE1peV zZ~y(2il<~I0t479lCwBI*)$^=_6ly7JqC~yB+S&OOW`u~3-7Yex#(Nni7epyz|HKe zJo(K(ye6Ww2o_Y!tU3D&dq4NLkIws2$KTxY|HigFX$G^twDnL2|C6{WunE8219FX~ zc+eN;m5#>Y_iBrb2c}q(l*ArxErDf7&V70Kit(tnpeDwt`Wi}}i(Kl{#S)hsK;)hD3THrEZ&lS(o(3WolFvSYT}fUn_sb0k(UIKOODbRfR{MYIsE ziG?&A1xYXlC)Uqq^m@>aaL2^Czal)PwCoC@+SV1~53tR!U$daBmQSpM-4d$_OR=6o z(N>E=Z>cyP3HjYHJZL^F=-Lwk2`bk^=x9bciv8bomJ5zN~7;{Th(OB8C@Lf)@ zvLtHga`%<#?7#q?b2-^>MaAmq_||4_^RkR4Yb(Ip(U(1f)vONUW~bXfIL=|4yQNUW zHF~+|&i{j&k3rc)#-hR+-Rix&>W^;)GhAzm!i&YjPwzxk{$XukogQ}0X_et+#3j>0 zgcBq-Mv%y8ev%#ka9U2NHecl@GGpj^C%(DaYe2$-4vZ8T*(U`wK00`BP|6JH%pKI} zkp+`I1E_su0hHuz>V`!GnadxN>mj!N5H^Byt7+7Q@`AM~?R$SnZiyc7I{(s?qMx(TF7Km?b}n(aZkeu=Pe7UN-qL7&_n z+ZhJLdr)5ZC_t32#;4Na9c6l$&mvh%p&gDM?tn6xXnoXrNk)^H1kzT;Su^4Y^x`#w zsGDO5o7eo7SYp!FT!Lw9-qR_S1`-~UQRD=F_MCsBwvFxT<0f=D6YYFxTa0081XC94 z#XkbWizGiEv&%^VII&QSEwdR!jwE;;Ql_EWH4-K^fL~I9fRtOQr5x0^SvB&zfGq*L zE#E*+mcY(;mJ7?}&N#RF6M9zO4)k-1Sv$K*0nOxa%q~R5S9knR%r_Waj-rCud+GDz zX@+lJDVgDUt@mVZ7|KS6`|87cAihpn5fb|fsQlRzL#{ta?cUezKqHf^28+t391y!G zr$*$dZ+VjyVejc!5l~^t7!}WIvncvFsi9Lc!d$PEm1G3Y@2#`Z_uB5UP|3Zj@eAQx z{0$hxLzvVZrMxcU;VSKZ!vK;!R89Dac};d|ikO(bY4(1--?doQmG$kj#j9frXHE$? zFzd)DcrK6I`Vi5LHKx3Wi#@I4txT*S8< zpO=juAi6+?WNWgM&v0AtJ+gN&z@8oIi9h5JU`QQ!)Ft7chkJRtl5C^qO9sA7gl?X8 zv(xr|-s=i#Wnt@X=*J!<5x8$L1e0#y_0ZSJLY@$|<=8o$pXpt7ojo#BBlw_9A09^O zKrVa0VG97eUP~03Cu0O+`mUh;(tH#;0&CR?4t;rBpQl=@SQE_?1#WH$VvcktTouj6 z>9WLD>*X3I&>rL3)UK4$P^EhWk@V3>-nZwK0`1@d%#D7=hMjbU0>N3<|qss48ZMx}Uv_zx7?@ufOl-Lf6 zfEtSOiL~xZJ%bh|kf6j?+}0Df1a^Ralk@d+#{TvHQxNh()l3e3p#*sd&^6nELk37~ zw8%5*bR6j%T4uA2BMB1YC^K{Xp_`JBs~Pt1eT5eb9n6_%EA`!OjNP}0Y-CZDt(Gvi zhO1Z8fA0uKLF=KEqjTyM20B>O8f@khh6g6;kR~i-1vJp=YRfY*=ydmzAaFJIk2yxw{bZp_NfK?oB zZ3LsOy|=KdI@8I9LTfGKl24|sA>!goQA+TI=DQ&ee4YSeA+?it9Fwx(ym?4ersF94 zG>#|aCp_V!lD+>=PLb__Tc*$OxwC0jfz&6gvU|K<5E4xvPL}!KCY3i{0%B(O9&UyE z`^U<&b1SNgR6K;g%)m0aW~&0yBakUPt)0w2zFM-Q0H*gTV(YC8l`s_ds~*YoGdm+C zYsLsQbgLm5!b$Y$!{s~4J$(4FwvE7Mr?(Na_s9fHC>(Q|ubXNuWejlSt|z6U{3$kwX#JWRFHTb{GaAZk=f@p2AZ>y)YuRV*fXchXziS8_ z<-eqKoTi&vCsksDr?L9_T0?1d@$7@$9Jf2NHc){oUwOXUDy@;fF((H5LPFjPH33z6 z6ygENvc6&5R&UHV%AIQZ{Z@Aej&s!xU1qzZLZ%?QCmR#^J;8dFCbH~d`ZTREfn8v$ zHLImd;1`MoSw}c~8nLn~2K}l5(tREu@~A zTv+HnKe%--@HnC0d~o1aM2d%*yC5jr-Ppp&_)g|Ozx7A_=?qMKjR}qOvK*zPnZd*h z3%781L{z?u=LEL*3j1O`oIbDwR2o`wg&-S28ubVD{{`fDuzgs*svynP5`v)AjO*%Q z_g1rG@TgX{7T$HZ{vK(N54Fdz%u9u%)Qal31cVj`A>e}GfFJqLSZtqS`OXEbWRYs? z6~7>ry4xJ4Hh$bLWs7b*9H-U&`hH4-Uj7yrHePb;ZyJ7{ic`B4t`65 z5$~V^I)PX~Ix%FH)tZPX?QXD$p<*mR(Z(fRVv^@B#)DU#;N@&cda_{i0H| z{JvXtq>+35C0j(Ka4#;>AUlAQdk=a_gHYoGvyCWVHllv?E+O%b5*H_}7m%0K%+Q@&3*}HU z2$=Af*VxJla@xGp2v3sz5ny|OG6Ew`e*)8k1rKorze7{tilMTYZ^@*^RM+T54`j?y z1XJQ5lq?-N+iEtLMjC)ZW5h4)q2>L)HgU?Nh03{>O)@MXDP`DONj?8X<7%TneEnWk zI{u7xDXu;HBT#v-<{DH`7JR9K0^9^iiZZM)#7AOPBD;KMPw1ST#AloT7(IEfV3Bb9 zk^sY5^(mf9NlzpC7FjN1O|Rh5(;|BK;uVnVJmhO;92#jsg|L8HifXVF;wyfGa}*n2 zxVnm+qI4ZAaG=AN;eXnpoVvtlD366OfO5}=gjTvl3wReSYqy81YV3paJ~ZtzX)yS1 z3FLY?&Jh`0C|paE%pXIL?~i$wDkGPVa1P@`$hNps-HYO!kCsWJ#l{tzOO_mUHLL8?C}*Lc9ILcd2@&m2s7PKKo1J+SytuKYIm?dC zB2A2RJW8I+>#!nc`OK~HyKttDWOK9rwG)(-Be|TJxn?dnMD>g}ZN7)jA1vxrZchxr z8ouq_7rbD^yB7_fsYaxCi(Oxh?&FG4$KP! zSW{TymC@q=MQS=KCGK^B^c9jDVw!)1^mcYLX1KZJvV2vZ2l{TCd8;Nxs8)H?nyK8Q zRk+w(v8WGNQ*f*LlhFzrqj*Keidjy$@r7VT!sxw72TAhssO`mH z_?QUn;1*^4M;rRn>5tE4x$L(tMDF=fx?9osPgWPPC=~6sqj|qlZnwU@Qo^!&&bm~561%{`bnX(v1(ThRIKRVm5e6AMf zC^vV*Rpc_lfPUtXlN4q|Su!PL<1zbS4nWx920kNf~T zK*Yap(zqN(BORPpQy=J&OmD+6-}U+HsGpa9*YssD4SNZ#&$!3?+pf?#-pC8$L;XdLkw}(GIpjFNUeHG2?S~I~ z-(l`&*b>_<=3L~vZG8zBXPgA8qFUQa_xH&xWz!M5#V+%HHu~OGS~;rHM~FsX5UAOG z=I7e#s{DmwFtw<08`!L6C)54~nsq5G5?nAw>wWviUQ@+VpL=(7k;yFz>rIDVyVvr^ zAKrmLZ}+0CqvbZNl?BmS zOoT?dLF{hYaoXFPK{i_yfDAD6dD*k15VohTA4Xa_xiSsOKDeklW02Y(4#rJjfs26j z1Z_CxF2@s?^3dow5)F}ji*ad6IzMqTcd((e0fwH#J&jf+<zT>C?goQB!S z>^ZEcdYIWv-RFj)}T+&5^wW>70ku4{?kXsbInTU^Nu zkRU#d@szHC1c>iYKL3Ts{ZwF@&~;hPrsBbgs7pSFZB#q6l3pf~Abp@z1j%#MWduuj zKxpY9{&|(k^Kszbsx3#>(}GBkNOkD~1RlN)7iJ_D!KF$C6daQI+7$b}4o2SdhNzxr zgm{bvcdx44cf;X&Pu%)^nwpUf^hK%U;p41oL5LGq)^_kAds&mVpV3{lp}Ihl1_bVw zTCP;o+OOk5@b)B*a&A>sgO`{fvEga

48ftjf7sR31Q{3`|=)n_2$9um5UoDJLqWg_Q)T%f%(!}JAzPBtMIehV~h zXGAA}jE~($JLt;W=W&+F%0)Gwpj4!ZnjLP3x|&d&v+l8j+#?`o%sEuw|F?tJ9d18< zDU6RoUJpa}?0=*v{M%pS<#DX);a#(=009_9bhQ3~s&rq0qB|b#zOj^x5M(BtT(uqo zaMN`}Gvsn3uU~pRaos8blZR;;9?7xFN;+FJNOx#EXu^r{deat;^2` zGKx=D2e_S7byVfV)xo$}?3PR)+1`lc*uHcwdHw%m;oX>af<4yM&H(Hq$G0$-J$^gO zZUfQIN8A=~xl(t?CwKZw?05BXHzXUasGA5pEhK^L{$I&{(!;>|t#IDl^=n&GJkal5 z$X->VP(cztiD!7Zt_+!YnACe2BnBxd1}f8C@?*@e9f-{u$UdK<+JBrpq_1hb1qP&E z&kg>^bN<{Z%+$K!nlcSXgK&&T9T zLSWw&A}(EgNLX)UfbtWqXK$W`?lI`Azc#3h9s#nE%p4bh22u>1+Gh_>Wh;0}_O%K= zv--!`J!Yk^qG;LG{M%s7aR(2|nEp_fp@q%L9`sQ29(X)P5{FA7Rcz3EBU!`8N}8y6 zo8yp3?;Ef*kLiW$)ESdqFGkm28D~-1$#x@1TMKWbL0PEJ3XHDrPKs6QFfvt9NCE%ZS{|w;sirFhCOd zKpm(#()Oxzs*2V4yNh9zpC`PdClO*vO>e^sANV`6Fep#{x3h3f$5~s!FYRzj=Td~W zxM5@yEkYV-dAZ7~tQs%j>E8!tzxPP2Di0QOEEq^4q}@)g#tjD=YlI=n#j`OCZG(8_ z>}bvfvq_W5RPkQI(|EK0F7#D&TC~{BLH}#KvgJZq&E& zw@Pj0oz7_pxuV)(rLh15fw8CXs&(2709+%0)rVR(1N!gc#VT5KhfqhQ06{Q%J?fs0Rr1v87hZXmPiy1Y%Zz=L&r z$y+r*p?RvFIJl)SEPo{25TD*~fZa|d{D{0CEUCeMO2wUe-Rj4a8h>s&a{#HdCx_?+ zF9$1hE+z#kg#bnnl!F|+%a*RK2BClKV;+RbyU7F7!?<>Hx?_Vydi3w`OA3WNXdd6w z9QaoP4G5|EDVySSemvcNq#5XY6hmN}4mHtb5T#0Gc||47v&Y zznK|PqDIZ`EX~PGW@F>2jO1B_-gwX|MDAaj=rnoYTC0Jhz!J&1L={okqQJ|f*ii9F4h-AQ5`>nPeZzTqc%tOR^Bh|paSp(m%O*5 zm;<>7#^vc_NBFy=Eu|=mGdeA)`Q_#YNqNkAhnuxax`Pr@bl*X$%q&IBkKKfpY-Unj zd`Fh6$ zjbo86iAqpU9UVf+U#!_n{t>r*5U{ zyq-2w0sKj&ddM42(=$$gQk2gw`Lquq0dCrfl>K}rPjI+g)JKSe*-a{;EBK(X8itv1 z5qf!LN)AZrs(>O~&20S?v%x?d5YWY1X3ExUvdIa$JF$QlXDSO`1q9fv3;E7r4K-^>J`9u4VHuI&peq1q&gD4)&TPW?z z+9OMUd*nbiIgT}8oo)wGXGzk>9(e=VzC^TeZ)t3fz6{2xKsPyFCZL{e2t^z&60HzgRNc{oZ>vKq~A$iie&=>wJ9Ag1UMW zqB}vmi)b191L^?&g;R~KKRJAtpyH^;A2)NN?Q!0KJ}WWD`&0B#0L7{R)@ z52*#Dm0EIEf|1^bJ_({b@&zx>0KX@o8To@jvuft=5;gi{R#eT!rmS?Fyn#!DaIo(Y zs;GfKDz$MScx9apV^mN%Bpx$omgiyQw&>VJ8kk0>)TdSKRk{$lCutMdkP6$cBQ(XH z?k~t&>bEms9BQ8~AlPi)mr48rfVGtJ+-rhX&sxsGS*_CLK&t&Xsq&~ly$kvwE{l^b z{gCTPx}M>~=4p4~p^%;dBgGZZuN_i0?ZueC82py`E=xco&O3@QjU!Q#K-q93IfF-n z)UXkO1KD%DQh{Z0EF#SPa;8t>Eot~d*3+q;kO1g)TU8#CRNu(9cu%3@UowJ+OH$OT z1q;qFDPD8T|L+C0gDNh06EZn%Y^?c(S61z)t*u->ZH`jt_pM6MvN z!gVinfWwrFeKvowebk*$b<`he;mUsxwDkIt=~MPSSy*xQT$r_=k^xdJ(x1R%@9`mHmGZRLb7pFdre|FCMUr@ z#u6D{c-`sneq+2At36k>n5Y6vxJ%h&ydsJVX`L0qj{*bY&eOn)jq+ z)jxXlFzd#RrA!68Iz8FI{DOIGD^EI5JQX{Ca$UP)iGMdg9MdHQE2H7KE<8%wqw_+f z=5Gk}1TS}NEoPHD=GLq9So7}$Jt60JoCru;FQ_&c;wZ2Yvq|Up1=i)YTHSh*FC2d5 z6_+YKS!@X^%OLNoH}%Kvj&bV0ONv_=)feUw3_M{*JL2bj2{XsoX1cfM>p7<;MV!%b z;pS`x>fi{3lX8P&H4=MTi{BiyHmpf7(NxM~tN zOg1qTH?*=Yml{xE+#9^s zwEfL-akOYMr8gmIrSDgM6aOHL>C?NBe86Y3Wp7!I`TR1Lc%Z{l!RLZ-$BNv)%uDvD z<0uMjvDKU`4KbTKr_l-V(WzdK`@(LcxhbvPdUGTOdHG|^Vjr=rnhR@^F3k2#QVjGK zzITiNC|kKRZMrl$ak2lxs{!~Julvva zn433Cm4U(2=_jdN0%|*f7T_isa@`I5DQ6MyP?^hc0W>k(dsa9$ol?TUygN!nIa1e0 z`Cs4ey_}Z7PJfTAXnQQ2a1Q#HmQ)FJ1XXf6)q7rFIUuPn}4O`xX_${bB<)Cp=O2d$f#;3urXocTq@;2-6%zhEW{?Whk`{)G& zP~0kWBJK#FWOEZ9FcCqEO4}c_07Q`ZLbtnQq?T#-l)3F&t0nHLQ)IZYmJ$~L#7W>es@oj~MZ(XRZ~H_Kr~nKwCR{Bg1nPpwg}?+ln2v2(!>>1~r&@`! zmIc>_o$eiiJ*TGx-8J9CQmkq{Gt#ai)B#Fy(f?n%6Hfw-@lP0Ylb6eEe3?%>_wT-q zi@HsIemk8Z1@(z>%U=4P#G+(}%=TOUm8e?7?i1{44eM_L6mct$0*AD@BxV&MU{7ODWajtIsIb-q$Ga&#r3OVei zTB>=dg``HaS2m41pk*y%oK`ez{D(8V}gF z9#mTOE|P96=&K0sJ6VN4^XgqDT3@=(7xJGB1L%LZM@d^)_Q^;McVh_9)Kd4uyuon=kGjd6l~!ULaVpeYH@J1_$3Ofh@((f~8dDro1drG<_50Y(N9kJSC-R7i*)P z-nWo`T*KJv5wTIm#GT~3Ati4li^;LJP*yR=k6Dg43%t)?^PxJ;I|yhjpbI}8O0>mJ zXlKLf9U)O1nem0trtBiql!r^4J%p~7`S;?KFgsT)|D3fxBqk?Y*1HsW3)<3)bUUY} zjr61%2HGt}D5!L;#4l*}`IFlj$#!d8eUBf~b0%COa&~M_Jzj~fqPBHe*4TCK71sWmjUFAs5p_uXJG4T3-eyx{92E|=6y9oKm^rHmBTb_9Ffn29PGRkwM zno_6+JD`6x3B~qFn6%*IWJe^^<4*FIszU>zYkes!i1DQZbv2I5$*&46$sM|YU^%1l z-e9_C0wAP_p{cDSKNpE9zZIj0iYi4pfF}d~!tuIMUU^Nx;^k-l|4~X!W)(x`R6bE{ z@>!M4(658#B;QD(ucYzwPVQ?vZxgrc3TnM#xs& zJTqngQc;%JkaJ1CDIghtyQg(Q2k0 z?I-rVQ2S{G=dVM1Fe6XHYxgXgr-)9nXlGL3HXFSj@esphiknvRo0K3+UGgrvydMs} zqg@*$lp+((5xUO$_f1T>P;xbQC@#T;<074V;nCt88bf_PP!kGT)H7=5e;xsiDruS3zmj13v(?8`FPQ zyO3WDZPO__8m{mWy z=UBRbc;$kv-G>dY;riB73-w#-J_j;=K>1h-cdZnY{A$h&$ZmP8uH$O*ZUxhp53D3?$HuFI|N`$6oa$6PeJ zhl$w9aQ+amJa|9Kt*DqL*`%R_GXZd^{?n=P<=H?nN zi5cLd?OGW*H|Dj69j7_ z@*=VXb=;KUYrPW-?!;KHrxPQf{X*f=l(eK-&YBfJLac*;Olx0uamrlo#Ap-#SQh7upI7fnMg<@VXbV zWJ>gd{yoJwX3Oa$B9aLmProhQs@(I=Ga}&^H@y}e?49@g4yAtR#7!~~fh(F};EGW9 z;aqV_m4IxOFJ28S@4ccHRS<#Sy%*Fp^q<>1@m%QefsK5mL-mBv2}w6zy~_^%sfVhb z2T1p`?LWL(&0gw5xN$o-LvbnvOfTjqJ zx_t*Q?H%=n)i`9Vq@M>?iD3E&@N%nd>v&X_?Ne)ZJQIs~4UpaSSR?k440;s92WNZqa*8U`(fCqh=(U$2YONA0=IqE1UhiqZ(3#hm8baTP;YlVe$_W^wn^#I7KMApz`Q6|wQ zY^X2MVkih@vu%Eu|J7tcWuvfeGg&#eby!}kIX)+7Xkt_ymY`x5ffYDI%`5 zn))px46Ic8zHGhX9M3stH&7!&PUL+OF5hLdW<8vFA_aPV44Shg5MjEW2){((lr249 zAd^kB*YLHJYtpAl!IaMRn{OjNnyZaU`_k>8&x`81ls-937BGtASm@Qd%j^#-&9%^y>d4yiT zTS6iYWpfwt3m6CydHwr$-@x@VQ9$%yz?bj7O_U;KEqUvo-glRGIWe<^@0((0jV(Bd z03g0xBP&)eb8OiyL{?;p;cBf5L<^ED30QLZ#GLN8`k!y3EBhq|H>cwycDcM9`?_;0 z6*3cY-EB|^aG)cW&$ZxFIm4K8D{x!-XTzC7UeRN9PsD%V;vTAcIG-tYH7VMRb;DBC z2LIhN@F|=b*zI*!Z8t9%#3xfa9r|R2Mm;tPwWl zpswg)Dp0Ohn-01-nVQO1R4iqK60PGrM;pG}F4AI!U2rLz!#}-~D{l@ptorm6d}gnY zKKFpiQ^0uU=t`GW=9kHY7#slzU=Eb5X&S6M>8}&G&jcOSr=NG~zo{_+E20efnmH65 z5K%Q;WWTa+>7s*$-w&3P?%OAy_b`Mu)tti%nS3BfUfw*KWF)zzx_C~~cMwCD&{`}$ zJ24b3`ht@WZcp*Cqv*};N_X|v3up{hSHQNb31X?t1U}pw5|4bS%@hm}u0%70D;?fq zNRJ8`s3u|(700C2&P+(69?Uhk77vSfRQfvk!t4!bxI2+2I(iu_fobbf)|;s=P#K1C z&_ZA6vvLXFAEPW0I2rd~0wEkwmdiVL1R4-lN7TsF0-@*$G!O^Kis-N+CjHokV4;U{ zU#rvx6)x|)RUA5)ZNI2BQNV&0Vs&7XKAWoD0uBu+4+n=h*9y*O-)I`qkIf1iYjjns z@KJ==7tVhp_+u~_MNiM9(U~1&qM1Quu+Izf;?6Y6pM$My$*N~8w{!Tay!Aob?jY;g|h9Uu52u# zpYa>EkTt86K?2`A%)I?_txkV7w=Cf|vl2G}37<{mc*2~LJAq9njO11wtd~moBa{#G z&HeoIL}l@3q`FJeAU4YE_BR2Mk|P6*W2t84x-Wy@=x!RTGwp!68Knoo&oJ}JE8cM! z{<|*m<*-N&P)M&teg~^iyf+0=O>#Y?W#d1*B|s{S8^z&2<#OYsaEQb`O%;11`T=rR zO>GD>$oGJOsL;)`Ii1$L-wT1aGlm=4{rj) z9Xu~l5l)|gAz@M4laAnvkqIFa>XuFVNlM$Ue0_Cy2bbP@*+)(iR(Q1z!EJ3iJ4ffZQa z|5K{=#iOKYIAeSzm3c+QylAcpNHE;tOuTRMjd&JXi`KG_nSd4Lsc^MIM{Z#%=&V|H z#z8`?Nc&~>8&?Ic{_8w11B-IBuYRlcyX-rNIu3tm#DMhnWKsBS{Fy`re4uv^EZwc~ zGq|6hh3OSN?sG!eAvw)tcW2mKV_)Cd0I&haYx2>RBax$v?7YFw>VazIvF?Q3?#>ce zJJ?FnzMYDRoN<41YA)4aYN6uSA*>Y*hOJ(2W#MYm&klT`JAYNNHan#ccTa<=Lw842 zor8Be?;_(uz-J0`9Mg!}Cx)toOjkcdYH!(ryDAg2jP=2%iBF|sHmj86-~W+*2aWLH zaUS)~6FI~tRU;&rUOcRRb41B5P*m?SYlV!3nAz>2-MGd8w;&NYM@LP@`NtbkVnW2!*ekZ2a3=dI_?Jg=R5;Ul& za5pvTy3954C@F}TMC0saYuHV0t8~hgZ@Afks=+vmA(G@fxhh)#Z45fq9%ND+@G`lr z@4YNH8UR&JF9)cwQ{JHYyz(zWqKh~ju*U}Q^?Y3>!M8uYI2Hel>Vzi-NSSx3@2w)j z<8JXervTmH_<+%tP@-&hXEOCByk-j^Zdgx^DH^cPX46@l>073jBN5&HYHHeY#ZnJ6 z$t5ANn;M{XA?R-2B7^D<;0_<`TKhT@MyyWwA5izPsv!w#CM5H4HC{vWBV39G@4x1< zd0j}HLE(L2rwwEx$x61og!uWVf0j%3U*OM-fOqVpn2Vb}cz?$`78Vtv8p202(5jGc z5+=5k6*Aw55i;%zQ)RH0(AI7_hbPk}92F6-qBE8=X!XOkLl$M&dYosn_I>L#izd!qe zi)=XjL+x8kW{Zt>TQgqkd3a1SPQZ4!X3Gvs93uG;Ck4!)AeM;vJpgKDoN>>n(y&8*MIv~C*>?MFB;NnYuRwREP=lXym01}#_ z!_DTVAS2FZGwmyPX#(kodzjSWM-5cZlxYU~z$cx^o|CFdi(Wyex!dKIW%#sq90eit z)1x!aTDwU$a#U~)?%?XP1@(NgC!pde3Fl5NmM>zQ!18It5Qr0X@j z$bX$Q@mssrhDYKKkKbtUyvd;CFpM3V#`i>CZ*^|^(kz6p4aN9^0tN>hPZU`7@x zC^U@XX3d57)39W|bW5T|pXo z{Af~S$QU&$@4GUbV{moE@bPn3;tcw9k20^37lAgnL+zV~CQ$s3sfPAdd925?7-IYc zsZgy;y0e!i1KCKBfYbt2gsPBPd6bqxm?n@UmLtJIz0E>QsE(kkQwBN|901Lptr&Fz zW(m1C<@b0mw`JIhYe!S4hgDj>wx<Gg2MC3eIiXF!QJ{RiU%(IOPeK<(*?G1krTv$oU0D9CsXp_u&e<5h zMAaaJw?Z~%pNv#>J2>eCxmSLf5~8=oRTHYfl*37EN8BB!2AGp;B)*i`p(u8Y-1-y6T?ztR%{zU>0-J`Ro4g%$LS zAF|CJ<)W2yr~r_kJe{bnX(G`fPcYq36#E#`n72-xxy(Rx4g_%w_yCe-Ud~4b3bQ%m zK|L*95?Wod^R{$lOdm2e!PqtUc`%4`!j}8Urd;E9VM=b>`e0UqDj+T@ZNj6qB1^Ar z`Ffiq7Pc1_)!C0I?=l7qdO+K=4N|(RxPBXb;Ej~mG2ahrw{Xu7^>gs4| z_8|(Yk9Xf`n{XAjwgeSx3!>ss9oR0VM&)Gxhw*S6=ll?4XX&4yg6Ca4>l+1?pi3K_ zpV?nF<<9moG68@y(RL>qrmkjYAWP}&mS72Jwz}Zq&p#A!dE$mAvcmOR3zDMmZQB@( zVRa8WfL$TQOcO@}7#aewgzmZ%B$#6;=|lf)czJb4+&*lI3tWYzz;|b4sNR6b$P`IA ztVt94ygp=;8`n_J%oqBKqgH|$t(`KAj;isc+{S*nIKJT5OF%nLoq?-0?qoh-I47;F z4aX)Qk09)*I!pJsS$AbaJ zTw7@DsKU>peoQT#QJwQy^HMm^iMs_n!OV;}X}80y{L^LpQyuEw22V`{fv`vs(YenB zlJFbcYn0M&aZj4lqOBS*Nm_e&ZSMI^wU@Kd%1}5GT}cd*7&jj4Lf8w%ZImk`B6{#p zR@PmA=6C$7`rqPlO^8qwRNi?$PzB#VGLwO-pE_*ZiWRCnSi!|&B3xt)v$@PG1ND;3 zS8x3z)G5U?SHh|wQ{kejVK11awj0=*-)E-B7)Qq)%($$uLyn{#il3g{Q!(h3Z^8CX zihy|zj6qdSML-n5BO)X zIMqtPy8MkuzU9zC&EvUhNhFN(NeyJchwFW;k;cnS{WfxSr1#OLu6F#~oD4bbU&uz= zWRBc?i4}}a&VBhEd@dRrF}uf7sc(AOCI{>YX2;E zO}U8NG|m5F1DHDel|De%FOiQbh*<6&QJVVKvm#bojyQGgLaY{K1J9GBK%fd>DQshf zdSM#e33J$75}>PDxqp%T)G0w+u;F-&9#7?s0KC5iuWH)8y3?*c9GUd8!P(&lSfYeB zB!M)+SRWK7dXW!vF{tZEbzNd}!cyy%MDW2b%B(640|c)JIa4*_8!w$(2kAU2q#}3tcGFX)-A-WJNP<1h4f$HWrB*Hp>#*>GLwsY?x^1n$d?_M@hjyOMt4hsBqq)N%t5?Ab>q;XNYRuT*_H(qGtpl7_~_|Koz} ztII{MisLolYQk_)e?$YsvF9ONZ@>qti4a`#fHBIBRSl#kvw0Xu_9;Td>KGV53*pxU zRhQB)1NjQkj4;9=MZB)4HzwTS#6D5fJ`G7*RC3Yyw?T3TCgW}peB`C7iOjXg{r9)^ zM=eT7EB#CB(VpP(DG1ToH=VF$_l;Lr2rbt(36(-a;_R|Z$qiToPRde-Wolg@MI)MJ z%A=?`aZ=>=Mk()-8LW>{!l@##oFuv|cfFjc?;0EYv_M%{T#rJ;B8*>bSGxyB_yo*-JGHGZ-td8g#W{Azp9a&HAa`&qhF=03+(Z#@Ne) zmZRhIOH8@Pi!&;AVhU#R6vJ>@Z2z&FnFI@&8Ori;Sws_YUxes7tT$fFE$UJktkf$1_rJ(#W zcnAU1upaHK#ghHd`|;_220{z}D;ZsAl+X)-&sT%b>qDDtas{a^FcRX#+JY_zZZVRf z@Rxj>?z)baEnTG@3ABXG>2PF_RA`h)szEre&=a>O7My|r2I`G? z>WCLSJWOvS5sn1@`KHQ1{ym^2>J%RYer#-IEy&i}Z@EDCGPMF9WnC5-X_Y>H5+07y z@?tnWrVxSce9+_$wC@h#iav_kMF=Yf!6$OV@-KUS4sv6H(EAta#)s7!(`*7WF8 zj*5N24+@MMB_nR1Gh`CYuFnSL5&f!VSjWlMzk|V?6ik3jm=Ke z)U37bsiN>){>PM6g!zu=5s=B*qp;VDHBlK7Fe^lh)wY8)aL9AZ0#6Z^M@jb4sWA z3?cDxp8~9*uqYV5q$U#DN8|OvC_$WxSg=j)L;1f;E{g4WMBHx6Bwy%KP`^tGTic!$+_cpM<0%jf8*pZp3EC>_}pL*sC;ZQFHZI9oi+2EQUihgoV^`TOJo8N_4sJr0-Qwv9IhzhO_9QQY!=DkPN_|Z|Jie{D|ccu+IkYads-P<95RzanA;h(%4c#F{wLLV@bLL@PXSBoDCR$!Dh z^i43GyRXoMO#|f6Y>ox-jjCqU;gV=f`fx!M=@FgO3~G{8+H+F8C={C?0d6HyF#7+^nQ{yQe@m1H8%?Pk z5lpgd2vH9avTCwM3y@)+=;7Q{#pFu0NG#xQeXLaj^6UabIdqZ0H%Gu@ZX4$KZsDYt z7}(xfb$6Mo>9AT}KGQ=j#Qe7u-FN=L9j=I{o9i6DXHLS?kGS~7=%ZpSw9Mfw+^+G% zP9ufh+o7;SG2DS8-K&<65TRZt`CF(ExPPE3*(xwvjVfesU2^FwMVrj43mwAg=hs^4eTT zD_Rt)daU0l0VweCJ?fyReg{NSbWj|3Uqg%8@ZX9(tiN-l3|B3vj3FilRdu%OU#~~= zUQvt>azNMoj;B%~w&ItSvS9^wS;a+uwxVm+9CTd`SaeZ<0CY0SZhQIh`mtf!sZYsc zRaFz!b%FT?$5AgKFM(N+;}D42ee*+L zJ;P{vNT;awoZ%9A169X=kh(5KzfTPGC{N^-P;d!y#~P3dF%X)_?Z?GKInXyBLlz=1UfCe*_VUk6O$+SbAp zQUCOv$j{fiaT@vS)ZIye#H1yJmkKy==uQhjfh7PJ-56uIzVAM^M1oHHoda#aCV?Qo z1Kcu&(LM>CCi+EPc9~f%@L5w5jtB-^0jbmu+{ai?x-yOxVj~&g0eypLqrm3Oa413h zfqA1E<=QbZv8Hog=1dMalBLMxF32>PB!#P&P1-7ji3%ysr)U*%GJ@QFmXSS}RSOFy zMm)B6HiS4ur~wn&-SZIF*R*lL)dMQn{CJKM&~^+O&hq7gD4}sEDL8mPCcsI%7jD70 zm^F4tDB&I55ozPk*W9Os{T`3lZfda{*IwOa{Db_+SPqWAXGj@h12;0I-)tpF(^d1lD#l)0Qv8_G>goI%kic{B?rLT*Pz9vI`BwZ7)Xj zEN|SXZvT?yLIy-4Z0^WGFlUkB5p$xxAKGBY=>HUx)J|C?Z&$@l2Nqk!^$IPn)9;MV zd{gCT$e*7>`cBrf=FEhJ@nf2~VccSbA&U8ya5P2U^E*SV)FcApn0uQ0{DG_8pWck{6(W}fyny#2%}-*I;WX>tYS>b2D>a@Q7lCj&_uaE>w4g$`%H{o zf!1HSyKf!*dmt>jM%=eN_>~#l;rB_D?AZ{eg2X*UvE0TZWZ_ybvL5 z-}TLh_|)et)!*K<@U;x-69a#`=3_+l0r(_5)}DZ1jQcBwjds+Q^FBFLu{wDx;Z92_ zTaKdFQBp&wc61BYi0;n^)TQGfL;=3<0`+i`UUn8a4D1^g$dOK!MP2W?p)+Oe~XBiC&(+ zq)%O)CzQ>L%~7b>Axjf}z3gsl^GfDI&fR|B_v{~3A|iKEDRtS!8JyzTEm(I@K)^vt z8SOMxbKv_#k>i@p;uu)L8`2CIz}-JqQ2OM;3Utex_aJbOqa4iO#XruMvdH-y0(hD`&f^VW%S&u$)WY$!)Y-=^j= z2F>1@uvyeE1U1%bZcG?f~bxHV;eAU%y zhfQ`o?^)|FE30v+|6@)d$FdrvAX6}c;d#HqT!6A^VX=g=t_5E|#*}H6M5-Bz=MT9K zcNiJO9G#w-S@<1jXH#(%mlk=;hhwhOpJ)g-?aOI1gq4g_1OSmyIu0yG*2NeZ zag#_`2uf}!-#hhog4Sz-$I$Hj|+M^gLmB}GbLXSaw#9=MiN-!bu=_8 z$nRG_v-iyjnMdCI`JT6>Sp{bdls)RG{ZqB4?r9x&kQYlc+y!L<3a)tU+mZ$-)A!pK z%3CLZgdZYYGfD1A-CKhtAEaQsMb&9$ChMTnmJ=k~cXXyX-F5kM4PR>-_-8;r&X@qD z0$UWSz<~knSzC!;zan9EOZb(;RCk`j%cbKw@w}M7;)IEz@9TltUOH()@`=OTiZ^NU zap$j@*ID$LC|;qsm0)3^fBeb}jm6TS(}SIhcRa%qa2+(R9gwb5)l*k=vzX6F4C}Q- zDPt1a+J=(!AJEjU4wFB3#2-48GhHqvQx_m|JqQ+%L(b7Mb*R|Jss_Lq;+!OyCRdwD zf!`^e7gt>A!;3;SPEzk2*YQk~d`Kgfn9!mPP3S#!jsIF%-y-fwdw-QFhlwHzy#Op;bIs_arg!6njxhyCj{E`Hm z#zz?f7A>f?GpacTkLx5z0V;Oprw|W#-d%HAU90#)tuZbs+7ooGA*^#DsSZEg=Ym3lnv{6LzGkh1zg}^D0tn zHH9(DHi%M-Ywq1AWKOL@B6me!{S`LwUaQs|gvSn{QKP}`cD9i=F&2n?O@!fhX_E-Z zN>kXnVQs<$Re1`9-W{LK>!9%WdF}C>ODG!&ct{Yjk?r=QL^b_D24lHy6~Z-%nD8jK^za};362vkwOT4l3IxKUIDlE!L8paNO^~|fEZbch ztgL_;|4a!_NdN_QTH&}{q)9HFh{dq5Q36r+QWgYv`wPTfl^msc<5f`cjgy$%(%SgW zYP8HY>)0B4N~R63DggGE|Q*Y>PavJ1OeCN9Y)3sWf%fH}fYIyJLXn zMK+FKeuN_>n2!HGqyKFl&X$nxjcT#33W9dX|yoQG0qV{3vrp;qbf4AOWid7R5ej z&QVR{Q1c0SqkX*1!!i>rOf|9Git@ot1MOc}?_1##^;?I{v)ON_U}v9StcJbf`DGRI%U;!h!x`Q1!~>3JwM8xsN_p^Z{-{Ru6m7H^&T*7%bHhE* z0)c_tCJm&o=^PSk3A7Pi1*Ll=GW)FqueetJFRfZ6^Qi6@ktAFz1)L-+*@T5h60aI} z>fUyS;CXxWbsi(239~t+8aNwUTN*@C+@4#m zQZ=5plb`5^`s&Sk3$`%#dvw1t*|gW(?~*zxy(s}GOWU+duRQF*xC(a`@oYs?_5uQM z02mk}t)!ilPx05C!ovO}uPLzlFIj+8!Vsznxf9c`k%I@Qwje1@{A;dE?ND}36FXzz zNQzm&+$`=&hrCHYN*!TN(H>nVHh;`pEm|VZAtj#aUz|l2c3Pctd22n|RybJ01=R@- zn%aNm<6XN)x$^8gJR<4=ivsa(0w5mxE7#PQ1&j1?Nt!`y5klQOz#l*s;-+jEskWC5 zkBUK5gUJa-w8mfvG`p>H=7-z-Frfd^E7~;7%H2N8CE~3ED6EqXW{?%EY?g#Fh>lk@ z_K$|<_5IE%brt^oLNF-7EtqKNpw*LKHy}x|MYOH@&s`i`rR&hZ3}GQJ4$#NLCfnjh zpR#8($TGV9%O`m8gSXSvkcL4dc(uVVR)yNFr4R2^F_p)y-s)n%BTNN>V~@YK?Ky7$ zy3|r!$n0gaYpK&3W6sew{5`&Z)%(wCiP={~tIeQ(kMy`Gg3~D_@Ta-v;qMxPLYqQ< zZb9S=ga|+1!#uKwA9mOzR-Ix4!2!sDDQe^%PU#zw&4{-Fml-_9g<6)lAYxHdC_D2A ziSz!sR-g&XH%6Bb#yser!bi%S7cg5Yp;3q%cE4dqc!S8*ol2b%FGC&~3pVIF3=nsE zR1G^Zj)q#PqFBh-;Svz^&6v7MDrB3ETrVg|RQ=oLCV%LTCT{1?+lI@{t{f_p1!#wq zhHf!&3IIT%m)@labYMw3!KC+?^8oRiL{7_37(;aH%8+Xa`xtSvM!vjWvoFAqN zCYdH0S@}^*HB+>ZHtS;N`D$LD{pvc{HltK#qCAptwIeu7nIDpiB|=xtP4YHN2cEl3 zD~T-yCqp-3sr?Zv6rWs+WIjdSp6M)4(mX_7-0+)tXrIW)lFYV0WTOPdfAr(pBey@& zxi=g##NE|Z$S8Vome%S=mF;RO5&jIChYsJ5BPkXB|lgO8lTwxqyfSNa2ez1Yy@F_U49przYd`%4hl1wx1 z4l8x2ujiY8dFj$40ltgjkukMvRZO?c>=s4UnG4Vlq7Ifedt!h&jK!IE zlqpPL5K+cDIMqBmL-LeqX6o%2|C*Q5P_BxD{r&ClmQ~S~OV!8>uU;W%C=ss+?#7p4 zJEe7O?xp%uS{Nx>s<&RWnYD2q`|tXmxn= zRS~ZYz+)c4kFgJz#F{8IHb$`a1x~o1P2F>e8(%j@coUeOdyW){c|uV-=diW8(@{bK#EUz+E2c;wu@ zQ}g?8b03LFxb*HnH{#y`kO}(020rvVA*%iorsm;OeRz4H^vBY$^U}s5Rku>6`c@dH z5G|-7HlWL5X;cwOgq|f4qX@TK(m5Nao_gf-0-~`#CnON9e^6XltxT|Uj$`H?$dd@E zD$jaca5;Ax27nFrtb|?5Hn`YY9H)-=lyPheYJPYOg!OlZ!&{?o2>>Hr(^AlI^&J>$ zy+HT8zbRxY-wRy4)Z&|Eha&d8prA25I82PZ4iE3Ef6iz~;W$c9+z|dwEl%@ptlKVI>9RJiS*ywauW%^o~ z{f=laI$C8n;v}k?yqbU5!AY*-r0t6_? zTK{-Bp;CPebU}`Kc^pm-7@YxR!ZL$_#_608?o!5`^L7j23W3CA-d->mc|S@c&9;hp zFn7svLK%`r*K(NzN|=_fUpJ?ILQjyNub5l`Cb}OkZi zYENx`T-#n5Y4$58@Bt(2=51TYmjqlo3sZOEo!FO zB`(eVx87-25X8u1GT|$OvgVWQYJYuCIA*-Tms851)vjDw1oG7ZQ67At&Oy@=8qYp( zJCx0G98G(U8z^}c80v7RaF?&k&Lzl;YbkTir~Q37P+VPc&xU_^fPG`X33{vyer^C; z=%9|gkUHJHwNAt1X2&2A92HPU;D?FUSy!Wf-R{vTOu-J9cU@Rq_eHEo4F5eSOQF1h zJ@NK5gyJ7v-jqtU34G9;2oXeEO{fD>7s2B#&&m;NK25Ea&vX`iwgrH_>gSibdS^9S z$MoOQcUOgGM)ij+2r3E-9G8H4lBE)e4bU zMj$Sfq0#%t*wH#}UEGpsfDzB6B~O2_nCBH?$u$EpHG8NrvgCqO7%a{)b+i`YUWsIB zeBGyuBgG>*=<(rhP~q$<=ICJS#U36{&DZ&rxkRM? z%O^Pk?n!**l~%^kcszBH&@ai70laN8gm~;5IAjYvRadCchf`mAFnav4`AS%L8(TL_ zOgU?^L>D;pHvO7y(wD3^55PbS(LA{I8OM|fxn>Hxe^WGM0%%3Ane;y}wu&xFXaU6q z!0M}C?iA$|(_7(n?0crJt7s?3Kewa4v$0iYNdtt>3!TTU!nQ?90MAgc0b3OD*n)KL zUVw+j(XOx~?>bZ31*y>JsR9361&`}zF3&Ehb)4wJdV3v8&*NT^e*I-ll01Ff-@NGnS#{oc$y#IYc5w4q8W8~m}EGK4~|GuK2#5$WFYtn*bRTLQ68jDzUVUUUW{5Y z1EhW+hA@(yUk^ZMKc$nD3oZ=(OoBLBZ%P&xH7n+8nBvD_R)n3yDblow3H#B8)W z?B)e-Y3-Y=7EN6CqnKVBR~0V3yD8ha>e`3My~Tu2sIU=e_#)SgMdd5PU@%0iD=2&*rsv|hQ4fxQhQsRqpY>oe`!NgEVhISwQYRS0I! zy_~zL^-r#Q{5eTZfRA*_llU=K7z(x%^PqSPEo2>zkO_*!htPe7r&k>AL@sTWfOyWpngwurJ~CEk2zyQ1jd(Vx^e;U! zA;O)3xSuTl*LO4%w{Cvfs$UN<&Kh7}W?Nd9^LbK;X z2Bx&JP*b)xUmGMJ9T0JczuCu02&nxa=-DbT>da#?-VrpgO_uLq|BVbV*4Hub8wLZ8~zp> z=EmNA)CX!muYB}8vF?9Y2%|PxzoiVsPBWFG_Y(g;pGn1SQlf_1I6nMIw%&}0KJ#chvojXQC5qt}ZO$*u(?w>rQ}b&DrOo>QxC+(@)hL7Dfqq<7QgHv>t1# z1F5Wk787RV(ueT z{`xi7`xL)917iK7@G$F@DE~|>aKYZ*N4nRG#>2;6jHqBLMuDg6v^7YkCsCYXmOE7@ z!plLhoE$qqgU>Q6@KnrqBAB*5C^}Vw4FJ{j$dXWoWsCJ%S+lrpHe+4dc}e<;s0O|Ei^E}XAu%_gXz_rFv}a(c6ewp6_UC0I!djTX9gODZ(1PX(C@v& z9S*;oOSV=h@@>)f*#VEgej-*o2fyHN*hQ^YsU?(a*)@*@4AI67JLu5XGbeG%;6cDC zKOD3>H{&GeyX9|@JL?AT^FQybJ=Bz%0VBv2hag6da;HWs|1ESa@%=JxoK~wNU#i4( zyEKBp^CBmA6VX`G6*7@YwU?d#=);;x4pcUasL0xo85W9fx`jn=f| zQm2PgBFxKWW!r|Z&2fGFC_3A@5ptDIQR-63tmB_Eb+r6S<`kg$ zsajX@Rl`rEaV7jCp2h3`xH86}5lduX>7Vwopgs_30VieY4alMN|Oxe&!DW+-k0m7oq`jh#&YUXxSaj+O;op;CFV!f-((>US+yPla z%t?9K;|+-_RE@@({ZxI8-V!OuKzZg``*Wr~nl;fYmJ6ivLI+y$RT;WR+;Jz~Zp1#C zpCJgx(Z#<>3;#LyO0{oE)a9FQ9kg3 zSqAuGRKLEF?1*e1nvM+-BrCpsuU0`9hCn&lY9E%MRe=|bTtg-`KOH&SOIPC8Cm zSx1rE{KUst9U~c`gsRuSFPaSV=V`@TOoU+WmMl=1mFy{lyIYR_@~oL^ppHJ(15KyS zG5HE`D8$G^7MO_UWuZTB+7T|s?fCVT8+{_)j2@i~BY@t(4+Ndw^~#3~D=f~Mb7fP~$*>fB7H4%$ia#4WJzk|QiMqNHA50rA|51>FEof~?}TFcYRL{l0^mvVV#E9wHr8%rrR(G0Qy+ z+>j-MDPqrv+{)y4R`v^qx1UAAl)3IAwMYi)LwR972Nk;6UCB}MLtZMFnaE{{U}}nv z)leH22z2kPk!$y6;sHUs2iE?woNi@_L2n_)T`;xPeJZtla7SPEd@x04%VI_ba?{bu z&;$}rNPbOOjX0W;#CUZZ8A)U<2F>gc^*?I()*Mvx&PijWY5~J$QNekkV))vQohXZx2vs0fg7$YaAT0^9zj}B$MrF%UOhD9LZ zr0@@XGHvFr)sc>zUW1~~NxD-~zog=jrWNL!kkqf=p2mqsy;36Uc@ij5lRRaFhO%}3 z>qj2Q0mfDI++s&vk7l2I>pwu-+wYF_`cPXA`Sv4E1lAAA<&c3PhB{MkeitI zM4T_)@3c^noWAC}L5~@4ocZ`M-|z}&k2b<4nkEzZ<2nE-W_~&a?U)h~N2*(d#2aVf z9YCTMIY3p`8%R+_mlaq8&5|4HU8pe|2OJariR+J#E-%qjd*1d8c_Awao4FsGhp)LZ ziV~xKS%3H-ls{vL(iK4qJRkP%kh&T->W%i^$b@i)zsypyYMtDBcRBZ|2=dIX=}R*; z%3go@=W&mShYrWo|3m`VV4ZXV(?YUgFSkYB_ALnjKT$a9hj#IZpEtfuI^l>I0vKK6 z&SZkir_FU<Se?N-{Ki2)92L+s)aOeECKeuRtuVF4RF**@c<9;LY6btYSG^rQ*s z^_eIhyRA#{4d{z;nx@)Pee6~0O_j`zl;gC|lAVTdz7PydD!$L;dBUlrTpWNem9v>EY^LUu>Xsfjvum5WYKul(la+R&g+~CT%oApeb;; z81nhyX%EpIhu?y#INpYU+g#rq;H(!vY937#a>(d&csB7d4~?JU%5`e3>E8D2Sc zesTK;D>=!ggv;hroq;h4=spR?74_RR&l2ct-=|`%Lk3qBdOUn}9?NDf!izp4MD!yM zv`4}JvP5Fg(Mam8y@$deZg&RW5Cis^rPQZ;4%mz!ThvIgV~#=uPBBr9#Y;?Jw(B{& zlkPQ&zaR!p;os~{5(C5aW94PPaeldNW;b)r4vARy*~M&ERjR+7c;a1fN9*u9hR$-k;JXJq1H!kCYV3EGtfp6g&ABzW0syeRNDsOg7u|GPr~t-t{hK zg2-A-vcD)e61gFIzb>B9@e`ksDl@cDsrY%MYdDXi;;K;+ZmQ zrvX9UFA@|u`Fk{{Uh|==K_CBa+BfcB{i}Y3Ylrne#dziGmo66;t%mjHYIfC>z#%5r z>Ok*qral+Gy%T;c^i5RY0UY~tl>4%%`2cpagHClk?-pt=SP!Or8`K1ci;EBJGt<*u zy92{@54~*94g0bZYtdCON-YttO*#O!Kw=Ya?G_Z(bcOhh42P_-jE>wlyXk`3H>dAK zBIc7%VdM0Y)!IXa^@Rwef`p^N?trtg464T=_*=y+n!WTh@Ad3yimdN0J%niwyiQ*( zu*X+@6KD3^`N{~#0jyYcl0_v-#8&4=oYxpb50}=iO|b3~xj1Gm+rsZoS@HVhh%mFW zGEj;}-7|+BL7bQv##ip~Sm73dg|wug)x{MjlvYARE-OcZ2Is=lK!W+4_{CL`fb2n$ zFj#xeAF)^?TFU;)&J)YXf}hV+s~zydJh8-_51H^v#d>``;8F^*pTc2!qa>S4;DBu=`WN zB|ck=p0cIJ;xz>}ArQC3*}#EtpR88W){&)^;e486l(ckeOWL;i4rymHNvoV@dXLdR zr-CHs#cmP*sNfV{Y2@Hb=P{qhL!(X632n?$kI{Ucil<&PywrMyA$VTTLxQh-X-%w5gu7oBL4jJ-*Dd#3Qr={47wy+s!ZD96TB?Jd(WXq01V5%~N1d-ZkRc z3L_t-lAFCo$?WtoKFBmwuI*51iNfMR_ywaRBy)j=(P7_58Sgb0Mo3gy@I(byAm>}` zt{^w)$!yBnwLL>ugKNX&<@^Q9;(pD|43e#xfs?2!pS~}#S`;2}sAOP+O~@2i@idTA z0r3Ao=lHimF@Jgv8*edSbC@-vgr7brd@>|@Hun}$)N&`|&n2*c*D82Pb5xq5j!}7_o zdPw(lrLE>!c`83C$`S9z$GJmAY?zi_OA_Vav|TVGeP)(Hvd%v@dE!>dy* z(jtxo!krM-7Y!vxu-r|YXOqFy;kv#UFdPTI6MS>CF6&{xhLee)I}+zXY@i=%%IQI-y5X* z{-vm4jTev!+!U-WLRmnl>aXO9n0~`vXCuuNg=H$2s}A~9vAygSZGF9HA%i6VumW_UIbc0{goi#QJFJ{~iE8F?I zrMOqO(rq~ec>%Tj+BdgTI8$uvZkoXr5G@!d2R6=f->*u60aoY+C7fyz$8JiCRPA?M zk@oF0#up7|>VWhceLnb<#uxtdrnmC)Hza@H`{!Sd>z6;ZGb&_ItOjJCy06&#;(=Eg zg(GwH#~x7`BnEqXF{dd~vO7)H zeqFAfU}BuNCbUfSA=H`=|+S=JKD=4cCMBT9Y| zx{6gQM>~ugpBZR@;0Evpg_`V96dg;or$1Q3=fKfs$&9*cj1Xv*!XKT00#T`gG3nG> z{4@^}PodGD1(#r+Q}?=-zM<@o=;4I8bT*3PBJi{F*?h@AQaZ=xdjI?V9 zTpZb5SZ)|HTXlA#Ft})NvgB1}kS3m#Cy;o6Jq^jZm3I9&DKpcbQzETsTLKrg*TycL z2bt_9pqTWZEqeevLSj#X_D#xuH-yd@y49O`%;Et*fs+{3R*9(qCYvJO$2}bWH6W#` ztbOoLA|*n1=^iz>(&;G~Qyg=)S*YYxCQrU$5%Fi-s=T=-L3Cq{f3@CmB_W@)Bs8?4 zR9W@kU^iG{Es^RCq`CRC&5a_SUR3v8o_cpH7#z?@5S253=+)xBOCRRWffqJdD)(}r zhaA92v4+1z(47?PxAMv+)%BJIh`Syd6BAM@CJA65#(RbFOy|_z{&b2IYXC)4#tPQI zk%WzJc&%N^K9Y~aPj7S6_tHd`zNF-N$lDUykVT%(n8%zs1n}`3j3cG*(~hzV%^!MP z!$kmxpj*t0h0nS&_oW>mvZb1 zGnPf;&X#Pe8&YD< zx&D|DX+SC?B_2q!P#IS_X>n@dHJ!xLH_fM{&J}ucnAh1=T=a8C?c(e7AE>0UmomcBP zgXr4Hrm#PYk(N(JR!{I{di;@9-#2!aCQXf2Aw;#VrDyx*&1AihI>bc)Qu4J`X_0mm zTH>)yS~9+w@$e#|@fQLkL?m9Ff%-BBgoK^dMPI&a&y>G~46>CuQy|tFe)M{Hndg<0 z9y4&6m#b{ud|7g>X*6zOw3UBD&;-SwDg_!*`yzoAY0ng*XCqLp1GYR$!E9%jim_T0 z7n0|p_^LZ$3eI3AXHDQOlh(VnPu6-f%IZUC_ST6hGw~L<;Cu5HBB!4o=x0%e09MQZ z2mcp9ty3)#Ktd!BWJTnQ`3Ja(huhv>XGrzpRdg3z9;ncfe zreOrPHx;YMW@X1*t^3QuoU#% z9n?9+J9;z-I=~kTTXxE(tCau6<4-v3HXo;M*=0>x)bukijZ1>EhE~yK$6-S$Lvl)( z#nAh>Y#qq%&?a>02A6%SS;kICfVv%d-lw~~%NSJ6YSUGLRPVF)tPQ*?0E*kfn|UT4 z$&fDRz;Ohn8r7*zPRdGt^y=Hvk#kRhUd`*p1bb9HW$AzL-%M-Ms89_PN2&~DCs9o+ znS}Tt*sL6Rej7#gguP{MSaw9i{$24phx*v2?fFNvykfFj8zwuEnYFWG{S8*P2|vgA zC*b5yKmUzFm+JnZPhj#hGu!N=ebh-(P$b^W3WrC8h6(lcCErjS+>sS=SHnW`vdrr> z^OUg<>bfr!(MTXf>lSR|~&6smnxnz)zvJU6P>={0TCWPbD-a`xc62=9AIMt+62 z(%M#cY3%`CYgjL0!L$3;etIG;h99w^f?2C~<* zDdSV6Cwe9rB4PUBmLHU4^iSmXa)}+fqGeQ}4tM}#NN{VYr%QQO+eN8cGcbnoX~6;v zw%{}crp7yU^V+2jn_Q2`5!ZW8{a{WxRHK^sAOb~sDt>W@Yhx7r*JQ!+vMcMYBufA} zKCHxQiX|GnKw%+q*M08wN`a4p$Z}<@_Avhe?8Z)&>1|9e4`0NpGD`I4Tn*uA(0vY6 zR3YXBVgtl)M{)f7I6TNw^AF;Sy(*oeKc}P15>Ib-7yRI)|L0+2fU#I_(5hTa&dte+ z@fLwXA5E~)V|rdBi?kt-*#J3}@=uMs;7lnwTeAaL5hVRDPQL@!w&5gj7DHpxy`?=- zX$!PGKCc>qv4f+Z>(qn;5yTTjQ`AtfHH#0J1aM4=PIz8vr{% z#J>*JRw6KqgNs?Uiz76t<4PG>t(23oo7WHeVu&P0*3@z>74GcEc5$%7iQ?iqhcS(A zEP}nqUab=r-0}Zf>y4kTCufPy0D2g=9fJjK_DtMcb&!n5I(Mv4?A+e=+4=>i&pM`m zNwOiw#rl2n8N|FPD&Wukg|(?t3&D>t^^2C0q!)D!pYB(36$v06Cf< zaIcm!APW6s8)S5j-#6j?v zkusM)0N81Fm}i^=v9NA#wXrrQ_#|16R!(Y6S6JKNCd&&@K3}_@8P84X3w3Mch)1A@ z#!DpM|Ii=dFV+9da=It#k+o-8P=O^kBntkaQa0pkvT2s(8ZSG~L;wb^xb2%7rM>!F zt5S*YuLMFwGmj_*4neW^-dFy1YR$`9tyR!{g~kQ;j3=|`p0U7$6CNQHy=DUOO`GX@ zZAI#Udx*O%kl0c{=*?R%6sZ^*Kj*eBM+4Pinj!j)ODYcOcu)#H`wy#A)&ezP*&`;y zKHV;FE(RreG#2{QIGTi(dtZyrn1kXtUcxzs?mdcn>i*E# zPSEMJ*~KTBLH;KA?U}JGwrDHp1Ip7${87=h0+V%qCXw^Ci3NOCPg0s?4h$QS^d=Oj zfh(8v-sXa)OpX}`NJF#Jy7?Jp{aUhSNqO}MUUztfh!3E{)?zC`cnfo_-;!S?oqi_k z%InHKp~PG|k3S4=n=o`M=_V26<}&jKOo?UxTQv47F<)d7s!;?$sIP{cAd=xJ^`Z&KT6OEreBdmz`D{<^PDrft+5?or$8&qU8{ThEaut zl~<%9Gd|j0pQ=Qa={!=j%XZ?C8z-Dc+^@-?a;j`Zb>j$mk7Onf6ARob4HQg_f)6@g z_e=B)nrS=Od?9aim(q6r$CL;Yo#sI(zA4Flq#2R@5jluus3x#v`FZ1dC2{YElp%O1 zc}v&p4=cca-60FjKmS+a;Wmhx~H`~{i~pyksdj=DD_y)o(KWhH;etPf>l zZ8tK-o(#}hOj{3fXyWqT zKmX=Ckt~|>CUZimc`@;vjwutNGxjli?fWlWA(uB8r;5+)#1f5R%iWl zX9fsUb=yozf9pVn;Waj0d*vAvg+`w9Rm*ccDit@NC-ps+N2J%*wB$^ndO(Cnp8CWf zXL3|wZ8L1yBcfO9SczKLlq_V9_s?FJ@>#|x;Lkxid~m$U{$1Ig(Vo7QrK{4PJgfmM z-`g_Z3PbKdVou>PWMlE9#xc=;0kWei zL{LOyH*en)?2Xb3`qaJdX29J=cicB9B=7YlcT*a$x=P*A;+?Z{8#^8>-l+n6bVXZP z({tXccdbHcxLtAdrfDb$Hf^~fEx`Ogz9+Me_A6=3qAT9WTnrbGNlJ2yk6qw_N8Mlf zcP!zK&1&??Q!@-^F$@K|0Al=#keV3}^w<-eXb6!rv8(B9wI_ho(F7s{VA*22OM`Yh zd^2{phDi|*+DBw|-(6mmv@b0lyvESDHCxEO(byq5rY|DKVkL)aO(3-7W?h&#QLa=Y zq%bk_6^|ngkRL0*?db~5#;=@YyZuw8So;vqSf7{^OJ|4C<;AgEHE&6@xU@Qufw7!3mk@P<%2MBX7}=i^tlV1k0D!AF^Ub! z2%9G`0SU!XD%sv*S&S@7o`@)HeDJQU4E?D}XKINuL4wxs?ivUozV7!K80 zX!I6hOfa(aXg@w6H~rFE9Ajl!A<9&=H+7zf2YbtMVBm2YJ6?fJO2!R_)f}W}odjvZBy9 zW^B7XS|a^eh!+aV$%=IvIAefoO-I18TJ;O`9>ZcA z!A)}~UNZ?h8mHkqfuND*M4r$b&)bxzY1ukbR_v|i#zQ)TXP*))CC5t$q+Aa8YG{@8 z>}N@u>WR3BJ|byV#PS1m74m-|K2#*4`{V?ZCIdaE7OF!$@IgdcFiGHiR%0N1d{rthYj=m_fWOdbFB$crw5aEuXV_cer(m>#mE(iDGH{( zC=m{K=JXjU`V*%|1T--O9~Ih(YwNqS2)iNCj15h%E0h=Xh?;4y;*>x@L@^)B|5iLB zbx*8OU#X{G{DS|3WCtvESc$I2NcjH)qxPTy7%~wnhgT%GvB~Rv%23}gl*|}K>97uW ze+LNY7Q0fm_i%tDWKGY~Xjq+XWDL!*&u3Os$84Z6AR@zeU%k*$Fz{Q&(+3sn>|$db z#4rH&6T+hE1Zbrt@k-smypU9&J?Jf!{oD912e0-|TIm=VXSl6K^(lNr)aGsaWOoJi z5AH_qe2nhfnxM>579*^?D7(~3E+5B8k#=<9oL4Tn*c)u`_Wao~YS1Z14j5LkNN26Q-2nu883Am?7P_ zK2)bQ{gEl2{YQ#Bv)ln3*(_lr*1%`}v~q{N|hb!m9;`vnnJWpPL8{O|iXU;ZmNj^=eSQSY+6<94ZL1v<^3pjXzNBcY{<2$9tlC&u<(!%zi+NDI`^j}@ zPG7eSgu5PG90QCw>|Ju-+^sOucX?Q#y$}4Jgn0R!?3nu;DLu36^k2KHrjR}>U1P*R z9unf?R7kdY5#vT~1HR@QopU0r>Xrwqr7LhLf!i^+C%D-wOnjb{(a-i7rC% z;l=efz6-;BMg_C$LQMa)A5L+Ff<{VAlH6FzSXzer5-hs8&j68>5yms?+ul8jNScpc z57w}oI3XcBV4SCkp-?k#L+eR0&l$JB5#yCz{lEpXgN($KC2}a;%oD|z5}|%PT+)(r zVetT_oyp^5Jo{4B0GwtREDuzxxJ|0Wj7y;Dk*sN9uri){VP)~3We-SplP*jaxKAu| z?DY>H5gFIboMQ)o9k-zlc7eZhdJL5$F|saP0_wuTC&CUj@es6qBEpv)*Ivl)=gYzP zD-mrcZo~a3W+hyz)-ZEUE|4nEsTpXTjh%KQJ~e)p7pQGFM^kx;TN$(PbPC!JWV1(0 z9_Gssfr3&sL6aBs0du$;omA(_xNQMeZp)|Qon*Q$se`i=-H%{i>%SOqizfA5uuJrZ zlM_+DAg!fjtQ-AfSt9@naJKjl`;KX9uACnZ1q#1RmIJch0 zOFNOxC8GAriv=MLHRG@ zXLX?yaZ;T1XMe&BF5bhp#dlXDp`6L7QC!P9+EY1onw6Jo`fF$d=-eH?U%SheR5Tfp zTF{%&_IJdQ>1YvEo}q&4hVwSdETF;ft6)=VF~@y(FStf33n-v^WA9h$2{!Z$nlQ(q z1=h=f^#8D`V`@Ft~mT?@n&$+Y)wD1E?~+wp7ZpgUrS#vu}2hg&!Gm zYBiaAcTwBRN`4F54}cJJxb!^bLhrd>Fu+3TO(y||1R=^rD42R7f0w_*uBcjcNjM)t z0VzV&P3PF9mkV;r0jIfXtQE2_;aD4@TW&jPfv50>X3L;sg4@XS8~oO5hqwNm%g=Gn z2wzbfPocOWo21u;(T3P4RJ;bx+-c9(WtZpOp470+$9(kn*S^!Q*%^^S_PDS2?5T_jo!Ki{FPJtlA_mEp_xC$d2%cT$P`q zz)O0IqwQh)gnl$ksd)Es^AYb+iY#N;;zFypiI=cj5{};Oq2FUTC{l~lo66Cd1>(0p zad>ZlX9=(DJNkM?-5XjqBZV1cMJ4>uXi__N2(ANLC#!WNnXr?Y;Vdt4`NKQL7)#QV z#i2D_bN#TvV0(eO_cmZYQQyFl_7~Pp$j}Kf(wDF#O65YIbOFwax|kjc`x$!px%H+j z!+eIRhB&3v_!&P#y&m%dr;442N7JcdLTvk;VsY{l9P8?bs@EXe!P2t*rrnv^u@<>h zs4^QrU-Lq5qt^ya<)bwhb@IuZ@S7_#0uw72*sv+fV|geY13QL~f;hV5#JhG>pCQG` zdfjX3j{_EmY|44RBIhA5*AW0IB33fYe|^%4Fvcd7=Q~UYmKkPIelWj>t_7LKoouc) z`Y@sV`?Cpt8Pt`DInCTlZw)NiuUso=fEo{o`mMZBLb(KcI$N%jkTGn7_3YMPSw>){{^7=nH*@tn12?i6VKdz#$8sNe5GQxlH`G#ad>^;KA@ zmWMAsRKK%HW;5XVdn8PZM2}gqtaKRqmj@REcMNLLU6(WQgYWPpiTo`UazTfZ#ZAn?P z)Zf4<%xLXZF`p!ArC1XKS3|7RK?!(>OcKf8IYlXyyT#5dph0(ewz`H!5tnp-W76sg(`9n`2(O0JD>pd5c3Cusu9DTi45<8>+#m> zt0&dRN)cDV4e#6x!hO`&{72w-3)<(^sUj^Q=e%;+)8o1pFVywDUG8GQOh?;F0x|-d z(J&0KvL*M3D)V~K?k1*`M5rZO^K2XJSC&8%{(|3-`eXoK9gGO+{UG}%>XRtDI&b=G za}!UaV#XoAnXaHt)A>JN#2dvSI8I+H}bA7~>%C*`}W*-If2-Z7YObj#D zK0Gc2S`F7kn;nMp)b@$9L~K_AG_a=KDWLGfv&b5E5tdxQ4rsjnQJ$i^iF!6qp!BQonU2VeXE|wiINC=}JFVzgw8P{#E$Rr| z?h=QGYW z=aNMx&Og&C8s^^>j>?=q({`E421EUw9*80TCd~281x!jey9yb zOuTut+l-beF9P_g6QlT#(Nw#w0^<{JS@79B{nVtuGSJAH_X2P(PnV1svPRy+Fg(ul zjxdzMq(o%ANH?U#^$85M`EjozCa7lFAA0mB*W|04o!&dh+QzuH#9{?H0Lfj=x*QuV zuk;wjsV+3iKS-U!pP#wr?l2B1zV{8T-yEu@%_M8&o-t}(Ox=ktAaQd_2Y045t6-gW zddp~cXsY}SBcU;XJPdgupE(y1QE!8c-0@b)ToYdhOLB+mr+5$N&AVFh7P5mC9jQAH zKEWHf1&<@8LF;Lg8nw5yiQ5L+7+$PGUyirjj4O9sh?avU`}6a}=jwc-aeAS2p5U>M z@~RmlCT|{iLQNx$D|{x5M5d3Ay+#K%%NWrSjqcTA(f2%PGo&VgSn)MUfSxVPQ7h!} zrI34;`Ke@KNL8)0jnaRfkg_B4>@L%$25z+nAp%yGir^~;zp6fxR{I=|&%^P2B=xu@ zONxYk$%N{;xRr+Ttj#)7#9R-9VM=k@0Aaqe)19)hero9m*HsJV=GS=H2wE1YIZ+xU zciG%bMX`eOHhLy)TkMf1U6Q()ns}y2$WTMP6H^ncywbXUTP^<|<@v9NuU{-q=fY5N z>%A+gYpVj-QYMCN#}(mzHf-o(q?{Ts8)X_8ST!gpKc_sj^$s)g7jva<@=~NgPdx~~ zK*;>*PF31gDi?e`rnfDO?3~-o=R3$uTs&I)ao4k5+PlsZ{c#3=MJF-F<+!Y-!2G0x zEt$c#t55?|(-2+zTQB@0qE@~#1?`xJD0ET2(m_PVj84=QH%Wr7QXiMYcO-iDTDqUv zaEO?;RRTKPsG~+r9hVC53+Je1JvpsOij`U#I@wV)r=sYwB1>f_qadzaa3YmrG|?SN z@`d75M*1rj3@E^aaj;`hrw2Wse+d8`(y-e4Qk^25SEi+-5V|kyJybu!q}Jrj3kS=| zifDIGJ6btv_6vj`w*ob3O|&OwtGxPNKxqn?m4yPNga*F}!ZsLbqEr*23NH(Mu`>Cc zwTSXUk)|IVs|Zx=)tEi;ipTG;gYFVicwUQNRJuuWa!hD3NVPsfr|WX36Fqy8P+5*s znWI~poYEKmkpG3OL!qNo!?rio(@}SfM9NCV3RLnV323m`QqereP>+Y83tv5$?%h7IXasjAenqV${}&Yx zNy=)vocX>0f*eX10t}lBICskb_j;@VQ)A8o-z%W5(y)q0CrSJz&8`GMK$9Xd43vmDT&PBjwSXl^H;~?2Y-oim#`~PsVNqG&- zc%Lkwd3K`PA&GdNJKZ6(TM)i|Ql^F~?Bbw^1uqw4mKyv6pH7M%Ryzq_Zmvc98CkA2 ztgIqTb7IOcaXOsAYw?f>83Uq(W+hf#&{UC{)UaWD#NxO3nh>=Fl8GOIOgc#<1TCUQ zjeFe3i1exbuF0NwTGtAZ4B#SLm$G13+F(-WiYPNMG&AlsGkGr#^ODZtcOqycsYQ;4 z>)+YBq+2MjD7qux%x+?vwdB~u(S{s=K4o46DZvT9I3#&N zcjvT58D9|cLMQQ%>J4ZH?-$2|baVLkH{C_XryJXRpzM(RtEYs=TthF@quN6Nn|V}y z=dS?h&go~BCvW>>??SEcOgAc&JsrAf6+b;2VE3he(`8U@P3M&|j>>!;5L^~URXGbM z5IO)3r_(y@^(R%C$jiq?hKZ1BCLF4CXZ_0nhL#GGh1XfZm2ta6GMipI;Il223$jW>trX0`Ckv` zEpKR^Al^&{k%{V9_<3~S{4>k>i00N%mGrN74=k^9l14dFJez7PU#{Pz#Nq?YS3)A@ z97f0uf!h}>mhU;7PPB>?PE?v-%Q#}#6#?qcln6_1$28yg%Y#*O?stS<$hW8nWjsE) zC!^!a?56Mqa@0q4l_^=?0m@quf%Gj73J@|yS_i>v!{u?u$Kl(8G*?*6PG1lX+B^0_ zkjYrbt-+h=i{L*#j&g`jISBgh!CJ6i&^R33Hq(g$lLoUtq`tfYh#|1}-IoY3kT`vE?#QcJmA6d!$Kiu4QJ~j`m-fDrl-dHnKjacxZ3g*$zS%1-Ph`r9 zVGrQAQYXCH&nUz)3VI;UI^4UM(y}Z^fjC*KbG2#ya9-)r7em5jN9e}nh-(^WR_Gu! z8&_Cvv!{ivaxiyQ=W^ZrcB5+c!5YoeJtrw~YD#6JJx``YhEUs`q+?QHcuAXO?P)~; zhg>~|bJIx;@Ep^S0#9J+QIk`~yLm@0R^b~}p^i#G>P>1BB;cEL7BOE5Bf5)!oI+Q( z@I-Gjv-6Ae6AN4TuSK0aDF&f<3BR+Q*A#T#f~+K@yKhf++rwwQ_0Ea6LrI?J78!<= zp`+hq%OL-=b;WcUScZTz70F#esJ?+)TRm(CC|ZQEQ7v$W)h|pu-dxVkrp_2F z-b|7|k|do^njfp)dDh?gNP(*8&s2Nzuv~Kr|8#Ufnw{{8%0_v?v4fo{1v{>BvxEgD zpYR)hF-Kn<7U?kU0W^tdm7_<=`+>ZwYc0MMB@BRxc_|9C?j{du)ehG1A|%626wZBH z|GeY6QVxT-5(3+0BM^c##}+(9dq?1=#5CGEV6~688RMQs79HhE8Qq*L9IyGReoTDz z!A3|O!({SDm7BjA)Tcfi`0u1HW@aPxq~?9Q_$Y{{jAY=%4JZx z%91qW_QZ9TVziT(^p#+^>=B$=XG`=fUE;$@5NO1od2x3jTwJ0HyRV1>$K}n(8qiUh z)8iTQ6r&6wzoEAOxcxW@Q-dFy=o}o?PV$@|NVnBPK-3J(y6FvPV$KRtzq`>v_vQZK z@xG>7&aK0agy9Vc{4<%Tk_O)1yLNjRKezQQNUbE-B5_}GdId^tH-OoFnmh|E5ozB| zYaKrU^WVkxAYkQ)6R}MOotPJhRGF>iey&*U!OIuxM`+(k=b^k((49px@EBA)G5Oc3CR(>IC>&N(9=~ib7e^<#*fY)W!mrN9+Ia#v4AtxN*Dy%V!4?f0G0uxK%s1*986n( zuk?9=pu*d+haiu}o`?JhRef|yX5q45Q2N-bJV2#V)e=9K3F|IYog`Yn%41w(nB7!= zh@+BCgV&!vfYwA@q#ZmT38ORdA|80!dD9X}~n=(T@_4 zFh~jdtJB`HP#XaUUtn2{8@?rMq)+Bb`1m>FISAwA?s2&s&VR%h(N>c4#sLY^9U7K# z^g}P6f=Qz*Lx`lt3i-t1dN4EXb{<>>VS%6Uw2xEv>qby#=vd5QVsJ+}ZYHCe8 zi;><3ol|nu?k26p4Sdv0Jf^IDuJYwR7+GKTT=ngklkefch9qSH&omj*bS|Cs@91xF=kqQdrw0E1 zgV>aWW5H>6zO(ng-Tv!m9}fkT(pQ&=7g;Y^Gbo*Do_t?gm@T5c?54QIP^t6a6GK!S zU3eTD#gH)KnI&jSqJ-6@(rskBMM;{mC?t`&bfC1e8~DS$)IwiJnfY06ePNAr*ZnC? zZddL0O4b;r)g6P?j^g_%#}Z&+S`mkr-LzGPo#A}o(|1_-9L)H^wO=@~vlZ+v;(tHi z91y^i(_u!X_c6SQBAeBW6K-ww>5Od05dCbHdEUJR9TzIv6P9tcXh8TtTZeYgx_u9) zM=Bp&{%>I=-GvJ{S$3i$Ft8otpK(@^rr>uc3L5(g%*bYa-FYJXnLi9Kxl+XDE`Vll zBrE?K?XQmJ0Zn%kEcBx^DQhm;Ax!?|;|Z*;r%d61ZB*b=wa)k1{d>Nq$mCtR(T(E1 zpBH@%Wm0doxDJqEd%@m1BdvaPTUU#SN{btPbhB7E8wzcn`gT;F-P+`mwfUEONB;(~ zT0-sWZR4wpv*a}t91xOP(;T??Nfkdjn$SX*x=k(<2G%=m5yOjd>zw?uz#_zw0Uk5Q{d{#tIq{)Ml86~01NT9bojNHeo`VQ1^BcXvg| zOa?7cE5^Hy&Z4D7EmHtTBLXfK_f=J9{jGU284eqoxs%I<(#f1YQJDnb=Tt1JmkMe$ zvMzeHyji!s%w51LV*qGf>}a#2RAxfz3&Vp#b`dPOulIrUFMz`KXZ~h*u0v<>^&QQz zZ`Za}B4@(gB*cGb0YGlpQ7~Vp46r)}af&<$jb&En(G<4|Db*VQwz!}k$IemtV~3h; zllI4%hesc>GW>QMoD3(ukQvEMjlC)O#m0b5l`Bl^5z|d66o|)Kxr}~pc%VYGo&v8- zW$QalS;bO(VHeQNQqi0d@bY1sqy|YWXDI{44f`FXgOl{G+vJDl8#z_HZAT;r%J7%} zuFh6NGqqc!23@(yt=Jd`kGHl@){m~0@9*mbc%oBX+=kiz^K>~^R9DiR&RBYn*^Mmm zmob3dyxINDMvqaQ4~81I@4O!r0l7mq2+y+^T3^vT%yV~+_SNJ)kFHA$PK${nT)=CXKd$b=){O$L~|?e zv!GIS6RjiGRavsInax_c*g6Goc@9dVE>$V`Ki4f}7U#A4ALM-662%PI<)}zHq zz0=`iGQW^;L0SuNg=+w%R9+#Rd#;_0BzNXaK{Wn2e`9`z(RM zW`f@Oz&dkW;}m7%$({8++?vPVISE_TsEJ?X_{RakBZV~C>b9b*&6g1fyvBha92qWw zH7w#2)Vd-XP$PkRiZVJe@^J4`q|(u&9|)z+I4`c+c-P#2Vf^g+EFibOO0*~{@%?i& zyV$ecPicmXXYeR(yxf*zHmbSYGD?Kw=A;J-4HjD6v?`UUWk%~ocEVSupa*R;`rtW= zI^}*n5FqB(NgZ;{qqB4VE$4&1rE!D=4R)iq{ zE`D=Nb1(sr3igi%r76bx?+Bh2;Lz@jJkE}6=zgsPLSK*axjPC6y$IL3h+r`$_F1f^ zkZv1`Ya-!Af9{pgsM;N)&AI*zGlcpvlKg8W#;c+wscJuv$~^OTJN~(cjbQ13-d}X9 z0>DF`?fh5q3B8bwEteTLgBK{TX@lQXsKaz^9#4Zbr$Yl=tz^^u1urTJt$Fv+&Bs$d z$F*TFNPC&7i@%|@=_q3Hfs}#5!QxFIm7g1tB$`*eYW?Xleu8CCUi1nP*>%(30L>%7 zw`{Oc`pJ&TmMZ=^uRs3k#t|WNaK((+vwQDmDQIsGvZ=Wh(X+74MX#5!lg0+7Mrq-} z0tPg-bZw`ytmyDB!uXk{<9FoUZBvPX>||8;8PtChw_kToPpqwRkJ5o!rYuFDK`*&e zqR!c#U@761aX5JJQ|cAeB$1iy#dMy(HP^F2;Ck^r>%VCK6V8r@BB7e)t|sM5(Kftb(YzHE#hhT)|ySvy=HqPwv~Vv)o1VGL~GQDecdysB8CFu_;|oWaz~1 zcnYjW`arJo6E3lV9<>BD)FFQYX=&qap{K}wlwN1eW8mckUGf-HZX z;Qsgb^;_d;AH;}Z&i&O08w@{1S-EmzHbz{o(nolC=1BN>(|jC0WI)=`tiLys z$!ScXHmd2 zGaZPs)a^q+hr7r4)!L0H(@H!mZc6=HXYQn9l9l@(hfA6Cy{;v(T|uEc%T8Kp)T)xM z9-qXv_YFcQOQR<6(1J!qdtv)#NF<#ubfJ1{t7P=!;x-Z5e^>NeBQa(>}MBy$o_l*;C4kY}`x!)kB5u z&h{B2k)#iN_gn2-d4mB)n>0lPw4O5w{0Lqq|xTJTva+Vi?9_HX&0XjIP$W%-u{)tQxrJgAu2$FHGg8JqNYb zvEJk+kSoQOqIn2SeF+%swaY~zEz`%v7;sytwPvRQeN zU@#LtyTdYjD5D12mw_20j1M={#9(?TjC0HiTT$Q^DiUE{M1>%#o%m;;re#Oa9l{nF zUcvpKRQB8@OEb`wJ&;~Yz~je76&v2d#A(!0SVzyZ0o8AlUw)%2`YG*s0zV`{C5$9; z{|HR4ww#?epFz+fk+nx38mpX(?5BaF4M%r*LF& z)UsuScd$mQt=jwy`Nc%c{uNMj^m*3p+%4#03(dnK1r0yQmpZ`UGpSUdXP7HykO52T zN0`myqb0{ixOm`2N!7hxX&F*f%vNM0%oQ+aS~x^G@iAR|!O+IJN<4nN9?`>HbwbFq ze}$H_$4)6zLx;6w3?#nMeDRn>0Na9!+blnqrh1=5xOo`kDQ58oz`A72jDRTK zm&%RBysIW%xy4zzYP)t4gWD*O8RNEMGc|ZTFLF*oiS(g%n#3ZGb!YsN^6e6i!m8-8WkL-t zpfK3sh#IiZoQCEP(Dp{<#VI)+GA-jA2r|qxTBeW(+Uy7PP4*t>kSQr0pEFSnD(MqT zxRKSeg7B;-`x01)y4cpj-xZrK28Hc7G{k_wWK6FvV50}KF9TZyQa%Y1?Yy!rw*{Hssvca?z;doz&B$j-1xhI$&`dfeBXmmIs`az@FV>&ABf z6QBF_?2sQHsZZJ1?|<@Nu4yLCp}rfYZK8g3s*&;6+mQ6ZA(-DS$+ta%MU{&%S-ijQ3V}AP|fr7?yAUGF|D9%8N@E`&m9F_Wo1yp&e{McWR}2;cC?} z2Me_*RtPb-Y^D0{*NY3Ur?{F*n}H% zHkecs6x8m%kP-ZR%=z+98K;G^Wkb6ExV*?{-ue-V4~Rry$p9Exzq> z#!*PT9zKo<@C{tz^C7R+VOSnT-{BhFeJ-@;z75P#KG1l7o|`QoK%E>07%Mz=A*?We zt(Za^cA(>y=?5OpVQO~mn?EQxIFHY(QT{%Dj4F!dylQnm-vLkHqB(~v;GJ27TZt^7 zu+3}Yj}-LQh%sGfYJPK}D(zx#W{QD(UZ*?hHd*V{C=ZIE_i3>3mjqy9lEQ|}h5`%d zq;L}+>-g3eW?&7DOYZx5`Q8hJp(@BN6>0k9a~-3uYV;(K5Z(m8+ETLT*cIl_r&2tH zt_AjNk;0{22Wy_71IDejY>o?xt>K7)F}UJdIP@8Q-waoTPLGE-(8PlQ3Di2_ZZgSC zKqE|I4`T*ml>K-^DTEd-&RU%ap|MfK*kCx+HXD5AO>@cbMQWN)_?v zH#!DiOCNb0!mbQ&6VCawSR4HmL6LHPF_0N=5Vf$)j@=JwaE|^Wwm58r%Avv(r=Z%v zb4nlCTm^L`gqJ_7_x^x-(A>s_auJwgIC+9_;MlOv48UK!Snn*$5J50|v2Q>4wpjgV zqrO3<9`tG-k2i+o(10;s0p{BwB2t|=S*ksy1{cUa(-3>X372wSlHRa}5{L&2DChuk zgdV#SWzb4k_{mY`Qz4K3V@z}d`+?SlffksFM=I~<7|r!C1~M&NCS1+*&xw2EZ(hN0 zyst3G>{ptdpv^X1Xycn(D#rd4^JiN1>$vS2+-eFq2`)BTUTqxTGY|rcr=P)15?V5# zTk%LZ2Wykz1%Uh}V$@TegH*4O&E>#&TK_dblc8kvOCAW}pO_|2OZLi3I7dyw0Y|2%NN$pA!4J(GjaQtbT@C@Yd}k$Y$z zzmlzYP4j{cJHF#RlB8C{iosT0xd)LY^Hfkj~meL`2)d8r6WL#x8s%gBbI3?hJ~eEktH4V#E2ok-)yEw>4}#TYN-;+ed5`6;sEx_M=QOLJHJWP@^mm;P@`@suYN|i60}ZHOS*B>F+7qR;{Jt&aaL6<(fBYxOvJC5Myvfv%6L~Li zqHiq;V81y3)M}o5lM^QvwoVQs(C}R873gxc-0lIENXJ1pQn?2q^1u_CNas&VPF#fd z;0YLjpK$Ies;V((!;iT1tD1ijUc*vtT|bLpS{bD`!p8wOrcv`;$8DBiFMt9mB$yZS zQaVdg=(V_>MUzGo7z`JwEPRfh!VPgQ>U?2(ST^ESn16i34)4DdmCk_6NK)w*e^NM= zsc?Z+r}04bf4cC?j;a*AH(T6`30!+;_1-H)>85ra=c%`WNIIyD-Uy&Fe%yVE=_2oU zDUxIwGOL@^fdNNEVXqMsm)^DhAiv#X%{RoLEcef(adBt?`EER6XmaG8_3$kF+Irhe zQGFPMi_inIT|z$w(~fgoLR~U2XjuTAb~b``fHR+s5@yxw3=ADz04y9G@CFJDgNCS+ zZZtTTyae_0#sOjO-zvRf`SjURX7~g@FPN(10dpfY`SW{;4f89$;O1A5!Ig^=+Wr0J z?i=^d`rg&E^$e94JS*578L|hoLS z)yA8uoS1RRshs`p zwfax#pJ_A$_QsUSQYoZY{m{mJM8WV0p62aiEur5)V^+V0m=oI7C)xW!>wZ)1PH9nQ zRkM|pGU1yx(Bl}LXWZ{A#N#40GiJLGHx~DsrUZ(E3R^Mf-x!*FXD4n4{)DHQcw52O zib3zAQvUvUz039K(thj=2XPD(8Ylz(s?KO-?TlK<{q*p-7F>!e+~FcO%gj(5F#bEP|8 z#q~p2k{UH-0*$RFP=_Do$Z#amX;gTDe;sz@pq<+)&qDaNZl0Gfun30*gmuf z;Jf@~B(i}kD$TeR1-om?)yGpps$*+1)UjOHVsi0F`-J9j_K2K$1!dZ;CPlZHA~bir z#($1qfXxT&R+%D&O=v#VjH?__-Et5S4~aV|*k=w8x4R*lbR#CBDua0Zf|1)MeParT89OPc2d?Ycu$f zZDqe0lNj~aM3M#i2NC%&S;Dn=k851HWis3M>jlzOygZgiq0p^-)Vz`)7TY>!Q0kRwoJMGDZq>S+{$WFI(8* zgg06n!z;XC?SeeOJ$-DO0=<6U$f*W^TUo5dfS5R*+4T5%tV(S)Aw{&J zf>rui9$QPkI@`QRNX?$0&hK1l)=`9K2-aCWX6~U8`Gl59Qt<)~+pnb754^B12?hOx z)xqmR+6ZejH%oI1!r1-2!Ac1!f>)7^*c3m(X09_h)DsQ%R_$%^vc{MN3aB{3z_s7$p$p?*9Aj_E zW37Yjx?U0u@8n%fTA|vZD$Z?=o2AE;^#R@D3!Lq}brqcI%xgWJ^|+j&OA z2R0sjW>*W%K6t8D5aBN%{kmx}4_PYH+f(SM^?a^Q%3Ew|DeAhETl#dZBorzJC=qVY ziszG*Ivzz47D=l+3OBFc?+h5fn0vAsvmoo<1>g4^tF9T^pl-srGS+Hn=wm50xIGmp zB7gPO*%W^`Nn1a!g2Y{adtTcvgTk0h_bi;z8tJJ}xmo`x{FUJ4Se1OV>tB|0UO+Z# z^1)5q@pmezCX;kD=&@^qDB{W(O7|&KR)@i$%tT2lAh<7;U;VlnK!64^p;cG{`GhWMnJvze0fECDlJfo98s$sw;tI+*@V8=IPpFVzU zW1;)oWwO?3#9@b~6>6yNaa*~aRc5P|qVrRAXE{Wp@QkI}kSH3eCAU5=^304dC z=YqF5xiAmsastk3RPXSM*ZKZWu*uL;G{3U<9PI=iw45x-=4gub6D|Wj3RC1 zMf&pYLW-7_EhzVo-|+v$MfruSDV))QsRHM`3z03S2k=n0VmDg{zpj%t8Yi z5!1+-D>TOZCq?eMo4i5>MLQRt$B$nVy*Bp?>%Qfk$8q&c$eDvfAaRDDrK_&Q{6i4)$o?}^IB!E7u%SvxvU-)eOz z+y;id+_FSvig5P%OL5kY879AimPpof?8A=QEcgt9>*jW9UffWEL%sn7sMlKVY71cQ zx3~S+kNOBVX6)(bl#cPlv(I6QgA`>J*DNt_-SG#lW9$o|~qJo;5V0jD1qNA+WW$V9&~^ z$;}vqmE;YM-OsWWl^{1=NpbYps}viScqY$ue?1K}3G~d`O825ALxMpf6Vza(v{(Ga zn$`?{hoA)2Ok)IS&~`qge+8LP%&Vq+tK??Kb1Y9nZm7FD6b-gZsR;45Dm>P^(>tWS z$v$0j4(Rd1v1dGBu5aa7Lqcczlt&_N@fewfX$Y!|u!{z@?XZ3m9|-Yd|Y z=tlg2eF(xx0HA6|Zf{N^Ps|upOV7_DtlF|_CwI*iY#y~fiOkh*l0UBO)+yS7rqjGA zr=uC##PjC|`zriUw!X5N35y@K{VzzbspJ3f-eWpLM*v8V22&Q1H8|50<;*qpx_vHB zhPGa?P&11!wAVPVE0gMUcsluRFE<|aOMo6ZAe*#KvaURFA=NUXX#}wa$nc!3WFV~o z#_XZzzk%F~^HuT`)Q=waLABQ$*CY0wxaulN`~+$RQ`QMiHdi73wn9vCaZqY>BC~#0 z)NG4|sJ*dH*kbe)z`W;GOhJr7>fGVR7~?t{ouC=H<*hJ!P~B1x3{#mQ@4MB)nSV?-suvFB`G+zFS{t?p39VN%9k_BMUQpRd~BGJEJ{GK>IT6H%sb z)*2gl-4KxQ$!-*MvH(dyw!dyT_2Tuo$G==4!*lC%mb^?Rae!ur;1(YCZ9=iiE(;-NmxNbXxn4b-9{4`U zQ+M0~MMBKNefN_mI^^1v7dW-1sc`>0(NrF&0jkre$+DUAlpn$3@(n_Fy6@PC{QtJ^ z;p8T3WExI(B<8>r+FN5{7~lx%iIbYwJkR5GDylJ6v6^RC^1;F zQAgJCBUDQR|3wC?xVv6EW;xV6&j9(8_t%`Jso`V;Nbny=(y z;oSYGo4=_FA_bw|Nb37^BU{5cSoW3#njn7Rzp9<0R&B7J4z&WeAg8aU_25}&jfIOOpi+#c)rd*z1xTe+hstzSzf1BD?rMe$f2Q)wHs3~jH z5Szkkf0d?5ndfySe*Nx?fZwJL4Tepi4Sp7_uo;=H@1YHwX3@zu<^ zJ9}H=dBaMv8|(DiB8G&JsMp!};N--*`2}6rV>86Up!UvKN*#XeZ<+PRNrig>(85NQ z!7kG?ek&hlM`$5!~vh_ER7N9R$Y{_PSK>lSJtTFwk!QeZ!eVibcCo<~B@v9fyg24#kT{`6aP0i*3iMt^sq*m&ynS49ejyn}n$aSUCu`#9Y(98vY`u3w~K&q;* za>;vQ==3pkfo`r6hlAvex8Y^O@8fQVHhNcNYD6nv>nrcYFZ#zY(eA^KK||)0Y%QaE zmH46DivtEv`}xqFMi^2>GWJSk%&ukCL*fYNq6P4|#7m1sS|e9;{t@Ku$It z-(nT12PFDta|zqb#SZRD%)1fQw+h($3m=kBpp1Ux?D8+ zG{uKdrWp_y@NJrK9|0IeJKa07aFzS_wuA#J!Xr-E`GGHBS_k;8URQg4HJVXd%G$0tO5<@gvT2#x4a z2E3KqpG@Npl3XRfquacrDNU+26hy-iDXE8{+$}M{(p`MR#UA_f<%k*4>@h_%Y}zlL zd1(U#5L?BN1V?4SwKCfEzw$p<_DZKij(KH3e<1J%68MyiN9>{0oMKS@{j+%+@CD@= z@2Ydhc*rWw3lK!m6V`IfZ9zFd$h_f6$pnE6a&DZWR9gYoU%8o^58487->D|ZYhVU{ zFn6py?^Ylb$JX@m4Rm@(VZ2XN0c`nQR~;uNS+v%qPv*ViR5rE%J#ltXk?Gk76hU_+ zDE=K`E-6wu&;g+wYIfaW{OO2Y)h1!eJvJ>_?X4Ve<@HZP{Q?r*$p1T+PEd>+KRq`X zH5VSfK?S+@kEc!35s)M)BEsM((&n6qnsc~@qceWUlD52cyXePB1#O z!-Oa+iC~KBCC+cAd6N@RbLJ}9(76Tc#EmV>pju& zCtdRSJAAKz1=A^13O&lVjYF*ZpR0Q_+^;@Ai6{NhV}yQaVF>m~^Oh=hN{>C{;e}DS z5AmXke-jOjR21D#q$X9PK)frm8`@|%3@B;y$4i4Kx%Cr4m-2N{P>@zQFAvyaCdxxTWFjXZaQeW;la-B^AO-Y6j60!&{ zo|0y0VpU@#C5B3@K}|tz!z|=R==0m;L@w%q;xcHu0dpA0akS_g3*ihk5q|do3kiQ)eS}bnXb013sdmYDhSblfZWO zo%m1JdTOc;J(ls;(kl5GWtl_0smg9@k=d6)%4^t7`& zcuq!2NXriS*5OmUpc=c&rAC}|YH2~N&3ma{7yCeI_2-Vnk=gcJ{MZaCH(iPP?~`II zS$Ux|$QR{j3f)dRTmiBFs~vTJi-EO7R^3nlqBfZ+UB-Dwz4GRlVAPB!QN#_Mr{_?wlCtWuJdteGF-(%``n|dNnEB>wa#}U>D|t-u9lh3w z$c(xW2OBB$csely*82xAX3oc9ZFEFg<&yu4gQuV;&#;h0w=)dLZx4OeSiLIso%hy)LxGAwUB-u#4rb=!*}e5Rxg5Y_ zQeIxClAl{6aqcXf%%@qs3HvIW+aQKXCiDcVir)fQ*)sSsu#5J`%Y?R^3~r|^-d)p6 zsg!7BIG?w0sg%%4)@$#9Ds={?NulLEozHUvq8=J%QMP)$5WpPAbU)ehmU`r^b}icG z>IgosSgrFlghb3stA$(}_N&HFF}JY{^JnO@d}38qJJ@#Nql3_?$|m%PuVTg*orcN^ z!fDjCLjU@nyL78eLZ4sdho&naR|Q4(*hKAo2l_0u6E`om3YLk2T6-b_Dj;Lld6EN{ z=aIbj0}R%>w_u2J6&7cXbh5Qm0E?tqTxH~SRE^~A6HyHfRQda3?D}po5nKiq-p7N* z&HnWYn6zI=z`MO)SGVs*IAXBhw@eE zQV4-|{24v&L9`K63*_ECj?JgKJVqxlYwfu;eMac=L-|m<0@gSq)gOAzgqkg~CwZ?m zr&VST7H=;734P!Tp%461Ig}9AIrP*Y z+ZmkLyI=E&BA(J(^3UqY;C zSsHWj60>uTj|qqP+jWos;kU+?ueJ1UC*~o8vFw~+Bi4K|o@3a!GnRnDmW7Pua5**_ zD^0%c6%c#Tl`#P&ld{YAu+_k|a;u;B*v`7jHGfoXWUz;rzz|u|A*HA9|9Un1AkigY z8;oI*2Q$U)dfzd)ara8+$u~d>6X{_LpZ`J*l|+>(<8jkj$LjdZ5n6{B==}Bw-HS5Q zKR@LNC+n9=Y@nWQM6`nLJGyw_+$z9ROU>~?ap-t`wzbJW^OR%gw(o#qdO>#A%?YbM zb*)_3bTiz#{tBHK{GS8{EUw??J_hw^j+F2uIUGv1aAfI8b2}}~a0iP#Z!?&b?nH(# z`r=9h=vn&{A&{8BGu3*?$1+}Y4p$_aGtQXDBDb8Q+HeN9`@27vJW z=uQ+O6kXra%Y)~sXxHA=502_ZKO0KaRlmb1nF!3ENjQHLbnTABf*iiK(KB$qBf7Ki z7KAc(t<;R`M>3Zb-S(n%d*36k9+fc=c321|UQa=@e zL!)lBp5@_!Wbq|%w3M@=T-O!WV!;Mx0R(VHr)U0P6oncT2ee;V5h;Z?EaW=;q1xmx z%s7&3ps@571XbA@@h&xU+V^nSt?BJb8#pgTW7{7&xXJV-j1`$#iJIAhOGkVCm|*xs$c?rjsu z2mj&9+KI8!Kb(HA+9BE2j{=84&+!oPZ3Hi zvIlXwv{&$y&(8`VS0@wXmkzexYtfQP56k?S!15)kEnYL@tnQCj6n;q8yS=4aLh`(q z4jQ1}p7=$@aEMx~^|)V-Q=Esnq3d#xu-xsSar!A|vgfJH#>B{AuXoyd!ke17OTv2` zKs_`eTbSe!SLr(3DT?Sa`q1$$-u_Fyl&g&T6<^UUx3uX3Gg$Dsm0|tA90vf;r}O>s zFTRfaI2WVns69@!2NI|;J|qH~xBnJbgpkoB|JI=e72h*!oP-PEWU;_J3Y|i}m1g+C zBwiwK%L-ND{g&bmbfGvb(sNGiGYgYmfAR2> z)OP+#?7WzKr^AkMW*!%JzMA$tP)!ryR5J2-SvLq=v;9fgVezsZHzi!RzFV%Y#7HQK z-NFre73T#f>otJben?Ns(qti&7E1S{qY3rV9;CpxiKuC9g!U5?|7hCxMA2J?xNj4p zT+{ZlA+pykU7hEV1-KOiNXhWLO&CB(=FrC2f19h4m7~on#Q#)&cZ^ga+y}v>t&w^H z8kYdzvf`cZxk2B&zxtZGHVE( z6O1p%e72Yf4k7YvUSW>dF6jqU6f>mXipALxye5K}H&l?hFBV*tF7FeR&Vmv#E(Inq zS%P!?p7fqJA%3!ZmRL+*LuXGL4L*IHydLd$As z5KmcmJUppQ*Mys!?(byAR5CtRO?Y@u#xz<+SJ#$u-U(?Vf4ud`-}f6j5;;{{T>Wj^ zdRmF^Z$`cXTRxS0~p+_?5b9LIViHQ0%6wur7 z$EUYfC=Saer5+fcjP~Hi6pln7dAWMQUZ2Ittr-fEAv={Y~yRq(5Je7n6X}*ME z8}N~cs($%zYs{WQcx#*nIB@_6bmg{|a3YFwZCV zLVu!vTU*c0K=rj~a``n%3?B0VyRr^dxl<%u#<+socE#`o?ou<1MZ|bW5CGS3Ll8uc zsf@-8P1y0o^>BS_@;Ejhkh^@}Phr(BGmR^*_8Ld86bhvpX8)ccwxj9J$$<+k9IHgGf`I?{Qu+8m$fg zhJK39USL{8cj}JkzWm!GUk+K7QF`XPyjOs9Ss#mgUxNQ?1{MuDI6+&2wp07ip3y(R z)se2FB(2S-8)kx8q-n5=KTi8jv1H5Pkbq>qDy)J<>8+8oJ}3 z)62EB)ooboS3O!=o|(ITrm5Rf=y4iRrOVviSWdZ5e2<}5k|z@D1KCTwg8U{4J)8OG z*DPHO+tEh|Nss?x->n0%KC_d+wL8r8X^1Q};`iqNl1tsyu)f6pV(8n;tG0;G5q>(F zZ}!jN&a-13-f)1Ew2V~T2O+CR*p`_hOmV8BRjuSF_GXi;g~JNv4Ejf?zz8d&WTQBG?c z3=*(mnDn?trKKa(T*nDv^u5Hs*l#@71cne{J0JSPZ<5^q?zud3(SuwX%UCs?db^uL zMhB}Ko2ZspXLOZ+StJL1eMkPwJe;|-H@ca%iRm4pt61;H%{5gNVpkoyk!M}Cv={7m zfTT9=IPLXm=o6H}QZG|6F!{(DrPN!S=9+$H1T2leq`yKNY z8^BJYOCE7WH5UbXU$GzNrxTh2SvIn^V5;r+2|pg zHCwTl(U`2Y;%^J@j;}G6;aL#6CJI|hHOL{sd<9mgZ814Mx`a6Kfx#XRIEKQGS@Idl z1ghbbyo%I(sIipci<}xrCg0(Irdm46E)Itkr}g%tH(InKm{{^?>(Uucj_ZN>Hk>ijNCLI3 zl$hl)aowhlg0h9ILUH|3vptmAS=r*{_@@ z+?Na~60Hw*Wz^IK8@S8)IjbS-*S~lybWGam%RCX&}{J55Z6K;lqbPGDoQkJsZ&Yi*NkSfGa27(B&%+b&^(!U}t3F z8)6?l*;{eEAkN%upl}jf(BO48N-o+Feo^rbq|5)zIK%p?BN#;1JEG8z&5JlH9PB7e z!d-fGY*O!nY8|>_Up4RP${5E^CT%=>yflC!4EmOl^)IlAzS&vUUXy~qQMTBdN(mAi z&SIUDS21ltreaJHSW;08>Y3ov6KcyJrL%Q^iW_=4PP%UR%sntCG-&fAe#iL_VH`vH zz$kd;b^XK^H>y3(ZJgCQVS}u4Di|tiAwx{#ENvkt0!G-&P`LPq^kwFqsb^rVB9{{? z=s-D4qm2Zsv>Wd;rkw>YI8sk${+XE_#QF-*?%<4|uKP?4_TW^(L&ttVRjg-VnIyhM z2y_@>J%xNbsViebtmsABKnyue3`ons@bQ)Jgz-?)6+I@2^j7B^jQNUG- zfML9Cg8|%cV97~D+;ptD%P6MhZ#TSj2t_MXq#rP<^pEquA>ON)_CMq6&!x({BK;}9 znt>Q_Sb_$MiyR}=GGpiW^^eiG%5ut-m921sw?A-?L`RK`p?{_R*<-p#n<*S_Ystc{ zha``L8IEvFp1^Ith_mVFI3wxd;Yivza=W z0RcsL>lFj$%c{b}8jvDJG0s_QM9NUQta}eVOJOHO)HB)U9AcxmFUSFgx4_AbU|zb(%x*R-mCuc{nx3L;&Gp z=|VClrsDJeTMbAk$FtKhR$JU}Gdr=E9A2yy)(kkwQZOrZ+l3j1gqaM+e?-j16e@Aa zA#-pOyfY;T;l!2`+ub_83=qsxFd~#z1{`_@Sk%-#&;NK|8PQWTtjwrSB_|k$uBOm* zEiho6**H&OECy94(>c#taUf`HJ}&N)4<0lSxIe~Nm{U!P!e{aSRsj9&c-ErRgX!ChWfZqeoF_AxFYKeW%dLZX|qARgv zEY`}@o>=7^)(Da=x-UOsAra!GUClRAVAkpQ2HIU4cJ30W^Q8UrltCsjBE6COx)i4# zfN;A>|2;47a-SR;!IY|6aT|6!JSf*We)&B-c#zr8ug%#kqY^cg5R@h=ZVq#N&r5g} zK7W~-Vy+MnZxRW44uy>Evr2E0*ef4SJ5ca;7o1ofe(nrIHiwTtcBPEFow`@NS-S7e zdt5~Zr)9G%c;1h)>G?(CUHp^(?w=2!mAjJ!puHbSIaGErzw2D`4>4Gthx%Tj_dOXY z({*-E7qRicyTeL247eJqZmbs|nqRgOc62SM$r$6p*7wnd-@oZ3gJpq2yyX-c{k;P} zp%N-#e*T}#`X9|T6JH#hOcVsRudY)IP#NIfuw_KrwJM(6nQUwq6Fu>KLpu)|)$mQd z4m@CS$O>4P^}7LglNGn!nQov%bccZGR(q;hRn~Va-+-%#Tt}#_JKd&S`M!^usU%ti zlbn*iazn&C{^rv^z``pEN3h9*?*iKYnTPqb4vN;Tb2;)OlWinrHgJS zB7Vjd;11Om)K$kt*1$YHA|0xp+O%%z4!?Xs}+dQ9%3{4m{N@?h%#R~^=m^HG9sHF@i`-6AloD1Zh9DK zW#w02)gF3q-9?GiaT{HY=I^LlKu6C4LTC7_B?ws?F%Z>OK=hQ<{f})|2T){QzHsyR zulQbvb@-I>4Ug1ZIEuwY<}ZWn`62kDjXwOnGJ`nZx?!hGi z`vq3F<4{{o9S|niBrSxdjqRopji+9kxTdw|#o+B~w)%#i#oQ6v8^uY5S{Wlc%D@;r zaa7bl>VqnsRA--w8UFZHBUCEbvke%z#;lh^(GkpE3k@Sqwx8W^Uzs%o1tAkUyz!5Z zAu3NtPGXwTUZ$F-oa?iZwsr;sLUSUx36CjsMIXg7C*0wanJuBZFI>7THC!qm2S!~? z4FJ8AeeuP%+9Jq1!@u>dD7HiBSQ;Ot+w~NAn>ki^=vf=C zUE}~M$Ni6Mlb()Rj`>g)Esr+TA-Q1yFBHzO(W|PryylU7;FDYqJ89{s${V$^1JE2_ zPw8s5^{74CDCl!{?*zS6oLaN%zLj!;YRRyuU*r>p*IB!baT3SgLC;yvE(YG2-i{Io zV6Ld5fJgoSM?;p?-A))ONlpo5?d9ziFZ^wja)~Ndtp)?y+FIegi2hKx|8La(6P8sI z)Y_K$sA)3wErrMk@XJUI@qLx`kpEgTY2;>cvRLc`MrSxjRugj9#U2?C>Ov#j^n0T> zke|6G(uDCN&y(<`?Us_DSX#Gcn2KAOS!mgg(onsNdInmd_wXqrBk7dx%_Cmcb`5$( z1vfc)(-}D#fGJGCT~Q=O`w9VKnX?}w69)(TF(5lEA;C)Wum^tVqzNV3%hZ$ud0x}B zye&^H3~V7bu=r! z$9A=*&BZNi<F}6t+SPqu zms`Z5A>p{gheYXPL315G*U1Dc*7bD+(Poiq9f>mE60EF)30 zfYfJ7=i!+1AyFZuSh@U%{ep`tV>7l0_8Od*lMjEC$(3n9H+K^Jz!=}fJ$7hK>7`2) zG#qpq6m$L=TM@7@y^=4;&B$=3j>^23F15@0%+Mk#bwKUSt*Z{ZC3&`egQ^6}Y3eN`d4H|RwmeNI~_IP9vS zMnkQT->7K5a*Sj$#_Z4AxfGrL%>)9^`8vH3l~(RJsE67VrT z*~GyQ)Ty_LrN?a-P$!xaEwMNpj(01xK_Qw;8rdcu1Z|n7>X*#s>F=U)o~y?P3GQrw?eX-FvH5410wNEyC*!fJW+G)q2^Smi(1!Bn*!UMSc>#cqJw?}DWw|Va>=jRI0_+HGg zmGN2&`!PALnv(;a>vP@TNB(ejFZD7qSk%TpIDQAh#B3zK)2w4UUK>WwZl?*d$K|dS zpJu$m`_yM*O+XgNP91&{V$Oqt4F)WBQ&0`eM(ArTM>xA&dIzi*B_TdcKDgs_Z=*AH z*t0jtWy+s8LthNJJ$>}ox<2*5m61X=p|!AuP21l06FpTwY2wJ10hKEWKsBj9{8WmM zJc0}C9c3ijC?RiwlQnrGtZe^NUMy|GE?b8u-A;kOu6+IO;~GHa|1us?7d{P5@#6L( z)`xfo9?pKV)vDqYR4voDEsoLMs6~hA)65e%4v&n4Cezxukeink>~f6;dZ1_*@fkb{ z@MKBV174`1#ldt@lJMEh#>BgHz~A~yLZ(wK0I7}R>tG58nu;57sD*b=V*h-PWF`4> zH11Wbb2WDT1f4+|adg>H;Yn zV3U29%&un+TNVd&Xgr6_qTHtrub#2t$$8x-YQ({R>_7?`GT zBa)5fRu02ls^R2VF+slmbI}unb=`zT?1SFQT-wNJr-8nVZR)<-Fia-3|Ki<^;J;^v z{HM0EQ$Tb$hLTt?`b%=1%>yp+G{)4bxcrtH2dp&!p@o6E7`0Aq{({3NB<;3IQ3LBo zdSJYk8EP=x9X~zz?BJMLY+Q7b2;J8@hUu=0Y=bb^Na>HE26`eO;dcovs;D@!j=Ia!k>@7Q^1oTrYv`Qn;nN!Kx!& zEok9s-NBa?RWN1~*xllgbG3WP65l}RoNFgQVyGL}=%jG#*^s?C)ViF;riTMo9L8^` zRqa6IqQxZ`8m2-L{G_}0FYRzadj_DNBIQ@j6Wn~zLQtn~etTvc!?776TI%IZVK|!) zD-}I?G{yd$MgfJ$6_3hB+#~Q=-@H9I(CSwr*vrP2cPTz)sOFMOh0yQ5xpAGoTc1jl zl7YGyk^j~%@}s{f2>bUIOk7Du^Y}JW1zLTyeA;rUN=aIe!p15<}1_!Zcfj(Ih-ASzqdcA12X(zgxh3fL#_kD!h`3jHLg6yXdVBDPK% zY7jWlMvm__PT7-`z5Uw#>9(|{j$-ga;AJPmIp?&drh7f>C^|#nDZ<5(nI{lrV$!iu zP_pSuV-_ykYB?}~b2?G4>3*?R{f}j zZg)r4ZlC|(kG&HY5Lb18HxPibbnjeOt{lo(@K;zVi;q7t0KA0`ymRAz2pS7$mR!)5 zKV|{weGl{gf3;$>jt^73cxV%ooox40*t-n;g{(rndv?H(svaL-XCqxWT0N#j?SvbOt_L4q;QU2$7|O@LSrKh;|YY!{S$`9oFKlq+mhI9mFh;%TGoan`y3 zV3Q+90TI9EA1DA38%SotPIg!xRzhoX5hMNxjc5KBjq1)KrSA_1?tH(K%FdM%pQ*|H zA=99qRK296UZtsVBF=?`g>1K_G^;&*5{I4VIEh5(*rs&}g(3$ncMXf#nq$(x8w76! zZtIG$ohy1n0SP&n^@8rFTvvAuLP14%brd_p8FTmJ_!->{KOwrAIT-69&?jOADx`AC+&pw?z+1@gq z-DxS8I`>~vK9B>y;*3To92YL50#?{bdX92G z3>6TREZHdZ=`B65rL`Ni=?YFVo$#0Z-nWxV{mX;GGn(JtCMvJf*FRunD{qAhzUX7%-haWsn}j8D3Kc8`ol& z=pIqdG$FHb3c%8E(`&=mipb)ejf&)?hL+*S41=f5FhFtV+2NeU4CiS+>1BKj2^P(j zbO;9Ib2MUX&xY9Zv*e&y9JInd76$r;%tMCxcNP%gIcrMVqkN-S}z=;&ag?AuA7mf(yBK{kEFB8S ziUqP3PeBFPGEcCjhc#R_UnwbN^KoKn3za8m$A#fpwi9jFTT~n2P++t}KdWO%b58rn#s8LrSHI{`N7Wk=Zn^aIDSpa_AA;MKDI+f>+Z9Wdfzk2-}1A->4IR zlZywK*fT_*pQs~pz>v74Qs4ifs-kEDzQOLxcAF%+z>YsaVQSz1jnIsX!B1$EY;^!E zcvFX)4)g5t9*T03X!(vF`gORo0GUB<`M~I4N=I&VNGcW3%@0-0w^#~i=#{q|f8Na7 zuwu#b8WJ`t$zFNioPUA{gi_gEH#2)w$0Qu8UdCtj8#$ilaixWtd*+2#dDQh9#mIGt zZGZ7H%#iJ@=f0eSvI*`kj0gI4`Vqs}GXn>?fjZ5p16$hZU>eG*#xS{<@Vn^r=p&et zoH7Kn@2J;`6bZTTpvT#eYNv&=Bg>Ly5(AiTv4l$_xr)KvnJjeGMd^Ky<&7OLu;GW{>JoCtJkt~IU>B2-J4kbAd&@6mS7vH z2Heh+*z#SFMm2lAN$(K*UAvWNS_qy=1R*vR?xZag#zZGCu+SzMe!e>y`4!Zc&>Rvi z9c`dk!uhtL(}3IO%gbiOB1_`jQJ0&hpI64tv0Qk$%UO;OiD;0pKX&1{{=u;^I$s{6 zYN{XPS*4O<<@_QHs&Wi8q)xhaV!Tuv9vRlfN0p1fVL2Ji&C-BZ@90!f4v?jZ za8N1)f;?hdNb>&1A=31nT;;iQ8aREra?~x(qQqAK39#+{u+Y=b5&pM9H(|EjhZtHc z5a6vR!{7M~D@GFd)zJgt^`XDvPdDx;>g=DVx*cZ~(ic}0m@@C_;NN_Fe&VKxKw}I& zW^~a&@*P1G2%la@4|J(j&yTgGvMpsHOi)92fXX; z@|+O{Lf%CP2{#8fFWtHIH!-erqFHy$=hmBkAv!%HmnF-p^xo|#KV z^|Ua0KqCO!_7?Cd^EOAZ=&WzR*(N>QaemhiY7xG6Q0(dApQT781gYopMn84{vvU4U z@l=5Gk11xW6tJ{VWz-Y)(ZeBtEch&L0EoDmu|i2uVpbkDf9F_Lbo&EwXwRV8BzQQR z$dC+4lX%O`xtP`J3M_N{eapM;d&7n41Bgx%lSL=>ZW7ScP$XZ}N~ixq{4$*t(f@3< ze1MkaZ!SnZjXc<5G&y z?+F2DZbJSeKdP#nVNR#FapPp(z4Do zV5=HOrf?Y}vrv(#aZ|9w2*>9{JSGBNgm?#DqW=s4s?+Z$pHN8SH_R4Y(}*$u>?UxQ zhA2O;*IDiM>2n^-Ew)9^h=Lx_7Md?h&B=ZJGTe=8L z6{VUY{O1lX&ZLg=f~G``Zz_Zz{Eti-E=wy;&X!Qow*CyV0Drv}g>$e`paflQp`gFJ z{@O{#i6%GF>D&$_0n0a&1uVnTOvD?^o~@50`8w$cJFbjYCoFI@XMGjrz)Z$lo8ZTw ztps1&#vFg%-Kdd`oQL1NbA6UGM$h;uViZ%vKE%T_7_6Z=^21cEtv9uMm?3>&hR40O z=>y*cUI#@cG&QI!v|)D7%vIWPLrF?RhE_Ivu=Fh#N-KN1VaeD5rbxmTqD^xka_w59 za88mUr3v2|ybb zBXQqMdoID?Jv6FzC(fUWlwo<4d+HIo1~J}-nZ~J>Kq-vczbB|_*Sm>d)aNGh0cX$w zVng0ERj$u(kP7+7fwfgJ{^;Yc@kSpgZ2>Ue%ORYbW__@*F5SIK&7bTg=A!~QzAX z8O-;@ew1s&{ZI&k@` z2xwewgyU8WbsW|bkWO~quT9G#{X^Ofhq%~!QZ#6c$`}=ljMZ0->~DRlz~@)w71UN; ztY=<#R1M*{rGIGEKia;HK1j4%c^}N02yVk~5^nj!;`b_#zHD-yis-RE0f6l9 zKAi*|_)z?YSp$g=oeCkb<<4_unBMBwRsWl9MhS>A@ykOX;ng6`)nsXK_Rm(LDctbj zGW*}uo8NUh&!)1C7{|77~_mq?i2$mCH%PLrpPT6;44oXXr)y0ZI7n> zX_5Ax4)^?TSuXtC5z!k+5|BLaWsRU0W-{Kq@5sIvbw)uUh7Om1C%>@d(#n`N(iN-j zwJ?`Xq0U6Sf=)v+EA9cgD@AkXDY9#K!MsG6A^Bfr;_)xrwnHKS%*p&7s_?g-Tc+zT zaG1Sn@s^_AmvyR4na4z27&&CLu4?VoO78w6E*nTQHh7H1%bUnm@q{(?f zNp-gu%j=7pX4Lc$_DDxuL?5(ox)aPm@Ssk(ub_0kah5=!SI8$9N8?XhGpaRz7*%&< zO>%3V1ZZI)HJ@SHF80ug&-}ziWgkcQ;~(ja-1Eiz%oc~i*p4i)5~6Vb(l_M9nEmuP zge(|dgnvVs)MwY)73Z(QknP8R|ISO#d2t6T>nei78F9RgXZySH!o2_hML@d0!dss4 zEHSptO%x2kXI`SS7(J_H*(=aAWKLgr9nyg_uZ&ybxS-yu4jlW^vs>IF2;k=?)Zvv7 zetD2UiBi7eS(HauxX|9AZ}h`hXQ~R94?>q7WaYjoXRf24jJk4ZH${1BS~$}aW#9)K z#aSbcE)-M`YH%X95Lb+SI@huD(HNXmF;u7~(Z-Gl_;!V9yBw*wG)2diPZCJ)Z=aO5 ze$XWQq=i66JN~a@Q&DgMGe?R$fe9ZLs#KwCcT!m?Ag9GWXA$}iC&t>S>PE|Xje{12 z6yum6oQ(|{czFV?Kj1m8F?Zd_sb3~FO|ajK*yNnSoa&n+UcAfuJ?JL7h#2j zdG;|^F6|NgWH1aL4RziNB%V?S@9VmRC@B^e(q6k~0`h>ua z)Oap#n7UNa=4rHO?<&CN28^1PbJIv%IQ0#3WWEhE8PL)Q++#WFv8*8#9s|FE$QO-> z+2gympaW^qXmFjMRP_6rC$Lj6AZh~iYnZf$MsY)o6Cp8%{*J!<5C@X5XXM|CoJo6K zZv$iP1_1To%QI8d(0h|p?~H(3-&bH?c0RN~1whViyDHb^XJf8*|z48#4m#*Un%#!OUO2AN)V$i%9ev+BcR``1-A zn2ub^Jd&i-IP10cebeB@BfQB0GVLKdOg50*;1=I4k#1gR0W`_ZtS*(fa<)%^9;~-A z-$J9E%Sz>niAN!Lzts~e!(JMGrslf){75V{w<`7e;SqLA)20zAv8sUWudeOwPrP#e zU z?>VOH8CmfssgI@d*OGG>J$GE1&Ld08#0ta^I=6BJhA@T6oX9KaQ;Hp5( zayi16XvwYJjRDpk_&WZtVUy55t)OKUJpuEuGCg~$TyITX4{v>O58x)Z3wf;z@Z)X@r~M#ZAj9kaz*YmriQ)@2R3dwFklsKka_O&iUemYs3mE^_T1W4~ zq0*0Iu|V~b%A-9ybPY#$!kelU7;Rou^~BNaU|aBrVg(Xtm_2_x@z}5Y*gR|BL0-$! z!vZf52|M*}^`I85iiFG{&FS``f$W8vXJ{70k@&vFBB2_iTEWvRC*8;#q6_mRSBJKn zP+ZS)W|S3nCLEI`D#=uOogD$v>D(VEy?#++;|A~Ei|x*`*vt4ut!c0j8gLnNo^zv; z;B}39xFiBC|6($%zebhlxFLr(Bdr7Lni5^of!MYOpFJc8xdukxK0A+bLKszy-u|$l zW2k>s9QwE-Ir&d^G1^NTC>bbqdOk8s(9<68X`rNqW_wl6hK~7|Vw=pxNhn@O;`soR^wg!rd8kLXE-{}pF0NCz?J%+%>4Nm0&< zA;SU50F#7=7^j(*bOEEV3y-PP-jFP0B7I1S{JL zLL6Qc8?5BB_2pnF_p2q4<~0Ytq+`ZfPA4 zVtPWs9Y>39#i(*PdaJAbUIHGo%{zV!W0igRjDD`^#Xao4ideHO)3=@>Yt~wcJUG3+ z1g~=O%%f^y-2xE)^+7#QrL5(UM{;`^dZ>d{*WbQC4CDePd2%mEcCay_dQ+ULv~00{ z1*iHuxgSul;&P*@M-{OO#mR$x+#FFl1vBX-Kg@3j-r7HD%%z-uQ$R75Xu9O_ksjI| zAo^N@d==C-1#Aa@y9lf7XJU z64b((Jn?}b_a{u^9cKm{ej4@0f4=Kh*{&In0Hy}wNy`TBJ&fhaa{Yc7Q@&LcP~mDS^2XkIry+d=fV&;-Gh7+l zz3l}|*k7GIw#OW+)+XPcGOTFSa5KWuGZhsGSjV-ZWc?v$x9qG|&2vZ>jT`b*NfR9x z0x`DKDgvF^Dux%fZisBGEy`)F$2+1vjMX?Vil7dxwlft(n}t`N@o-oH`KlbtnS~nO z-)RE-wj_=LYT1z@T_!4>6{g&C64BrEw0WVJ!Z3OFkY(|xeQ-!R%3ON76?K_4P~Y2c zq$t>gB)L}8Y!KH9DFxKvPnyjL;mNFD=i65_ie( zZNld?epKFI87bMkGeq&kbiNbcMEYaNT-sC0G+$0F-7N&s(*22(Qbvf)0d9D0iG&4p z*Kn(C5laH;+Z2~8>N^0A57$m|Yk@6(g#Xdo({?JgpKmahWI#%^6;YcL($x*5%JQ%? zQYdKq(wf&&LsZY2Lx+;ijyxjG(@&%bb*02o$S#0nlqF+(W7sk^W`=j5p}FXe=Q=){cL6ElB!h7MO|sh4jrZ!89yZ6#&ZXw=1-Grl;W3 zhT@PzDb~F^**%&kZe2%eI8B_;O!W1<$B)nL$7`FwL$ypq>V(Q-LYxX6%50Vcm(L@& zX9+k76T@!b=CJcnc9=t(}x&9LsM=pAuyXEE)mC!Y=8Bav{S1osOfeCfmf z)Yua@GI4dhL~FckqDuGMa$DuCtD#HJ_TMUhS>Rf#3I5H2L4kO}y55uQ#LuCykS9kP zOxDamwx2cN<)iy2pGl~AWqTUgP~efv&A-&TDjr8Iz7?SUO|ebr32zHg?*GF0`#@kJUR2F034O}t!MfcxBtpPm5tC~1F9eYkI~$RMsq7+ zQDmE4w~#F!B8+G!k2r_x&JU^T5%&7B3g{EjVU#VgO=33$`N;0`jwzNghWD{j;`wvM z*9utm%^W-Bx&dibefFOrTG603lJdnQ{%AJ6cJHC`EnFzAA!r6=DFCv^~@ zM42jfVP+)6f7WLw;ZXmr68K4wD-=Ye!-!ouH8PH4^$Q;%MDo5H>?Qqt*c|tkd;GDw zi2z)t7=X-sz#h=yFBaCRF^CMN(CNume2Lh_``er@Os#ghWXwQNSw^?usXXTYatij| z*Hjc8ifCxQ`M>$e4#fb6@o!p}2;d*3X5G_2Mb1Fb zY2+^=yH_D*3Sp`9k3fULcfEm4g&kluvZ@vvFtfb|hA3p6nbaemm8v-o@fWzAGap{o z>uB!daDdEVvWX^?DT}a2J8Xlw#8T-_j^+A$>l%!z`rqfANUHv}TQ+u!sdUcEN!MSp zBr|Pf<8JmB+}9|Tq3sqa4r`vw!QcXiXso&6Y+ zJ)_^4q5Rnd!j-IA-OU+qA;K-LfZ9|228>|jtO)CUlTh^2_MVF@+X*6Qmmvb=K5b9R z!;9MH8%m40>$p$bp>9Fq~*R)kwfr2T)4Ps2!h$Mt~wtY$>==da##^kIpcc@uT)C} z-vR1$sFm}+x|IfxDu`7?kvkvVce*wPARu*%e-2olv~=PR55y&p&w9L=gXz%-g9EyTn3wp2=dtidQS-J8Z_ zJQkJZUM>uHcHkUd)uPiuG{Un)(YyrHwbkW~Q!&AQ)X-vJfjP2}RYI(_xMm_>q%xk% z;5qS#uE73039B+{-Xtr)Vzp1`>$PIsh$ICDK_ZG6#qkN-0KxJxu|fx z^WjUVrkh@433@M4u47mAwU${%=nxdn6Di+;vIZj7@D(84>8+)BdGEQv)%FD=cD22# z5ZsI0T8EGFQ`o6hM{rtpbKOL0N0i+zcnP_Y#%x|20Jbm>0QZbq8@>iEjLBljh@fU0XGkb%TYJj0x&1afO}cOb_&z3L#-5Z1 z^=VNWXq@9w^Lr`rWA%m}1AKNM)|KC1(1#eB;&Cf^sVPB-iym}4&l9rax0PTRtSMgCDBL8Ao z+F3b6ZqWdY?K(>qPmbhiw-ZXfU7bUnOx!w1v7Do%{#h{T;XzJJ1*XNk0{TJ|Tiv6o zI74-B+6OdD9uPl^uneO+yGqqmAxSx)EE%R(X#lJje82GvVw!x$DuJ1#uHHGqOOA+D z2gEB{rjXBCT%3F7nuluk4NH6d#-;k7p3_8L3)l4ZKgJTXNdb(PBT8**J=I{)zaK~^ zH2-(FQO)45Ir6chrW*r1^TJh?a;un~zJc za$E#dTjPq1ri|BN(0Zc8IDyCW0`d<{&XIyj9!U(>C;8CpcT?gBNgC8E!+~&TiZL;@ zP7o_2o<6hCH@0-J9EBiT$Dd)K06>+^V4Jfpu3)FcKJyJz4?uI1*6#+`Wd)XKBdGGx zM|o`pmm*=2A+-n9No<`F0PK(=#|LEe{Fof;@(!Q)9~^*p8oA#_9K=Axm5Rnl*81FA z^AC_x0^yXT<8$Daw&J~S&HPl)owW+DCCMcOp~K3ViFxyeH}vQgN4>q^v@+zxSk6l& z-g#Afa_w2MyLLC=vQtyD);i z$ONhnLNdO~4N!i=J4okR>e(ir`YscM2Yvdet^pWW{5Ay1a!v)ULy^65fzPI0Te_E_ z=0^K=vF^}HF?}1Ivv3klD#%3$B1>2A1g7HNGu4EI(#=dhWQ3Wzj<)to-?1#_~%U4r9^^>LL$Y0Uc1T~oz=If&G)98HO!WzvFu^O;Q}0{Taw$bQ5}L`qS)bDDmR8Og;}44 zQ7idLEf^gwP@_=~6 zHuEgqQaNt&TRBWtZ>NE24+Pj8co{-t7^Up@rK%WE{>t$~bGs{3_=E~=sIhwmuMR0fVKNn%O$?(XVGaErbiTSpJ!s9 z@+vK0%u<_#x(O0rnZ74$zIeIYruwtjg^^I8v3%G^_LK(tsY1ZLic#Mt(8{H-DJ0#l z6PMK*v0Yp}X%b(ydz$P8L>RS!?{9^25ZR5(EaE$(#ob63ZNhvAtAeg8@HbQ`Fk>Gy zApqqLGj5TW>w0Fqo9b$n=&*C!%?O z(?H~!gSjEHF>X$1!y(=1@4BXC4A7NqV(Krc8m!Jb1o@F9orOL=5Rq^Vz&M8Elv=4O z;X8^xoP9}6$N^M8f_;ZYs$RCtO~SfZXER|SnOk~BnDQhIGX|yzdJHP})s~Pq zZ(-raS^u3l_>Lk76D6E^QfNGmQVmfpCnAojv*3TN9esT!mw7x}dXQ6G>y)K%V)m?G z@d%ji%N3yvw4fI{$6`p7nOd|$FWm}9&6G$*WE9fe_yVDJ6edwa>>mKaC+8kA(#?h& zDbaNAa6+4w-}|CX2L-@(k@W}Lf!)_IH=G6NQL52ofsM{%HNvxB1S+qjH_bgQf1yd& z3M<51^mzvK*)OE3vTvxMK4N7A${{8BQxp}!TS0I+Mp)SD#on;+g!Nd^PAExp7L3^ptlTZn_blI*sWE|@|f5=P~TOICz#d*;R zu{J-m%2l#_2v6(N$`t8o5Qh{ZRT*Xs)^428#Yuyx>i1Ni76{Q&Ma2yAah`BjzVtM} zU|jjL{lx&^{eICM*zos>=O%V@qVY10ml*G-p|u#uCZyNZ(Lq#aW?Akh-Pude1!OIk z!6wg$^U>lb-BT`Akin|2ODXQ1B76+uFz1wajptU>mBs?~pAAi=%6-#lA*_e@O6 zg;iOe1_1dQgA@Xj47`a!!|AD=S{TLlOvhfCc(gN^w=!II<|TZ4<&wxS)93B-#^xpU zX@K)(Z-fF+(=PnaLXtRb^`kOmxAOuB(3`TaJtrnv#r%~f*&t0eRlMu_ZIXonfvz)@ z_?9BkHDX$i3cukBBkK+^7YjFHKq9LjIYW<~C4bHku9OhP@o>5OJKkOd>=8^RaBnXi zA08$yV-mczIZ+FQOD-zU1Vvzl!5(Yuw?>cV=_(osXov+|M1gU*;Vk7u50Ap&x32x4 z!<_9UsK!M7$NKAkzsrwIPO+d6@M79P%oLM(_n%Z{($Jos!^|7*%0fZCbXM)#@xA30 zp&+D4r@SLF~%V3o6wDfPsGTVBDDma)z{ids#pMr~l}SL>Q@(ybIt&YemMpcwt* zZ*VVoT|1~sCnwDO@56kWn-oN-us-We6K`>#lttV^Ng^MsOQ5+Kp7fBgayG{j&iLK9H@rD5TBBE9%HC>!Jm`F@*ulAhd1w9Gp*bVhGUaz z$o%j|*`|ocAap4(MX%7GOR!mN_xYUfc^`-MY+f#!6X6>MloTS=XL^a;92s?+0lz4D z3`UZH;H425Nb^6g?C_u;!JaNM5E0HysrtN6qm$;iWhhp~F^H?^f59mUH$=D>SOhMHpM-hZUC{Xcz8h#{E253Z8MfQ*xF4zWi*TnV5P*#Bfn{;Hj zCFKsHaVHKHb*q1FrujZ9uuh*}P%-~#jb=PB-E@nEP(XDKyguS|;yBEENZsp6{JAXS zew18YN+|xRu8kI88*qdcl<_4s3c)WPC&!`R8zyjZ&~(=f4HL$r~C0 zC&za`R_+zWt>(yT-DONFa*W}k&@i|I4#4BERBsL&5#1FbYBpgyGZN*gmp?*uMP z^_V*$)Tvzt+a`x|IZcssTgmQQ3haer_&QH^Qx;kJez`4S87FDMr}ADTpVbZ5FK zp;xdG=ELRLffaY?1;KORpF#o(T!otw?2kUsq$jrZ5u}!)N54w-3E#($DX^^iuSZ`# z=GUKL!e$v+`FoIJp)`e2M9G4E=g{X&|Gh+S_$SccQRE;@PK9Otsi=0e+QBb0AW4Ac zmYv|awICQeZkoy5g0nl#8(!fG3!OW3a|g|~6u$C%dBdYIqwL0%fM#x;VCsk={$NU0 z#oj;&s6OiY3C@AaRcjz`>nx<_9)bgTzx)0m_te5qC^H|P$TMoDN6xt=v*uHw9a_g# znNpRhV{Rk z!kCd;9ZyQ$Hyu0n=O(v}_ zSj9kv;?9c?6HtVp^LdZaD{laaD@895dRd^D0Z(vPf#VlMQ6BVjEnbaGjyp_qJ-vO| z2seLt?g>g*B>Wv|`90?2A0(n|!gH+v&0A=*2Vq#=D24>{S8FTgA~mNtDAuMTsV`SS zSxp{A`kR}AxCYI;4XjRVzlOeSUM7yw8vhXone zg}E*Rdp&D85o1B2Uqi_bijVi?{mv-eSnu(@X$czT@8dxc>3drh+D945EP-YS4Q9*; zpx8&19jiR$JPvjX(4rH`AICcXHkD4F#Ah11tVn{W48vn4=Rg7#GSPXyGT0?doF`HZ zDdpRMBBd2|c#13xeZLd6N@bqlx0^)(9&zYj|Kazd#;KZX@)OOcGiJh403GS;L6 z1VO#rt9q~XQHCUm@>km3+8clBZOEnFU()@mh(dTA4-lzabs|@PYscI!d@L3 zF9w>c*Yr9-DkbMJ)o(hQMT`Y82y3hY)ULT!{pxKo-*( z4PA&U!XBPhUT0vpW4$(oK^zqV-`^1p|lhBU&Y#Oc~e%6G6 zIRRDC*`hi%vE+*e&8ZI{zfM!LC;W_1ejqk5OIM@C34drYV4W=aiF@3LA2fs5!jxK z1b3yuc&5>Fl5RAh>Xd+p3xs%uHm7yvwR*|f$dJgVVMO@n+DnU8Fr=$q6)gg%e zRpGUmjMU2Gq27a<^RDtm%H*C6yC|oDso7}@ME@FqqzF9d!@QX}3Dp2ZsuiT7caYAG z=Vn|d6NY-#sTnj_=!llsq~0OpIV+_Y>4kvPs*_pDlYfO!g?}=vQ_~XIjfErX5lzD< zjYg8j!~{WSIUtbPA=?Z)!dFG}W4FH7j_MCI!p+BhQ%0$3n2;Fm{?BV}o_i791C6*; zzsgxoTo;lli(@DC1METRzOnpI1wS)O7{pGIdnntEZw47R$I!Ql$K%*72Ayc4baJOf z23r~8l6i9Xfn_BX05+NHKAtaw=g#!3HGl_6yd?ncab9L`ksM5Z_ZNv-&F#(D-Kv0i z?^NdK3q#*R#ScyREk6J15*sCBAP==ri`r7gQh?~r;uQD{qzaUHQZsS7(Et!RtxnKs zFm$fx`Bn|{{pBpC@GTMQ`!A6c92eMm@>p?Dr_I9=l9C;?g=j&|U}ytFUr$xs+>wNa zp7;D&_oWqT%$l4nupCoion@5vR{%3zT(y;N#z>d}O{JU-fNL2#Sb*WjD5yPtEgA<3 zR2<(Qx$$*G1U=47LxYnpz0@nnJ2S(tN7OpJFe#KV@-7pd-q)G;t<6rN6vdcgU7#Bd zJtFRx-BfRUdo1M5(%F>Grb!u^A&ip(p@_a`~jUv0Nvv&`%FYYuCw-gf8wFi zCe{9ve25fg2@KJQ_T*)yRV!)LFCfBB1>sq~l0lw7m`ai@bu-J-ZBgJDVknqDUU>+2 z%)KHPy5M%1GJ>N))(!!}&UP29ZU%+|2=e#+;E%0!($`%vrfd=_&GYDxEC3}RZY7MD zds@i8{AUfruTDgoo8~ES8egyW-JjlQ?ST-zJ5P0JpDt= z;c@m2GgLI5b^=oNe|tzod)Pa=&D)@WQLvD!-=Nj*>cd{FlwgP07k=f~sEUvButwHE zn2b%WI(d*G7C73d>@+il{f`_9lTgfCfG~o{`E|Zg??^@fb4ycVAe$2GENW+SIv&wK z*d-9SbG3er*huM@wWi!hovB$S_8`ymYT$VyHYibJM~}p*zbV2q5;+X;9Zmokd4wT} z1ZO!`;OX>r+3_ms%k72+uiPpSkT8d_Ehi`5|2h|`Phz@zyd zrNqHx(%svbGsHpUR@Z$iU3UOp3z%qVws+vCwV_%GO8redg5K(oV=f(~&jjXR@Q^zj zSgrW##NhAqdN4dXFmqsdjttau@cLMKAdQqZxq~_z72)tn8neP`lo+LS|4wdpZ|ZkA zl9DpZ^ob&tmCl3SE+Q&LSQe9pr_Go{Kag`6v~)jD-}?^B40z{xm3>z9g+$UAPZ6%% z2{I!g1mMgp(7jHT!@;)?Vb83nBst#461!83iA(JBB>hi(`FTv!&=z@6P60E54kFag z6`{CXVF423sFPjXe(lzK3cE?Ge956O6GO`eTaFXP#lI>4ZWQ74Ops+$G#j#a8a}vn zwZch~?AQYM2}<;B?GEHAW(8~~V~uSr=BTSiz0a6$KC37(#@g!%h~mCb6pm|ad7gsF z^*ls_$r?>Lv)%KjMU6YlN~3aRhzOm1g~+y7bQmIGiCt7lvzPKFM~s%4C@4`F)T9O1 zXlk`8PJb^dw?J0BleDjJqc4vQ+-5KR0P$$A`^DoB}RUQv1sIq`} zSO{dPvuFy2+}F9#5rnsWTIYH>pXjMp=t7eoO>-?ta2Ja`a5GPu^YIfEYh4Us5TmOd zpje*-+??$#qm9{woWhWDw`x5oL@v3QllMN$?!@u%#v7;jY>V(=@l3M#7O`5LPx-;z z3Z#9~z#X;E7NtQUta=*tuBmA9LZJSQ12X2rYPUM273v$EAMtZLQb;qH9Y(U13bD(* zq>H2I&2^1_Y{mri{-FdL0FH=?AYM( zGY8$8w#w8hbQ+1%9>f&1XZMfj8DfzjMg1&bLe76|Q%stwMA3hdkA``y9!2nrr6u?ZW$wOF2y^GKj?*xLN90J); zVFi<%TR!6N`3KczI5LgF3?bP^`y9LTk9DM?zD8uLo+Ic#9#&n~7SFaDDukVKzG z_50~^iquZM?q+Aar?|B<9yMMD1}=B4r2!(yYFl4%e5Q|yvcV5*b-_B5=x*%n+=5PK z!PAv$1$%erq5@M?Xm1qKvP3F(p}h933cbQt%?}ZF$g|r|7Z=*oCjCMh&GK%L-m>Zq z6pzKN$_?GzC2#+go|Q z$YaPQT5X1UT;%2mEQ!h?RZm-YB*x{8l~JH+X8^t@>L7w@5e*CZx+ae-(O#zmv3`#O zoqu_??~zU0f!-IGazB4GU&8`tm%gGDgI;~|*x8NHFS>ql*Ozz0&`9NAkzKHHgLD~} z0&GzFEG-1!r!8v4`ghDN{hv%ddn)>XgbI- z0d)c3=U!E~Q#R;DcysvbQDbglybuGRU+9TLhhc;A9;FN89Td8=$o70PhDF%qCk*OvHZ-KEXq}!(Uxdl0I`a-ggfv7|%j~-+1e=d=|r6=V~JK*28n}pJI zcAu%LUG_9fR8=fHBm&z>A-ja41E!9d7^CfG7j#qBOT2BU`|&0#D({_V@K4QZ4QE(? z{1Jx*T*J(Blkb%V{aa8}G6-jTKJlOOG-Qz3M+ARb07#-=TIILPn*X&?ao@~+ghl8k z_ofBi{;G4~)0&qut!cdQ+&V?hSDS{Tbxr27!^~a}lrP3i%~C^*@R|tPn0s zjKU!q@<=-x-tpMOT>sNP-1x@AF9XqxKt*CB>sz!8u%*V{E z=LWOb8F)s|76Xe?&mVjvwB^_zPRomhL=cSyqVW_VD9GgDRxOhn=Ny^H&E?UJvQ6I> zZ&|pXU(xgAbl75y-_Wq=$|-kh%>fS$5>N!~bYv8iiP)hs{CL9{8Ge_vDI@LUq}xcp zqt67~#zn`NW#76OP7!^d1)Zmykd^-be%@Uk^UhHglc76Xpd}mWV(2d>WQ%s&m~3*i zaTue^8%P$JKBn#0SG*Ll;%6vR^@@%OGwaM+>;~E)1l%20KM2CFS2cGcyH(_(se13y z%{X=K{Bv%V3svJKoEca57V^VbyUF4byH3T0o;gCr)2fXTq9*7tQ88a0C@OdXCrg64 zGREFJe=lQ>G)tOWtX3-t`~g^OS;U^uzIX&0_Je9(Eg_85Gnx-#IOJ2`&Nl}~Cu}hC zhnzF!l>4?pSpbpRSM9BdM))|EA?4o;8BH@syX+>Z7mra$;iol^K{$yMRe}RD>A8#J7!1b9Y=o<3!92i*K6RR_;Y$DJ4o_} zS{LB03leJxp`jRm6iX75~#2fut}<{?pwY-YtYmiR!X zPZ@|4!YL%4S$b@l_La}I8+uPjO|G;OUf}=eB;Nog>7@yb8dkmykRBvbwuqbx7ShQ; z>h|SE5yf$(H&UEkb&)!hDB0mVqwoZP*(xayYo3yL`nEQdj+ew=I%>X(ZavbWU#1(47+_slZX2OezQCU8A`<7x?h@+0Z-d^I zDg+l|N`~}3_2Y;iC}!x+sd4QyIt`k>*2Ye}AvU~cjEJU0yu)om+cobr2o3(Yd*H|6 z@g*t9_oJD&N@N6L;G)pp=db;kO-=_3C|{T1`-|2Pb2IJxnn4IEZlCDzf~M3fRh+(~ zzNa(yH*>}3v$>~AC}9t70l-MCLD&?ECZHymJrT>bWtB?Q=r-HZ$l=lC;%`?iyB!_h zdVrW|$d(AOUzkVA-xw%2=xB&W2!e#7+t!P=45hbftm_*eM?9#4VE; z(A_sIP18k3a&V}g%p&bPE7y5`e4bPt53^95ntSLt1jSdsc$aa( zSfO1*+XM%|v-2HXT*y(Xwn62z!;6f`P7PhEv2+*e6t8roaOlsh4ZuDn6aMDB=VqlonpX{Vpj zcK4K%dn0r<<#vv0>fs?p2KSoaJvx`SgwFalJ3}Nua9q8#Z2sCfZl1i}{3+5(H0aqXX?%Ox;muo{hD{5Aq?=Y?GJ`=a3 z3L)6rO^E^B`{zwXzMp6v`aE3}E*0lNG+rJt-9Gso?+lH-2kQ=xUu8}wV;8N->js#YL*{@EkUxfyioQs# zc;nX*xmFxbOb*{BQILQ+Gd3a}r9G>+m=|%uAkwLt4y<>e231f`xZB;-&p02)W7{S! zb6iyLmO;n-+j0T#&O^QeGIo5iiOl&kE3nu>AC|;v%ZK+#@pyA8L0nrFn_#Y;8Xx0K z(nKU*&+kS<_PHOs5x2@)=S{~M)-q-l{*$wql^r*!0ia$MW_XR~8l8)-Q8=kh;DDpM6EP0DLU5VY$#QvmS}!UKY?keAws)Mb`h$ zpn~k#Q4M%@%G0Vr`j(uCQ7gprTXS0jQ|e@-F*#)QF90{qrLG~(pE}F<5y~V!yJUT}_}QZ>?E~NgfPqC!EZ<@$XiEZrVtC3(C#}jQ1R#emx8CSH)6Wa@p2bw^2UQoa=W%HTTWqo8%uRQEt}K(?t;{}bt~M&ct~sVI5R_YEis%EJ z>#Ne)uT3i&%?O}-A{oG^>EZQJ2u#e3V6jhkTSLUu3?4|lT8uiKKrGCkQY$sWt|bz3 z=tHUUyFNA31i-QQXwgJwCQ{FMuVRB!TO!b^rnCcN!fx)-pw4C%OlWk0OuU>HuhAK` zEOtUGl0stu{U82(Zqf5~aY+k12~c)^tRa+70c$4pGD4;aI|-S-oF#)>m8q`q9+OJv zx|Ap^dnnj;-itDRI3;bY1DE-cHfr%(_RpO{J1 zxV?$I^whw6@9#is`q#QevzgtknYJ_DD1(2vR>A|U`g9>2F(Ji4D9W7iw>{YUFvZ)rV^@ASJ1hU^t;)zIVMJEvHq zs4)UU>5l{kAy>=m6 z+HM=Br(hC&(HP^tX>5POx>b!tZt4gLcSGfaxdbHxxU=5}T#U}f@G`aZT29&zHiG%F zxKfOHxJxSN2k4Mg2jc4UN^&0NB-DO_!@$swAiATpn$V+BRvJ2pOr)rwDH zTqDMIJKGr`p7o5K!QVP)4&Jy4*$ckyRi=4mEdI3LSq&-g1Vg=#k z^J0HsndLcLxNL3zAknV zeDv=akXVa2Ix=YxCSg$XRt8m*;shlT`1~5P3G-v2`)ohm-x>^|q$bLTTGVKz#H0_< zw1WjF8|nQlmLXcP&BYG@qCd4!cpycYr0Zbj!FlKg%B;5mz=$#UaJ$ZWLBiaaGvY)+ z#U1)F0>aA&0ua%{4b=UGux~Ek#1NBu(&`dVLt~5W1{#Jd{BcRojT?rcmbx$ubtF}n zI?K3Y2?#n)x{}Vo^(9{FcPm8U42RW^DYNH-09)32$gVHnIR)D60FC{!U|b`>#P3#g zv1$HN>VwLLEKew4A4h>gWd_)rKs@&#Ia$6wCYcwFcn=M}4rDu^>lq}oJ&YZ&_(gRh zL%uFeyP*w4X<`EODV@py!DdBzsDRNMzsJu2S((PvF7aiqM>E%YP9_W=IunOdN6Q87 zaIWgN7c7K%7~I#D>Z`2BQpE-2@%Is+g$jS+#6b3Y%rpxI`c;Er_#8*P{HOM%y9>XF zqr(S#z@>mjgO$HSSX?Zt&n5tkIz-&#zw>bXot6R4@FsF;qESho;#a-eq<@O2+b}^N ztf)uQk@*9+EM_nOZlf?*MmJs5Ym|i>1g#=X{T zC4rN?@rHs2NnD>qTqX#%T$|``*!+qLaJ5Pnj;+b3eHK{6%j^&H{^*pe$`Itc;VPvy z>X~>)%CoekjR_(9RQ{wtZG#<+5u}`*ePs--U z$b4a0_ki-=nuqNz6txV$XlUmXQ}y~^uYI@g=_RR{Jy)i_V~5*u^DOqcNY!+OrE)Ln zA8*7%sA6P-&4H$QN&<+dMR$YNTA%TQO5u}qzfgx~7vUk_(bu^>AFzd;#SHAVwQ!!8 zL;Sd~F`pn~j=XUe@L#y2hFj8Q{pb>5pq~T8>Ue^n3gv(X8OTy93SIZyG4yN-8hy2- zi(4)@aV^BO7d4(DuWEr*p{0y+!VoXhB%Cy>G3xroKsTl8!*E~fD!ZoYJ$Z0LGe8EO zt(HxTA3-4?$E?H&%0AhZg@KepE2l5@&GXt9nyu6q>>_0J*o7Cn74{;Ljdau_MBSo6ou!LItumqJgE zAu4Kb?k|H2Rr(V5gC=<1#o*OMMz@!{#}0R=CbrrArEz$%k=z&qYZ0td|9A7~P$>A|@^-cq)7PD-;wQue3CzEg~Z95eO zpD;!~LocDz)7#lY05?mv#is(cOqTNUGM32ESvazdUhoUftKCx2T&rvMVc)yIqHo=x z^KJzl{7;!oVJ)~DWB++UE0`bxiCe#^g9iZY$b*?)}e*=;rUL63^oA@7x$2YEt?3NYUphp24{#V4%in7bC@ zk5P$gBm6;>kuP%{!0)ZmMx1-#WCjH31`9OWVkFIV8`T_jF71I-eq-xFOK}{_Ayrjk zKd=x6Hgza}fFQ-ffbS3_Y5loY3(=K6CGcON1%*onv9vzZ_`%xu{@WhsLd?{Cj`lO; zlwmR6foFM2$M>u7->E|cY{j@mEfS0>@qdE&e+3e9kw%MaCX3;vT*X$TFbBWRuDMr-ob>JiCP zX8LVOR@6TnJW;~*MS+Wx)PV|ZVgtZ`@s4)UBMZ-GO*!!U2YHR#R{=@`gPYtMIvBE+ z<=OuW3Z3CaS?scO3PhU1Rnzvrj$5LZP2&|3i;NK(}BbO zt@r_Iz8`%U#LBP%{#KZAn-X51+E%|0F#@hKO0ztEV0x_>(9sq@+sus3BDRUP{p(ur z#3uLuF1jsP|9!?t(>Z9mV{b6@(;m*TTr_=a8W<}2c$!RvB6lGEXbkDkF=w!Vt=&7l z`Wgl)5vjoE$)6*Ef5$yMMMmGKUq}H2y-n5uAI6Rlx|&i#ZyOE+yrGhWejAgMZdnhl zK9pktjp{8UsRpaV+=F438;jBWDJN~tUkJw|%zFu+JCqOTpSi7a>sbkEFJ+3`e}?Sg z+pjrmJ9KVy_!hSI`Hq`&(D$mhJ2xUI<~D*6qA6tYUn$H$%E4UP-TY2jQLH@s>x;B* z(;o24neNP*9x(XfLz)YN`{Q}B5OwUrqWl*Mf3D^ms`XnkDG&?;r8qrBod5Uq3RCkX zt9rA&cCw0xT0H55#D*Uui{*mWFd?-)rOrCc`u8-nH;g{G4OF6>*=ue<(l;X}I~K(R zXs+!uF_7KM-u@KFVrpSv(tG8`i{P{e&6}@^QE-Z>Pya$%7G}rI)f+Vo?mq)PqJ)D! zPn`Z@l?C*&gvXEKVPY{@W{`$Oiai)6l1qU#yxttf15FsPB$SH?w1rjVxp-u@mC|la!$>?}< zS3^Xo{jC>+S*8^NrQlQ7w)Sg0%2|ImOft}&Hcw5C;bsAV&GR<+JslDz1N?pe;tu5` z0;q<0LV4kN?RtxVeCA9`7JV=F8RD$|{rf1uJ9rLPQxXFSffcI@ z?-ttO7mB|{nDP^Anl+voD3qeo^3|P8>>%2pJ?4#{9!tEWVx5evb}o9}9mzXRsy=(; zMg~HNjnm`QUH0)oBaI8Z~ zex?*iw?Ib8>>udIj zWo&q7qYsry#BGgwo37GxCJpxFZf00zNUQgX&wDQbh?WlA{%g#BNVe#Y=MO8P>E?r=Ae-?(f5yaax+v-XKw>MX-i z@U6Ka2r!+$a<#{I)nr*0B$@?JQXfbHyttAke{X5|MT;P;Dxjn zTsSZIm;9H<5oJ-LC#WN$e)7Wju2zd7_a%t_)%2?4zbMXYwQ-ZQ=Mh86(gRgRy|lVDn2cNy_8y6IhJ(3iHF+p`DNtHSS_ z-V)NXJL*mw{$25aEgo!tF1B!o;BXCnzwIb_8$E171t1-sYKG?wQ@@Mp`}Axi$NEWl zo2Y|QU%h`7OlUzl>#6aSU}lRg@Ldo;bF5zixhsrdY}%SgVm_qXaQx7VXZnpqTrldj z{Cx-*YVE>pP-cH_VmL`XIA>s%tMvr)+UB`h8 z?c~OuJ-t}GqWbaWZAU_Sn4IqrcgH9FAEsu5ukH456P-K}s{gQ;!Z9WC`NOrd0A%UG z$ydcekCvR>uECQ1#p^s^be<>&d2mT5;-y%(>pf?du{( zFUvkgdhn6FFsMo-v0fNRF0wF=CA~K3B?vMpVI}yJH7A8Pb*7M$8LC(sP&s z@@46xrhXz_0jBh6b{cFStR#UhP$pQrTy21Rg-_EydaFEGjxl|YOVp4=YJc&fk6fPw z_Mzo5aG`j7sg0WPvbNG3=L@jPPxR`lU-fhw>y(m0Lz#pZ86(V`Wb4}tN@v9!jH0G~ zwJaN^D!-&ocixFZ;N+7Qc5r^)HNU9iLEIG&8Hum8+@-0I)|Zu2vsRG?6qJ^1dU*ig zT=_v17uH>DX1{}tB!HuHo1O%y_aTh2&tJ=pgYqZ;U*}yd85qyn$ELSK6kf2&CmFm` z=TL8XP|m$_JZ+lb_(lNUvc_~9Ot|mXs8F!mX9N`|BIGj4bqXS6yLTbe=sKNQN3bkW z?I-g{M3v%RiS89IkIZ3*oBbxIf!ggnN^RW|z=GoueAncI*E1MMQ4Y*!E2D2aeG}@) z-kL#cqdEVjQV8Dy2)~-@0)ybGT`L6Q4~PLK8Cs~&)@fZFh6w(R38I*qB=e?ihq4a( z^pA@61Qt^!b@5t6Yi3%!0z0xc20O-ft#e4bGAJRGRmr$v6;BBoCWu~4rkaBX6|3Ct zpKOTv86J_P364w$9|P>*!fpF4w(Fj$K{A3cdx0}cZdI7`#jmvM+V0Ff)}-z;2w|`w zo+cqa2%Ae8V_=Iij?!;4Di81w;ft8KG}gOmu%wG|vH}m(9>K-w%F8If;n7zK)E(w| zvu{QBDFWn?E_rj-M#vY)Th)bogU+J~0Djn^K*=nIDA%z4kkkW%Ifa~gnk=}zL6Vk2 zKI%c-yR%J`6L2Vq30+oR9Q)<4aWnFX<$+r@o>eIt;&aULQBqsc1GE@?tjQ2uz2YTF zCt#j-Cx9U(+2sn8;#5DxFbjl@xP74gEZw(~N#FeO7!N%ucm-}CbJRy7VrsFq?4+3}?jn$R+whaLz8J6u~NOz6N?S$>L<#$eAQKMcl?AF)d+cW-G^MHV~ zHT+69CgldFZT8phso9b-ta1;rSabHdrqt-@DdQ<~NUf#mk8MT2)0gvPt?%4XuxtPa z?TDst%~P$#*buot&`*J|c$smUE>PLF-H8JwS%T^(gcJCo3d)byprZ)kMFU}#=6m57 z2grA}tU#LnjfBmcov?t;v_m@)ew@-O421J(=v`|o2t^UW6e8YMtPf^Be%w3)3s}SL zfAc<=o6SkJ3&(3U1%S;f4F_+Qp&@k5!V)buQy+iKlEgmuD|{xhyaMfiotB*e=@DCo zV5wC`+-t#H)}TaoFXFC86cd-s^|OWyg~Cy^*PL z9Rz2CuEG*1?()VW!pS~-?=lIm^6;kb-wNW9s4kfF%Y4v%_I$6(eA58i{Ia7jTrWhk z+lb9d&ATyG@JUwy54uPz2Xt=Yw=r$fafp{js?t`iqh1?;*zn|l_sf4VAtid9U4E@e{#ZF=fT69spTyWHe9J(e6p_5V%Btj< zjXHmp*!-!<-@5N|!j6P301t=kk>`0WcJrtV*HfBH`=?O0g`z5(J05lMmkcs421vA< zZt_XXFWZ-i0u??3n^aM_{c(~>SFxQ*s??6Ye-ls#nHPU>B$&4F_h<2ex`Ci@Da2dJ zQSRUlf1G?EuoOUwqJFrcWh5F$`pF)7i1XjhW*nQ+xY+``VllpoV`nXd86>u~m)DR! z(?S~9C(HB&kDfeX!1MJtq0DgJM5gD zYMd;c47{1(T6<%4gmbt$7BmpKS-^V}G*H!86-1At&a0H_3a_m9Y&hANTTKI6|lN-Sh_a!UY z`)vmpnym_8>OpCXi>5!R4?kjN+Yk`NO{*K))`Rd4Scs-Q=BH^WqP+7ubxt zl$fo0V1w^&vNly)?=<=W{*In>$5c0epuyEklOXutsjQ<_+&yyauqBc}ofrnlIXW&_ zN6qVUq(a_jr_*%i1}(mEoPp~_MP@k@;o|k8vdvcoKN91exRj$McQb|3rxExD)-FSf{tUrqqm5pAYu&>NpYhLZV8_gp*;t@)($*ZHEp2xKt`^ z#SCztTUv>=?oTnSllLW?iEQp1K`CUo^8YWiH*JJxIWz=#iL_A_vroF=OFK~9ZUuFa z)+oc)2j0Q&-xM{66e4ss#Ohy)cN9tb7IQ;E-=FKtm7<5WB7%B;+u|Y|U)QFZ_VqvW z303E;ipnMdOQN&ph53P{U7zm=PvY7AQ@zPkB1a(O8_@ISe)8GbC%W-If6~g))Mwra zeO)&n)7w|uyM{@*-6^o+#s$)tZfrq*JP?z=Z0?ePHOr#vhy)Gl5rZ{EE}$#OxZH84 z+7YIx7#em2PU@N2ZSUtuHD|5PQ%DJz5tCfe5C5{eU0s%Ltm)z29Qc*RK5ipMZ{nrg z-YTOi${Rwh5t(6v*gDlYHeE0@zn#$HF{?({CrbsrC-e$qjVk#j$}=M2L_N50)s3hpqpkEHH@ zi6wV4zAmEsYQTuXJzBQ#A~;aA+`<`VlD9OWSmY10ECOfK?_MX+E^{U~jy9(n?6=xc zw`4Rb1!e8C%rQm^T?PJx8#m&{03hoCddr%wPYcrCahsJSD1VMI%842?a0*i_yULv< z**2)`|7fYQSZ-5kdB0M_djgJo8_BZNUEt#e0Snq|!-}?(dC?u zlejoC*sGp~%ZUMjsQdL_r0YngJonQkg?Oh|;k!fv zdS>;Eyu!>^qkKWCW=c+yA3M4}z?-JECs<$Sb@%G14L?N&xj;f(;p8_TU@v^=Ffu@J zl#{7u3^0||FdB2QUM>NVKGf+>%eVx|YP9+B#>A@b0<}z8c387)%MblODdz#E)((vS zxiUmvKnr^?CmK7N*fz&-u?Oh=0z)Jd3X)#ZQZo)$>XRh$WBB=twdZCi}8d!x51wvus?4}D72I!Q|bN*#w2+0Nk;gqyd8TP2-mZ;&DZaVJT42S@m!S+4!WDSby3-tKx=N5rGq z-q!}R{&~Vt!~8^j8x?vkczM}%FX$W1VX8gjjXN<*C&~d_C!o0>89ZpD~b{WklKVuGn803P1olYP-Iw9N7ttH0UpOmWqj^Q6uay`1bf{EQ*&ggrL{$}rd$)96p+|PJJvt;kCD`M5 z8l-DK`MhaR+~AbVZ4S@Z@7hn4)tccOc73E zhm)A0jnS3QsyWMDe#W&_ysxazhk4uv&+XfC%*S&Lvy0@^iPwJY^&1Hjz|(Pww9XEe zVZFDl12y)W&;$|-{~6@Q&rTwR-V8se>T07PxF^`+@NjE?ymO%R@Ew;re-1l}g*3~~aN@b-scdlF3ke9i zkM{$Z=&#Zy*a{82{KWoQ{w&2&!F-!+Yy1z>(enFS!q!Ou_DzO--IPT+=74~xth_tsJCCyZ^g!wnLMS)=SaDn2EEGcx}@@4*fm%4&8YW~lQ6!???YG7PBWDv~N^KLC3gAH~kwQ*OaWdZVbb-qHGm4m1NJ*fsK>k+rjMPOGgHy7p-9_i65nE z_|Mz>;=4rIKm40HF`N3XBDoOp3=plbqQO|mem8{>x7r9e&(vDzcWCO$VF&(3^~e=D zA*5*RVOvpYD1f!s&q6M?NqJ>sB_=#T#%6qf5#n5ax~l%~xUN%1vq5deFR`K>c%D3`(;&SNr(NJp}!po2+N-N&5 ziVUSbgLUI7oO-{!+mLe4`&9%%jkgeRm zl9N`Rt8adt?#M9@&JzhHXWkRMWRtE2Ps;z+ ziqJLByI;0$(|{X=3EQqTfU4IPlfVMioY^#&S5E&1%wK;%MKur9My!4&qEsS~6n-EM zvF4^ZOpn%!8_0jg8vEG??a5aCT#pqST^pF0d=}6ta|XZgFU)>L2j{uaeQL;-^{qZr zxz@Nbm6wNn0!!9BK_i2GIbvF}V#g2a5`pRK$kQ-D{jPkch%p%fsQMJR#`U?CbFqXad-1-7` zL<5g!j9Pr2JS^}pdH@Ajb614%X7Z~ec`oHvo-3{15A&B6bhRwPfWt0=sbL>@A9Eew z>g*M^m;Xvf{+X5m(u!^*{pyTc=$~v~&`6Ph(9Yh38sDcuEdo z4vNe_Ap(kwYJGc@=*N5W-iRU+Dq^`)8SLFR`VM zw+i%QRh6h`on5=7+`nYIjUAwbOGFZK6a5w2m1M_VhlVLeV|7B3Olfwmx85rZ(1z_j zPjg%{qP|(Ou76DhHox)=MJUVDi^2D2aTHLQ#FEY-w}-#(Q*>;UX;wFUPdZ6x!-->)q?1U*nDetK`?OmT2an!I zp)ztUU_wQ>T4g0;fk}Ds|JBLe*N||;&e4rnY(ju686BzFWe39ZB2jL93S%KSaIf=u z5J3Fv2KuAO%kX9xn5Pmj40u%Pv|M+Poo9$M8e9Q=f*&9VWmg7z$h6UHetEtzJ{usFiUkF`}B$gQ*3!W%y@+CgSs(Oc@9f7nK!xKlv?N!Je zE(g*!c1=L6r*7^q@_cbw<_l zT2jAf@YdlXXF)l4A4}=S6}JVSTi0~UouZ(atiDkv03ho%Q2TI7!P zJMXmnF~~+}G65#ZTT^`(vL*%#P2%N>*hxFc0{1hQyCwLn?9X>DZi*pk7XK zCG3oU<$#E#^?CdgL&x}CO^9^L8^_`gZiR!L$R#Unr-cX(zK|Y6U>Jl zl^;_;I=dsW_XEu(9C%>_kbY}++p(D7(Ha&NQ)^@dP=g0275nju>JkZlfu4}oCZq}h zR9qS+-|p-MaCw_5P^iUUN~0B%mT<)#6mUUUZJ5%)^;N1XBieA z5~ctYfoj0OsE*cGQt*L6@`>SOZwzw5t@ffatobspLEL)omLtP+=pwj#r36Ni25R)@ z#G0yqZctG<^y~fExHR6MNpsaLYw}APhqHt5ha@WyzZ)($x{vy_z-xw4Pqx_r;*s63k?XJRLolzW8N0J}8kv~`CB&#S6 z2XD%@t@9CDOu2kNI!{34mMk zT@Z>)RDBY)7q35%`S;Zq^7X`2FN4h4U&Zj{hQcY4L>AWGZ(GD2FfJotDhwCvV`>DT zpZ-Gi#$q2kjU$fx7E)ZT=owt4xKQfn22|$-(H_dKpkd`qZKM&n_^SF*I zzU-*MMeZU4k;b;*#jAEXUaBP_ZEcEl#@4fk?QUFXLW}siegqKLChm){u;)>i{mq)^?V2G(QB((&u1 z66DoVIHzhQK?B13bKNJ6H*ADFJ7ASd?FV!4e5!^#6sX+1Ir}shagl;=p$x&`g2Th9 z`!1YT{QD-CCHo7iPUznEC4IOL485jhzy=P)UPnR8K59DT;593_MpY=&-@47UMyF2{ zghdsJN+D4khT}@SF~en>A4|Jm(ErEULF9pR(;ailGOEyp@Fb@v&5y&e7hgM7`8*&p zKChhv5Hb*<4-%bK+Dt|P`c?0l{gGeJYjn?)*mljk^z+IYbRhZ-5Wq#*RzRsIRxSfm z;KO-=?^M9IdRuAd((tc{zYPH&DJ<{Y_FA^ZU?OQ(d^A5KyR-x^_a67x_X5+OHpNMV z{g5pXrXkrOd&?0F$Uv{=ugN;k*=kGnFV#WRi~x@hdMcI2sa8`jkdgHq%h^kZ(&P-) zhTBxhx<)^i?cRsUqU_KK2g^&+Gkt?_hxy$Nko@FwWUJ8CNlkn#_Vj5obFh8>c^6-1nOWe5B_tCPw3}jzGuE0SKWR7gm`&0m$%}ZK>riLgwKrogtX-k9HyYU>1BIjHM2m0 zA!4B*gH7ON*c!`03vcUvA_h-b=9b%IV8 zJM#mWo*> zDs>mTtw$hJI10)g&_D58>AtdYLR<4Tn_)?{-ESn0a(`td4%}H%#J}~+%-x7k#!!?Q ziUF6;e=GqA04WhZOuhsXi=BP_(VwUul(B5-F{k*1^}=LLz5)2UD#G)`3ChBA?PrCQ z8m(ZwFe%7We#hgh#O|Yr$ghkqiy$$$Eur4_F10HpW?Y-q%VhHWeHn&b%uqc5vptNF zQ*>O)iFEthtiCu8zDr^KhfZ`1TZ&Z)xRH{Rg%5O1R3QQJ67IH_DcU9vpeKlx$N zR&%C{pUy-MQ}O1ECtTq$fx`HRQ|gs#wp-<5Zb#nn@s4*1!h6z{O;$G72t=t6u$+d< zg}73w^C9(s@O1g|2;(~Y6W3Uah^tu+XC_iNbhgR1SaUZ{BL{w4dJ_EJ+_4L<828vw!1f(5|Il0`oBJ(mL>^EB&;qPeYnq zQ%zH~>`UqoRmC$Tu&tZ$!_z!HYN?*X1!zL3g9=t+|5(7u2@INz+%nTkhf1hgV~X`+ zLGWK4CPf(dR75-oa5nB7R-u%e}qgyqCTOG@bs$+I3`cej{x#GT#fMtrxGz z{mKj>wUS&oLN>RPiF}{1xf@Y3$T}Tt?m-q~rY`It370NH)KpMuE3CD_{R6N$FX>%> zC70x3uZraqDaH@Ky5NrMUC!``z63~g(y#z58ZE&L**6$AuvhCB8u};R(|K{{_eHhs zGw(A?i%dhgzxJm0mf9g08Ij$2qMyx*ptL|=QS9j4et43?^*d^I955raUv`s<1 z|0^0$H;u>>T?)DxvQX;b8HYIn5Rh&$bANPJLDRjHJaGr?5JOa$-fj+Xe@}L+W9g~1 zh9AZCn8@OmkHI%THHew;Te)3F;fw?{lmr}CLfIK^GhD` zw(Ju1FaSQnBp$J>>V9eXBAK>t)AS#?Vk~z(dG3FAj#AUwY2qXyZ-Kf*Ilr02_Id@I zR*93r^^LCWZ7Uq8`l}k^;(#(qCib;YXIt!U1vKsgqh`I;oP8}W{dl`G#v7b@RtGx#5X`^6z{uRl-X;u_nLk&| zn0ssd%qzYaE3Aiy%|Ab-+rOX|?g+woFTFk8AQ$G3?3U$B{b>Q^4Wu|KKCI!Oe}n9I zBdMC^+41Qj9|~^oRB>5lq=BVP5YgJ0d1KPme4!i4+clV&YMC>}z3t9UmNM`R`qcGb z+XapU&`rHc_Z%vcay;hyT#VB#1w(o%dywKA<$Lr5K3ra#v~y-3WUeLky7dzc8~ zkRUfc{!b6`Yk#ijk%_)3uG!#KqZ*+F_hR5$qwVowu&V;Ah|FbR^^=9i{74WuS&JLc zP(7^;lz&toEI{3Ys_XuFn^O@6xMsM7Ka=d|1{oY+F_ zQU<~|x!eQkGwI@&SKl2a_FXl`&DW9*m zReiy`)q5pN&N0YW(?p)@2)|GrGIlPcqJoTNcARrPo!IvtBRV(s>}AeMxk(~IrAVHVYZ-7gK`2_9jT8FoyMG7l!^Z$? zMwu~Lor|tNt@f*ZkLLo#BGf6n9@_4E!Wt^7k5*yynEC!E;7WDw>fnvmGxC_yTImMq zgaiuK48*+*vxs|x$YY31m5)(3nhm6LFr*@N7GSSkjS7_pSxxsy856fg{+3-L?_SNQ-BYt0jK;v@9Rb7Bm zSVvh2pS4A$mKs5=SL<$F`6V9|$#!U4iMlp(9mqOR6`;EZCnhH8mlIVi`xL|<8R)Uc zcD-xIu{GbePtnarVr)2*S6 z@PJGkjty~r;P=e2tl71_g&9GQGj;5&RL`Qw*ejQOgu}G5k~?vtfq?&FnNg=$fSlhE z8toUR|Dt-D0z(VpuXe`LPK+>e77%nl_hvL;Gh|GrC4g%kvHqd1lzavtG>^~J(=e0! z`*@6aU-dT|#Vtm_?RoUk{oSMf+FhOPKj6J;yfeljcIYgNfE+)hmC(Ur1%1Jk1m(O0UnfaA)4F;+8>N z4r%F8BV%00a<3*%&yt|wY+znl4YH?hm%JvNFg6mj$Oy}3wMsPXO-H8Q!rGYU8-^K( z>hZ3tHXrlPhMQO2B-lJcphf^4#cN_`HKL?#&ob!oroM|4s`7&b9c5_HNz*7JIdP3+5p|HMKI}4~Z5$ANpx&pH67>3nur=U6s8sO(*SvJcCUq4e2kU8V&BF(W zVtS~vXP>DgVAJScO8oZ|h58fOw-7y_l4D$Zo7Vc64g2-{vy5DRQA43h>jh^-qdYM} zH~1ayce=X`0c3J>>n(|dNvZ}=1Yi_cVE3o!VKs5%nc9S(aK!#omy{>8P5PEW0|cG^ zs|{?#%x2U*S57Nqs>!NVZ3eIbu=HVa-H?DQW2)k9K` z>hkP1vHic%HS;G~!Iv$k%6;$NsT3VST6i^5zGX^{5FcK9DKmtL}6qT?!!A$5XTLMiB@E*S@2%@TufE zCAHzt^K(Ld_rv3FZ{eHpnH&9xeGJW+?4W7LzYh<;ACPU1tPZZ)?)RrSeyNrw?p{V) zMMxtuo9)Q_s~vQ>=)$P49IzzahLXr_eIDb*pBegbAiKcL2CLe|+z_+JENx4|5M{+> z)vU9>#|GT~vFJg{9N@;0QMV@z#;vQd3n@U%X!E<{MB=&^G}oL*1mvhTTl|hO)I~!H z^vTu2eDxtokt31k0T$|Upx0E-(_v9~jjiVC_>ZYN~1UZnb_gOa0N1%AxEdI*_nufvvM+%Z!b`u+;#jq#2wD zk&6U)M$}s9A@M~ckSPz`qG!FdM|{jO^8l3i@=~L9{IkYp2A(fd_poSlw3RqgF;A)Bs;DU*fKeQ5`PjU*_iMT<*Owmu5DD2tQEsq9sX^6Uv;9XVT+ajm(^lD#!`f?{tun{+J5eigG@ zICM+suB5bM&AeD~@Dvk8YZPjlpq0+}6SDiVt93l}Ia(jl3usxv0D?Y;Lxl4hiricn z4)&U>xS^NNr?GhR{o8$mV>S!7?5xnv0`tB85r0M4@s8?_fPF6N)sb&YR^v!r7|_^1 z36!2VFl>nQ|;sGJm* zXBQ6FR?}ex&Z?e6$^i%FX@fsz6mOzYK4v;X zPngN&IG4?EhB53ZIxxa>gz4+}CS@N0^_Bn$3fY9$k^ix9e?Q;s823+ucek3?ckkaj z$cRJ|Dg8A0i%G5Bd*zj)vt<)P?}!eZIOBOoJv=5c+L@7owvhPNRmp0F|> zthif*b!}GbUtEoA3D9wfyQkrh(@%ht#7R@r-g0eiwRyF*onXY@awudO?k|!Diph@3 zV?7po-HjPkpDesBH8CdG=6pM>fFUdF9a!MBxk$MIESnEzPl`u&IuUU(aH)lwF8=x00l!5yV0vaP;Z1+Q zNdpLZe!fnua)sJJII^}GLWhsE@ClxRL`N9r7POIoy#fgo(~{P4e8QdHRo~Ptc9S#e z#%X1iZ2pygC$O6xbYvU?+O*cpK^!@woFD_sECJ>pRqQ;#~?yZ<(P7l}%)xi=O`_@FRPv z2$9p*rfY4|-$j2B0VNk7jE6GGslP3&69Ml^Ce>cf<#C*iYIAs%-1wl%Glx4eyW@1I z+o=2<6ZCyQ^SvnGVDu}?K}}$;D2Z_-RpEoIHz6waB9LH%{&bkAKG4~j72_4Aw5Ol< zH549WFfytsx_5eRw{Xi?@V9L7`?9Ns4H=mY@U>tIMm^4yJAMJX8E~{f0R$zEExH+^ zIG8(=YiQO4e=CTRh|}-4RV6XfZMoFZUr$ozb5wSd-LDFn=wP#Ye05z7w579xl2etZ zkcIWDE(D)+=oBUV*nce!OMBIH4IF0h4G?{mSK!w7aLvM24U(F-_AECS1ab zIGz1J*v-$W1}aR=lMYaUxw!4&QGRe7VbcouiOY@|n1fWKFArWP)%5~8!pC*zRV+5@ z>+c|x(R;IRe02cPJ_6KJC|uPE`jfTE7*KlUNgzb|CgdU7FvES7xM(5*AJ`MekGb+V zC%7Y6L{=DAEiHYJg)Z-#f^^&s#|3yjJ)MFASK$in$eheDwV=FOqmF&APA0-}vrtf; zaB}Oti4dMrgp4V9)`@wfVb9{cr40Nk20Fs|)D%H{(6 z5Tv*ANmG7bPy;k+DH1ouYR$DwdS&VKwIAwq8Ezz^n~f!gU4J?~U7@-q&K)VS%DOKr+4VJS%olV0!)@y_X(xT=w2N*U`w|bc?8nOJCbK`I*Ba+EqS8?fx1$|e zSKl*{2}?W>Zm`kVfz%*^p2#ka|BGUIUqT4}?wI5)6H_3jDapbCji*}&6l!a3-hh~g zSf?=Bnm$Tvn1oTW1J*EgT&nZPV0vYc2lC+UfxH2gG9;|mJNr~1N!}RhESsuo;@;?) zZm5g>t*$wShvzonSm!;)Qp#YoB|759A!^VR?~)J^pA?aF`!5u3RrYvrq?F9gvXLIq z1PeC-+*=V<5<7oMkK;7+1KU|<0AVa~Yav{I;i*VOuaioM+VK5EboUufXc}Q13xG*X z;|qUTX-XYWMIT?LsD-^C@>rl$5`d(6PQ+6q3#8_- z&r6Z;7gSfczHu6xaGbtYqx7dI4FhOjRfHuqb;35tcdSd~7oi}4`*LVAzc1F=s)Sk` zJYZie`c-*HtH2}Dj=;(*g$CQzUBB5kUFk~G>`{sUFF?@0pRBzJlywB7%$MCaDY$c3 z%|WVAD%awGZwB)f8twjxBK0R7)YFA|W+2qp@W8-K0J_zrav#kBE%8SZYh6)dd#Yb@;8jV zIlUR$?|M_86dK0@#y?enU5Js!tZ{%xLd4k6KXq-}F5h}A?PToS3j2{u^@Cz}6CQA#(Q znaP>bo%BoInBHTUslr^#0|AZNq6T9apy`TP4ER3I$Z?wru%~K-f~;@yE!h+i=XvtC zC2P-lsUzv3HVsK$$60auxqRQBGwdvSS?EIeWvF6=l`*aSP<3J7z@*YgQ;n4sZC?J4 zva<~mGFhR&(Kbz3RYVZ?U%o>+A?UjfjTIz0z}}lFmh|mF5`5d}IsIlC1>#4~YirjO z+BhzOO2q?o@UbX)WRwbh@(zvr2<|^HtU`mrEDF^{^PUA6;vngr^$gU~PZ(z! zaisbK<(kjP{k{3gA$B+jjCRg0{qmsx-;aeO+5gfOu~Y?HyKxYhI00o`sW`MQ6B#Vk zB5HdGAw=X)GokPMJ2m@ynVd7RODP}in90&Nvt(FFNBdHSuS>Cu830g{tL{}J)Ig+HFPy6J_$m#Ax7QdhxQ%cIRa;K>^(HrsIsb zTb?$|ie2{s3)<7$kMQJWQ*@lAKa$~}nTHBEsfVG5aZE?P;Iai*#LE%F5BeX(e>upC zt_;*q6b?iS@~k*1=$&|?jXe4GK3(vt3;b=ex6FFo)n51zZ;>@IIg+h=2(zPqxJpn6#S3i3)PAcMsysNI)*eI(|39=uh%374vF= zYv%OLWS`vYus8JFfkaKDrTd?)r!QOn}Ip}{0Z+Zfn0+(!2xt{MEu7HRMTkA zfQbAb6TQ||hx3c9Hqa-i^AwWM6EI&*6tC#TP`BFB1)$#=HDn)??_o2OS56^;xU)W#VFUpH#dA|1LTO2)f_8&0M%u7jBc5MX3n58?c925? zdOs~MQ)($1=}sw7LXZ`!Ok@JO-fq=+pKD`1wd6R9_xkj|y~RL8(`gQOjJ-@8EAC+f z@I$Nrg;M;JH)DCV9{=`!h&#Tr1ylJHw_S5#m166LNYjXx>HemaLcXYmBKb+Rgz~>8u@UCs@rMH1 zSw-7^8a=mLXN6Y+TXSbl#J6gPBqi)3PVHa-t8RF%QM90*t7%A0YuCCW4p{IOVkj$^ zT!q}m{we^34uJXs*_%F^Z96%UCzrNX}#AHp+60_8y-Q3en2nWA4YswknN*OBl zA!Tp7`iVrjvZeI`X=;?|SO&-6U)Avf0GaK7HNaR+C^9$@pRA(Ka~COVC2f08VK$$K zdLmw7BGhaty1OsU`u40L2+q7<$B2rAD?hrC){P$qzerF}PH#utV8Pv>V|nVTs2{1# zgc*DyLH4BF7C1B1{!nMNp9NS)ZB6Vs$#O}kg=D-7Hk4QY`>_ZxB35{DXAB!q8-T5G$zziymX`@nW}L^3h5SvbF7N#OCTeAa`??j8fXl(F#%Uqx#kriDhDiO z018qS=A*Da7#~gCl=`7G;+mGBlb1T>{B0&f6e;-B(#W36*P(&f#CUjLd0NKasN1$R9B@EvqoqI;i1dNdoAZmM|kvu@q5LY4-a zjIGXCC@|HK;HKT>{J&%E6Go_5yL*+QcykGeC7riHO?0p_zU-Y!Qd_j^kE8#O<>OU%mp`{OUK7DeoQ! zJ{a_eFy~?!zr}$gsY$L;_;vu$t>CZScDHM*-}yFZaa_J<_vyS+bfWH2puDM(AIT+7 zX=)xud?_IrEFTwu^F<^XNMVOf$7zSUF1BM+RMiwzh7Qi+R{Hd1FBtH1D9C#)o3#h% z;%$T#O@R50h~{Rbb^5s=osJyma3qr=3BNZdb+#x}Ok`ru(NSfpo`>0Aw{$utNRIz3 zku0b3anbvxm($hMiSh<{7VsVNiKaD6{emK5{rjAXg|;W!2mCxzSbT3{! zNjdh6X1_LzrK(}busRwj&DK)GFqfOUMLnYbM|s#{lBBO18}IRw93{KYv)9x0u*p=? z>1A~DhmrgJiRep=#qoO~^14Dc5(=$mYc6-m#)yr_x;mpMJGJ@-P-b-N?1d@nIa3<9 zw~pT3at!$Wp|GOYYP0Z}f6#&N>A_S&VsY)5CYru7I`XD(S5jZ!5)+I>*7F^Ol)kQF z=~;)S70ZZhzF~Q;3f*7z?+3RaVr>M`ih-AmTaZMz2JJsxt5REf`c;5*!@!{k;Qov4 zo}bQvE?3$!e<@XCX%!F@1+ zI-&dL?R86F`1+zW_-Z*6`kSj`7R2=sQy*}WQwaPsp09-LI&Y{tjSecMkRRwx$SkXr zm8+**2#2DJH=)%5scW>P?zuT*KnZecolYLnyUk45u+L$>uOA@v-CK15TtiEcgzkN* zDqpQ16htx(yfb2UKlL;L4Y?)k<&A{A+SFj|+p;qKIRJLO3(F)#&fq;efeYU!PH|%< z0zq3IC@=9-ghKuS!k^i5_<$yx)J8cPr?vy11+7@^)QrKkSDidaHk{L)TpQ&{*okYv z{7G2BYcLGohcS;j6;m-q-I{=y%ENKhfSi(H3A^Vd=w$vF=u2 zS1!`?3T!LX0P8lA97VVB2w?KK2SIMpJ|LI)9ezR9jw^^T0$ToDpV@AsxhM*nv_Vdy zkEL7W*(u7Px&;)7c0{Gkh`U6eJB)j>r2aa0j$Pk{OsLCx(K+IeZ2g&QV&H_uYQxiNWZK}kR@ZB-gsGD^N+ue zgVX`cZ7^A7A08xgLN_JJRw`HKJyq0`~OAzB3i6D-zNRRXCO7 z-52NVE8eqhiRQJCdMpX;dSWwQ?xBpt>j=&KxqprQDJa#3Jc%DvB`lFzfS=+6xS%4| zmMs;*ep3JVN>73707Rfsk;Yf_7;wxMaD3OhqWO3ywq#m8(eOfuxieAs! zP`w6V1snwg$lXcDIM_Isx$71bZ?*2|TK-kb%bC+9{{fyi8EfqZ%h%P&-^28H2kI1| zCdBE71eW0dA#iz<+J1xt^w}@Q05hAwYJm7vhf^Z^XMhS#=a~paP(9|epOp4gu>iHI zwsRKuk=3+#V3KGwX!)dr@Gmx}B}ojp;J0Iz4|s@B5q~gz?0-KAX+CHs$HTU(!2!1g z@Jq{LmthPM=7X<|g zWQoCb@58@p0ly0)Uj(g`>S93W0F;~bA?kdUMQzt);tmq>+m2toFIMdI_b+_vYs2P^ z6H2W`FUs-i=6%9u(cmAE`KMY2ESFXf#}(A$Qk)%n1VW>0G^+nwb8(#&;1e~G(?uBr zbW{RC++78#(zqX@R^ikf^sME88&Yq<-;iIW1;l!^I9`A9MMA;JfkT8#$(qJsb2lI^ zqtaw-nqliQp7?!AO!rJLF0 z9b}W1tWF|vO@M~Yl<>xZIbjn8hnnn$Q+m6;&oWw?2EnWBiVz2c!~odbYGOB~R8X$> zbX=asNk`r>=}8!UOzOj796GdCLu(ck@fb6bq#r4YhCc7AMU3A{Dca=5DdgT#mw2Ym z!tbfQqZrr6le{J+Re7Fd%euqVaA(vJ8uab@bM_74qqEcY>@@R(-DIk;RH;3fQgxH7 zJ~WraAuXqJJVdr-2g(_lmj0;rae8HbH_Yeu>GIB+w0)F3IR-}?kE5kBAUf{^`JLA= z)z30qWRETL3gJp2AoRmiVeWM-iOgt=s*Mz-5c#^2ns(uWz-pP&#u{~vx0!2_qxzUH zPb&r|=Csj5ciRC0_xiuq+%??bG#bm+x57`YiUVubMkV-~NwN5}~~PYPzbAl9PF#n)cc@+3nY2?@u`Yh3F%;|APGsNS#8A(}h2 zps9Dd2=sNJ&Ta$!tJW(=ha|@M`doDny&u-n;5myB7k!y_KpyTN<;Oe!c_b^m*don0 zU5g@o@LMGd%ob2`Y4!VVu9heL&CNV;iP&`M-6@;G!2#VMQDcjW-M(n@^jGrU1f;fk zc@$r5#8Sldtjz#fLz9BxO*5_wr!yST#kBk;#3w}481yY<_b&m}Wy2Akt5jXQ{_q|JwBWtDBd2TFO&b|eQx*F(U_pJ0sAx zde&F;_+-F|GmnU6MK`->VDXuzNu}}JGUT!jL65YJIhO>um$ulayl-hzh_ZvG&vtfK zsuI3DUz)P}m+6j!rTjIG7Ru0ts9FB<(zl65eapAIxbzR_yJe;qfX0a~*ok^u3odc) zKU|%d_tHaiWW;zO27wnvdHkmvBt_fPAI;Q~)H09&d2W8!^VSOY$V7|Ng1Gdw`vxiY9+A5K^AHkjgK>T zh7Y!;8V9%4W+H-52hN05Nq{tJq^@01r>)taZQ$oF_vT6IK9QQ}QGJm%E!#K4wnh{A|o}Mj!LMjvefXw*m zRSe`70fZGIbkPt;Z_(X|w>BEX&e^QNHqjf75Ng(?G?+WP1bMP*Z zwm-laQX*`7)w_Wr0JKwhXP(O8mf-U2A-@+k+QTCA!%O;VQ(wrfYR)QilFO?r?T7G8 zO8eU!nUK1SH4?5e$7N1L8`DvN1!2$RZnYci_9=TPZD!K-hX6fEcs?T$Hsl9i@>0Im z7C@#m9t=2*s(qdp+G0gy?u9;XCq1)NJ!x?&*7Q;h#~rWJbdT;_s1FB_-H?Dm&m8_7 zJOE^D<_#lcP2W1i?n2?G%<}&2d3~$-6Sjy7v^*MaZt5x#668L&Mlo4kVfTGcu3>x6 z`1-^pt0Qz}jvHUd7wZ<<^2|P)R2PUhda-3}8Mc1Z-z;2eQ5W8tukinDO0z`>Ae1WI zIz3T)PnZ##FOH5$6>aImVT6&EKX(+~d6EqF962%C0w0#=Xtdw{SnZnDuq*+25Hy?# zvdS_{ywFAq_UizWAMwB;VG+l=Q33by{_)Dc)`yrV*ZDf-sqm@x{5bw;svAjFR&QsH zFj!t4EU6sGEF>wzEqxMpQ82j3#mCsvcZddF@FRWsQZA~;G1bfe>h12P5Z6aZuFP%2 zjM%Ep1G%l1Jm84wm8IZ>OJxKJ84p^803UUy|Eh<4i;io+wc!}jEW;8RUXNg3O}ZQ) zOjc0TA>YsrnS1Af=d>;Bb^EAMDW<>O9eJke5V^@*DeGMoZx5x2 zMn93j#$cW3F)eRJPlO^$cGzV>7a{t~SmAX8VTk8{dP-wIYon~(T5_u-K{ea)qX8&< z8b|c;r{~H;?CpBOL*g~L%!3V0nu{SB;_ch7YBC#1vfgjOl<%b?r$e6g+FYbe*!nD=EbIkE38V-0O8%DiFbOb8X0lk24NZUzuM1dT?RP#Py}+V&gi zH|Et{%LW$WtH1lqym(HS%)CfsS@;H)WnXH9R})m0p9?oZm%Oz0qaq}iS=OHj z0LtsWt4WAXwX+*MC{lpz>$gP2#q7uRjzMgpKE#_F#YO*E>8hi!0kr$V0w*vCU#HE-kE1FR7xZNyR&%H4{9vM@ZtSP;@##xz2%D~9d*}D| zDMFcW*{!qVorync`&_q5W54^)VOn~b)0c@yoS|A+b65TTKt{@u*N_m$N+ww!T!FAo z@==o$d2d;7E^-p%&}L@d&1!FbDQ%o_PFS6x?N-w^?wV;*4NW{I+PL~!A%F`E3*SID z>uEj9*2X_CvQSm*lRFSfo|tJ`ZC9c^e9*&sP(x1TwOJzMB$zY03w%7daJKSY_Wy;A ziM605VF!qdaSn7=K1vEjJ)9g2>LgEeFbEtCfwV&l zh|Wp{uRe7H@s!Wv_O|i_nO!ew88Wt*dm0_hi5cx1;x$s5Zhx5s%oytJ3U%($M5}iR z0^|hGEs@vgWLS5UM>}x-wfwTp%SY7{eye4Ma$kI@Gt_Rg81VxHb*&%QiW630SYh6M znyS2-8;MJMIe zR9&y$wN%)`jlPfy9SV88;Ihh}JM7tTwSsT0t5^z+p8~amIjmOS`-D)P+{O&d@RP*` z`Yz}?x^Hmp2KOlL$tUFq&2H*c&I_f7Yy`Av5Ou5c#&`caP^P3Cxbx0TJyr-f(Q8Wn zc=4KgbZ+fSp(+Ovpz`!Q=_Cno`(Niml&rW8)X zteX1fx0)??x0WzTPPYQEd`x2KS|qg@;7)4J2}M{%I_Pa>w2UxsdHGZr`xd$hcJllU!HSRR;1&+@WIjo}L#(JUj^BMj-Y#~Fw zVdgq>-V-Gd7G6+|CDyV-ST7^w;1sn@2C&sY$+Hx1;hz^rL90`h1gLd9}_Ov`-`EtMG;U z;PfbKljH<}UAyRDHGKFYEyho@@10Jx(A6eH#z&+E>tMGmDFb{BE+>OCyU=2k#1__& zB!Hb*a{(Vzzup72W-yT#PkbbzS>U0qDT)sw+B#2u$~dn@Yk7iK?@vNVagIkg#uY;P z>C_D~?I=Apjq-_v^$KW(A~MU_Nz1BaGS;suT9$=Cx7d_zd7LP`WbrF@_!L+3T(BXu z)HR=?D?Vy7bob>e`xn}agJ90r=2#_{X&rPt%Nh@Q|4BJP$Q^F7k~#4C4On+~ub$T8 z+AxscnUfm#yexnkLv4={3xL~1YN-VzN&CkF#xj{_k&Y*i5QURPC7;%#7+!s)lu5w& zs>(KnZ3dps`HsjXEVQKPPvZr3@Vc{2l7Lf$nAW6B3-fOXE2vtiG*xD6)GN!o`L)KT zHLErh+wm3}&Ag3ao*TPIe8(;6rd4;1lXriq|3%`xw$(&a1`uj0?zw%U+?}mqQf1Jh z!!E`o7J<(v^caKpCiMJXv}f+SKwJ>rsin}jKS~fuV@|`IIiXdS$1frpU4|*0Yr}cl z^z%_Y=hpFa?N|o&and+6ig!$3hq5CA7rbOJ@KT_ftt7z59TpFelYJoo1$66`HxBhF zB(J~7lFAaH`Jtx4KZE7w_k52YLq8Ax7Q=!YR3d9jeb@EhE$j#&AF9fScs=eO$ zE#%3;=J!@a0No93%>WxhjlP&*+v-DVQ8$M6tleW`2>{$GHNW3OJZ<=bG!4y9?9IFq zJ>ISRD(p18&rgPmP2X?Qf3o*N2rjsP4 zqoCz)rBR5x9EoAhO{cleM_LcjD#a;)85#*@&JPC7 zG&*daHa%w=;~y*mB0I|mL*)E$SZ#7=-YP22mV)>^yX`ng33E9NYNrGhG5hQF4;edW z_34vb=(BE8et62+)I9ATK`HY?r>ckhEpW{#GjW17f*~epZD+OhH~!NCET6&Y@=CSd z2n>kF#9u?Qeui0#_S+s#ZS6Fz2jU+@+prv z@n!*sgajK3YL5EpUO5nTZ4oJC>H^M4+L3m%$!^{@#$TP!QnN;!(hQ8cAN%sS42RU7 z3@9QtkWRuUEiXSm^RHvOfL(`(iujB9YO!`V(+XRKAco$Fntko$W<_e2fct5OJKF2p z*8R~&zP#U2e__+umldC>@6oPT8gGEr7?96+!`r+>UoJrGj*&P)%2Q2kd@$|3IKSH_ zDDB!QpD>x`$#e1;=Zy;t3=W=1wYQB=e^B}Q151li03te{O9&CJP{f;V; zC}ws80iI@)*d^;d15vTcLuARj6=M#+DSd^7DyU-XAig z(?P?%EdPmtZjl?x=r?wqX``3}rl3Y**?lM6JxtFG{N?*)B|*&YHiBSH*ZsP=z3Q)7 zwb=WKcQ&(YuepT(+ic=d=MHN&U7U+BynAYgkRW)wTfNa`nvYjUUc4AU9h6?E|4q^J zU{p^s>}?l~<<%XL9ZQ5&l=1#30zXW=Ywi_IxNWkF#p->$Mq~+Y#?ppV=#4o*=`vSj z`zD#zhJ7qK^%h=Zz={wYvwa`m*o0mquyTbp6v$^Zr@`$KYQxU$Nx*m(3qv~tBqq>K z1VdX(sd)*~XaZqU09qRs9)hmHYm`Lbm5rL07wMX=77sEiJ5DS>r~K?*55WiOb2z0C zniARlNklfh@v^jD_NhhG<_cqkX8=du;FrmL3uB?y;T@8abv4 z1+?C&u{{3S8uQK2g}cNiR)BG)=lE6gVZN9=+?6ft7`*SR1iVPa`b1;?%`U?s1agkn z`FxNFJ3+A&0q)&incj_E897i(@di<&))Xr{IaFJ`B7r3diZ&{Ub%r%k@wuA?w5Q6Y zcNR51y{H9*W|0$S!8F#9)qbIL2CBRY)f(HUFV&MLX$p~2I@J$Tk-v74SGF~Uu8Mg8 z2k=AIQZ-CM1c)Wn7wVldGpRRoSQF>FjrGEL&L8ET`DLeiEFaR=L)chCQmoqh)(vNd zrne7Ix6bPZ#ScIF)oNvXJqHQnF}NXz5qGi!;{s=h4%&hfpu`&+F@DN3<5Drv(zK=3&6k9y@3121c3w`j1Wb{R|~#eV!>OWz`;%xpp)&`M+i}?u6OcEHY+b z!v90XQDppir^e%eI*io$#_}#8D_GjmpJfwpVvL)uN(@$U&_A#B9%np z{CI9lB)Nda$brG-R*KMkAMhq5QH^}niMz3~T1}W%0}1NXQjR&GD+wGqCzOV9Ldzti)^gv@vbhJK3~em??H>16vC#WqJ2Kxdn_Y&{wCxxkyI{u znIP}>=b|h4;8*+iKe`iY&QU3{$^LS zFfUHc>6Ww5nU*Zp53m7Z=%S>{XLm02F~P;egj1(GbgX%(Psu0ykWMlyOPASzQ1P9* zo`yS(sR4d%p7AARuBJFNF30*2ocps0!81mZz=kGID7OVsFT>tyz8E#pGA_0>BC;^# zU1lF};pbpFVc0a>Fz5AEH)IdsP!2^-7>x4iE~EC#Gt+wS05)rLGeYD7AFmqO=2OLo z8XSt#MsPX~VrT6JVb_i~hwS3QQn0M}>pOJ&By5mR%26S&rzy6Gb9FgFDX-9jEliEZ z(Ik>q5s`W%%sKXSUFI-$kIy1aQ79?kH(`T?mg<*wQmD;`%gpU_Z@{^So&4evZPU1Y zl~H8ovZ-w`pLJY(^@bS5HmXMPbw~FmDZRr>%A$%0Rn?tqqR5g6>}={62WsEVp1)%d zzRZ1$Q4m7k7P~!*8{Jkz)dZ&LyhJ--AbM=hQ!~@*J1;^)uxc}S(Q_O8_N>>M0FzWA zrv&HwWJHpkhDzFwXfJ>lt`X1mH~@b${ZQ3_wj^>AY&&ck_+U+@THvo{OPu|>-6IIA zd~3t0MXxpQ&Lg(FlZ4o1mHlKVYT3$Zv!EW{RT>@b{wnLRPeKd%J?*mS@D@~Ue~ImG zy~}L~Z}!9Xt7NJqeNf^1YKuR*62Gxh}yzE(K&BWIH+)`3ko-UJ(t8j8!%>QekfaEr}; z*XdvN8Qx)HR^U&?*1DgvV!=375Lw$fe=H?@>=rL<+;ITfi!y?5Y5d3p*xe-1AqrI=FHt8bLb`k|M zc3&+Fa-fB#=%Itfw$`*z1=%|g^RP>LCZAV;sc`8n`d8Io5ka_xND4+}L^AL|P<-iK z;+S+HQ2daVj@Mhu#W?4&2m8Xn0@t;YlTfjS)os$lU~@TE8O#dhP3ZT3V?$D93;#Nx zt0a0Zz9441&|klaV`2M_G$Vz4y(DU`Z$ze{^#n397;%gblBk>D7RcQ^h+}q~#}8 zh#%wAwBRJi<`?)}Fec<(?>2?cu79dUy+@!Il@DlM49nNxD?*LWCx+FpfN-dYmeD7| zYZ|PZQWjLOQSzm(?VtDfy`>_Fs-IP7Ht|PwSj5CJ!ipqVL&=FBO8YyZ^3q$!1z%w{ zHp9vwiK;Snbrc&X~ zZ#hwSR~#?D<9&rIY6Swf#V5d(>mz4JC(Ncu$=njs*JLN*F;rh@M!mVwn6FK}A-Ve5 zfv6Sr9|UbxK8jjyY{Y7z-OT!|iorbYT+`Gle@jRlShldj9zA7xE@`eN*3|)IZb;i{ z{dwDX+7U*WgfL!GxtN z;w~vadM@g&y|!utdZ-wEQ@vtT7Ucs)JzHmjaF)Jdws_56j+LM&03!?XY{}(thi}Qu z9fG6iyw<3R+sQ4bGZ4Kg5YfIk%A3~m9IBQu#}HzogXfS#$0T;au~hlNICsJY3@E#6 zzI~9qKmzZsz&n&+7-U*oFA45xso5it?zQ2^3OH>=xO6*IoHPecOR@u9>YAed!_#l% zPzKQNA>Pi$nRUq{{;ue!6L%?q5had84VW-SSOFv7P8D=ok>wUc>%6V_%m+FX62P09 zGwd^`${!2dSvwyB=dlr`UX{-nV~>IJjI0hiM4N1?iNUQ-H1k*KbYy|YJb}0o^5(It z*aDHH8W6Zs+Nk7Y^_Z^2q|RiDr-B#r6}ks}Kiicqf*6}(wz5h`E3ZB}aK_*igGn_< zvKjUr`H5t=TP6%ep#*y8zbR2sauSeeLi2_Z?48K^lbRY;I&^xI3$+`vuloLdcIv|~ z)s5}x)t8$GuWXUmZ0Hx)VT!wx1EpBBOYcn@=LFwCGqHL8BJM|!f8xt zMDEL6c8RQ|lF(moMRc^1$WZxt5ZQ(GV#G}g`a*id|E+w{>3$x74dGG3LekoP#uVFDpD2?J5nE4dHqlf{ z=iMjKIk#dyt}6XC!o4>4ZrH$3V{VAFMmnymbjD~+C{H*FOQHxj5OAUB?g~EHn@Jkj zm75eKAdTo|>`21FMP@1WJN-pZFLGno&bk}`zd8()(+a#T?y25-*GOo>J3M-L{t8P} zbf$0n90b9jEOv>V%4VP~YN7X5FBz^+X(pw*_;OdsS1wnWV|qs5Oi+QV&Vw%SjEr%A z67FJ-UpU~CY=og?S32)Kf`fzXkw0CUw$laDnId|ON5)W_Vc7iCKV2ycs3S6Uo%?O~ zKpA2-^_CY!mQ22hzYHy+r8SC?xD$x7{-|68O7&!4)s)U*J5&N2pm+xabYS;-if= zFb^3gXn>yfmD&+DbSex$2y0d?AB^*-KQ@sk{(`2i++71T_iX&?6JTOGGFa!OZ3F_4 zXd309Mb&yTkr%Sg%A&PA7y&Q!@L3^fFHlPKM?z0fYI1UksmdmRLGe&so$%MIxKvPT zi&yOH14y>jnh$J}kg04Y&{jD&@5+ziC%b&Zs=gzDeI1t9VUlw4DZTu#%ewXK%ia*Z z&j@U12SQ8?6M0Ym5cyLl9uw4XBa$;OW8=jeDkQw+s-Cm9h~s-O^VYuMRniiTPfrZ%}*V#u8MN zxe*iyIn|Xkb;9Vr$Q3%Pav(w`LK!xM?Arxf9bBupdkwN|H97K6hl^m6s-v}Vs1-~q zfEqB6#dwe?az!)5bn1U9RtI1c+AKvSj%i3GZAWX}@SQcPi{2`x;eh$qkHV6MgID3~ z2zW#}dt5Ufu;cW>yJ6K6Bo2N{&5dQ$M^vXVn|2q2g@CA+&b82DTdO9-__6w!5ky^i zrKn!>D_WoIG6S;&%S-ZAgQdY~)}6gXI;E7)_&`1%CvOEBSkTbOaL6dZvQ$nrY-6wP-#?rCT1v4F49@y%*jZtQ&^m&vyG~|Tic2C1NRE@5Zk8~d(BB=%UWUC zqH>sf;^>&23<6yVrWeayqP^h6_Zn=lB}ePe=7|f$&3e7^Dr-u8OVD5spGxV!-G+BR zO36K`G6??s`g)JMv5`rimUURSDXFh=ne-d7fWG$-SZ5A?!Dx*6zRE(aNZvw zt&TbHirbWd5#>?rCjj~CtEXAu_vhuB`4B$!otJoGHCS$A9Cdr27q^d zvq;zqcNfg@D;Gqjk0$TQ)YmE&IlDQbpIf695AT*)FFpOLbF@^LBf8aGLrD4s5=ZOg zK7=a56Wn+|fj{1DRc8lf?1`XdKks?A*d|?mK~W_9l-xY2PBi0JAYS@9Z!SfQ2(F`u zen;SS>qhFufH}e05W%o(h(_dN>{-eR;PK=Cf<0VoKo1KC1sm)R%b=zJvw>PbJYO3$ zZq}t0=5@AUUvhBnT|*$ae<(c~f;Q%P19@OTdI&y7Mxp{(A?tPWyvB0+bSh#gdjxu} z#4b!+?_g!YD2(0I+b5*$bCe#)SiE}f_}lQH0U1H%Vsj?f3RBT(xN0*DGkh8O%_SSy$XMTVEaC99fWDF@oy(wSkqhw+`uR|I zP3o*0(<$Tp81k*=CoPLM`Z-mor?K+#sU#yuXQcqX?C{lkngrl;N2gcZ{{@>biCrj2 zSdkPJ+^4{uzDH1+`V4>|Y)YpX&j2+$iI!ta92Erbe~N37r-W)wAa1I0J_p0%VX3 zGN^(4B>G+yG4U-$Ai9sT6_XAdqq>@K!&t6F6b!G^BC3F72(?gy9f*d6taF1+6eV~` zn+TpOAKmVaWTzu~a-8`|>W-%dLD!tObca?+KiZS+fJt}ZN7bl=AEyw0V<|8*hIJgs z&6kf5r-oE51g9|97X-bccxvXixIFz<6MaE%UzABMU09+MR|`yq*tFSZ)GRVgR&MK# zHP6EPJ+u=kM7w0?Toc$Nt>&%XLUszc=t59>6R%PA*MdHLi)W8Jy0N~tQ!qHH1R>Mgz;lf4k7}-qcf}Z1%%E$kxMX|}uM+PpvMuzyRo67} zMIA%{hY9vibY(EJDBXN3hVOsw`wl!1lRJnH^&9XK$G#!&BT!g`+Cyte? z)9vwn&z7KsflwAl!Per>VT)$oa{Yp77Jsh7xE zrNde(gfrVUfm6YHidUQtyVKk$Gn*apB)Gwc2*Sc|?9AF&vUu?Y@Wa;A%?59UyjQV4;F}iZtlo*>s zLM3cJvMSMwZuLdh0rA-BkE8I`zy(+Z2z2B54F7%Wwd^Zzz`R{M(3~GN6VQKDsRWjp zo|FuViN5*Ec6FVbhj*wGvhrn8|6PAaG__Ut-OcLgiyCkDFLl{2r=RH=zO%feqpDIUNW23l`oZ8`bs4RX1~Z*nUFz<>rh}=`XM0Pf<{uP8pvx-oGDAJ# zaGx97W#ksNr1=um=_hnVF~y3_YwGL_2hrug61Gbtssf^zF0M}^(=GR}gvX%f3mS%N zcR#-wSfwsAz7~vC*JZhtYbjU$FjE0%>;^BA+>O&Kw%lI;H9*S0TG$;(_sg=^pX^|i zRTmc&fetKN%lw|m_`NH1Cdb_Bsha-A?g04i7?}WAeQ<5@`?bSLSObnWZelCY=DB7r zWisPG)b|VY=Zj*2HuSpK290`)Mr%-%z;vXe>^+vcm8(A$Ky;eJ#I>Y=I0J{kVYzkQ zNNC;^D7?qV*3b9L$>9P|4j7y(=nxoRBg7V(yoFO|9!_INV7C5SBw|Ej*BNNBw2rxU z>X^7-Wcwo2mfFT1%oxVvAXI8c^{@dLqA+Cw zA2TKh3(Fm_u*+bW!8AAH()5h6G}tKTVzQk{-0dqa+q~QDR5Wtr5IK-PAH0@Vv`Rh` z&DM6lx!zg@gVPiX^zDf!-}75U^V6EWn9gG}9cU(H#QQ2CKw+jW&&nfovO>ze$X7;m7f;;neY+X1dLo^8VwCJcbNuIbCwuHV9JZ@LVvd&H zI0Uw5?JVhvvG#?8mOcOcATR*AmPG=k)*q!D*EEB7I0vZYlzSIL4J>7TCuBN|dqG&* zFW!fZuxDX4YGW0`?$}#8*OWXm#x&0eMJTWT7Y_yK7d8j)8!~p}uLOA0iYkhwqfaV z-Cap-*f)dvmPh5lK?ORmX+@}YVGX+7>8n+v9*MepX9SA0AkYI9=VD(+F{B*)hthl4 z7q>rCrU4npoT?Cwd}h4s7pp81j+hjQOo{T98djr^x-eHQ2Ldl=*q3yQg4M+-`&EnViIcb}?0Obke#x5G8Oxjvpk3fn^lnBiv z{oD;L+=|^5?-2t?KL6Q5SGbdPpF*%^q`NeBN1+k!zya7MZ|41xYpe}71!U4Pu!h)y zzB@e1vs(9D!|SLri`&ScybZNiNA3hwYqhQ0jDzz-8&;XrUs6lpvO;nCN?(l_OsB@` z>?Pl-kfl5kpS&kaaRwEaz|OrDKvJL>Yg57JSfhU5eMmdxekM`q?*erPenmP3b*#*MryUGIUs5S8K}Omuntd*a$?P9t~n%_v>9u%fH=!;HwR?G?Oc z`61?D(YIl0UBrmoX-5O;V1uNl7Dz8!RB< zBgFxQ?V1yo{$2L^ok7X@l@#ayz43H!kC`EP_3Ffwv-SrtVOMUdoz6v#c0Q>>S3g}G z_f-&Z4J$#1Q7mQfP*+19%Y${&K7W*BT!1WDb)*bw{)oU_XXIdblxfgC~RVNgK ztps0oW6?@`=a|j9N(Jql6`v_w8g{le_P5eU*{$FE`MH3E$}AZ7Ga{jQrM?AdBU>{^ zr3p#lBh-qACN5JUL_8?7Ubn@J1oF?%Q`H8DTlL=L8A~iR@PT(*hrcw6`%de2NB7t` z0XMrCwt+s?J>Cu9bORX|w~?tT$)VA49}x7HAdz1Ct!Ra|ZHBpRX1zW_)2eR%6?q(>|OlIfkO^T&zSD+0$==VwIqi zrTSn!m6k)nFONhoYu3w5%TEoeJnU%|I{GlfET9%AAN}CN*coANb@7vxf|fHPu(m&~ zXHM9V3l}lk6DVk$gEj=!(wW&21s~N(+KD~J+#8+-D=Ez8VW9XA9@3&<*n{ja_@LIK z1S(I7nO>&yU1VN}{*kq5W3daoPvEKjJ5!9f&{Nx(G96nCV98=I(6cCc4Zc^vpu5n& zG;{Vn>y`7oC$0z~80>^Bp4cJ>k{8IAfC^?@#w^nI#SmB!>5ZZ{VykYC2yW;T?+0f3O67MJ71^Di<5Tn#0Uh2Q}v!E|U<`A(vcG z@&e%t{|S>V0g3)=-%I&)ce^L~;0>v7pJiyqJw3HTwdv_B8%s2LLk{`Ze7>iJqe+fh z1aMDsNvPIta3OB(XlsEVQo$ zP>YKlkTb-37apGEr!TY)Lk2q>cH+ld%LjYwqiS32ejqG}QSPV0Q5_Ufm4dJ26591X z*Gm%w=Rab4p~QoPYQF#UgTxGE3rGKyf_QoBstW{A znaO>4QJRT$7|r)pVP|X~O@WrFZ!?bzP(E%MygPix8cG_gsQnzD6;`QNg(zD`lfBXt zXx~kNDc|7wBPVLZh^ft$ut|i{DtIa3Y<);sGG9qDmg5NFw}&;fHI>Oe0N!8_>p&w) z#=Yk#n?%y515n04jNMJFH8l>OC{bEznn}iQHnYlx8|IKbPV*>AYjI|$pHw&e=g*qd z0{-jP5s#D*Q4MMv^#tR}<+RWFNwz=XXDp1J_?%^NPxF{JFQCa8ue{m^FRvJAaxy-Z~ z?pxY7ewf-u_%Ub#2?J1A(AY2FC5>aMtL(9CDLr$gFr!9Kh;!oSWccG2G;yT3ab|_% zyNkKbuWp_eG(I<6Nf@Ed2$^Js+%_M=H??+)s|y+TD;#JsPS7}Cl?c|hbyT?@jJx== z;I*Nq!pTd6rE*`LDpRcQ{YfM~PtIwiuwr*4-M=Kz9842ict|FETh1l_N+$Uden*}j z(S-*D?f06FdM6lX%V=D++bn*ERH7CwWPq=Z$G~?3DQC@0s(4`(WnLgt18ci`F<=%vdjg1Zts`h-%1~D;SV$Z&o)-9tm4^KOB z7VNp#bu+64kQ7wRK|V=dKYR^q6LBNgqOT#;63()&k9OLQ3s#I!z4;=g7M*v)ky9IM%Vb$#At@~0E?0rT20d!OZXvXL8VnRV(} zQC(>&s5-P0=HfVx$7?;Dv=g8clfc8?b(lM<@Q5{A)`J@ zycY6KGkc|)YC+SZbw7oT65Q#$7|6SsqK19RK3Tn9TO1wsyfzg#xFNTt!Q4*C<^qGE z(74q3qFCoc_FNpis-rKPTY0KxUiLr5B=^Sow4ea^#lG5)&jj57L>C(a=J0k}gWs!| z{*tB@EJjQmA}CHZIR4+d@)Z|WD}7#l(=vDT42;c% zRi36yg0M`yRE&x-ud0#X#@)3c;W_};DDF~1n^$5Rgc*5I&)!qyL&xAoM18ZQz7!}E z+uCEXIH5>{e{}(mV$o{@4%-@;>c_%`5uB7%;xVJ060@tc!(%GPAm3)ha%-y}6AZ^bkMWv{N}aDLh(eeSUJdOrzznu~_UWK5w3!A55sKw<=99LNYZ%vYe~l zDs!MGEqJ$=&#>poV3y=R3nQ1^yy-t8RT`zk>sf~d-lEkwfGpfm^gdu*%?b=;luh8@ z%$)O}nW5+D1ko3oeR*5Sg+VpO@hjGo8({F-4ycPM?x7W7a$FGN3TA5-c!vX;eG~*s zbj&Sb>vGIw=$)E<|7jAkif(Y&P(xA!er_i@Y4QBi$*g>6sP^1n$qu`(mqWR}sl zi^9Fu#z#}2&$1*&k*zMPE@b#aIwHC$%l-)gUta{)pkQpJD0I52Inv6A{kQ_j>mV`e z0kGoIHxBmWJDovE(ZE}8=t_&Y)emMpWkm@yae1^H}+81QyugHcy!p;gi2g!CXXKyOnY-O63Pm`ij-P#SZ6@& z^gJO?lw0;mD#Zcq(XMId#Jq6$-ycXDyUNK`ZT01|Z8`XqD~jEhyzPdcxR#KMoK4|} zopVABvJL`kM=a!Me@T8Uz6G5jNzXe-BOA4~L&Ktjx3JX#*rb`rSU{XcZ522p*Z5j8 zqAMBixoJFs5KzWo{W8CJk7){=6Ff0>gu)FlkdWJYy{dUy?1Dm>MfsKeCry0ID66%5 z;@5zGah6O)jjbBBp@5Dys>5~HV)PZ4MlG4BEf!cjsedZtA1R4>V^WO71m}uehHps4>rR((B_~PbS}%1 zRvP-4xMTpCmzho_*o;LwFMD748wA{m|@h)_iV%F(MsY6JFGYc7|ZHtF4x(?h3EQkrKj_~7tVcbN6 zl*m}Am$~kxN8bDn{{rVLg9|>luPj5bb__6nh9RzYK?N-T;)?)>D!!Xwzr2hp*Rpl6 z@c;6Ni-LQU17)|<9w{1mO$a};G)pII0MhCI~O9QCp8a4<9 zMGc|%ZA=Rvp42G6t^1Q;pgTrzpLqyldE38rn9VyR0E2O4-2Z@Ui_<{bF1gd!Z)2Ed z%RADTZ}uN+SWzK>bsX#nQK8Enu)k_Pqo{LhCm zYB0O*gnvvJBdgG+Wj27;=?0b4dQ=uN#49u&;>5mYU_Xf$LDqZZ*qutHE0gzWP6H;a z0|ArD{wU8`(jtG8;uG){f}Sa;u=lw2wP`4R-1i{HGWc;@Ot#WPO+ylAAiMQc2#*lq z`FdwVsoPWO5edGvU}?~Jadq)xN+#ZOFvUYG)EMN0NUu_bPE}@)w6VM^VckLUbxzL% ztd~df(Yu!k_e;Zv{u=D+g+k9>OjrJ?i`3T^9wJAaO@$`yco%5?I)n!h&(=!0nG<^k z@cL`A@|oO_6BVW{QDc4+SXj^;$?03jlpGew1KGbC2E}usC_;l`A?I?;93wsI^znD@St`k|q6CuFR&-CvczdPnF^I zO5ti{b)W5EB>}Q#9WKms#>FIbgt70MUE}2NGj5}M$F#0GZJ6+)(8&5#Rqex{+chY< zkZ>o|Z$`KQGFOjxiLcfA&mcQa(Adw(_yTQF$;UGkBeA^de&W|EE;-9}UMB<0-jKVd zc?UGjK0_mIiOJmLWiCrl3L1z}UuBL9Am8{Xn6U3QDxQ+KX%tuyN7##Bjvv>z1)FQ} zXv8Kce{v8Rx01%&9iJMkl@Kvr!_E~RpeY(01<@LDqND{Di3J~C7sh&W{&6zWNan9Z zf`JR{E$lgdAPt!LIX-->U6(wU74BjYPDmY)J+~mcMCBkDupCtw`yZ7%iYUh87^8t( z=ZJ{GCDKBKdC4r@;er7hM`-4t0>8WK=R|!+xc~>XS3Aues|XO05)(>-Z$XiOQUXB$ zE|}*rW$>a+Bgb$KGOi1^*Kkf?EjD*M!pKMR-R8y#@>&?cS3p0=j$$#Dk2OR-JxhX6 zu-Z(#gQkY~_fWri__F!1ls6gxl!`@?Us2cwnC^2)pl)VL6i(P6><}>P1Sm7a`D+Uj z715l1UXew@(N6`Dv8(Iz0U-TW<4BiaYW*l+hU%r_6>s5&@EcDzX98o>^v=i2%kSJhuo#m||DIcM)c&t1AuDTC4HaUm zd7AqT$%J}Z_Q$P8{xp=^>LI%zj-ta2vCK+sZa2zmroLWBT2^dwvG1?DhXuoagaQC; zTq@4^&uNx{3+!)LX*k1ZBQAgq{Cf|**>3X!wk^aFZOF4lpR>gc{d63rJFV4x(PD@e zJaWOLP3?&`uV78Ol0Ea8Y+u<$;I7Hfbkf+9Cw6w_v+%jb_{zw0yb0%y2Z*CW_Gv8y z{tWVwqx#68UR2_=6$GXTsZbe)lgv|o3?A!uBSRe>kXPj33gC?;FGI7%8JtLReDgu- zJq<+689%r@L10CjC&Z{dKC5GS=obr`seZb86@BDxkG0n<5VjB@!2yC$5HQGXf&>FL z#%VBlPqH^vtvk&2MzKTk;@hepob6650zs^-1F2tGQhYGvSP@5djABw(2wwPZTVW~dgH8(8R&VkzK+sC+_XbU2_vZC zNWtT48sBYq4uBMDNu$#}9mt};m_I3}$}(9rx}U|5)qSkZXc+~uEJuZ#g)mpg|Dpz1 z+Qpb&nYSilOblJ}A{xMUVJV%yF=1*{Nj}9+hOdWR0a@>-tj4aDVRwVK&Ws<8@>#6c zKX(r4NI=i*38k)-=CxX(Tn>n^t9#cBvRXU z?fq3kzD>g${3x2Ue^NUj7kN7b`!UEe4;}p-P}(Uso|{k@y17j*aLiSvZKzVH{D_IY z6|pGVH8!zcm60$%t~)OfTXG1Z^4dap&X4Fq$FcZa&;lw>pI?2KFBQkDIm#SHhg5Vi zsPO5fOk!oH^p;eFl8 zM1s3D*w*WnK1E-(e2wrS-8k>%jKQ%4(ZE(9c2)H(U=;$y?C1@3v04>%;B`Z-X1N+Z z?K<>SFfG^$ktw8K>ziP#OPi0mlT!FJ+j2U3dwLSLYARyON)IUK+~PX2c%w1a3L@yW zE2^qU^`Cd3$PVE{Q+@v0*#+0!IUTOre7<@D?I&v=vmch#)AVmp#(VB5liu)`R6$l7 zS69$vcLElfA_!7&*`uTR^MNIqD1^%Fk%aLMJAMcb9t3IDgiLn&DQaN|#`S&MCjgwb z396Y2Zy<5OOwJU|-wu7>c#M(RU$ETCoLlB?V6Cg->#(ds1;i{60=>XJ7Jr60JiIFm zFu)9@DvX7YPZ*Zz&V|VWda{cZsA7iRFi1U9GOho<{UoapoHu0~HrgCeP+KBAv3xfp?zmmkuO_^3w3l?QpMFRohB*!#%g?x9sUhP~Z)aIDeil zgZ~ZT$+lwPRfZuhsNrw^q6jvUyGdTEUW06bM^9#YeITtQCT9!7(~sG|Pb6kMXXX-}21vh!VWM zmNmN67GZ<$swL@`Ijjl1Q(5Io@JX*H!#wT4Dm40{JAJ`5D! zd#K;G(?9yLTV8@z>K$gEY+7~uO@0F!)Is)1%1M%AiINmNXBo}&xsqvui3B&vdQY6} z2G_R@Ak7P(D#3@0{amlp;`s%e&EO{>GPgn)euW;wOdt+6a7!4cnUD&Y(ERVYW4IgC zrY%F^Wsr2A2!7oSgr+i5h((f2^(*m4=VC zA-F~T9ezJbA6OPX3?3$uCf52&q1_><54(p)^ZEX&a9ocE9?kErrl*( zM#vNGVZq5sQba0nK?(~;41@dr{I7)0DS!1fFgtYG>8+&6ivyxs%0Ll_fCYBXIS-CE z(ST-*iF?bA_=;A0bpC!tqM4C$G);>yx&ULF;6)= zmS6>ALPIXUJ^Yq@aB+yTeWDVghFP;<_xU@o#*p;lrzc+d*=iw5Nf&j^DL{~#yO4h$ zq18{e@=Tas$W!=rhC{up~4N`}!wrI}#{ z^MM49J2Uc*u+Fg5Ep(*6hJrV2iDL#bQU~Ibol95>_TEtgmw{2r1w~V3Ic?gc@A~WR z{Sy)UQDg$V(e{?EX8Zk>Oh}jmmFAMH|B8_3O>}ltpv-Y1$enORCOj74Yk0{EB7W;M zwJ6E_+ZiES9~~j+6plG^7d`%!Z2@{%Kr48n?qFtgko;nJYz6f1V*-;y6n z@8AY7n0Zy`j7mp#)%He1fw67M8n7x~U(o19ff4zIN~3(+?!49%>pSDe*4ti=@nIlv z8QsASV^s5EIgKOtboQo#Ugi<3nPZc_3N-G)m1Xy+Z;Ad&>r}>$L{R`o(6Hrw&BjSW zBn_ECnL3M0N#C2g!Vj0|kj@dkTXZihlOlRO$o5M?UK(g9(pu-3&U_0r;(ZFF)dK^4 zL&0rPCaq&&71O|R7iiEb^VOL3R)oQqW}~brxzc26_5KbMYM{v9sQG1(9cq8Qqm;?O zX++{rOnwb68t8Jqe`hV*`hE~m>9wd~qRkSEl;v^4O zP}iv#8?|AVr#C0=DnFY?dPDJmW`$`f$5j<}a(SLN@%+G=xsC=5S~T_5RxuxM%#O`s zjEfiSrfz#sZ={9~`ruOSZfv8dpPX5}R`Rz#j5B+^`tyN01Q9*xd7^g`0|dujenX{o6a{!i)qPL=4=2NC&M&iAT}xSi7MbSaA2*# zyqPg_8B|V{@f+&7a3*#e})9^tGgKitN!eOMD#q6h1!B*^q_C5b%_m6I5 z*FVkQra3(0#Mpam^cr*uc!a*)Z=jo*5JPB#yUlT%fK4qu0)*O+1EG-=XwmiD>r0de zDo-nqr?*vI+$`%@7V;5*)zGNu+7xBEY3>q)&_2QzClDyzE1#FT4jo=H;?h_@Z8lh) zx(m|ECB+r(0Ucf>>RTYm_W}-Y*ehpAB5hEr^L}HpXz*%=(>N3EQKQfX*c2L>08qQo z?HN9M|2f+X=Q+lS(ZMV;>def9E#px!hH;OR??^Qi)Tr9~deDjj7mR)G5%#c|$~hOQ zbkDBtiul1(^~MWqQx)nvm7{9Bw%dZ-X}sx{(zHd|k?yM<3d*{iWgjIyB;liYuFpTX zGrTiT%qw#?v&Ocg3W>JVYky!rtS%P54)5qXu!hiP7=ZWDz6A1XDS96WRYz=x78yYz z_UKyKT)?Z-(&4~z>FD?NeY@K{?QD;h70(zlm`=9W{d}NzsrzH|XjH-93eiH*UAzNW zowm8t;ZgU=JA*^~%CTi>>ZsMMU{|Ab24lGpfWyR{Ol2ln5QldxkGfv- z51<=`jFigqjU>8HwcKnJ_^hf9n{Nzr8ifwoSTtT_&i8g+8lt#<^o&1vZ*aK?j1H+k`B!DwS5_fgwW!1H`WUx<|~Po2YlNM(X+8$C|0GFKi06n1=-PZldILvW= ziTV^%A#Whrc3+B(45CDONOZlMJKO=vp21LA012bgF~E6+fID7VroyVNS#bzMA>W&} zWn!}b6UqDjFFc>yleOWHvDI-`!L<#}z;W(hk?4Iq?mL^ts5g_N{dIyJH7KLI&&)B! zT9<0ysso7Uw!CO+0R&R=dBYPE;NO~$f|4S#@Sx7KXPSiPwr>me5Ns&l)}fKC<`OSTyjDI^{jW&kQ&7Avi>;kCL>2^ z_cf4Ck($#7vS4nt+VmoJXHT!rcPiFg#|&Ho&aR-SmEowK&V4G4L@5DXzuFHfA?6BK z))}k8zwo2ek*TU;zr)M{x}kk#*K}GEL(DxTS#Ad9e_iVnD~^igz`55&rVugjw~NZY zB>Z8_J)BdtA~1B!SXOd_?>-NnvM-cEKWmXnCL4XwIaPEQL^{*x?)GJw$b~5Lew*$M zhBP>-{cM5dfv7D<$Uh)6c5{M2ruzb2!>L84+eodO)(%>ejBE9I_i#&m=dEZ%M&GgPKp9J52*$3~5eS(x$!L(fH zqwS2#&m!~#8dq)kHpgvL7~mS}klBZWDVD~#QRXvuR?Crw)^~`Hv=Z3E&pI=pCji|%Hw(8f)!sj?4}=~VKjcU8ss-Zr09_^o!(dGM6;ZoX zdm;rI%*8gb66z8W$rx3!>))g(UOn|Xw&dy6XAu8It$4J5^4i8ewO5SKz~Rd73R>E| z>?P}33bb%;8sY}f3SV5%J{lELINL&5j@ibe&2)YnMgH+tzGb%xy$rS;;CuLiRKkiq z=q_XdR|Nh*683t%Cm~(Va{Km2^WON3bVTi>p#sVKO?2Rm`O01Vw;)d$C6v|1MCG|C z!hPH)PICjHcNRk#UJ8UGKERy3t^iZpMeQ>wx7ftnbRcH7ghi8+Ggy~h6f;bEI;jNy|l5{P{XPJNpqzzq!+A}G!Kq!I+ zHkxzLgW%~_ntVjXedkZQyP4*bN#vzKq+g4E)RG^}+7@)-Xpd;B1Qb3e<-=9ezM)yg zdhd}_c6hCC4Nll}F21Ql6BC>U7nYV}6OiJdzc%yvK9yR3 z>jGi{GWOW&UulA$H!1)Zl)07d>bYoK)!%(miJZ5t*yvUekQ}&hK1_rSWva$6b|AdB z=_<#zE>51O%lH?N+yTH)?h+~WeoN#@RRI-WRrp8jl}=(l2gh70XxBT+%@5? z*;{nS=EkI)$3eohOhu^+y@3@M+)GX>a_jo+2ykH;0kejRoD>LO0$uuV)3)T!6$KsP zwDHH+QLJ6j9BF)MLYI3{HbVcOTFjEDm4n?8jp#?d=Kf-BJbHzXW47j!FYKE2;Ns3G z<`fMQ9QW%;;3kU$cnov1VqmEQYg8;t?P$H->hC>wOG~HC_fp4)8XKyWitxIoaJXF5 zmj`PcaM4o3-_a$7jM5~g=(e!gx{H$*{LD5nDfWuTZyfgGMV!3S_IcFGGTrUsJq?Ya zec`C2Z3)|-oHfxKau>vmM>y;EBK2_7uHEqp{UK;RqN@3C_03IwGSXp2qt&sakmTgE z!sTqBVpUN3mtSCA?6RcJivHK;6|R%^`?^Fs{#8iHoa8#MNrwqFClrm*7W=)DHUfpVz$R~hqS z9oA4#>E*yJY@}GJ|B3Zxeu%_;<1+0PDn3Ny2gAKYF=NY@|t2HP1r zoCA!Vx4zjq@$;=Le*UucbA7kV!jjN;R>y>O1J9WePCI%Khqr^^TM^GFT1^}@^uap| zF%-{u$3w1m^nJ$_+@`x-p?smCQqc`HNjFZL)#ceSTf*`qy&Fe{p@B3A3Kh)gk9P0j z7Nr&uo*X*N0CFUtNYyg5El3^z{h&8WD{LmcPPI$KQB782Z|l-6koF3 zRjf6jff`M#)Hl6AGaQ+MPusA(iW^f$5k{=wRu;RuCaV$ze{?AkxB>Tkw{FFPH05${ zZ7nYpIZ)T~ri6b5M-`})fQsN;Nlqf6sfPRnYO5l4&M z`Y7V7ncBgq2vPmDz-r8%nSLmc?8KM<3GznKsuEY@W_Uy?NR(z~2ZMY;$a>LZ z;GJZ3P=YfiYyREKdnhIBPOb!3AgN$4`V9O~1Nr&34}Z^?%#q`UaDt=3S)bqBM{$V3 zyQBM4(0f)ao@4UBaxn_{18R0+D8Y7^bi9g2?KE3)or zQ)Ecu21rKau}rjhdPZkS%}Tn_`qYF?L`HVwdInA#dD3cC|1pIFVVg>71yAC~pz=zI zMh3i*H6HOl5w9~>gva+n9KiXNMebh0PJ(4x14cBS731N07qCgl89Pnm<2`0<08zc_ z(*@3DwkN4RO6yXt!?bMUk9RkrD59LU;biA@tgUzo;ZwiQc#3@}Vj2~4=*uJIGElBM zzKW6s2(W1U6j*-A0gS*Y8U%|b4A~D+ogFd9*Ivg07?}ZIgq#VzQRitU8qAvhvA^6L zG{^r8rKyphHE_#_=jcU5%LW*x%K8Ol0$aUgh#PW{4#Dlb#rTJ5rI(}?xxQ~0w>j77 z|8DW>RRrB({w>~rVLWDwokKGYY(0)%hv824R;RS=u&w-iD=knfBT~|H4PN7g7VgqsYPMq-|9fIL~x}avuHXfD;<- zTk3Geu^!DjIN=QGmtkOl9XLVj*DJ~=2p;n21Y(lUBD)$J9i|&3ih~|$8}=yN6|!6+12w#0GE}V44gKsb3zyrT4+YzD}kliFh3rMTp0Go zdWrJDhavO}}Z1RI*G0F~D{&odlurc(?zS>Tlx(RDL@~?PlaxpDVFj7_6OQ#Hv z_ol^xcpx3Dsd-Uf1RTL|*zqHNX|YkeZOZK0C!%_SL-fhM31oxETNv{DtUHsjU@Hw_ z)R_x}L~SmmCpHtFO+wdwD_S`}^!t%_Y*X@PSUztLqNzx&qmu+cHD8jl;Pt1= zj;Lhtho)Q6zrC&)Dz2nqf2^TGQsOZb@UkzSS%B&R)S-Z@pxXFl!K_!AG z9_@K4t#(zIMd0k+b7Yytb!s@2U(sCtL?%~Xm52G*AP^}Q@GFz>=gdL1Jk@gmY@+iv zFAQs2v$W1&t|pbz)W+jX{-$WTo5b?G&oCC(X{p249>!|&r^0MgJVzCJn8ZneXNOa| zp2t}E+CUHINnl&s)oPw{#a~7o+Id4QA)_~Ez0G{n7RbQ4tQ;9PLsVN0#81Mkyt3E< zWHZll%;*cu`N;AYuL-SoMsqH=9{?zul)`pz(>u-R5UV!UM|X`KIl>P$of_czl@WAn z=QR`MG#E85)O2l`6Mzcu{UQojd+6^{;)kTvrvLD~Tmf!!i--A=-0gSlp3>5;D0c9T zt9IdDFQqJ7Mo&&UvYWWj#1U>K|p z;n})p>sb% zX35h8kKiK`YSTg3+o&>LbJI-sc73}Xv}h!aH?r);kIGi#WAZ;SfwB3J{2(F{DnTsyCTa{l~1MjO*c2Hg1VKYULAn2J`3->N3}zg|NkbIrz5eK=z1QK*k!e9$vi0E*nn!D zqF_R~^oGl=nTcrn!DY}O%i&7^m%Or9n%tGjaaLU1mYs3TxBRDyB2l8!ytra;kyjtU zMAZ|?&TmYk4#{ex4gY2kSMj$Oo8h%rRmo-=mT0VKW>%EiSQDRJ9Ud5+KYpicJ>jQV zQPZ82YR{?KNnGFc3Rg=$VC+fKB)oyY)>N9rf#tJ{M+`EvL=gsnBrvV!O#Jh#5_l;@ zQVsL7OTGi<2VO+Liz%+zOxnB>st)JOfU!o?#_$^KLiDO%eb9*B3`=VXd=42ZpTE&~ z_G_oWYXH!Pdpv{exF-$rc-I<>W?2Z;d=UH3cD=u)&1oW+oZJBT2yo8{R7OYJSt32i z!p8dRRKaF<_+V7lV!s}^qRF6jc5iLVTpt6mF`RsiZEEfjd6U8u%`wK84q=9@(H!aY zL#*k?&YucTt>d1V_tCCLmVdrQns?7iM6Vrj5TM$hlY8c6KjH?P-YSQkwDeg^()uOJKyr8O%j49h_Fcn?bYd~CBa2l2UTtew!w=fbw<(r$aMP((51Wc40; z=DZ_2d6Z{eZcGMd9k6_l&Va7cyF9Gvy>MiQL$;rWbl-uJ)#pZQ53NW)gF}N=Rb#2V zs|)&Mv=1sjl73{INJ@qU4Ky?ge(<0hUjuGLxGVMKY4hfVBCbNhswN%X5+e_| zSFfxAV!A6h#;q<~LBP5oOi9*U+lOC}iKp263a8t*KClo3PLNA7*M%y0;Wrnh#e;A~B_A^lu<_91oS4>zHR3m8#eP#2e=4~-~XhT2Jiu6};Y*EktYBe#QW z1g&n?%LIBIE+-K_8FRP$@h?&fM}JS2?|w?~634;V0V=p^j=k$hI%U6%o;8DYn->hJ zus+09m+6K9s{g8m;ToqB@E!*Uv9W;Ros&~hQCQrH*cTFFSvzUah;)iXPfMjM3-HeU zqkuc}L&o#h?;;C4Z}OhH_XDnO=c7B8Y!YXous}wuog56GY~}@B#EsSXFm9(e_xJQm zL)6~B*zlis#)epB!m5zp`;LPPYe<#$5nob3+dO66QE6Ph(@u{AaBw2Kq4zHenii!> zl$=1%i;r|UUN9b>?!RBkZ;b9U4Uty&s@&wzr_#Xm8491?P4uMwwtCxrXcFQZ2z;5a z29owo{w0wwB1dNX^1eP)2Akn2l}j4)PR-E++KpZ=>VcBT+2I1L3HT}Fa3=2 zEkgkz9aD(9RMtyoZK4jMX9MZClgEBjT2;m`?t#3+Ew3hsQ^-$otxX~y@3`J6Yd$jZ zFZ+>b74^qiMpS*T6?nqiYt3q(;;R6aE-s@X?x5CJQR-V8X7%mS?OW4b#Rnu$F*K4* zj-+*tC}SqyD?OD|_$}{TP0jCVSg{ib@hHQ=qpBMNpiq)Zr-5cR|89)JUJYbqTj0>S19N?>|JVwomQ{Y?G!F1?vnrSJF)~Ki@7YfK0xfk?vA>lg#5ky?!8ozAZBgS-Q2Jk9=`eb_!Ji*U1jxnzg@f-KxLlJ? z@#Hbx=&VYw*VfatoT-&a=duxA_Ngwn;dDMgd=dX14R6UxxW%M?S5QaB7MPyX)b8?< z2&Yir=v7p&H~Z%vhg+Px4)&h^jFpI>^L*8dGHkPS>wtgJv=ger_^As{=QCC*i)o7JGa_1hTHt=-ztTTN?w|E=uxRPOA z)FQUMR;PTL0n+{wB8?@)H(&@-ND@(ui#w#6!Y>^CcxitN^M#BYZf+5kI1#J}(S zeu|DOAmr0@(kO^n(eC3cS)Af3FChU`fgk2un7ThDUJdc^VsID*u%^o;h3-ddQy?$$ zKlbi0;`wmsjD^x7&R`oGdBNXvvb%j7UQQfnO^-q}G)G4@J);A$U_2L=^1&>uSb;i9 zy}!HbD9G+9Mb)JD5yb8olxsUpi_3(F#x5ahs@edg)SF6A zVMMxWf9C>0*mC?`czu2I4!BdQ0fxRF6DN_lnp!^mtl zjVCEUo|d9Hsz3($IemERQk{Tx#F*m;+(+=E(oz?g;dV@inEL~+NZDm6nhxFKh%!UTArM)zFKDhdjq%hO$*t0 zu50t0KPU9wZGE` zIdPB;VP(w4Foy$O6GCW9Tmcn6BZy_JZnbhr58aY)Gq+v2e6A@_c+REB=;>4L0x;~O}j$S#E6z%i|9&QGka(Fi`2b9JeCDI&7 zVXssZ_qYu4*8-NW&YSwU>`?L%8Nz{}@p-olbX8P`Js@A|Yo*AyQ*s*#ii1*5X1dWL z`~if$af^0YG=Q{gk$Zb7l3(|q6Gckf&);0n3I?)!Q5lP`IGa*AO1uWk)cKf0x_rcO-L)?_A8YK zX!@i$x2ql_6c&<-kJb-o%QmWe$HUp@AfLnU*H-Sr4YEM%k!7%{Uf zC?;hQ>YiBPQ`P5+v=g*YyaZVJFiO=?H;Qgvk-S~16f1jgQnxXsbJtCbEnSW=_AcHz zRX3HnR4<$p_GP`sQM93?+tR<`?jk$dQ&H-^`RaBMvtQcMGRJEY%cZEK^Z4yf0hcd9 zvGx4NJ@D(867)fz?-Ze;u1yoU&1mr>3YVnJ>`vy#gr5AEna^iR{N;>Zm8qH-Ew;Ho zqEjRpTWjL!9UOI8yc4N+f(dM~V;7H)0o)G)m{nzx+N6G?2*d?yGR}Df_JKddGhiT* zASC1A!Dx4X=5OO1Y}I99Mab!P7zSlw_;7iK2vBH&?Zwmq5*x`Qz5875y5}efbCXhLNEPb+#5M=1;A_ z;&7s|D)Q?-orQafJvp0JJCGZc3`1|?Z~b7^a=jnhbjytygHJU&;Oy3E`Ahk=tl5FZ zWb;jboue|XJT!Obl>0^8T1-YB@-t(Q)eqQtjsIfAI%Sx>4+U7bci!-l=t&E|gppiYe8=5`X@) zdECQ>I~g#$5c{)dcHcwrY`M1U+uJ)xBglzU8~bxpnu{$>53fNS3h^0jN*q#TP|L6r z%Onq~&PtOh;3hyDp-j^Ckgzy`T=`R_{Jfogas`Y5TB2Rn785B+9C^;7rKUw%WhWlr z2&bC6KWf7@U48yMK#p=G#p8>9Dgs1Ik;bx8nOIPlQg;Ho;c(EylIPL!P zd%ks=6=?ovkWk%c9qJ9LB`3%cx2Hgt27e`<=AQ)Z!dsF;@583$ndp4!E%RLq^QpEF z@NumvTg6_o#Al-wZ{((UaJP@61hN{PxQw~a+&Z}2XhG$;_<)P%1?S)!mpty{b7qMf zAjqQC3*#fC8h_RmekBUO*q9YTJ^-7-a^_8mrWd0oT1!pI=F+&~E+o&b+3rEZH=M7$ zZ2f#c!KU^py4RL#ME&dqd%3cMdP{FDXIFIxodEKnVB-32z61x9}jI3HnCD&r{LO8WQz_fjF9a!yiBW z(~L{FeaUA2xcois9c*_m4kV}T4V3B(F%4%Pj-m?Ap#QfFcE=-SU95#Fvn2|Qhd!sx z|15Yk>W3OY*GaD?$U&?0f6yCeS#B9dvcHJoMQ|L2?W2{ILgRC;sd0ce3#yGgA|EZ# zrgO;0_njK-d9;q|LIytzr}TPlJrUbFX18~vyhw+yFCX~YSou5MqHlAOmMgb}2U0e~ zK)&49^BWL<9wOn)S0229A08q_{|VU~d{Aqv>Sl?@0pbyI!H7`XXS=rPH^g@X#Lh=E z?)7xX#5*D_ELuA|RqyOrf3`BYe?g4Y6rJSLAN5HHkNzpZm9<_8$iBF>-`$r_==TN_ zDVmx*$yM?7+%eQ`kFoe26cFYccpX7sHLHHlEokL5j?;XNX)H?IK7V3W zssqA(G5SG==c(vrV_?tTA#l=*=s@}#sR>TtKmWe>mO%J=o0&Z;BiOG;9m+nD;?C3M zWMue3lHe73iBI6U$2L((dL?%5IZ&^mE(Edz%i`IahK@xz3yHnDyl|y!{w$(cH$_p5 zZd?+Bdy+8&4F^`tVXy7@%g8Tg!@tXL>71PHn%_nBT$76SNahj>83($e;fiBz8nBkb z`q0^tdI}{&O~eCpPVB9E)fHDuXXCRP%4_TH(~^&~XcwbzZDKhds`^2^7CSzk=zK6b{7`lc;1dBEcK-Fwtv z(%r()onVW-gW*u%2c)b&w}B5y{KfR0h|joz z%PWGooqJN~&fC5<(rBF7wjxCQezflQ+?}QE8VFUqMX~g1uy%{PSyBGS+FNklYzZnS zU9XVgaM0XeBe*l=YsLW$Rh4@+d?9a26Y>1t?CcEjammf?4+}dLXzo^!l8m()b2vbR zZri}L2q4*lbt`QIKUH7?or4*<|$8u(`7>PpHJV1WfDN#eyIa%NQxFKG%6h zI1GruKau!tWr>VO1=oIu{yoo40*!Ih5T0+6=DLfIk?GgEQo4WrS~#oDk@HDADhd(m zP4f%fK_aMOn;b}t0J}p>K5NRj6`z0X2>~-I_|8pyUcAj~mp2=S2T2>7=9iOt&Crs- zuY0bRUOXiHiq>RBNnIGr@EOp}1Jk>@UJYKBcI{4s@Pk$J2p_c{)UHUIsc&JMT_Lp( zc-w(jy%dreJ)I^|k8q4zs58C1Jr3)sM0bD+5mGt@mpt0ge&@QE_yDqEy5UtsEr&JJ z=EhTWg&Y4=(az2z_PEN$8j2jA=mN^bH|HpH;&{iHY_&#wG`Hd6LI>Y|E-i3GVmn-( z&n-DDOkmn+5?CqYi1t=c35l1Y@d2$YWG(lBg3?~G_sD1_{i%3hO(VS;_t{2SN?fRD zGqPCj5;!T7A9AgYtLom*-V(mr*hh+-KFSxa5v2B+q{CLibf_C>zG7-unco5FW;70_ z{jPu5)0&Wn7Pn4c_dyx&XTdEZDzM+ePMkrJXr0p()fM=Foyed;ty$6aQr?=o z&@=BSXu2YhihX9@U4r2+w@XytMwSWUQ(-!R>t22i@+gAcr7b+U4<^d%p@ZJD=@IEoT3r@fEO{0Kx2JZZ^ZEq8kK3&F= zRk>Fy#@#EzkBtTgbhK`=a+=i?@1-|`< z(y_Z^8}v=?aW2ehZvc@7Dt=vnYZ!-_Z=aRckR7~&!NX41 zk+rNJ;iuL*$2mKM6 z-S_gZPsiQg?*TL?R;kplQ#m0d$5p~x%*Om~Bn6|(J$(Jub{Ws#EBW_Zl53e6(NoZx zl78hYjrG3s!0~d!HzgyN%78y;01TyWr4sc4dK2e=hDsaIIB#76lB$sqZqx=)N6on> zY%ajNs!r`q0FQ3Es_cOWO5XX)omWetD|z{e)#D&$GHzUN;F`&{Pr?kU3m2?3?Q+ob zOqUh7zIeV?AqhUe%`%KD7_YgVPGO=<;?4pK8XZ7)ge`=^F$pKNonI#}4Z0p8RX@A2 z%v$QqZ2|hWf5sYrQ`FBybrfn@w)LO1VQyFx$%Y`h|518I)D?>*3qrBuv2U`|!m*2H z6i!gw_UTk8QNo_WTi$tfVIoUNU@0}A!(dKkQxrQ5&6x0%lCnzZcFFIWIZf#lg_P9f z#}s^|2ej_rP0#!Q$@fd8Dpjb6k^e#jXp$-;>wNJFet_|Owjk%j_FqZx5rEtwJVqjy zN=>A>{PN+3s}Z~{*n7=SR@9jc{azQaK-S!cfR?g4$EVSXe+~xam+y5qvTxxOH_Gy- z1bz{-B&-vBm*4^bCobG4Aliel#4%}9`G5)P_VzOz6EE4?jIH#^7p}*gwmd32nEY1@ zV$9R|XtLT~swE1PI#58YsYaj*^L%C|38NLBIbXhcVbA|NNe{10yh(p#$JOefn~aUh z`gHU7TyyOG6nmzbhJ#1jJ*FN{2F_#jvu==hYrZfSH%qx0EB($T*d>?Qz?T+LHJnJ= zNJC-)eeSpo*V}S9H}zkMlI5vVQTt#sRI;~Pf9LSqQU5oxJl ztiBu6%tRw$JRfMBAnYbPaCcTUQcV^-3U-~geSvbE(DHx-emhwvG2uq0Ykl6>^6vF; zOwzz5ZepX$qFMxW!{^ksxitkMBPq9>{4xErqjU|M40xWr=exlye#vMOhs^5SC%K;r~&u4SfE(3KV(ShAG8J6re4|0*E1K6F@Kv5S~`e)5{HIhu)>hyBiPcu`EtWT_`?&Bd^t6-JL-Vt^BfH zmi1&&(hIo>G$v)z_D1g1T)t;183e7HvLSjf>vXz!OQA9K%;MH<;Ma`?sDNDD+TwFi z=VR|Fj!aD3n}QG!&0@`pbC;s7j%cCoIB9tyF&G!xy0HVX3#M0RRW5Ge=5z&EirAaY zOmc_HD3FK$j&WG?w%61a*B93ouf{h{HP~9ChJ}ifMCw_&s7>Q|W9ggzte> zXC#>tKl;bGVzd3{y`oXtPqi&Upv+qhf1J!6h5H-9$&}VQx2i1T*A^qnES;T4S!K~( z#i0c9?khn`Tb~ncB1~Lb##q4>;7W$+Vw~Zp8G!h9&ofVkHQ!C4uqFb#WfeJak(y0P z(xy*{Ghu0wx@_Oq$8!y_G3N1M@0qtpMJL!0fX<|tL1@)&ekNde$iBYzB8q+gKBSo7Oqw6A`jIn1)+Vib8gRxQpjq;6Pq;d?ppj}QRk-z zB!9}`MC*+hE>B5rV^88Hi_*`~V-!V0IK(&QiVI~Q# z6EJa_z1E%LD~%2fgU*jiFU?1k43Z!U?`$@+q<@WDYzL56nAVlvqr%pNZFKv@R96tl zYaKUutbhAfm732XYbJxwu*@lzU)hMQELVnRrmdm9V%y#S77o6qYhC`qE%E!mss{)P zY)@pA$A$fS#)@+=x;Hm~I(Jdqw2>q%*^SS0C)uK>Z;;hr0e5boo)?C9bg4K@xHA>r zp6ocO?78}WTAE@@u%s2}LDg^<<-Pa5Qw`Q%{?f_!b?Sgluw830g9MNxymecBo5SgE zF_CV9-+Vx3Vgz%?cup>n=1`)mp^FH4Lq9Mfr>j)f2Dl!UTVwn}87O|H}T zyTenbzqf6O6nINYjng>mZ@C9z5;C}tDV$3q(k<9WCm~#B9m8#+Gj@Vg5~p@7b>kj$y+t8 zG;|koxAd|=w<>cRM8fr*im|I5v=!|xN+V7@Iw@~Y7t3oaf*C5=-c^2li!UC<7u*i- zd>~1+-r5Ej7u(@MpTA*UvD&1u|GC`F_EPg~d&Uu`P7;FDaa*)|8i+&cF z)eR#O)Nt6RRATG&RPW`-MXDk>O10R~?SKJGdG)B{s<@|WuwEVb?BRlZQEeN5T1=&r zJKmGLAl|D@`lDWG-O_qR8p7kYH0A3loIid#BZ*m8Aqs%^q7F9j$`}bJ)YV7d>y={q z>=)L($+x&*Y=Z3Geo>JNEthA-Lyf!QSXA*BKM>UgB z10(q=0&I311UX8f8TG&@tN-FL`;P=rW`v~)=3-#dlIWs?v0b+6PP#aTDL=GYLdP7NR*nxaixvOD z3Es2*2}FycmkN;9uoDwWKvWCX8>0Gmi0@VFg-DUn#iDISJKM^S5$BAWyo3y%?E|~h zB1~-Br{LWI|CiAz=|PT~wftaH4&#FtWk=VdfzOGjD+))A$o!TfTheYNn_Zs#a$bY# zlzp>?POX_=_ukFKp)^P#b`@?nJ|!VKY(uf>VxPf)65?f>t8N4Pr0OWzlHLwvMslY6 z30n}aNVji~oNi1MD*nQ$yB5S{X97GxdkPV25*bW$vl2EF+7SA#7d4Q98J#qEfvUZR zXBz{CF@4H2-C@ya=hS!!K0-XU45bQus_XXNWyj7y%S=s<%0l*aVV)%eamy0O;;XK9 zd7JvZL!ODBv4m65@*-1x`bT_o#fvvX8gh@4g_$IVY}9ivO{vr~S(0D*slhaV9rc78 zmi~1lSo7J-hC;9pDzZ$43`eSp(`279?6U!b6(k*6M_aHBtM%Y~C%O(-MW{4v8NduqTIxBp%H;!P3cQgKzH1ADQ=lGjtn(IU0W(kvX$`$}=x6ujr1&^Vo<@x}Ewvn&*Q#qP zPvXo;gJecxBMM~r{ZAMcrZ1JF8u`n@;*jY~Qug_P@LFkoy#$i+-gqkt>Nr8P+Hp9H z&P}iarVr6il8-U_mTF;VT_o#?ybj^l#Z~PMHRb}&B_=rm?KC<}2!4F^1THXBx)e2> z`!&JQwduy>)5&RhapDhI8XU#RLXz)HFDEHCcsGS&l1~H*sc9^n7Sz*#_X&J#Z|C3z zB-K($30K>V8KxJA*8+ZDTVyI8xbh zGXN|Z{J51?@hCk=ZGS7!q8ct>zO!-H2Be?|L3|jt`kDke2{;30Em!%ir>S`22X8c+ z|Ap_lb4}hDj#;HiGP~|&CJ91!vmr}^b6M`wv&=_K7r&bWJ`80vlX-(QBha-ur{}!f zD`7iBy}sb7Ij$x}DY|~<`J2h)_ne`8=*FUme4Rlqx91DkV;O~_wpmL7;Jg=Or&1p) z>Y8ERpQ#Xvt>HI>@eCxx8%4A~SnT7Zk|lXn-s9Z0H#$feMV$LCTZ;%DtVJ4>$n ztA6x~hA_lxm#6lG^Tu!WgLDdEzliBCthSBYl<-3VuID~c`>Qo-w&gkLUC2 z<-zM3y@n?@`0ayg()w+(kFOy=KaS$7wvF{p0b@rFp!{(;^jbKt#(ehJrD<0ihQ#aQ zFXF>bUOJgka;2xDXhn$aH`&d!IZl?7e})n_nWw|wL&D+{k*=EVT+|<%7rh|Qik(0O zh~IxI!E)e6)&LLg1A z9$u>H5|9FR|1~)WHJ)ExS1y`nc*TJVF#4y!=X@Y)1N%$EgyVXkO!OLez)DMF875+z z{#$^-iu5SqyRIiAb}p487{x}=&#OpgyYuRH_NJ%GZA71eF@$h3+^LQ~kiU=vCs4|Z z(-{W8PcBsW?$$WvjDqTDt!4D^XsZ~E{?mG&%4ONDrmxb9`S{rXoi)f_z`9ehyL^MQ zR3-Y4r7~&=#a6?FC%~%9pDC@wb5DhmHC@@q#ec+x<3+33aWu*E9bL9lUeg5-@ajf^ zF91-Oa22A}c$?btd1HrnfFgD=GaMHIliSO5Yg z=0#FnrJPlW7a#)Petx&OEEIAMyV#xDXMuo=E4ss_ldf3wKmVzm9xaV1dA$QqQuvRG z9bJyedvmmWc4LipjoJ0pa4u1X;Z82kurdehn! zrKK5k5Hq84*GPgYkv=qhAVUoNQhb6Ijws-3=;I&amd=r9!;gdnOYF!*SEse+S@ zr8r+~l@Q#)zLNQ0oHY==l9Rx?v0PztzAMI7wzYO%0FZ^!lDsKpQ7v9 z!YKjKD!~$bXNKM^!CZM#n!(?Dx*Ex65^jzWG%G#}}rBmyvWG!9_~48)E;(uyy9%x261Eln>8%#cVw$1(rJ7F-(2^_Ncek|69n zgd;9}qqpCR6eGTU_tY%RdZtkBGBT*@?Qi->d}X>zL5;28t=v6%01@J-eV-ad`iaoD z&)C@9@y46l>~LpUUEk(xM#3A83NPKLZZ?>-HnOa_)TM6gr2Y8jX>F)4g-0t zg&UHnS)}c3R>=J6*RtfffAU)T&lKJ;n>F{qHkYD>wM&$vN|X<8p>7h+lpSe!#hwdW zh3`F93l+nJTO^YBZWrR|H`)Fr6{%V_uvTxaC|@(BBO+|%;zq}2F;rQC47yXFCK^6C z9fEL!dei28R(HdCZJ)r#X^|7P*gR&_pu3TAAv*Wto9_gSv1+R>N!mPMfh+1|amj`GYkM0O}oL zd@UaCEape|rXb8~D!%dBZ^OWPTo13mm}ZQ_6A-z@s@nE%DuiB3Tz*T~)>mReKo5>d z`3HQ_01*IG5;X69o_s7l3BGa1yMo0p6 z$q-^8Dl|y#*et4)y#0Tmni;3t+0)LQYjPsUkB&Gz{O>;hTxfG>EcbWhcUDp2J*p7O z8|UsosIAFU^|8ijnq6VuZV3c39=5ct*3FD*+sdO)-Ta0YgN8jKPl28T*>xR1kcT#q zZlM=-Lo$#)!eVyINv}N@nPSxK-mUJ)zt*~+r!6TMh9FY9%Yugj?qh9}2Pn1M6vLd7 zRs`*XTj#UIMm~jbGNMEBe2AcaK{1QB4=IdHJF=Fp52=|P-6=n>}BNYm$K0QNyXslNlsj4Uro)Ru@6}BY!DUpzO6=jGY@P zc8HYdy#6n0%wa!Qa^MTRzg;8@&Q~%O;?cIk_b?4yNFkirr%x%VfPguikgo>wpgE^> zIysYrDj(Z`U}Eikb8fBn1i>7o%Ba4z=H~a00qfB_a{&~ zUtyJ&{s3VJ_#>sMuG>q^sLpFzx`kt>>v#8_>r)TcArV*k3io4sdjOZWc+6(7`LQV6 z2Rw?tN)IhXQ;}qbBl-d8>s3{t;(mHX{1GA7I^=yCBmG=MVF>*BK+Z(`v}%gwgHEkG z6LfrU_M_G(mYst`-?MxO&Roa1Qcf!TCige38Kx_2x(<}}w*#eEx~Q7$jF2W;{Z#Guadg3;^i%5A+OKrXekhzVG1fy9E zwI4HPudJMX?zRFARWE`3^~rvJg3g_Hs8lDS%%R1pjcgUs=Q9Bl*L*5Hb{}K6zsD~f z5v24_5@NcUe*?NWTvbY%{srsr?T610i&>IV|pd z79+=n^XzxECgCNSkygU0o$uY2$sKgS2bafsqC%q27-chj3c<}9y|z0+ocZGlDzBE8 z`ESQ1l@g|LyE}{Yd!vC;1!qjD>|0a4y=d)iCbEEXXuH@<-(^{DCV$2gZeF)(o3L7M zccK5$L@H+#8J0do(vwkf05!7Pn=2CoBfuOJgg`oDQHSn(hR20HaK$@4#6Zz=%$wJ} zSYZRzcH!>?u7W7gm z-<^#R}Z2S>M1C%Z#%wiXCosH;4BGd{pC#}6{O z<>^StA!-E}A%&xdT?z?mxtaia^_Zqxeo|cbvd=god%i2i5s&}fN8(WG>ncUGA`?Wc z?`56Z05u&){EhXNAqd3h+Tig+7cAp$EI4*Rz!c?!I9I{;e$8>uXvADOfD{=n^yW1| z+m@w=-Ct3Zul4x$=$kx{idCN=_&8u#p~?=%L&A|=Ky+C-~BeDGKb5_i`SCT5EOz%R#ylvz{+ za1b-m62ujZFk(p;s#Zvb&|U$Gj1wEPa9pkV{z!<)Br9jtq#dY-ggz< zIE&~8MBW1^0pzoGT?g9T=V5b_s#a*@ADUlDHp@aMQ0#V{xC4BXN~-!(<@$k&>aCMk zsocJyrnX5S(t+%yWWnj#u&c|Z;4BQJ^3Qb$zt*K}tEO;|Hg~7@-l{;l9X|P#7gWqAn)&;Ma_SS@bX6SLfNwuVZq!W^ zb})|HN=2F=_P0W;8b;7=!TQ`l#Uab{m-%f^^2M~6peUD^Aqlc>b*=;#2|NAwFLCRJ z?`}KMraCoKx-J3BXikLmJ32>jyIa(l>u+}@OZz`h7!ttzC$$KpO3g2xA=fuT6f-PhzFjBhjt#n!kEt7S!MD*WD+`k9IYnGMs5JUNk zh+f5Gp_?5w599F19P0f8x1tx*urS+(4b4c`ZFHSJ(;>-D;{7i@0;Q%{8Q0ZR3A?5 zzG5o8FiIv3Sl59Q%FDv~G3qAm z7~+u?OmT<(UzKDxryS0T%0u{Wv!N+I z^3m(EA9M9`=wHN)nB45{YFsHtE97vF!4+Oy`ASW^&sCiBwUswv4-3SkslM#?K_~v{ zE@7Vr;>0a#sqjwxyIDDfMAks2XdkZLY#n)Z{q@%f71U)8MciURg1L4Dabt(M^COTW zoVd`#TxH|@*~_0Wsx!pLEVWcuKpy|IH;NX7dN^2ewn^I#bpW-j@ur z67e>fLe8pf(B1tX+wXAPDt?f<-cIv6;)9dSxjW=0(nhsT`Q*P{$H4Wo|5IUsh#5vL zGq*Y~g_2mry57v|v-SLV*FxRn(Nn69PsO=TI-({jWhPiCNETT)m@ zlS{cgLn9QI38aab*(4!`}oB7mUo* ztod#hdF7b2x~DRItH*hyf1b_ROBEiR?5Ez5=e4q;00UH6p2UTxbhtpATY?5pj+qTW z1*P+vTo#AVa!$753_rqJG5s0^h^K2!O$?Hk@O;qqe&Do5LDlvE>aoh4m8GZU57YMn z6=(niiFuuUy6KcaHV2|=X5}H`PS*-|DnS7ctf{sqI!-dbDOIE~JtL8H>~|}-g(?R_ zIr5Sh^v_C{w82qRPASd*u9aFg17QF|%6LL(z>rsEU_dimx&64#vuNQ5R@=#nAq@PeU7DF|oc)rC)O2}pD`LMET5Y+P6w1t2Mw&gsIR?fk{nMwb(ULsAJw z1vuAQ(q4I>nOQJl3H}s#T|^&A+A2z1fI}KBq9x*$JQ35#FARm zRO_ma<3Lj%!{#Md-54G8(D4j&AkJcY_<(v4F6vt^EK!&6Znsi&@+ZOMu+5ws;KvE7xzMUVDNqIUZ!XPM_ zAs~{0JlM(2KpS<`w19xZq7Kezzi&v)9g+8g5>u)OQ!=e;KVTiqi^VLB8hTgNvtaC` zh(Z0u@X*@_WO^wn#XDK8B>grjDlH#{wgp)=(s2=ovXZBw^Q#kCU?IUOgsbM{x9(Qh z!Z)Aen(v+~9PQ$va>grSFC9OJJoBd)_7{J7h7sGvBvTy1TkgB>V8Us3?x@49tB0Vs z$3>hyMA}H??5?AR{su~AUzgZ(n83zx@1`-PrXnTwK)Bi#%8KBvb5C^Ulb)$y7qfq7 zPgz%;>78!lRhZ#X@rgn(ngLT9o`gZ3u7y2HF#Py9iHYhty)pb#e}M#(etSCmWo?sC zNjqOV(DjM7U-iI`f--(Uw2Cy3%dL-6knU>~f7?%MnYekgDe}Qo=^}_>X3YA8B|m}` zNTCJ6A0q5f%szbYxZ092}*Ru#|vbp{7x=1SM779@R*z*D3zR7;-ju*=(R6AQ@h%SR0$^kIr&+a0*QeXeV<`%&Q2Q~o9AST_C}DH9QahFM7oCHC_GZ& z!E>k)5wS5ZzY_pLJHKmzuFL` zc9YeIPn02yY<47~9G&b^30mVmV^Ymr4A@3#ylt?91PY2sQ^#Czu( zg|35_*d_U*4Q9fmDG_fegGnuHUaPF>_6u>bgdDMlk$r1Z?(GFaY-)~b2 z4zr<<#>JyC#MDo4fLf(86=eFRBvjopDiA@ViMm;o{CO>h&Y6wFr9q+OQ}dALl&vKjK4-zMMOY zT9Gj8Hj_qN-I4d!cSRF3b7tVOMZ! zos(zWda(y0;5QfyxjJKQZ50!#8c5~fe<5Bf|JbvlZ9L^ z`rLiT*k8_g+L`EN9WHKLbBf;=gr1kF6xZw&cP~ymXE3~Ln-k`uJ;sG(bCDTnMv0T= zR4wg^3T(YQ0h#2j(aO;tptVTrI620M=K-();~_TY;phZyz0Jk<_;}K5KzWd{h9^f* zIdRGZHMBzIB(>f%qPYgf^+|nM@^EvIG`P z$YX@+YMttj&vKhgFNCXq;I6t_j`yp+U<5jkZTe$G1i=~RQr~33erX_Y@)CzhpQS|i zG3((7L(Ve?WzZQ|Xwi+P%tu8=q0LXXNXoz@+pb^eiXJ%}B%SggAVMWR^pV3Ws;{zGZh~?pqBHFBEI2=j0}VI^F1a;LO|>naM9s;Mds}uHuJjwe-bs@Z zgb+#5)zRiMtUDQ!bB6lC1Ry+98pEG-Qgmu9m748@B*pl&8z3xs6pI0V3xoa|LN2-^ zeSujo_%ZbgcpMIm<*=Zg9pqcPfFc`Ji>&ai9SPEIwmwXq^e*;ZsiEhzAW&Y;iQTD5 zVBlFa5#Q|2B?tlv*6tKg*9S+?g>g?vG-5T94JR`A$j{%jD-(smAi_sOHM{$-3ZxYz zvTrJQ8(j2?2>T))Fgxf@M}hk0{XWkE_7o3t7_Kyu=@=QM41E4WboW02!M}0+e@HHi zY->ThvJpx$n~n7MZ-0x-n+@<{zz5W^Xj*9~!~w>QQAODfYA7Nc2Q~V^t3eRTxG?&9Hjw z7V1Xi`)vcc8c}gThJ6}3iM_z}d0MYbRS(CY5z9q^Cfgz9Lm)H@aB%fNH%pQngMu0T zR=Gl7fFpTQwmK)9y?-=z3|xkPplzaKV|bj52=bU)qbkLH%iN8d&jlP4b5fSFyLQlO zHTCe~7WR6!9M05Ss{HB11-d50eus#}yz;Fk0kBzU@Oa%d<4f64sA7)XVteiB&;%mm z2%9|9<0_$U@B`l~R-Is%$r~1So=LQbwD6;5x##CNf-vdmGAs+yeY$EEqFZ6k4ZS;= zU3f-)?nq0U)jK6F^VKE_7?r+J`Zr_z`%OwAQ-bgFn#reysPbil1q=ka$xWnydkI8k z*?1vcu!^%zGK0UA>56YD1tGiKOfmx9I1OY!4_I)1;8tD&lx@Nm=}p|ndsAJQa-vW* zVDE1KcWNB&-Da(Hd7W*6M+0uZI#+1aPf)%)V1MHGI)VEQ?Ft#2gR`P)bpxi>1!u#$ zs>hwoYLX_rp5eUnI)t<|)sMe_D(tLbb;YV#C$gFq`01NYxstl8aNKDAdZ!Y%rJAAZt1w^|^%Pei0^|qXO^XZi4ac0k{ zu~HgIN0>2@NicK4f55*~6gcco@a%B?im!m{U;U&Y8iehj_36_k_P<@K0ClWG{e z%Y^rNKo=22b8Poy3Kh4l*a}FMn23m$#7_=Jt>O@=y0JVk>4$2*W+YEcVXE~6(wII2 znsrm?s&8FFjJP=JV%@5#F@i4zg1imNS$gSX7?>1p3BySEv7SnU-J~hDW^qD&La&I! z>I(WziFiwQt6y0GDr@kBZmQj(ya5HG7@la-iHNzZwHanT7h~+M9@=WBs>f@4Z8KuH-X$s591qQ}A zzUv=hPQ%SY;4|5`k>3E1p@F(TK)-0HgN4JRPG@80YH4vnUT`J7`aM{6io_0HCinHx zBtL(-RLizK>k<37CK&5dPccViyc5T}KV+tB`}#X0AFvQoM|?DMjYB+u!%x~zVylI* zNQbZlskbCfE@z6fL%+hQt2M;l=sKEzs+lQNo50|qv#`dL-aob{jsl@srK38>E?m7u z$xp;>Dk)WpgFkrSPfN2{(If_lw1+wmnj-~cJNwh4r7L)^7G)M#%Bnr3qwonel>*{^ zPM%6sL)8HCYdAq1PY>uQo}cmBJ~UxEYNFI74+Nw|Ywjstc5KNiWo#4c<($U8y{F@e zvgG?T%Gmuf5ijh4xq_4@nK$Y0G}qM6u`n{vxY2J*=+vbU1CuUpKCYvdz>T=)B0wqBnV{ zx-^b-GgD`QsVCtx45QTH*>xDxpe{yEfZ{czyn2XF(#o$Xo(8K)04x93Z<{SOQu&R2 z37wu0P8a?%A*64b99$eWuECD*{vF{0xJQEPJ^5S}jWrTq+S3CJ%9E5|*l8Ke%uw=v ztluV#L0T5+%&N?K@7bNxueB#$Jf3oyEBqwxa0 zY|@yMXK=7N1g3kAnJeztYiqG!mS(sY?CO;~JlBi`&360{03_qzh$lKs8*2+`yYUcx z4$IArK)SVRXuKCkFN4-JpNH-(1|sQjOQ0m48zpBD&srJMb(y=_G9SrJs|vtk_P4(z z@Ho2N{tgMFk&M&eTNBKtCnUZ}4E?w*#~P8{!u41Wz0_QlL7-6szsjo)q`t$1j>Jl; z%-=McS}CIz5rXE1iK_X_SD5A5M~q3FgOaj%mPq6%o0hBeU-GLhE0ZQL5@9&}tBcV8 zZ$QnIr1Rge!GcWoTr3dXrD!KfRO+*H)&hMU4Q~)>U)89)$tji)4 zJnzAF9y}a(^1!Qinaj-5E+PcE({fae$1ty__Y@mMN$GDxISzv;SF4Gk*|EnLVqSTa-&;qxgKP&m4* zjo|0w{Ykvv{$`1&S}ibw`&+&5EI7MJ{@h?*upxaUXsr(2c7+`+-63ZDGScxC`7b3R0rRivb$dpbxSi&eXEDB-3~}bxwUG zS5a8IuH4!Nc|{LC{UU+yXm>e##)R=u1rbtVIamUF!GX#9u#DbSME)#JWxV2K&UtsI zMS^|5yqG>^1jiNf>-3U~*bdeE#;-7ERL=!a{x|@g&hLue(z%OJrEjc+jWeC}C|@|R z+fGxdfXPw{Gs($lP!5rrpE~*s<=VJ$jBP5SE}k8qiw-Xun5LYOz39II-|g#$Oz^Rgpk`+Is*$B z>OZm5D$61CPn}!fw$9NfiSwH(qe!mGZR`8F^RVDK{($0Eh0c$i53HY@Gz>J3-Er6a zo8Io;h@vUc&v_+`mDx*@ryWdb2$UTHZ9(KciL|@sa@9*7jTpq3_)IvHu}q7x6*h^B z35O0$^DWc!6eg9XGB6TNH2}&4b*bGgn z`lPF;LkkxLcwka@R*lPKY+DuNa;%fZc@kV8ds4^$027(2w5=N7$JetaOO=6(r3(DM)?3%D6R zUg>$d=rn4#{09>#PTdP&dkB~(B=8PO*Qt(}tY!CS6`Wzw$u^<;n?gC+4Xu;TTqrR6 z5NEIz(N^yD>6zkU^pv+e-Dy{=_wEVD)MRn-3MRpcZzJSmEpc(u0sKU#peeGGpfCyrN@_yW6uP3VN zEo(Mu{tu-l=9zI>|Ej9Aa=y3bU>n;|(9c@2_uRsKiSVDPuc~L=8w4f_L0GgX^bW5` za#@tr&E@^}-?RQQ9uFGRD5RuDEy*H5e!due9E_Iysmy)8`WA=us+30q!Tztn0xYTe zI*a+OCi#MLjuc=6`?H@Eb60w$+%XMY=(J`0#ybR^a0b@D!_D2 z?88gQ{aeaX+n=ih+d8CV&i++17P(zf9Jg7AB*?1m_?J1?(jnRHIbY3#1ZAlSK|~0g zq^LSY*wJjJY&%#AQ5X$4zA!qLUy@ zLE;bZvZM6guX)f4(GKuoGX}YpqeA}Nw(S&zjNVbR@ovo-l?(!oKFWhq2n$Hy3qg{- z(+AdvPc_b5%b%_%!IJw$U6OUIK}Z4i4SKziAMGSr#+Ms5vd_R+U%j2q`I5!JGRLoZ zJk|69X}{`%3oEEaF-Um!;sPcd0(m9xvB#trkWj~JP?3hL8uQL&7+fc5bEZ~&jn&6F zYW{~|=h^?McXZ?Dmsrb@_x)6ghz*s7;ALTx#eJ^)WFF0VYt_PTPpWiaeXy`nbjUL+ zrW&m0uviEA;A|xNVw4$+Yvrb+1byS1_tbEjFYwDvXB@kjYL&&nB1t473QSem>iCeH z-b_7XGX^^`Y^DH9L#to9e*Dt8ci6`A=DdlU@1qn@fb{7jni}3TPekcYFaw+&q45t z*V$@F-8k@P?<~PzN-ZWo;P#t|&Q!=)r0M1{AkPep!IkPe#gXi;9Ro&DSZ1Q?+x!8x zoRq4PitA~o1zZJ5t!ivgH>m5lD!YJ60Vf^x2#vsKFJGoGt^IvB8uR(pY?VLCj|Ry1 z?f$IG!BW5j9?q3cuuQi!Z;gwknMUu-x9`7a)6cwcH&PXkk-bWgl23>4BBrYXnMpZ` zB3Q(SV}>aR@Wg9rjVDNqM(&xD0}^OFwQel&(87q<10@vH3ojdozy#ab80pLz<>HVY zb-P32ORI(KO+AQbC5po?RT zOJvY%7I-M2i#CZJUxAFK#US3RQ0a~hN5AJ-I??yb0>xFQ1_PUyIcXeYsoeELij<`R zY@2qKS*nnVe{eq|pCaTt@3^^>@ggN}U@Z>tv*{|j>x0PGQMcWz6FzF8YFbUVV$_%o zx}XaXm&wIhlW9Y{N@{Q2Ql(c;h2lp#o>c-<==hsHT7FkEFIM`ncf@@$E+nz;sb+E; zgAX(0+U@CiI69K!IVee0RfaSC7>3N&=ez7$4Mx2>NMq4}a}mOrr5+#h{{sGMzF9NA z!#)qx+O}{O02>`tet9cUr9?$0?B6T7((7umjq47Ei~vaX()M{aOu(>|717i7=TYEU zr_sHqy{hj|?z1j$!lNDtO6wF2X6-L??nOc_DSV;%j6H4#D(#G3QQv z9u0cawV_V&s$}0Im1_IjKvm`b=y=o3ECQ-Xy(RHNkIs8N?9k~>3s~AC_hba!FFfG} z2EJrT55&;BLTB+}BxBI@P9SxP%wvuYfZ}cLrp&AZ#%g4?V|Z*&iMK(1Y`tlXj!7^6 z=$mt?U?QlwPdXbEf(C_##$K5)s4samdL7W|aB+}@08g;-5j9W@fENbS`~Z!7Jr(Ae z-X5u$n%1fN^&19Pr=-8S#H7p>GimO>sybd5R=|%wl*@K))k2czKo)A(yI*?D$G-|( z11hDh#K+?z=lH)UF69Y470uoPKzUfR67~$J*Sc9w7@9T%Bv2wd(rSgtLyr;6-!&)p z3v955a##luhRF<42ZRoOs0+hApQiDu;fx6u{$hER4sr4>GRu$bFd#tBV#=NQTFq{Jug!vd#yk{ho+#6H{`&k{)z1V zM&&ooECQqVYy{!wdAemF=7}-poK4QF7)cLT^h>e4qbDf!~Fv2KC8ijf04MgV%8c;OVKhq?R6@mrCA>VPx zED4zMmyzt5Zw1w<=K(njppmT5?VgpcUgvsXDk%;sE-MYD$}S%*tE(w`H!?~YrFB4l zZs}R}q%n!#>c#Z0Z?gJ@Nhf#FPOb2?l%MI5QNa;0wE?+zpgG6vk+Dz83h*;8^V=1Z zRJQ(${B}v)){9-qX2z`$>(eNMIgy;aDt-2eLQB3$^Tok(2%G#5Bpuc7V3pu3R+7fR zX3qxf!Ne8hBy|xq2E5?_$s5)9&I|Xsl$l-O1YQHxGSb8oZCg9omCnFfhoY|$fQ}t$ zNC~Ho*O9hGiX)=I@O-^`Dl)22q+w_Whitq+tu_>JyOH!VCIb~F=gB0SNcKq{cnXdd z1RF7hiStFH7a@=)7rEo+aog?)H7bY!rcJh`$uc%WFPYM}{YfCtiWs2?$oP2h_$Vnd z=;NF-*Ic1HG_88z(y(GUD2P4LXdOiEJw*&GXRsvTp_O>elHpG{VOlox)BQOHVpehx;1=0< zq>^d&4y5vHUMt?TULkgO0)gfRZuMRxyIHXk+B;g+Q;_!e%C}Cf(Z(%eX)e=6Qyo@g zfPQ4G60FXL5elP?WO5^a9T#|{>sa+@=_7=|`h_4Aeay7#j}GG$g^C8VJbui(pjd)q zFdLH^k0Bl>)E5E)Np(wcl(LakMU2oM))?dfOH`u+8a>xna|Ig?k<>DnaXc<#Z2%BI=s}G>@cV!ZsTwAgS@rX+RBbY86q}f8uY>-S zwbj-`pNo<+sPW>1AklJeqG_5w<(QVKl><<4Y-GOr2F#-|^tqSLq@@1qI62%y81jbn zCEf=FD;C0`OBUz79Ga_mJ$K_JEM|^T^TpFO7(N&ctNG9Bs|@gvGDYkx*&FQ%RWMA2 zTy@Xj0*&Ox-jeyZe`t$~wl6Q6tnvlW=m+ur87&Q-Q1^chtyuE^O?aTHu(ZAzoS3Eoyjg+^6v$tkgx3WDvhk-ky z@Ah$OR+u7j#hX?_d+6bAijH=RHJ2*nLf0UO6c^8>L{>J9|0eqm8Uo$yGcdO3GLW29 z3oEfR*c`4MxM?3);U(B<24-dy4o_;`tbG`%Bm(f-`aFXFjNne1RFU5GLW;$*(9cdE zINd=-0WI5mwT9}=Y!oydIF4)cu^CsW8J?y_k|S@Xr_m8o19ps!{0Fr|=meI;e!D~J zL(Wp}*$TU4l_*ZJ<8Ewz1b1E$3h@$_Fn(@a!Et_=SOEl^kMrb9r~ZmuX7aBnpjG(W zCOj~#4j?~9=2bY8K3FJ|z$u>4HaY=F6S=v+f}*_kRaoaTtbamI1RAA|C5XGHw1oQU zxfmR~@8cKWAc`Ue^BMB<-yW;oBw5Y$!$`7&X%Kd9uQfCy7abWQ#tc%qzdi*lNJi3~A~OymcRW2Q1L+J5Sk-03_M--+;~CvLMWm1B0vY;_?36Sa;37Ma6C4~i z{WyF|wKQi%DTF@C>LMjBdZo+yTr=bj`cJ%QC89qt8f;aBMK$s)J0ZCpQP4)tYrV-x zXYB@R!bAI(#WTD~mQ~CeTi4$w>h&;{6~+f;I6m8yk&CQ>^tKMdZj704z?1wHRT}KX zlNkSVhPq8n|0zY$!~y_W!CU@+9=bX@aAI(^=aPHR1tfiE=TAyQak#f!4F?C=ftM)h z7|{bhsl}@$JLR^J)^Bo_^Vbqjh`W{6 z7Wz>M)48@^u<8BY>&baGHl>hU*DttTmS>xT9Ua+{1TS$}EnIRzRd;}AdM_|kPz2Uv z6!bG;rpO9`dGG~AC~o3;SOhPR&8?_YS@%ycP)Oj?U<2iD1bPdy1uf`EL||Uv!cJPRea?~dDBaodz+@xv27VcAf^FT zFy+Mn0ythunDthnJpnCCqnl3y($K#(M=v%HUv2mL@}FP@`(MqxCaz`i!aR2`o~P!I-L3ZwFc; z(H}TY8Rjnsb432$cK-e?;xK2Q&RRLhHb*fp0Z`JpA!(vl9-h>K$4a?dgoVDJY>O)T z9dLNz4r&mDYx_3A&QZ7zX$l6%Bn<%d7{gL>OA>{s&qeM<1^rr52NC8&9H$Hg*@Fe- z8D3*&FxD0i7c?mmBM2A01YdR%P*j)4w`JK=0kQ+~uYfqYn&eZqbQ&b=bg3I}^o?|` z9ua^W_F-;Z>MBZl-25Io zF?&6#l5tSt1l0{>roEC?CE}ux;3xqJVJ?J?n?jXV3?1cFE?Ct1u(bk`E>NvUi>dB!CVN^9;>z2A(`{)`R{!TW#FaDnwr`=K~~z zI_Wo^9N6l5Tfui+X~|4`7vA_BC3L&ODb?7xZv@iJgDTOP}Evuj4?m!mGm9FI{m*t?eFOca#m{O*2d-JUsuQqEGReW)nynmL9PCw z+sjBuP$Cd;P;R+Wq!1Oade8;kD-z8>!cA}A>4RB3eRd!T0eiEmR&|#gc|eJ6`g_1m z!e3E5@`TK|!^oA73XftQ-Ss)_vTviBFmjQ!fvbPiIv`Z^)gv~RUR z&nYVmc#}CuPdzox%PR~K>$ST08pNr2MfJ)JIgIF1`wbXM|Dd&_0?7g#9b#nK6IO;r z7B2}egJVd}r7lRWf!exxQfp|s7l0L>FeaP(0TOKoBkHdd*F9z-u@){#KmYl=mC8_b zgngPOWBBDh;VPBcWI0KH;m+wZ!1aD4?{98O3g!QdhYEFV(9)&hITo|3gen?1w1B24 ziH|1%*^GdX7kV2`mdT1Z=@iYMnT-?Oj4X_!uq~9!V}a7tEeK8gKJsA*EsfV6#+f@F zkT|w+L|ucaWV0?uvtagoGdxIG%4i1Ib;3OJ5+o68yKrJ?SNaHFgd2RUJt~=$?s(#S<8V?bh_W(B!3?EQ(6MQhk2hLof-Y(x| zx^8In{!&>`Ajf^Z(>`CuFAgHkJvv7%*h&sKUdnzZieHb^K5{op;oD>a5176XsuL`q zRIKE;!}~zqB>2c%sBOO}ifJN>ve_J#m!;F34D~7U8B*4fG3gvMDt`bw=$Zm!Dn&&N zK&=Waid}v_h!Ua|Bhxvpfe-js79d<)1ywsq*8t?lU|XW+av7u<8*8jJ9x+4X5-KGE z6Ja}6&-^V=J^`qJV6E-(NDUL%P)JBN`|v^|w4l6n5m85+{`}c)9@|4l=nh&e2}R}0 z7sM=4B|xhq-540s9|-~Ppb@bdvn-Daa(m~H?TW;^T~Z)bB5y-7UeNH<=bjF`gT)0i zDs3U%(NKV-vIN9-l-2md$=m|o1{&DGO0DWSMG0# zu$*wx0`%6E@`nBq8t~sgQ>Wo>2)PGT!egzpXk8I?tsAble_wD(=)7h8$f9G6Ha;kJ zTSCzkUs`A6UO_~2V2uXLZ?xIHiPJkd{F%+M0;GpKTMKHtv|P%};T@UOkGjWu!AHN{}r zlK7rdY$;Uf>VG(f4}0L(AijU8nJIyzbH*)Gv-+-2KWtM>%+;Whq5qq(s#K^>b(RR? zScEZ?(VAOT>>GQ#HGVYIb5Fs7Rz|tqF}<~7qsBR9V53+6Clh@@^PX-EIl6bX4FAQKEfxp{`tAqL5`+{XO=bh51BV=MH~ z48Ul94EdGVXwOYB4)RgZZTY3-U|45(@pyqW*?AK*F0~z4mzl9TxB|>yw2<}1o5auP`EfM@3tE3t zw8Yrbqx4+II=+^?Za<HQA7PrMR`x%b)>$2r3&uVKrxw5EQCuwR)b z2D5svUN0%6gfFNCTsHYUEaU?;!WYl;R=FON1Kxow@YuyY7(t68bBJgwwjb2{{YhXS z0ZFEEWfmVYT{tsv;l)nAs(}f5B;L~}#tCH@tLsoHABiYRUs-?6(9_6TBW!w}lN$cc zqdSwy_{W&Dg}d9)o6sCf!avc7+;|bX(9DQ&Ocs0T)Qy=BmEwAoBf>8iz-24L`;LGK zc12$(S~Ubo`87$PptGi*w+k>zg8^(BvNc*yi&53}?lcrIuBc|Sys1-ZFZ?iWeKK#K zFCOsT8UzpiHn;6njiTC@s^2~f*DM|L2*|rlv%^bbq$D`-!+Rzu#{J&u_}2oM&`ag1FzD>y{h z78q*Y2w3ELk*LTvqe*^t5ET~|W}Hun0YCCHTU0Tz;yfs_3gvv}KB`?k&~gXzxF^eW zyj-qP@MJ2(I}jyOpZh}VO?P>Xdkq=M@b-Q7gF=vI9zQKBhlUDPcY$Fw9*L-m1OO;- zehs&KtGm7?qgGj@-el-yOd*<)?QW)$emve0EOL076I`*G1#vWP!+=p{_h&t{)O-5J z>ISi|B8NErW8lBRx7K6=Z-RN{vBz@BOH9hhVy2^9(Wg^nSN5woI^`_fK1skqub`Kk zBohvFPHAAqf;03ih5@^6@ejyuII1vA+;8Mhd|vUIjVY{brY z!XZS2XoA5Cs410E&peI#6eyJYQ2dol1^BU=SaX>m0$-0VbUm$b@aC>yxyBfqNv(vI z-7PsXylI;A&F1whT;&S-rZ>-t&_8ml4VxM>ZRci=(bfMWTYpy*>|JyI?VQjnFoEri!Tmux|C?IeD-k}+T2+abtf4hL^&8~1Tl&Ak_P=5?ZBVZwaA z$yeAL32_fANzR-b2uO{BIc~mtIA2}lmePt6E5Z{fGAe?5qBLqdI`Hl`W4ZN;7p5zS zNJBIMzX~U(_tpJd>E^}vZ0T?k#{Y(txC%4jt*5cJyKv;kHc)2Kl+z;xNw3-~+$in^ z|Mnuc!ADH&gTsbI-L6AjObohkVbLt>@(2gsI|KL;9YsEQJ*H7T)_X6TKQ>d_Nq9E` zcJ9-2&}=Ndv>ZN7PKchOQ6rrvbcnDdgzSORe$jakt%7Q13|ke%Bort;2|w_U@!o5k zwOib=$QMz2b0^-aHLVCugRK8Y>_do1RQ1U0GU167-P1`no{Sl2=J*`|#yAZ5IE>mi zd@#oZb)9s_b1sG@vO{z-n8Snw?jA_eD@KK0L3cg+S6xMgym#XEIxNdNgcD?~F_P$6+|CTf+)O zo5t=(ni#Yog8n61Q|Z!EDp*(~(zfN^r#wuaN44Y(Fmu4X3Qfhu3{>W4Trq1@Q6Mi*%aJqz^UCR8J zvt(#$JWcRZ!~L`?tG-m3ZuY8}`HlK422v92>{I1UC~g_5vOqFRZ2RGx%a<38n(5nE z3`pg0$)!$J-zRc!p3Phcx?5upE}OCt*qqJt?%`hMjZJ>;iMgKhaimNVt{*wAfO}pk zQQoFx6sfN1+8s>CP!S~TdnP8VaY5)y)AxoBFS`Dw7)+83BClp2o{zmevcb)!mTe#~ z3$Ye7AiP421HkUVTi|us)3Xa#mk2EjVJe$Nh`mi9T}H}|FAn}?Q)R*MIwy{f^qPwb zv02+Ge_Rl6xHh#g0iS1K;66Rj5k>}N57*Y*f$HoYbXsI9&k_NcL1Qk_Z`VogtWbb{ z?hK`lXl7;#V}tR5)V!<+?~n;=0akq6;018m0i23aaI>hHiC!FKub|kC&^hqez>z#p zKZ%zK{CaSswOJ3rwJEA2F)WEfQcz!=c?L%+SdOEhG#s%*PfWlBJ^)~hz2 zjAv(x(AA_yw`;&wc$`F(dQ?&oP8wbBT?XFfwBkdndHfN{76fkAekZ+YtzryGkDL`*4-_;|!6^pdYOCAdoHe?K`5k2EgA@ z{3%5?AoNhU%)CF^T*B@ihiYu32XCX@pDOt$jpBQyiKl00dwBDX5_Ma4#0j>araH^P zH`6{sbD_aA4osb$LICGAwIN{d_xB+79gBEsr9PO7I9~^D!g%1gZ*LZf+JVX26+L*D zxj{>3vIS4L-$&hdO;)@uftK{L>2Djm_b2bkqhDCPcqMYEXf>b2wWdjpg)M%v=Au4~e_A7yg$0GmEiA>GfIgJrOU_$B z0u$vww7n-PY558p7pje)@0ML7Cqnj7Z4oa8Dj$J{!=~om;AT3y{UQHXxo2n|yEvPx zU>P1ol<>XmNFn4#xSDb=9)VgN3oLQ6dSW+CDs@Bk4;o0trYit@IK-D`DHWxv&`6ig z)}+6n8Y}h5DAXuu@e9RBMa*LHX~<~s<7$H#iaoc%TklKh^F=jc+BLXdP?V}VMGw9&q+@01scFYo+qtCh zN+TK3o%M1#c9>igqiO6}wZR2zOOi|@t&<(x3f`c);JkaMD@ASh&Q|X+3~_BhQatje zJ3}n)FJ81UJGA!Ozgb@Xd!%>X`6G5xtLyf!vP&f&VLA5s5ciO3Xvefs4a^&!H?k{60N7R%sm)(mmns{|HMsyrQUb`>41&rbXHxDQaFop@g(G55kd z-gVioB$@yq9kRlzD|nd$L6^oh=UiL4(~&Ih+9B%#22gP~pwFUrt|gf@lm1gH`KIK$ zkA9IATSGQZYkSJ1<&{8JTV|{j7RlQu%d31ci3Vo8BYC9=SWGP)9arr862GA{dy|qo zTvsEUxd5h6LcEKsaKPwDRO6mAGNI?PjvxCaquu`PB?*#Mx*rF$*kUs|UZ+@1;kSyS z)_|*^^M2x7Bfmh--o>=ME1;b`ll!@Ml13zCM^0Ypn1O|$2yw^yZXT*L`8ea4}eWEw} z@w?crR!f?`lpv-VVUaaMF{3Z-U|)-FbJ&y61b)9>3a< zRS;o9>g55d(|q@Ch#X{=^+bi6xBL(e!P~?gM(Fe+)jyBdpnA zfFOgJ6j@1H*4Zw2m>M7WMQVvr;S)X{DcvPqo*@ZEl_$CAzp7N9M0&N5W4R1MGv@Y@ z;R}0ZnT{c7qo2$q?*4#{7i9{m6#|b;z#PKFtBEiIBS_P^fKq01sh&D~Yj7ppxk>8k zbCXqM!~Ac$QGKfGH4ApzCzOww46!<0PqgSP;`Yya!FvIbApa%Kq<5EmMlc}D=mY`D za<*_2{ivVZVa`{h>zvMvQ3cu9g?+bjvG{?FIIcoFjCw|U@`CW?!QXt16{l5DpVytN zt}W4q@}hkAlwY8m+L$XTc#H*u+y!M^;uY4ft9E{n#P8rDxZ5zeQJCu z`Q1oVEjP#Sl$9gVG0TgP7ChXvOtjn@s^?#|DQ6mR1MA(QcsyoNe-R4Um3lo zpCE-`lCwJmv$kKp1%TOr#aIo(9qmu-FRq_&Inw_7Co1D+$#@=WgWuJHfDWOZS28uZ z^skh=1lv@lyEZz$rBi#qC%PJalJF)#r{I*D$NCLwS|c_WC=%X*2uu0SO6=F(Lrn7- zZ*XXH>Ep#X%(Z}9A}t>s`7GueR#)#^^-i>d#JjR`j^DiH~4zxbuYTL)aCjrm_&fo5MM!pKD$NZIJ{l^L#L6 zJ`8y3;A3*eDh@gFrIxJ%b=8YGKGj@#;3O8KkB6~*T5m`z&Jaiv-icRtB z0++{gaL!XsJWc()5yC?7Q%KaB&3S{#Cc5{n&$@Ct*%AwRIZPz_1 z3UOK2)mia_&~W0Xw(nj`=n1Or=~<&32WKROA14AmegaV%$k^~dbZenc{zk`t0Jt2j zHNrFp;;eeuKikg`4cOWz!m5L5z{a|q{$}qlQn}u{((sg?8BZ8KM`uX5TfwQ(%ik3g zVp7=>YPhR=ltx6(-bY)N3}S_d1y?eAnD=DFh>kG)nfat9EpBb-t2z!O52WFU6_Hl{ z5P<5_-D+W|h_FDgsU0(>iXHZkNe$XLqSBQGJJ6NA+FlMWjHlGH$On#r4mCV3Cla&X z*COJ4$GyFC61~M3|I9y(CAjx}+Aqrg(zRs|;S^Xtdj+S2)u`t)K5KO;H=w-u8^glC zruXw(Svd%mH?8s4SP={T8p|IP2E}r!O^|gQ3?h*eHY^No6?s;o@F3uj(S!&V8eXAb zlUl>}h~Ld699T+!%--B+v7&y!qB@=uAwP%I}Wnw>H3_RR{MS8rTp zb1!BG#tiiK#PLMJQ#~;`;ItJ0!HIl0-aIE#x0rigAK?dgwsx1YT1Bw9O+%C)jX~Cu z1d->Il84NF+k_JY^x}~iC>OZ2Z&ZkE8=QcWd4O&Yg0J!4kNz24&BXaUWUA6zvG`HtU?9kAT!qWJ z5Ye2rtXY=FNIv%oS<`42+a=~w363DI2ImkeS%t$iw^x|l0QtWPwOilOVmH0J(L60o zn=%iHum@avoGVP(c({KFfc;=(_9wDB9hyrN`vJb zrkN0BEwG>3T=f(`X)1x^F?2dU#?Wu4%A26a%GFhS>+9T}XtG<#@j=uWn4onzeCSk* zbu}i@f17$bTeK03nQrl6yip;f&7Rlc<902&;S5?yecnV>;Gh3wDJ09@egP5 zNqkLy`cW;Qvil9hW7m$jM92f3puL#-^4 z%K+RW{vDZ%mA!x(;|&Y^1CephNy=c9%MRhTo5onwdvvwB5{B@2;?`HuRZ}-!bU$8= zdbG8G4b>ornRJk&-G~&NUk}JTv zccx&#&F!Ne;nY%ZXAb$vp0!t}+b{Jz9?J;yaG$+9hN%);z+gYdenGgJT9dD%?sqqd z2azA6-PzE1IR%y_$1pK&`A~jXs*GmaUF);JiZ((WTt!=Vd_E4*)LA1z4)^|gY60EA zAY>C5vt(a|LRY!^aF5+s6wxuZE*B_k_Qfcd1eyc8>~rlTcz=vQ)NW~k?t#rehKuzw z6;2CMfpvx^2!d+~P0dBsx&PusqYgXSRRS!V;hR(tW^oX18U+7)j8>Dr>wB@!zQ^*Nb=}}>y0@A&v}Ib&-&v|o5i#KNm7U&SXZi+v1O%)7ZW?n zsn5J7!;{gd>9UZ~QUe|RYfOi;%@VN3MB85_kcnR zF0&;p3_uyKl{LO&@?0JTFg*eSEd?oTz`4WS?#p?9h%F|=1bR08Qs$U=R!ONi@-;@H z>*;oeAdpFcXsxp!zgO#*U)9<%L(l@Bx&s;{PmtCNSD$9atd(fZ)_IimDr(8S^a15? zB=CTN+hT+8Yqe~r%4Pu0>G-De$KuNCklEL;6$(7?L^8i_Tu?wz=jY&}E#>V)f?rSH z==3)zgY`%$*TPOQudj5ZJh1<(62X+6DGm-zX zm${imSdU6tEFSyBeCqQ53XVDWbo$6U=U=U+-;qlF&~Mw=iW#bnza!(_M9W7VBBaa) zBR2mII>ghM@Pl#0pQV~A6Y90R_~Ov(dKXW=M@^&azB~Ojyd?RK-VA;BqGiqWVlo?um*0i-4?X#Y-$G@~7?+BQ;|7 zj*7ZYOW25w1F`$3cC}Fa{~;~okNt*c&(ULM3Bb{^R~QtPz$i;}z~{AQW49`aULP{a z(G+s&xt8tG+Cq$mmrAtfwL3H9#}0V=B3l{}>V*bvT3C>VR9H_y&s-3#bDu{s7QmnW ziFR=*m}=0PfNi3uljnMGe4ES?J|E;j(Jy!)+)uWvRV#w-b*DAq4TZi||1Lc2k=VgK zW^vS1Wt3y6dln0DtT74x?zJqoDqAXFib6QM!(bF0x|7@JX(V65A1<--XTvED+7`P% zm(9tUR{aT62|{#{TQWNeubH7mWStuz{~+5K1y8}HdT#cT$dF!4&GG^{nooZ%bvLPs z7tIhrtdeM+Kh0Dt=c~oOrq~vGtxrEquC_A3@AhHMQiOFFh91*Q z?~aR-Sp0aNMmmE0phsjYZ4)qW=o3}S{E5viz(1x%y?=Af45{WClu18(B{R9mtnQZQ z9$-i#=sH4HAa)Eo`>U$Zo#N5109^v{6613@a=m2DB+@`J%94d;epn;s5}w{IdNNsKJT`TTQk5VQnLDpeBid`>jaerL0Kr+~Gz=Imz4VEkjNfF5R`371;>;Zoe=Kko zcE$4v9^^=+&@4N`I#MDdGt!})c{u#Kw%K%W;R_WxNeFWps`uAuP%U8TwTvQ}P+KLQ z#;c-Sp2&|1D{LCCCXMZdAM2-sIJu?u&N|qei&VlZ+GF!JF;+5sM7O1&*C^G#CzNyp zCzh8Su_m?_TD!oh(&{K5wzp0V3nP}*?nLX>Uiz~n_mL7ac~qa*9MI;ql^FXu_%L`k z-G@aP3Z1gI=~&~H%R(R@k{IfZN|x1;?EJ>pRO!KuJ-#v|7dy5q$zRC69UN}lC91?f z5nygUxLH5+g4|I>y~4$BZ`#0cGz@IX(&<8K*c_H;P@WqRe!)?L2o%CixUlsaC+I%l z0mv^Vp~u)!8oBZxdtd%D;U2+xeAR_E~8(vaYFS%;Yd+w}+#U-U!Lw zg$`=spD!b*XxOC!m$XYy_c&=;70@pTc7qIjU6aq{%@IE2@?rgpd9=@f*b-HoF&XFe z`+G-0x@W_?HFll*(C0=BH5;W*b2P|(n-mrTCkpM8e*p>q9?X6?91!8psM=C~HPJJ^ zKMo1+s$ae5$cPOck7MLBU-~nnnJycds|THH&g12yGyvmvb4^o?3-P`FyKhuGz9?0- zc?GHcSDd{X+RO^2@{<~;j^CIOeiUERr=(_6@}ip!t^5*^&;QuthL#@dUXOzJcx1 zNYO}p~Mrw2*9pCY+;CEFSnAfqiMjzT$gigTq6V6Zs)>;?yf zg#|L_w;U59bWR1H3#-R%r%mqoR;uSZVxGMl5fY-((g243KtPTiOW4|aNC4An)i&Hj zEPce4w6rvQmBHU|tKrp2A7_Q!ML= zz~AZu$V6|rr|%;lW89G*9qZeI{D4Tj#(0ngcq6rzcmH`?489h!=H#V zs=3{*nB^~_AOI$JXC^9w6q{7%;Ey;6nEMHdtv5SPAj=^j^FQ|mSK;u(J@4e=K8Skv z5_GrG?8jC}+C~94n(>kQDt28#uFcmQhW0EpURX-X95dB=NhoFXyL#RRJJ`*Wd_P+7>SFBU(!F#2a`*qV)Qcm>3NHUE%nM_{tw0B?3Gmb%d%KQjglzGEr`I^`fe^ca9@ddT+-@Slzlo6@E15z zVL+SSp04!nW#;5n;u(I5Mt|dgHTZFC&n9PsiY8jK&lXA2t#QD$_NSRG}CsLBWu0~Yab7|QNWdV zZx-4_HELW>OLY-Vs8i>!2Rd=zv^IrDZ|1nW4+=tYId43dTrQ7M^Bpr|;P-oL$)J7K zhPfHhC2V_rG6RFjF~GI7oh_x7hF>uLxJsxHFe&u8&zukwS0GTR7Z1rtrzgs5Y$z`Q z+Kz%#%~Kjac;_={V!(hvbNhhc`MsHPPsyh@EOB(jFOfek+}$KdCRJ2zlJz-)PD7mS~ud z_X8`A2CEIb0_|cRj`TSk)xP;H9Te-9+eJe)9~#aF(N+^(T!mlfaq(<^VyA}Tc=pmj zDg*%+5f>WuES*5z+9EQQX3=d%3ZUUrE_|@YY2C-jt&ZuH+X&z~*J@1UR6 zi15Ba!YF8D|8{83?(zm&o0vR{K^(-0@pm=k8E|4EVpJE*dVSpUyRte z^0Wq&2XT76^lXzUp}1I@3=tgP4Av+3Y3J)H%j|BXujn=uQrGhD23%u^YU6aJv&YW_ z!aBEs^hL@s!qr3~fxrQ@kS>~Sy*jVkV_K-JRJa`szd2+DM)nhqW>9Il@BBNNU`Q47eUBoOk@ZlbVzOvJ*(mDYb52Fjv&U zs}@cQIwG|UG3FE?5l-D)nSgk3PiVcjuYB6<7@97>IIpGuOt$>JhB-hK%=%`|zU9xQ z?p^Qk<4HTNRPw}PeMea-OtzEtqSAI>-KBT(-n^lP@#s&p7)F1I83b1dRqLq(c=91* zRn}bxWFCu#b@&Ucb$-bdJ+yQ`46=hxlFIlcz)h{Dsse7U@z$wi1OWNf`_@%i4>uGg zI&ipXpv_NZmbZ+=b4&KQjao6t8CZTgy=!yF5-Ycf64;N23(D3#>I!r*;+NlhVeccg`1 z575%gj}+Bb!e6a)=_#6T~lY6EzRE&f1Z_~jKCI~DXrfryQ)FL5tF;9;X2G?R$Re>GL#lGN0Yc1n&oVc^S5VPV3=^RLO3;?JQsl_%*3LgNAQwy2i=%GX!t6^cF z?GG*)gE@EnN3RohwTyTbnq4l~Xo#3$3wik_jd`*(sRbmsIY}3Ze9)3-lV->M3B5Xw zP9?;@#`?45SyvzwsGDRi#Jm#^T^v{UW)9nWWPxClb=fJu@uJtZh*so%P-y-=5aJuS%>FWS>2BJqR z>ifraK7q#rRASJSpF%ojjCx-onwyQe2!ZGj z8;9~jfxQfAG4+EY2XV01t8QO($`tECs9h-WeumLb6ka(A3nZzDFE3tdx%Mkv*RbKS z-90o7(``V;jGHK9Pf!$;OcWcNnzzy)W$oLf8YAf_%>NEgD5fC!#E>tWlXY_O-S-_00Jzu%vv@V z;~4}HL{v%I&u80-60%H=9GKdv3 z31;;egF6*O>lb-zK1MJYf$Nw=?YfrC_nVXH)}}!`$WlXo$L-l@0ETJ(a~w$q%om-a ztV-Nw7P1X3w#rcE?{`Pl5m!L&AB_{&Gw2Xt)^cfT`60=Dy{;f#)J1=S9*Ok}*aL27 zG=W787)*FiLtMlihO!1GY6`|#nY&diulp&~=}3txQi=I~K8g)1j>o_aVaAzDHw1L9 z=gnMaZ=^R&*{FE=ZW2r=p&N1HMWl2QJ67L08q+eE)6XmnBXPg$KrSm$o_?hBj_DIAlUIEWsu--nC`DeX_b0qo+C=3-s_ zUc;2u0%V9+dT{aZ_QL+sRcF0{vI}zp9xQ#W$vWR-Bwdc9E&kbjXl}1Gv9Zq#egtsE z+za2@CZ4H{@ta%ei?yJfJ&)zjEK3YjA>h$#Npz8xN5N=_*9g4HL!I>*(W@K*DWn;S zD>w^nT2LyjqU|F#(5IHD{fvju@dh>xThf0=X1!`likIo?$vU;y2I5Z3B%9Xu@zZ-?sP$yew zGhOs#b^u{PQ;;&$`NP?P*qK|FhOCVDI!N~AsCv%6<68%nl~wC4h)Z(I+>Y(h@{3v8 zBrnR|<4l-6hvH7fg7J;r_I4C=>{5%xtt!r4`iDGFkDib4DST0_GA?f^X(Nb&Z6Ca~ zZXjdP-H9#!Hk+4)ZBcw9yhc!@TYh^TlK=}4is$L>f~s+I`#;pp1DA7(vYJ(>ood{tCrGu68M!_J zVP(HY$HkW#EVj6sTTuS5jY94V_GUZ+T@Rtsyg}6DXs~)HKTgc-t_xD-Tn4W_cj=Q zYvss!|D;0sal(D zF5YL$7CXGtIESJ0SyvVuoTNR%Ckw=c0@%D@~$HPC>74ZZu&~7VT%j`wwQU< z5nro^Pw6VZe72*FlnrTLYe@Zpt!;k^MHOH6Y{c=VveGZ<`QVk2=j$gmh?DH?>h8Pc zca-OVt8D?Rwt@Kh6SAu(4gP8VUuDKLen3edSTLWlg(IO= z@b=n#H1-5OjTw@tqBD#%c&Nl#`eLhUR$LBT^wFp8D}aYzdonE@{Lr|bkZbd=LZ zq}1zOkc;!~%qZFV7nx`*s*lqgZc3h31~dmm)`lKaUSTl6@q1P}YPjJeCoge$k+Qhq zW!vTnbFcGt`JzRd?Gt-k?3Z(jsUyGY-7JD&Q8^6F~pz6MRzfA`OPq16P zr{SY~oPR|tJ2~lGyRWa0^)_c0&Ux{;ZwameZet?H)|J*!e%$csn3U4Cl6Ris`R{j& z#lv`H_1(9m$DgzAKj=Xv!iZ&*WO9NtP?hCG$$zCoRmXeUqj%-HGeYB>NeRl;NNkBb zqsnPGx3EVYmm`yZs@d~SnVpc{t^Sn%+ z!ai9-QV&M$5n=YQA21pwuy+L)<(Sm?#`5$mWEG0HgF0%_NrLX(Qtn8N@!-E%c;}jN zReVeO?BI)ek~pw8?=M{d5KD~Fz=4#HVSu9xmD(=0JyW zT-PpO8m81Ojg5X4u%P2%Ve02v?sn-AO1@U%ZX%_12r#i#*p6`Nj{-1rY~{f{X7^@R z`p@6(luqSg``KxAAdjdCx}$z1G{d?}T4;`BVu2nRmAt~C;${yW`hMhqW~MTxA*Ek~ zJdPRTQW0>T!XR|B;~k z|K#^Punu@6+F}K2io`(gXN}~1Q(ue%%i1og#*Nle=G2ueygi8-6!!oH>-2P5dyRmu z;#Qy_h}{(J0Pv8v9dgiRmPrh5ED^{7#b{R(c|;Uk_iF^7+lqFJ>HJ#6gA~1#REWl6 z?F=}7T%(R03PzY;`N9prIF>$ZWdvBH&4ALM2gw2aO?D=!lin3|%8?vOGeyTF$OxIL z9Ig`~00lrtGedZ<8Dgt=i*Q;&4RgYSl|hwW!%?esxEib*kl4fjpL|`R-oVTZkQ>uh z@=1c48q(j$-9vkN_IbF)SyH(rY`RVOXrf)zT^hmyX$^j8eat@(^rk~-sPu>6;(_K7 zB39F&Nn7sw#1nOtdUQ0lB?y#h5X$G(*>2z;W453&i;Zl_Uv99a#r>E1+8mBI_|pp7 zEeGo?*p{&lFl+$6_2gON zElsJcfFn^h0DmlB_EOq;v$8p+k8(Fw3ty@-al=(nSZy*bm?Vj>K* zOaA8;N21MQ54`;1LyQ(>S$$mA7@CR)o@6c_e}PsNBucCQM#DN?16>iiwsno3|B?14 zQZvRKc`d_vg1DQpNTI7iSSWRX%4Dc$_MeRf_ig3){b%jX+=|C(!(T?qx|CYQv`c2w`^)9~HNz7~7%{?F#gQ|yKT zSRUgo>r)hXx|wb^na3zB5j7ou(qq{0$n@25dToG5hMCZ!vC>S&(0u(jmS}d#!vU-) zPA3>qy^1qOahc8h@YPdd*iFGm$HDU&bCO^}Dg!)};r9YT!spfgq#`gH^KU&-xEQM_ zCz4!O^U!~nkLNMwg6qfpe<1Aig0U}aINAf90*>h3}DQ)Sh4NhVo_XS&1R`G24llPm(%f6sm2+yHG>iX-b+ z{pI52?)76ovYNn^K^DQ|Am!B;WT3@nmMPq~dg@m7}fG zi;4oN;YgJ>v?U2x{iDJW(O_926l{f#nqqn$Jm)|CMR^Hx8uYa4)<+V)OPMZ_D zMPmmD7H4DPInIqwr;|?KoyV5A)BR-FQ;+OCwN@ISs8uyAwMnM?&vF9EY981t5J+VH zKyTB|SuZ_Fo_&Myc&5Mgs;%6HqJHEaLgd`gzI5=9@Ijs6Y;^epwc1A5uIGia|B~5E zNsVUF7sKaoFrk3Z981bxb@6s#>F)c5_E?LKoxP>kwEg?lg%hyk8C8;T*-8<sb?y~h;i&=+%KBpVoTR*hW5ulh0I?xFp31Yjp4A4O{fvk_nMJ0 z2p=mbtFk~WpqWnQu7C2Vb&Pep6{$k>ppq(av>MdkxA^ZoY8$S2tfB=Zcsat*9JoU` zGX%vu5R@Sh-oVE{PXM8)PTYuPS(e>XRj=B1KV35Cx;p-Ay;#-IJ_i_5-^0-$&xzfR>h`JsaAWgO|mrbbr zV~8hWI>uwvQ7it9QuD2C&a7f-H6}2DOt!oU5%B%2A6)lS%QK!%qBmE>zTMy?O}Ed# z{##3_{Z=RUL{}mYMmVc{UW;jx+r*6=WAHbjk%3M#HY{q~CE^m(abn(EMA67o?6S|X zcVhw~gC)8hB83(9?L4k5cwW9dKFQl6IVImSHrgl;G8n5yorR_63RC-u53QGnlwTj0 zfxRiKPe3{ePcdI%NQ?UJHaC?>Mbgr83xcer;-y2%k1SXigl>zJE0DWy*R72Aouosl zVY_u~j7EfL|JoxeI9^)*@h9&uQPs%esztaEC(<<;q>fu@F+79ZhGWDTh!I}P7Vs1S zIN(bhrT1b!w(2vn#I^o5`>|7KmHa}GgU;A@dbw<*xJK}$5D~OqJ*_8N`Mt}dEU!FX zj%<_l|D~KBfpmQNxvTY(k1a?FMU4Gr*Zn)rpz9<9e&`NOhIX_6K|wV3t<~vYU{5>Y zL1sU^R8$AFzMTI}5MwSGUjr+wMH!8&A!+e;KxZKgUlmWmONR1-vV<4n;bgC>=9Sor zgFqUr!EOg&`YuG1NGxR*FN`Mq7j?HLDJer@fKyG!7x{HJc4?qpkuWI7qb%a1G- zW%d6m1i<|IAH~|C{}O`dC};)!k7r~5yj05?hkBO8Bymw{k#>OgW%fYGCW|XKQo+vN z)+$5o|A5BWdoDhqzqoueA?@j)1fl1YNC8Y)_+SG?xj_z*+Aq6}c*LGXHNSaJW%%18 zO#>}yKC!)oxWh+y`bx^a+=ZPs2aJ26u++;zEW$9>T znLYONy$aifw)>StCy-^@$mj2|>ce~gD5gGJ9*2EI(hRB<{k#DrwFlR>O(#`T29jJb zkW`$sZKm3=Ff#^&D4Go>4l1asA~Yj`V7btQUt`FkUQkTmyKF64f$&*^)5bN&md4fH#_337xk;3y`p(~0HUIk35IP(> zkVYS&4XQUJNdM0m+pHx7PckH3I^GF z9GUR4Ul;>L?)Hh@1?WV!<@1rJUiLF#o zGAt?uyjgROV)OJQ1(bBM&qG3_x-^MK6vZ^5pd;ZTFuPg4{S|PHyMTzjPoD%io7@Fb zZjssPAF_@3Hu)(Ya@Qgjdh4@mt!T=~)yi(&LLl*4Lru37g&w?BtGqI$UP#E(;;b;< z?A?p%3$r!PH1nkmOxDhg+RCxV#V4sPkGQ0ufZOv*p)nam|>24ioHNXPPreC?zm?|PR16gOa+{h ze^LzpEDgS%bHy*4DwN{VF4GfU?OR}it2C+#r&CJc za5Je%?Ym+a=wWM38sS9VZ8?VfP#9QWSCO*naK9v6(vT(@Q=xQk^s0QzjxeD8{CXLh z31s*b3~WL8+ctFG6op{X!>4b0m$thGFe+1{qAPaoyJ&};@VNAQ*j}XNV7=cL-Xt0p zTaZP5lth2Eh-gvRV~rk1=NNH33k%wDwayjyL~6iec^e9@p9o|qU?9U zC3MJ>)(<@S3nJ3JSBf-ay6TvN=vv4>#r+GFr6mP7aYN8#6N`k0Xa{84s&oZm+ED$m zv+@LSLq+y)SmRg(k%y~5_bJVZ-m`R{^MdCxtjEOUlFG$s6yv^WGi+RZkFsX zo;#)%2Wt%S1V;%l%EDvFd^qcHJuYz|4lNkNFX`Bgg$l)Tv7qCBmsu!LWzTc>ADJ_`5XH{M(j8|OY2 zvYe3KUax}n1;Q09&Ub+6ZAi#;y(~+LEdG z$#8!+iveWU@_3#gofrCMW8d``HHy#?Ch{}>O8ZiWUo&L(s1UmjMAn9ygII5~;MEY!kLun*UYbZZRj z`tJJFfiPgR$2Aw`W~T{a0c@nc`k#f}LXwAijCVWu>iIk^!|a@uCWYx|q@V0!J%d_d~Z+z7@RUp+#BaG;s*fN=8-YynZ2_ z$D0K9S!#5Q&UeFPdmfdmxk9%d_(?h zg$&)Gv1a6`8_&vOutf3LOsUh~$$#ouO~i-5u9LM7IoZRu5J|;-A5OdGNT-@0l6a(q zbKWq|#Au-3#$>npug!JA3wJ&)wfGIr-JaxPV`F{!Zg01_dVWNyNqEa_GE^5AV)?|1 z+*~L}*xuS>pKIQ7oKALy1AsvxFmcY}VLNoszTIixU`T30Hcao4~q*A%LlI z%-PEYOEVNtCsBP{=V}q;a`j`w5ikcMGQereAP$9^11ErgcTjKH8|cw zf~ui6WCI&rf*)Lp2?=JuKJ)T=c@V*_{}-hxtxf~1hgkbSp~j$;9Uzx;obK){?~Q!- z@52g+gi<^nWcL9X8&R`!qiTG$)igqEIV*;Lnr`&Qfd)Cc_*h`EmNuSu6G>=U-vPwK z8_Y>=1E%XI-*83VG$#bsXV?t~_?N_wn1wng3*)ID(N%{97fH9=YPM^ImwhforO?+n za1bDIWCjNzxf|e>JmQyUm&<(@S&(dPBSQXyS*4>^TLE%j(pG7|kMF|ZoTk-n7f2bx zlfg`_sO1dxN5Rj@O@b~4(uIh=xJ(oRnMlh>F(=ay*G_s_^3kO_wv(j5??DJ-9}vB$ zW%Uv<+}HLO;9zFXa{HrFbb|DoH9ab54&s`JL*yhycis)o4wQ7Q0_1+JztN$Xs#nF| z3M#EO2}g#}8yR9YF$1KbErJG{mNVCT9U}jesKu_XRkLh--WyZ%yZ9MpVNnNTW2}&p z86Z1xosgLpE8#Bc@>{ZCefkE?l4-yot>O_^umN3}SEdZJ>;wvtkqRXC;z-oEI2sN6L*=b)Fa-H30oXnLLwwbN81<%tv-4dW#WJ+rYMVb=-ki z$=#gVWSQ4GzT-!G99&fmo?x`J4Xyc-#epi8Gl#DnQq@tDotl)tsFt!Dp^oXmlch+j zCdhbQA9M*rSz{@~(*))7Qi_k~n zekBAX?V}0Jsi48vno`;ts%Xu(Z7iy1xI)n**z|YxQ6%_2f=qJgfbVqvjWoSsE<2h3 ziIN0C-~gI_!PC^&2%yjdM~m{{Y!g=m#k^k`at1|1lM|U?8r`T$qjS5q=tC!T)OR{g z$O@oj?T|nWSnW1Lz<}%U{(BOZ{}mhnzvM;-YvjL51UnFASB5>G4VEZg`EIz0!QlJ_ z4tP)_!hWbNBV?pgHT~Z42*<`U#hmhs4^>s-?`no@h+?z32`Dys$DnsT_q7fD#^|z^ zzwJJWe=v!8@+l;Antu1GE2NN}83QWZ7_rDKsR62q9OkXA`1nJR^LssFk0{7_DNmlyM zA4D%?gx}#^B{SDR4HajVDy_{0r8UOAQ}^CJ``9*FY@N7{5a46wA zF(?k19BWpzsA%D#bOgIh=~G^Hq;{42Pz`v9x?o_Vm{nAT;G%)jV}h*`-O+2_;JL)E z>}Gchogm36b722fFxm+n0{MQ`fI*|PNx`v(CBGSoH>YpicE-efidT~2Z%3q)R1J5274tRoPOmBoq_CF9-gd+GDqL(n7I;M3E>%{bTXYA)q8M7e&Jgf z$sF_phB6m|S0oo531dS2)BD5JQ*VEr_I%<5Tm5lhRfwf}?89)&h^p!xt5%JApG2!Y zc<*j67bB0nNOH0kmE_1a=yPAI{@FPIIZ&dK;=a4v>p@WS7U~MHpAhJBd}&yh5bz3a z_SC!Be-oAwK5%|~A*Z&>=y97jPcqY#R-}4fjIqKp|EM~U7ih$+RU9V*5 z18`AVnf${Q{bd~JU3Rm-8t2SH7L%l2N%2&Z4F~uUUoufQp!RV|4W@r?OgA&$)-pGagFPGcP9Q&{I5;q0uHEUga)}aK%W9?2+&oc)s4hXpgescjVJo?CUsGk@aE5x)$M1z!RT9tf(xV&7>z=M@$!4cHO7xKLx&TSvG)IVi6 z_p~^%HW&yrF-C6UgW<8|c^sS=o{%Q3R)rqqVE^-o)H7%gFO^jJYP9!Op*2*e!n}8D z&&l2uxHB7ies+IQr^1&;iZX~o&{(u?y1$TjHeX`o{ytj zMmQ8t(-#6!5brn}%{jy?be%ZY<*(C~X1$ll2%kxneg|Qih^V;5SH(mSuql-7WLOTW z&mQXilnMVR#2;KcdfJR9d0p1~E@QbOx zG~9QchLS@hHfAfTV+tVx;kO~EW<-od*2*BX;?x(xiz`$ zkuHCTx%iFp25~szNC|9*p!^PbZ2R1v1@+1P$1*aTqUx-C0Ai3npcrzh6=I2B6sgvE zmcl_inU5V+Ufir2<_zl3dl*Irw(GCoy!r^6&{albQ9ml z4N)iz6OxXW67mep%ouGuB%B2GVZ9(Np&&5GQtb>TKF`QxjF)`bs@sOqJfR-DlP^S% zVeEaNy4`DY+iH|h;&&9^ECod{a;E0)E)!Kl6?APLmh~(!spd zU#uI#Hq4N)MEgoJYEWwZX)YXeH2?bZG3R_7y&#Md7WZ2jJRH9WvuBJl2D z?}+PV5Xtddc~x;P^uxBh5|CgZ4JDy$0Im^nc?XIv`P!wexD6^L)zgLIc&-=-{uzM3 zd@%=k*o1*iG#}5PJx3X56(dqVSTsOW`({YrPvk!Sh1 ztJ3*R^qed1%pT!(Bj>Vm(p4u0mukkpq4#KD?3^`NhoNU^5BHz|F@kND-*3)%roI9c z;O-u)aalj_`4k+c=ZmGF=FxRUh-_sPvik#vl=_6rCdni!wpSbra2ipdsK;Iwp|N~9 z`5nSD2=A6RkGVOn10!Tn5U_qb*b^yV-HEo@dwd za!Fgezo}3&{}LzDIVju-2-5qDB9A!RRFK?~7*m6d0-w_h-5!*16RH}Bv6Yv7bdoV8 zE`N*4iAd6(oC!+;tl8x|CGwq11Ceo{U#X)!_i7+-;#JvtJ!!uaJzs%>Tt#=SU@kIR zX_$uAIrhPi*7q*(WM6KP8Gz*nBAG+ z11sZcshC$3%tbCC?Lv_-F?{ta6Ptw|MR&Tl3fGEnsQQ-h3(;&xwtsi%RQ zg*lDumdYC4PIY|B*duRoN8I$Hva=J5>-f; zy32EI6W^PkaiQ*Gu$%}`bel^JScs9X65zv5h>Lnn(~((iflIm}x}!1r4pr=#5G*+v z%BzPGyMMuEhsyy+97Z3H5C9kd%MZ~4?iby+hel^ z6#orPBZ$Z@Y%YDKi)uzF;E!ECveLBUv7fbA-|Ua;l>d<#*Vo6A1{z!R;j4qq0&%k9 zyPuYSd_3>y63gaJ?3ob|&i}7$hqQ!Ibg_&_No+3H7>K8TeGtYY97ewqanh#Hg_j4< z)*2alT0>RI#X<;jRVy;<6P=JW5|?*y&HKD;el?Az;-zdLR61gm+oUMeuRno9YP?AE zLp=g=^6-;4s>K^Gww_#DQ)eWo0l-RMqdrDL2bI~GfF^_lHRkIx;G9R>OTiSqBQ9mW zw?GwEz{U^5SX3F~AB{OGR_!io|B|Nt%`6i_x1&dqif!zhp?j#0|3B+>hAv&~!dy@3 zlOzH|IiZ1Ph+Y|OVL;oC1$LeWF6Y9{7tUFo5v4JYR_X_CG7gnu{(W{U2t5cCyZ&LZ z`saeB>c2Fs>L;}4WzH0u)9NZp3VN+eHYrUakloR;B`DM3hC#_i{y%LsLB)eKf@2Az zZM_1x1!Jg0VysXu{*H^+gEyHbvHYCME)(2dTQW89(dd1oDjvF|0y^jwv&a)HX#&{d zW_g6`N?5nV9T;kMN+{-@Ay%uc?`u|Xo4He4tt=bjTJxPW))G;AqK7|X!!Hz0YRqL@ z6Bz-~ezl%xK5Uxg3{RQKyW&J2sa4ya}>$ z04ly&@KErsLvqPZ3MsZ8ovj~8EpWvxJ&=fgr}?4qza$vRODpuKeW~ST_eNwacHcFo zGTgIZ_lF4$rZk#>sg!*myO1*iEhU^ORcXK+vv4MuJdX#3d~dajYuelFXq*dD-ax%a z4t(;0W!mfg3kEVEujD<)HGm6ho9|Py+V(DXwM#W>yEz2(49smCsGrq z|LPw_8uJ?9gq6v6cWVj!+&Jux4X4vLONH>5Zn=6ZM&z&kBgq)5<^axOsM2FJ%DCf> z!A3>0&ZMKvLdcMEe~z#a#@d0xkXepz3wqw$G?vUM6}aW7R0CZgsI z>JP*Ti6ml8v2W`UgLpthLpIOM0#)cL?o;(z}5Q9b;>-VPYI_ZVcUhV@saaP zsL(xpb_eR@u|cI-t#`jiqg!h(Uk#>yU+1I7dqnWTOJo+F zfC8A05yF4*tH!qy;IFl>Q8$EGf%+GNO8&fjP3@3L1=wTWSQcf`me3oU&D&xDSU{lqL zVvi~EJh*m2*}~|&QlNU2@Wood_$=@|%XU0{z?9m7&$5oin~#jn#&iD!tcC#-y%NZr zI#|5j+A$pUhVo%~bVx<~>=RkVg{8Re)`4{m*ZnM+^qcX(2CY3OJ zvQk7`3-Th4e$`8*t0SE$XL!*mccdj9u-@-yNFu;sZwIN?aMMB{r*9A(cc9VQaH{`90!G2bPgfeSS)rZuze}90yrQIVeyLtH;!&pze#kYePnM&ckFnR zjQLH5Z_IR~cfKSoD9{~Lh2!q~Kd8&sW zo@9cwV#eoyL-5WrDi6eYxxX5mlVHf|Y4i!_EXQT|4p8$ZT1~k9_QpTj(Nf(d%Ot7$ zXje<(`rUe?-<8aLBM*Y>(j)WZxoGKRsXyYGbvx0L3rLRjEWuTM023bgNT}BIPiu7{ zBK^cT1zAY0*~aM?XK!G<9!ekoM?y810WIIQ(h4aicKEQ;JBItDqAAVw(&cMHg#h=A z511v@s}BRpXQm)nB18+)&bk`aak3;O_JF(e<(nZr=MOK|``!#-^f`V$-$-H>*AnrJ zcZ6b5wl;iSZ+)qwwRC{We`9Ng9(4YACP8gHN`nRWUlpm~+pufQaNvg&xHe5<-JIHh zH1$9e2D7d@9NeA=?7nOf=F1$I_sVcXM4)21n91&cb0w(3T8r^Lb$0yXacsJ77_TJw zS1fpRA+(M!hbXP}-}7hMew3)uKB6{#bYGGgbOE#00e`W`X;c*k$aF7)5ZF` zcLE;rheFMV*955o*E!~C`(~$eB{|_(aW$@iPxyXf6|ettE}r!=ZLAB1xRju8?|%N` z`M^+9EL31pbHIA+W5Twe>QTz<-Iw2zCi zKk=82dKu|ff-EJ#dWQa8ZZ?!C#+*`xynyR%j@~b|&iz-`F*eY>BmORSf#Q2W^7((f zMI7S|ft@sY@HYnMudHG4z;-n{7C66s^NGW>>TPSs5%W_)$@i4ZzCkq+m-#~FTqH`| zOz;dp!~ZjKgaw#^Z8kKU1gqhiMrqoj(e!VPYSX7&hnd(@ANv7iTQsyKVu*I!>Azd~ zl?S=uXy@Arwg?c5mPAv}(e!tYUDsezQDie>z6s#T^rnZR&!8vkE#LEccVc_0Rw^-j zHCP~?Ln>8W_BZq6q!v7HuBLSg1k2E&YEp0@CuAfTC0IrP?QCdwA8S*aO@R6VA1Om* zAJadBJct@Z7!eb4kxYoHLIkJ;17Nd$7W2J3sXX11yYZQsB?g-C5+4P7hDr5brEt#>(QUIC(=auwC+<)CN_6K3xc@Du1 ze+$2;u8|3-f+ut8+e@Fsf*|s9ei~U*sSz1WxcDw%(uzixLi}|xhKuL35ehx@9!y8nS>1H@*oBT0C;0ys>wqU%yf=KS4-c-h55w3mkg$0!J_1|?3(5N#lTjP z6mW{xhN>~>mE6M+0cp%TMe%nm0L@>;;o}(N1SdY|M#8q_q3A$rUlwZN> zh#dKO_^my0_1Re;f8m({wQDru2VZt|0fKcmwAeZ0kDN=8%w6~_1wQO4)ASat{K$>P zzyDO?4q_Ru_cdx%nn(OSP9-5l3ngjTuVL??o9ZI|<8qie>CN(|2}YBZJFy7%&Ci>s zCER+#%vjD;&yyw?0i&m;Jqo)o&RY`}7FxK+CzS-r{k%yQeE=*N{@^hLH(j zlkPslxr7XYpACwJoB@APs9-a@qpq<+Fd1LH@~=@F!80R9?X8BG4E?IDBLp7SkuGoa zn+?Mf63n34x03A1tY_Np@^sXrIe+(*V@`|zNaYP%Zr|s_3)TH^s~9Q(IY0|6%~rrTs;Sn*~?mxQx7h*(#^ zK-qnr$~JAHRDiDk(b*61qI;Gw%{pi;l0ucZXRuno(pu>9@Mhe+Ce47SaAjB#HlJ~L z5M2*_+&qrfEc7bEioFz;?)N_M|LFO`(774@DVVkZH$ce0zpKI#B)~aR=X+6^VXJhq zgdHv>snJkOEzg9L>woNo?xt(+yvo+o<2GrBa%#;# z;#W-PKzZBA)KN#CXh#`X|1nta{)DdPoLo4@^?iO%maO_DH0LqPTX4vLRMm}X~ z?X6AT>3sD~kra(%p#1$#kFu&`YsZ8H$(hfBL!If!=Coo;t~=4g(aFF}6qzb{4Lns@ zjAP57-;JgfT}2U$keErZR=4!k9qq|RSqc0PPp#T-i}&&Io7!@Ducd!25gxLJMMg|V ztx~I-1~dn}l~`?iS2Jg( z-j6thI;c~bE1A&r{1|TkHd~Vif(*3Tq=oaT4$!WrnUS7x*?$lH;XN+sIeQYKX+Q%$ zdXzaq95<7Wpfh6$xvmc@FeTprcl$3;4~bwt{Jp0 zwdG<~6LCdy_MEnt0<3fYKti?+&d`DJh%~LzXeneS7p07J`v#)YJjMoi<JZx3#>9+_mhdZ`T>rN2Z|X%Q2nfe}e`mvj(3YT+03qP7Ijt)$Me;!^HOJ0ZrZ z!1ju9&3%KL*d^m#OeiT*;-q3&*6gz9e(H~er;$F6oU7zOe(&jm(Tn)kTK=@l_vyFO zMW(mG(RU&(UZF+6%DngW);eeFNG-|1m^*#?WMBB$HS6}C>c__9d zS{OaOY0x$Npq`MW0CpZ%hot#E=vy`Y?bPNb_#X9ny5KjoJ32P$qOJwM!!#G=b%nzRC#@x|T~{U{H5J?$+}ck(RAJSBK82UDTxhgk zv`}5yQGZbjW^p(vTkuHOcq5b&BNZ0qe(R8DNhJotXVA^AE2N=QZBNa1x<@b&I#&>d zO#zpK>8KXpcef)38ls&FHuGthbgvHJQcEkxf_L|McG1iR&AHacJ4V++l&16PGprod z36WR2?kH?OoefS0(X8ZAi)4A{xniswSw20MrLB?)Jdw2yQ{sdB45CiTgcErxj0Gwjyaspl1mXL!A`6p(} z3wi}9aN*}AvBa%o@ot1Lv;Up(X{!Hz$?MI%M8g_;R<~q7Llq^S_# z2SxwVTcdXV)-ns9>M3{Y*ctH_qXRMRB(8npR;;3KL&4pq%u!PA4E&JSQt(ImSmO5xM@Jl72{xm$@pNhy9__-5y zfn34sFD&5zLeYTJ9oXT zI2=VMJ1+ai2JpcPUy`{l8JB3lpTz3XsJIQTh#05#QbPe~!#TGt6MM=)KgN0+K##N| zM%71z+7}sfeN!*RY<}cv0GG+dn1+xaQ+yo)y052i+o5|`q@pU4%_>o#)5l;FnhBhr z`(ieYjFy9q6+X?HGB`XZ^9R&yPHZ@k97kB;oy+Cq@&xU<$%Cj02 z4F+{t!hY-(&w0OJ+{<&SQ%5>#eOWGV$)>9J+lu^r+rk;a8;^BlFGrwEtCn)`5Oy#J zKt`B=nnw~gClBEZX&-DyZV*ABC_NFzE4sBp?O(9VuTYxY0Vg=*nJSTMMj*pSxbxX1 zn%IM_amg~Z#)K|G&N1=Uj?&~{7Og*ThZY`hCfECB88Ug0oCHarQ)#~t1?O(`;zRY0 zD4K@%*HeB=Mn~aT!2X?4{lDvqZ8$YZ&;>jxhR{?gD2IJzD?Fe~8FlUsz-|i7VBV!s z;-+mFwihcwp?}Y~5zHIbVTiZuO9hXm%)N-*HZ~CP#}Ec%m6r8*n`f30DPNS?%cZ>p zvMA*8omPtjIPXb!BuLs2H=Gj9AD#`z4plTymT%9v2gu$E1)ACX5@jbzpb7+3q5J#A zG;lB^=O2gEa`bW0g`fc4+bT*kiWe+5(1b?82hhl}LYf4+$wUQ^X>P@2ka(aIz5-kZ zl2J|56y|K0*hoJTNg{a2`(^zXcQRK$!6Sfz!JK;7buTEDr0=IdUXSvniks?3*ziOOfX_!*R?#s=Ppy$5D zsvF&v>Iw&{@WCG65XvAE><_79x73TLEkY7+2Jf!Zf{poXt9|GeRENH_TSk&6UEs;; zgMXWgi@_R%2Jr+`rLHIFH4Pvrj&zBzrKJtnfCWj`Uv~5DvFU$GPvmz2Wd!duYfRQ( zv?=OLD9q%DRDM;r@#k0;UH5);sDoH)Wd8>+5mskfqlSPdnPxE|n6<+BDosbOH9@S+ z9)($B$t;OVQ@5yR?`Rhig}oR<6RIaG1h#fFF(^ruo^jy1HisWx;ADGQq|~}Y*lH+A z$mBTKwn34WSy^Tbl(UM|j27P;Eh{?`OLlqKK&co_k#|HuZj*wyLqdnHiBhA@;(JVs zVVzuI?}pT$QC@wY4x!pgdUGAP z45sY0fjw^nfT)Sy8;heLSxZ3HGG@4Lo%HDvUVTM7TIXs4o$W!u0I& zDj4HT=uA3ch|znieG90(KLm$RjD|@B?zj^zh`Ik>)ww2;(8*JF#04OB9Aum%>m~FY zd>r^A=tkIo`Vi92J+eX!I+Fh&;;=L!)ov>vq4NPLT-?x0ZN_|X*Cq`-UFU)2NnY4e z^?S?pK0JCV)&tJ3L+%z!LHChU9Z^`vGJKdNzdu%9>>8I@yxVPiVs!y`n3K*KtrIr0 zGxMC+!3cissiR_{)IMH(_u)$qRKc}r@e56gXJlpy0Gk8Uv=7b0>4%#_b-+9D9h23BxG@teJBrVDnJ_Kji?v>x*bzIaGZ@5x+|sP3kwn3veG_E3NXFd_G#_H2Q1< z#)e-6A~O3f^NP0WM-Q-?1fV7=5sv?iT;rBGxrnNY_hg5g^6xkF9^)QE zSZQOjVO^6ibAjtBf>7*BD!I=QNJfYBaiO$SV1}onhyX$VfcwPVG$#cz)Fb8_v;g*R zE}P=tAbvIlJI&pc!-|7RJHAMcDF?16t}VxbKA5L=&#^wr7TeGqss)KCKR4`rcwg(S z?rfR+k3KF~l-IQz51XBKw$rQCJz~#<2hdX@ubI(*wG%jdu`FHN=ms!<;7BEOxWF*w z1yYjCN+0CTvVc~aTow`*23_4BvDk3+*Ml)XMD6fi0F^yB@_n3?l6QTW^@K90zvgu| z_RWQ8GI>Ce;_^pbk%K#^!wDmJn&+Lfo7wD*U$dPD&e-Y1fMhi9D6i;pU%>}#rXwzd zUL%&?HA>8@^5L|t7-n86R06+LB9@5}Sa)ljCf8go;WR$Y`P?M{=?c0c^P7LNA6}|~ zIww>J^1gC}DkRYoh|FSsWixRHMAMqQJVdBlzldsxK7~c|0;Cj#gWg8IeQSP(H0w^I zUN)U3jl~AQ$%?cpVHJz!6kl*=$EU{BA5`Yjw|EV(<~d80T2O|Jo@ z4Nw>5=3zocWQxizr2rC(g_#X08L^?1OzjcC_nuNNakAJ@X!gX}|2)x80TJ-LOu*OV zU8q#w8w^jC!t*$P{C1oJjiv@!gvQu$P?v6AnrjSr)5n5>LiE6U@+=eJ#nUPV=VUtc zDtWbTCjq`*6bD1XewzK(%bWBcWDj+D4ax98yVtY*7)jyj$LRwqkk#WP0zcy~^$lQB zSJITwd|(t=Z%J?K1VtvP!Nx*)2%#RKH!|x&b)A*d%H5z$$3F=QYyEI624_@W`vNUs z%f>{1&!&Qp;3?#OqPT^wRYQg`pRH7;h5QyKe=*OdwVfN`9Z<{k#cEEBow*SwM#6g; zk41}UqXms2&Z%3q5IZ+y$SSw7Fe!Dq;1WM$waBBgGKkS%IkJO!&Pcr+Qx&!bHv69I zi_zYsg&NLeN%!f>5jnRa29j4d9$k?&DPkWrF4TH)%QhAE9TEr{Et_0M4Q=m_xL>ZiV@X{smx{Mqw*sgaWl{u&<>sGGvAY_OB1 znxChrur_0aFtM300l_+(V!3OV-7YqP9I=oZjCkb;-&WYbA${~)?h{z4SgpLQokRX| z>M44~#kXh3wazAv1;9fTN1?#umHZt5`V9N5k8DtPO9uu~jH${rDn*R!8r)X+(R@rj z8HbGSVLq>9@fSv2i}b)4`($nX-FT)RdN?qTTxH22H!RwCGCp4(+VzdExa9mQ;@eps zsaAlo&gG=1X%9`2c6#_i@^b?htSCWcmEAqfu*g~aw<-(fALo|@$=lvip80wrXY|rR zH`KZnoDzVJQrCb*SJl@d9YNzF_Buz*mKLqA&I`A+YSx7)VOg!O2n7@{saUD1X(wHs zlI|~MpR(J%ELup?nJL4y4yhcbcCA!PD$_sButLhb#k(iUSWG&0Kj8Yqa@s!WqESIF zzp6p!K>jq-YWsx!fqqN}>EZahY^?^C-v$;FUZmrbE2>KfOi|X*A>$%Wg?M{$t7T+Z z#5_^$s&0EI3z*5CKOIf7w6-X{9E$rf$KnmC6jeOmCMHw-%mulvKYI-@QNY!WW;FAS z&xa|e_yH4uCDmPm)NH$pGa+bg=Y!|t9?JX!-;{skBW~)0vx@3NBm;z?9-l_`Gs2eVJa@@)w_6OWlQ4UoHtB7(Kmug zaykDfG{wGO=Xd$+Eilci%Lhu%*axFOl`0GC! z9mfWVoz6J`6v~YBaZ*R_M*e`!@jcF0aax#z>vhDyPe->-OANi?Arr{4{t>+gCY<`+ zs6agR2s5i5U{qXNzg@-M5WX;phtd~e_*;ij>l%w$)AtwmEBhvi^_ z8`MuiRXU@sAVvZ?n+d51L^F@k4qnhHL!9=lGHh7ogJFL)2Q-ncAT6fg#*lO@2)|GS zVIIfXJ}{Tu>Uj{pBK(z0a}!&k9y||er5>bIOtK}_Hb8Kz|HJQIKMWWVh%$(&4DKJy;PGs1>3{kbqKspI17BBJ4B6X~{YOUAn7Vpq;%Hv%oRMMs7)F4; zf7Z&p3kV{RSnawx4=_a2*6%v}1yyYNDFAE{;$<&DoxG$#fVuG6d4C_(0{sFe9Z*~t zNs(65NwCO;7M>>SoEL_{8ow**#q!bePJ%t{{r$G$L6;y$KQ6q0+|)Ea;HRB}uL-AV zm_L2{!u5v8ev z>=gbP{$`hFs)3Np~3c!pDIHKLY}ZY>4k zhwl-z0wrm(IocR*S^G2x%p1gyI>6mckgT(%E4kxkT0^d(A{G%wC4?L09xtP|7Gm|i zyM`2nfBjg$jU*j!x8uZ>u9<&l;>|mow&$e4k6jlqd}5>wSsP!5pV;%%Vr2u8pJol` zKIs;FJN{fpK+B8dqBYB{y_S$n>+AHdE%TXsD80=6N*!)nRFtS7I=S;sKops~e9lh@ zY+71*>+w3Ga6K6A8fkITLF;w@JML;vjssc6A^xH$iC7YLrHHTgG50z+sJed1-7l7g-~Q_e8DmWRWbMgP3%=!Ee#2rX4d=q~ZkaSs^s zrMy}S2QonFMBXK-n^jH#qVP)S75hdIu_`PaC6I=++*`iUp|}h|gRw|szY8+%!Q+$Z zn0X+DrE;q*u;sohlyY^55G1j%ZmZF9xIu6yNg6ggEO2$Ysre+FiiQtYne88#zDFwW z#?B%Z6!WJg7|FW07(9JatsMv8mwcD+^RE0`qm`=C=(FW%lN#%S-hUGFDnlz)jE0V9 zIDn!kj|}d9m(fi?*7ml^FUta%0Iu?XQ?ZNAU>x;typzM;#jC3pNjb;4d`pj*CiBUM zQNn1bqTVGrx175vwG_YJgIBI3t5pYQhKRyG`iny>0n3v07irsBif+1VR^-`l66;1!#Lp;x*-IUETzzJ*9!YC1u~72#5PtiB1eM^;22gd z6QyT^=}l#6>~+*sNmcVmT2v&xzmy#EqatrQwepP?;o33?4X|`w(NnrKIn0%Fl)1N# zAI0fMmI2-6RFogL3Nvl=B`Vh|vD%IqTcc)b+|=TGNTpcLslZ-~q+*u#HE(P-b4a-w zH+QTNx%K%C@$+M}S{~E`?w5`!Dx{R=jplPAO-dY+hvUSpv1TKW>!D?IG}!*9XzC@i z{&I<%01;^W4xJ?aUe~Q1DLX`*1pnG~q|5Fp}a}aLAASdPXy|~!K1PwzslcVs%AyK;PKaL<6v+;j$n}(*U z%14-vixkcup-va&)CX4Oo&TjkFy(y-} zFH5s*{0g6$tc1U^wqd5aQ1523t+0F+t0;kJ)fKzoyQo@GYmDP=ca|ops z7$p^LfzjC%WVSr1M0Z0{GHHhU&UCVTOO49fFcErw>YO`n*3rHGXItiIe#;=663&U}IBN;@6s9Ioc+>`h@nj*Ls8&UH;u z3@Nc+KGkyvFf$Nuxj`VC^B@p3%|T*6nu}-Afr@Stuey&bdC6-XaB~Ck+OC^VL0j(? zd1{)y257AG*96e724=qo@u3Uury9taPyK4?mgC#1gx!<%eKuj%#gM&CjLF+&NQmHxDCD|NcI~3qb2JJDH*a`>j1h_SE3Fdq zKJOZjFiN5n^!7rW!jIgbX3%4uiKH;&^$EFPmUJIQSqIh9np{!#aEdX5yx_p9kJG~( zmy?%jMPFpI6_V_hv8oJFGMoeuCjg@h{my&+y9}rAZT$8mR8(V_NaBX?_}K`{%7us^ z4#kv`+I=@+D)k>NN7Me0av4Ztqj>2ZOmi)Ew)|u`145cx; zhR1$}xccILpJFz_8DCh{@UAnst0U#D+)%CfN9vFnUG^WwoUR0PLe8O|Kz8ndI3J_) zoNjRX)LYB2_?ob4Y9+S}vYTjO&I<2BLXGvTZWN(IR(hUxc`>t|JmsQg-Doxr{@Xq7JJlChXB#KXw&+DK6(^L2@ zVA&9C=;AgaYe#r%{@b4LsqfH)Z%jvhhLNRo-qv>n9&@uFGU{or7_bY;j7RzpuQHhF zbo9+;sw<~JU&nlUkhOO2@`y{$TvLv+^#6RNP=vTjoBr``y}2s;UtjA@+_#kQr-A(g z`i~c8kfZc!HZ1FvzoJ8fKNYanwa-GHNF8Z0JoZT3V?`5Y)^fBT&K4=EcNEd7Y$)Qy zWhc$Z=N`+4u2y6&2?=qTKBz42^Ha9^u@nBez2H- z2lYt!Fn^!0V1T~an>9{!tn2}E6Z{0K!@6pI->7Irv{c6eb{BOI&wlrFReI(}P~)*X zRbdkEV4|nsPz`#zqNll3@`~0R_6xi`*(SvwJa6zDH^P2pWCA{3zv~|WVv4_UJ6k_y z-3e?t#Kf04D2(a^T-9nHLC**1oO6CZhju$Oa!}))>##0T!&+QP*K`KQ*kpTg#U=Op zPDmEL3l@Cnk!ex{5zag{$GB|xV$|wtkuANE{DXl4PZ7nw+f~P>j9D&>H1VA8)b#T^ zdOoy+(l^DQ)xO!Fosg#UtYYiUuaj+mdLDU8fcHj=Ouv$JV({wS?f;G_AAu8|=7WAH zQ&{qm&XOi?k~F1gqEJN9_8w4RGfz_}lz}Ny&_K}(L~k+*g(vC(7(KcgjLk6xcq#j4 zUK_p14yL*Hw=jn42i(ae%g{oHbrCE%D`nUm7sIr#zGF@jv4Bjqk{D+{Ynrcr7AL;u znG8BPGy3$a7*14aUNUK~1Kw%kQ-}~7tM^b=+o9M)PHcy-6%iCQ=GD2r)2L!UdaDnE z&;eHYS-iN`MuNS+-$VIB7vmY=FE$9(De`_{|Ihiu_D6FT{=ZjOmEh+u-y13^%xiIX zzNa5g)CKApE$-1DJEmL4|FMI7UY!jEZ>zh`CCM=x8#_@P0sl{K&8UWB&RLZh(y|KD zH&=?BbD)g}?CZ}eA@+=(Ob4wGe&(k@j+TG) zAcfdZ&NwE^ZJk1Th0TYjyzQ-foym>^RV5joSRn<+N?VbrV^0E!gwfY3KgBI*KLiN1;# zWTpHBS?TFwt}Fcgf&s`mjz@p1G}@d^jc78o@&@~ibap$h6mX)f2dJ_w9%40BWd3+W zI2iwXh!d$rr7#QyZPAs$QV`pyEQQ(ut8JA0&RdU4-OicA2LhtoNKH>M9GjdjgZ1~xGDHC&YQ^0M%~a^jCV<^XSujJAUdu<3;0h8@$@ zcHdrswhg?0M!S8b+tY?);*kb8oBvHWdfuL(Uv_&a&%;@+%mopSwqt&A83vKt-YVnS z>@TTK>)fVvnxyzPEEA8euC`(q8^(i2b{#DRg(u|5=~tkgpRK@SZwk&1fU_lyN6(b- zj@;N3(?VD+GN5vn(=F0=_O$Jy98!j02hTN@dzlI=Ymwd0HM)L+#!_iRtAE}=xYPyH z=K*zQ(``d*Ss8y^qb`a`BJMSafO?0;>M_7@TCBGsy2A^3bId!u&CAsCYO~#vPSzt& z*(dZg+rqC0zE|nGpE5W#lv&ip2?Fge1wY8k?jzX3SuN|83;7CMP0zqiKJtE`(9q01 zs)UDEh|ZMxx=h9c`QO(1wj3HLYJiantIjCWWkm;``abdVcdagUFSh$usPjQD4xJ|& zAk5VRaWH^(ipmE+Z;2REV#nR6JWoZ;qD`e>U#PbsEo+HkQ$cvU8c09rhdtX<8^ivl$G)%{TmfMZ zaEs63Z^4c{Mh{1I8UZbd6u+bo`wG4*F@;sPmC??!HoZ9YYix}y5*SC=5c=Fa`;C`% zy2dD=9OyO5MQ2nwcTNO_U!ot9I?6`REFZA^Z_+hWLBW;L!rH)fXV?{KG+$MU>&Cyv zEG+F}d`EMwA6$IB%a7cqP+?{f6SYCRBHz8ZL=;P+x6v;vYpD_kC ziIZSTJ1&;F&{FYC22h>=TtPCi0w|QFup|g(ndpFrbSA7#7j>6bge{Og3$}Slp?8sd zy0VFl|H%&@@i9X#JI&jAe=|%1U1#h{=*?$bdPI@>#d~8JLYOBgr4RkfIg&s*+utMv zxM2hSa!_^U)-gg$_L!+_Ex8$#?ltg==fGYepDO(n$5#c$Q>0BH~5qIcOwWWdw_!vJ1cY+ZvJ@& zIgB*d#rY)Bmf>qcD5~zhn;PQ^wMquY0{`vx6NR7udVtVdYABxe`kQBu3>a^qOE1kQE*=T^MX6oEGwi$~bKO%v z&`*@i@`-_pH7a6ZZ30IIYmS^9Ix{1N0Js(l@+lo$c|pY1+%By7Ar}PC;0GI9Y_p+HL+qiQ>;J&Y`eoq6_Bu= z0~i}(B}n-u!V@;x9JSl2DP~ z*19nZ2H^@}05OztaPvZy87ZFH8%1D@I++8}fM_tq2ylkRs-|q5g0taP4?NH^+8ZMZ zsh0%36N;2Wf}Xxr%k+o-iJ{kBvw*R%w~f9XhD;;0L&Tg5pY_L_SIuBG)*s9yphsJ` z(5_fR=~rK16@9okaZfCJ_OCr$kAz%Xbs41-)xd;B=t0%OJ^IU}KYjJb{C<8w?(Sj5 zI9OtC=6HAlXNTGtCY;FvO%$A=BTbOFx;mdl1j1jZNl{I_ApxIRemjzC&MMq=``Kdj zoMf)lhxJuQ(&B>+gEYmQnNL|*QnlV$UO9N$ME3a%oaFUIpr+N_Op*dE7WW8=c!hv2 zkMv|b!%2OW9g@%f(3R*7h-@Kt*D0$AP&t;|$IPgqGqaWo$hHVoe@q$BPjD}iY5HyFmB=18eOuDs zg;wk5RBC-kr>d2vFZ4vJFH^_EDBZm-3rqjxzc#5i<`Kbc>)rQZ<++GlN0XE0POkF{ zXuR|mfqpe^4-ixM&hRwIC!!+e?@Qz3L&2J4xuE1rf(GZ8qy{*gq4111q5L^*xkkLw z&wJJNP@vijLJ`*yNBFA0-)H3=tH#>Xs(efDkOZphmP=G<;yU~3?kL=QR#DowE zg%S<1Mr=e|pI;CskQ}Wk!@UvkWHLZJ4^1v60nQvd=gvLC$?Sft980+UE7a2MpA;!1 ztL=3siJN1@kA;9PDu3rxY9gH8Uhqp!yWN}L!rw(VfUPk|dO}%}LgIVae{DFv-AF`W z%cud9Ki~=Q!q&|GQZI#(w-~kEJx2R{(%Lf`lr>MR^DT)_>@fBbK{`cpCyay7O3EQf zADW>i?D;aE`~VR8$8n6LZ=bt!v`5idES6*B*4H>n-YZ$MnSIej9^IyaB>5AHvcn=N zGuGBz(ORHvC2InHlAf-?@nC3u@Bvz%lauvd%xfq3@A6qEk;%po4iusQF3x?dVcly9U9m*tpbJ{vo2xa$HLzaxad4Mpy?+3%ke!y|oJ_L&6-Qg(17} z+z(cL#|(-Jpn7l00?!Gf``mwcrgV`4%z99)NV;(hT|))Res%ZXsS2I=whMuK*C^g+ z?_yEyD^a{SH~WGRlWF$&R%K}pswg{ES8DzZ4fLv$yd6hpot`5R2ZSAWI&M{!-A}hQ zmg+z75;v{gjDI@NeR3qFlXjwS2(pbenhI@$^Wl*OF}A{{$m()iU8BBX>-`VHEqeiv zfa$Kl4l~t63UkbI6?hTOeSU>$Ii&T&u+5JonA#dcS3VR>H*Ou2dn79DSm|Uo&H05- zYF^Si>dI5|?>?j7P`-}`q<88B!4Xd~>{_>9?s zD)7%+JXv1RRmhyc_`{l{&x2TU^qC>70_MRi=D*U0)dlHRwm}+Yb7LmoB;2o}(OtxiE_fah5!L9CUi4EK?qTWhaD|HTLtz+GiB| zhe<=ZU46;Sz2))auSeP>Z`P5y4N^A7K;6fHE55S@~_-~OoLo{w+(qd z1Q!O`l3L+o1@x>p)s*aon=GN5FYUkF8M5Ke`;jRufbc^F!l)PUry-C5q^tSI(^~x# zb?H|d$prg>LZe|@%tK2VZ`TnYo_lfk2%Vk9E@p9*T|mkS3Q{?aoK08)xwg1)ta#iC zKaUPS0m_{`tQ6RkCJhefuKodwfl!ZbLLIyY8f7ohEkG4WT+E6CE`gZL%zcq?l3J?{ zckjDRPBed**`0YJgM0-C{-`CAs+-o2hM#kwo0VI!jV>JIpOc(+@8pZ@M0+&X5eks5vn0Z$fR)BXS2Wrv1B(r+NLC8l4yxZ(Tcmi<_Izy2M z$zib_&Z6@v^{VZtqFu3}hlDDN2mrOp=zj_)@oOrzxvY80{n-6P zigZn-Fhby6=DNAKs2?we(lQMEnuhZ7qMNgy4^Xg}l(12C0|sAOynAo()h>lJ=vPl2T+o&0?H7JA8a4q6UG;40__pkCFSc-GA2(ZW%d-9!4xa&%s zS%i@qQt}r8r00AD8Lq-pr5G4{=Yzh90v%c|7LJe39lTnu#|{Gci7GxC=;j=21yzU2 zXr}`?GT-tUXXH(f6lq9Ri-utjnQ7o}fbZ&CZY<_$qTJ2`W#shScguQXbZeiU@&0w( znHa29USZFVX1XjbX@G5ZsJ@oine(yv#IBje;6+J9+0t%Pdd#$M4Fk5}x>8?*7lpYF zU;<<1}r@Chm|?F3sWUh#nhFvDeJJu*?|I|%=H;e%tTxjhsU=*!b@9Aw}8wv#U(ejptj|R>F zz2;=(Xakdo4LkBHfqVwf;VI`HU&f4c6~xB%kXOFK+PXKZnj>0G$;AXN$8QHoM4(6{ z3cDaL-)io7XMOdL$&DO%)VfGWvGG#zKI2D?#JJQLe~N$9uO`DvvI*Y$FfIp&9SLW2ivRi_ z=~|&=n+aCIvixD@OLtO-Fte~dUrXtsfnDmNg~Bm;m7MzX1nUg5Pil>HQGMj`wPQ*b zjyaTQg^IT`gjnc6tWh}7dg3WlNOzX>Xtf9zOnfFrQ(JO8*))*ai^JvJEKOE3)=V8m zZmKgKDnK^IGHhY26%Mj078CBdJgvg&f{@wMOOBz#SW1rE`;Egk>^wKCsJXJ+uzY=J z_`&VMVU{9SZ>{v;pjW1O5e6|U7Cd8_ev>OcW>F9s&7`Dc0z4zxti4Vh;VCq&s1V@_ zVf0Etne=uxPs6PUp%3m6-Ia*Vv&Ff>HswU`pXV;K1xnd7C7N)EueC=v?o08;00jrg z5J{6wK4j zr{{>w>3lvgo1Bvm@Wt&oKZmWf$Fwjt^2KK5D?=P1V4VdUX=b`H2h5Yee~4MA|2wxs z_3fEHhw(i6w`%VY^UmP~h}T=m;${>)l<7x)pK$+JkofG7zQ=K7+#+tuouvno2$*o8Li@pXe`G=&tDC zo&0>;1&TNdF6fuy-Ns(mppl4fG9LjL49D&kt7=9NX zTi?2EfND2yobfBV9uBD4`tr3Gj324w5nys-owTZ>5suFy5|!}#wPn@FF$fWp$n8-7)kYN$N55A8uuzCM&4bQ>u$d}! z2usE!#ic&tmo1;O=8}La9PJ9`7Cf#pnmF4YO^j$aV-$HCVgKaks5R{K|dBC;Cf- z#LUCupY17}DbE2OyWpazEfTuH?dzw#dW+mo8mIt>#o=N4^{uAk)Hz)j&B7q496LEY z3|55cz`o$G*Hwl!tI;8C`PJQl6B|ih#4^M~Mc(cqpT;&DaPo|`48hAeR8fat;H5`2 zPjH%H{2Rf@&o9bPfwz z$p180VWGWqcbhLXE8&f%!^=PCYEmM;sf~rRJsQ1WvZb4EMX*pPgft95&$5kPzT+7e zKyo8R%i1jMez#~QjBH*`z=^7niwwt@CrL}3mG(lhGUmLef5o-hsUzIVg8!sDFJ9Fl zIJ_ALx$^O7F6Lj06_BkO>=Tbddz$|;rn3B{Q83Sh2B!1)w1^?~q0f?SkZ@&bhwyGG zOpy;QKv)uZh((&=_`>~$OsH>S<3R*tqm0aDQ91-VVU?smY}<)h^W8X}5R87uyREkm zU>wpu-IAZU8t}Ph`^mrB|i*V>kjtN}SUDSXY0E|8|{j zcem*v7-^o(&OlC@!b>|~SOwq{9uXl1K1R&HD(uMrV0a*G>|72X=vZYj9ea4)rN`Po zp`4PstMA&7YVmWv9s_l-(ctLiG*bCs2<%cG9B+ufM;H~Pj2q^C5IX+L30Eo~=3S?) zH1sFUQPuU)sS<9mmfHpYfifh;<#b2hXvfTh&RSwg7;ZW{wtZ^E96W8ybOv#P9NLP? zwq11$T|{)+DL9Bjs~%kFNH~X%xc+sQpW~jAwc+%#Uta25K}!V$L|5rk_LVTT6!}`A|h% zm~GWt75h3R79O=yjHYlizFDAK7XyN{8M_R@YjagoR}R7=p{HmXyCFvQ2N39ceg15u zrG6XZMlhBi?x7T-T=Jzqc{qN)E-kg!$H(Tjj1J+)kt8~WSm9^-*vO|L1bN!M!$=nr zrJ#HwHz3Ht-dpFUCht{QLgVcO^C;3Cekgtdf0xKrbK~{cwz0)~^!U^gJ9%ARu@rFJopOs&D?KNY{z) zayo^e0EyvNh(iDzbO8l84wfVf7y7a}Lr%1;>}?z8d&jjfo?HO!k52$1AdDa{&Z5#8>&+LB*knf;brv~#69lAf<7X&g_;x=b$Gqqw>Lf77_TF17BFu7;)aj3=_FeM~A@*I~0`L^Nya5 zYOx;_efd5(XB0wZeP8lxJt4I+PqJ4a&(W3kQ$f|1gx_~(CW#8AiHdL>xzAG`BI%Me=md(WGpUgiPIpG&0e$wgjs(!d za6&MVH7aYR(uw7Qq1&NSbXp7SetwHt0_0-7^RJwtEG^_EB-X*Y%&F zlstd3#bMO=u6LgjXE36|+xauP?quG86wGtwyr2I`})T`z3^SNY|4J%`T@V4Pk#bWa#3hr6)4gy7pKR6=?VQ z4Zzsg&G~5to%XHE>1=#GVM^qr1$kgu-s73ar9x)2`A;=Q*eYilJNJe$m(aM8RxmeZ1mKPwCrI2;=SPyt$oiO;rZc!| z_dTh_R$8h;Vn@6A}#lq5Jvc#YY7w&BNx|C*FvAjlaaFi zJ|w=6zm=`IJaT$V4YFD@j$4@ZQ0B(vh)-#K&sfkXkLt!^j+O9IDXs2nq%MRLP>Aj9UEn$9SY*$ zgGLfIu3uWN&Qghtd{znHaH4%13CO8huq^Z9uHSw?o9L1+@InxE)FpCqBDf(0vEKrk z;-B89&D@;}fg({1RMg~A_WY|n)pIu#$S7yge0vm09H(ANuo%tkkgtCxYK|n2dR&2a z;o_{j-?H-@@}VhW0yOCQ_nwLu1dkt@=kv|fTGE!8UqCc_g?Z#KlzutlO@W!Vyby1I zv!fQHb=zI@G9q!LMEl(96Cnf1=|wFNh>zHl3-Ru6yvGN}GFpc%TFisk6mfJzSu)P; zM&brUKx5><9;{BIl^8;8h1?0>M?y(`Jf~mvdY%s2@u%%b;DwQ;4*z{~(b?!TVncb_ z-MiSR14Ez(9Z@x|G2h7ZhiHYw<`8EU99jTuzUZ|fdT6FzhAdb$R+?P8o%9!s9uUv| z(}rP{n{~i}9XMh=R`>~%`FAP>Qw>jyU;f^Zw9i{Yj?XR`+|Br$E=apeX=N(Dlw-Yn z-qgr!*4Mn-&D+M(x5eOP*7lSxBoOjZF4NC>H6BEp?2mTEFi4s~q3vU3rBmlg6K;5C zdJvr9reZqsp^3N`nYJh}IR;~3BRJTB04^oUt8C+e8a}`Y>n>ft9R8-DbbEqC!csB* z#M%_?B!D%46qxmp?Ga>4iJ+o0V~75OQlL`{?zC0K+-RAFtuw6<2I+`yYQE@e@6;0h zJqv%Jw30;z$!O^&3P>G^nR8{&)yZB_aH=BGuU1_Mvkw*8?fg-SxxA&N&DXm7dF$p|gyD0!5#Cxh|-x{6A_ z3eAGwz40hvb!Xs>gWjBwgFQ!I>2@yxbjfe!Dc(=eetI=;sX-Z(8H)D1s&>T@ZDEqYf;Tr$!9iRu`%Ju7^ zb8|i|=jLwgI=6ToSOPUq7>dyY6XQvznCcFeZYD0=@e@#$rs`_F6=+DgolpJ4Gf9zb z)@!3tF@&j16J(S}pYcP&(7;g%Zmv@2@xnS64GkNJQ-V`Rkj?0=aMCQ89aVO@b9P*) z_iuYPbl6%($_B|aK*QlQ@-sL~9&&e>^v-dBHzp@)I3w5>4-OTEff3Y+!zt*czDfG7 z8OB7jU`8oNyRxb#kz1nEI^Bj8KYXc2#fPDAJIZbiUfTFRl$u2anaFd^Or0Im2V1QL zaZ!DDT8*F{b?Sn)Q6`DB(2z^Y`;ex)!!Q2Q&%io=NbM0~TX#z#tL~8m!)`Vx89=6M zb}~SQ(wp?rEVc?K&B%-Zm#oT%Bd(6+6~+gDks>_s^GgAvvs(2-YOu(sFHS$C5q8p z!FQ^PDVyq?aeJmes-%gEeVeriZnnLVt+<(4_Yn$nY59Mb0RmKA{K#!*p5w8dUMh_$ zDsBVomv?DOm|<}mYhd+1pDKgX_Ssl5vS>lX*j6gD=mCq2fo=C?n2Nx+JpmaHB!+hN z4C>>Erpgl-MU@4idp+`)E5+bJYhvPYU-xVQVlvr)=8x7;PVR zyG1>pMO?}=ZH&RO7mXohox~8FKa-~wG-(Coq3wK=7SsPB2umg#$s>xN!r@DB*_mzN z1^T--+Xu-OIT?e<8>B#1#eornv5(hRbPTg~n;Oh+riio6GtX`Uoj=x1kO;LMr6`6> z`p!VM(;)KUD10b@q+3gfg2g?6dR3DKjT&~Q*g$~dJ$KUcsaX>-PjM>Dn4;^n!9a~4 z5@`?0HEk#8&&@RQI9{hUpH3OeZJ4w*Rk4dSHt>&OgS~;493nz2Ta4;iE0396ACBs= zLNUvrXAOgHZgPvV1uxfTAj%Bi;T^mHkYyi1@1g1Tyvt0}f3B?m9MV|BBpL@AS{QaF zm8B7lD*e7?^G2Cn@RudI6Bnx;XzH>7=*f8J+0L2b9RyN|t@d2Cm;YyYfRj$`zx zA4Lt@r?$*<1W~ale9z9hL%)NK#C*+6gQs1SSYLa(A!cecBCQgmBci4=om&Q8tF+r! z+)CE70WA-AhiCCHaTdE+*Ef$I$qsico5iIjACJ1A~;E+Zu zGY$L;pk()ad>Rs&VH8-$wOGSnxI+#0MN^YO%>xCF3)?;zXvc1NN>o#EW6iHZ`spzq zNXw>+y!^KgD)#=9b*-NGrVRnAmQP)+3#+1C7tR}Fw7s5DAWOA1OnPUYB$N{Ad>I#b z=cqx5zKRJs<&)H22xS;8nNvovq9i`!%GK0^7QhQD>%nyv@O(V>XY9yi-QpShqk{v> z<9b}Mz0VJ;fBxsWM!>?82wUB=y>=Nt-(43ZMY`Mm$T^kJG6>?gw1=n#qgXkyXit?A zKy%T?+Z}m4fH5&+9ucfl5xORc#8b8Rwx*n_v8cTAE^F6mW3Xrr%#m>PV>KM&A%Gt zbJk3H530Zy%N;7u_i6!XTmjY6(gv(Won^P1o7q{V(HSsOYTYVP&48qaTzaM@yuJTN zfNrhG<54%n{nNIao2-0jae+E!ehsaIAtZ{b)fGp_wM8_7G*-4!u(T;c5vv^ z3}x>+Z$yUADx94WqJN<^I39NgDp=<4B>75CXpye|NE%kJ4HtCcYSiQD=|tOgxUo#n zHE)I7L}DILIasn-@IJ{6LFs@=6!z{%RSW6HvYyWaMS~o!T_3mwbCLu9ANoo+|B3H~ zr%~(Kej^FSDl{Me@_%`hBY^gE8;I@amwo_rr92=%*^aE=&BzEGW8}Axsa?E@cwfyY& z*L1@`$cCKfQKk?kcz$L|TLK1tROd>SeSdY!y1|4!0qo;tcNg^UAwOr3FaJVrkh(y(c)n>3hALJuwBXP^;?WS?+BVk#obRdU!|QS)Ot3q z>Bo02ZQ%!M@*hdXP7Y@4hB7AwCNCmqHFQcrbo}s7oyq-klnfdKz0RtFIFviJ_bI|x zmTV|b%M>D)VTVCNQApv982zbR{{5@jVL5F+-D8ahn=cyk(PC$as9_wyQzR!0JRL-c zg9L1*PHOrpFuB*wz1Z|S+^#z-(&n{FChg&Z#En)h?3Y1eu55;!gO4L04zBpR553hUQHCo&XJVH_gsmm;{^iR zFA24%%bc#CL|BW_o4nQ3d6`tRv5*`leZGo9iOvA~DWz;3`F_erb3%5dKZkh$ zA)1KJ5!okU?aLy^HHi)cv4Z7S14)CNgzUc*vXyQ|2IHo_+b^T$$=fH(9DPzx++&A- zIpC!iTA9~HJavSuGof;Wnuscq7GSUuPfV5oa|;94pFwA*lOl?JeKWHHY~S+iHP|T< z)WM`fi?*(0v9o}xK2PN1A~RS4-?T%mgT3B?bC8w$&^ZfT#pzDS;2Ft25LAa6sn1Z} z)VRF-@4wQS8QPl%AP)^1(|Yyp6b2HLt#k%peWOC!MPwm$^MdK?Z#(_`VURjCRHw#S8J=?U0Bisa)5G;xh^dkr1Yh-iAJyq4> z1gz0yIB{t(b+C8!oS5Z>g~X6r_(89?QyNB=Yr(H!dJX!+h{4~&K1foaf0e%q)#|{# z&kBE0c6OmIAWNBkqGTJWkQx)l$f%hxqgE$?g={}_vb28+g4|c}zX_g*Kl50U8GJPp7O`Qb!uTvZLA5>GV5)WsVST%)* z2B2mViG>@UC4oZd0+2%}emefHr`3SKXZEdrCVJS*OiYg*LFUZzl*)4Wl z(nHXx=nlUGp=XlZ6C1qb1&MNinZOOlbTvQatIij>9+-1T2-Jkzn~0)LKsJ)`M^j~j zO6y^(^ocy?3;%FG7E`@D4^POB2qJ8YuGho!&0ors-%kqrJX=6YaSAl1`{vM$Vhpt@ z;2oHBszFCT?+wAS31yzv=9f6zzYD3Z??Te^){HHwZ#jVoO?E}=knj`X17x_h^71!r zU+M;7Oi-)*9QnlN&;6bPDLdXJ%}=)n;KTm!b~=#Hxu~J$HLtYjc{8~8nx_-eABB6O zSlq4NjFu__@P5X0WK;C)YW?^>fFP0ee|$3^nK8j4cz$P3{$(?n?&a%+U zNJ~Ge%wSV5EnyneljM%|9Lv{?@ob%cIO6^C7!nq29#<`vfmOdhyzYFBgr@(|q-ipu zpYnx!pcoBG_vBuDW1+~=46VB-&!uVr^Gjv;x+JGj65th$PwM(jMn(569H`}pIXo_B zjL~Y>&|H2bO`8wHR}l6pMn%zI+heJJ>jNZ*YKv;>#>~P_98SPnvB;wtk`v22jN`C9+_(dH zE-1c>ra-Od!Pmr+2EwL8H6nBN2qbdqKNh*Xq70u(#DKA5KNkD9ch4LqLAtk)@8xec zWr~lcL7QRBvX+0ZaeySc!7#3mb5lg({0Gbe5fOAsY=UA!*%j$)`C7y@Dmrp3MSjmN4$7iop{t3ajb?=wcH zc-Uuhs1Os&J1S1xjg`Q>J~9@#96`G=;+oiXF|OawyFojA0PJt85cFTe!c`Z6`0e^y z@V>4Co+{Gq(_9Zd=3+&fI?)PTYmv};sKVB0!(G8DSw6q~thMf9G=}yY3r=#13%;n7 zc?z+@Y-cqo(9#>{AGUDq3iw3zGafir8X51ohX|0f6N1oSDsMBepH(eb;*Uuzi1m6B z@9Z%Q_sP>E$&x%$?MrNBD}t1@X3pfSIi(Qlf@N9Mn-4b9A;Gd|J@j|iBn0EMKUSx& z`5yi{mPEr24{`%FrL6Fo*Y0}&a1QVB)Lq&SK9=5vYA@fJyfqL+XF0;s6%3)1gb`0fmsYIeViu1)ut9^x$sv zNqUP7GB5%^8wTnrE!Mq-)s%GknY4*Y)yN~p8CDcjd%gmCkWc3s^M|{N{Vf#Pd(l*^ z8?>hiiE7ci+Zelz30W>OLW!HQ#0yYun4l$oYV4Pf1KBP~k44~1I%f~-`p%%*k_)h= z0v4`u%=v=T@Al};o6Oq;%LSg`xx<DYUa0xDT%uJ#DBWVYM{#(ksiUl;qtG#oVxzq5OqR;IzlA;d9tfSVil3a)-s!FUU8(63*lD!O ztEa=BEsUKq@#pzjd9|5nL4o=z(6tuTNe|b3 zHAxjLn9>MRR>cgVxy99?EN=nE+VMiM&1Q+b4H49}%oGtA@{Oy?W7X4G&(C1yhhWdv zC%c;-(?-xonDBv|JL%FZkKR; z=*ZxcF&6B_q2R@9-;T+pRr;HIGEdSg5Ea^=dLdZ7`%|JPqoi5^flOaYn=aED3=zyf zL0fDsZ;m-Dyakybev1fsYK%XyVh9|0eyw8^tYk|SQ4}0?#ZWjTkNeFvPkpm9t1oVQ zd&mSA=-TLOT4cJe>}VId_P2QPY#r+AfC<#G{`4Z}+%Qx{xU&qS7-8;9DadoZXgeJN zVm(%fKzRu-c`KH@9jyl6v86k}q522Vjd%wxBC|&+LmPUoyBYVqf6B@D6K!Uc0T&Sq za)^;ULtCH0%BF_C9?P>CUsYo7rS(KDbEr1-aZL|r4ak`~VDcM`>wd=U7@b{9XTI+< z(bLhHJha7)`S4X4W@Y7drBC50(yAvdxGO6)+1*f<-#Ya&n3yBG%EzaRDMnRFV~(QG zN3ILhcT$NV_7iTU4J6D47MONWpVEz!vq)~7kC5<$aY(em4ZisCB3gQ{anlk^_mx;# zk*aZVt$Yucfiu!?z^+S;#4XJt!kIMEBV6yl(vmYztF*62=^e~*Cap=zD#P@!7gntR zutCp)j$40|uxoIaZ|5h*?bZ=d+>E`w{y9Hbzw7`fkf3 z8Hl@j3HFc0bh9Qb32dF?!Y;v1_E(;l5WiJsolfeKMD^F+{Gb#khK#BVCc~(*aX=A}(WaPUzEda#T)ykoq>LmE^p!{}xB)X+&= z3D)N6Z!f}0S{!czz&ZV<$AN zRmuZVo0;fPC4q)j@*vt_*ArPcv|1lbE@U&C1KPhrgMC(_HbH+00PU|=-qMQTnq<3` zx^Ed2F1uZ~mM2E6-m#xLA@h)=*kp zCAh1@)8~HolUPxyzmS3ssf8TCjTcQlYqd81{ z`FjzHE68lq&kW1tJ58M-%c>=&b*pdzyh`6%aDG#_Q%0FC2`(`dYf!73E5A^Ejd%;~ zGQr%PAB01pA>t#%fNR~+0H|M+SIBS&a?;BFLt;d8d%|*}8{6AXt@>*PoQ2KQ_qFSE zXD=pmHEhptOjY`Rn~39*0J9AAe#2P`p@^Vi;O=>#v$AT55Z)nxztB+Wl^pvZ0UI(D zH2q!PLDRh{qNONrP?UcUf#7f6npV*LZwI7}QYvtRwks~j?rzE#U%kpe>Mc5-QnQ5t zD}|Rwbycn=t=jjy`2~+c;4J$Wp_B$6RCx~hddR#^A^VgjpzveL5iImo(?rV{Iiekt zd!e!zV4oYc+>5}(?=}5^;YiK{S1#Enb6M0{BBnn3R{bA=?Zm~fAch)~q1@r7+ML-g z`|=m;mxzVc>KE{d+c4_=XXBhXXFDhhHM28k)3v;)A>zr4&kJqX@W0QA2lhY5ix2so z$QLKfLQswD0I7Z_wr9$rJtZ)GkAlulgF9Gpboy$fe++8jA*@l1t=H4H9Pth3OF^gl zeV;djpR~=Jn9VZ|rX#^>f5*SZm;Ln>2AoQVqcJQY>rqh6!l@kWG7!tqxp-cUUeJb^8e|HbD{u7dv@BZ=Inil4qTz`}EWY*n+%S%)xlX zATLP-19sn2%~u4nph|$&p|W~_nmC&i0pp>AXPZWR)1}+f{BS+}_>Ta&_lLQRRexmf zPQYAW)08zeHBBnZ>VvjtN!gB%2iX9t+ z0j%?~g5ljJ(imP?p{%5h&*LN4v{+tC*1`mh>MC{0-aXL5c^6!aqoSpKo1J8H6PzIE zWKmyJ)3UhTkNxIYZFWBTd{Nw4!$iWR4%t}HXJYmb-0g}^bHXrxMd)2;@65Rw1AlR2P{lhVf_3EBD=>{%2|HOJRnCW5iI@m&CX-HC3F2Eau zYeFnxxvJpum1Wn%5@z8_kBWHCO5}lY5mL&;=UJ>HQPKKbgmh>(TE{1{4|y)6h#mBw zh09&aFfN8Xnb7k8{72=I_d)-Aa7*;oh<)&E&K%lj0qrTzFOF&Duh*xa7&_|(Ob8bk z*N_x4{BQH8t*Z;?UkE_m!S7iuP5>waHumaQ_)3Fr|l9Fs&9a{v;B zaokqZfY~1}3Zc-z_PtS<&DeZ-ut^&@It+J#sI)qwLTzE7!O+!1Kvix@r;==t5ki4X z$5W@t_r~1jpR6%w0n)rIMZBRQB}yb$XvU*RcD1mP8O@JjEMVkYy8?L`1)* zcY<*pvMqkJ!vOuFNxlHI1JzzLBJbd&%`z{U;QH^+gep`!temu&eS8}fsq37 z!oOOtUOpM0(SRYdxr3O*KzSDIUgm|5X~ozAOX3hhcpGa0!gst`F82fq^1C}Ab568h z7QGv5Q1YF~M$0FI7EC`-lCHURPjlPcC+w?IJ-xWMdlNGgj0x(*5tkJCC`~dV~AM)zgk;x~!3618^hDGW3<{+x8;iV)fHQC9ZLxhr)D#xp< zZhn})U*kDxtso)^VJ#`tiDjcklmXCwtFgEun?k5h=|g+bw#&njnnGcaiu+QQ)tYS( z6|1JoO}7p1rdrxuD#&15IO(ouG1cF1C&o8V;URqV!F^=mAK49aGWqF#h25X?L1fbj z!ZAl9o{*?~Anr$6oTfX@;PUzhCX=tl&Z1gEeGEh4l`oe2#;=?G=PH7~j?}GSytj?V zA3#aF-;+q=1^7AZTs|?rc=4i21^7?jg)M>q)uUxO3y*|;s-hU9+oZ0j!`%bpt({S) z`fW$Dg(n!-T3W1f(0E#+nM^lyMvvfp!n)Tm{@|gPkLg}~o0PA84JPGNaBOStmxYS? ztCTHMp-D*h$Qt+wXJv4^FZ(5Cikv)7Ktdk6lo0J0uk z2E_8q{Y!j0_qi=!MqlRt``Uao*6o8=yoZhmP42Z^d&FTq0n|Z8Y>f05+N0KGt36<# z0&@Jz+J9TXk2rxqH^Q@h!OiH(*rjMv)CmT_yyaJ(#aa~DS9C(g#oO~;yS9fcmu7?{ z&0M4>kNg6Yv1kSK6v0pwYwO=E!aJL#T~L#0j|RoOzT*jK<&nIJlEOThd#Or|fX8$L z(uy#-aDpr>`!3k2GbNFK9_JmB}wSCzjKqR~m9e}MlB)780B z=YK&!zXYam$MbS{wT9m);|=7xLyC8ZZWy+!t|?nnKL;s(IfTTLLvdiJrtqIUUXlA8 z##lxJ90rwomHY^caxhe&tFOGW{1RbZj(Mbx0j4)jQX@8mb}8uPt}~KfIIZ9MMUC)V zvCokhzETUj-yxfsp>WM$?-E@>A4VE~ml|ur4O^=F7Cx7RfdN6a687x&?eq2hx~%19 zgm%tXojOxry*gJe;4#5spa{>js@qrj&#p9O#&NX=+3bE=7y zbBOJsLh@QGa};<|5^E5&45ITJ* zfku3PA($LEPXlq9#v4-^7d_-xLW&1D(Q;_r=tC?z)sVRu6gQl>8^E_BvM3%t1x~8al?e15ip6`Z~8vfx~K`qiqI>|7pJ71vM3Qj z3U%kPqFp2$*oIt`47{lQXmM=_tv8us1}5Uedx)7{^VyLx&%Ovf5-2~b4R;rBGk}Y^ z9lC#HgKE8y&c~WGqZldzlTtCmLA22)2`1Wd7}u+kta$t4_mrBv8IEJj3iU9)=?4;? zQ`dpVAH3Em6z~z{chn@@2OtJ+yjP5eD%AsySWuNDe!tj$;x1T$5dNF%?>yuKj@ulc zN0hAL%YwPdY~m{utma6<hbZJ)zBs;EKfBa8L ztyn7vzY%9%V4n?r6diQOY53EB-{pPXK4N2NYvp=E2J{HPoH{pBi3t1EFsD1?l!Cb4LU-ZBMb)zs7k=N#qF!jJ*sawEx7pAwQmwn1D z$~3wVt)~+&hg+c#)y^KO-5bGu6ayeG%2hHS*`FZ? z?bqL0iSc&#-LSy_z!xdl0Ml9M@|z9s1@nlXG{##&cRys4og8-0I{Qddzu9xZF3v4m z!)^%9Jd28RBUOR<)9JFtmr>qQ9aGqsofXZDf)QeTzR)#;RU4t7vc-U8Cs)dkvPMhe zpw%DQO3DPh$ijj+187|Y(&j;37FwRu&ogNrRJT|j{pzB#P(Zqqz-p{zWXpgZW6 z65KeuMvc%_gI*q=Do)a$i~A?Q8v)+H>wq#?VtNDB1P1>IVg_q8Mve`Uc8kNwf7)Zf|U7&UGYBpoJ_!j$w1=YZeI-|i$##f46uGYeM}G7z^H zX{%3ZA0)uC!;%P=7Ng!UT;_DReShK(gp$?MUiat6KuBEhDvQ9Z3RitX%E2rGm z6^AofDS^mYYYA2<=V&K7YJJ#)Q*kFWe)hoWudh#LJ!HGe+TaVulI4Py_2FZXrs6{o z%wl}Pj?v*D_A#&o_U{?v7hrQTu7+#sv|LbBnP6=>zx*HQ+dXN{IXR+4Hwgx5SlpQv znZT{|X9%!f^L9}J7~PJ?b%=Zu9!zG$bW}r^-!zvFVcf6cFn=29GLRCdp|625@I`6zj8dX;qSfrIajh+nZv^`y>2eJ1Cf3|V9AJ&g)|jSS$*&*0l8^oDjlKY+&mLY2k(u%o+QxK|Bs)FvAq2Xf++w+^h9^*rA3muuNaF<(9=}pgiBj0 z;2qz_e`w3YjOYV(V#GJ^^B5PFA|_A}842WdlSe9!A&akp6=wQ0S-8F8L`VuSANQm> zJ=wjTL=dasX8TA&ZTg5Q>-yJ6d8SIj6`f6{11rYFI%|1;e%B<{RjG1>vxRKWlpo+tduA6K`oN{9WTj>#=CIG4F?$ zsaCB4r50;GLAq2~3Yfc*V0V2BaC zL?N+dt~*JY@{iPG*gfb4v@MGQKeNlQI#*fCth#|nP`J>?;Ga)`@$A>r4Hs_tg=Iw#B^~$(G$zsG|Ve#v*N9`jA%Vq zq4iOgb8mmA)S~M4fyM0>yW3ul8L>&1Zjl=z5;~ z!3XVAeaJrU=oKB=?6ln?o4*~s%DdOo@N_BCz;LS6-I%%8U?n0$hjehS&_Q+=IyO>J z5$SwXmsudrjEW+Xc(G|aT0CF=&4qLuyW+eG5wsi4l1y0^aa}2QDe`v}(rYAgYFFgv)}7f;J^xJqK$! zT9R3FOrR?_(|1X)zw#{{tg@meq-%+@9T?l)WKU=x>2;uWZ0|7UXw)iWS=J-u-+`g# zy{S_`|8X{b`2a_z@iUBve!vvneMI6DO^eSrN2RnnH#@Dw-4N{%=21WYy9DT(9Z9=- zKRW~V++^%xnRDVYRSn_SkXG%$+Hk-KiMTUxa!bwIsR|-vA54t|z+!)YWp12Ab_+x> zEI;>p`vNVa5bR5%ZcMycyIWSF@2bL$LKra!R60FZ`rC-Ky&J41uVc=ph2me$PXrk5 zZ{Jl`81dM7D&4%96*0+r*E~(0cIH_LeCX(?T(DhRY^h?{>ITf9Laaq6yuTih9YV)t zY2k;QL`ap5<3yed6EhPG2$@H(eLT&?b!PwMt3gq+zl!}=pn~8I#-#M#_1zDfkdPOc zjs}^sLQV>$7gk7phjAK2hcW^2Yy8;%EE$D4api>@)+AGPvvFiE?}_Mf@Zi5cl7EvwcOr1R#<|jZF+y2$rw`+T_h-CIF`GD=*sKP2l6?csIA7OeE%q;tQfLMaKLCQqbBoSpK~gX zdB%Hl zUa=l&I~b);^NNjNokWAH>|I()ttq@Wh1ss$eZ#la3|7i&at+xXy!+JxCOF`fF*F@m zH-)1PaA5)_e{xMVj6DkoXzN@%brn)3g_{C*piwohEKSSf!uYn_8g~nKxdNCy7qnbs zYr~KcvT-Z7l(G*V#f&G98n27&&4*dLsv}P|y|u znzAlnD^JDGRG8oEW9!+>#vkncgiFCxV#ey5sZBsdW@a0G{erhYxsWnmqtK9YV=miPPz2DJ7<4UGH&)0Rrw}x9mbAgMB#h9GuQL%y3sc?8K(?yVDGfs@+;d_- zG)B%i`Kfg$J~KSOzC9dxZVu#Lx09oOK9@9W!D2}gV7)0rX+4~=A}H0&Eo;Fx!7=ww z9zf>n4^WDM6Hf79WC&s+#<1I7$Ye31gYPhs9Wj9w%00~(IA0Ud9+SqhRubjIS;5%Z zDuT?U$)8);j5N;7Y%DJPc@`Xcp&PxB)Q=%Hc3$%K2u3ZwxI=f_K3a9BpKjC;V3vZ{ zl0+WJXwV;@@V9#G)H<#aIg45@>WA3zDQ4`I@x>@N$0kuuzlvIGfXiOKJzh1l}EPfXwb@2A_DJat8r=UI} zn2AdS$`8VV9tRl7d*qQa1HW8tIS_|#f@*vt6+ViUQT z>V~@RJA1A{4nyP$HUtA?g6336MK#R6Da-?AZmRqkHJz=y3iUdn?fYwYc!KTkZdoDR zeMN=MY?yeW=;-NUKLO~|>qEV-`M87SJD{*2hpwcJWO;&S(~DVBA58gX<5!&2EL$r? zN%Q%xIX=k5N!|=fqop$rFt{YY;iWWN;r*ou;|;ic(I%P24#1dVII4H7sy79Pi+Jse zbkw3GLo02DE%Zu(NPBq;y@wrW!%4Ggwd}q{&9S|sls9y&k?RYk(}g6duyi2m(C-mb zUeI2mA%9iXB=Q-n>2~A`4wbyGBhYEJuyK>&diz0f!i&$O#@^oDj`ZMX+9k9#_+BhN z*vk_=5>eIzLq(^Vp-)*zVa$XHgO!y z|C*?u`2$h>Bq)41mDatww1eg3&1V7|4L{a_>}&xxE<_V_c0 zk=sh{pvwFpOAcUoqpD%Qw%D_|)jutt26*e*xCZX5;s+gxpWoS4`gANc%5GJ~zW~)u zUWY%J%enRD=QYBop)q)%W!a^XtVC5psTLFEB;_AkWtdOH)Z+p0gVsWE_z|G+?OI6_ z3aW=3RsF1Kh`jRwm*`t=E)c4^f;Nv|E+qcSiqDi@LF)3}f@lGTo7Tmc4DK(QDQILU zGrS$T(*-w4RtA-pY#V)Lf5AGd_CWpzSn|t3@Z!+<5~bBDf@gWl;egD@V`p|i7*xJ% z!UdtmIBto!LPbhoeUP($+jL7R=&HzgBw0ZJb6RTI@d1;Egd6taiPD6MVj4<_BL0uU zLK))75j|Dd1MuEPI42eb(oStAFQpc^O;24=**SGZ;r|y?gDVqP9;Iqttj>Roqw${X zUctgxfkp@ z1RIxavMxC*K{NiaFN2vfCb-9>V&OCf(R`VuY)H9e?40$k>7k4^|n$3lvjnm|IWs7FE!2aXL|-6 z$_e)BBSZQ9E2Ws~)7B*@0X-ka*8Rxr@7_@T3!zqttV2m}WazQgMq;U7>Q|iW{K&IP zbdCy)wHtUBUlpz0B3ZJ|2c9NdIJ3xj$1QPl)nZw+sIS@Iuf+j7Ow?EN=4W|qq(VqH z&u#5Sgwpef$u|E}?ev57Z0c$`k*nd1FiqzF8fz10kv{5J(TH_#%;TmixMQnUVH;S( z!yx{9hsGsCB*>5<>l92o_v~9r`j5?=VLyi=Y%tX6@{{pms#)uvA?y955ctQoV)0IlbMy z@}Ko%LCiunppAUo>{m^qab1fp;bTra)PSRDINu7+78CtKQ^bc86kbG{$f%@5v85fh zbyGOw#G{pj{Ygf1;Y)rU#5ZyU%MzAHb+Y2B&H=C7v}v+Mi03BZUFh21qz-&Bj4^0osyU?3a=E%SY4ljdS#ld&D|+{HJ@{_Ff5I*mUg3fHq#p z%sV*_VE&^vW5AJKzw)~o~l!IA_uh%RuR0+ z+|d~lZT708QahZ-y1_SVa-x-{)FNhi`hZ4#?wow5Q84$X_pD7U$xcr)NX_ch``WzfGI zY|H9m!c_{ug0APmWz=VcWm@egktjF9QffCCdO{> zhfaa{lOMHz#l9QiYOC^?oO_D)lX)?Z*&hA~7d8XWKrtBDWvgkQUWuXY{>`ZJOf0x4 ztr7|#D`Axi`c8br&Fw6;L%i^IDVR)kJY!~%xYIhsoS$Ae6Ft|a8vW8ZAwv&Fn|4$O z2I>d>)RUw@1099eWqZqz&&`*IpB_6la4#kao<|rbBumt{yHxj&&<7^(E~`Vy+!iGv zxS!T-q(G)%4U@61Yih?44_*VLp}<(o%CtBppMXJe=a{~}iNdRF=R3`U&38`wDkpX< zfauAxya0U(5{dkwzn4}$H zF%JL&h)zoYS3s!0ud#QyU3yN=3Se}zNN@ctGTQF6EQ03E+4B>*g^%0vSAri7B_z7tV+VWyJY_C< zS(}LW8b(#K5UY0L5F$^{Wtbie!+a+*dAj0MW?~wR6#Wyr;0cG6HE3){{WDRD)1;jd zk4{VY9+Y3ah-9tW;g>zrbjW}lPXQ}PCvhPQz>*`Uyyu=ml!&Uz?;!2VJ#kp3#W5?E`v#3Yc<6OF-9+%(d^#iyI*u5_7!c)k z@x^c*QiU&mLbw#{_mD-RONwP>H!MRFO2Ufb=%3%6&_j;K`(+DNLcfc;NLHEz?yzQ|7wcNnXDss0I7&rV3a(Gr}Ro$1XRQc38NXD zG+473%9+~D)8Ka5(dBVwHP0Ve1~G#Ob}xhwB$W$2SYpln82(CKWSGbibM%7FUvH+? zLV;0qsTEM{eEkFUdwr~~2)V}06)HotJ;oba8ULtij#jt%kh2YYkxMkqpvQJ!=uvF(aX|qTSuO-XdMO zV&{cM%3{oQ^>N_!)bmdcAm#JNpAed)(wsC_+GdOI}!+a8_qyik2Wyn3mL{$LxoOkNP~XA|ttFiIcFL$j%NzC|#b+b&Jq@ zNhI}WI~0ww;Oq7bNSzo!II<0?;+q4V_n}csRu~N9G)* z_|*R@(3)I6U*uZ$ZAT{J=)aL11$SBXPUCUj2ZQF<=iC(B9c>d~#GqrvRODi#!I2AM>KAZWp{zigBkrjn zXt>`$(@92?at*m2w4=#Tg`ugyVTN+6N>fGyCaO#Z_xXDya3#C z*zTdjT*9$0BvJO0;D*%5-`Hopp~oAjENV=6O0b8iHobg6Wz)zU)cA z>lwA?(zrnF;kO&%QuH`U`DuA5(9k-$CmHdt@W%f;topc(CY94UcKM6vTztcF%tj9}JxIHs5OrI7n-3$7Qb;9g;R)nq>jn}oN(G?Xk6XV*7 zwa6G%WBBKW8h(|5tOes@Q9hA_!&)ivHa%hTVlRraH7nvT^i9E8?R&Ohy0bta^7@g| zctNe+?%&0fmT#`8&i4PGnJsoBCB|gaO7Cm_(hZ39bzhsJnVw!4u!&@p3BT(@Tzt$T z)7mvfc~8)GJdkr#Ub)Jc3h-s$B%fp z8^ZE#TF86xb&~pJx$GTq8^@uDBx79EGmi>^8rX*pOb1JUhCEzcYqjM?L>!M|{sz@e z>9a*-1Q48`w>;ep@G|$8XG}G)a-eHAv}-1gtE7)TZDSl9?3WlkCHa8P2Yc_=p3bol z`iV{BWizFjYM7^#!XuO&Od^E5q+GqFGsVftguY>XA9!fIX@?{-C3JS(w9j)HDJ{l# zE@8ujLVaynA#4?LA@#&Ki2o35=<=AuDuk)+@+9UXZIxChBcg$VZ(0;$xhTnJ+>tcK zfuN#~w~q+EEfL*kFXZm)|E^tYW=3(oGOYYK-(%V=2_Rc3%4GdB;xo6|YfeUJAP6Dy z+Zd{BEVg`r3@_FhAgRQgJy;(vx{YUPA^a#d>cl0f&RKHL;}JB=b#4)k4PYTD$6FsD zocW_Je^bR7vLg{_`ewalXK(O-kc3X=npj6MO0++B$&@pa|BW7~VHy`OqJLzr-6k4Q5`J zMe)qY74QSwPSNZ-?DJ5{WUA+l*7QMZ*bKIWFD_<+oH#lp(lM*UPk-BYg;B&knmv=p zDqT+Ryed1wGddMEGFX7Kp77uEe)`VRpPu*p^$-eTk@9kSP2$7m-J^@wYWAV?c5bB@tdgDg z$>fP|_FIHI;Psp6xkOTsn)No6F1_js=!>^`d-6Y-^jcW>G^gr><@rB}kxho>!g8jB za;d%b^pH>sE^GKv?Q%%gHGanm(VV{k-yPv%lhN*h`Gn%5j-vzPvM~3SjoGGb7Ik2 zPsBU?8o%!hFQl1H&Vj28^vX=+C(DiPH&6?nuoG0Q`^8c2F!wCQ!6a0@HfN$pOX<2= zvL-`B!S$!Y2O9t{rezm*Wq&klbP{o5tTeOnO*)RmzQ)*4;-;~Z;Lc&3FTjJ@HRw&! zow^u+@yyG)3=-&)N=0(~mDvy1re%{UG8m?-wO zFUB|TFDhG>z)Q>Q?&rq$s7j-=oR^8d(j<(MUY*R?>Wfu#>TquYeOE-o8jjp-O-wAb zZ!o6#gYf?ZaX-h%&acPH`?D7=u9``MVHN)o(j}ZP{U#w_VBJEWg0!cC6-7(HmiHoU zT*v6ARt{E2XD0du{N$-gqdc>ik*xesV8Nj^Pyu(E2x zVr>$7LZf^WrNy<{P&XTOFBveD zc7IXuCsYHRjh_xEmk&QcDdYAoR$gG}!CXT#XZOqHy}5@%7k@KMqbc8Ri0uc%!ZOR# zyGB@^4#HZ)yXq;)Rsc>cXWSuq=`Qo5x}rpGavqiuRW-$j#1|~mei$v46(WjZzn>?u zqo-uUe>wJ!R)6hXwbvip%x4u}(L)0{zPv%x_{d0)E>)An3M8+8i6_NXUek`o>hT+Y z)-D19_p0-tDHdzfm!qkH5RBRX&?#gvc!QZtVMq8?;!pr503!I}0Q$ujJ$_#9v6E`X ziL9G((sx0{V2#84Uj*AU)NJ*j69}i(;MI{>-BMpu;ho(*yyIt(#W;~unS7hMszju` zB^d*u*r>61rKM0CV6gO%cI6)`P&I7pxb)ZUeFY(Y7wMep?LFb~xE0AheRoX)CEG0{xX3poF}q9ZqaqQ)TbCSZ}L5^(<2p&uZ=4Z4gv&dA{76_i}DZ5N)*lUS0TNs z%wU9g3Y+c)8S+M_ce_ZY%25PBVq=+=0QPW({1t|M;gfBBge3*Z zggnUVIX}gR-ELh(v>JnBxIvamxxvn{^6TKXP@jIzftIJJ>4oVR@y27;sC=)e?48)C z&(8h8z$oCsaTL%175VJMBl3e92XRbPiJkke3ORf!lHO$E-nb5qQ8IN6t_oP}&z1_W zJU=O9bAdCTs~NDTBO(&~lYrCc__iw@Z~5rF!5|y$r&sNH>so0|O2Gt^^}53(NDAq+pm z27xUoAr@gzu^ZkMePt_)t4&ap>k%sjY{13#~AbzJ!Qo#zCdolsKR4n75kW zupPliQTW2UYwc$FVjT#1E~JV-k``P2ErE5+a)j?juLY)utaGqCjRyQFTRV;VUVVf~ z;S*>OcerT|b#$}{P042&wiDS3gsz4{nxLIXsC17#j1a8rzB&H-3mi8mZ(eB}0jpu9}E{pMHpMT`ht`Q=eR$}6Gnk9+E zhe_2IfF89P;F0-7Wk(b;mQjd^U#3mXwFY5$qP_(i-Zg)z6MX5aE&~70q3UbTuKC+1 zO+@cP65ciyRWe*z`(?f8Mk7p$M4z>8vF)m$joBtGJTD;^XTlR^C53F=`FiehVuUa8 zi2=04!+A2}95`0q8@|8ZTHtp=%e9LzTn2|+u>^In$MH*|p45Hl-TWv~PnIMsbf45w zyYYF^D2Y0i(9i|pPA?8=M7}r8<AKI_IlWV#RjnkdRq@0}TE9wF#V6$A33`eFZ@3m;M`@<`%65HEzX> zs%Z8QKd2U?)y_O?K4rrPc1OW7KeE-Dl{ZtUx!GbOhdGqr-YS>+-C)o;kL*P@zs8rD zLH_$Vs$D!N-1dx@TK=vZg2Ee$|!2!Y0N2-Bj|=H|f!1MXt<46o=x5@z98SoP)-j8mox{-J$4ZmmT3 z?c)F`eT|^SJ+xT?@5rW9B10>*ruzp=19mCC(C6{#=3|QahBOz zncDjXqXB~C29j%zHJ>%?vY@48r5sC1v&{k@8*CZ|>sPVC0u=5+y{KWSHE6~6n)_ZI zRroD9PFUNez{FOu?Ys{%iQZFjYQ zh*e(s6tB8o6?g1Px66QV(J8gmoL>KGZmigOn_Lq&356cO%@WvQwhkaHNYAqRIEsa! z`uW0Lp8qa!flg%1ZKfy&mtDrrUi`~-wRHGs;|tIga-Np^2ljg)TQ>Ihc@R|Xz*X#oo7l|%z$PBv`ij${pOWR@uf$}Xz;*h0kLr-%}V#T3yKns^ngzTjB zp_vb5CH0Xj4s^SUwQfektV_CMNCVu0spGwirbXSFuBn?!)Q7iqb~lt$NCRcd!kZacepfcW^~sMfw=AinjNcB&9Q?LakAKiehfy zA%;XW^XW1*p{T0}@!zoWKOF1w#z?G4L#B?V#Fu+YvCpc!xHG~gu4om1E%8kSA1d`mE;yQ&A@XSNeKsQ1IH5)^}NkX%i zDh1FNAxgP$eeA~3 z^`zam5IJzVD=bRdq(zR4m<9o`0Q%2kc!*hpQ z=kDw53F)7;BOC3%0Yh6GX^%Pgp+>(BVQO{grB{kaLOHZGGtejiL8pPDQS#UPzNa7S z9DA9tCHYMdpI?Or&!0twm@`>EbE;jJ(Vo5O;vyEloCEPY6Or`%M@Wf{E=hqJ@Ek$x z?(#=PRRK7Cery(PnX4BOtjLjns53V@;-^pbhk;jQr3GN2=sXr5vD;gA>EkM}ir{CO zO(CUeCi0mXOo!t(GB=cbHWhnNsi^kKJ9{#GWmfM9x_msF1w#rvzhs zzBLXaa5aJG!}Mda>5gRZX{ec7JedJpzh@!^)k|DqHudRab@V7aVQLOAhKs*KB6)M> zSkiqLRrG*{mjaanL-O=%>lv*(L+QjAiXF7E+aPGCSN1fSY(598H67@fXbVc zIqGTn3r0JNuT@^Q0JXPqtiB682EY9Y>VtSw^8MPp8=39taAF;5>XH6tQ8o`s+_0K} zF$1Yck%vDr>{dOp(0iSPLwRKf49-72Y`xJ!1LNl;KtO>qQ1ivDa<9#FMrgnS!^1v(qaaCRk0RMo~OKeK-6xyidLD$JnIV_!l{*vCo%{FIe~ zJ6$uxptzzeC0$fgK)WVDJjsA%O^p29CT*upw-I>oOhON92v{s(N@~cZBVl${4|ZJ z6^uom|2y*9&S=A~GI>2Qal=TyW^3U--4Mmj{2vS*?C!=nXpaJE{Uoo^dJ)e^mS&lG zMf;7HKM%#BKr4WBBEQ?w``by;mD_0cm*44qJSVKFioO)Om#8ktVS6fNp(@_GI%z+q zT1kp+fi{-S%PIlmd*ECjwm}1$Q-7*re54eO%#TY?oCY033|{P>|GT&m-r@Nn90z{s z$&Kxy3;g~QJM0e`V$}fVIc5Az`+Fppa#R5;D6LKR{0xWVU-{qD8r*=#{H21c{$*Yd zlzrY5s~@0ajHH=E56N)qsXr&wDKo+rcrN`$VRCaFI~kT#*+xI53s}g0WI{W3b>F|L ziI^2+I6J}Ov#Zu+%Cn>n#N0|tO2;Z+CnB66UJ{6aC6wSVlW;h`a{JzQ5`jU1QsYyz zu1(j+Fc-W_fJaUxrmwI7DVH*OrsEspKVkCiZ|*zKXZy0FQr|a`i;@@E76~Wus)cZQ z8#**ayT~z1AQ_zTLvbGh(u%3sKB->Mi-+8WZ96!K!Jtm9%0#|>&hOJF?p)395dST` zpc0KxU?v5V=m#T51misORq z-n|YA1ET$vXn`lzv^)*P-xUYfJ=?7$gO9#GoB?F<>ZBYpk+*!p%T~$CGD9`Vwui_0 zQ-#>HNGOdkadjWMThRyS&RkL7)CzMd@nlqUmtM7}cp?`-$v1$7Y-vyclpaQq{iEkc zbD+phY3P4?*Y5jG5^+$@?juxhS!7&j89K#2Tz-^+&`QBq>i|3jfGuPzRtvqM%FJk#Fx%L9c;2FQ@1+Z)E#p#fNUEfE9EMONg~VZjP3n+Q zvm;Gfv#?d=b7n=%94e(u_xG9DFVP^Lmsq^$9j$i$kv*Esm9n+mbM0c}pf@m$bdu)p&uU#SxFhxEGiS244W1udqe z4oosLSpUV66Ld`-krPZVi9{g*A_ zV+!WH=DXIzv z)P7uVTeqZ#ByDJi+^XxIM&UBU@^`Fs=r}~lIfJU>2hDcpg0xnZ7-@*TV1-Y+DBbj|Q5eK{v&i|Byxku=W+crEO08G;puE~J(&MDnl?vmme} ze~HZn7|uEq0-M3du+6f!sz&uVFJAG}YYyHc*g1B#zvynGGbFAG>_5fb;aHf0+Cml$@Mczjq<^L(zO&hd-(aL5@ z1oKw&gZmGV&;rWTH7ppiSX2Myp{Ac93`-j?B&VP`>m938Nf|Qc$zz;1QTw9xlRm!A zf5%SZWA?84h=ODc1o*sE)alu@avDaw8UD2mN)<%1%LjR3*f+?WZ9lWgrUFdmynYT* zjk`eFHPJY63dN}9UHyx&$)d`yT!;}4SVCaY&zfDqi7q$YDvYWecaB7SBGUB7FWB}{ zupe+-@$-4PCXa)?ppPDol=kv+F@}cza!l18wA#e5V=M18^e)N8q26JydfcJpm6UtJ zO{|^W(@OFMT#|(=3zl*RfT8!yag_Z$2Vvc=9*NI|U2KRO_iy$00I!iN4pG9fR1OA=25~`4;N^Y?w669Bp&tBb0 zRIe@rp(+lEcT;0HsBf7l9$%CVS`r1Y@Aig%XD2HH8)`}60>aW9Td5Poe2-{ZNc;#doYg7DE@JFT)%hKvwhuvp zJdoG95HDRN>)qCV{DPos12kk^a5HrmKt-`hVt~rOgkl>??mq`V!)mW9XstoSY?IuHjsR$0beR z@H1(%STC=%+jzQ54vlhd$7#G~u2T5u174&n04?|Q*W^g%p$9`OcZ)`Ew>Q;@Aznt$ zE4;-t2dQ$yrti1EQf!i5r?xJmy}1;d_a?A~XXem~!o?2@$^HAG(8f0_gu_V8NrRu7 zO}r3=S7frgT%*SSVL!kx}uUTGflrA&xrH?NA5&MlCKX1(Fi!Pp=*n{h*8# zbpKm`*gCQ2#&JubtZE%I^BP?vx@ zL=p<8&qaa{!b20$>a_B)BO4-C+mn?~BF%(frFKL9{TQ}nYR-*^5h!#KBPHxfOjwwb zCU^7k!1X$p%Mn!Z6JS$7?SJltw{!~M0L;x3j8V znp0}ki#6RDIbvXiDj}NmP?^mp zr{ju>GRoZ&D=ho5VPl@NoqykK-vhf5D5r8UMZwJI)!~kovBR|=8)W%vq8rS)eUzj& z_nM0bs@I=?ttpGtFF&R)D?Sd%ky)t@s(ey@H3XPwQy_Ks@1d%NzrAux%@Kk~P$YmS zN&e7fmo%5IT3Gh?p!Siin9VI zZwh1WjwjrlvM!#|nPB$v@pt-xkC1lfGr+M4dV_CmuUPFE$ zTlA*73v;&rJ17UyQRg2cGtm`=UiBjJHYtW^Resxi6o7!_X{(Yq@vtFhfUJE47#EYs zTw6TPsQTdBr;E3~u|5_;W}b8dI@H<1xZi@=;fYH{?prtZd;h8wymjQ>BZYP8=_T%jvv`W4)errnx{5UW*?zY9d^;Np!rB!HA4N~xg(qu7f%L${g`sKI`! zHa?cgntg6>H2u7NHDqT{9l0Kh<@Et1-F5;JRBodpm}Wt2BD0uvsjhJ{ZOT?o>NFsg zY&T@0m$~u7?WgK{)l})sXS5P&9A1w#yKsX8EwR{QG*@pm^5|6>dR=!Rk2f9?qFo#F z*ku>B`BYAQY~97X2ZM(UDXLZgC3c$p=D?k&IG|1v0ekV(xmk&jTGNR6=~0LF<;` zuPAcfW4O`Vs@uk`b_Y;(DRw`aZ(oPK;ZmaHWZX#d$Y4Zcwwtg_Wgnd4p&5wV*$Qr8 z&{YJrAT8CH(qN&5uieV53z`flJ^I^OnmS5t%lz@}2Qj#|UE#N6lF;|;3w4apX)+c0 zoc9q344hkfA_XO^SX+aItOmxIG?KoF4!8Q=MtK(|`?`C-Y0oXB`wN%7r+EA!e2 zXLIChv6XgMVv-hUh}u@4scGiusuDQ8W#o zVwVGp8C@U2q3!o6Mp7h@bsCbr7Khd)uV-+Zo@J|mvj~4*(?{tlyVztkq6J`Chv2n( zJE7ho^Q*S=GbdvD;#oY~exzj-=jZmH%^R{peJCYOZXCz(DtFW_49yt@#KstCRj!)*N^w__kgu{&ZjVlIVW|J-b zvh0A!jF;28{L>QJ0()Z}K?kU`M4F7CjrKVm<+b1F2&5m9iGXJ;YT~Qg6Q9tOoV{2F zxyXH5@q6pvG*sgKl=j+!X|jE^Zf3~JgZsw(8^zy|RDZ5dhSJGE+K9}QfN1}ivl9zM zO4TkB-%jTeho0UB0KIej@w;0@UN>{7H$SZGAKq zAR?G`+*$99EF5ZQ5iZT*D4M!?=0CehrDXY%TyG^$opPxJLl+B|eD6OQand%!GqLKw zYYLYf{Yb7)u@4FvzY-_h_5I$sB&N79=SGlUJ8-Tm4le{bTTud6w&B`Xw+YRw^QgZ# zNH1k9j~M5p9_fw+XULzA>7%B0BuMg^aoM1V1rI%q8J=*3elZ zreL%0vSwJ`;_5mAoBh_>i08KXSdz(rek}e(?qUZ7R0Ymrg*OvK@)Yoevh-XdW<9Q- zsv$5cZ5*9+>VYAXnr8vq*^h29-|u_eE{GM#1{QvQ;psI^8Ek@TM-PFhySZ^?(nCD$ z&7K=9{TK!`dU+sWAqQ#zlP6{+aa?mh9;9?QMO4Ln&OAdf5{HrDoCABwFYpA^nEgm^ z8`hLPmefr#P;_;Ayuh;r(t~fX&s4c2=_w{y zw=gG0o~t| z2aL`V=beFzRIOEq6|LA)v!eli5obRH$~om5Zspx=HnDsiLWR0KC-)-;k*+rd2EE*g z)ksz(K}tD~YnUU7uFpE!OyXQZEyDx6e?dwH@-Et{o$w-(D-QHbe@HFeforkzKnzPV zr8+@8jG6^JT#S)P0F#JX-x$pD3-T^hKmb@!SPU8`3~W+tDbqLVl+HA*bX|7OD>N9f zMWLhwq9ovcRBu(YR2U?h`HESy!h)!6yY0QjMWF>;}0ylI<40l1*Ab14|jGwp03E z0-2qI2T|XB78*>4SUTZfuLo9X-h%3~!AkG{f&>S{L@cmlmIu+ELt1&{-Tg&xeP2tn zfBh(RxUpKRIbj_A7buNmi_?2mkipO1A*Ux0G2qy38>?dw&zCKR13oyyK$|`v$&OJdR6hqjUi=zoAt+`(m7Gt#*5thlFf=ZMj6?Ai}{o*0D1??@;d` zB~${_GL;l8|q! zi8-{Q)aCkhPLD;a(^pqd%#KJD-xQ)zc02$sj|xzS7OOx9M*LLOY4&^=nRIv8G|2h} z2fw|PV>uYZd{5%*$Rl1LuD6V^!_z}}pSiGPWP684h!sxbl>U9wD1e=ro)!^f-Mbj_ zqo<(1ANq7*NA63Jq8yXfEX}{SJ|<=%^c(*hFC^KDn|~$zE;mbR-<-C^I_zaRxjw52 zk=_?DTgQI@ZHz85s5!c)q)Zgb#Y^R|&t12!#gcdli_xT9CBw?5 zMA^8r+_`&9Y~?D^jn)H|ez6LA*&^&Smz=&?r_i-7QEEEnD6oXitV#U5-2ZX|&2997 z=ZvT>*zf-q66!w0Zt9NG?nVVh5RBLXjOlZOD^3MAAvO995iLjS|8+y)A`mDys1UyD z|J)d;(bZ=ViT9TXndK{t(pm~Ky>TLO1MZ{jTu*vb8R;RBI2Yg$-MH44VSbGJJ1Rnm z#PGwh+b9hef%)x-WgWputYr{lpzzV@u?|~BI{@J)=LiyD4$FT%8k0tLAN%^9TD`@i zo20w2fGdJOg5m_H!}OEFj5VvoMBXR0aVa^7(-N|Zf{F4u*$IdsTlS{jr=%81Sei$J}L(%N_3AWhpk4h#fp?l1{- zX)VHob78qsV9%4P*0s@qVYfT14n^msxp1eU1CW;_tGEi$byoj{6h(z(&G|v*+zhq5 ze$HU4^W7>fF(tcyqXC@sb%vXKd#uE#YvnDLkrgxwnfZnji_iz8ZC*6c%qcsojf{}{ zE==ceULoF){4$06H1A?k;*tn-CDEZ^_ik_EkD!F3u)_*QlVeXjkMFxx!rz?k0H?MD zgU{GU27EdtJDNtFQFMu>%S?Q^H;XO!bj|;3zYSl#pGGd_-J#Vm8j&7TDNCUWa7G?$ z`;;L9y8Xx_A?n`Pw*7vAW;}saLA0vDs-(o&4Kc|0qtUq zQnaOdm21mrKcA^xH*(vOt00}dg=uyjZCcs%Cr$LuOb=i*(om6xjYSZ;-h)kv_FuH~ ztV~gBreLvMo_`$pY36Y;jPUmu&Quq{KaHYJsLb zmNVJ4bFiUNccB-n#_`F`AOcYC+UTe4R@#`!d18!>^qY%`kt3drQLNOrE=|@2ZW1-iK7 zxkw=q@q)^w2?>~Rz7uHrZ5{MDfslGR201+qLQYw0*&g6j{)HS>Fq8}BHl6}`ots^% z1rncZL+PEWu+rbWs%ak?i~d>aOA7RU#2gu{{8=6{jOgotVb?B(Q_fYC^kI=5tYLu`b!JAEoSSz+Xa9bLBj_4s2=XNR z3US{&tMTjSt6HaRBupJ56p*$z5Y@W()g$pavxM2L<=%X>{`Bh@;_^wIP+sbL=RziG zJok1<-WQDx2Bw!)7SV?v98fu!@Nx`L{l>!SvP{h)h{?Wb-YMra@Ms{YlGzKC#Qg@y z6YyLtF7+1b&B(D$zp3ldIM>m#Pvez7Lj}zGoeawnX{Dj!vqqr_ieIyABp+B5+G23t zUSRV@Qaq-}*zp{Sf20X$g4AR9zKNv@Q)VNeTr$_Q9O~e+hC-l;x*iC8RBz>pWH za)1C;TG_7wv=GsdrE~X@pP*c8o4@IiTK}-etVlO;ZB2}A# z%O%B-ms$Q{6WHd0=OdRe5$U15AD(tb&-h6Gb2RJZoyxCPF-XMNpYYQto{NBy0ZbFc zsiVi&R@#-NBRaPgWdRel^LURUvm1Plrra3^G9h{qh7C~9aV%4 zAJd^ITW~?LAPI<9KQvICOU4nU%#xne>tB0tK~SFc=pfmrthvXeCLptxT{$H21;eH4 zj@kPsP9B|G3vriq2Jk2bkr)owriIlUbkk(cVwSShHT)=mg94NM#v)|+y+Xw!QqC`C ziDd(IPQ~ORk2tsM;y+MbloDCj@l2Lt*!5to)_Vi}jdLU>hq);?ofMqn!n~LGkS*l- zCtl%urCqA{%&~;I7Ne*fu6nq9I$I|D!zVM)ixjFiQpF@-V{356d2@e(t~JTP=D((i z$SlMi_CPN6Bqu~gxEIzVhM|4J!ElHUXv#nXMKiXtC830f^P-mO&|4-=gDn>Coa6F% zZ4XhBTk=LG@FY9S)7eqABcPCJz8312ZvjA%cZ|;SlmJ218VvJ$)dzKz3q)z=Ls$wf zo$Wn`b2()ZGWs$?M>fVgpo$$MHBZJqR0+|W)Wb$wx{ss~AuC>Q(YU}d53OVBKsGF* zdQZ?1Q#0rJbrK&K_~n@C}j>$x2cjMPxLW{<<7EtKV2gy zf`=?b+lN|viZC#95oJ3?vw2Ut_u0TqYPgMb2S;DAzw41;S>UBEV|L=9@}sXtKju{U z1LYSt3KP8_4Zzo!ZtXrT0wfb*b&lJ7q8Kid>oXZI0n-S(qm)^;@3tjvQ&ut=?%AT} z0=#{iJAZDLO-WvxK&h57kP28<*<_>?^zdbP6~svyfD@Ew{t2Yl8IWQ!QeSR-Cxi`& z-I3EXvHqsPp=I@N6aW2*CIicY}@@eXFn2?k!)XV zEYJ2V6NY?CRCh0I7umcv+V8^hbvf2HC!ANA0;5iweK*ibV6TBPuPYhiln*@67t&9u zN&lqhn)gZ#nzJizRBB^u-+F~*4BoOOa)XzQM6NAPxQE|AY==?0bdxU@_BV5h(}%S?O-3akf~G=#7ImZ zmt}_35KAj&*W1E}4YeHD7fPbCXaudmpFjQ%pFjmq`NCyWtG*mqPujBtW=UOCwX5? zJUt%gy&}BB44xupTQj1a3LEmOH|)!be?Zf)75N7Oh6?g5Ax#2k1d>i0NdS#iPlxuK z0C9B&vMksdF{%CN&nQ}ttExr_;O#fz+PgSZaZ?&WcK1vWxG!G>KLPNlK@1Dkujq!X z;Z8Sc=d&6BFIf)Y)(X`ozzqY1+16-!5upGLuz^R#FJ7=wbVxryA z6Kr!3dZxn$iGI_X%Fx3KQ^otZ+@J@wlmFb=Y!@&4F9QN<&3@_U=Y#zapqq;v8m6e1 z%L&I?ev6C^QmYA!iShz<@I^6KiU!a~cvy%W9k&fhFXSAxO8-C4+^Gn4C#~xSC0hu* zRbeiRB=a5%B%xmM!zy>?bk#T`T>Y&E5{ZbR_3&R#{SRvvyD%3<3L*$kmgj0MXNwvD zS3s!0rr?*2OkV(-=fASuD&fJ5C=50)<bDz@vqGU?k^~IBoaAC$rc@!>f0F?4!i@ z(}JjPTN1GTyc33^uq->Zaf!(?%pPG1UB*w5<)LQ#+NcFsue7eF0?b6|6n3|yemv-+4I`|yR(oO zQ9gguTY&vEY`WSYL2D1vzTv z{hl&R*54q8-+9%v>k={Sb7yj4mXEUx?BNL~sA|lng5B#2Ljsl-7M3LJw$8ff8KzddnSf zzjGaeke_YhK%<0j>+|jCW;hk+8mn%Qo!u7kNp1)Sw%#Pm`r%`aHjb)=!wcz}8N~iZ zfb-_ddaVeffX%$*|6p3{nKDP_;jrXKc5V72mmQ)<{V!lvji{pi zWKa26EB1@URdz)B#n}8dpBQakh zo35`BGxr#BHf>3__EyM8LmHo%qx6DiP)45Ggl>%sQai>ROt(rydg&hmbm-j)pX?)T zgl^^AGB&PIN}Yr+kQTO(B*;=E@C14Kn?Am+Wfpt6Z*O(+a*$tkobx@l*MP0DN7j}+ z2fGpF3inr}Hh|U~dLr8qErVsfCwa)AGfOu=$;n~FetOtb1jdcckP8dbKFDF{kq<)Y z^KbmG80^voa>APpy#B^Xq8jMMi3N?9X40A*ebWLt1QOz_c7&LCqP?94y-9j8-;WSp zd2L=9--=IOXN%`^gqImozP~m3|dA+ZISpCF;-th!fH`(YWr?Qq8gm;fAZ0E|+Zy4HdKUMy~=ne37& z1_XPceM$9D#TtV^GZSD6LEQ2ENZ#|q=wrXhDqe7Q%~vs(o&uIs5Nb(*>`}Ti&4~@>K!xPs~~3S>Hte+S`=W6>y7z zBtBYC0Z$PdwlUUmI3aQbf!IK?SJ0YeDlV`s_XR7HARF)AO`bRx)Pl}v5SfKxFmkNK z8H+jDiBN`luN3G8iuB>eB`2Wvh5=z8(mO>F@0>|x?X4zyaLt=?2=Liz^m}04S^ZfG zjwm9iD`)CP+@e1up)?HFcNQK55RF{ z)Ai&+eM?U6Nl+o=?=E^&Mp1vts)6HYTegBsin7qFpL9S|K4E4lMvg#6D6An`inv)J z9!Bm9Jl&1^Ni-YhuC3?DH%3pt-7e1Au0`DS^OvV(fNYTNRi$Y!!WRZnUuu&tP4KLc zLGL9^hbFdi{B>|5*M@zOmXU4!T79lsXEH((OjO&q3jF5n2`(4cF8C4i~U%;KEA< z<56@1%QVjz9UeNGYZDn}nxcD9TvzsR3#NGP|AqfU$I#cUmFKR_#jLoeEperK3>bpj>) zu`kJi{Qqv}aZMA%d(ceVyPA3$Uoji1gS!Rp45bERl3`-T7-LUbrdwq^H@RUURn>gy z6&S-&jJxr=Mwnbx7lA3D6kg;BF$^msLBnvJv#Q#L~ z_L#UMOB1oj;(IMsbxI1?(-4#5Pl7HE?(!~F!S~-bNC(ssN>6imv|*NC|AVf_fRou+ zv)uLFFLbtzl%rot`WT8c!RxZE`SzHAHQvQT!Rb=AN z-vhFRzMnIKRDxElMQ1I?u|@ph#F(1HX~`snSkn9)&dP-0hFz4d`Cb~T<>e|EoWD2- zb9LvQ*;u5@LoXqtgAcYJucTq=Yw_dBg~8zc3`G7Gbu9j?+j7k~C`SIxx|Ji8K>Y(J ztOR%n+Srkf8T|@~j|Q@0nvaaGFJ4u+0y3FS5Gf+_CSp@JotP62y0@wX~HjA4+}GUmAw-x11m*a*izluYjf3=|=zyFmgQuMM19z z-P!Q+tQ1m^;9?H?0Q zm54DzRGBq&xSM5wrw9+R#S)q3;feJRX`(q^LkBiTF8+jS0#6Pa=IS**-NnF2${<%@ z&L8ts5h431zKsLu!#xQ;kaZ6yHSe>a0YBEOa(>A$GFoi2GwIb}U~kP75lODSVhL4> zRkSN}Dc2={r1D}zm3!97?iu^YAzSc>mM%JSVZQB5HF1^t#_4Iy){IdQRQEtokF7me zLHl{Zc|b=bk_kR2+i?xR%V;wjuXQ-d;Z0&=UPK61Li09ojtpv1n-kYFY4b^fGyU~P zJD4@D1Q*%v^JZL8&V>qQ7QuqE;g7CdJn)Vr-}rJ?342E*eY6!`DY-VFkD~_oZ$CMV zT&u&SWVMuz+Hx$X zH|32brm}Ono2HbMQQ#cUDbZKC@m5PcEJqYNqXM^iGG4zatKU_m-vMdfbNgyCge*gP zc_|SmfKgQ&xub{*uoF0|KZ3s8*jJ%v#$Z#kLETZEotwtUmO(>X>b-<4)0Fq_QI zqwVV%&Axhp*l=0?&TlZGB653yP#$K3AXXO^_-3D zh;OP@qv|Z-wc~hB$t-d7+h&o+T6R~S#40*)HMuUmAJc$3YuHjheXR6VUt6BrqDj}3 z93a99QdzShGj~qUxqst4#78FeTI8(_+XS$*)9qrA9fq`=$s!0)q3R=QMFpP&ZQ4f2 z(N8xpDoM(sJBz_YdXwWP0s%|j=&n#U=m>=y)kGcJAQ9BK2S4`(VRgt?na zfZ^}=R3wXMN%>>{FOO`Beh2({Ov%Vrayw|ON!YDj4=%zA7+yXDrn@IqZF*dL^(r6l zO*jc=L0(UmX=lQ`Aha<(mQlIp?dpw@RY0`-Ja}#NJI~n-+`*@fmsCN($vJ=u2rnLjXI{|vT&5cvcJ6M z>wigw4bW;|`$wp?pp?vawp|x~p?gd#V3l&>?l>-obb7|{6a9~1E64CE-$h!$hYAlG zYZm=tD8tRbj;WW=cf#(!H=YFKM|+44C-edX_#JBQ>1UWhqE!?Rq^dr4HI|b~h-yKA zg|xIrDNXbftrcjlCg_Y3wUcGxS*!HHgyj5+GU>SIL3*GF9D~oqnWTR_ z6MpHhHs+4Jolwz-%>e3>Vq?o3V+~rHpMlyGCqdNfYuib!P65Ps$LY)03mZR%smLS1 zrWkS)&Xg@wNP$w`mBtc`1fW%S{I%4+1DGR5D~1>*l?^eh)dJ(|upT^0^@RWDv5CON zdX$YPmfdm=AOFg=gin}2J2ri3N)9Cns~4WR(^Tmh=Y&5uB4C7ra~KyRx)m_*E+72% zQD_KaQ{0WNTmcVU?+m)e44-R}OtA&S3K3aywl*&-p*$>wzJiyY4aB!5s36(YzlB-7 zz41J+LE1w^T|`4w=s;oe-W+^aW8MRxUyf+aaa`sONML^G_94deSG}C^wKU-`uxq3%;zr02(UgFN0YI9Wc!X1hJcXKD{8qcBXq=g{ z^0PXdNjfi{2VwGWFSvXW*C)%`DD5c#Pip91HjMbJ@65gghN?m=6hR52q*_Kt7-EfY zVT(=Vb*Kv|^$Eg!$B(p?;q}#)lEl+t_m<#yPRHk8+L60y`m|p}D65ts=>3~*?`TmA z$&74WX~$*CF$%gvV_Sr~nAgtI`l;I|v!}rI?l89)xXlTYaUbu}FNT8vE*Vi%=my5f zxQ26CLU|@evR>55X0%5C*}st!dQap5;mwFsou=eM+T^{=FEP6=MskU!Q%p z`M59K$p4Yj&}~X+-((r3^mc)+5zuN6bc;rhHZNq^W8Uf^irC!wLhg`p}By=Y7y)a*^3tHTM1FYJN$S-CT%lT^Cv z+x12!Q_l&NZt+aw$YSpBG*j~fn`uvFuM6-iDHoRs?jfWdjps9Lo6H@+f<}F2@nN#0 zqo+&7rFIL~iS_t>(H z8~#zy8xAlq7jaD0=K>a#(0%cN{J~~&-^(z&%U6mHk2)d zwlguV{j|Wa&JJg_ZK2NJ)tApQe-n31r=hizs(t8J@qB+Qe|P zAk<4}!YB`hP0fjSZD)jfE%=FbR?Y}Az4wmfq4S=GKl6JvY*EI%SAfaHx;ixQq#>P- zWzak=CxaSW7>5itMSt+_=pRHt`*1_(_q)E$oEhiOB1d%v_;_qOgI3|}K5+eg+=903 zxK>52Ast#44q%dBM@Ypt=`HI84}64f27;uUCYwsj=Tqe+kiZV8NBzY8XV!Md0NGLT zD3qE{Kyb=C9}XR=jg)p0PePU7KkyrL$VNz7XjC`JK7OKk$(aWhru=oc|Pb)o9zjx zE@n(hU$@miY+s(_jtg;m2wf%}nu+)anDi|>gL=*pP{q3^?I2rn!A13ShMZKVkZg5Lr|3fx%buH>!MzBMa*Wl zUcY#T5Vg`2P4vw`oNL=f(MXQ8fPsCX0fDA~D?r)Fr58Pb{C3d(Ce@|28c~*Ti7IhH zhm@uU=NT;bw?9rnIq7kOBb8g&`FzSpMcVVO0SmSn)Dr?^7@I1*MA2mYeB4*FI}Jz{ z5p!y-CuDUjYHsS9FadZhJ#@h$HW!D^f#Lf;+z2;B9b%)9*!&Rh+!Q9`&Y-TFoBvvo zLt|6o89)ZN(dX!+V_-%fj$1asKLuL>eD)p(dmpC2c4u#hOlFDkc1rFAX*mkc)EPb- zAK5sL1*ATZ43E5>^$%oGf5bF2TeA0FqTD=08NuHQ#*r`#wJCBBF%1q=a zS$z>l+swrmGdV1?7P0#AuG$sjego-1%NRa#vFnUZLQBB(JR;Lfmw*j<#r;j?Bsh1) z9DSiJ)?IP_?)DGSE}SoT{kyS^B(Y8uL%nVho_C0twmQf6TpKDc;tGl)omrxdG6QXv z;-t-F(8M-YlkT3Zp)g!hFf0bw=1RQE(R8e8Tq{@YPzT4gpFD+^xJQwiUB zU(tI$6SFNifwvdr9&2u6e7zZ@rIWEZ!M*lf>`NIdU%ZoZ&v4rP0PYSG!A!>eYhLef z#j{mP?rQj4yC|f5*HKC@*9ZS>@6JSrLIhgJywcIp`KDuU-%|Cch4jp?l%@Wwzn~FyUym!)d!+|Dk!nV zyKYoB^hlcVxy8FWcx{+T$=PMZyLz+9YzM~1+}jZkjs*rILxMu*#YYSoNrZTDC{`EB zeS+5ih@tteJLK#llkr!O-)oD}`0IW4LhGB^)jXT`%MJ_F3RbyYgaJ*gb_82CMUF|f z=+m-BlJ%V%1Oc~K8h0_9xJGw|A11JigAjz9#M-*JHtBCdx>VXwf&dPc2y02CoJhbp*mf#)vb9+JtmS%|>HjC2A90mT#57gRh;?-QrX z(15v5l(a`-vixPhWTQ|%oytfYMmP6H!=Ps2f;zet-7X%p>MBCTwEL_yJz!MR6(hea zCC=sLX@}eh93Z&*y6?;}t^7pG80prZD!~~YR}-*3F zxYq>-6I7EA0}vC|A^D06vc59oCU_BLj)FYW z{bJUq*#;Hvu9;xdp!syHom<09yRyO+ZI`9OX}9v_eQ5vj(f$#GGKBjaxGp1j8k%NO zG8_v0X7cgCE~e#CK{MoUc!3?imyRhCos`_J*#QSNdZJFiDd@bLa(Rza_=P0%^%OJ}E*rItU++JUM>h6+LWqNl34 zoR_FQKClp2p$j0}n3&u_eAWjn<$&AX;tURuOp}p3he%%b zB^1Ohl>wLnm%x^XVkk@&7j6!1<2*`7cORX*rl8a-r{jS09*4zLg1b};iM7p`m&pIy zh@UVLI$i2cCt-cmX&ZD)SWKx=QTOGfjqy->2vD48K8!)`Q!jq(qOc;NMAS7FjBBh~ z&Dm9|sbQeaQsSNrTS*IWt0H;T4BrPMV+Q%u^a7e2xk{D0xc#`!AgIRGjO}8T2V9X# zb$FQ>mk7N_X8x+OGwSQ#abWJPJ(+W5)&AT*y(NMo2giI4y z_oS|ZQR(EksT)>KJy4gbiGWu@_P$=3>z^=kI!Mt4qGYB8G4qA$7mh%S;eGpZyU8wx z7s*WTEGYS~GidVSCwB04fgKLQ%n!m%76)hXJEQ@elb1qTM{8hm8tmVbm-YTYi~D_X zHuB5Q$U{&M5tover_#_Z?`=u)rah;w9x49$n|xwmK`I37=`__m(6hb*P7EVOtzhtC zL~=3UVnacM{-(s{qj4L&^xQQN3PSQX^^=@13KOeTT;|N^znpyD%w#_CJSj=u0E|&Q z!<5Iq&p1`i>=OSNn9G%{RnA~_7Ja8qm)9(!?IjN$^0M29lK@`tJ1>B*dXTWk%=Ut} z1veB36PbTUexYdx#qe?b{_tfBE3@vwv|DSGkqF>CydH6vCPG1xrHi}#c-QjB41A9J zopz`4$U0;el0}a*G#UBty~j4}=8111ieUmA)BLk*Pzpd!77RHl8g@(vO_iV}`(rJ4 zb%0OskY zgQIrJ73tzDz{nD?#xc}#`zxp5EU$dAYJs6gP5Ib#R4@=9tX9(N=XeTSAgCg}&eD}Q z0OgFPERo%0YgL30JX;)rBap`$j%e2kLZ4e`Btr{Dt+44O=oWu-9mdo5sqLZX-E2zy zJfONLy8w29oz-uW*~j>+u(nimbS8&O15;b7FZBAWdm%H17?vhkokL5;eSr%Z2G5Pm z9KWewG;Ds}#Ho-N-6WEU)SoDQ$QAd(wp%kAXf&_07 zdO*hj9@&nkYgzy7YV4SV{>z?qfTn}4VpO^yTrIgLf9N$61M9LSW2F^<3z=(?&PMz( z7Ld09rA6tSR|6k_4=_+ez7mJH84XE=r^^#=6Zah+2d{=+oj~MZb?TgCuPC!g)6i z%lgyk!LuCUjnEQw(s{JPSDDyXL4?q~(7dBKE8hxqN)$1K>WuH%_tzl(rQRbX{%iKr zIaADX+m)T!O$-y1_j`VEV>MMHx7MB4W8xu$fCSpb5c~{CLAZ4-1pWaM7l^SbAt*Y( zX>qr^H#>F*9m{&INjKeu@VTE@Z8nUkC1|R_4-R+|Py3Ac zfC}{kNYV4WJS2DPPi+Rg&7^@?*^m!<5B{K+!nU=bmS?TY#Ht}doeZif@@w-bhyqfX z79e$n%NJ6(71>DnOch|aWK5=K2H1yH=02k%9v>A#ulwOIO|un7)C7lT*GiFQ3dTQ{Zl)1&AbeCuq(-!e%zhHK6Z_D!!z6Vrk) z6%P7Q+`Nsk7I}hBV&d9FMHpG}CqX6?M?qx0YbGK_d=50Z#*Pp(-cJ|Gs-q3R@Z%cH(|-l0PnM@>2^)jAg<-EaTa9%F0>j zDGXQz5=`WeQp-}qCkoep9MF~L)ZWz}e*^7jdG@02j{u~1M^**Y_!*ueDmt69~JpvF- zek$@njZ*P5YcLDxh)A6@lGF?$_aHiZ&6~{xjYn&f7oB?-WLOs4$nML6f-b~I%^*?p%*MQ z+VB1r8HkzW>kRcVb&kJ5?u^w7E%X?H`6jd7m;ANqvETw)M7~*Q?}XK-ZpXnF*z0+z z0#*x>DR_@)fuhH=m!oRofpPyZ#&v}p_qpp%wJF&aDjNk&XyteHELW&vCy{A6WhBq* z(Dp0#ds5VuJ5=Qr_(^laIqia%vYa#v&dFQ#S_=8?oLg(IkMg zoPmrU(!oknNCa~}mnp9b8quFdB$M8ve3lkyf&O6@dXOwECcWVf#j17lvRa?=e?rKJ z9*EwCg1oGDb-@zs# z(k=QTeXi>$jKG}9vS}*sNZegf1M>D%m0q)>#ge{fmy)8YRxfC$$hi6#SBwOq3 zE2%~vdIupN0zy4t;c0%0NI(R`5^WMiT?LY;M+yd_r_Eh+!O>>L<;F5=pGFjp}kMlMo)N3$*DSCBZl<tO7Xi&*R zQdX*yk-q{HUC>N}RC=vK8N1;9SX$l|)NC_Or#aihG)0y+7ntB#MiM(S*a>i8^!-Wl z?_+?{H0_k(btXE;z)=l&S$+5UIEp3aTMgnOUMr5uLObK|FtaV8f3%vZ_H@y?X^SJP zAFl!oj7|bfMf@zhd~{@VEq`?W~P{{{AC%*3T%tT7a7Yjuoc%mpw zk&r6m?Ntr#4|oQV{xd8J}XZ8}o+1P3bxIRnSQmCw;w;nz7}%^%tLD!Ko?a0GE=AfXPD^f61tUc7=3 zd_S;itV5KBJMDWS?ueMmFMzuAeHu>seUnikAd-o?Qe=nch=IPM7$;5f1*v~Zq(*Dp z#AT!M%^D=hdPB`f`&Lnkkd)hn8j$RG5VVa1p!2u(6mC5zn&X+o~p zaTTl#WnTV}0CAeO5YmqL9RH?Ont;B0O-xT^LL8iyJeBM9V!W18evx=cXa)S5^KMqCjBfeoZto(g|DAi$ z^X+*^KJ<$E>2~XEoG-zt9-1F!9mKPCMRnZHLi=CMSiJ!!1*THHN?LFy*{v*sZsX6J zx`0_rjC4z$?t~=-FJFQv>%E|>z*W#YNR902C0Wd8Aw60{1^YHa4HDJtBan~a%K(hC z5R;XGT7$j`wjte*40iWcc4b8F%?< zL0z3L+(E#%-&ykH4eL(BPO3R|(*dooc~2!O@STi#-|rg+gWwvQoke&-!; z^C<;m*L5R^Hz(5p22Cn(8Qj~Vrem)nl<;nZU6@8w>UpRu$ig?wCqTioHM(8Yo0$$X z{J-*CT^CGc=y3Es(W#8?mSRKZJ{&w13B{x?|^>=-@6J{;d*xw zq9;R)4?x@fEDnFA1IG}4i4gkd1EYJlG|oV8OQ65i6hr{vMr)Q?d8~pwB`G=S0t<6k zTQRu8ZGKNleD5s{7gr9GMV;A6y!fRBCK(z@#X_6*ubMi|Y_p66Rl6QGU-@LF-UQw^ z`rRhb7QI;^H<1om-C=Qhw88C#7@PCX4rS7#9BQgUPBb-+-X< zMg!i@P@i&^*iTA%UI|X^tVH2NnvxcjVVOdo$iGRYs%neGuQ&`n*t*Z>nVemtgV`BM z>Q-++>oFh3X3v^yX~iL$o(?%Lh!pFmP8JV5_E>nHTOqd9HKt>Y+aKg1?+5yBIa@k) zkMr@v|0^<<@hsYx+wK7{VaIEf6n}=GnoIp_!Yk^rIlAKqU=kCU^cw^qqB7ae9toqq zvM*?E$)F5X(}4agR%js?S#X0)2r_=eG)Im_`{*7+TAagY`|TKPtr4 zO>lZt#`0qEK{X<+72f2buK9cm`%&S)(NNDRQeq=pgWWbu`E@Nw8f7FX~oAIT#XH?Ywkh5$Pu zWW#qG+Tv#tPoAX5Mt1gW*Bus@^))=j9cMBvYRHynxHSr^^j#9vM7mIIu>O7@k^vO$ zd=`QfJJbimvAA7JhkyTJ1jcMFsM&^Xa`n$7& z550&e48gLPQ&AxR$t-Xld3^5fMi`5jh!7*IQff~uAmu(v1u6y=)RA77SP5rz!9V3S zXm7F=4~zLzkVFjZ+y~ExQF*43`8g{rGzmXZ^*(46d@Y06cG1CLr)WMmA9CTlUXFTp z#=`^VJYx}4Ut$Ter0{8G@m!TQrLt={odyd$mOx5MCwK_g z>?+>$<~cz;^+;e3)z1(WHer=d^teKj~Px)7QA#CK>T+(Ei8Hr&V+bGM^%)aNDvC z6&92p{BI2z9z7$ygnfw77L3S%n>vB81@vCEWOvHX^3swA2-bD0xTAE(HB(f(1}VH} zdgD6e$8Nnqs($zxb#}dJfv^|h@$~n2(!6-pUwS-c2yx}QsvSM;1|O)$@FikOQyWZ> zqkQgzLjS);o3g7|f+*SWK3$~J4o{psE*E3qc!SN8w;%|bFIHsl@`V9I6;DCwIf^s< z+WBc&QZv-ku1I#@HMchrr+Bl(b!JwZNC_nOx{~JSd zW(px{p=W9auJbTwt_RkopWEvF!3M@#?J(UW82@Fm6_p%%mdviF6yaXnN!p{}I==jg zapu}9XRRP_Wa;K#fn_gGAL+a&D%vT5C zVJ2beGe*&hi2a)`d_1Di4#Z>C{2>@J65_KyyJ5F7REAZnNo!CTzT#h9x;vLg6#?{e z<@3py;S2Z*>OS9`zf|*mjn>MvJz#w`DA1M>e+)xwBnlQMDiN})@V{W)7=>67j-AD; zr0^M?eALvp0@!c;Za-@Z{l=TGlb5}MncYy`M$PVx);W+gX-`5^2qhhKF2co}E+bfI z4G&q5hSZklSBWYiwknST%aB{B2UKA$ISS#F*sPgZ9qzhHoUTts{n2P-_JBb@aEI!e zGQf4nq5qZ)%)m#QSsQfjjhs{L5HL2gPJ(|alKL9!nieZW;cwKz&p-5WxXY&2USJv- zJmC4z+hG*mYXZifrtiFIaHGZ^`1D^nQKNb&nQz z;~Z zb29N$5M2!*CKlFjP;9iPaQA}nb23TeG(zM+YF8ZrI+PtJylq#U0PDDpNB+^C$dHEU zE&`fu6sDB34UIp_Sd)s&P;Q9Bgm8&8oX{=7moqKPo{mwRm7D6A9*X}TZ+r2aed_l4 zyq$C5aCRK6?qRbl5?KMktrFF4H2I_~G0}Cq9MjIuU8Slv4tRr+C;-38O_6!JqNsW0 z$z$n4u2I~|IEii3%xBPF93jhbFlUAj=9mqq`ZczI0^hK%G9{pr9RiS#W;OG z@||-c;ep>yok7;-Nx?wY0c_Dxkl`7suM` zW~Jqekga|KW9ttC(%^VuR>Zt(gb%$PS@MyQ_;?6d)op@|I zEShMQ6C~g~GAy9z3nIulLSoW6&B0GilR&!&9XqAtw)&n(`(KS;D)!|9M%!^1tTjiA z|DbE&LDNN;-FLvuX<=gl&3?AjzvP*Dqq0L73c2wfHri2}&eZs;@?8YI7dd`fT_DK= zQcfjY!>RB8ZwUmH&6SSzGS0s;~qfnxgQ)id2&L-S(~}nx#Aj=7#G6m5%9nHg3!lR zA*(5lw=y$-Wi*BxyGWV#3mE8cp>Ol=4(RHvCYvZ1=)o2p`|SVWCs9Dy{l{I*$vzYDS>oq4*G&j-=RI4(iFou z4MlgfprGvaTZT87v&LlbcQATwdJw#7f@Xs~bEx2Gxc^sHnGEvaK|l~oN8&w7Qor8l zn4EGmYb4L~-Yk2+b&Gd+N!Xa2!E9dzE+yQEvwA&KNq$mR+mB`+OF;c~HG5h5mAnGe z&K$Tz#!QwmM}$`)Sjn5e%3%xotj5&%12Iq#6WcX%l#LtAy(X(;Xw~G2J)Xdxy0HDY zyA)QwM(x=$z#nLjMXf?lf(fvj8Y8e~sgfnc=&&{efP~9;+kAY1lYdJHDn4ezl|LA- z6<4l13yi|IdTb{;HTqVy zWDb=%gof~b_W3LZJ+zoG9xg_TPjn%;&5_`9vUk#6_guWsE;J$HnsXf=N`h2S$^&${ zIKM-6HOQ20iMXLHl0EH+$);R~tR&^31{!cP9)r5E%BhB~6~S5Rt%{^9$4C<>h{4t7q_hB0^uk+_$kXgX7GaF(PCtx7)Fg~9?nQC*3OxiLR1Xb z77_Ng?R8Jf!tY{OFV3T0Gb?jPnYPUmwVl z4#((k9?>X5g3G@Ilx;K;E`>ee*Bm?ynr1Y5h=RaHnAp(>tWpF&Dx{^sB!!Hclmor(5o<+>&I6`v@WE zy2+wJT5Az`{q$HVI;x%8v6k;$!m~skh&3(;*vx7a7+kBK&d*Ps-64DE3mznb!%hd8 z-@J1i_C&=7quk#5R%rN&lZ-_e$Q3*|!$OVJ5G~qYgNm^m2ePwe*OWF{Ki)n=-8ovU z3A&nzW_f-WvzVWB{K4OEvs~>XmwibEE$tPN6Xdd^p6W|8q6af@P~jl4eq=O%m@u-Z zzUw^19u*)4Kp1feK3*+vd+@b5<{|{AjHY&{rR)*#yr1sR1zSD) zM7Jj{3NYx(H`Y?tJ&CD9^;^)S8`fN8`j@e29Tq>rW5~lPvN(#Q!~1XBd7JY0ukJYi zVj`h9ozebPwwT?jl1Gs1?KjbD$o5A3Z_K5hSZ^|*&U@8}A<_58iT7+Rrk0w2^ObVf z))&X^Mjk?;+|mA^13JZcRJ0A+ZS=Cy>Rd7hWm>TOjPHMt54VANKfIi+s*A%xYy$uy zpq>9b1mL)0XrLq8rcPNu!k!(&*!>ZfW1sDr9={^w#oUw#Z&2jbckF#3V~cc%Zq@*( z9@TAYqlX8bhOV{PdJ&a(WDLTNzVO)UJY~{LF-Db_!rx8z@k?w#?@m!8S}uF$!Q_>j zpoqn)g{wu58NOo*VCRz8w;;tpLyI=YrWN*Kn#xkf@WXvu(BLH3z8T`l(N;9V+-~N? zXUt>u6WCLhMq-@)iF*>+I;x^k^~djzs;K?qCQTai9y<4So*+t?Jn*kaTt*=7;40PW z;831YQwA~BhyCyKA_*w?zP9$v2Fej{E?rrt72!+$AtA_b6)u2mt8~UnI&ogHL!VmI zjG+vr6F9Y`8cYk0Wpn^nOySqlgTj*W7aOyu&9hIKalH=n5UaONq1_aJ)XXvVr1GhG z3Tls|_DVOC-tR)!qvkg@;K>sg;cv&&nz%AnYYh{kI`&c8Pye{uh_Z<$y*J~MzNja3 z7<7sole=?&L8{2>$E*7jE$`tvCyW^Ak<8)CUbY?ySX7F3)DUz4u@;s`mS_`2YBv+y zYlati)V$^;atFOtcNEwK9>yrHWcZ|O_KrIy5&`u17pS(0eJNe!IjhSobrjUu*`kB= zJoGJ$3`=P*L|gx#Z$XZ7B==lbv2J>w4EKaBTF&KbXkz7c#s1S$uWns*d(8Tzv6DY* z`K_EUM_58yS`9ax`2688V8Cne(8MkTvT`WX^ddoUY-kA{j#Roky@kpSCyAf5*~x+! z2tUhYas5$89Es%+Xdl{k1EvCGY@WBB;?w{PWa-7#4(h&n^Dd4I_n`Pf^gbam*4VS_ zF|a2s1yl=5(vV^_34hPUpDmkVZ{LXvSeiS8*OLL`zVoy>X+$M)Vz+C|3bbFW8~87v zysb@DkWNu(Hy@y49fy(1rj|xc=QLh^?(PQ^=*bSn36}hIo=835BdIh>P(KMmg1fgK zseIE<^m#fAJjXh2sisEeQfX~mD36I0)zzo6(Jd$^>|#P0++b7Fb$A>-%=0GcCG1$8 zOMhOKPlK|^D`Xd=AiWh)SsNKKM{!t9T!3&dRvE zZ?y061V(it;>``?4|9l6%`TY`qUbZlfQ7I89it}&$Zk{;nufa2fV0CnSYi3t94z`84Uxz%6;C1aGhajaGL33@1rlkJFA z+zF3Qc6xY5N1cW?zMCrns^c~@CY5X|rR@Y3oL7 z#e__3^QDJqWe%cMlVR`xYP9iy3&PsVMKKmPMVQh=y=4zhxL;O^`40)f$6Fkj?VZJB zoDq0>bT%Iwgaw0xNo*Qhi7vQbg=?B~4^2QgpviPpnOXzVO<-PRdf`Ahy#*qHo0_nF zgrjIu8&UXWK^U(HDbjuH0A`T$)#qcN%K{rlnF%{m5w=dDKfXUh8HNJp3{#2Iu(JYK zWzWi{P}h=01jV516N=8&LBGP9NbtStJ&>vGMK`@IyzR4l>RNbJjsbSwq#<=*f`&&U zz^C;1iFN4&OPrnpPYExA&kR=JvP~5YZy!ZPl1Y62s@ zw%IyL1zPW;ztNA4>UAM^e(ie1)Xw<3(+9Az{hk2ZQF{lqz&@JAeCxL(?%-+(t@GT< zZ?4u`hp(c%8^klu7)-dU#euwv>HAn#;ST`Tg&Q8PdX6%}tg%`}>uC`<#juFI>aunc zQA}P>0c=P*!gV!r0u_A--2T9Fa}2D?k5+B}$92#pRG<({PS;PXF)4}^K>6i+ySx^X zJdS`r-@Xb>c|v$*7lmoH5W}2 z%hu40W-m@qx_DBsdqXx1m^d9$qXgEEN|i?O(j^?TT(^)gYJ}|$AT9RwDj-Bqg}!J2 z)aX@Gh2#>;oWpcNff+rWz8tEl9Ov8zIZCXMed_`GsW>koe-^7f`^Yk;mJt@>dfb7L zCc6k=$9$dSd1Q?zZjqT4oe5I%o$Cy;fYr4O;Y=Nma_z@!BOuUTU+Km=whwnX7e9L> zzovp3*nBj;Jf9m2GuUtZ`x$ujlc=vHu=_2Rv2Ems*$SGP?F7V6MB#=*$ zIRhB}by6HqYw!OWi)SW!pw( z;Q3VTU=*WBFbSof3$7Hlob@TheYCDbWIMB*S|e zkL%|SNNN-d4$aE-L*2YReRzK#p2u>B4e<;05`QUA;RL%>CojU#VQpkxOjV6JGODWB z-u-B)kgS^qi30iO^lQm2bdQ(oz2dPdj)~i$GsXVM(=ncmr(mEhzg&aTt%MQMt_-3e z@ebv*a)&+krk>Eq_^viJe=Q2Nqio+YYvS3=-rk^Yx+s2I;zf!@9>_2Yjb;x9rI=QZ z2+t)ozZaCHr)G36jp3rhQJAMCn`;;xy2N)OmYL`(d{!U_LKo1l1`CN~8)K*UBS>x)V`I)c@T$65 zMu&JLMAHERls=N&Q-Nh_S|0Z2pg-Oa#3+Dg=x_524a=uxDJC)4JV$_{H2ph!LP)pX zmP98-raiY@IQ*ZMFdaynew3l27Zu`AZ5YcZS>2?>l~*u^hVw>{mB+4pb=}ba`R{m7(d<$OWs%`bT&I}w3GfmN7m#i~N zH9xT49WWEbpO1P^QsRf(nWQF%$U|1iw&}BzB9X*Ex5y$?Wo!FiNq<_Wh>l!Gldz(+ z2gCz}(r?cAN_C=17Gk&z{aO9cHz8E6Id=EYphW>r8`w!%M}lgSYrE3K&h%VV zht~|>6S*1efWs4EbcVS-or zt~&h$Y$I7}rJHq;5csW`9zLNPk^0_biHGJYx;H^p*d><2zQP}szr^cY6se3wOK4wL=GcC(-#^lIo{m#88@NonQfBN4gORjM>mn8*ZE zccU_~s40t@LWa0_aT9+G_o)y&Ve<;(oJSq;m;Sw42%?=XMt2I%d7`H={vm&K5{ETD zJRi*&F{{0bjP=9u$2AHtEOw(gi@ zLLRe;5~4WGa4)`%G5J?8tT1inPI7t#JbN@XM9C?w!;ko1$PRfnW9#obK*_+?T9e-s z%3R8B4j0viEH2FXQ+|l%k_Z7U1oI3J2M3OELi+>1q_(5{7uzPqFB@+y9P@wfe^A@w zC_L&G8Uv@O@x4-aEChZR-v-tDNFN^h}T5AB++&aCf)4#3$v|elOQJ52jm#YHrE(b9ea&5j04U2t--qDjoEW%7|}m# zb~9bMAjs5~+jU>GtYWn(^HGm%1>MC7Kf-hLSWZS)Pn372deTb&Tw<> z(YyJKISV-Nx|rbx_kPPeFzGK5GpV1Kse=EUl>?o5i3=wI2A7{!M~Vn^u$`viXY!;D zN99zZP+c%HB zm(hO3gs_e5J|!pzi_Tvx6ah#*aZgB~`r}04!!4XHxA`~up@JfA&np+zY6sC@jmcf$ zVp;98xcUU&5T8nCJQXskOp-6@=p6kwW1TH4V2%CX7j2vU9nwnW?Wi;f@C=OY@7zM2 z>4qCrQhol&@?8am64pdf-Vs8tvIsjFnWU~ozDGXs^K&f+RG!3TtDI9`qzHkJ>FPG* z8)_w=KtFL(dRHu^Wd5hr8QwGRa5s^;+>-6t))UQV+==cgddl<>{?y)g-ml!YA|6n> zy(dofwtQIVyiRDm$=Nks&+9n1ACM2C>w8&8HK&U}e;$~mCL3>?>V(#D=HKC+PHflA z6vMc>nfx7e_Q#udmhHu07Fr{)lkqOz%}$d^m=LbZyOS#N^+@p*VS=T6_{axUWL|E+ zz!cLE{b_~$ z(yX+sEF@OU1)uZV^8_w#)iC5QPVEZ|p9G4n3`N7rtwXnW!VG9p}>qEF`3tI9h`dA0j5%+Td{U`ggp>v8KmtK1OS)e$W8xk z?CxsRWpEwM%Ee^_vBEtfLL|E6nI>}hNG~+p!F*NCU)l5*sPLIU5>c}zCI9Tc%W&cGz1sa zewv^07I+;^Df_Cc6BNGVOMEB!{y5MP;Gj0R`fF-^Q++$8g7oNSuv0njBbD7QZK1yU z+AOr4g625VBx;U|m@Ot;6gm&hx4JNT1~wxZh||yYuy#Mf)W}+6H$l=5A6ORyW6Q;! zjVJPTAbq)GhcEzj&1J}3Fc~0_5pS!T{}MbFu2qm2{?$GnRs>zYj*3Eqo20`54@%i( zq7#mP^i8eOw)QnURh?u5a=0~CKAV*_g#pqkB$=UJRV1Y}Mo zk*S|rqoq)EQ9N05KWom^;`kdOq8)Wa7=Fosd5iPTowh+vR`^Bjrg*JHU9!_7m-%^1 zl&-FkWy=|LukDd0;9f%_+}*ASZlYM`L7-ch$G;b_E$v)vp=Qd#t+`7f<`EHA*5QXz!EZe}K_T^EpP1UvEXX$XLmg6YvH5}7 z!d`{e52~|5M#$?mj3yQ3digvCnTE7njAd6(D53hkvPJbgO`qqosXXc0m5gI$BjTqN z!1Y9QQPlP@Bi7x5?=6G3h}5;tQXrNW078XyelXp@2p(6^&4>yN7X}H~P_=57FudM} zrBe~!^tF8g<~suForWEh=r(4=f4tY{T$f0WG377&UHnM0nmXkG)DoaVlUuQo5HgS+ z(~RBOYv^`9`~Yx8$egq1bms*-Fp37{kk>S?#X~9oR_z$qo!V1iJdRhS`aGXl1Eh(M z^s;c)s+U|0B_nYP=yRt-Lwd8QRzv?_n%B^^JgQl_5u#QFwI89D;32+XucNBISWRy1-QVVI6Qj))|sM+C*erak{Z>VPP z84%N0Caq3UCUY@I_#KnmR*SfSrFe842Ti_rY6SWRs$j&g2ux1S<(@zJC3#CW=SH}v z{v?`N6)kffGO#b9vOI3Go_AJBUfKV%+0LTeHg^Y-D*KjjvpF1ATNqe~n|+Rr+4(V7 zNJheNfnK(aZ_^l)zEhHUEV35vM7bL-Zs@oYs+|Fc1!+m|dH(yqDiprn>W07Bn8liZiJx0XWTl!e-x2r12rB~KH{?|Uqw*`lUVV3G+v;2z1 z=J1fb+Mc=bT}1}A6|>Zapr4aNn#5n;UP>hoxuUXgpkv#0HLHneG{ZZCwqr1hFK$wV z1V=tq+2`bvHU+?_c+`}*ZrJY+*P4eAqh{|&MmD9QRjN4-e~f;mc1Putz?lObw3C|x!Ps>uw%mWztRb(O2~R0RV}E6d`S}B5Wc!c8`LD;4*QErU`NUw5^-QKzk|757wedd3n3X-bB>=(F$=yrQ?W(7?ON82tFM)$%C zd~1OyU_l+*(=1yb3?Ke`7#qzmqAi8+yXrdQg{6uemun6&W?S&`30iJOJYxaqOR`2w z)FNO}%)k_{-X!!32GrEw8g{1U$1?WmH}7_A=pwrHDDpaMY&pA{dmASZ#88dMtl7y< zHSX`s51-}AI$8ywD~Bn9#LIG z(yn}&LfaEaE~KDGPR|WOI@{A3dXYa%LU48?ppFkhepEuTQ(31VP`Q~i?~9=cD0uwn zO>Un?PBw0?GCi64&aCEjE8-!@;X405!hG%0vMrloK5*@m^_cSieS4KH>n2>C>sYmn z8lagK6za>K&oC z?M%(OuB`?5A$=RzUz-y(P>4J&P)Rk^ieH}VrF@$vdvQET$YO6IU)t{-gF;Hs2n~Ht z;h;Rf3OM(|?wQA6jB$*esb>I>e#!BTyzhSYOs6`n1&Lyyla?6z8J!&c%%4v(sy%S* z=PHvxKDp$c^Bo~ejLES|0GqjAwX6VmQyuWEE6ve76VKuLTpzzHsKhU)!QSOYn+++C ztgZMi_x#x_Y72^Z>OSD72xJqIT(}0UTNyklEe9^#O5*?J zkO^__gn$(A+6nr=h+e8AWwPQ&ehztO(4#`p6wS*GW;Iozq5Gv3}$ZV>xGG8Azt(RC9WBF_=Rh)FNCg%BWOg_II+03U{W34J3vNv@* z75v_+!kh9Cx(~!@8cE=WA{?UUrO25gN3@UcK<_a zc%I@ZbeCC|z9Q%fiAOXt`ED)o(4qAswi9~*{)4OgHucs$n;t}1)iQg%MsoW4DMpsL zIL*<$5Ky|9EwJbKTT8u9MQxD(c5s|_mr&Xj@lIu!B{S&U9w;cUoCVL+LiWp$IZIoGt95aDHe%R@(gDit5 zi82_AU!!wY#{p75&Se!^);h`FM+`h7*`Y4+0kya;} zzTESDUc$=(qx&v7VXwhJ2Y=Nif-EYrkBrL2KUTtX_qiP>*;1>6#xrVam~zfA+i$%a zUtR)Sr_r(gApVI5$Ylk~^Elfq*)nre-<=}az@?Ocpz``|wJe=0%DgWkShips6d_KX zV1qHC&MV^c8+1Y8|8F;e$4P91!^Pfh+Qu-N@@g306ilq>#K5S0>4D5nqE(e16vsH; zaU~#mET1L!?(8Jz8EWy9Gh1iR*e`rwf|=<4mn{!+Lt&r=5jKU2v+!}*=p#y#$mte* zyBd?p3?!M@-_hXy(af^=?AuIDLAsH$Q#tfFm{BEcE2V_L6fqW$oqbOXH43AnGtpwi z+^x{Rz}krBsBL5>)(%UrCVi%IBdNwFVdCjr2iXo6@H7#^`|oCdxh&KjZp48!gG(#* zTq3B{xph5F5z9NX2cS3p@Km^-JYPl{ku9niZS6J=*mH0FlL#32k0f=kmu8_fL)6bC zMVf&S?!=3WzbIbB1&GIUk>Vt=F8d}BNu>CCkW97(s?GjZqaffZm9h^Aj1yHZot`CG zwU?rrXf8YUUZGRAE-yI{FybnL1x0eMqwx;^3y*Oh4PrOe^-Xi3*imZIOV!L3Ov(Qj z3-2ox%;bub0zc(0+HT}~E|_IdupE;@>I!S|QI9cwei}Uu z`pF+A5O7|i5IOWb(#zG5s!OGQ6a(vB1iH}e_84Tkx^_7M5A4~ug=4{`ciyqEfO9ur4xTwnQ?SrqNtZg1}OTi1! zCRwgYLS&ZnkeF$aR32W^+a()gh73Yaq;9Q~zQZjLR-Tt^C&VVWahF>W66u#W8|yH8 z6sr~>SDBH!Q%Ae{Tum_3!I7=BK|OZY@Us9j_(Fx~*yQsP4sZk(VdN}iov=U8*KSBF zA`3wV2A?|v*g1NnX8^o8r)ANX2-e08@nKrU$7z1vgCqICecyl=!!u*A`AFSS;Kn0! zz+kXaq&wHqeLdVtIn8fz&tBP@OU_(RBqpu%%LdCf+Bd>GP@R+SVL5>p>%y}TlPn7_ zU!kz>{>-LUbz0Par-ic^S2!+lD|a8lUN|Bbl_W%brWoa>ehGLYg$tL9HSwTN~-Hb(Yi_AQg%)VsyX4%}x* zoq;83+G&No0XQIxw@QwpasH7f4s2Ys(0x^)en%*m?aDdQ^;Nb-R7|)j zW+AF)o`h`7i4vdeo|u0RN)L-Kqy%Pde|7w^Pa1%loiZM()`e&`dk+KvFeDVp7DxZ! zlkXGg-|6X4Gi~XE9XRk9dQ8}Uaty2Q=-40xx8F@i0ywh{Bq*j7eJ19ZDb%ex+h8mL z4Y0Of^0ryH_@u1%BHx%js;8*9bD1-RdFz5z#pmyiIn&7=lry= zr*aS24Y-Hi7C#OGXzM3K*U7+QYhcy9eaCyH8_i^tj?>@&BShDi#SiG|QILc`CO#>^AMY{|&6C)80AE1=^sf5k?Lt2;ftSPy{N$5bkDe+Tat)KP z;IjmJoUq@ZuQCT>4S^*n*eC!jvI@t`C^E0!B7kn=$2S$eRy&15jBX9YR8Y8Rv4<DfsJHbxR(qBPI z$lkZ3sdY`Pw%_cSMX6;pE1qnzp@kx{hdGEb445YmJB zxZ(Lm=PPIn&v|2`I;}u@nDap}0U&!e@4#p~RK~1YJ5nr%@9VD-N_^;9>hQ5|7T;Wt ziu5ozWZ4IX@W&+1jp5i^gra=qCo7_asKM$ZMtg*#^!Po6Eg1#rdI!#jai3)ud! z!2WbEyd1{S=+d=4ZTkD}JxHu}ixNYNm~EONP{s z10HA7pa>()-b?FZG-cQzJ;Jj{J4~Qwv!w8!CNsTGr&ZqIF#5rjd9~_V+iLZ9#x7V-w2KYOO8O>V@zmz5ze^xCk#gN<%qXdx5%G$A zcg+|e9pt<;rp0$kW{J=3Rm**%ekv#n(NYR?fURXXAqzzAUqPt=sF-xKVKg3wYf9F) ziC_e~RfF#lr4P0Lz28Uw##-BX{q?ue#sR1{oxP`8=&vpQu!Y;|#6t8%4R*k_M&HIj zgB0IW!d7lC628I#2OFlFS+%Yol~4}o=7)ddQx=pmmr~k{VYkku^?n?Dm_4Lr(x~Q@ z=qXsnk^4q}j>Xz7t(tuq+(iF@GV{IU^=#wp?fFsQLbWdv`H&X(`J@L0NfoUFfQHqZ zG3DIju{FDiCufQ5{dfWmp`r=|_IXc?u|W5Q|5IZ0azFO_f;kc!j>z_UuHQ=LCw35- z_82%uBsV>WhyuAbDo%)c4&Gk`VrifNi?8IYH>6czDnUlgw&mQv9@19;S=?Kqcwx>p zX0tFH-|Q*<;|E^F<4bb0F9S?Yiq1%a^swOGGYLr0;_{caNEf{O+R&yCT*@Xv(#ls- zb*MciE}+XM?&k7(xf~WTif=Wi%EZL5Bc~hkwXirYoj*TmU)bCSKS=va{_5|xbUN7A zBh#xo8vp}Vjs!g2z*h0}{@A;JJ@~n>FS9ukeI;qQulLQ?>+15wJpJ@Ja`ZO+$lfK+Z|2|(?p4{{#JD65m8N!p2v&z(uxuP*>*f0#U}v)A;O#xypI z%ZzmJUBi{>l??C-L-K^%48neb5m8Q0W<7^G>Ie=jBoPG4d-UFj28*i1z^Xz&J~i`X z$Y`#}V$j~j;><*49C%_st3El=TJt6vWxr$auzvO;*uqD><6s+a)$Yj=(r|+e4srU8 z(6@JR-~O9^jZpJU_{u}Jh^wx6F!$oovjX=8e-#Fp)$TLKyTcnp_mWT0yFU-k8Z61U+1{*Fneqk(CFOg$ceU$Hc zs{h$Mr(3jFqFa0=2_7MWJUh9xr(f^}}A)t347!nD&Cuv z4?p|Gf&8YE)KmA?jWti zk;{1-L1~x~ViT9KX6L=PqMUu`I%vm-3My(Wnqa645Mi56TiBpmIm2g2T^dlH$e&nW^>%8)hr$v9Y zWbN6>yA(iI&Ftx}DH+d7hkekmPHPb=8n5yCQ3&tKV<=qdwm9u6UCUK`pf(mVi1UCE z(yjRDWsLoXfjI{MSf_@S`gxKrjN;@2C%!OxlUbd9u;IE4)+5usr|(XysXV3F`Hz;1 z4U_YE;q6OPY{3X0SU?6_0>0NP6A9Q=(fqnDmkbU00Kcwe39-#F@IWYX9xDME<29vq zbF7pH%k^B=JP-`W7k*_=E*Q^us*edKCNrPK4LaU2c9D4V-@)gUwk;P$^kBi?cd+?# zV!8lcs#$THPkxn|_k`_QmS5%Nk6|3{OEwz0<02_lLdi@73H)e^1buTey8U3H8{6@I zK(h(%)2TQ0`ynadt{Wy{k!SR0ox`y8&lFOfwaPJ=(2oKLCk!S0QMcGwql$qP-7+l9 zPDeoe`9)3I??>RvO8}M0x8360%)EcCrCFviaYV}8tWBv!zqti?h~w0!&c%00^9c(YILd)R(8 znr!#NGb4^8Ao9jeXDn9|S_Qmt(p;1a#=$@Hvh z3!?twHWxX4p=w3}VWm}RM+YO^A$7svMz?_qTto^L$f+4KB-zx*#g0>`%|gIgYsNvW z)?t)tfRrdt?uvPq8EQNG9wnaK(+u8x)l)z`U`8b3c9Ri|zDX1<+^JdA@U+SO7P0UO z12S_aCUWAL!@o5Pvk+7GO-cU4AF`@$I?MFE?WU+Z4MU|NU=3r>J;Je2A2+Ni0`*<> zzFYXkhrFKPdSZYT|86AcD38Z1*gB?z#cmT*?7xTokT; zxWp}wk8vh}=KuJGmb7`fLyKXZU~-UM_z}TTG`TI?x9}~JoR?>hunFSwrNT|h;PWr! zm8d7_M9>H_>9-KymFv$Tj@5V71iPyl+B&N@*@LK?9y z@ycw*P3tsVn;sY)(7Qkz@it?YxCBAi8a_hhpQG z*yg&%MV&`EE#X0sHD70=<$gqQX}Vr2-dl_mAU0w_Vf;pwe_cUqCF3VP&O;_!y}iiB zqKzR_g>}QyXIa`g)2g>X`xu~gfPHawryRZeH&vwj4wo^39BYnUC{=WzcvjjekBRDn zPPMZ*FPl#`nkSsj1lT0O&mL{Y-~d1-7v4M0B*xa5vc<{5-jwHA?a=u`-+@^LHF zWt)f&M5Bm8vwRSql`n9#oIt4uFmFl~s;4B&>7vFpW6`rS5BVAtrVo0-bnGgoDmS(b zF|W}U50F{ubFH`sk>62bV6QES&lTd9gD!Pjz{Ct#Q;exV;kzKmR~1Y+6#1F4YKhO+|D)`Yfu(P}YlGZybo^ldw(i@; zfvRto1SY~(>K*`&NPsF#vg`~lkJS9)G3v1(jH2oJftF!owO=mm8r?xF>EI>52;+Q* ziiWc{3AxhZq6U-Hd0~-FgPaR7HWD|*@)*yyp^D7?4C5ARYv5l_Z_@ggv@-fv*U^rhGS?l>6#*a)aQnzXhfcVYspm2 za~uU1e(acxO^(h?FE6;(@NKrwdP`{k9mMpLrA-|S;Z(N?d|3tT@&vuK)gOuL{0l}T%si{tDC| z>CjizP9&NxLE|e&BM-+e&7Eb9K5PFmuo+sMtsZY+##Q?qlzYw1jlO!(?$N=6l8IRww>xoE$3~m9=4$-9ruR8Noq*O#&cF8C zBWbwwcQ_lnPsi6*4o;pm&-;qAHuvKmAV7(iV1{V$s2Alf!1R?leC%cy2R!fy3l$`l zmbH&?&T!JKYxj^S*D$m=9+}*<@N&LyZFx7=Y8aY8NV^m(3y31V!fVz=QSwZ&In34L z(MR2Z*@8XodTFGOoN@O)?%D4mc5BjXh#p`^LY1e6CNH}(5lOW*jZRvm7q{vv5hzrN z5uZC#pOYiilGf1mR8SpoU}FPtP1m~o+Em_*exiW-<&h;vEf?y5KmY;DzJ@xw;sN16 zq%Kj#zi%^OfUopfX&M(}+4tGKId38#-BR4mX*{LPDBCLQ2^uPt(V&XBl)R*FoR`kB z*p$1`C+=u60OB`etFps)a3>YS`-BukDAP(p?(!pVT2o#3#Bc@}s)Yizpf&Q1Y~pP!7I(uI?&HW2RRTRk$$yy8Hd|5abEUB5cgKn z70oJ8K9@%V9E2l7$y|0w1~ZK6Q(3b*V)0EYYGIbRq$QDmO7S~e6HQqYo7r+m-2iFb}t}#g@ zCRm3K{VNeB4Sng|m!m$iBfKOJ_CM#iR}Zkm&TLLqPwY~^a;mnxKx_Y~6F{tmteLxT zfb~%!6nhyH+~cF9zMvVs;Ieajh;(5cEICVGu-4sPO5AC2;x#$RXx%A1QafbV^ZZsh zITQ_NUOUHG5TT}F0T_HkNue=xQ`=#a|KXR(F$5FshvE(lxAFUr5asy+<+~~aS0v+b z<}u?UaMrmBEaB?57=1#j`aDOD+J#P)+1BBbU{lJcQYz$2(K5|H^?M48POwF@STjN^ z_~m4qdgn;H#S82mjBKut-64?Bbes*En3W?J=r0TIzVXEIickiq0i#JWk{XdOrj?Ff zC+vR9Ow$ZJ$`cfR2+~Si{0!_oWo{~JGh(B$M5F^UJN-qKl4pXG7rH%w9z=kiWgk5$ z90VvS(g1FO!CPv`rk;buC^*orJ|||JZWzj`dV?kD4>h{DL1|E56o`2WT=|T+XNqCe zUR8iPrwx%U!LRUhI@M-VhkdBdZS#xVHRlf>c9Pkq$VP<7f82)^{qT6&++sl%tLh0b z5{eXPo_0m?!cjA18mKmQK+^toS zf4k@M%xQ(x>1D(TPH;$!TDmVr7}%|Eipih@mk98(n?H*SBH9sCMYn*>D?IYZW{gus zsv;#{CINE_Ydb|;W_{CwdP(M@(*LAEP94zJP`H)fG21_&>!gbs$hFGUTagdGwF}v&o3mK};7{Za)~|I3R5eHKn~>-zd{(FlKBH`|XCAe4uh9I=I;q z8g~0zMj+`p8_<_Fp8iwn+7gc3?9n8M-{1(+8iSDymsTV&v$Pmj&Cn3>0Tm<+l%xDI zMqlecix?@%=7zjRm~8(S*J^?|wGT|3K-bmjw)BqJq6h;FY4$ZMP0F1!AYLQe4#B`mWq!fpw@Bp>x!91GD2eJ|n>7#+P>Ai#DTFFfipELQ>%vW*Nk zq0(NlVd{lrH5IIo?!o_vpU5sfG&7rjW=vyVisD)4x>~>_)N-5{mK2JrXX`_D5{XWS z<&kce2Ii}P)jL)a{}^&@6e-dh>_T9vm09oBw#t|~SqD;K=gqdzSzhH22=_d)67<>? z`kL%3fS&V=N1EkDIub1Ly(i-i7{C-b1bdh3Tc;S%xVgiT!tWjp0%r96LL4Hq`<^Mm zHW%Y@2KR@BsCZK5S;sWMm<+2EN{lPn+x&^JDVvGt$Qj)@)pl~Il)ZDJrk$>cb z*L3FA@jqn(r9-<_ONf3?yyEbxMre^z2YN-CrZ2B)zI6plA$GZ_Az(#$OrJ#c^K6GL z@u6U)05P6wUFat!kiR82r|Y33ob2H#1W@H!!qh%jOiB^sIg9nwWsW=Fc7fSS-b!j&=X04a0aT33cBs^bYgwO^bd`GZ-VUAi1-_X!!&y zjn_s0i^W~*D)*eLwr}f^nq^QfXyv}>Di9yk{4$c#MOydVR5MHx6TLtEcANj&8%7ByPkaW$nl(c+CwhsK|CB<%v{fG zREZkLry5h!6wh*ZGv^t~9!7Ou!YiqU-2&sgj6EGFEqSz)=kJ)+-rz+>NF&x>4ba5U zLBT3*rA@F-#(nUu2v8?UKlh*I>2UCK22tNgUvkO5S9{>&ieW4=C$7aaw>n};Cp7J? zaIEhVdil^*`>yH^ZdxCs*|YCyW+$A9c*-J>(+VbDR{Urh9)>U1s>f(A%Bs0%aot#P zEc9NwYi<7Lj>SJUNn0-}l12yc`+Fn)?D%*;fvWWdYT!+I9-1bPG8H*&0s{;C^aI;3 zLvw-sI!N!3kR#Lv;%R|(Hk!e3=fQh7g*cl^4`DxJ%Q&+}%14Y?U(%9A%iI!^$c4E-%? zlku_SoEyi;2Y`eQpwhBOpwEB|mrA^n(kIU6E?9W4q3&&}X$u4X+>a({390@)IM0J^ zYi+?x1^yJ9`|;RJ-a9?n@s^LV*B(r%Lhg(q((bALY0(T>V3{H%N~{VIZB$w+KEN*5 zFbQZpndsmn0RGWYptch~(oK~iI8TR+57)Z=c#-wN8@AC%$U7m{zfzGY&Bm#slxUYK zfG==UJ~3M{D^o1~y^#xwS=+>+^I^J)WE)QI=+xz)BXG!&#GS3&O1KrZ>(2amvm<#j zlxnMYMFg3?5D(x;j#QYli@Af}@_0Cz^Sq9`I8&3dC&hO%KQDy&`|)7Mlb{pQa)okt z4dY6H?X1SQ!|jaj$~~z+7~F3eP^aWXR_l*Ka%r2YWmx3w-I&S;H>>YO8KJ`h{Odqh zVP8e}<;iAL8VkOK5t!?bX`tRB@kIPB&Ttgv?<|9}YKWgOc)eK9pz*N_M5*4Aen`DV zay`jl+1C2x=B3GfPne-;p(kBucbRo?#o(m9NM81>j{GT(^9^#{03Xn}h3%t52=!df z`AFBzGxQ*fPkY#5H&J-9QrYiLH3w`8jcDotC^ z1EtpTc2BV?_lL_=ELtP|?RGM)0o)Q^Cg0sZ_CE!|ji$oC5Y8r9#&()VTAS$meNH>k>hf8Y$oy!+ zi1SUCfI1{3@zk+yiO3IV?Po{0hxtD#40BEv_QBeKUtP^Pl9MZJNdi?FGu384D9&M%2%Yv^A{4IS0jYoKGF-Q~7O)Sd~{=9H5o&?=z=U zC8W`=Yc{S_)=jLQWt80;#5LUpPY*9nIjWHOG;8j8_i?)rs%~RHpCI?>NF^o4wtWC` z*zrp*4R8tF)`LVCJgwmrb;sfLPhiO8^q@kJO)!o7cj&MuX4^dK_Qi5tsh(W7MbV8h zC79-HuZNNxje9p%f)vj-dsRwRe_?uB>pN6Q>1sVXr@LeA7Q^RXpG$m)l_THTSG+ z;hwhS2TGOA5d`ZO{yEhJM>X<40Ape}h)W;| z2lMs@RFSO0EjU}>=^L?D;~(!k}&xPX2xSu7ZTuihdMPplR6#z>mR ztrf0OM<$cjI_m@hwYW~2he4+lu|N!7t{_D~?y#ivCE#HyO|i9jlAg#E9A@wm(MNzZ z+$f2DLCXi5K{NKPus&-HZ4@1V46n|eiy0D&&O&Sl+bWTyZ{PFNf{9%(-9j#YSfI!v z_*mfVM~GGNDT#XN7rOH!;)tqJKuL0^|9`O^zwY!|aYtPn^@uVU=%tUi4LRj%9K)vc zJ9}9J0L^i|_-T5FZaGv|L$kU2KfX9zAAoKh({ZqkifO2P#Z9C`98%>Fyspoq{!VW( z`W>~Q7Qw^NvPrRQK;N&btpC=@+b-SFvC<>{h92&iNZi{1bXc)x`VNHW%k2kNXH>Z! zEjmtjChllH7ff9eZw8xJ^%zllQrQn&xT6sV*n84iKA0Z(GuWp|Z*dc`EULXjHWn9S zOhFU;84hR1Lp5TjQX$Of#e9hetf&7d$#qGOR@5ANifOzAS0{&~oIU)=@Kzc_BG2CJ z|0GsSgNc5uR9nc+TD95@bM+7=$=>0m${C~1h?oB-{3qZSHtx!a6`#9A3B{SQ z2r+;hfJmBZ)kmy;K8ZpA!1=`L-+bIn_AK+5r+LH{dO^a2rq8!D(P$AYpMm~rlJc__ zeg42DVQSj)^HiHs5(B3x!zi{6utn>X4<+W+wbfc*)xhpHm|IOwGstSp{nrMHtRsQ< zml)Sar8q#r6Mlyh{nv$0Oy) z57_47cZd*Zq$|4%CStB)9OnX$Caf_qp4YxRsBI$T!M!LcG}cBGm;pq17PY0xx#e)b zUqe4p)dT@8H0$!OZ}&>LI$k7(Ls`p}>XPdh0LI}ggUntKn^1USGOr5nW864F%8*aJ zD_AD z!mZM2cCX_9^AeVp)?9RX1RDXirTED(oTV|@V2%y-5Kg=cwW}VweLJ>8=@!qN{}ntI zp3H(2E$d6SzR@6IeOfop&n~0y%gUtc@97(}6PTo?{)9j8j3I&zTM&I!lu2ve#b*<+ zXS<7!KA%qd$UbE3yhG3~;mStCoD5jLu`6D2FI6WZgYmu1=@{IReZNH+uT=}dh6Y<+ zyeRPzf|x?xYX=T2d-TDKhAblk)va^E<6Xw}7E8)}@yY(2cAh#I`k(rMmN6esI9Lm3 zpZo8_m6?o~j~kwL1^#MVMqmbWRQ-eLMAT${-OA?euw~)8Zs3%L%$YULUS4v%yFEvG z3yNIS4^5y^FP9G z#G}1k6hBgQqcYl%*+ss4;a*iu^crT=N^vKq>W>oggFdDsSNhQ(gb9)7#+Mncc2fGUz|sja|2y-Q#;e6l<@h-@vJ2ljK)plI5g_xw z+JcbGnF?P8W_wi)tBiZ}m44Nim%aAbx-1>Pb{Pj@?1?q~)2L-IU7%V zv}Q3gIl3FC9V)$=>fS_5rjba~5jDeVGH^?n&(zCI3X6n%{@LTU^hvB^(KxZYjdK@E zOupi^(2Y|9R5YEYN4t8ZTXwnZ7Hjp^S z2QN9RO&avbjkTaxXUKQ}z7tt-%oLpxqob0r2(PYXSRmDyR(s0Qm7z!)=m@XBjs(-i zZ9Fwh)A?ETSLp4aH&tgw7|F?Cdr`cB&;-C5M0w|C4+<#&j2IhC_+fsiTr%fLN)k(8 z(bn_jQpl698j64OT-*D&&tfHTj?v~Ks$r=*+6Ho4@M+w669j!#;P~QZM}nOFWYy|V zL1%F>HXXKr%TE1CkX!=$5i!D1<;o~0cM+m`r|I@++k?7yt_|$dz0#+s`<;aWm!lVq zmSb)xq}34b-PLo65_SJfJS&5rUST5A@oWOVvK2vOeL%jW6b(|2v`;`DZny`3i^HoY zP{iBDPcP_iAA1ZL>6<4i5QdAXZHC!II|(e~QJt4L6q-fr`&sXE3)z6uz}=q$GYC6I zwUuoOn`#J`e2GEpiJ#w5s1z!ld{hZh3y@=kt^2&?S>jz7re#*8j5Z3sVCuGrgoX6T z%Q^C+jthyb;zqi58NX$bUP(bl67Yk%El+N-W*XbYK}u{yWQQx_MSJ}OCuL}fTJU|7 zMlCysF92r6H0?ftj4N<4l&>#Pgwm0j@5fNi z;mbB3p6@^waS*9pS|G%OoFgoo6`*u0MukJpY_sVcLpVmzl3}9EUR60w(dPokfDf;c z5Hf4YcSYS|tOYfy1pUinY*znf0&8|Jx2-r0Nea!eh6>)Sme8z9{`W?@Xh5pD_DoI) zY0w}`i=RYA0fz#9Gk`s$iA6s@d_Vot!in6>2}FJxY;d-f=#hpI_+>Afi{eIe%Ji!5 z_x;4|7Exqq=ODh z8$KwR+?#N~zzOJgAs(#_%_%U6&VSi4Fo9sB-$Q;;nok@#&!SiPYA+6#@}7FKE<7kq zO?Y)UG*eGdwPf6VJ}%e2(M-Sy!zESS!toiRz(d9C>q|69hnsllC*!bQ53s(tAX!)D z32JC6n9sp$a|8FxDZNsyz2*ZkX$i1TDdhJt92O$c8$?w|=3N8rIc!d}nnb6v3jNgAQ}yfr2vj%_WX4n zXZS_Iz)r%$HQWBK2#_f zR}sI|Qa93-pF>^kG3vc#;RTJU3xS-_Bsy+;{n02x*U-i`dk{({9*n{+uxTD-?-Pv} zuEnEnm}z|0L&%qA_tG;vz}p*$>8s%+;sy)acg7wiFw6m*4csc1IuEy=#v8iIT!a7{ zhm^&}5P__09LwDG4}%xT@XtvU7Bc&J zWBV4fw)4hZ7IglFi|Lp@@$OOq?J zfSb*MnB_>AkbW})J;CnRl+Olf)}fic=NLX!w_eb`JKPfwQVg2Twmjs^5MC5|yZ2-F z5mz@Jh7-@~l~K?JWE&#u;1qQNQoF!672;8iW{q2)t2a~JSs}l{Pri*ig z!at|e>hD&vU&AODS?mcfV8@7`KOgocuJnU~b+x!4If@bUi-&?Gl;nP}x zsSAauCCKD9G`YOf;FGXcaem>@2?2a7C0F8B+ghvyS&hf%?jG<6S=NjIUW>^kt>4K8 zi5Ua9+5%Q8aOQF~VG8Fh+S2;()-O4tRt%bG35&5n#1A?=QzQzu>G%dGLf&j65#3o4 z#kjTk^H}mVDC?g5>3&lYDxri(gNem^SL@>gA`6$D^pgR4EVx4G zKR9GL$kmbdndmhOMIaQ|snEHr0+z1C>n%Y}wO!%a4WhbQ_tU6ydE7p%YH-g+f7F^> z18a~`uQPRsFN%a*dyl3?yBZ`HgbNd~WJ~S!i>QE&j#p0*Z7a zvOffih;nGW`{LV4Arx&4wRZ6FlpVvIQlAdRyD1ZFj^_d?WZ{;VaTXeSI;ClL7lgA$) zNt~=zjsEcf2)C`L8AbA^9J2r`TJsRv@{aa(ysIN-6GSBo2&55d+eIru+HyN2?WC8lcY1;zW_ zAfx122SNt%Np}`nP(KFZp?AEzmN-?HuhWY)xPgs!kdV7TfsW??zrpVE!ZSq^XujrI zm9}maD{SzxAahVkq4ht1f{4-LI4@()>5K58%jV zqmQ6^uRu3Zn}g~5K58y5z0eQPIG*1Z5D>5E73U9kx zCh!X~Afi^=Ub<@o99S*BP8R+|oROoora|(Wb!#1&=y}3~tFzcf1Ro7nQY&OtyTxUC zN{_!S>;yYt+gk!MTB}c*)Z=}@4b17t>e1YrKT4pmt6^xJomf9Zg@d}d?;jVj@A-FM zWm772vzfTRa?!1z3 zw)7V24AtFc!MGu_jwLjo7`LpqfpflWIE;Y*4}?xEO$^L%(n2oRHEU{?|Nm|)?>$#H z;EG@stx?1rBk4?dX+7pAbQ$`^Xrpt3_7f85o&rWsQ!33ejr8{N!c<-_Xu=HUZ(6x!F#kMH0d^tWEvs)Wn0d3;!Mqc(a3~MK)4z2X2kCbDgVh zE1*RGPdnZm*aYPOUmApTt`k3B>rB2jYy6LXt$|*ATa*^MMbI-JE?k3+1nBeX2EEjd zQ90S`->ufybciv!9)mzB($bNLno)cFnCdVAWoKX?V5}tw6YLfowe@?%bfeuGE>d7+aRe6CyNp)sCO^2uM_!X^bV&mZxWGdx3+= zX_g`47J+7FB6k$}J{Lth4O1hdPr2bgM?-Lr;BeDe7F>@4@g2LtH1wOOwZp;5+oa;N z+&FY%^uPe|VDX>WKqZBso2o(unCY&+fS^8kVqEis|DXej)`p34A!PkXS!n%2$4X8@ zXtNT|q8aou9&HG_dAM;y(;RafrE#0T>|$e)OOyq18zQH3CkAXz0CVElvRUXyE+SwR zLI_ZN+CN1`SBr}WS=S;w2tx7JxdSOfmiiRn#c5Im2a0p=QL z*AkH8Pv}P8eQ#pE0LgaV5T%bgIy+btWi^O@f~{egP*950T8q zj%tt+L)eUV1FZkL4cf^>pa0$;#A*K@`BXsOA{jpa(oP{z!fMb)^RI8MKwHrwMG-MX z^ESNC^~uc~MXF2<^w8;B`qf_xjTfOWQaB`z_!4s`YwwQ>29(k;0J#R^FebzsZ6)ZR zgb~zN=Cds8+p68qTlZnQ@U~0y#8Jl}4uL0{=vGUy;Q(hOLZ|q_-;AQV+VT|Pf&k^J zB431HHWI6D!gOR$ZH`yeino;F;ov->dzC8Ly^ntHpvqs%h#pFB5P$%lpjQC$FcexR z0BR}Fd_SE>ctIKShfh|r186{t^9W}kx-ZK~B@`4ej8SD}=GI1qbbxyw#l;D`BmxD8lAn2~UZYb^-G{b6$j%)_y7=4cv?2vlR1e97?qSj1ZiJ zpph>fQsIzV=>a5gLqq(7D@S;odo{ruIg00|p06GY6T&V#HdN*Q_cREGn<)-o9y!m- zG-140x8)OK+<%rPou<|)u5~QAjq05NcML2s%x8K)yi=oN~-~8eQ;Qt34-sh6Y)K zg5%}|=!wKS$f-sSX-O`AIC3pK%JbCMT&bG6uqJ4S z(UTs`r@J~m+_e<+H11PsfwiKB%(hJP-MT%Viz<>GIlpcK4)74_oKpHxhz@lrNj2!8 z1LkmkQ4t(52+Z_#6}%u(c>jlrtX5&a&wVRl6>-gp_!+G~mhmR+)1$}*tnb67u~)q;pKCss3`D(iQ%feZ+R)QChwo6_QQT=ySCO?_mG4~( z^Ve{Bg;y^$3OaH0n2DZ-fU%EN5eE(1fLp!PTcmpxj{U6F#YVxPT(k#xLliZ**AT9x zSreIzfH%y{xP({_(Sy*Km<{^npI;$Scon4qK;7 zljDlWWyp=8?9%^Rh&PQjXSa~xyAOVLLIK`4?(?n+9UQNvfd!GY{l%%vNh=lA+En?a z_%M-j<+u#DyqwIrXPVvqM>g4 z5Zhy)gini>0tJ+-{E^5vXP+a@a&#S4b8yM3`s=8Id*=i$JVTl{4j7SwngA^v6suN>H)q)V4q7C!!|uDKas_`y!icUI*+o~a!5lkxONbaujvn;9 zfogFU1Y}NePSDAL5Y*kLTyuXAE5^rjRHv2E&f3`0^J*xM)yb(oj#1+N(-@@`mhvZfwcD~17Sx1cWPAVj2Am-__w0cL&;HwhoPRN)xXLKLgk*WdpT za@T$+9L(>_`SrZ}lgxtA4kYbKS;bYlnQ^+EKGuc?HpRaT0P9@$?h2{Wo7}FsE|p?E zggQrS6a8$Xfn9D7XS(frSZv42RQhM>9{hNUM2(V59TIXm`aIHyXY8HxaiaYLRM0Ty z6Eg5J^0e_=Q4{&~EAP8OAB&%kbT?8?lee6>OY2*nGb;ZG^HCF&RHSfdZKQTy^6Bxy ziRWPfENhVls5r)J-dqEV9dUe%ynFhOGd6C?bng@5FTKlB1XZKJU*9ULnD}oS$G6U8 zDiN?v7BCtLm}LHP7WQwV06t(+50*$5?}lJVCSpYb0Dyuq^wj}b*Vv4gFC>%fcya z#&;>A`01uCt01}i7IBq8YdeO=8z}T|B5ibJ=J~E3R(UJKQ&vL4{uB;MxJpfrr*4lf zbZ8kX6?b(OKn2N*wm>}yeqeWsH9r9b_)gOy3O>_NSr>=+OsWXd-fwuR zI^fH$g|WFW3fs8am@~B~P9ajBW!Yju>kqCV>}R;`{PL3#H^zP)lAhx%q^u6=bFhQc zc-+uo?z7{l^dj@M`j?7wzBg;Us_9Cs~I&kpy(i%s-zrbH5fL79S$%JiJ+ zX>j^4&Bg~>XaFflUW2DAlU&tg+Rm#&CeYnnTYu^)QdFxhIqqLqwhUSeoJyBGKX5Vn zOdAJ!(0jTf24!+kFgLtp`NSy;dW(-5{+7tqCK7Q1?p+go0NX7*J`?_nd))E-<|WoP zxc1j$jrZQ|HG6fLhr;JBx&%AW>GfxCb`*qN4)pZbv2(NjRhu6dEn_UAs6$bS>}K1M zqy=&tlB;C3n=4ZH3zG@bx4BgBY4PkJ_{40dmGl?R3;IGi=nXoFGjHjXts$Bc@_Zx^gZ{m+i!E7 z-p%7{NG&)UNYnapHH$h=dV1eTkoc(ftt^>zN4em~Z`%25loGnk#JjMF!qgUN8I}5u z#7Dm(=Qffq2I!mD+0vL%!|LwAbJ?CoXZIyd+W#4v5}QF=X>ANnGtzzW$V6yYbi&ux zilkv0yw)cLUo5NIFu7p5IDjmJB&Fwc?U`e!%5*X53Py8* zGHbxVXHG(qJbNx%EMVcV3u4U*glvjPMQ<|1SM+ZmhaG0g-6?|?&uDoT|AR^773^sX z55aerYFHT|Kc=(XGWXv7UFdF%?ctrG{5KxH0Z1<#fc@s9@#()*d%Gl=t!eB*nkT9L zau8_Kw`(3<2D;T@naJ)*hBh>+mX=swJ@m$#9G^F08-QFWHs&}i?HP09m2{9+HyB_1 zDXeg6^TXrraiOsvY5Vhnr>gp|$9!iW(1$Fe<{jP!`6y>MWn45+k(98PNPp>aq8gl* z3K*>ZRCt29l*eZ3Ok~V0#!;QsyZ_Zpl4y|9yO1QLS|(4xy+ud#>*+UUIjIbvGZ5v` z^c2t5-t8_G1kd^{dEJz}p5(y=&*h6)r&^y>W|pcm{L|;cNK(T3N$`-~#XBZ62zoRJ zAH;nHiPQ;~*ga6G#~e>*b44FRs;~j#(^99pzG_sT3RQ3Ds$Gd=x;JDBq%$lPpkp7q ze&*INbk5HG4jE9K$AVe-B|Y1ja69@eJq59xyW7bKR@T~=Ik+DPSez$(%Ka2!{3 zRNcgvIZ`n5$Ln=cIEnj>xto(UYX4|8H7!mkrbAB#4rBL}%%CocwqnZX5%99nMVUVE zBtXt;v24Xim4Mrq1Q$IiE}NoOf5NFzV-8{h+>E!06)yZ&n+v+svARz;SQDEb(2cOr~l9Om z+Ru*srZ_Q%#Iwq@cq4X8zqVd#XLzCgut^!A3Pn)jrt2Nhd%>&(_M_1{$(OUFR-ubb zj~;aOJ_=_&Y$B-ozLM(x8n7fMamaa@NF&U}gJ#d9O11hqlmNbQyn5{xPl<@?vR4`I zrAI6VUvDz5K`;fM*m9Z#on`GoyDkx?4DE`23MB1T;mx}gHmXrLB1t&{eWUts{|q2d z_?V87)Xl6a$biXt_er7@`{5aY)Qo9+-()Dgz`Hn$Tk_9e42J*LTs%D6&;7-B1T7&F zhn+rjjJMD;PdvOvMS%E;Nw$YM2ZQeGE$UHI}(qnwS$1C6vcI9HWzCV?NF) z+~?B{x`*Sj^q#^Y1;N=5Bk;>#PHwh9GM}y10{{7~CHE|)Pl`tjJjYiYCl?X;iEkTr zGl-P(W}+&~+t-VZVqYxTgqdqx;LZ|S)9lild??eYLupyCT-?nV{!>t=BDB6|UENw| zj?BUI5?GeC`+U*cVp-viwzf~(#H=N`=awks1<3zLO^q5-Fz)uQi(KrvTt75kxCxSQ z2&i0bmR%0hhM9@z6w2jsuM>zwWJYK@hw^qBsg`aIF<^L>O6?uXpW8G1#yL)WBT8a) zzngAw$;z;Q)7C|CC*_tb(pF-q0D=^jf6Bo6NCv1)5QcNYvBD(ShsK6vU$kTi2~)qD zv|`S%P4)npc!{W1qRn+#ik+Tpe<1F6O01yA$&n*-f3#u{4;seJfH|m300E;;gq-^C z#jW}zUfAX_uQ8?Pjt(nUi4M90wV*We4oT%c;P|Ax`s#=i$yy#Yo0BW#*8tm^%nF%f zH4q?>>|NAzf$Sm!SXrG!dGEK;Iz{#IYp)c0>~TmzWEwHE>nF1`{O{r=l3)^{p8xUK zTcK5cajzq!{T}A_LzfA62yGhWcdAfPyp2aqX(3MCOXt3|@|(oZ&C>ES+-_Y>`j+zv zl2Jy#4vqoq)>}_LT&}g-63e$HiNIOrIdLPmz`h7k2_+W83IWeA!_5^l!s4y;Z5U?M zC<)`9OwEeS;Y#{n)#Kp7%J1L+t@StBBb5HB_SjmU+me4=i+e@&h^MP|FdnrV+BWlI z=L$s)K;@~<4idHYF6Td1=Z{VHSKumvi#1GgRf`j(f8gV^Ap(*lU3aatc0$f3-qx?5 zbOc69etb4X%6EjoW$CaVhO{WD&N#E#9?89Zc(Q>8$~^2mF7yU83?mH2YO;|MMSGd+ZegQQNTn8TwGDaUF~t3YWK;J~o^)yqyz?hSk-Xh-gf$*RM|z9L znNi7C>fq5A{3L?vEf*T#+#sS&&W((xPnyK1bPpA9%0iC=S5X7Xp1~gg6OsR7Kz5>? z)TN60(#WW>5Com`OUUJM3>#y(Bv(08o0noFeePBL;gejJnbTtkXhUf^P`T}Bn@c)%6J}Id*o-B>BeHN zX@cxQ3&<2fPF+vl4~z^NKabP!c@l}=AP3PNS|$$g^jY}FR-f6F>W0WeA)@REj+*)i*l)+y*!rbW zt7)9JMv%`~fgR&lyDFLy8h%xv4#?;~>Z zE0um5g#$UK)3S|r-|*o9leNW}XM?2}UJbKj$Nu2|fxm7-5R?8!_NkkbSjx@5T*aw> z2B9|m(E2fUDh}xDf_pQ1{XM5uBC;SGPta_-1EDS#;aiHjr-K!N4_?{0+2MR`$*PLM zP+S)lb&H-*Q;BF0;PH2CiR=6p&JI92Ll?YWFUZlxWxB=*IBtYVK8o=^!cn!7zT}2} zUF5RJW~qp&Bc&;f%9T9cyYpsi*@k&nWkc80nV_5L#^A1PnXDGtOER zPs`7yc?YnuuY!i`P*rtx-4B>>jiy%X|4S#!4EN{My}(P4tGxP4R3e$e&t=!vu|!}6JJqc^}c`Pr)y*sDusr|NhlT~#0PFEuvxNv zJHT_GDwNRO>TK4r1m~rM#G7?9Lxw>z2=J}92X~ty15ptsJ@w@>?|iR^{zJIoQ*{j5 zCRVFOQUTIEPTExIvRV6IQ?CTjomRVqRd%zplnI?s1;92(zcvhg~{4H&UpTvp2c09eZ zI&E=ggp?A$QRa7I-N~t{eimF&dJEYLNI{DUtACc43Zy06&ga&4K#Joltp@O^Kq$m9 zc zYh-+kel~oV803eoNtYvPjU>T5J#+G0Jj`1y^0-7r%a$L{^sPCvu_{kTeg6XP9UGv;2gn7 z_gK>69q$ed`j`2&vN0o0`DU&NzuGQ7YnzCl+u&aUR8S}o26(=lcFa?^Ze6m6siMPBVWxgMul3(B8k{Sd7&W3Px_-r=7ZJ6J%}Hl)xRx{purx( zqOAvzv!GfEE1HP?H|}*&9roI{kKsOlH+FhBKo+=}g93|1TSQBvhQ1P=f*3!>uYVt4 z3B)%L_0%9xcQy;wwS>Ia+h=IH$FIM8Waun`*$Z2RF8K_2r3IQkz?y3wQgNpy4{LPe zq5cD0L&^gqA7!cQ%&?%{I!#%umwq{$s0o zJ&DL+-VY06CD!zO3<<=15q~t6AtxNlJRlWBg=TTI5q+wUkYl`Lspg%VHi6CoR{;w)wFsI4=3_O=dP&cnrhH0zLF}=% zN!9R0y`TN(i&pndGC^^zuIT4r!#K%GWO}Fpm};f+jl-=v9|k)qCskX7&5?cFq7T3K z4M~%>=~qq}j8qQ!$GV``%tKp z^WTeiXh1dXe2BrEl@}oQFyL3s(^5qVlOc`_E>k-pPG@|J5rJYTgf(v2!M@{ zAuIQsZFWu4r@J!tMUrzDll__GO)RGY(yiCC zr$-6c5}krRH13pwL{iYZG4Z$SDMB8eb#sK@di!vKAH>)I5_p)@I|jqwrQyFUZxE(j zN=*J}P<*h>AMndL&BylnjB7WCU$N3vX>MIikQ4^cL$4~J8&?%PiAPDef;6Ql&@lf3G|HyD&b_7Cn$U?OAEpCFx(8&YYDP(# zI$B1iC61~RC=#zLuRGNjTf1ov+!svR@LlSg6q|6G8?T2s086%?v|}gF$)BbYifKcN zw`&(C!sV&Rc@AF(<-Gv~J&QE^djo0{dG)XPOq~4;S>y7s_=ek!ZK)A-7o37c7t@FF ztfRriEg`*VVnyy z$jezW^CD>~vZ4l|xoBHeJGTDGe^vXvIq&p}Gd^^x{ZumHVRYcF%jPtpo!Xg@nTxt_m;6>(G8u86l1%s$??_#4USL=dsIVQUa>DAO$Zte4%E@J_-8R z`h)X@-zR1w2Z`EJNX}tnb($cwH9oOR4`K+8Hf>l2TdNJ_nF4F`dqFodO(aKjS{ha`q_?dsS)je9>#0w6%Z%?k)CS1%$a+ zyMPJ2qk1tBR79|q=?mJaPS*7Yo7%@D5B}cP#c9iC!6L}jH?;|S4TriOZ%Uz$@fuA4p8n#!~8xoP&sFBF=M$ILXE{Ef=J zQ_Yp@i?PAvN+mg@gwt7^>i+l;^S$GJLDtRKf959&WU!8g$1Cg)ECq`cQChxI7D7CvC7mO`~cRhkNn6_rTh5@i9=PI6|uNvDn((0z&G zYxtXx%y2QhS)g&0P&bW%}NtqD42AqKhOlhu=WjUYd^oVJ_s@NbiNdNcWnDR^7i- zDb?e=)u^U;gPOcjBSDX6Pd^W!R{#4L^uUUpD~hRErw8KOv*)^1@s{F~#tqaOe+Hj- zS{lL)bM7xeqGg{qJ3wp|OJ7dtAncRbIZh+^lK#di0yiJesSZc8NoT&hg^Ev&RC#O41yO0lUBI(UmZ6QajTh-95mW@Zgi- zImGk`L=FnLABiP|4rfdZx@vm)In>h7E;3v=f6~K-DHC~pSC$9kPBRR+j4H*i!}2&L*T?h(^1T19xRuBx3WV_t7X z5EV)DLvsMq6@Y8Z^t->2bc=~3Sn_&1zsEX}Vh@}O&oj&JozXS--Vb=?#I5sKJN?z? z`3RXt5cfm*B#)@`_Z%IbzUHt6J^-#VA2d^o&+w=vg-V=Z(Rr24%70&9H(!n}H&l=} z$f{x10{=qVs3;#)J3KbmRVGwx5W%g|FiKZTtw+ogr?a3Nb zA{G%WnchAYol(-y-9CX65kVt2s;D#Sj`=pJStl72QW|?S15tl0cZ4g+MZ%4;zAXm@ z>cq@En=DhmB{=-^=3h|?zvER7eB`cx-sDyvp2rZziJ$OvISa2>Bgl};zP7e1DRgz@ z0dU#KwdZ!W%)dKCvVH>c)w|~Q4P7;a5o)BplDORv|4;X9mI!VNo6T8At~a}d?!!2K z*84IPe`wivqvswP%oV>~5KS?8^^cv)y@rkTC6_E1odSZY z1~2A;*jK)ZTr;6RMpu~V6FOXvEN-kE`gyCD<9VFccX`h;K7PlqL$&b<9<_%l4TIj5 zH(aAZTWtW;{gt$=Pn-|4)!ie+pn3AB$KHinP+H;6kGL{%xNyT#@sBfqRYf{ggWzBu z9GQJF@Lq{Le)iVl62B#Ueywj*F0>;wvY%L+F)7u}bC2~AVcFcQrmL;!{&65PNkxTs z9vd988^4Qryz3QfJD4lemI8E9y(&*y>0aE-q_&Uw^X4xjk@?EZ2sVFB>g1 z=M%8tN{ucpUu&}e<5<2vmBdWIB}dc!?(djxO)zDe2B*m0DIrU%E+4$ta;LIkZJA;` zjW+-++g_XoyEaw5=dNDB=~bR7fpv1T9{zg?q7ZYlFxf;R%xP#;joUxvA>!I~S7vc< zfktA4jew$9=WVh?DDUsdca=fFJ-(BuQMeI!JboMD2uea^E2;>dj;86s2fvXqFL?^} z41!l{*7B%5d71plF1G){6`47|y?o+S9G+uR;-8pLtgOvrd+;EiF5? z6<<27lwd3qzi((-2@pxwS%VD^l77$!{qZwbr$V1DCHHRLZ^%j;v2SBms;d#DE4Mt5 zsY~O*-(aK94oajQ!Wsowz1QIQa5DhpkFIhh?NL%ony1}Lyj7-&$E+;b1xOatw9Dp0 zQO*q23W(jW-{-)kshu3##xrJqhhBVxck9n`=(WS+Nd6PInmRxasT^52)#suMwS-=$+&}-=GA9&sGvZsc!y;Y?ZHGQdv(j&e)Mle**=RDy}Tb_bSj-P+pY_J>F5qMfW#60 z%;=312s+5Hk8#Ce%t0l#5>R`P2o$0hk%2>sW9wD}N?wEm+~koiwr;~+I8|CRk^B-+ z93cg)x{o#^m?EK_f?S`|pNr=mrZ?2+fF{eUjoXWF>EMT>_vuFAphu{H_CFMPROOOg z*k`Fq|9qPBIA<$9ssLW1pef00jE9 z6FJ$l5f5s`U7G3@<4ZT7h~jRwq(Ou3dPY;EoofiPI&(5xS(^16F!^M%MQnd!l(lf$ z{tuJ&2g9ao&2m~yl^Z+pDpINGIPc5XPr@WRs)a1qmS=Z|{C|fn*`%mMxY!>yBLCZV z_s_YP@pjih?4v~fPK(|hS!H%(--TE^5;2WrlgF#FHnAR6Kq7NM_d5=YrtV+N3%JKZv@>)ZHHT->{j3vV{ zyqP$qA-hIL$kSoYr+39DtkB>rP;+UJK2r0e+F_kDL=k}*P4rGHb94(y>}Ti5WQT^k z509Dm9QxTos)WH^Ky9VF*-7fptz)u6so)R|vgwXigi1*oin&eny6I%?wV$VML;;Q6-ie{8o{&U{q@7r^E&+B3}5TU=k89dYEOPVvi8K;9Y zf*QilvPVGM%F1*`7;^rM)KLm0$sbwzaJ>Sf*YPT%=?60snk$}IzcIMG9I9aya*@SW zFeW)t^7zshGNtDh2tFRxscSugTPY$XA_;P4ohT&-BDObsgYS<#_|IfDTf&p?hku;w z4K;&@P(pS&B|L=xNfBQ;;Nfj)moKcFuFtqHKrRsOuQc_IpF)fBk@*k=gLtzhALiLm zDJw78R9Fa9xwjet(Vl84mPRBhr*{f4%=ID)a$BU_#lfBg8tK;GNH(Mm(k}7GUCPw@ z26-Ol+Fvf889l@=JyY$}3I5n{b?;+4`R26nNQvt(_0;kw+LilG7C_FjwvmD$2lij+ zwATzqfcnkaP?964^g+dBiOJb9G#=a}K5EF(3dL1hr#*NBmkIG0nKwQNAEcx#-YxQE z@Rrk7)K5J(jDf{GtF2voA$>0N^b8AN$yn~U45*ZR2mdT4w{?@+G2f5 z;Umg|I``?K_ly=uH^o}%{zLZc(l!W}TT6a8gu5q6yWNv zFREAgdDyLos}Jo>ivDWoTSCKLN_FG2E6~&T(X}7*&f)k7uk;e@GQxh&CMxiRVQI~S zuDqlUS9_hu!H?w_)m0U4Wy2lglK{c`wL*p84z^hYbRYG*iJ7m~xkMl4*=^tLaI_pE-eKz;F;1je(xsg3zW_Ek2EhRG zTd#J|?G~&EY!Xd=Pq>oP5Zx5IWnrR;>mn@J(nf>kUFrkZ*;&7-H??S0HT(c0w=J(F z{L`oQ*qAsTquz$pcnwM24m^2gU_27s^y2yiF2eMusD_%GiQzy(Q zSPilVB{HENi8`{{b8u{B=WG4G16!t-)uc7wz|m0gpHw zz7p=+WI9gSsJ1|TS8VIf7p&v<5j1kt97R}~?hS;UoR8i;PXZ4?j1cuXRvKXWz`hgH zj@Fztyr8(G`apeQ;Pxkf%|RH~T?9A{UJqAM|vvy(`!lZRkBG!19h_wdqbe zOp?(jJ5_De9MxU7o`^tY$Br8|qMM5TFq?j+WGMsYL26|q)&*ska?J|+$_j=Y4_EQj zFGnQ6{sKH-uY{X0M3^I{6nL-Al4hYtReTzwfz8x{1 z=m@p>%x(&F$P^@9C-vwCHg&|&n)1_S2Sqki3D6w}-1h)8K+L}tAZe!J%&%6++J4vd zL?GuQPqn|(InHfDYONHZ;f+%!w2sn$$T~yZp=Nt?>9KD?MRzsqV)Vtu9!r`uUd#Gr zvc#=G!GsP+$u@Dkt@28hjka8#t0OGqIt~y%!SES!?y$Q?*U1*sB9VG{o*=z!<}69G zLJW@7%h5a?>-$Oy&c6UL#cX}hsh$BPaj@*WY1A&r<*9Svp1~MB2K(ZVE+J7Uk#}fP zg_OKGhq5b9RL-UAN)dleXd$LQkfz3SC)5)c8v@=J4GU+7&=ezG$c?PP<GZS z$>V`iF)rJD-196fj1cG?-zI*AA-JPwCSB<~pf%-yZitJ|m>c=+VJ3EvGPfMigKqVu zh zkO(~h)(J(sM#QQ!CgG;}hVm~UzuB9`uCZb@Gqa!22ZaD0&J@UfCpt$Hh|)m%41>2* z7C5>oG!Ewii3kp8%!jgow7&Sd%rCH!Adv;YrFpqpjainyjjtA&sNiOSv=9u@TUFLq z183gb-Byx<+$1)BsakM$45a*D4WYC9|6hcf+L{Hvw)l)EdbtgOBb3u-CAXLpzwx;t zC`vtl$0#yvW*aztId@)>sBC0ejxa|Ac@?O7#keqlw9lHsJ;%f94PGmEkIF`iROSXu zATpqR&C;Nxbql(_E7y!+Po&um|2x~|vzS6CuS&$Ocax_AaOi+KFrebzr8iPcadr%b zsVi^Z;(s<la7F{4r(*)~OW>Z7_YE{P09Iki(lzLhc@JA*g8}ca8j;^cYhX6R_&~lP z*4hItX#n5-fnk*qpz?VO+LiBylWxdfLA7+KcJvI5^VaBeH=~Tb(ItppUcLC|6Yhzs zI7*%_I3l?GR!bEx@L7rZ)+cT8hJ&~@erYZyL4+`1^G(g-gs_lNo%F+&^u;65{HB^^ z#8$m+xEDc@pL8$I-Zu5{aK_w34&Ew&Rcs~R$!*ff%VhN@cp}HGs3{kcT`ns2EreA| zSXX-p51E^7RLaR#%D?`F8kprxvT^6j4rNGjsPRIx$-R1*WKl;V86Gk5-Ofm<;tUzW z(7CD`xBgdv{p^D8-bo+;2YUHko7iO)HrYrOkjy;}0VCkmGnSY>Ol0-P{<3AmGSXoGp8{6CJA>7-?EdXI z-oG6{QTkx?1gZ(eJ) zJ#l1h{rq#wr;c9ZLFQj~l`17WqMX!Hwg8KcOSL`A*)7Hm5ZcPDikw6NH_WfC2y$jx|wD}L&aS>VNGi4Q1)&sts)*bDpiHJWAH5~>%T z#70rozW1Q*Uw=U%U(!{_gFqGs-hur}6Z{~aMNqoigQ(H4-212ohe=2xdt1XB0r)uW z8E5TdHxT`VH)rAc6Ql~h`dvvrY1DUM=A;>snh-P z(B013U|o7Ayk_}xN{*gA==^BwTZ_1#OQI4S2jEDABtK;UxVG?5H~!YnX7EhJir4Wv zCL4gQx&H!_s1z=l<``Pg<~k|=<>iRUm6Hvpysoe_S>Fp#+-e1=p0r;vx;>(^j7gjY zomtP2nRTbp1+{`G|1x04JUqU|gSM+$KdWeqHebBs(ok(ivCs-QCs8MmacX1F_sA@xpe6bG%MN{Kq$p0HZMWfmJr9dq$ zcu6z~~(P%g7!yhrbY z!o9iSM}Lne?FM8th%m~eCECpoMLpm2mYJdL|(7ef>?KuPRHY= z=YQ@F6==u<`T);D__2sCw)9ta&28)QH$2FfpX3s~h04R6NK{62csM$55 zMY_w!$;`nXJ&z`R58U+#G&M_<5+}CeG26kqh;~TIk=B%GisL z8GuT=sKP^Br$G5nWy_|m3R|;(+_hFq<8=$C&YDhRE@=BD+J^y1(7dW*M})lX>ieMfBikBb1P)wJ{X{z zgPo9R7s;x=E_bO|p__1ysavZ0#n7)K4g<^|W%x%h?x-p}q(uxdhz72YVEjx?8%mHM zgGc`D>gXlV$IRAt@WYMYFA_o`$3yVdzB&1n9}HDZdlUm;)Wbf{uF5}mC}vF`Uhd8Q zuy9<1r??!(p84xMAY_wjIm~{tI*V3-?-Icui&nl?E^#{b@*g+#p*aXIrVZ`FFAgeS zFU3`X(Eo!E#J-vIJa-u)p&|4%bb=Q2Y=r6Lszk$z3sQIs5-QMr|D_r$!0l zd2%l}*$0yhMp_esh-@3Xf_Zi+Wy+>SwC%2snCbvHVgo`$RECtxk47>&(iPehqPbZx z93ZxkYd?8?w?(5tn>Uixd^YkU?pX0&tXo!>{FUB|!pJl9D53S&>|p=;vViAs%M<&8a2>%Q zgWpKQB6^?E=+V6|_0>&AQ9N2}>A55IZ9?n+c;6JUXw} zF{U!s35MDna&#)|vogZaky4=QcmDWziA#<^rKIDS>XEw#y$x#t0fZLfAS{Pw=2Qge zCft;NvC>)ES|{S+CDmg&vNRr+W;oBzuLaS|sp{|S7jOIU7xBL43O?T!rS)ouR;?)s zN|fMXC1b(06;j9q0{Fl6)=Xs<^vGLqXYRTCg_iHkyLiVBZ#sLhK8xDx>!gNOF9|t$ zSTIm>&;WC*NC=xyH`q1LtAj02AIw@Z_jc4f(9U>CI-O4m-bYn&NI%&`+8Ku^WIu0z|4gbb}>PGg> z5UO$L$*_TM{xj^MPv&oeX~bkHV71WD_j1U3kPlh)VVsyd%^$ z$T4x(;!Sf`!#fEYi+S{c!DF%H*9~VOGq#{-5_|q0 z-;5&5*19T_I=$L41J~_^#iRExpGp&lX_c966>b=n&S1xjv=2;VaExI(bM%{`54+}o zcgQK~Hu|~8$9@0ylzN*vBw9o^2ngT& zzo-Wlp`yw|qcc?()rRW-_8|q|bZ#Y#p!>;$@2|Fh8KoUr<6H_Yro$X4b(g6d5j_VHvfingX)w555vZbAsB1`P2 zz?#6vgUjfH6?GkcrgWiFA7@@G`pPO5ry>Zimg5Oa_VZb_BcLl=z-0Jh%~!#vDh4Ml zpsMn(lI^S`Er&-E)Xd3=G_pJYIu8z!B;SZFX1+ejz^S)Mni4A2)Ys&r{c9l&tUnUk z-uz9FB0=Ot`QwN)e^eyF5ob#0O{se9{ZrunWa^sI8?|&un))wg7Cj&w`fAp}=A8oJjZMYAp-LhFq*Yp!}b@8b} zkpyM1YIxnAk1+J~ntcB>3LChir+|9?UGN2w_C^umBR@>Wb0zH zVL_h`P`NZ?BOl>=0gh@U^%-u-l^QDI*2Na(l_jNGjzl!gRJko0OsjH-*2n|xFE0)X z4*y0WUPZJ@kEyO=z6h$K+E6a6cgxjl$iNw_JF(CO1!&rV<;F~ls&+H1p)u!DuR(r0 zhlUbPWJ2(0UiSo-C-QoVye0XLf5eaPJX8b@Pu=vr$KbF4o(_X~O*%B@u{e4u;6Q_3?&&k(TG+UZZd%C0LHiXcLy6t&6W-Qoa3m!Of~a6S-P; zPeBnF_Kz6OUeR@l+S;9QRG9spjDY~_TijRKr~g%Xwd=x|>5~BrNRnjeT{+a4V(}@) zcT{cG8{BeS*gL^56#IA}Lx#48m-PLvJ6P*Vm&wnNmX#K8oCC?D_G9NRIX&p*vz551H0r$We~qzaX9$~=d-QvQ+9aRfL*nPmBCo!wIIc%#m%G-W zB^PG`Slk2@X6Rh!;c|AHSNvQFRl7#wW6@qlHvA*wCngb5dCi7;CAW&m*S&s7wE%*i zrm6mMmPT`EC{$M^)NNz<4@zQFBHwYhd>#`_-@iT|r3=i@u@aaB39Vk*#w18MxK1hG&wLJ;7{CuDt}bCMl>OiRLWZNwd-m&td^2?eLuCo z0!9k5gRpQ{qyb;1a;YU9?TF)AYL2_fN(;+cdPd9a!v63v2+g9 zQ=|{JYnUBYyFnfGxyY7l{U9y&y8Ro3g@08^X?|$i@1#BjDV=-P%sP;Fi$Pn(j=LYz zvIOrWMC2MU`PAOP{p6&>>5;-C1v*YDv#>W*nV$T1fIIj7$a6Vv{utZNjZ(QTtT zw)X2S6PPj2sg+`Y361UafilmrfaWS!|8&KXfa2MZe2Do|mEO|p#KtV!Y9|#ETjX~S zTML%Oc)=Y??%rCp!g;)_{~bOx+B|`WKa%w=eN+@IDY|!De{&-$ev;ZKX&Zvydp{38 z8pXN8O47S^DD82|-W+~^&Iau~Lg~fyixwhzemzOx(|H?0mdeej2rc zy|0AtvUa)|%mR)R%ca&2@ia_&(iyn+`2MZd4;?pPqzS0#gBIBbx*fVrOuLJ8pdP-G zpIMX#0<$*O8n?h|Q@_T=pt1Y5(DJ@?%U+4%pc!{xn-q*u*E=UmsMb{()ag`-FFQvImxp53ce~ z&k;XIpHbvM1P$em^9d)8Qn*z}Dq`I?mECkv;HNUg_ez!^Cj2h0e2(Vc`9AwS*ET3E z0+rcQ)WLc~FI-Wr5QtlK3t|tZ3}ly{P*pvXk}eHy-xR9IWuC0%qEdduUGkc)9lMvr zW1Lt}OX875f}q9E$q~0dO@{t5jMppB>YsT`CT(V3P;}PBl~$6F0X@vFgY%OwbUD#d$agtmTQL-lu^%R;GOIQw2;&y(sjsA`x4ptR5^7@3%bMwskcNL} z-Nn6r=YRIkl=c5J^)b49N`$YBR8f=^&Mv=(%C*}FmsIp+%}nRI%gj-9jNC3n6_zgn z8=98z4`9Qbe=9i8%N`%RV4UCpV?FBd$nBjo6{;@KW!k38zQLx3w2^*t$Jg~9JOxwk zd#W+-x{6-_&p^wxWR}-{9&+r~4q5J-qTEv;5EO%)#DsJAm#MmT>_fDNxwM&IFrku< z>+wo+rjczNlt<}hHz)u&`hd)jol+Wv|MnjatqKd)ZexMMAqy)4Rdk#r@ir#ZLvh8h z(yX`wN9$NoQh$7e?v52Xo?UtPSPw5w$39GxH&f=lWY%7??Y*W3QP1%ohmOt=cTEi# zXI!Ae#*=@3(LK&(-z{|vqS0`}C#d~Kmgs>5M;={f!y0~pX~NaAJs{a^6X{^|uUzd_wo zLG8{rnhMwbGn;Wk8`dKQ%(BQyP2cJadpc;DvoSy+pLov&c2h)93we1r#~Z?qJKl_TSJKDL6h`-4_3x5=7Z1M2 zvFq(VE)s%%GpE$;wgxDO-w13^+`#FD=%@g8p4HV9&wQfE#?Z%v-SLd0C5IIv=N*)F zvj-xdxuvwGTgj)*M;o6Z(W>rB92t`jiq(@Hc?WB|^TlqQ+7WOD`9r(_pz-qQAhp%| zqeOp(#j>X!l|Zf4T9@CFvVtH+jhTFBcRsSYFxlX_!`ZB2l)37KB#%N!cdY0+j1wD3 zC%)aP(G0joiTsqz4X&D=QHTSlW>nOBgypv~%=Se#Dg{3g~ zt2D6m?Iszz0qV0qq%Da*{$d!DX$GJ!6d$9vsB}o5pL>RWPjp1Xg(vYao7MGeJYTQl z+NaQ)!i^Omyy<-P4j$sk2#ImrMrB~_7`fWZgwGCBI}jz^{0gTXF=qE1TE0+mr*BQO zQp)B+zTMgARF1wBHR-UGL**A@Y|@d=DY8-(A~pIy$nej;We2ch?7!(#^9$nrJ9PG&x~Fj zEIhSg$hJN=;6|pUPaVjPH-*pKJK*`BPIf zWAnR$@OYbK#}Co_#=0<~7fH!CWqOBTU?F6B$|laI*~ORF#i6NtW^>}+iZRf=A-W2m zFxUyu-|;8mo8v43e$Rbq!QF8SoBR{y-?wvqM%437qEyKdfC!*q7{zc`X?~mE@D!lJ zw^}Nf^P^44<43SJT{dn{jKnjJ1d?g)-D+aVE4+Wlp4!b*rrk7{nAN_w*znPa>wHLJ zh9T~573f=t9Ob&>mN2}fRaoNWV+WeI^F4ukquEU7zxOOo$y#Cy-GVdr)2Rk|rtOt2? zWG2F+LQIzoUgD98Fal%FicNV4ke+&=Z`O{0SijlPgF)G{%DAE&wTZ~`F^SHWOggo588qAZFE>5X-U}oD{#u>dz-tuc z*aqFx(Z0bAoylijhD>)VsH_jNF!B$8QrV|K!E(w9R|&s-2qLlLk?UAx<;2$wr{(8i zZRC_Sb{Y?b*C-Zl>RKyX-Dm-zY`d2U4yzsw%9X67G2PHn2J?5}8ah3OW9Nesl)lgt z&thlnUlssBm3$T@)HHU(Qq=2>-AJL6^?z+tf;i&Uu4|AnX@yj=v;p#|N>TYG8VA%u zIw*3+GQG|=BLbbM$kCiLv5eCdI6C(yAVOi+M`+*72$VDI>VXVkpI9DoynZ+e#)H1RQ*P(r_ zxXyxU6^UmXRgwn*u7u&g?3pD-lGjS!fYeY*DD8AlE2mUDW`e`p=I!sM9%Opv!W-}~ zLb3tor&#Eq<$hc!tQqfGxUjE@GFtE(Vy7ITktPN7RJCc|`FKm{gg%To62eL{D)J`` zhiFfYORNeoSZ$pl)UM2gHoQ8Eql2k`a9+g`CU85TJ+_Tj(H?(@7BxBUVI5f(Kt@@w zrw4aEA*F!DmVAa}7E#T0!Ek`;ScDZu{yp>_#k2J*Ok6F-qcy9vup@nf@Hxsb$SY^c zo~#&j3kv~JWC=3Mx@PNL1}|Me7k7^zu+k2Gj>sQtE!rBj%awr^JeA=>ItZq^Xahcf zRnHP9p2@G#=}k;0pF%x3YplH(}F2#p8(3;!0DHy z+2c`cIq!)77$urMsqnb+(LrO76QxQPd$wVIg&hr)nj%uGuK~^9&O?5~Py(=$5YRS{ z?iYi6m(kQSZ6ul_1%1;{pHSye)Z=Ha%2qJL8N`M-OE7HOe;jt*ktQ6E+ya{Mq!P=e zYvtl|eIAEyVH?f+7sukPzz=EI7w5U-B=m1H2;=ab=e;P;Y2mHb+*N);iw4wj(8~l$ zngAe&r9CShkzzLYRh)^x!mQ96_ix`H4Sg{J=gMCZYr)AUMof&orMHK`9CB=N zhec#FHGPnwxcwJ zIypreL9sLSErU4c-yTQ<{TIg?-RVlvWl>WBLyiu_@>YNm^8ypfef^XNx5qBc^62bV zhbUvQ;=I|_G-lA!f4`9?9JC6WT&6`}Du=d`^!-ehS@?yA8`EakW|2@+FH{Iop19+q zAi9fHE$|q~wn?=!VA?8up6XW_F?;n@U+f;p_1pNEo0(f2b3=H={un@v@Y43&l`X3r zv(GU5ue;|m<+kcDNO|227V77F3{Cc}2F|v~YI5a+;)4zDI8!-csdq#>KUc`uubiy# z^CT(}+LgOrd@4)kpo}en-e)*6GmA4g z6ple+xUU@%INun5Jle^5t>dx?&wF9{=j49X-oR(8CH;!I!G*28{S=4R5v=?!ImcnC zxFXAkVb#gRKY?~C`>&wD1dGl(pq__AD0oKaWQ5|hTa!`3g`to1pgGS{jt|vlM229* zk|#vOt128$HV6sEW8H>885W-r1g^%Q)QTbR1LQ_v z+6bm(2AF&4QHGb%b0Z0zr^ZobVuiVLXYw6-FA-1)|9iUv&UNtTe+X8peXo(-DSAnM z;o}R*(|81n#2PU-K4JHbT`N^y{~&t^am|z&@LjRq1U>`1g~fgH%q<*?3N2J~(`}LG zMA70JU7_#0Vnexyx(SHznN9YYiFG*8_yhp1C$shOP+9?P3XN=A937ZYOwxlhI#JqC zfNOkcfc&ec}#(k_Un zKK-UiQ-x9diH|Zr(zp(C&h=TJB^8xHWyq&R7Zt*x~trLPMWmI zJYlRt&bE(MvKIp_t; z+479D)Ea3)UL)=KAB`Ifc>sZuRZA;dy`ukW@7JI<7neq%rsTV$2%ePkXpG_uzWQrL zK=0GLYzY`AnmcjVdr-RCxMcXmkkwn0|0p1nvSM*@jDjp1Ep0^#KMpC*T%hFqH;Nr# zS2g`FxWa2l2H~L|q+1s~=&r}7lcW0L;R~YP>5rq>Zbs`YWQAgzQ`54W+x=H0KzgEp+YGrLmBvF2e3emd{De!eay zP!Net>Dd4+BG!;PcMc#&nzrL&fyYb@jk2=ueTwylu3)(o8jTOxI2t}(^I__9CA<;q zX`>#qF`~`0eWG@*V*TT{{WTHPz>NaY559rJ71j*eN_{FaPA>S;J9G$JVm=u#VaGbi zP6By!GlJ}+k8EBPAT6v&$g(f`Q%)*UP_gYG_=he0Xz`XG*^)*omn>L~#3QT>!_y*M z3tOknqpdY*YB@eXx(Wg|eH&R8Uo~4(Rp%x&1BN-K z1ecHh1fQ?y`_K49RN8wUzIg2KJ}r?4z>nf=3BQ$`+hve7&lXIzKruwaoIfxw5#!;3 zQKi@wo`5YaHNGc=K#sWCGr&A*2k4}2;ox%G!(S543k6eQ;h!ckWv@WAlk|1T_4GOV zJN%Pd0vtQhXVzU{D`;osMF4AQ7`mj!ZYoywu}F|6hi)UGNsWr-Z1dFmtd?0=5md!| z73>gRr$r(=f0nMbJZ5q^cAj$*%M^IVuLr_5IoSn#b z;#bFFc<$Q5=8l<)+axhnz*cRn<9x-slDPDPcP62N=(HU-M~85<;-RZ5`A2VWdvd2W zXmVBBzezTg(ELd(Ivdh0{{`t>0+(XR@9f(lzYQNuDq=IbY%~ED6LtE=$XQg=U;-B@ zIgm|G*WW6XHP`aC%RTR6J2B1w;Uvwk0R_mwx z%sX^B+BJmzS$2gfqqdSu)1Tvpe2Y za6I&0UWN^tUhC6~9O?tibU((4aZQW0;Caq82oIAhpwO%tTO^gD<(HTGrf)zd0kxqe zOcnjbX4z+FT_l9*U&I{3v|1&Sou}yEeR}JmF;0~=aMD9DayQ#J7Kt^~RVhE6Z`3vl z^3fJm(PHVgMLJK8v`f*M4(AgN9imdo#a)9FS;U^@z2J!PyT|#&H}T%{&l0%? zzUTj6M`Dwuffys6`D!DKAKwIm?zV2P%1!SpsO*fqPz=0ZkuxS1Bd6msr~Zt9lOLZ@ zh_l3A*S|#h%1_&`m2!zLeC(AGdBZg=#8*^}XXu*CAY;ohE)z)9Wl`@wKJwCQM14yjUL-Fg9 zh@VwOs7{7Fe%mQlI&$Q|P zNsj+aE$ZL%jz1O6M)T>MO6rXvgGcI+FK}6u{Mh-Q7wX6M+3B(os>cDWs7j0Ek%&Jk zr6%XF)>PxpQWMSaD!~~An_tmYIOen>Od{B8(Je@tZ%=~C{k*TeId=%Z^Qj)<0dk)e zLBu}73(Z6R|8l1anQ$#z8Z7q5ME%A%KjM_0i{tu?gf0*!|GZAfJ&U<`?=!q_2^3ah zeE?!D4IH~s@grRi$xxHGfsw%DE()KX@Eypgq8WQ^>>;1EhbhyR{p3ewoF$SRyA5qp z;igqiPt!F!^LFsGa~~J>cRcX$A+KwpH9jBdFJ*8z91E!DJ*Gtl9L^Y^&-#hE_U+!$ zwHpAa!u2!Qua?9?9%`1_HfHzzI)tpn3?YY#3l_e2BKyj0K7_0Ti;VHircpKqh{DPx zAK|Q=7@LSJU*>liIlx{3BFLNyAZM>XuIih_RuCoRk<9pf^>Y7u;zP4^3p50U5W%7K z8*1<8_>Qeq!3Bqfk-2O-e?K%8t_Go-;U?R`W)bCB_ak-xu>oIKCd7)c!))_$Ejzg` zNCTE0x=&ai6tBw;$;-HO=A13nfN%USgw|3hvY4Z3sDok-nd5vuFrI=t{ z*3PgQ-{OP{I~j{mJUYUjTFXcC_25+-WNHdNyP5B<9&oJhyPgjydpMj8BgmcMM&iX| zJv_`KJ0A0S;Gup2_Vw2AMaZg*_G|^G8U8dATsX$j*Z0`bj~_RB#@A|W1y$5-DB2ht zmG+@BTvRrDX998uEVbsp^3)Q=P=m!is+G7^!7`I~;UJwXZ;Px=f5G$s>UuD^3LW?t z=_au+8kv)jMx`{j2!89z1>dluWmERG*~~1`3`aY>FL*h>-K(Via<{$CXQo+!HK93j zTtrTeh>*p->TRSBG=c*&A@4&w@2f-m?D2eetYE~F{OR*q^o6&~WFvb6WAP~heBB4Vp!v$%cvn~lokCumcE)mg>Q3=ev56>81{jB3+?$fdkU zG%uV|$i(*`l~0D^BI=%ceLR{Gps=sIo)Mp~2s**N>-P1%U||}+f0gH;w9`EBBx-nJ zng_F{(dy&A70FV&fF#Yvvm3!3)DU9{#D%G0y20Huvd_B5B*NSv*i4k=YlOu37S;Xa z={DkTzsa_WiA^AUTqDWoGaw^9)*NXo&J{hG=btr1l}MOQX8s?g15g@*7d~*IN?6uIH>hi# z%9SP&pwoKqox7-ZO5EYtNlO&2w2%wY!2_kF^gu5>XK{BuM4k4nj(i1{wQXl1DULhC zaocR8pH>}`ahT*`P-C9W%SU(j3!}<+Y?3GD;FzW-6iDt|b{8_RdKj7iM*5_PM$bkq zQ$n=w$A9ty+2_yD@`9;QSO1xho1u)LFs+(?o}Y@drogFN2XRFFT zwd9;Hz|W_Kc!oOx*amQyUOZLG4x&y3wSPc9e1)^kG%8BXD)TQP)4*oaMrU|A5OL=z z3;9)25=Tii1w-Y|33cC%PSU^mvJu(>ZSmzD+{j0MFj5E$vk1R9Mu>bR&jO_;6&nbQ9^0|G5R-RIOeUEn?F zkzthbqAM%EibV6cTA%9qMSOydc^P&OYXX^k8rFXWuKI$QONcywgQ|OB<(L|n*?f%2 z)dX{+01!PYs7ewJnmvhR12(VFC13TRC0JVX4n7$KH%&-sxV0IHjT1W^0i$J3z+kz*m$B>lf* zpC;8y_($bZ;lz)sfniXIGBb@OTiXlZ#XXjd-9$a{2@{3VmvweFoB7voAU95iy@6h# zSP6gJ+F}mWi(RtGaO*`r*A}V-TCD0ioymb)R^EF} zVO_A-LHZ7)Q+i8dbHS^(@0vRR>!WeJVHUx_OYHrBzc`iDdW1LW8-h z_I1-Bia$=f`|0GWh7eFjS;u}OUZUAp4J{Tyf5CSlYc)v~dpQqlryvH2$Kw#n^uhUp z@m@9=j&_XeDyy;O@j4&Y8lGKYX-#+P*)FBw$^=+Aqq zgS<|FnTdAW)M3$_Imjzbb?s@hWsYKvW;hcMY}^`gUdV4LRacVZpwlx5~7$b`KH)&sa#v9uRaf7_=Z|{EwuqY45<>_gSs~5MS1^ddnuBY(5io4+^VeX8Lrpy2Hu#1TpA80v?ythP;>q$Cs=}FUQwhEG~~Ml84#3>gkgr)C) z%qcAQ2gXO*)Qr(@bcq6pQ=up=k5eB1EFVw5Rwb}}zdja@hkHF*uM?I|uQA~e(@WB* z6!e$!NHM*MRBD9Ul2uH?T37~W3@Xb@s?;LN!*6P7+zyIi;QQX%rQhxuN^bq4s|SG41VzAYe;M_es1=MSWpcOEWka0DWIH!2&V}0^X9UR`AOVVg8(r zfS6ikK8)O^8@yCSPH4q^%J{(YCX-+!kfrerhUc*l*^oSj6>aomLpQR(PLzULuOx;^ zutoC*kASkIA5O!y&|lk6az~rmE$Y|nUbuzi(PXLJAWt{18b!cKb|?9kG8 zjF)be^ov>Vf{WEF9d3}Y9GlXPCT9w%HSNl?H-%KL^?$2Pl)9Ym^{53 zK5id0E9T}CXS#AY^WraD?Bwi{H6P-n&@GTq+gq>i=x*tZ1rD=3B0frG7h7RBEc-G_ zkQ7y6EcJ(6;V17O&v3VytD{3p1*R!Q(3%re9?-F@^1e>^IcMyaDinx3H%Y_(X|~x{ zKwpQl-I&UI^)CdBtu`>TRfJr{cCAoqKbwsg2Dngz$7b=@hn}-At|;z$-fU2SyrQV@ zfNES3W~&Yrw7}PqjS!PXv!ks+*qRL4YsoPy_Wk@_wAFlC<@84l@}KPXc>YsQ^e9#B zR~qcy`r>9Qg&RQsW+UwQN%pa=d3g+~jU^qwZIZ2xFNs-q7RmW^JI$B2J7@Czcg2YLPdJe@t2MR30_ z7^DE=(F-NSiv6vD5~;eWa3*snC}32s%PHt6H)e6i%6K(uvClf@Pnq-}k_X)j61Vid z`)oATHqB1ZBI8B1KffnZv^AXw{j@_2Xh)gQ zLM4d%I4n?a%#=I@#5!R2$a*vZwrE{p@vH3iSgrJY0{_8wsK<9xjJVBr6y@BW%u#U` z`{{PROKh-Idfhed3#qXziR45rG&Bb8rzO_eNc=)m8T@y;4w~2|XnCk$fz)r}xD53n z^VP8oGO#4ZV?JDWS>uk@M(g92DnB2KvP-e&6sjZ zL6H&-UuQrT3il3E zW6-;_!9JtP`BI4rfjGG(6$9=%GGPhSJ?hq+HGyfZSWFRSRoj+pf|g0(G!!4s{LA%u zCvN~T#0usp%#;x-^Z|g%c`36YVbO=%Yzx?xG4S}bk*_}GL^UmtUE^pIw^n(~yFyIp zn{RH9Umu_Xkfk>XbxzVusy}dvl{3KziQt_2XK9{rzEKBm;2WDWocBC7m{w^XVd6}k zAIoE{(2A@m4~f#G&L!P65AJ~4EPBgn93RVxCcg-(GTRo_c|uMtj8Vu@o=v(mc^mfF&`HQcTXlPnhdPglH%jeW8Xwte+qqLOP#lNU(`X9yiz4!H>u8z zBp}k~1`cRbs;(dpyZ|ojXHTzVBMl5?V((~YHxLY({B^@Qwa+EX(#-5p$>_3tFyfav zmpp@>h`^m$LR~oz9Z{h;LqH=c@RPm#91n64Dd%?d|MivJI{ZcG;cBUGd+~$t!mvR- z9KafF{3Mx(8mTMg>Cf2Qa+`U~5Nr~euSnziDmJScZ^I$iOa#CMz0jw^W(Y(R z-$gs?Hn_lR;*2jEhXZgA{|zqbOTMV$N_VK;XcW`Xu}aQ5UWv&aU%1v*+T15D_1Y8p z1I8lm;q0qSaaF2OaHYiYB|ueaJF5dQmtd=Ugkf?jwo5ld@X^^A^;qVJcJENAigHLu zlC+B%YlKVGg#Z%lIX|@P2C^?e@1`Z*`JyF*!KWQ6fH49p^$JItxDIT=?-DszV^jGa z3RxZ0a#Ud;7a|GV>zJ5iO_QBs?>A*L^2%E3d&QU8_Q8mmXSeb8_)WChK?J|dp@zQK z#F*!b6&!43zJpE+6;4KeC+b->BLR)9%$ns0(^~1~6JOE4Uq+93?||v*jz+(nG14^i z%>9N-9rCT6ApXdMcIS%wWNChT?z6HZOH?7Rd3HmOu$+Hmf{;eIE)p5K}%~% z66#e#*RM$gd4wO^z!Z91+#zPC{{Vk4VMia>Xm`#WuQU!K#fj}vaq_S9OsqwEvK*C-kL-q|G3&%zAozRS zW7T;_nNO#B8viCoESlpFUR*4F)Q#q^owTlVKy-p1@3E*bRWRD38{01htmmoP3xHSq zwIIOZJN2|YNAUMqDgZ!6E%EWBNiX_7x{KPU%40pGBt_MHqV~bd#R$c>&NVPyIPfmT z?jGibz_|#T`S$Lrg0?;m=!Lp;IVRp!5?yKnedCYqLWdyw6hs-HFGu+1v%70D0HNdQ>_BspU~2iwH+6g`KoMe+c5PB=2|MlL95;=UnX#dI%(xGM zC>!5}Lee0bAF+oyE(Ci2NSxupHQG^gHV^>Ypfvwp;V&Roz`uQt6&+XZC2gMy)jg(c zbVt(-G|o%~q)9+-e3Jb&GdW@%b|I|^v7Q%b%;lTnb|JDp#Oe4ZkS|71?R-Tl-F0a?U;NmL-|MTH8o zai{Zds+XD1+cXB+ZwcPERC|C!8b5!QTv|)W=c55oLjN>L^UOl8+!_mJW+Fib-15UF zdHO7aT!Otbn!|o^U#!zejq65|b_B`Ks#!U;^!LX<-@E>X!c7G=g`wjuLTa#(?c#0sa|hV|_Mi zzwS){8lwFA@TVz})l};LT!TE+Pc=KB7rkO^L-(GA75nLKIjxsDC6vdQ-fpqtP zHA>}>+)$^Sp_g!$)F&9=;iPg5OYhXlyB zIuw7$nlG_LfTmK1o$BJIqpXSQq3Cz<1;Mf6H2gRjJG}9E&S=q+1n~2pEB9EI(f{L7 z_FM8qF%E(uu_yodT$nDNAP`ed12F;k0nI)iIxETYTOhsI3GvA&-7$lfyY1~*V6I3P z)XlF?tf%pq>`oMG1U+6pATO+*aypFd0gr$~?sf1)Q@Nhbur+)jC0SlC&Z&@OVlP9A9p|=$9D?J7r0*PTea#Lq z%CZUubyLju?F+&CcTv8bFl@OaotISqY;)<)NB5Zyoae-IwF1hH$yQ35&l!ar?p;qM z3AQmisojB(BT)xR4UEud+)~_U>{qSpov_LqO(Gu}VGmv|;$FRtA1f+PgTxrgyeoyM z(~fEZlID?=5d5=~5)dJ#6vPq1iKO^`j6O)A8Z#~>2+$P1W&D`f)&3yAu=S9ZZbdQXUJ_Z_RJOw)c^iP;{~X;l?qBp87bCvm15lHrDAXS+K$J!v2)cEy~CccB<5EX7XOuGi>>&#!$1>7zBWM**OLWyi!Rgb}++VT!I~9IRY)NB&tJ1 zs|oR!XXm^1?mNI>0s*r67Tp-5-r%wdz<>MvmIU@diOj0V2E)ar%R)){rPIbG2^(*{ zi*ew3Y2$Q2KXXUQ7x|nn5Xx2=gIEV=YwM~n8`Y%iD&R0KrcLh`=c zX2X1h2#rp?u=6p+3n+a89Tn|b9iio?_|wZCzFxo!xth$d$4}MqZ^J><&4hgFYxafs)06%1K4Qa$qbk4zE}Ohu4K znNrsquoL-(GJ^W%+Ru7X;A!f*wzyGb4zIH`%ScNUDA==q=VH>F`N~UFK0*1iwdVnNq@6Gok&z~e?JSzd-H=EA*g1!^2C%;F03xa>esP1wI_)Y054 zm}aF7FWHC0E&ytf!6MCO@B=V8e1s9~omN6b)yzGq9kb2XvkEFU_JG9iPuDeR=CHj5 zcuTW2Cz_x89wi#5AvJdl9JzyKu`~m|h%T#pG+rNI>A-yX4aOmwCg_5+AsMWykWclWI55~$h@2FYl`j2gV*Lh&$O%|lM|QU<9XTvW)({(NephR zxEa99X4r~QN6st%R6WbxM^*$55Oy;Dh?=+sdX85%gFSm~o$uaZ;GEIcP@&FgLCex= zFkLSD0~qRG-50?mXE3gi48rOm!G+TQg-8pX(Hv(M1%HBvb%=~5^f=soJ1aT2A1Uj` z#(>B}hd!dc_Ce?XJh+{w(kcmjm?vSOHH<9`U#3N8wduHl?dt<`@(hbsm*(G^TF#bS znAFyaL_3cF1mDJPPS~p6?9urLbbcdX(^oe8lS*s1=-f~E9nx6O zG6`hTtr~ZlH>D8=(drp1SMR1jVcDCEm<=6&+$I0u|4w)(xW7Ji5b&>oyI6@-92sM6eOX<|M+Bhbu`Ax`h0jqHLk+rr;o`qcBPq+oLdb!G{y_be>n9Jx zhwmh$eFHJs6#~~QhHHA~^j|FTp^bImsFsTS<{o3EWwBXJ=Bqs1KSzdl$!@?%+?Qp4 zM$g)St$+dQY)%~_a3vAovrF3!p?A{{KB;S?lTc`dl}H%7pUf^c=g+$`EUVOHVz;|Q zEezVgDS^}B>c1o{4J^()u0`XTqE9=kC+AIlr1c?<)U^M?;pdQ<-vY7xzQL|@m(H!x zGW9@w!E=`_qcQ|C&b76{tH5G;5@;nk>uk?KBPN)Ap<@(UU$`kG^AH9=%p%gtG6=7)WyY#vH?Ue zLZL|{aA#?U3kwIgA_I_L2rcuE<9vIYSA_uT?2*sv-bwZ(|A#68>8Oi`0S7KkN2vp| zD}DeF)G|R_kRcfysU@lHFgfPWDsKQNf8!@jBZO+kf7N3&?5L6d8 zYQu*=O2AM^O(GIyCd)-y3O}fXbZB}zaVc<(KMxFV4>Xei-8+U5)aCEO2JuzTot=m_ za}Hcw{os^KG!1V|L#kHD%x|3cqUy%h3jWjq_Ni}xdw+QY2N&i|* z10wccPn{;i_)jRGRQdRy9UDulB=}eo?eq7SIa6OeNYylX*c`7=^ zr}o}S?Zp9IcO*PZ_Ka||M0Xli_}}(4zP)ZJ{`dPwYq?%6d3I05_xnGzofC040GYUB zI7r*A=ZSqHB|S2cZ%FKOk3ZXF=zh$qKTV;cJVXb~3-hG*WJT$6aYbd?-3@rBroKmaXcKqBA{G5{^h_>2q!1(q_={!=RNy)=@S&!T7t`TE2wzAm; zD0CxeIT6S_yhXYGbW_0(UM{YHEr*qF&5BB>@HaJ6Hhk!9pFmZlen5H14~0jeH~(JF zZC^B8l=+k4{b#A<~%R+s7L^%5xxZ>I-RWAdQdNEOr#h?|T0!$vvm zllhw+&ILUXaMV4MChzbLhSP2UR(nFO%)CTJEXzPF(V5Y@Mn+?LZ36|Fed%i=dl?pOB1xlv*P!jb=L}sLY-gSsbT*fB(rAAeslQK7MLxLsA=(OLB=gm28QzO&ebbgBk4y^(&DwFS z1Yo1A^VZUG5wD;0c1=g2Ty+HSPV^bhJC$9ha@=8BKcC93*mxgX1B3i+>%M;j*6*OK z(@6dBfmmw@)RN56qtx5U|ftw$bNwS${#8D`>cF_dd{I*emU7i-z(YGplFLhn^!auM;N zH~Yl#hbjKIp95o87?IC0B31EcjLnNp%HUCEt-jN3ugc1u4^UqA3~#p54d@t_pcgHM zIubqodAd(w@v_5O)(6LASGZl>L@?zig>EGG>=mmAw8%WLFS}tRG%G{n!T}*rlVN%n zg=2HkL35;FjtLL%(CLcs3-O2v_p1Y)Q?XqCE<(6bX;0!wKry+*HB0`K&ka0$YNoD= z#!th_?%8DLx56qUrjy3(PvtR`W_gr;nA}i#Y zdETJE6Mr9UwvrXZR9pNH2b&KYVPt~4MLV^b2I(y^`(bJ(TpjU|DV3@Ybat(T{= zchVCH!~whf4RA8hSc9;y{o{-BD?yb3D}uu+pkf0Mphj{-_5txiq72AE6&k^ryeiD- ziS;f@QP@4Lwc>t&Q+gh7M!AsUF$LKVzoPn-+gz@`84fGV9 z$LZz2aW$6Nr++cd%1Mb|crY!24&gi+;U=-9E?W0%yv-GL-MJ4o5XP3EE;=xq;(W0( z5Io#%-*d{xi7j7gWx>1?hGBiL`o1cs(tCNTwi})!X!V&>RidBTTI(%bAjrg@+ZLm+ zD&6RStuD?bcG)KjKP7uPyx{TA$>Vo3Z5;8UnZRbO`%40l`lBqb_xneSu_A_nk=PD! zj<~c6x7XE6w*|x<|5*L0)+P^}z03w%p+h7U`leeElfCX-NA7I{)BblWDnLlkqM|^I+JnO;*$g7PWg)KfG+_rS%^an;&3V!}xM8->2sQcm3yU z@B=2x^IU!Xr=(4O6pa*pHr{xrvYQpdf%sbYzy=^S8W$xRxc#Uu211d^-ymiRwLrKU zaU|x3ABvjHO$VSMg&>;43T01-_&?68$?L7BJP{UNO15j8U+F?R|Ygm1_G&s|-D(PZ@*d~0uXwCD92 zZgI`|(||2~gnPvVVl)GX*Y^@vUF|insKeoflO__Jxk9_MiyJ2u$d#`Q!;j);^c z%GNmIt^HY}VW2lZ#xjs{!^YGf4rk5IOMtyUIjg4K*{A@L7q%mNeaO0ilL6Cgw##p^ z0*#Se!o#!j@d<-TG_rBzIc|O+eT%5@kZN5bDS>msfcY4cOUen3p616aNtfGLe6Vs% zPbkwyt;uF@SMYbX=}JgZ-^uhg+Yn3aVLmJ;B;;%FCXE+z2CE* zSDNTas@Zll=e{hN^&J)zGvDM+V+46Yo z(hKI4;H$n+3w-aUQHEzuW6ch?xepM+a_m;45N^&)sQ7bJ$>%T#46Xa?cltqguvE9{ zN1qeTQexymRlcFf7wHfDl^gvC6M-q&+H<9Fy4mD3zX)B8ml-NuQY#zH0noi43bI?OLuDWXxiK zvBMumMA=e~MkULspW1IpADXm;!Q=BX%E-Js5{5c8-3jLS&ozH{01cboC8(YO#EY>F z8GIi57*C^iTAwuMpkB{b8q-=fs18&+&W>hlXHU!B!4cg0xl?`}EB&n_-u-aBNBo4d z!mPkIH~l0?5m4YC8rUduKGzq^PyQCDn2z>!ykrDcjj73bfHK-%c3%Kf&wHIn>R zHb74|n^90r$&jQ!wkXdg)gDx*U*&qI5)l|#iP_3x@R#eQ|Iw0#_2g;Euhx zUaUo|-)W#%=zKWKySo*ZqjD(`9XbLh15JrO;Vv^eb`GX@1dx+iz_QgR*Qyi~KypWn zBh2jaA@spuaf`+62?;(MlFrOGZ_!89+Juq_KXL8$C?!S7$o)7CA??)ujA-3A=pm77 z_;4=eW!gtm40Gs?b2ahgsR~$##ja9JWA=HB_f-6J$6m}w`|_~fzemq*!cN3D?I!y3 zdZQw7_9d*Leo5gQuL${)kqb7Np*eVJjKuyFc=M0Lf*rKc>2JaHQ(I??z4l#O5%^(; z8?SXw#hmXOh3Qg)lo_!7EMJ<(*OzD@pPwBTDZeaq%PX9~y#oz^8XnvZ<{d6ajib9g zczm*%5BAL(DeDWwegii57g~O)0>SpmaLL+8pH9+i7C&t>Zd==;-w?ND8-YBG!P~%% zYF}P&*uL8Gwt#XjuXfw=CIA0aw2e`nUI`4|WyqHG{N@YbKrMrh`KEp5Q_QHNBGf2n zzI|q0ty?rbhpGGU$%9c%6Gr7|>4?7`M+1A#_^CI9+IXoDO3;({t!knYH8UYO^0IT} zM7*$bR9aC8{Tg@YgGg!>mP$B$FbS1e>BOh+j?$vpoEdg$+J+v^jR){duRp6bp?h>E z?a^&Cj7nx&ojqy6E`{+-)`Pw$7=NGbvRplH*E1r7 zX{j@xU_YdSY=Z$XcFZdATpuXc-uZy|qL?hV2lO{Z zPdP(>6;+t{LKglN&QZ??vZqcPl#FHFxZ@giRCHU6xQeI&G&mmwy#o? zH^D^rb#4YAG(8H$iTxo!-9&IF6eyXUNXLg$sfkF%BK%hCy>wO~5{TI4(b@;|fHzZ% z?52mC{#eQjyi&FnRH$Yrigy>-Yz-1q0E~GQ%-aHp)M)350ZUPN<2EUv=IDFiy^~XW zx`YCYvPZmu+B!-;l{**MfieQB;IxIr{2oi=nT=R5=<1y)N^G0yViTVeWgSmuZW z3E>9ysTzvc0fCjU>Swz%!24=T5<_I-IVAu$frb^;Ol$1jQ8*AZo2}qmd`u=#lQ135 z0U-wiu+^=O9kPjvo9WNl0MaWdBPH`08^b|XB>wnprz2Xz?fEn|(Hdk| z5t2I&`wL-=U1dYkS!nJ7JIV+IT!nv4SD%q75Tg{4m@py!f+zfYr zk^@I3Wby%+P#XK5u$t28DnjcRVZ@1c$*KhOrcoS1Y<7R6bl?<9M@ohKR(@6NEmtMM;-q|c)^-*sP9T3JT^2Ak-7 z*=VI3rx)SmX5B!!jXKD9Rsb)OnSwd< z4Ft>{TOhP)Q`ip?*tx0OT?CcFkaKDIQ)1ZM*gV`h>As`zg8k{+qGlWjFt0m zWJ9T5FDvxHO+@$uy`L!58{=6>DQxWPT%?RSI;xuwpgE?$+MjEbeX<>p}cNWdQs>lml#J4wz+D`BYkI z^vkZ&-MxZLp0(@j4wq(``SQgnRj!JDFVKeF+x>oSuMwa;yGS2VR!WwQknFZe`6_Q! z&u<}jNaGY0PqKDOuq^EGl{&1uWQq4fajizCR?h3d!F&_un5s&`=AFbpRhL^gDi-?) zQlIM`9VR|cgUj@DY5wK!VFdwDJU~E^GeV%y3A^|}Xh`^U>t;Itf`vX_iz14q29kO!ZZHCHZ zqW%;vFuBg~SJ2mSH37({v6J2tnK#C9KNo3`BO9QZ-)F)!3>ctqD)scigRIxHJ>z59 z2QK(e9P@-;b)5UR9?-d63=-a2wJ`=(N7X%>DUjHWO_55_i+5Zm|3H5{Huvf8VhQHW zEo|nebR0dblVX~9xPNA)A{x#@0>>?jrvaq0D_(9YPh;F9rTIsCA0G;5I^RLc|H!s0 z^uxWJLZG5vjbCTY0>yh%6_6pf%psyi>JBKbcxJ<$pHt4Nq#UUpVbKmL%`LL1A;1n| zAy#3GxEgxiQ=lJAs+9vp9vz*?2J=k{xt>YKq@p7FXxbm;)#P#Yo&RTxN>rcBZuUdx zr<$Dq|Fx~y`|A2YKJ1x!XDg7`0v#KEfVyeLvcNTFYa$#Sabs zgQh7HDo(WhH@8o- zO?#>J6ZXhN>MZN>EJQQv&o9G%q zI$5ZIYK%j`zFYQvw}1jZw&-JBzVe z35`h%QsZRUowzvQciZ8ku7$bOIff6XrECVNZIX7;ec5#%XYqD`OLQXLdD76mNpaU? z^|G3GI|t|TLvP9&0=g}cgFz=}w3KQ(1{jWP8V+J;t^CyhZkvz9sdlu8^&R4mW|xRw zY%vxzg2DL_0ViSpcPKRn#Pf9xFsEDcuu>BGxg$_1s|2luc_$?*#8kWibs zsm)(#*^P#Okm50drY3IH$Ho01rV-(F6>UM{#X*3QOD4)Tbc{+~&3&*nw9h7_F{K$0 zIC4i|HL6uxOR0ueQ_s85$aOEwLf=7&Bfe|32pcSatfTwnX~H>lpcVu?()ZPQq%56y zWNzxs6DO6T9uC(7lgnX}xh$l12qXL42^HHp>Xg5n9i_YkGQ%l)^qzoeEQZ+qi^?W= z*QIE9Ey_0)JplMuy$Eu(K$<%b(2aE3fw2Ry%05G=Gju(E6yqST#(s?82o=cOu91_4 z`%n*bQ6SGyA!@iY#`SaK@N16nUx`xqG5O`q~GWGB!jc(w9P{)})G z>DK(K7hU_9uEAnc`p&mJfzOiVQR5Kc>FE=%)DkMX840H=La9;Gzy)cirGgcBF_9@ghQ~`fb&9}zC8wWoX z4UD+>+hi9M?Hu1GPDNfx;y<+MJ4h7`gn;rsVoVxjPE|5x*~AQq<^Ob7oj3Est)~KU zQZ=u5sP2o~{Qwv9bCZ^^=h4mMYFs`eA=qVhcPgMD0r`y;c7QU6FzR~DBHBBZ zSBqd8vecDX{OPDNv$;`2;J<(@Xz3#}ysGS~62kp|lmCGWk0^RZK5=+vKYP8usU0Iq z!&3-)v#ziahpez!akt#wPKKZlAs>x-7A*FB*!XT|?pb}t0Q>M5=Hrz;_@PQp9)F1U zbwOB7LY#V$^{#1Q3ka>ZCmJugG$s*n&Op>E;U5Uj)NI5JHyL1G$^k%#vIi^$all?p zA!N&4Kr!Z=WU#$z#u04Xcjl^7rebd1|6;sHvyjzcnjW+~%8?DFh_-e^TQA)Eo3!if z>#5#6RVbE>a`xABPkSDkW2!zbEtmRUEPdpE{oAKh8O-FosGi3a;MnElfFYds52R4F zrVszCM{9pwfme(R^=Ew*WBBK&$8dGnIni%K_na z1kL-h$`{<~ZN2C=ODDk5ARdJB5Z6WOe(`mZL+BZCfk4@G#zF*&5v$%ds zmrGOmL~L2|O~MU?wCbu2%VVwG`oZl+%ea=uw4q+~9wU<$OPYZ9G-=@+()SJenuMJ8 zI&Phg^tP}}LKzN>nlILqbWgMmClXVX0^Z-! zkg^oguPxF8U$}$f`%bdos!hM$P)tD?=r?ce2HL$au(jl$?d|+Pz$C}H0-LgL$B(bf4z5ocLn(-A7ay6t;*Ai zu-0av@*A;VrF1ivqL0PH6NS(ugvI*{r;qZjtOK~k2)^(WoRVHA)_P`0AW4LeoPd*4 z>H>Jw*O(7ojE?T{>6=RL{RlZ1@X<^sRa?=vL$jcGBVH<`i2Z?_#*HIgZ4UQm;Drm) zD+EU_|_$0-Xpw&8_?A7DYHf#xeES(t}VQC+H9XE9%q=!M`(YSMCu z-TfaOetTGCy!jY^Fg-Eth&Czu-E^F529r}^y`_&~1p0(+i3u#`$V`{O>Y&D z@>iaHE##5HI=2zEik zOShaC8Y+4_>?3K=9-~dQBaB$E?@8XYSG<9^zmMG!H$@ShT=(fmeY=@FtpaNMD|@!#|1SUjGuq zMHO;U7U`q@8=o-b-0t<8?*cYjp9oqB7L{BFTj>1U^VItkbqj}-KGrNIkpOhxg1gBa`bxwaGc5b0@0n1^feRAK5a%V?|2O||k zR$^Sca~LBoIzKz=laNwmG{+@+7B758v3NOEp;saPhO+Ip;%Cj?WTKV-He9^W^?Wnc zA9u+q9zY{Di4SUt2+=D&{j}$m_By>IBr!qzFc5X%5CQH0tGF2^l|Sgm)ZGWgj!K58 zjvnoZwY4v45f2EFi5X9V2?-@ICI&b}Bo8r|TRKVX8oN5Aw5I{0-Q}tf_Xg=;LtmQM zDPfTb?UCWN&;QgUiOB801}+r5%N!-GLW`3NC~O`R&V4=-am0pA$XLs9LvH2*CK$$r z>xxlBG}Eb1)_JEtfL;2z41>ppLt8hzRhXq-X^?Gvgd#s zk57-L{nmA$2Zx0krhVAv8*k4G9+1^Py2=-=hpHD)hk9Qb`*8^e-VfUJY&zHlqw~kF zA7l3P={*k+@}J#_L%4M6wz6-gR;s)cE)!MBJA=>L(LB3VpJPGewj0FHs=5mJ>AcV? z2UB!q>}KLD&Yg5t0GluR8ULRu3y_67(b6umL|P{3fd{g`=exM4Qpr73sGXo`-*J-JP#jQ2 z0ZP1X%qNRi!Jl=+zILp-nc;N;sg7wWUZqH;$RV+>gRy9$7g+l9z!8n%uM>QlyqH-l z*vEu_VOpe31Um5xH?u+aeuSQ$d*073TglWVJd0;5aAlv6s`wd{wPdN#mM=(81{>k7 zIwuciT7PMc1Are8pH00TDvGHNsq73C6vg&9zqe|a6|Xjw9G;!VdaGEn9I^cjy(-_ z0mC&`@x{vLz;;_vLKvQ$Q_S^*@=2^J*zO9^f!As!tTrxe$NFNs~%0+Wj=3t zTP{lgRasE(lp5y}cd5*RBvHP76wp!9NZ~o`Px)@j=^m4QmQ+~G5LvClTXPwmmf#Gr zImsgC)Vw$`S$Gek!Cp>v)w6xIo&WLj=^#Lf@^zlyLkpYMeamquefjw4i{Gv+f;}p? zhhLHgvwDwJB6hm1k-*O##*$k6)uiR_DCt9U!?cBkOa{eh+2!DU-!LBD1%u{Dp&2xD zLcy-ywZK-6L&tk54diC_HZ@UAswSE$05Ybu7wWXhu3p6(IcP; zb)%6spmISmuNc{y&(t@ZlpS?Jhdq$IK;ds=VHyk!G#2a+g*gbVHwNwkIJAnCmK3Uq z78o*&38}xOw~Lo}W@zmGa%L>zM9wH>^d48ly3T#)N`W?i*n38F=Xoch;L-YZ+=$98 z!5HKY?INEHF|YBfT)b}mA|KFkew^h$cbt+LqbCl7{sWvcPrztxR5~u<87{ub%3Gy9Y7Ia#Gd{6Axhx4ZsaB~SlYQ{Q| z!u)z?mbJsaK7Ir&HKQKm*;x3Or3SJ4^qkIaCTkEb!?50ADso)NLHLq-j^VUiAZuJz z>98K2&&}z!dIda@`Y!p5R0TF2yH4tHG2t~Dv_Vs+Y~jJyIaagQ_vpHk?LvxQjz}^J zREZu&?pWKa4+C??&?8z?D>0R0ykh{KHe_*Dilsh6A5Fh=i{~<`#KhvPgKm1_>b*25 z0Je)33jbPX=f+i4_mNuj1orbPUO^~1|EGHZR^ltq# zSt0Hn;|Wqh3g&;ESoN58A8qx!yRMOJbwx8J8#Slazf}AZZ`ScBrSknNTS1Gx<|Bj5 zS8`YYX;g}hjNm<{w+jHi*r{n&-{vo{XLcP@97~4oZoVd?R(c)F2S9`p`m_TK-?nQ~ zguQS+hZ@`~TB_!{s%w%soKWYUQfm}Pb7W*H-rpfZK;37OH~EEJ6`VLaTt8)qee{o$ z$2d?!Y_e?>onZ1BzozdqL<6QmZ*f&WN8EA#p~>AV%CKHuXT2Ak{|N4LfvvFATpAj?aSw`@V_HuMWq^Xsq5ntDFZyhe zP?5ETUPc+0c*~!bQd5k#F%{egGdE@n30-)MdpvjKc>|5GsYS#!)Y}JbPNqx`(^%w6 zz(jU3j8+6{1MF1!HgdLy=$Aw|V*8nD6y&+s_Je^ z?4>aMJ805}KPS-vGJS7D-(G%!CcO*BgWr1Kj*b>0M$*sBgcQvvH)9Z2l}`&0p16y3 z28Fmbb*8nN!NmVc-9J_@N?rm+MufY|fn^Aob~@aFS_#=$ z(mG^4POr*&+>@Q9<`r<%ort-8xX3*#|4PFQg(2A3GtQ-SZ)XopBw6@m+Y3azUdY4yf1i9oA zBKEJ|Lm{dN=N7?I@0pT7Ww;xg64z~oqfSTuU#^6wSFam)Ono=g>EMkO0=E07Fg7z( zsp67B13#kNC|kT4CZL?eM4{m2)Pw8v!NKH3B31ra(W3KQI`x6m%Uu_xq`st5 z3&U^Ok7iOXIyZ|L%K4bd9=3cF%ZH%i(f}j_J@>TnElx=sdM)kJ3)2EQP6`ug`6&^f zYXj4nXP$&jG=idcvE0_$8BesXsMv*$UkTILITTI zQ9-fi!xV)`=HHp|4N3n z+v-Re8-2!O-hNeiz~vmYjEAzxL(LjzwTd=TDhMxUj}T$nppnNiKyOq6WzsbT6u;3P zgOc7p69apw5)@;MX9fK^#X2mGG3=0&*#1;v(nkPOk(d+aygiD~V}AP3Z*D&Hf}eje z+Fzo_1#&avU}%QV(rFGSyco3^;ADAlc+gQn;(7G|hvUImUG~uDW}q1QFJ}CM;Xnk@ zQTMtEaU^L7hyiyCBP-Ke-nDn3t54%cbBUj&KV?QP((v5s+v4wL3iX(>;+3okjXeXG z`8))O&hsZFNb@}J@`}yTBc#A-@B|`qkA)~nEM>q_ymDl#~bjIZ| ziwIHLgxK5EnhV)Wcxq2|wm0Uvol+?T^MYReXSCKRm`xS z%D&}GaAN;I@lE_)4<00c%AZ&_qv; zh6msEApONn8SjQt?|L8Mmj?dR8LS5jW@fybRkS4~&oWbYO7f*wxQ!-Mw9x*W3uOBt zRkTr4A&Ju=66E)9Hz6nuDC4Rp(&uZ>=DJ|1pg6;;FXLwVDs5&Qd$NfKP+0n85>chLV!Zs}*2X|3Jp5PH>riU?~$ePfL{&1%-iqs{vxHI71~m9P>wRtk6v zvUoK=f5)+m8}R&pN!d|fj2>ZjWPIgC`7ojtkc&&6L`I0Vi^gpL149Va!OUv>Do?Ju zKecrFkDBlCxUs3kqZEZoH?!{VGWZ9nwRZ}zB2dp-_dSSOGX@OglIn0L-G;O^^4pm& z6lwhy8R}x{6CHk9PQD9NxeCO5@RP$QRSMsABws++wyQWqQ|NnmKY#Ndu$>Jqz~ZK) zMZU>Hl*X2r!@oSd6s;3JJ1kC08OvcO{)?%O*ebW{R!U4=l}z(Jk@mN(EfJ(?`yN!m zFvxaUR#43*Ge8gMlfmW3+|=3KNVL!qJT8ZDPSZ?P->158^@MIedMxiz2I6ca*SwBQ zj<&Xv**5Mk@4qZh-yc~Ronu|`xM0ca@RfmD0?7hUxcfyr1P0Vb)~HPUfxSv+d?QTM z0g^-R@-N4=b+iVyvjo#o#*OF#GocgafewdjZ=p4{+2q!w{~@Fb7L%zP($)By9POV! zM32ieRE}^1y-}UM@t9rk!Ng+&h3VAJLbv&t6h$NhhT1aMSev3ADE0d%RwU5%n9q3~ z{_cU@f#75k#QC}S?98g^Ssg-g+zAIz&;^MQ6$LGh2tL&e5O-&DVkbIqewD;^&jj|V zKTICx>0LXXm@l6JrNmrez5wJ$?=IzldowEdD3?Q1|6VKh!T>u!#J_nrYo=8vv`YQB zGK#;kMG!#)KZ^HVcH-9rECZGk^bJE*_pTy4uq3oC!+yjEz|EKm%W+ftEf~N!BV5E? zR}##d%J@~LJNM-f<*_7;oCg^4g5{jubd&zU&hkJNxZ*6ZI*)Ab4jjZSsdmv1ha2Mq zio2-zCNvjO10_rQVEdeOr92h_m}`Z@yTJS~poe6q zvO-Hu;DDtCx+phQuEFPmb2J3)g!YnPH%@sR59_^k&oG8z3 z2cGh6neDS`_|0^tT@b*Ti8_7hE~sKdH}UrK)h7$++ydgF>NI8Vunk$EZ)aD8zisZ8Z5+-doMt zHljiMKC!?7)tVqyd`j1khLAXIF>d!rx_6+#clDENiG&dBX0xJWkAss|M{XSkzDPN7 z`~QbPu`}hqTD7r6O|XWb^ATs}aTI%;m$m)4-DbR#m7qyyE7+f(?XmVMnht&G(AS9f zJ^POUw$h`+dd5D^)x8Gl?`Ib3Od}NbU^Eusb>m`IaDUjy3*@(ZDa#5tDbGgmiq4j< zKzxsy?&ME8%q`77Z|tJ-FiK5FvZiD<>Z=QbG&zM4pg{(pP|w*gEVl$=UcYp0vEd`d1=&; z2W45ME7)nJDTtEw_eBuTvEtnZFTF9Vm8DT;-K!Af3-tA*d*`7FTw3#)*l>>|c7Gef zu51q-!dE65P3B94)pvzx@5AZ3Z#ELS)p6x(lz;(#z`*W4O(#jCIG@|$bFRocY0S!d zV2j6uEupl>xT}U_7M=*FCoRkY_Y8lB=ltKyYC>qyC;&0z&~j6Ag=jIpueXbM6M*Yr$A)C+_c(Ze7nP$3 z0t$b2&fjveLgPCZ%R?%6oC9zRrrDPrmtGokiDRZ zazAgmvhoIrlRai!vbus6^HORL(*b5BHn4C0ehJHv#l&u{9il6dqw$~4|Dez5#0pS} zDAfckvr?^nKs*wU`5}PjyP988R(2lKE#Ql2@IQ|HTyFz>ZHN*Rk9fCypV@U0VcT!6 zHcI6cEO6a=EmS04r+lk7ub38YuJm{yv){Q5@(RYX*~&ZN(oH0^;MGz?Ka{l{Ox9{# zOochCH=n(>+SbAfBOk~Nq814{YSK9SDs!t2Acj#7mRoLT)4{@{qzd0pC&p^2i0;Yc z+;_)oQe_>~u33oF#SXvdzDqLch^a0gp3s2Y9dr%a=zR%`?e*Kp@9 zF=IU>e!*Mn^bH5%wMNdiYw2kJhYxCh4bk6myr&o8=z~G~vhS32W>y?r^kX_o_0!!V z2h(AhBFuO z>s_r;gZirkt{ycZZ_dZ&{FSBG-Ob3jAs*N z5M5p@D23FB%stjCg_9kE$BjoqOCg7bSt4?k2CS{lEeC8l2{HcbV+5#aa>dfOabU28 zyNYq^^0TO5_OjIGO4}p-UNC<*41*FR&tfmhcpn6!Mx*K1CDYYId8cN0uq7WHZiXCABlTh4pvx2a^1ApSGI2^DWGbut&Uc^J zt;`>B3^U6NVV(YYFgLKTGFQrQ^=+L}y>o6$@49tmC3c;tuYS(Z7)006j&oklp31-5gFgIGN7{_YSYjWj zlc)hqJZV5lcDV=;P~rwHEWknqsiQtW;{L&((_5u!mYJAL?8AfG z*kPk7_Wwd$S~Sm#91=)^KI%uVG0Di;$vFW(l$|;Tz3W8LYM6n|Ex9;Bx?fsu=K$MHCqWVG^=y?CMz|>sffijP{o| zC=F^=8Xz_U7{C}iH&8)eE3JJZpZHKW&0S^dsWn9f#rN3r_^eUZUB83*>x0#A(HW57 z0vENn0|d0dZ15$^&D7vg!B4~xi8s(Bk_0qfo|!QC%F0yS?ySFt33UPhdx4#GUpj@U9UWVzPn5JlNN8=O0KAUien9XbwkZIz9y=YU>SA9^gK1bCG15f)-@ko zxC?8k>X8*}Zf3Ffw$Tz(?I*dSscS4?S0;ujmp>W{5XCqjz?C(R0- z8*RF=?%;X_cscGi?P(fOYIeKZ`2L)k#x(kHeZL9L?Hu&yO3#X?qDo_TRu9M)PB;{q zZ-b?GoImx>Bz0vSq&fP|K?LNNTZ=c!(OdSUBI+?=C0-#JsIdRa4|7}U!cyOhnE91c zD&-hQiJlj&DQWHmNRg#UWj^0Y_0~h_}&629L zi^xB$%OhSa62Q;0F~7;J7NWFZRk{Xh#kX2{@)x7{kVB0xzEI3Xt$%@L&5JvoiLVg^m&%b z4H{eiV+ZQB@yQeO%bgb!|=CgKPB(agom*F+<13f{sEJK1ETnMv}@ zeK6HX(;M9qTYd;75flds(o$#{#QeJ`9S^(T4MhO)iEKvn4fvaS4$@E)$^kcZ zN4cq4ABnH+sGJK_!Hz2%P)fleUYB5!{w53=sd>y;80$Tm(D_l3O=VKd`(v>FVF|VQ z=9(;hwp?Yn9~~ex|C*_D2R6%7wIW5rXIYbJ>Frr`k~TqlZ3o2d6Anlw_<{)RM-)LJ zuU}>WL6-DyP0T|c-+l;R@|7uW08e#}eGBVRJ{c?RSlqjvr+%dxtl4wo64vg=l9r?tv)xS~c~uc3)Nr1byTdOZD`>$zTv z@hnmI?_RRFnqYN1NI4)-H5|K)A-|$o?0$bIKhv$tTCWSPRoYt}6-m=Mf{s2xf2200 zCDOj#W%2fh&h~I}Lr8xLm=$GPfEYv@Tb8uyJUDv51bZCueo_8#vGe1~)bfnQklaoY zCi63(_tzVywR3?TE1RQsLNdWce?%1roB6l+Ub4{o^%ZP-Gd|-GP$Z+;0J$Rj9~F72 z#`g1g-S<{04GIJLW!nqBPf>e5C?O6J_mt;EmRuCXPF>S?KJwfh~ z>`)@vG|oQNWlgdKNa(pGf+B!O2p;forn^Fsp2S5D}7TrM8U`lHd-lN-#WwAew~ zFR{~I){B^eJNwjXu{MV|>cgP(%N`Sl^5Yq`#L^a~y2O9dai$|LxZ6M}-riBOnqJ@c zzYLS|F}JvMr8Z3(<{>K)a*Mda68%5EDSNokofUJQE}i+#Ll(hnQkyEhp{Mm-bK*;qdb4Bxo`1cBGG{VFaC>L91y`b)&^de*H!HQ(4#@-8#T z1H(W;UHs9nvX#Uig3?J*^_5vK3(F;i)gUg`+6h&W# zvS&V3J3!>R0ZIDJu+tv-z$y>00E_;=3HyGbh*VU4)8K=?Ss(hA(Er&1yl2>$+89eO3DtY$vFRE_&``|0U&GxYDv zEY!BZ9h00#JE(M9?XkaI0WwrwQR+UMt8KE7zefn(1y>DCuy7^cZFd|(hiO|}w`VnK z1-q-JA>yVnCTBmhuoYlrjX}05>EKyL4R%}vc0;H9+2~mnH1u=*#)pMm)2q2qD~g|$ zE!qU|WqE%ztjJ^LYU;5szQ7ZhhC{qqh@;=l6uIS$o7%fa8hB=Q%l)Y_6tFcM`@Jo5 zV8gxBfucI^2UAg?{f%YmvOE^t@#G2rT2oMbn11U&S@BU<*``P9w zc@M`&iQtmQ7cvIhV>YAH(xd%YT8y}>5^8UM;Aun)hpPnV>jX#0M7t)tX;5VegWkss z9^fBc%bp)i-!7}@lZG(O>c_Ge6I}e==}4M9Mz7Z>F7#R1Ms!!*U~#n)45#mb!{E}U z809Brt7zbRFwNH2!~T=81Nha`QnHt%4?RP8PXGw%VLo6BeTrVryeY@n=cucn{B~aN z4qlu}sHl7o_ZASG{TPN%+ADw>RFNFWg-LqPfH5MSd2|qlpW}g}ZVFHl@Z-6_Egkx7 zoNWs*9DAN2D#VSH>A>**%*6944BQZ-D18E^jq$H>xB(9uvm2ti=hht9Prfe@z zE@+v=nUDk9hOAOPfT zgD({ut2We-clBLpR9>O%a4=!t$GCv2{`4%Kp%*AFD6nwAi||ngkW>F4A`vwspRkO2 z#Sjs7Ab&Zr7lHa&?;Co=3Q$BMuLY#kYQV!T&ho)I0i4O8n-bPy2`Tf48jYRf>F!y) zZNmw%lIDX0Zo}B&B75h@r)=4^It4c8x=ok7CqYfF_q<>uzxO7h9Hl|^;aIKp(+uHnWoAt@Xk*9Rhzetc)nSct zBVhD@?-SO?#l1l5iUqj3%!n(j22n+3?)02iYGr~Fejv+XQ9YRjhM4EAO*ks1nA^X; zI{1gw;ic?gYfB$Rz*G6C0awf~!)@DmlUKNfS4iEw|2wO-l46QS1eaEFtohsSd5tq2 zg?_c#Hi_;dwLJ(U-TIsItFmVv-toz#a<7mXtI1@rt}lmsm3;1u#J!}boT<{V)j&E#N^`pcj5%=oYME|t z-6O0%+!tfNdqFzB*xuux$0{EHUD%(dINAYJfxF07}n0eL1Fa4K?p0&@ubV2f(S@t-tf%Hjp`HZc4`Z*-f`SU4B!k5YJ zM}#7wNXcEuFCG_-;-ELZ8A6-t7`I$gj}6}HbQ;$^FuJVGR|Kvd(hNzgSvbg1j_4>! z66IBi=xo-qLAp?v>g&B|ev1tpZu=(j@O%!wt4^(*aNsG>fm8UqmrumCZliyc_Z9>gVg59{901>>=B~=IA~LiaUr|PNs^K?9VmGI`G zIfQXQGg#S;w)pNp<@=k14m^7%^&zZGv6;%AhrY!{cP|-*)zhAb*~G9m{5U`vn7ClaKuP4Z~=MP1(zurbjDL9e;C`E zP6NScP+(nlBZk=JF^R}2J*$_g-d}M%C76X+66GYqS)f09vr54SI(i?Xj`+>8TBLpf zn{r!>VFMoH8r;CO1W!Ww;L66h>B*Ss*}GZ&j!DuH!o0k3UkK{|FU8+q`S-9QdtD4h z7K#CTh_MVBYQ!|V!b;c2K{W9fbDXZLEux+{J4rxFxcHWyAutP1BxK-z&G0ys_Nk#} zhL50CYZG1mZn7t8iS1L@E)&qHKa~fcW`$wD+gh#PPAmkVdKiTFGg&pa90~sk6IHxJ zrQ3Ev_D=#}WC;jKiyP0753&ukQzI;$){rDnes(vXRTw?LM6P#Hx$VPMd}{|bv*CdF)=w@5LPwPE$F*kC_f+eW%4+)qxu3QiA54*nn8EBEy}-Es zbB$#V|7Y2vg&-F9W+Vpq?2p)?@Ww8v4}yC#{9l&seXuB8<*RRQuxdsZ5nRsDeTx1M z6Y3n~3l6XMLt0_g**2q6=yay$lu6~(hRB#3)$oIX5)72nzrS*$7PW``t1-?icbbJJ z!sjT7n@n+3o#F)oLYZ`P@LJ+OMRp|omQ!{TEQ7MKzs_;=E{Rch`l%2_Fm<0on57vP z97TtH=3tVB9^NOy1hX5|)rd}80Af;@Ta0I5_8QnSe9MtRUjS50*UM|$C7qTAMF`Uf zCsWX{azLHz#M7sP1X(%pCS$R?1hd{_ChRbOptmHe0y+bAMGti)UB$R)5T~)Dl{VVX zo3ZqSQv`8o_!jNRfk-5AIzQAW;>Q-8Zd5U-88T`3;e5Y{zG+ObxrK3$CZGN$krwla zow#34ZsYO-cFNfet$v-pf?n)O{n__4sP`Dp?s>L&#%z}V1N|mU$Cb2$XiN&9*B!vK za33&Ds--u#(C!LeW_01uiX%tP_<7_kk` zM5PQHDitr`k*)1LD&EyuqDArEt;)9#zdqL$denMq;e6jP`eemv&rDmr5nZOwGYQ>B z>wZ{}L1j!F_Y;+*V2!2I^s*fL_@7d%m=ht7^f&^e!szMB_e5emcm%*m@gg`pxFPLv z^C7iD@5C0-#r?$iWVQhm$D$gxxv;+Zj9t- z&OnDae7mOo@Bg2%)LXJ25qGPc$BFT40h1ft-Y+-uUbe9&kugE({<3ed1I@TCL5gMh zN{EzlI>g}6Vd}MX+1zh`DP?Hv=)^8Qt-v6mzRVT`h@ChaCecSWv~ho3C~U);37>jV z6McU?+6>Xct&Xo_??va(TDOtM-Y_*3^v``E0%2ouH{02VG!}BZa64Dd%{25!O<%E} zu_Oyw*UHYnR>B;h|ML_XhI-m3ehrmWu_6a2@TkRBfB6LCZII!1fq22jy1i4avl1CV zn!2`L+=ZO+UXSHjHKvUbHElxt$xpbynV7Vj&P_)gg22vop53`=Dr^H5x{i<*+oP@0 zH@<>p6l-Ug1g_L$9)49l z7YQcR(K=;bz?=F_6{J3k^08^=yoKu*U5q1g3jL^cy^OELkw>rh;W~BtMVBp!6C3c+ zYMl@YS;%T0Hz-jA{0dgk5)^YG5i7 zr>!oflt1akQwey+eOo~Z)+jlP_|>sp43ZUs8YymYO049iw3Y>W5idz4l}$+ywgVFsA-eFWVZ zIVw>wFPtEe@||%`l5uU37#RoZ6!9iL^3{m@2DcBmn&(SK_Y1E9-D8vo`W=Jsxm)WM zjG6sw41mwn(Z1$)NEl8Gy3_G3i)`Cb8G!usU+8UUGI^e4V?xWMN~=H9!6wB@&^AIB z(0Rbniel!2D3PP|_FJ9Mc4pEu3WA1+DAS2in3wpkt}|FJ&fJtt>B1>A+^aDy6G&=X z<9A}Hr$-T%(C9aiREexv%nUV#->T&w-I=9p0>q_rgbA;qB)t9(kNJ~z=Hf1?93}CEO)Kf*%r5r>&_%DR5?yrdWN+tO|pX*8;7_Wxb{l1 zuOzH!VIAOF95Q(v* z9k4)XHF3JFrIx9ae)mgS+S|`-gj&r9Bu%P*4tG4ucF9JsDw>2H0pOccdSeh!7141m zsQWzR%{hwRhJJpJJb27wHsO=1>wpQs&^-+1RhMP;4VFf=D9RzH--fu>8 zo$ceJR7e8#msw#qNCsJdVR|J@WOdgc@oXw0pn8#6m{?0WFB?e!?($p+eRyPIQK+$g z&&2=kt((6jg(SQ10Tusxvu_ajz@g*^quP3UZ^cWH5UgB^PEO+03}K7+ z!&pNbxJazs8HOfS^O#Jwar~o@XKj%uTcgJY^A>`^IU^hF2&9!;!ye*dT}%E#|72`6 zNyLsGMx69?Ge1?2fIszchU7~2;}0PST?!^M%;XT*7DURpxO!y*M+^SFb63&j3KGbv{Z-!|-zw0bR2F66LJX|cmsx4>0qwsxYdU(lg37jW*lR~*B zy-K?K>?eLg;iX{TL+bdJ!;3ye@T^|5giq>;5vQmoPRERJSpuxMA0!;Zt~zAkHVdkrZs> z!@NAAb37tGkmd<|F_frKD_*UL%K?}TsCz>mlJcjpvw)SsG~+dgarDV$jzh?380*D< zi49}}8|}wdDR>~LW6MvADE(mt zuVY67t3lshJ@?FJ-q7~61~Z-Sn8aHUezJMZ)T=wH<$KQ*k~0Cyt7+SAxb!;|mPg?q za2HOt5vhkQpCzUw%&+i?FHgR|*cfJj!-a&*6N{=EXB?otI9}2W$RQOF6o!9S5QO?Y z)UjMVO-%G6bZz}Plp^(lutBh@d$T5Tgthe7bcI|%4qmp73%cJI=-=L!HqiSG+y$)x z)ek@xH2g(ZheQx|ZYjXmj$B|0)u~al`OA)ZLzRLs=Ln_+Ryo!$FAq^+ZHKKO9S}?G zhn{?{gEYZ50U22B&DPG>0`9{`Ns|OJrGKXBN7rd`$|cwO`Ci+*G&>v@*0l@g;icueo2uS9Dl-J--5^1_dBsG~_Q1Ng~dvU^2>svcTV$a8 zUzpW45#Pz5hbHQSAl{PNqke9my3t9@AsDCN2cgj3X~1iagZhEgmW6OVMuEF{;uKz$ z3Q<~S028=kbs702$F~(spn*aFrisW=C!21=w?raAY>+%I`hbkLsai>NV}c>tJf~n< zZkMUdFer!~46&Od$GHNAd?<^jZ0>Dc=XfVlP{;sjT1kMQmvx?F$;9c+f`B06a?|#d zxhu!%EBySCX3sj|u`KCQM*Q!30sM{VLKY9xR|vBs*X(jBPO%CaekuCQSuM?$s}wT6 z+gZ|bQ??xVwSmY)unBqu)>nG3`WBx_^xIbX8{UzNCrIMN*Fduq9GnTap4#{kS|=B> zf0-a(3Dk8J7?iF$la4o+81H}30Ddj(A)lX3lJ8XENFp;J|;*=L5Rf_DQ(c?{Xz3yqGSZA z30t~LYzp4il;vk4@408p4e<66?5j%0jjk1Q4xdHr(}lqvw)~OR-b?gR6$@fM@t>M8 z$E)E9(C}ujvT*ngVD5)x%-a7AtJ<(eA5Gb4%HCBsq5h;A@)Nd^6-}-CYb){)pWWH2 zIkf@gL&`-@y8K?e#Po)(0RLroqw@?WD*Hk9Y)T_@8_2wN98eLhL2DoogB+grYaJRC zjivoie2fi;zg%SBi}OdBA>JGyxIWiZ8P^FBXq`B>nTtP$tm5%D@!mekjs3$+6U4*) zZEyxairewWu%fq;;!nw$r0(09ylRhX7+^ z_-du5wGuc-uVvoxIS;(YQlo^uz!utrJz)V_dGICs1!|l!x}5$u5uLhfcZEb5vN+FV zcFpi+Fm_5QY`Xc;Hfod$=yk~S@?w(6KVvG6CPgbPjm*kEI9$4r#pcg;BweBd`7P#j zF}f9y6#E8+rOhVpAdt2zl6mO1PlLQz2NL=$9=~zoDv;q)Z_mkb&z~^+i$E^1xw@k3 z8-i3_Qqr|B@0Hp?n_S#w>IeGT-vJ!0!~Y6t^_?#h#pHU#&pdPydy*5;q6-nWtT5bhvAT{&iwYB~243hE6Yn0?tWn+fB{j zaZgKK(};I5AM(z7KV`3imzEr zq$^IoYCq!^#I4j5TYva$%gA_VLO3&OwL3j6qc&)*eLF|0{9sAyS;n@xHbbABtySth!(%mx;8&xlci$?f-fKDc!BgM|Yi`u7 zR3C<-M{|mh9Ntf6lk{$c_A%;VQa54nly~x`ni`-!U|V3mvs0cNZ?K6-IpZ*2AH37u zK7IKZn4&g@2|SnUs2~h8Z)u*xZc~%VYgv|p#jXr0BLktiIA%?hxz9sCuq_!bymdJ04G30K(DwHCS`@-?0X;P*{t*3gMIEN>L+m7N_evt_1!@Am;Ww`Nf0 z&5=U5gR|jts@E8$w5iCEqO?RMZyKTKS6KzHVfG_new-Q=5D{~e+(RImCSgRi#=zTs zN}Rrv!>+B@F`9>;K^aT#!*-a(VriIz&)q(Ar`6kD83+)!7`Nl!RA@fLH-LG2DD!ns z3G?{h#o_k!X)jPWgD^bm91}A4etuwiz4fDIoIP1LXs8{&Bo*02HAmv7xkV!V^+&KG zu$Z@V@lURf+<|o~YwG{(6rL}uYGR+wvhuz?oJz=L+d5vrLQ^E>&TY%2iZJlGO_0FL z6IzqfU?)q>JsH89y3Wh`+`v+lO+V+L2$k0%)d~i=bzhd=&1mtNz#_nWIdU&gH(|2! zK}2G3gZd0S8@w_@Z^G3IJQRlTU(ZML%hDYuvN~Ghx6~>jw7Kw{dMPQ^Rbmmay?=ayo9w(DT+=c$6iY8R`AXT%eZQY6q%Sy!0uBQ(=Y6cpl!pic4K9&@P(Pqj7_mg#|h?q!f9 zVj-%m$qL!h#r6E|d|$_P8dKeiIP=87d#EiIG0JI8jRowoHa!6&%a8SGNSHGh9`M;~ zwxMBn!nOpa>Em~yFnKgH4Z6LoZzAPUnBD$04A_Lmq=Uy!?{GnalHCH7;q)T6kQ7#HBP#B0Q9V#V*`r-}QqZ|bC#@t(9{$9N;= z7Qid0$xqMDI>s(Rvu2D<2FHk|-=l9LvZ_CcIh~V|u0u|UT+Cn$lxk$Fc^UK!nML>d zK=IS>Qkctxd(RLqv#V}Lxe{)C9;o*HfAwtjm=1T?>oKc<4EB{Hr5j_5%1J{M?wc%$a4t9A<^+A!Eq*F z_A#P!xY?V2oqzUQ&xD1u&s;fdAitH#{_6IZV84_v)4k-ezPwRngB0>^cS@Pt!B7>t28L4|YEF50uputn&JCGtQ}qKC7B2DVD{OId3uT~auJKvrB$T#rXwsSi zkfWOiu16-F^!qy*+qx5j^b``SJnilsKHp_fHPOgM4zdi$mvrGC&$x2Fy!u^a48!MM zbCnGR4ST=Y9c{+TzX{e2ZGy17hBvld4IvyF_*Cl?yFYA2LRVO}@B1AVifOj}FuT3= zVt#xT=J0hHm@GIveU|nEK^HwiD80TW0Uue@pjNsBcnTR_g(@<#9bkE^J1XT{=aku- zn)~lk{#&Bgo#9j-ouJrY4`HcS-6?sW3WP;d%UR$fvf=i|@!NA&I|)-n59+6z1PYDP zpHu50w}E`YE`Pv_IA;!Ph^j^-mqkl|FqZ@fOcr?@K+zpzT%djtcm^$klEQO!i?C$3!(Yt}z8bHL;;Y*Rh@ zIM=&Y)+S~K7k;^~GCJ9gV`eZS@zt$IeojT@FDl98>${AORk95lkG}ckLrxJHp-A6( zEei{0ld5hW&k`flfnT%W_U}c`Wc6g_ z9?!`h7Gq>FywJhuX#iYu@t~9xEqF5yHw-qgkCGH9|Hpg~ghWa%{jSRuHk<2|uZ8f0 zz_;i=G`_SlmxqLQ;+w3(oS1A7XAiA85gjYnzUArgV;#NvRrMkdq%Cade%#f+}#^~p0zkSS#cb+55Q^=L{ji}5BOLM^j{*<(zq^1&EKO1)qa&< zch83$=wD&{&ByG>u`-!%lwNE~jsEMKaf@y0%ar#+YY=S|l0Hj6&y;5_!5fp$)cG1;}VAP9?wUNAoz>94J zTp`$MNrs=@MT(5OT3q;ib_3hyd(-IG4n}TGi_9f6Q3dwUj33DWp$$`9Y$mxp7S8<< zxe^hVV3j>DhUpa!!pw&bbyYz)=37BcQ7JZo5_ILyUZ~=EV|p*f1Z2Fe zCq^mAM+e%;3H8+oY0cnbx+S?&amBHKYFM)lWeP1;M1$l7?^_oy(v;6Q9|t`oq!l1yH6t zXtE(EX=`Hu(O^?_Evjd%AQYzds zNR$pXMSw*dKjGU&UoWqrAC|%Z&-HDb1DRYw{r}zgUv4;zu>VI`$jfD7<5yN1=|$q{ ztdw6n$E8__$cqvXgY67m01*df^MCY?S_X9~FsOlX6AqY0J7rCr(=WeagUBpVpyF0i z+oOwYSy3aFiCVN8S#Kqelz?{hH#=)5o7#kD1|^i^^$v`JMtbimO>HUj5mhtWT zO!N30A_{ntt*Uw;D6)Ef%czMVL*43;V_vH`7ceFRBy?S~XdiJRGiV#YwNz&&Xf9RX z2;cSPi0P1V`&xirLXki!gp6rBg*;Y|U-5Ma_i<>j(B={~t5y)&gJ*YJ3*gS1kJM?E zmEk^A8c;>BR1Y;L#=)Zk8!Bh&Ozc{1(}cfbqt{Es<%`oSoZf%jqIsJY)5W$0-L z(>s`-|2ur?!&U{~8ZtFNlV}9wp6nbn7_C=@TKW+@p6;hV)YU|Pcq|7FLD>WZ4f~Q$ z8!c@uybb&#E!3j+I;9p;FGVq*cB${WOnQ0QPHh|+rTgu8PVvmAi0$1Pk(IPPFm|+= zwwHEqZ*5_N_#UXklcn;S{iAqtF=P5@oT`B|y1w0&DRL2%B|o?HRS_SFsrHEl`Se&90ZC0IMtv11~Xg12LnQaJQA7A=5(DBNGx}B`uRBT z5%qoFyg!=i{8aLJiy`&pHpb7RR*_bIa=AhT?2L%2UYBR7BQs9I8c6yZ(3f1JxQ)8e zvi+-@2AJf+Awcj8f#ISq9{Z(BwL6wXVAN+;ng;SVARi!e(kJE)dZ7X@$+V(zd`P$i zS9ht7cruhn{5K;7P%)FQLJiF6(D&Q$CF##lZrqbI``siVg8*W-nTDo|S2PH&!Q+uO z!ZUErHf=6%`sn~<;f70b->m>i>uFHJMGYxJyE3t5)khiU6?P11nGgXyA%uKWNhfXz z2kO-Iu605f5+7JM*alJzCNl^7NCVG$536F93NX)d+#OS+6q+sm0UFwny}J_H|KhV7 zGhw}6wgv1>n&?){VC5l7YqDS`ek0ZbwIHHM*1Pv|%Z2&*u10tp5dJ*K_i&jZCdX&C zKyDw;>H3Vxe82D1r$1*5pj?fD6co6hZlG2Sa#7)-L)qct8YC#fydTPr<7?p3IUE`bgAK5>AVIc|q zt7BKY!R`QAg-AlJ<>S>6G-kYwH|jorPIIVagUpf}gr1SB46CFBzmNlU00c($t%5EOVf4%+jfl6B+oedV2z)r-a(EUWbo$C`X2_D3RKbIM6qf+W_%%frw& zdEC0T{G|k`#sYl&6u74mEX>+E0-!Mm3)E{~>`{wIBns36>jEJU^d$dbWOgpZ;04r# zcEi9g-k_~r{;U4zarr>zZzvB9n;_Nbz0CwLgglvePS^2`*qwH@=s8Q@%i%$BUL#S& z@92=4>z$Po;f~%=EuaR7D6O$@x^_vT~Cq+S59gFGvliKI_9Bd7>`>GTKI~Zr# z!W052^1O14|dC5Kux|1=3z(D*khjBm?2Ew8z1x8Jdj#Vuke8-!l;A&-X zny{Njs_EVoo$ObZ#~u7dV&ZR~$@|X#I!nZcz&a&bMTsiswn#hKR*PK&DzJ@!XYE0D ztfOun<$=hlSvX<67ppyGqFph!nB8crPDP(I9v9Y0q#t@rco87P$nI)QeS<{l+C?T5 z4Am&d&eUSx0mIDV=iwaba*Tq8bzHPfkUnaHNlj|T(A+=J24AZ;VNs=&?I5(eH`iaN!UR41LsQf#(i-6nmktiG+Hb5WWyHs#~h?p zRZOSvQNE?D&2(K^qRsBpQQW;Etd}?gZI#b(V{K_}sw2F0IQ0)db{?|gWz>@!7nO;o zcjRDpXqTIh+VcTjYi&8#R=6uZ5Q8WR&)=5^Z%+8O_`L5|ol!H6!=?z@lQ1S{@|CHu z=92Le{jC>w%J@MJm1yDN0 zUPXsV8-em=)^A>zIAMSfKsdGL2{DBO&WU|DHqE)yU^0>O6dW_ZZ)FTU+Ht>E(`{SN z(G*~$Qg4tWLFmynjzS*9sL)I|4+!ybx~ zjiZiiP@pY#S+W^~)TNm?@NfEhZFsH~*~>CIAaujREJ-V*&#C$BPjOnx!uY3JxAl(0qk1n!t8GA>zz1x0g@1%mYk$xl;zN3wfYJJ#w`0(nkuMxKM*_%VU}GOnsOzuORm%lr2Kx- zeZz0?Gph6%-mrhH6vYuX?96~4`~n_uW!I`=QmbetkaU#b+^Xg=;tseOP`3U!&;v3{ z7&XTaHP?(kBW6(u=k^Ztr75hOO!ivS${J}uTh}dU9(_jAD)=ulEh6ZL3udRI0Ba}u z_u9JNC}nmGV#2yG%f&_x)&=-|9Ag1EIuu9BKI(uk0&F78v}B~zB<09Ixv$*48-#Fa zU7W};L>$lpn>Ir3?Vm>is8Y9QKN*b(`Aqt7JNZ>}YhgEI03Adc+=1^^+mU2xxkV=* zg|o_Hmou&MQNdKXx?*BR*4i4UhtYH?AHvF4Cl|ps5zgTY)5pA_V3uD7KT{U-<9z2- zRk8OM2E~B+fP^L2aJR!uU$#qJWk{dy9uz2m{-%4M6?fVYBlkWIu79X5f`(KCrlt{-hF!sQMhhQq{BMY$%B)eCZN1UH}@T>S`GJPY6W>e(8GA zO1l;3kVoMd`=k7v8s1I{PdR3qc=)=`=U5XnMH)nq@*iuwe}fXm5IC_!`Eu3kd)a?G znXNstJ?2_NrkejRBn=vv`Vi=%C%WIKFtfG+HuTRY*2B|QBavQ((=BF?qxAdY_V7$2 z`)T~4C4yFsXF}meX6DDz!7GC!g{Gq@S1vLq>VxJ=(>Q(K)R)VEYC4n<#D1Vp``bIw zEH~WKGC?1S7+LmSTVlolk$sM>wI3v&(!!F`20SXr48|*)>Q6bIXdDThKG8T( zbCnY4?PQL&L7_)lz@}j+uXmKes}s&|9z%fe%EutNsmo*-LhQ&6Yr~jcw_yh?z*Kmo z=PAwa`Mx69xB+{eFO8m#`#b#Pi^d4h2q8+UsVB)MRW;K{!<`iI5D1bLR*ub}QsbYT z;Cuy9mPj4*c-Whx=BQTC8(yztUo%l!Lt6Cs>PS#;0!X9(ZJJcU*`0$x`7%a^$DcDa>l5erW2I+Jtz2t`c2ntBb9{JG{{))Nu;Y(`t3-eT`OqaBy9Yk zN$>Zjw~xeFB`?|~mz+gTU)21cY5MvnB19wUH&oAQmI(&LVC~BqOqyg8@cYpN6$}Ga zYl@%|$`e5TfJ{Az5;(Q%XwGYXzh9UM6uzxhd0n3yV>LOSNix#t-!)Xt7rSzM2}e@e zuB~e1>F!d@F0PHhg4PyE^2!XOuK~g;L8S&khb+Uy_T)PoJp5ww)3d}d@VErY%r5+A zB(M1D41mBe+zHK&C=j$S4#ukdnNo>wnfpR_*4Mbp)Sk-?dO_!1FOOH$+E{k<1fq1OJB`s=LUE?m9jx}uZw^T#@~(^}EKiTOwV_50jfu!Ljd z`kp|N?|BpI5uM0CuB$j`5c*H+^ii>_k_}QD%y}^+#NkJydm|S$@C@%s==7OyGu-F$ z*V!jgmI4Wm;z;~L$)_$Q$m5BOFiB#JWL5GkPVAToWrI6Z1%|x`*S!1!k{NtUREV!F zDUPOtkfUj@~q~=nKubIPUe+E+gGd_FWS6K zEn~ZG?g>K zM(nsNMKz>G9XLmKUL!ml&=EJmhE=Y2odxYErk~^Z?2iNl5m0fNk^#qY!arA%dz)=p z172#f(ZrjTILii$?U^iaVE;N|UK$i<;MnD$KRcQtAxwTXC5aI!*?M(8rO|OL&Yrx3=f@me%N5`T#e?tK)kx*qE@})gtN_^YTX@D(M`m7Kmxvo}M z$kP|~`O9uYqsC2_pvM!+%tr8M3i}74KYL;vSc1wKH^`Gba?sSxk zyEebr9W^rSq>_Loj=($V;XW)Vq36zKl^V`7f_f3gqe{JVv2w3rEoJeZPj9O+!mkzG zjn01rB#EBK0BT1jM{=T0H|8sC$x?>%c7-}{hiw#rja2TOb3cAyVGi^qSKM1{?nDU3 z**Rc3`H|XFwFEF`R$LoM&k8!e-2r8DY0y@{zp|ld<_=Lf49G^u=S_+B$$tx#Q6}Oc)*+YPL~$bp)nfr!&=)jd^ePQ2Sjru zHb#g_x>;u;aPCgOI5?MR(m^6M8P(HhyMp7jyAL&PBHz zqP+;fWw*Ulsvpau-_yCuxx@Rt(VPIQ72!C*pQW^@`qp&md;4CKl_h5 z$>*+z(fW4%GDiY8B5Loa_(-6c3vk@Ppi!r*-LC!jJHwJQK3)flYDCDGEhE7QP^Qhm zXFV&1dmG!h_aVlW)3B1WJ|5Kr+-cTo?4yFI)^husZu5Whik-ev#v|VBYNx39>vOS7CwZdG^l7|Y#si$aAGr}hBYp%qwK5Uc^H=-u3XB% zZ1F`V*W<)Vq+(l5jN94{hUvJ_LN6@IUq+OUlFGSfkznL(G9Ywn*PDSe4|%di+Z%9h zsb_@_q0cu0;d`W$-*Y4Io@?AaVX4@rHF!r{nrNs)hd4~^LQZ5!!OoFKqM{v)RFTSmFE6Zji3X0fOGnF>yk5h-510F0B6eMvMu3?!tAHsp zuGtIu5-aKue$@`iw}_pTFZe2{2!+~sWC*Lv(}~$(ZzrjP0&?o6J3;+H$N;mK#=)2k z5vkpb{U~7^!IbgGQUwKIrv}xYIyg80w_1o)Hhqv~frVmT5=lT=ae77z;?2qrRc;oOfcG=Zg7hPq?gHHAhz8Myeh~ws*yeOSo)JRF`L56b7_ZlM)d*(`(+O!Huy#PyIG198D z;f%>I`8&B-kjxGWa+4+(Nf!TL`MKit&xst@lfPdYhJnfZkywwhfoZ#;6H+IF{=sQ= z`UQ1a8ZEQ&$R-*UHq;CRAhn@#jd<$O$wi+~so{~c?=*6AarWeEAujCjaom%wg@;=r zpmyEi^=9s~Zqin>;f%P#p(Gh}YV3A@ot6Gf_hw_`{L4mmZME85c&WT3K^TGAM$_MN z%Jo}9`0`r}(b*CvLk%WFGg+40j$MShdm9tHUCGIlN7P=B?h>ap1_C=wktP{u3QxL* zZ^qFppmd{YcVI>Qy2OH(+3`DiC+(5C${g9w4nYs^9WLB9xX#A&Y<7{=>4j$kZR=IP zTPxtgVH~bwz4y}uLIeaQ{sZ$O+0>Rrqs4=#dZjbULTA&D!lVm+X4G$S&ywT4)i@%R zI|a*&CT$A{g|Dn}&xB`QNY&d-H;4}v&vF-$KUeK=x58*TqPY-A(Hy%)UYNOp@v|n7 zA%7W*5ZmD^;&@Sh-b!UZ^=|e5cIztaXkV2)qP(9qzdq`n)mnol996P}6QfM74}B}cZo4T1@9*P! zW1~V~dfI}0VqPm4ZtBhJus0(Ur#sJ}9(PZ}A)7$g7zmF zk)O1v25@xZD?~@Lq-0uLh|evqR^#TSN5>L$TH{En=zf324*Ow)eY9xEQY&zftfM7+ zB29z@q|&dX-znZt&dzHAAe^GQmh;{0iK|6xOMRQdi8sN2?wfG@-_7JM(R_U3vJXEs zPI}dR0?gK_+2u4lI@N6vT_)c=ju|4h7zJ6zRm6;CK^kQ{f-O&P4PTiEFJ~xJk<5C} zx46;<$Qa!UhX@hX@m`o=Db5u6gUr|`uemPba+1uwp5Sw(Czr%5()KUPUuIioz?00H zZ9!SKcN015MCTi(uL=NzEO$OXFI6fwu_dltM`Y^@QRX0BRdJsGkzk~`2!9=-d5z}Z z=U_TbdKIlEXdMmftBJ;2^AO^Q<=QJDH@TG#BHb@cv&#B)u$F_8CX1hX+llU@nk#a( zunVqa|3Kpv@}$qPtg&DGM3BB^c@XOv_RUs-rgkM#eC>r*&eKAu+6)x?};%Wxd@k16gN$l9vON;%IA-x+a1rayBH<@=^zX+6@pVO zF~&8;Em?@9Hml-(e<+pfUAIfos$53Z0~>>MxMlQKxak#3$j4r;T>B;}Dvam;@0&42 zZXO76pxOiIje8|<*iQN=<>#Zr9mqc*v6BX}lVI`mEi-yoU6puMGNHs+`{v2{5WoRE zj%cjc*^G$tKK(yH(PpdH#82;_m6Ye|Y~5R;k?%&_J>=(R)YjF!q(F||U`JmWWf$o( zeY9}6T zSE;Hp7|wIWco!jc6kKU;SJP1MdHyrg;zt_|wu9J*KkUBMYwG1@>p!JJdDF6?CyF`J z#>O`_lx=M`QOI~SAsmxXM{HI^qIcEw5W)g$P|vr5E@%`dy?OAV491$SE0pe%l(p(( zZ9KMKbcRi#ZFh_kzsQ2@&&dt4R~T~AnvfAb;ZD8+=LB$}z&=zI7D8Y8kInZoUHo;5 z0N>^?5*dSlEWyyoT|*hDj>#uuhzYBN#~C{#QGt;S8x0b;N((VM<385Ge`cuGToB)U zRYEIrN#n?^s*_V18e8B~-;GB>dVW-n3OmqDs!;*^ymk1NhNb%!H#z~81DB`&dSK=m zJ$(#=DZU`xEc^+Hd_23~c>t-H1o2pVi-ZLaELG@WM+_q-52=yO3KFe>8;dy50FH5l z4_>GhMs#}%OZ_<+NX;c+9>{dh+Y4e*1i?#ktX}xI=W@yNcEW!r)ss3^{9RW@nC^)F zifR-POpIu+%gc#Z8k_DW zZ#i`mfARIquQNEA8cItp2)R{{mtw_d6Pm27f(0GTv(?D z+5dOo;|@ac>W`G0TGRK%r9g(!iaI~zw=Y@XS4B_PAFN#LivDelwB?(;yHX!2bIyaP zp=LR*0^G#;x|ZR?W&**6K|b+O=0vk6n}CLX#vFB0b``0Q{6Z!~psZ<(Y=I_WqJ)mf zUh6@-a`s}`oagm(CdG{zh|R-jQ6<&J*DPvncU{SdcCmLozzeL>EG009^w=q&l1HqE zfIL&!41sJ9=7JcOMjxFJ)K;FI1L&!dc!pL|(S#U{$5j@R!qpxPhG8n>kDv~xC9?QP z%1j5P(=PJP!n~KOg*B1gYizx)L`pIk3Og`=^GqS-2DH@+6kOZ8wY7h78;aAs$rfQ} zmFKN>YK|#8sf+KxkNT=WTsp75c%0iIrv7BLqKKEMLMA&pn>2A8y!#2@mDK&`K?G>V zY6l4|BXR>vWs9j)fzOhM3EsoV=u>Ugw6kV8fgjzBAZ=f;I#X0T4mZWa@DUh{DT-tV zncbMv$cFlNySbg&w`)9Q;uHM*8FOVrp&4v_uA{CFeUupWOWR~4a9>Km%*aZ}Tu%Vt z<$kWV%Ygf7a!Tx~aRi%KjDc$!kn)=ANZ|3PwE&sdy2!c~lF!D`Ahm3A?f2&1L{R zb=Dt&^i=cvfTL#bB8Ot~9yndF&Fc_| zz(5@`j)fd^TcWIQ%0a>eS9l0Y1`@kc1!w4CA|`$lXcjt?-b@uXcy$l7 zekVGp(y`+o%un=(AgaCy9>#;@L>JWtFVgX9P7lfB4}nvXGldFgm>5x}W-4b6n->)0 z0g^05!w8ODC!E+j_dGeR93x=XdxMJN?7jmK%TmS-dQJ#1FAC>enfcndHPwKDcW0hk zbUbA`Fp;}=q)Y+%z@Wsh-Clm zSQ*Y8JW`**o!S@chA^v_U%72Z#P~I=O&%ft$YCj1l91Kqsb2sGmeRB+pkDPpZ=EiBc1&25&(qUX7N1>YvKkqvSUswv43Nau|>4$`6Az-lfY z@RQxxs^h}YVH4yO2v*v%b5Yn>Z7RPMc{^NAg39%LS*MWv^deapcmK;wrif5*cR&hq?2Gv|3FQn)Wbn z!riiwA?g5~Oq{?XW+OC*E??RkYw5JUAMpTH2j{D}D}TN)-I=*fi-14T@ob z6M0E2?{fr6w1Zzj=x9IWVA%^o(!k0YG)GJ-(VAtRVNQVIS2yrazT_jo%2R9h78eA0 zd-7C-E%M9P25{tk*GN9hbjWCVsb2Tnn$q_+s$pD~YS%u~)J8V5+-DD@-Q|j4F{E+# z6qm0vl6f!AD&i2)Fv7JC!SBZ#$V4HpM{H68jks86%##vJakgMsLH<%1IIz&deLm|- zN@14G7K5f%M;BUTy60BQHXM(N-dbI{3#vOr@xJ=6S{+x|0(6bR8Oh=RR-Zdtcy;|N2KJ7!r0IdZnk*Dj_myHnPM@~?aj z>gA{6D}YlSNWm^BP5g>Heqkn40M6llM=x>e(Sex_A{^Umbz9CJ_DnuO^TN?P%MhBg z5;Y^!lUm3YD$H~$$I5SiIQ2_dKG)3M=(MA8OD9dRpfdy`wY17>GZ=~KPkfWHGg`)iwn!<*fB>ubTRc;S|HL%&JvUd@(9}<6 z%x&dO+CGdTA5d_nhaW+vS@D;E_DP%CI+Hiu<^OzFHG$sgH^EAfpwFcH+Koi~kEq>DC$ z(UQ+=At9(Se3c?q4Iw&!J{mULXVy5NB~j`lF#^UmJfam*b=c(!AMMnNO8emDQ$>82 z?zmobog*d~&jwNW+dvA=H$1r(0BQvLI9;Q71p>ZCypt%>{iihJklo`q6!}lDm*hbX zs&oGJRpv5^K)_qUH#YkVv(p{DqwjSIIcZKHRT~Hek@F=d;QD^qO-T8b)}w<6z)#`= z48Z$Qszh4L;^#}?$Yzgq}Q>Sro_ObnaN7G;}WXoNvIwZ_5VL&6EX zj-R~Xs`=vB4g(o+-@ZugT@cMmceFOxFUpmru=fUWk<=hY=MDllxevCnbkSE2NivRH zf^`c|q|#mv9?0ZhO?NQ4fi-_}u<85Q^|O(!PI-jYkyAjNQT)Y? z?Gcg*7RG9=nR(vYA;s%_0?zZOm-=KS|HJf`9%33EfHeJ) zd1sjtLTKc|uM}sA!D6f-d*<@j4GNQDHPJK&v{Y#rGt465eLhmFVxA58xPL3nSGJT% ztX%0s(ss_;GMrd`jV^0-=D9e4;J($4v!bTQMZfPLg7(`7&VL7GkNr3uxdlz=lPo}* zpi8XJ*$^x#*qfDxa+^aUccU%a)<;1Sr42Gj5BcL9CcT4Mv9bgAs9l9UBXJ+twfRb|3P+)xfCNrycHz6QEJ%;M}H?V$;M-kW2QqYSP?cX;`o1hQ1jxC~M&_M^6@jntaSkoposS zn}T7l>?ww7{a$`G7$P!S%~$Q{TAgd#t_#!u#X> zYjl`L87tqi%*sY-G)qil-ExBtcX?m3Msu$EMLNiD2J-PhM0@g$H8A1H?~`>`-O;#h z_ed-!CXP!*o8g1)+pXfs+>?EmeIIIWBiU!oOG*|2I)ogYD?42@xz)pAy2;EE#X;^r zM?P66dI(G7y5Ekte_8T>U@OzCr@yV?zma*MTq{GIK`jJ$xDMnvc`ZD8Q)TXg@w`jM z9}Z65LF)TcIl%j!oqaM z+9ss@p)PAG-wlWQ5t*7aw;D}1&s2>3=AKKp(wbCpq2m#aB6*rM(86DA(z;+zrB6`$ zjxyMS=U} z*yk1MFVl9O*(i>~J-m9Kv3uu!NNrSnSWdoGQJk#VT?l!Ies<+XB`MCyus-5_m!u$rN#(yE4kGG*6@vzjuQYUCfzDSIqdpOsXXY+A>w z^h>Q4go&*RiHJen3zPnkb!tITv$h_Cqcxs<*l4({>bg&E0k3sINg{hbarj|2=fy31 z;D{V^tQ!@bc)o7`8^e9(_xu^6$ueuL@q*J){au13(_W`OaQtHv1h9mBB>|jZlkoBAYZ| zw2%`5L zTrsBo_TKu<_wP?9zG}~?(qFQ}U0Nf?ac9Bgc%Nbxn&HyWHWJ#n=R~CF^L#1}&u{Fq z>vL>gbRLKd)MTx>MhsiVO^N0As|t=-_|~$no9ddJ)%?WNBT5K1VZ;mz@T5F51JNHo z!B2XR!S%KQ?90u0@sRPzv<+Z7mjTY`8LiWp%B3CQi?8U|I$Yk66eD2_gM}yROv=G@Qd^I}Ba#W2K7^m2|;NjE8?QQ?*runNjg|UoNd+@sBUCD2hvWpUQ&FAgd!0v;`*MtcQPsa1x2UYi^LDUjj><{0iRpz?9bd*B$%%) z{@KZ=NfBo5Op*Qr|BXT_SLI~45oaVZ_4xwsYPnN)WW!WH!~gAxPH&FygGe#MEu`Kc zYN2>fy@Uzvy>>v#Iaz~)NtcLz3V0FjRL-+~EV`^f9H&_Rr;16**33H%cB7|nlR|S8 z=f#SdEUR3Opw?Cc`s}%U^!>uUe4gqTFE=bbAM6-i0HPU111}sf`cic1xo0DR+t@2# zto!kv+Du<5>?L~h^cB%?zv3Qil?T9!jfxvTm2EsR2ITQ&jAnLv;Chg3Qas1Pmr^&E zNrhBvBqUOx!vZ23c=R%bxg_Q$OZ?o&c@+7;qitq<%DqD#w}u&tsF9{MFv)MTPZrqT_9bzi7-A#gyWNY3 zvNc;!%{)x83vX`+oWN+&;r1yqV+6zaeiIE?rafJi}I{p_Rb(K;6wha4e& z_^3A3oYa$H&gD>Tp0w*DV_&ON;LvhTELxi*YfGxRxw0-adCfVHF_eYkWV$+^>WvnTf2Y(--g=t(m=LO1&tAV2xqADKkp(6Nrx{E?IbYqIQf>C(0f6}%+9y|0BI^yu zxGLdLPtf#@>5D%Wb~ekiF)m8~{vK>fe`Kvt&LpQ0S&kvks>q|O8%!0B?F4aI7nYYk7#yZkQp1c1X<4HEDeJI+HjA7{D6|Q#r za%a8FKi>&c7fYlaavV zMQuBHF=NeyH}?4D#;n5VJR{fq*&qO;@=rCzT+O+;P=>h&hE$T&b5Yc-D>0}gxDLYT z@vrJp9)wcN(;Yo*3cDk)@++-U5$LilU6;$0cl{PVO`26Eqq`BGq;U_oTeZeGTrg1T zr>^VP_a9xh=jzEHzwu%6UU+XuNyN)DGM;7Q`g>fATo!)W&uA#XW`YAZd&bmeTgVTN z&fi5o4g_Xo@PO*NdGLd6uPOucnH!mY#tIl$)dX$DC-0*u%2E(F2;iBr+&EYV<9_1L zx5z=UQyA&dMy8`SnGk%8(V{$1@BAHR(&V2}Cyl3U%8e5?T2D_p1NPg-l=_aqe=L86 zRJ{h5hGjX^h&zpAPtihu{~Z8H#x253ZSe(N8W^%y;Cf0TBTbDtS}fcTsZozB_6CIP z?+iHySt-wC#AB{ajUzBcRzH%)63CEqn6y7W1t{A=X;JS!SZdy{FlMqaFia@?EQyga zg-5wiLY~3jB3ve+XUAVYA_{ouXaNf^-3u&aUZc=D2AXSAUB{?nxsUj|@r+cZnxRGE ztOZtZv~qi)h&tVIJN9gI%{<*cQ@Gj2-E*=W0?r_%K=5XRk~}+fngLEq|G_sr@++*p zH#ACC`5u~w7X~mb<|nR8RlSE9{>B-QeuFptkC<*Pkd9IUAWxNs-zmoR`KPpr9Fi>2E+~Att&wI%Z%)$d|z3^YNC4 zEiI4RV@r*CzX&lO#ND$`X08wr7u&+31G3_+1PqZlfkE+0-2|E+$PRU8o3>$X#N29u zW1!ovDKcR4!yk2(xyOWYAL?dw_)Y6GckRM9t7Ed;GKQ3^%P-rg`82!AvS!#`QK%zH zfU?~qF?IazV$fo%q?Lt4)vF#s&uNnXunF`98U?-C@K;yK=%9=;wxqDqevODPs=cd& z(gm~1m6j*jkpxw@&8%sebMTO7tlHM2&^!50#%vkbk@_@z@8iz#^)qEcgyK<0#9pW*Oe^r77OD`2hYNVczPs zmHeZ0I*?h;cZIWB>|-G~WBW|#a$TN%Oja0>4iLjPxN~~E6cJ~ zxPdEPNaW0fgC_N7Cx?}oeUot?r-AGL)>h6aRMl-*vLG%Y{28WI5KS%kG5l9(kh9XktBB&Z}RM#+E^O&{xnNpCIVrpiyOhVT;k7NIMhKp?i-V~jerdwis_ z8_LyE8^PazLFUScc$!|(EdnQS&r6I;ZTE{c5LPvS6)+isL5$)WIoZTjkEiX&G2lmH zGsj?-Kr$?WO}DpvNFViAlU4a&?nC_52_X`xfVI{hK0ym>!(e8z9@`R-0C^kP=8D7c z)qXP%K{efUN(s4t=!mxbZ69*Fy(ERg-YqIkc{dwklp6Kzz_dPNuYJ#R$+&Y%v9)#>|F7&wQ$rg9&LUJDR z4Vw(_(ca{0&psUAYH>@COu9cdb=i_-3g=ZYsanzh4OmQ zGv>rd&5~rKrWix{q;nrKV4DxCam;uLb?JTxbzQKIKtQKk?dH5X_RUsZg^MeVowJUo zXqpI~%U*VP?G%mUQwU^=-U>J{^dD3e0h~|H%ogRkFgtWwEhyoZIPWioOPVYu#P@xR z1!|}Ml(Uc{YTANeT4TC++q|~Lp8sA>xH~SOa3B%+?p%?Ue7q#bzu`xSL)U(aRyK~y zSYJrDgzFe-Xea((Q&c%Tr`JuoX9R||TcMYz+>qYxeEd-1+b7OF5BIZ+hI6gcebn2m zkj!(grPqBSZX|_A(UG0$(N*FVL1rg%4vx$36xOnbik&Te%-*C3JdM2WLWAx2g-S)! zqRIqoLZRm{*a36fOO{gMAuxTk8q(w(9m#{hIdq<&2aug{V@(A^Wmjp$;36zjA(uI5 z;yTrl6hMw^0qCIoy&_l+C_F$Du(tVQ-Yd;Zxs=U7emATTuJTe)cnrldU?8YL=R_wI z+-en2=_SRG-SZ6jT&ue(vN3sf+sG|1{|cIOp^A9IOo)dd@grjykfaIkmMVEoNP}j7 z3V}Ylt8?`yI{#DLsL}H|=pq+bZgb{4u}lT8BUt29{JwUWL`jh`I-u5LBDGx5I8ohdhgsYYdn-VTQ0Yz|abedxOK+$*Wm2#NLK zn?fEbJ8}6;9{E9Qs4-m;%1*77gGFOhLUe?cLlXvYD7_#EdJ_Lu*U54f^peMARpIn^ z`)RR}t}16%#59&myFr;vsNstK?yZQEBZ8!rT7S3zTnlT|OjeyKTEy#5nRsF_)Es?> z)DH&YJ5*PeJ@>=icO{Z#8@`fLnR1>Rrm3EO=!o+YuU=pBhz*QR#}JDYdkFsCRS{AU zJ(q0g2YD^Ho-rJnBDVQd?r3I%`7I~0tj2iFz2iEuA&n&|jE&8Oo78%^N2=f!rP>!c zyUPuxhhyJ&0)f{<;cmlQo3tnbOi8-gT;fTC6Ye({QzV-H_ntHQWIbcIiyJ5?LY3Xk z7nCX(&Xh!dR3~_6)re}I4Im*x(UE8MmiWeFd4P8y`=u(qhAB+al598nXIVRcPNk{kfS6jcZ9AKoq*z zl2nq^s*TjnRx2Z{p`rZI+>Tn5fjFtQ*11q1=xDi@LP-Z=wzKMWAP}66reX{OCNLlIL4anF2XRxAxkRRjfPL589%BlIbj8q7X5;krnQvg#ZEO!LCuzxB^PfccsH|CGjZ|1H4TxAc6j! zN*zjIgeGF$4|RzCv?`k)8>Bf@ZfY5fcxbCO0K0B}jeM9`gxc5dbyIcot-M1tHV|Ry zN~Lb~OA#pW1vi=51kI;2_n$5n-OFmOOW?|qnz~r;dWk8I6qYmwmN+T$z8jnWPZPt= z73iw4*j8MxN_Z`FZ9<%T=zn)1ZRcYr-qnmjESma+)Pl@b5WzZR!OCt}G}$&Y8>F{S zyU^F)#rn-@>p}_8R`PTyPlh|l|3_5)#CV(&d1j0i`Gu7DzDh&E>-K5{z_L*4vldH& z2~nniD|tsqOJtlW3h?1OCmF-aH6o)h?2?b``T{GINi2r?h?Et_#!nFxGVDY|(ZRZ5 zN}y-+PAY^^$QgQh*GgHOapM4JC%o(+1Z&=+Ol+dP$AQ6$&&&E8q}@^Cn1Z1`U&fpd zC2!*8vy9A{pmEhC#7zXWaDYJ9EfpP|_sppXFk2i4U*UD^^!kFqwDI84bIPA47c zO5nzbrddu(NJ0Km-Fj`>6udH`m)`6Pb|D|oVz>hy&1Wk1Ip?%(+!E%`|4P-AH@~pu zArTJ^vqgvY#8OXyl zR~I!eiOP1)WcRINT$|j&L++0>jfkAQ)Xc2(2HWTe8dUj(RGxR3CX2fD@JqHbg}%SR zZ~@nyngm>iOea?8y+jg80A+1dN3~o$_*T^mqYx#NI8X%?D*XL=~ z^SyiFr<8{N26Fv?N@Eg9O?_BGsAUrQb?pkuQf^+Bg@q$>qWwkMvo+UC5-Upwqz%E9 zq{O$5ZbVn`$Tgha`o*<6>`iX3t*$J1Qy^f=$W1&ju{AVI|FFocbhUld)~>CkC#F%> zVsX&kdm%f2JVY87cqkwbHXnI@e(BkLGXMd@1fFW(Gj=xE(e>fFb5|0T(Brhc$jyYr zBALsc_+V-ukh*Bm>4O2X3&EG89h&A7_l|Qw*I%wRELb4Mue424s;fv#MTThB;&rS| zh2wY1woUFF_rA!FY&8_{G<51h?P8&T0};)VKoZsJTzJ!&&HD?Iy;IW~pBw`RX%aDK z_Vwj?5M{D$ZY{4EuImchTILo3tA>p~tE?skyd})~4=Y|jO@MK^{c9Zqo?Iktn`g07 z9>l4paD0q&C=&SClmA zf_fVOM3d!5U^&W!*Rp+Znbw7|I{U5Fn0zj}bar?~8|eTBEJJ8hh0Zhr*ZC$Q_ifMe zNbx|cFZg8*ZL>OIAm4kJ6~fJ+TfFb(tjNo@&+=~w?eEU$v8bs( zaL*s!(Eh~BhZZkqWUV)(zIY&DQxh1U;E{!Sh%_x zJ3eMF%5^IVT6q8iyOthp`V#}w|MP)9UmN;uLf@3NEqo%zcv+HHF@P~0oZ(!T4X@m; za36#6`r7uV5hOr1o4ivd?=zDu&X>3uS51W&T=0`q9EhoOIDTVxsXJyj^2&+j?soOyABIq$&6ip=-`C^ zg#OZzvFC|r$hJpLy&)vBpfCg>N>(GU02mk>P0_|g^yPh4*W|6Wgf!VKMjyCfBT9;w zFB|9S*TBM_*A47vF(kkE(wLCqX7~753=4NMxp* z_>Bv^xc+2inGX|@jGHE-c5#arcW!*kc+KELnhZHK{@U)DYFR3kI0+rH2I-HroX*u- zNR<+EwMLAlF)L4vekeFC3~*s*n^k@D#k9=HF|~M{jGR`JFrD#*Gw@uo_Wc?aT8S*H zW9$Tx@J3(*QCh)*T3{WTDM%I=!kc$1mpvr$%Ts% zvZj#AqUJvGpA0u?gl@Ud@VVz9{fSY3yh&v%`u)A;u_7~}-&g#kS8}tz|J(5D1Kfiz z&XW0h?Y$d>Fj_95PMZPqJ+5&Pf{8)7<)A~8#xHtOG7R+~UApnf_mNo7CCTk@Z8Av( zUUD@lx-pF{G&xDMqDhDNrvWKErqlq&9ws?^m@1=Ik|hA>a%fpSFru!<8Gc@wQT&5=kg@L7mb zI$U-e_>cjMz7&z`qDhX0FzAG0#-wFG)uUyh$KLM~gsO@WZgd_gE>L?)8mZ9@D@t;d8SdKU$dR5us2s0QpbYV^+Iu9dqO-Hf3azUQ# z#;`Ds(_!%S>G2S(aaXTCf+P^bI$$ork7UO2w(cG!QmIo?t3@;4t#^T2G(t;n7_Uq{@{CS)}AvHc@&RDDQ+ohMkmu_ z7MP>Ia@!v-Yl)bGFQhUdwj0_A>LLEfc-4R zZ{hy*dQdGZLGg*(eYef&Xr5GW!I?BS8~q~&Qk#0n`>f(q95j_TZ$zrQG}2m-UO0Uy zOzC`@tHlcON@-wfZV_XcC(wfm1Ve~>BQ(52xy!Ad>^7e0RyG6kS|B33g;u=|#VN3= zekfpJ2c2guYLcDn6ensG>KmSU-A_~iJz*bqy3<3Ab@XYDa`LaEigWj0UaCsy>SHg< z9r!xPA~(J7FqRui+}WouQgrjG84-pj-3;o8w7xj)}AUsU4aq&1yDLdUxvS&NIR zv@~1jEFaDNJa3-4O1*_dc6s;B0jM^NQ?vpeU@9}wsbft6k%ze@P6ILl?5nKbb2k%t z!-?Rmg^@b|;zV2+q|Quwr&g(b0y0w&P;%o>&%it=A@MaNUdMW=*krOGb5;#*HaFo2 zvLWn3v2J0;nx>?ET8O;IILEhpbQRkt!Eqd;ek~h;aBqQd<3*3;G}%bwM6(Y{P)9V{ zA=r}|MBR-bv6bt=s8UWPG=8VF6Q?!d@By}8=+EW?s&q<<^5>y2a#6+4@{@zc;)-`z ziRs)oxaR;0JtxnzphkWEdQ0xeUx}3wyzf1@6F`$^ih72+>rIU058KqV=6loi9<8)d*$Uz(%5aiX--__Rqk< zwd8~0+txG<%4w=kqe7By0{4WQ9^7y(4u_pp2d-sejvajtbCN_#iTkDxy%AYFB$kT; zp&JW*HD*<FdNj{E@)%0yl;u z^-ak}5G6M=CtAL5i~h(F&0eJk6i?;w{JEV@J5#id7_F+SL_`fb`ymLK)v@!f};PU zU)4)bZgoMocJ>Eerz<@nt_di?8?IRkXyxO2^|*0@C1=660+K|-peQPL%Dai{#HY#? z3p}eqV96lk1<*-Riea*@QrOFopigS)| z4=qIhOz6*dZg$y4?}(~EZOTxmYbDH+o6O)kWZhk!Gz@V3Vb^D^DXI7GJVz!G!woKH zgSv*ut+w6n`pD9fUu|?i_He06S%-^P3nsCab5jN4`*i%MPLSiQt3+ih>9|kPb>#BB z<6RzY8vtV3mH)?<(8g@bx0(9!6jvz)gDDVg3{V^Cyk%D!8jme0-Bed0KpnW5^#uuY zURMOsJ84!B531usXWs-bI?$`Ll?;svMo6CZR! z<-7QV01>NuxCg2R1p6DO;=WX2a){jFQ>}ti^L2EB&4&Lp<3CoV_C9>sM5mF$3*`(Q zW-n!O+$P-a{fWC3Cz;6EnAYoQ0J5@#_zqY`;I))c*YMC}C8gu9Vet-6Qj0IsE?ywC z6A0Cx4Q!$O@?j_z+=djIUK@9Xd-=wr$K^5cBDw7zh6*r7Y`yMcxO7ny8K%!|a6}Cl@ zrqct(fRPKCLP4s~caCzOx}=GKH-9a78CrrED-qu3M^`;K-cn5i1cm_OT6`xTDhn7b#q0gejqS@^B9z z&TW-9EYzrx4e+ztQFvi$Za>^u_iLD&Ta>>sT_f6gQ}O)=&4!sI%423je3@$)9f0Z<$lJp=q89 zru8JYs4t+16MAm#)}PBk;HJv|s0uk#O{emjRfu)#$p}?82(EHC8Qs?@Gz_#k{83$I z<8t=V8`!|+=tprH#@)n^GS;!L_;(7ZU4sl?y4?i#WB)_c{+ed(c#{?e2{M!uR5~Cu z$CVAyxF(9P)c6cFon7=HD?=wi_BYYkFu z5`jxU&y9JR8*8i^-thaJw1q{;XBt&FVe(Mx&VTG9H(*MWvwg{Cy{$J?ZEOkE_6Dq1 z9cLzSze~1%CtjUa;wZSo;f(bIEJf5a)GW&3#y+Lks^d2 zd%wTARPASoV8%Qt%4PBaTqo&ra~dx~fk#MzLPL)*Bc0{WKVoLdb+rm}(^40(bVHcq zE|}2vDm-|i1O@{cGh-Px7uNcc0gq%)nzl}2t>BdLd&xI1(=(qShkc3_FJe5JFMU#PC4 z&HT8~iCKRP{C#rnuU&6HBgRu&Nbxo?ce$S8Ob=Cx*I;lA2%kS``3()OEGINY83ze) zaY-;zdIhMtmi|WH`8I{FwNgJ-G@oGJ+tPc6Ca_JZQV^k4h4cyma5oCv7vo9K9eUz&$!XKWrPy_J&)e(h0EzH zv|(g+6qUz@dG|$7_WaE1s=@Ke74kZTLK!rntoNH{Mor3t4V#*8yl~BIpwzxPGk;## zwXG{mbDpzOgyE(?Fqy$3-zNf&r21y2+P-8Q%V!``&(`>Uujs_dXO(fI(gDGO1&$rW zBQyl%Vyk}~gEf!XB+8riE``Tgx`nrKfO5pU6IJn(hV-?ej)88XY?U&-`G?cG)lM$6ND5Yil zX{~kcMH7hB}E*Vi)tv2HTAD|tktkt@acF3 zV)jx z5?q|MU&cE#5bT1m$6)%c7J-t%zoRyPE{`8G**Ymk=6fa}ku#q)f#R3!X15+y$%&_f zjZ7oii=kv@^Xe?uTKm@zSFt!M4BGMMurZ} z`O=^e)S$H*i*RRwXY ziF~1XuOLh+#w*g6q_*9p((UfM=c0-HzwO8jLbgR(&a?x3bpc-pV~U0_kpZiOYPGZq zdX8P|D1(@q{G5D3t^gqn152at!K0ts{z4uJ( z4!v{=QN^w@$*m#Qz?LSZ!88HuqUE=Ts#8}cAiIKzRL8Ym_xRu|J$Yg1<|WS2 zMfU8zCBh{bka-(UmdK5^-FrgOq%X6!1KlKvi5N3DOiS_t&Vd_oqyC+{5iff+I*CP9 z*hhY?a-aB30p=OPy)gai+jy7!K8Ird8XM=h9o;Pybtk0d81llkbp^A!!gk+O^lO86 z%lzo0N@C`VoJ)ulpn&rG!*7lIwzBs@idhq(ANwuwkr0WXU-RlVyd8Aq@9|_$h13~o zVv*kuxNN`B!C%MnZqq`Ttd#boK#-xy)yb15oQ$%xR#@OEs|%$lH98E!YE(g7U2AJ4 zzv|m(3%eGcYGLMYlNOD;dgIZ+i@V@b5YvBUUH<#ltS;eOfu25N&D$mQ&hGX4JaUCP z;B1Nyic`hDC}pvjY=-lI{8*W zUox|TJVnfLzg9mek*6^wU_~?@ur`?((W%0&JD8^m{k5|jXA=YCP{mcpnHcF?O_4&J z|Bt0wo6u70kn%j&k+&G{5JT{8aOO8{Nya&^a$(Q*J5^YnLFjzvABnaWc@sB$RkK<_ zw0Sp!K&0yW`#$LBeW8tm1Y2K4jowD+5+uhM9)#F8T`V#GdEZ@P-G)0C!)tySzR>s4 zQKXV-oQ0RwHmZi2(IiSmZHxTtj%%sokPhUm(MJ~(!6T(A-bVV>?^y6}GFF0-bcwwy z*zZ~RUZhuRUuag|Jf$L5^;v#lLcGXWTs;Aa*=As9UP~r zdG^Z0hiEX4McaZA=SRNyyGjauE%h{*=#m;|D)#IW|qWZzHQPKN&VJ)#oKmI3~ zN>!P&YZ2}{o8Jd-eC7(~ys_3$tB-M}RAqO1TuD|E3LhCNaS@p<%%W)&u%X?840MI| zxQ6qeaL_e`+@e_v#XLOD9_(f3D@EomAR&Qzl?E4kq1(@9J|;(xj|cpc?(XXE%H>-e z3*4)xJfE0vcNBoIRwjCEt-REmoDtf1o52UN-Z!+l>SlIH2Suw)%=d=J!TG$)x7GJ1 z_Iwv8Ega>eA4=|G$VyHyl9;6qG(Sagu7gY~rQ0FoQ*9@D4kIXU=qI@T?(nCEZjL#)k#r*6R+9i_7=K63ByWotD21+kHkFe`abUX zaAjHA04d*$1O_a!04YHL%-YBzfw0RDG~q~njh>!`G7ax+50=<{CUYU9q(Z3q12wNq~nFy=Y;>k zjSRH>1`TthCitqg%$IwS*q?eO{d{hz_yiT&Qcbd$Z&E?GgaT=JA>j$8bP~RfC9aKh z*b$_P#R?a8;DXBXk{Zl%GzZ?5pAwtsTjeuDgC27V8w%iLRT15QD$7e0!6OUaf6dj& z@5&H|m~(=;^R|D+tcK~K`+&x4@Z@;Yfr9=1I`@p2L&&UDWRKyCmd^Ve-=;b5ao%fMdKVaj@f!#gwDO3=2QavDSILff}43R;)1Ea=!R~6DO6D%&oMnkt9U{!AU4BZQ#Ohvk?k< zE=L@#$p|bawDJ9Bng~!jylIEX_rNKwHE^7`B&5FfYrp;GUcZ^Pfb^~atx~@RDBxY` z6NW3s1}a=%r;QVw$@W|SzRKjxhft82Vd%Ut@!E=GR;u*0(}+8(SAEZJYMyDIg}xi} zRx2KA^;HW*w1+`mVau_mMlKiJ7KN!{OStfvh^^a2Bgn=2D+mUhqc7Of!L|i#jYu7o zBJ)%n@(4JdHqA?%jpX@fCnEFG<#vcUbu}mPOeyhdNGJGjwA^{sh^Dg((p&-)W&oap zd1EE|Q~h$)%Xq|b=(pRE&qH^47ajW29lSq^e} z8~Vd7s_eNV)!b|r0wzejIiFl&@w#Un@q9!OH?9!_Lqk=9@Ptih!hXp6<8&#h9<<}A z*7A@=Up^_UJ!Cb*8;R%h6vDm6BEa*tdaXXA8v|6JUv%@u@lS$y#8GD>`1qX*P3SP< zC9>7ZIwWIG=4V4_)l+?Vn(}E<~s?-Al{L*J`?A34*oMUIp2JBZOd3eq#i;rS(*nIqX zFlQS2C9$J(bmGWjwkOBNpL4GiVz$&JVbEgy>uJ$s|HqQX*p(DLTVu&vrEw=f+s08p zdn6bzX&r-eig$RU06jn45{Y+S=My-Hri0$Ozzw~`ch(e@4G$Drun&0(_ctI!DR=Wd z4wRh_!`1N9Q6VCO2(qM2$K?6 zesa~ykoeYI^4ALVA6!0@I>mRja1mc1_IDSJb(=Nk{|)q$eqO~BxF0$R0Tmd&@;F-) zZD9v=%ykW$S`bZ69&f0A=9bFX7T#|SxKFwM?$)N91Uxd^5^PUtD8_uw#Snlxk9vG~h zE5}3Gj9sv+{+ZgAqVT!z6vzBYE*Fu%N~vN`L>S=;Ph;mXVGKqh1e2S9rOASwPLyPt7Gt6qpc<8aHY z|6ihYV^_(cJi^Zc&^WG$&A7M1#cV&MP?Px;Mu|v*oqXLfQDf|^g{PG@W>m<_Q4qQ zrUCX+ueR9C4|+D;XgRqrtql%C7RMbFYrsam)a?@i(olSkj6AQ{UkqGKw?=@ed znD72uB^$Ft>kHdBQDHDXKu~7Wkp$p($=|x_QLv7+MqOVY$jyD{aPP4!Og{R271Ko0 z`mS}kLk_n-kLnbi+v`4z)8>2HGjPyhSrl^@A_7C%>q1Li#<=mc81OaRG%oS$&Dd3u zPRD?lXmkTme$h9`yfQuqyR^WJ6O33ylT)jwzGpqj7(O;_X2o^~9C2@2RJT_(b-gvH z(UsjiA!+Fj;DGR19$w4vDGu);?vwGj&Fvy-u>6al*?fX7hDZ5aKn5Q3$JjzUj+Yl* zmT3Q+=egl#-}c3tq^56RlD_nhLp!M2jD1C1Xp_5KS{`#lP3u_;Z{fmRwEDP^YnMts z%;jIZO_Hhsn=LcAI{%uz>pK$-E?2g3zqgi%jXL5vw$d3f+n_igpx*HTe7U{WTa@6% z7~D55NQkT%Z~1zgO|Yq;i)=sWiE>iY-1jlOzopbdo*E62nJ;21KCZOwYE0fyHe39> z<6Y8LQ2iqx(B-cJ84%5xq4MO<@#lwET<$UYhc>OM7VOfoAngiBYG)drjY>F-H>V{S(3xo_C*mp&+Yw~K~K^uuN zL&qS@YXgfq9}C!KU+;S)3znHZ^_Ubvpd3Ck>BZ4h6RgRd2+=Z=?T`})Tx+$Jv6D`V zT@`<0NWlN*W1nr$?3+6qMVN|zCezsd?tG*>>XgaVko!PX2-doDHY9j7IpyI*Wdx<+ zyA@?sk(vsD+pLm=z@9@!sa;wGzC|5pm&LEaF}!b?t;aX}tYmm$s5nct4{~#lKe90t z<=>3NC%EhaviTkyz6#BSV24yS&2r0ePc zz%O9VO+$7UfFcRMOt9O@-(z_SOs=XQ0%A9eKJy-=jul78^5>vFWY`S+X$&HcGby*~ z75lWD&eJh5EZ0Vh&im4JOi@%0#a^1l!yfhpCali?SsHo7)|wsxi#d)pxYdRAq?gTyXmq! z6vXGOSB-iX`0|39{MaLf8)zX4+1)QodmM^_2}i9qF)I}@)$xDt>R!E!X)#=HHzww<{=2!tyg)~y;6NDCsNH;l!1DQRaH4S+es9qd@rm2O?4@e~+cm?1B@0_a#ORr;d_Z7M9`192@QT|YDJPUF zB9@x*3)Xi$vjONmNar9o4l_diOxeXX$WTwslOoL{+S@$wvzptO0E^gxK$|zTAKF8=6hgz?p~9-h=QQClJ+Dqd(q4OD2?!}oZk129a(GG#gly*(&`kMLo{xzX9xQZihDq$Y}qV4l9C zLo5U6j=5mjRBUzhQxUC%9T#s{F;$hLfH()5PZ@mr);G}t*Mehn&jg#j7li6ptCs8( zol5vjBRV6=E$`fJ2PPSAA#;{L^uWo-Bx}~Aoj_j=_I~YZhXeulB=9iFx7$rU59fW& zPW-zRMqh`uADeDgh_F@^v*2+}d=n`}o89lR zGp+vn5aJ>xz3p{5{#cN*EB0U ze&jCOri?LRrEhwg`hH&d-2+Gp{uGehC(jRn>JVfyM`RH zvaNTJKf(JN^TUgDRh7&4r0ujQa+W>3)WvySV1gqG1)arW$;~M+X8L<)_i;tx@a^YW zgnvTXJ}!5b<+-f~WQ;{=OL=4g`nI(&)J3;u{7=Rz?aCDiMwxHZz$rW0@WX6Snq0@j zKsb`59RCJ@Sl;=oWmT$>-14?RaHSfq;hIf);t+-`=$#^id`GRDQ1o2)_p(*ceME6- zf^;9*PWmbIGj|)IY~e(`mbY4nvT;D0?IJ_Gf7zUB~m-}kjc*ZN@V>4L} z9d`7%GOpinj|88`2r7hc@BB92&J@$j;VGyCL!Awgfx)V=463*z3k8wbFT*zI?*}7!)JxhUXf2N|M$pc!T zbxG=SQa1;g)2EPX%I)CJQzwCnR6!gtZm7PP1P$i@=!Msl^;X7yhsm@ z^!Zg!cAPvO1X!JyYx<@Ps2~|rKBeNprzi!g-( zx+Au(Z~1v}Iui0FX(nN47Eq|&+h}p;1Eg`oIhcWa#WjxIf8Uz=ghSS8zP=QPv%U8p zZWuu(IOor2TSl0=f9q-4MJWyeHphm`%>=eCAnf5A>G{^2*wv&l$3gZubKJ9@8ScRM zH;lwTs(HEtUw;te;7$cx;HaQJ$2wb09JS*lj<{MgxgGC-?*D_6_<(@!ev-RhV>w@K z<<6lxf2&+>-=j>yLN55M`@x_Dt4e=H=aw*FtDmg8VL}V233EiWKb~xk1m2`e%<)^>f7xo}%G4=UZDy zpy4QFW1|ihne_qIBvn5BJ#jqqMD7@FHhFzu(T}!?BBExxUC^6DRbdKq!^zNmxXJ-a zuZX>aUSze-&iJV=*9%oVdI$IGGNpHeoBzbNi;o7bQtUEU&$A`%psO87O8CQ{Nn zGX@Yhq&J0%=+2}tBYzLT>LyPis|f*Z7u(c%dWqy?i6pd+ z4dU55(OD$?zdl~{n(~zLH(5m5220@zlBjO7I(EDv<(Ri}ha$zkPaiWKYB2r|=a8dV z^14dQjP3ZBmiDVOycTCN&zyXxNkIdWyp#_sUhv>jX^_~4F3Q2>efw8#LH%STD>rnm zv)r!ADo|LrU)v3M5>dY;g=u?7bpE_6Ue!YtG07{Qs|S0MFVCG_7Uqqk(854IXLWoK zkbhhr#Z`weAKN^*LW?`z-gude+Q7uY=lgqqw4IHdF|oseh|&$}#5+#cMuFN6ZMF_8 zsYK#)6jACUv1@JL(ZX4fw;az=6n8FX)zNhZmRKQS<(r4l%Dh8iXqa1;HJvGQGMr$@ zU~wAU!@R~)jnJnt^=BLwnB)#K0VAj$(5sSJgK0x zx&p4T_(b(jm84u7v8T_kDP!&9afh6SPqt0rSr)l(NJ56mWHjyyMvjFkKlb~n0K>~e z#bJBF=Imk|VRZyJntghk{K8Q#EPUCQ!zYa$r)HBk*{pLsrb^X*>hnUizn%l0a6#SQ zoehEgJeL+xBPsi-%$u@u0c?IuELhXzttn@?nhiUq-+`4F?e}^I7(_6iI?w|mf9#J{ z9o$X)Sp?1u|IV>3Qp0Tgr;XBDYaS@?V$*2IRe6lCM^kSCG$3?aK6#12A*2Y|tdd)i z)59;t4|SVwIaqKp-4cVMjOnVS>;qTbT_9UxuJHW4yul1ganqK0bY=noPtsUz(|p8&#TfFBY5Q%L|#Fw0+iOO#M#?B}$6x&QfZzI7B6M&uNERJ>)lb&zEvCdvYH+HijD& z)$BKbY2w~)-Bhj=rX5L#SDHjIqE%>0zyUl;#~f<*P$`HT#-B)03-1gcL^l!Us4IuW z{5-D)(Rvoep|Xrd1kA#VEfb$NY=3jt%G>y0(0*_Q^Rb3NE&aQ_-fwKcDFEcc=UO~g z(C&nVD!S;tDeQ_9-BszRySh8*xnH!*;Vj9;3i!CXrFUbq-$iq~!d7nI6T+Kar8E1J z0+WXd5;w$0XIoHiIBsLm-@?Li2>KXdyaDhWYq9vD(J_nduj^Y)R5RW`r`8HK!XfG- zu8fx>Zrojn;)M3tf3SZq9$ss|9veWH;{l(e(-wfM___HR13?3FDbkK7omFwfCJ5lv z!^#j8xF33qo$!Hvl7BJ}5hADT+Yu#uQKFXh#Z0lE1Cx+8P>s-4j3j}LF2QbDkjb*f zsXFzgXL9Ir42{LbDa$~AprSI!;$kg%rpIthrlV4{=vJXwp!@6MLQ7jRCWLOoAX=4W zi-<8a3RO9VeY>)sKU%W7-L5Rp&E~Q#76LqcT1mro zEBzLI!6KT%P6a>cjC8E?_VW6tRcUj?sh=Tme=|4&`*?92!K@5D*FDE%HPHMjnV>23 zmTdt9s66io!a@dgxelYt74?G|MKh2;DI6!O5Ht*>UkT?N;p;+4=EB#TtX+h6Zs$22 zqr->Z%vLRb_YCu6=<}X6jQdvaR#Zc~G!?T=xgo17>_$a;Ky5sWzR-IwX(Oc+9@t9T zQH?O4eSA#_g~H@1Tp`S$;ynJU;1Y1MpB6|U$a-kT3qP48od@PNJLv`p#gtIfd}Udu zDBnN0g#CtzwpB)p+TPQOIBkh79L-1%mpdqd;-qD{HoT4s1nk*8bVoi^U|$-T z54%PgsgoL#AFZp1SZlOctV&Ez9z&Ab zGi0+vT{_A=PT@bHLZgLQ4zhK~1)1NQvW`2Bol5jheTkc-BAny6V}5&+cyjO#A!Ozh zTMM{24oPN2^xNn3$}200S8YWK3K?e(6gjO?(Ei$Ds|Nk$-P0e{)IVXh7T{pNj400d z0-d0b=5KJsGqNdmY)IUk=u~qsIL@6K63O(dLi8id4dt2m0I5yiL88ES-l3Yn-+`q; z&`O5a9_Z6fR5BEfI)@ehOGQe9a%5sdWau_gV9<+TEANYdKC^zd*Es0uIe90*he`=g zl$S!QU2*o#LHT0XxytF9p}ifW@6L7oWQgms+)_ljMUNe;$&Mn$suXFpR30$$Nhy61 zCnxKgP$@c*7fQi12hXS2|5hL$z?&F2)h8nH)x+aq@B;uy-4eU=yUWEU<`_&^x z-+2c7@ycG-sjjE%aPU}fzVs4QO#?Ow=6pi-&iZ6N!=U~0I1o8S#UuxXAqoE0o1@!P z_iAYU?m`cd^vXdyr=9;t+h3NwHidIqC0R>}k-pUY`eKo7lt#;w_0pq~$N-$aRap>) z!uT%NJ!2V1KJT71zBXIY_xtt#;K7u*$xAD3eLwh6S#l~AtzP6@bRw$?;EsHia8Y(F zsW#kT-G+#!#sWRC$+>mk0^n$x01;|X8*_H@+JB^1p8GHQGV*j+{#SihMzQZla^P6D3 zWEJ5O$gVGJeg^vaqVZ*=iPA&IdpA+0H}s4+gc=idByu|>qvye|Cr?45Dnob%wdp_^ ze=W+KQ3;ki-KtuYXlLEGNjI=D<_YLf5(`L95b6u4jv`2_-YllZF&uu1jCjb0rbAt! zKE0g}^G1#vQwBpq`u1Q`@Fa(lPtuS@>-5_3XXkWCTuGVIWi(eyDs0d@8vu*j_eotTSGpd zR~|P2m?NnuSR!kmVM$C|ZEBRjg0c4RP|u^GZVhH}75CH>KOv%FwDrSQi? z9(>_&H0$EJI8KVUbAh7r$8s)r1~K11btnxfw?M5z?S243uiJ9N^!sEt^{tvJQHaB) zu=vn4o#NNhna_{1h21g1K_n{!mb7z>gYBsji;6<6cz#p{yi1`6vcFS67AKtD0xg!J zm!7h57~iGG1~XS@H0^5JhbxC>OWQgN9)64QD5OwGk7DAgqsFL3OSiiPI1InPx5BGaj>G3hvN$=EX8ax*^nFv~P~Re6uhXX4kazt$9?Z>9u) zDL!8T#n0;E;^tchS2U#jb#{v9#TR`<4#UrxiJs#IyWgO@c>9bx4$)SkeTnA z&jS098y~kYOLmcQr>HyGy%ED3YZby3?@U$<%04}xRqUF@Gi-@+8rHC(9xxjaC#;q{ zcU8-`K)>GsOR}c9;A1O0u>cC2DOAGcbJCXz&%OGfrHY{LR`fl+&#Xtq0n&8Do`2lS z!`|uh!ruqAq3%dXZy&517u%}JC>~GpCAO<{8piJcsoQ_xYsoqD4rRgZiD*n9W9f2NEDO219J1FS$IwlA3yi7O{&NOGEqVte$#7zdN>$#H_8Rx2&^Kc0xR z2_)#4!E%ECadz7Yx6*&eUDqa%Jq>K)k;ElZ=zag%oK#iKz0!;#=2gaS6y@z&#mn*m zZp|Uv%W}gY(@4RXU$yvdcD3cn`?91z=z3-U#~`va)Kvv0P!98FS=>HGjvfk+q}dU; z{o*tq3gQ_W6ns`;Da9B%^a zhw@^`7Jicxoi|2FxqfP?aMl;l-x7s_{!yG{(7U4kzM1ddb}#?sL$S#VvH9Fw=woW4 znjz$hSj_Ni-R2C4=kF?>dwVHA-{DGD*ned&ygYm7VjY&Tdf`}~epl5Cl|Fy$Q`u#< z53^EsZgrKzD{x<(uE^z@?MO=%EpoN30V+al-W-Iee?nS^mdHPMF!Lq1;)jShK(F^7 zUUBK7R+M#hsaAvBQTS8-t%2vASS2<^%4uMaC{QUM@Ht+2jI2EkV;Xey+eHI<=Gy?D zyaTwIB*lnxpR$;|#G3piO&J>VqQu<7TE#- zLmCKXA?2e()((q6+2{5H&IHXrmn!Fh{w+?majk{;u0jCzK`mE0HAub=0A!RnCKYpFALG6aPiO5#v;?`0iZZ`dfm|xZN?4XnuE*IxYXAdSQJ&UhLkr} z#6#Ji9_b{G#%uYU z4udJ>Ve6dND#^%D(1vwvva4Va*7*loFokdkx5P>98jp{bm+c%&(uW7)LCmc*y9QKJ z)(3FOZaWUiMY(|3r^u-U6+=uAbtpH-Qxio?Tbuv+X74q$tuBwV^|>)esDHPaUH1GJ zDm^3=mY>q$%#+A+O zrHb_?I*Aw-&*`s)WH5`88V*!y~a5|AE znY^Bcx&x_pTe7$PqL}&8Hqa=~2>K5E9~ZD^SgIyunF&~~FvsWk9ix7-HxHKcVE7-?S~8vnHvaJNUqFxA$H9zIMKa%LJjKb9ZuKeuXoN_Tx&{Wu(a&n7`d zYW3bL!%K5D76So==W2G7T7oZd1*-2+>GRh?wA@6ac^lt>)fU*gJLTr$tFAEn8b+Q!k2&yqBH^e7&e#mn6^p6}cID!4 zX#3UiP=1-^g|c_DC`EctR6Q1$mG~DOz;a?^=>B)*jhL{~;@)MQah-lsDH6~8dcqAx z4y^jag6>`ZgKmx>Nd6u)+j|hY3}KccONAOJ3EJ}awDlz{^Bx(i%5xI5=G=3 z$##jm13nsVaD7Jnm>+#NfX;0%7*erJO7LO3aG%b{&W{un?%(CObLX3VI=KyK1&|Zz z7HrxTMzqHnhvHT@-cEeDCn*FR5hO?M;B&l zStZWn#tD2>Hz&5#i(gDqUtd47I zm$PH39Mf*mzb~C|FrK5?z!3JDb-PmRn=xt}w%O{ak-16O372~c@EoR*uWO>S@1IjL z$dz}^3g(V#)9CRdzoxJGZ{Y2p#?vJY1v?OX@tP3m`1?oN-$o2Iv|=Q9OB4^`5=`!w z6!=bevzv^~FW049V4BmYb-;A~r>Lo`tpeNRo-gwvl^@{frkZ|L$#=Nv#ubALWEOq& zimDf9@veh5n6j_jnE+qhgz)O|)N3vlcGg9&GEQ03zP$NF)$wWF zWTuDHHi+A4TAlumZ$wC z7W{4g`RvBk(a+jkb*I>iKH8LJS<1wZ)D0)hor?iV0Vm^XK9dlSXn=eacB^24H z)jv$yI^}&$3Za8ZUK91yLW390Pg)n*G(~VL!!MbcHba-1QgF4jdy={X#7oRVW0<^S zrbvdyRk_uvIiNt|giYP6CFb^dpU1C~7gdSpxAf2sJW=PrW>aBnBKnA^`%v5)D9*>c z2j*DLg26#2FD0fS#|qFI}YPhl`0@B+dtiRAD&LnnOz%bD+GLfao&!$ z%&LGMh%nHSk^vBHOlo^_n3zvDW5Cw+vXgL}&i*AZlkZp%c1JrDK9!OV^jWS7Ns@J3 z)u~&et62dUma_qVBFNmUI$2kjUIJ!HHh@RmN}8)A`cs|aWOp>tCD&tGgZgFD7S`~( zU+Rf!hEA3xg}0tM#WkPjV;PiBjdnQ{b%{loV&#dUBg&BHjgmMvY5SR+2vuRzw~N6S z)=e|Jn%v?f;*70+&C!ebL<3UM^lFk;cUAU(J$cc9HvmJmZw*-E{J1yo83zW}h%6TG z&9=WtE#`h__#>#2;?B+aAK}1Gj;8`+N%H3NDz@GZ_fcgVspj<7RgE0BoGua;s@G_v9jDn~a#dp1}Rcrq&R2Q&H z#d3rR6|oTkHkCq2i9f%0p>K$dXGy4wglm(2V16hc2S@1#X3IvzI;E7&??TCTkPr}V z!&~EW%)(=sXNWMXNyj5IN41ai&*NJtJwlNw$jb#&JsF^d6vI`6b_Ft$tSWf@(_X`p#8KtRDLA6uvkKj#Iy zWY41`6lU2~)l8$O(oiw#mV!a(WJ0- zT8MEswn=(Bu_GWv^O)qEux;bAUkhD`biP^Y%M|yrFz`S8ZP#vrQ_9qKG~Hk(tHpTv zxRWro?BXY%roz-W5=GRm1?F%vCYtQxrQu#bExAQP-Hrju5Ovz{ovJ@AD%5{M#9zw4 z>@VT?&>hxY8d8#1l~()i^nHJ}>2e&q6Fx!HprI*V5GTyte4S?lMy3v}q|4TZeiS)a z6yl1odin(?p-iGQiDr@E*f>-ph=H^DglxK}B}(a=OKwaE@?{i2jENDgk&ZM58P&dh zJxRimC#9_m#gL$dg~lBpl76-=-*Y`T5Ua~N*geW|e755Y_{nBGuE?WffM?ncv`u)P zh23{JzxNNJSF4gyRB9WkE3thKo7(hfyb?pd-#bta(ypb2m``&G{f3&>oQDgoLE{Uy z3_W6lQk(@~HaeqQvZ-olnn_s0)& zc*Q}EzjM@Q_QrqC5AwzByfNy)XRrjsaiz4tH$ z{|L9m-R$j}bG`hJ8F7HW2!h;{YiS~GgKI(_4bu%gQH?peWodrbueAFN9bx^p3z?-* zS|HZVS)CT<2fpr6NPDBzBLZ>V6}BQ6(o{iV=MV+yKJ9&n$*Kw~17wPHncg!Sje5O! z;}5=OwTi#Ipx3v2kq9GsxrYRJlFHkr#H11Hd}51$rIpw?xeqm-6A*t$whXbjB9F&o zxq{>?0q}A%yVEsVh|2N)Z6Lw5VIT9T9F$qi8_vzjT57q%rKL34=@!plHAOV!4R3sD zdZ0vB9WW-K{GzjPqK6puD}LW1IB!fK%M*ZpS6T4GuG`kG&r&^>`}CyVzyd4J@tFAt5^ z}8P$t%Wz0zjE& zWuZQ7FIRWl2mIsh;o^Pan@z`FKD45-kS(eCr5RUWrD|Zs*#4psk(ZgGQPOU+tMd}+ z(XRG1n{RL@xqTl<2mzqqqHw4m;1|WppufxeEDr_A5xjIN4}7(}(1jXd8u+i!2D44g z9-8oM-2Pc~M7{L_!83fVN(~gdNX?{?7YwaGP1`Bp^jO*NLdU^$dByY=poX#Orf*iw zLy0e9M8P)iCKGr#tJI9XvY5Z5tCM2!`}vEik3Ma(OGNjer9FN5}*?5M58-Npz9q-iVHfS)ClaA z>9U+Ulbev};9DJq(bFB1l%_>w^m+s|G<^>dF-LQ-c`ar%icIA9uK}T1^Swq1eMZ6! zD=3J9`mn_a^G3q4-Sgl@f&ew8b>LFG>qmTstCiQiX%K3- zTh(CPs&v&T8*RRl%E$TL(!fKzHg!WH|L)_cq@mXgU_8m_(IG9B0hOBW=c#F*M>n_{ zfruY#pxKOuLWjcPVZQ;mdhE1086l*0s8W=OOaYkB+`zKo-vyN^Wbt4TAo3B1uz6(6{%<62HqAR!YL4C-7dTuG`V z>`IVUEa(B&^R7{7VZv(uAN~1ecEsQ&B=e@ST^ZS5$S3b7xYdNNc^}$B2%955#)RS5SRMwNLin{Kq(E(OPcRN2Tzo1avGx zw!|;Q>6Ftp8(fk5oo6iZghM$t&8zy=B?n^rTlqL{n6HE^$xLKzuBg(TDDMQIM^p{| z1YC15)B*_SD`ETo_Lhtrk8-Q^RFSQfmqZlOIH%!=R6u^aP{_NDetJ?EF+HebJa~C` z&-Mk8Dqdl>Q1W@F>s(T?Njw@ADZKq$Zh7k`-aAtKUMAS$L4j;cvTq#2H!_;^mmiU+ zi5dwLm9&wa@ac&&1i@1mfCZD+QmWA#)NG!0c&Af-aMkq zXFIOdCtB5NGJRmvMTwmW(2YEb>MvJWW%w<6SKNsI)`BcCm7e!ZN+R=gbu21efxDjS zJDVXCH`)|Dv*d`j$xCtzug896ei=1yO_y7qq_fHP*CnX03yB~O3M$1Q<~2TJUW}Vu zfcJy%oEO-(VtE`qisUrwF^X@?;aJCbSrMYT)6XnOZATCX+_6HkfAHBDPXkHLD$l|4 zg2s4d@Rv1Q5g^=F$W}h#gOQ0_?C?qOD~jC7V*+!P{d$q?QaIArv`RgK1LPYD7uhl= z6)E;)F(Pt*3#+`7rIQ+IpH6m}hrgh^YO`k=a#as$_si#Lur`d;ZrLr+LyABS(CrB{{KzE(C<2qc(oXXWdl1<_NInp8l zx+KkM&?sPrETi^z@dHm`BfU7MUW{HJd5wgHeGGACy?1Y0^vu&m@cV`O6iiuulZ%|#euByG%J_A7sl zU`G#G@{ffLTUgWcX}9grE49QBf)*btOnvxgww@_<&=ZA1u)jB>?xN?r|DR;a;!;6$ z1DLYs)^{$DOEn|>dSA39m=q9|Q?AMcWV`ZXvE##=ZXt3`g07vecBSP57Q<;|x=H7l zD{(Ap^D1(ax9hHo@K>gG*u&TixfH84%{z~$VV`GM1$YsWQ@k{);up~N2bWgAV`L4R4{?O+zMP>!94e9+ZKF zV$r7S5&e9kSmE-b=u6&L<+O0f(YmZexzlq>P)rqa@WkRFm8?Ix`ek?m`>V7vO-)i1 zwlmnou*{}>r>k}MCaNAykrwK%AjNTowv+JruBo0lp0IP26$8HEC6zEQx`J_2V-Q?ZRJaq z#z1uau#$i=?7vN%-83i48u-eg-roI%`5+3_rP4ICQ?@-${NdSbNBz8n{|9 zCe07^>X-v>fqHZT^Sx(^K5xglAFX6+kCLkpr= z9*!JvM>JtA(Fgu<9pLiPyo`5(9^we|6h8zi6#W+w@mYvlONEFOSbe338AM7I1sDLo`0L<=~{0U1fb(@doHlJGM|vbeq9)lzYzy zll-BgnWQQPI&k+ohXQ?vv+8ePL{x6M%K3rc6lD#Mok~kTF&d+nU%>`H55DIxfalw(m4SErbMVQOA>@WW!w6Hp%T|kZ^UGP8L%CP}5c#WNIJ)w2qfC<#ATAR{53@7TS^#RZQLHPd`9VZb}nDI@#@L~zg z9UxrpBk!L?rWu>P#Rsd;$o7CqKR-UR`qvW$8xx0#;7FMn7-RyQSAycQ!p}PF+P-)& z_{kF5E-DU7AwEbEKZLd~2KI2rv-J&7$xmoc{_pLvn6~Xr`x<)= zr*c@bAV)#p6@;3pMUQyhm4h$_Qa-vtgnU>!GxvIW$n~^0r~X$<9OFa1!>7SnAdL!( z>|s#Y;y9S33%TH9`y!sGj2?i6>~uchGP4`tIy0OE#3m=h}R6{@nl7)T!!Jw5j%T{ zIJPQ#rF){KLlEm&d%G3|Xd*N1p$Fy%s}HH5t7ux)rbd}5!&`J7 zmJU{+M>DXq^;?lY3~WR+h;SC}x2yUY0_6h*>VgXkJ;DODqTvB_+3g-WQij-@p9O6X zWgq$ETXFQMVLUmiXclbPuzHy#iA+jqLfMrVlTgV@NWu6&sY=yOJwk4T(d9Q7LEMV^ zKURJ7npBsXOzWuvW(CChnl{_(QawgNt=qe4bceCMO3{|?)`sWL@@?Vx#&Wo+77ZFS z$;PP?S&;O;|EG0h_yT;5kePz>;ob*0h$$me<^_t?1f?l*MaRAS089&RVB!r>BhR$# zx|JwLO0wdx<|R2_^Qu>2EO>A&Yuo}x;N!~|S?^-9`==%fbw-?HG(kECo zSXaCp;qLjWy9mJam$02Gm@p6%%e{a-Blc=q^&gPW3XUYubizzoiHT((+VT8F@p~+r zV_$MJ>+utC?2SEWv*8rnD0bC`87Wv4ZM;(k zRKB~D@=grb4tkOrRV8{Es>K1;xU9l$tZ%ridN4y?v(x!H6X|h&##X=X7jBk}|!6A7>UZ^xw zVBPik>fvZ8Heybt6tk>^3SS@Qt z7~s+z*+N9(pgW1%M}PSNPJP&)mTbdDX}`PP*l%%~X@F4DYL_u6?ZVK(8hiBQhGEtZ zwvtfN`78A>b3lJIjLo67L`?~03~=^<&NkV@W2V_388fY!8b|f?h&ikwp2Q1J0-7OS zYTBT7enmfwgVtRUT*j>mt?bg|ezp2Ux}#o63ZD?)9?s=cm7J(t%-hTY`0=7%0~b1t zTcUz&Eq`jZ3oJH&;;U~@6h;N z5Mup)qFa*J*qG(qW@5}iIYGazU=;eS^P^!q5+bqZ2zP4Yf z5J%p1bn$`4?4V}aUgyD8saKclmpgqe@;7xcP4~D2gL#+J{M-uurt6-n{H>To2hwlS zuTj(gGnx@EwlO}sFawhe^D16f@J5{!NiQS%J%n!;)+{84M0|l+QI=W(wS9EDcj9+D zS3I#$(!Dx@iLS9e&PTdSwURtI?^sApVU#5rE1fSfuhc;HmDNSiZ9h1T1JWl2PK^Ag-SS^BiCrhhu zCR@}4o)DUH1XE$o1*Qq;)yEs~i-0NUsfd3TYI+wxpM?Xn9Ob}CgU=Sp>zOz2>ZcCz zH&I>JvKRWR@YZ0@{`gB3n65k?^#KFuH*Wd%0_~^NVtM~Z$KNKFZZle%EaPEPV%_Vj zFu%aqhq*~rvvA~55P~}XCU_&b1X4O*Z$AkFCV$qTq%AhEmcHZ=33rnF-qY`g_pzY9 z?COn3ypSDIe9=1Y@x^tMyTR6QArM!e!G9zCjl^?f-;bNrLm@lhnF8v1S@MqsH`rhu z%HPHYipN1-<_toqYKU=Kga_MCZ;|KVXDMz*3?&-$5KpSl?bI2q)>H{|U(Ok`A0mvI zcTuge_?g(^iqC>ZNfERInfxhHlL+Bq6`(IjObOCj@Mz~GaSN7M-72^_vp`s_44fnn#_org!L zY+F1^a_^TQ``an)*Uj+=*@ztr%>=Xw>Jg$F~|&%lrxc0kg_m(Dr`)JfjI z{fasUKRX1sC2yPx#@l-8_WY#=AG&;%dmROk5J%c767JRU4w#5cK;e=JDV`T8RcikT zIVaPTEj5?W{!7|n`;F`FVFc4samXF5k!y1$f#?*Z$XZ{rihj>cBOmcA1~S9}Tod_; zZJT6SFVDFovvfTndL-QPdUe@S_6glANe&Kr(P1jUaiXpqB$OkH>)37<2XPEXdFEAu zs=YzfheKG?^IQ zEsU^?4#`cwH?}}<_)wR&C3TAQdrV+Vcqw&o9OZAuRCi5ppmn#a3Y!W`! z^+M#^em279_tpg&zk|Jns~Hpub8hBLaUBqjQtwk+lZ7Z!FqzHelfcsys{uG0Enw>)T2ii4Px@ z8`j%=SOkV&;t1jb6(k8)!B!v_vFO7xwD5i{ttG9BNyNLoO}^cyW#FO+IA7^@L(m?4 zO7{ojk8N?R#VC`O0|!BJu^o1coafRmcovqe{=cud69O{Hnzp0rZ={rKfu%%>E>D61 z37}^}E=L#Q!9!djvAw!J^x{IO50chfxG7hi=;=Td({7a)uVI%ul`43*)avRm;UuK^)#lUbNIgV6UUk}n~|wF$rC0Zf$XXIO(-pF=!Q!R9ev zJARCV#6UB-ZjckkkZv5Q3a(?Jkfpr@d+|zxdpQ%uD(>aPRo1!CvrP)jBDqW)d+165 zvB__u2ryE&RPU$ZAzYqw1Lj?Yr;eL-s;R=%o*01bb{8`0ECU<^`|A1g&e=XS6rY|( z6-4Hl11B^H8tyfWn^InEX^B?1VYMlvuJVqfI`MfrJt-QwNzM=E+ERQ}YPm_(-R=rv zv>)qbv;v1n{MSRu3jqxiO+Gp6^Sj&|=h-K8=09Yo9}Bg~-iDT2dtoPvof{ar#n#^z zPX`|z;+m&U4?0GyX!&JWr&m5T?lyF4m4wW)|BtJk+~wIBwL^+Z+yYUfP>{@YWfj;K z?f1aajFm;hcliJq3@l9GHT83cwfxu0;fkK0Zt!DIB2)XQVEJL{5*1u^%FpfB^|<1<<=flZgm*A_v$kbg&EC#FpeX0(ndkU2=C{+aFhit?!B zrksz2#xI$g@N**K@o6j&MGhkBVrm=8KEkQoKJey8>31lyGshd`lHsr)$21uQur;Mo zjQYR=66Io;F;*9D-$CG~sj8>*i3=3$oB~T-aGbZn2sMe#nApC}{Q+SfG>P|puK&DP zLG5!P%sF7flDkh!0dlm37kBd#fPn%^B==TYQ|xT1&~&c7&kICkRiyI?OW?NA|0oC| zKPjg2A5<}lKv8-?%cut7p+LdBz4Zx>1a>J!gN4_)b6Uh7>?S!p<@E4P;&LN-Ku5%_ z08x1};i~cG7T}vtXOu{k^E;-N%MjN6d!d3~qfeSSqTkQiHlVy}O_uPns;-<)6b`S# zd&z>;Q5G$dU! z_s>@Re|QkHy;gc~Ue0VPH~N3@0g1XAPft@ZT0=bBba7ir>}f^dxpx%IQn798L;ZLM zqpIly0f{Il_+%&=`c4t!Peqc8+|8O6eRWbr4*Rse?l27l#;qNGJ|L-3hgi50&XOo< zid}M{$Jw>v5+PJ2a2be;_)I^1WDi_oT{?Q>YL>Fj<-N4-pboudi)wB89hu(gu(Acc z_!z%Tc?8SR`gb^Egpn?czY^Y`vS5!3hhe%@r+oIY))kzWG_9R!^Qhvl(!squTZi)m z-+!r9_fXp7H+ns}+rYZpS*m!grr<}> z95@f@eYz=7B9?Jb8mjVk?0D^HItkEqc{j$uxmXgHJi%Dg(VVv7a{l#dTAW?Rz!AOJ z2EO;mHy`=#n9vjOC2^eYDa|Qr7XTjD4n;y10FK*RycdaEb07u3PY58S<-rx{7Z?n_ zHC*3#h<2nr%yR+JZcPoqyQvi%OkgLPIsyK!lfQ4`y;q%4ETk9KIKi6x8kAZgdvarz z>JN(N@KM#?JCh6}%6dDP;rOhE+XE`K%a+YCsiNMMk&z^{(|kSiAw&fE`)^FG(UUps+Jr9=b^ZMQ zt;y=NM@A_@_6VEDv%1O#J4+aD%2Rg@N<%@xu7`j>h9#t>u$z_!<6fVxrc^B+%qUAdFNI`eGBK(5T_UIIAz7QBJWCm4AY!! zyD}l3qhPxbtj4j_xJ2H7>Q7H~e7%z*h2E?+yvZ5xT<~GD=_gNn`t%2yUp`ijkX>Rc~5C|1o1+f~R&nuGq$x_X3YkmF&Ncb;K>d=3zM2kgT zfnM(RCEEPTvX~;58hDqsossw32?SSR=LU;Amq_E4Vu1?RX+ib@BB+)LrVVypCnSFdMknpQCwRWHrO{0OSqXRv!89S{a>(aRg?*e$?tywP zza1S0L;osEX!Y}@W`l)oF757NtPS^IjFu(2$-9QCUB*+Bd@hKnl6*5EioM^{wdBwW zKQdxce}9r;0z#pC2@oV6I1P}&D0WAAGYFK?Rlu&*ep7F9 z>;U_S-7D6%V@CnvS`4Y_=!tkIpg6@r3>;1 zCvl-yD`^Qw!!fmp)ZHs!e8Rhm@DK|&Ik^rmFNbIEz@i%T%sciCii{!g@zU=Zz}V?n zAnm@i5@;A!x}Zy?o4F^gXQQ6EE}gn{5VC*@^y;4QOI#A-r4QApL5a<#X%TU@&6|;X zUhK~~qkdV~&8TKuZHWOHp3Pz!qR3c~%qWw9qKNszu0-I1s2i~dl*i-U%PNZ8G&;5q zPw?tuD17_VvZUz~>^nBw00lHQC#=KO%h$binQiHyp_D#JUbIdDVt${jZ{`XLQ()b_ zwP0@7hVEzt;_{b`(}VVjr~%FPiwk&;jgr&@wdR80n`$(Xd%tC(?2QeznKQDoz3#+% z+?l1tRA>LWs%!vx+)5oxlU`BRmtPXBJiR4<`m=p4YI*a^QOZ{u0dMfI;)#rO*YSKV zr55W_Vqh0*l1$AGKhoVC(dnKRPmC~Ngk|V#BXMLqkY>YBeI)scug1u)U&AzE>$yNRW*oj;&3@-9nL{BkRnUP{M#o z@L~ynM6h~}LEltg@5T3#oU%z0sgp_$DNHNS(9}%%pCaio%&|NHYe+JYli!Pi(hRvI!)d%pqXq!;5MKI@+8vtvVA6f zV=zp#*@`9@d-l|+`cTwQm1$>%s>5^#UlXQM>FRU-C+;fL2U9CnXt;QUomORQ>@}b+ z>2%&Du{bjcjms>4`z#sWpXYTz01_V;Iua0Kkg4~)Dq@Oeh1k-F?Qg*26PJWo9B%tj zuFDg1$~Cbq3S+fLBri7|qdfU;IiE`HPNWaQhq#&c1q8ODG*dX4&hmyH-u~c$l053y z9HGyvonhis8)iU(TpCC3-)ldEX!TAmwIMC%m>07rB-pA>oF0;RqN8L7SiH;p5J7IB z`1iVkYO`8Sx4vcl?hMOeGeiiE#AMn)?H_l~yNH=wZ$8~&NwaHB$TMjhc3dpb-|MzN z`+|g<_wUiWhFgbK)f#QObQB`j=iqAmFQaek!F=PDnL3N;4g5ksBKuM9;A~qPU!r-u z!Z$s6RU`vzM+pukX;Dd)_$L^MiE(XaPCiAmmHm6AONV z{gNS9>q34y1|}PK{;T6RO{M5;)>-`^32fNVU^ZK>Zmn(J*(VgNg<*ZvXEL+qTGPF)+Md13T;Hx7 zhhc7T611SF?|VYwGN34bWy+)qWn`lFguw0Ughc9*l#jkolW&75g?(geuRB1Fw@KG*fX|cH6AA_FebCG4^DKS= zKN;mv&AUPrx))))NIecmFI39{O*w=Mc%N|~YkM%p_(v!u%Iy*_^?bAlYilYwBV*8X z@|mE?dVvz5pMXCARNgNgCsFI=DVmm}73vgsTd?ukECzq@2_U2p*a8QaJ{%=!yu#k3 ziT7NcCwk?U)a|CkB%zu+!jEgPGwT!^~!pzd05!xXN>*^)8BZK(RF5lM0}0KWYd(S0@sV%=6a+{X%nt zhg)%rpjR`{8ws)u^WsX8@S?b+^sSk?E<2NV0Ldk6bz?vwZ5#wPUSy~R0T#?_C*Ah8 zr)F=grQ{SZDDfK(t*^}3kcglQ!n0fv=K&~|X{9G_EC=L@0wreQHGJS@a>T+)Eo_ys z1;V8HA3DtKiGiy+S~{=Hlbbxu4dcz+Z>QJNnkQNOxJuiZg8kS=@WSGXqFxFcMBirI|cGpxm?xcCATV=F>y0`%?kw#ITD zIDM(TS5AAz+8qjOsN*A+@=Q)OjE$Hg(H# zuA5|f9IMNfI2T4Jk$?{y>gTbpFZ&Fhi+}h|Q>0YU29maQ){w>v zYMK^AR-V-3PRzkvsmgBBgyCix8=;`VbA)iFB+LCUl$9&d-2V$t1`RR=S7tF)M?S2*Z+gG8RIt;Pz+wk=Jm1_MMTDT zbU=Ani{iN;rvYUi`jO=0-LvsrvG<)`}W zVVI_M{1H1OL1^#Wo3LlaD}V-Fb!zGUNS#@r6EOV);)+1868gsx6I=@pWPEbjb$!bKhsKs|`MkEySfcFOK}t23zJz3&Nl=%NYp{;!W~6BOuHR%kKBa(Wdvbd=D2i zD8Tmhu%n?Hkqjc_%^rnSVJ7%0cpR>N`DliJx<5422m+4HPZ;k<;}TxB6`;(@C0>>}6IK zbfCkyy;ltx1p$)(+(wft?}CR6y2#g_?#@y-%t6g3F_*tfO+odw8h@s?hG3e$-*ehB zyVex!)82JdKajR7-D|}3sWLbfC@16aLRZx0u`d5mo*HCd6V2|SUTC){$c@?DVbYlZ z-YHKw`DfC-g3jmkB<9!c5?%R&C1%_{N=v}1i{G|ta3aUy{pLe!R-Un7z(U^{rBRicxNuH6k8ye(^ z7?s=z?pICu7GC{AzMB{KgB@F4bsSslM}**(MSgS?tx&Sul0uZP#>Nbc@=x(R=>bI? zxIn%k_O1oxz`697{9L7(FSkID)g4n09V~U8n&AHV^tzq8nT!;pf5I z!og?M!uaOhxmW@E)z}EVU+uCwv!av`af9Q3$cqpt`G^M?S^zDPAFpiosW~vn9B4P05s5mDpO%Qa(%nA zCFbHhln%CYqAfZ~&A=Fmkk)K*5bKJ%%7^Z4Q$rKtF)J1{Op9bs+JcgiL) z9xsH^oi8Cs&y`kmrA(LN&b&AsWR=XL0M_%eijLZZiDecV*^kASTAp@LQ+$~&GMh`@ zYo3x#KL+r#zbZ!?p7&6p4rfHbodkXX5frx^@PNj7;)Sd;&CeTm&!>b7=g~)UM8_o@ zK;>vYIVnP6qJ-Z3YfzR^(rPWf8J^GnfTjYd`#UT=zgLxvtm`r489=X`9`&{ZF`)0* z^qaqHD-m+Y1^jIyEp_xst8DyZjF38gJNNB6sHuss7X0i(kS}M#nMOp7@6^7B*ux`W z_d#7R^POp-l;8NG1+qasLwf`i+UlU|+qoMI7xq_$^EJgM z#JWoZw1h}ZD3}A5FOY0Vh|$4q$2SjmcaQ(&l->9T)r{Qn%6re$z`bzcdF**VH zPnreXQLplf?X!7M+4>`ErPdOf=FCdTxKbn1;og1Pz9pcVcv-lzr0M6v=?h&mwY|JcIejoN31Tk zCi@eAgZ#~I3PwiJs&B^BJC~$Lr1^qnBcKpBgLZ&VldLOgz2IUyRy<350$Kn~>1~Z{ z#6rb4WCFmg;P$RS=vx>&iW~@Ac$0ddd|nT*UvaT;|1P@cl`Y_F72wZ8gAaVeg`gGK z?$6y}vy9KmFhWhr)6SOS$AP)YKrlej1r$U1nBi+`}Ls7F}QQ>uQ>e6uS2ZHv z-Vd@|#!a+S7igQi%Q}*1+VXe^dyNuPJI$_e(4akS|MdM}w?rAe^N z6eBD7rlnj43dc)#fg09M3oJPu%^hAwbOxC`1wrolMd*qd_+-g zlrIuM^jJ~^L-jy=Vfuk?v$$L>di-!56?%fEX~uEDcjy*;R}gq~%r;Gy8p^SiE5 zf!>1~jGa3!ev5b`N5iXSU_Di-q0a(I(5X&Xxq^a}%+|s0XAKTRau4FWfWCiIfCh>&q(u;3chs=;?v!ARU`CFRpTaYpVVzKDdu%Kxk z8Q*Gq6`lsDy|-|%aZ|mTT_5BG6H2^^YARs54$;U`iTj%`K76*rC+>}?=vI>igfS2P zN6+x2_F;L^M^2F%ZQNs|$FBL58(@n+m$g8;rIRmj&o(O~pOA>6x%23WcTJ4;elPY$ zB_Jq9NXh~UdXE4#K+3-dT~^F^yCbB?aZM7IISi|U~_GM%V2onBY{1x%#v?o!GA9_<5Xl=J9 z*x6KB#6%ll|H}2!|950?!5iz*L4z7`so`C@-GMZa;=m72*QEv(SzGeGewPIm02uK< z*k9nEZ=Xq+XfVw!(mjCjEcPSG(+G>)_6i3_lpno$K8Z0bVwM@|8NIM$UR~yQ5sw5` zH-Tge5mBX^bd}mJXb0`Fs*hE5{<&%J+v6_21wsdOdej=i@3YVbR^?fcNp@lNMyl8# z0L+9T&TwhF=xKqc>?>a?zqs)-lGx*W_R-fyq7lNcfO_d+e>EImr`WV^E;D7vS9cOb z^3GJWfxZ8!QSFM@k%n`O+8rxgkN$lXOJC zjjFr6y6=$6ald}mZ!auxzv`8U%x#Pi5Oy%zQ*>tzcfR452zC`tcH6ThdIZK7!osWDd#zeD`kAQsgunH^{d89BhQ znoUWOKd`nflx>b8f-2Abd6Nt2mEd6?GTBPRF(e1a=s9=j8~_DsF|)11r0tJKnAy-( z()HGpGO~jZPwrRXlk?ST4@;*-f)WedcA2(Kq#A*x*P)VMskbIHY>MFNJrn8V4)rvH8F=eM&Iej1x8ZIIl&> zdG(EYO-=iGM*xFgG?NUpGuC{_I5D?gT$1mL8=T{21ZYSQOBHx01s^grI(B5)R`m>D z^)u5H1@xUIo|9!Ah&>}*ve+!#c)C|X5Uq8DIw@O@46LslCXb!%=j6fbnh3u+Vy?(d zQwLOIJF4y^pD;Tr?}_x}0z*_tH*ZqD_2aSaKToW7=zQg^@l_dLd{J+al?owU{@8$2 z6cJRJDs!{XDb7PoOkS__&inno#syV*t-()mL*RHJbFPcJ*Nd645@0Xdy7gVG4n!YE za4m6*8b)I%;%x131&-d8FKd7CsIYFIM*DKxls#d|ull77*0mhnos9?DLYXT=n%`hK z(uk=)oR7FMP=PTSjj<7y;#V{b$G^8V28Rg<_96{usv#%QvZ5r(m+ACI&fH}nG)OQ3 z5)oWz<_OmH5ES?0I}`S15n`k(WEcg{gZpJTiO=@^+m=U-73jFhPABYb91)U>ja zZYKrcsZSJoDTGTyk4F(ofH6qYTE@2);nz1%7YRgS| z-M4|z#VcESw`ohrhc?7+Rj}w;r!n2&57rC)%RZJU-fabG0H#S2so+<*s&S#=Fa)eF z_D0A#^`e+FGo`cXImTEBS$P~c_zcZi`(*NK!|3{o6=i-fYe3V49Pw@gLj1@6&g5gE z$hpkk$CpW~Crf0}kevP6_rcfwJ=EttEaF8Lr@~6^}H%Vbwz%pz=V6dXJ zj`1;L8S1*3igV0S7*od_M)(x(84%c5qYCSz%y|F8itF5J76}iIPWpgiwXOBIwlrj= z1dgMsW%UjT9(1_gS?sc|8|1_KL%OyQE*b+)DJ~k-pP=tHXN*Be7!)WGO-XO@@$Z9I z%Grd^oZiBvDd7%ajnN}*&*&^#1&0lMDwbK<0sX#?DAjO;>qQxB^oFJzkEL0CUbq2( zy$4}4e~fSkMh{j<=-LYM5 zXw61 zdZ_r3_(unGSBAM6iD&S2@sv zv#htl`SsYXEYU`uCZca&d?4-stq#}&?*h=H=r{l5zh^7RdMAgh8%lFjd>>s#(6X_Z zxo3sb2YsTrXdjfeczn>>oBE906S^-$n*gO}b>PK0*LPTTY{@p28~b44_>nB;XcD}) z7ajBbvyxYZVmqObtJF4W1$nfZWMFE~y1t+mNJ_F_t!#nXdF6YK9F}$(V!}_Yvoptg zcFF_}pxKUlP$goTDEZnw<%qckd1MA8H)rclo2LzYlZ_OT_VlTo1%^{U#^=_-OP-cd z4q431O7>@5mT=_LV1Epc(N}%1c1ks|}B)jOgbh8bh(qtJ!c? z{n(EmS1O}dkTf`Uyi9d_)vRquW|g>D8*ezJpt)E*niRiwgdn3B4Ew%gb&Ud)6g#5; zNcCyu(^-PpY(!qBgz_*83XT7V`NDCQeAAu))$clkOs00N}oRMPFml*Z@oG^@n$39 zzrn`tau{U69HJ4&3gs>K-V@>l<4tW#mCs3f2yumuJmI5X2zT60vV;hAVDv>#Wt+QavH0b1ls>;y%FA@a`WcQ)KboJP#fr>HSHL3&?u5}d+vmnJ^l-BDQLA$W>-HqS<- zVwx}Nca279T;KHi;8taZd(`RDVV3uW9nIo2_3D3uqXLBUmr{}-wYY#4`BV&eLqrVDd6}V$B8AYtM|4=VeEe~B)Wiyxka5Y?qzU+wPEYHJo za8$Z8<7U;1(1Fa5{EKW4h2iPEsYOS%1ofQ|_xz@b-j2||dn)#88F$cD4wPnmZPzv! z{ys-}3AjfL9VhUX^<8M9x|k^14v9??+Y;1i-U|&PNc8;0vxhmh`Mp)=5qcW6NQpDY z%6_cp-n=(GM6e{_aTA1z%!*095Fp70_wq+|%B-%saFuafA1dP*Gt?-QO{5K)>~x95 zz29R&DCyV1uVS+al}%Km+7hB3R6O|k8@$V$7*Ie5KBTG5PG*-i&oie>a9W_V-(LQa zWJd(a@W80h&Sql81(OPl_4-P2O8CTQc06vqqdFLwMjbi9Vle90h-kl!(v zK6JJE{%K^5`_Qj=9mrMy1nWXKAr&#&I4LX6(rQ6)TGh6D7Dn~7qmiv1<<}at^8IFr9Ujb*$0k zQPP-Un))Qj0nGaVv9MpPh@DA*>#3m}BW^`ayEufzj>!C7Yabxm3C$0JzOdSNb5?IQ zE)6T{y_yBy`>i#f;XrX*>K0o#<8|{0@7#kRFJew>5u*AZc^CHGYp?Gu)o9}$w{AC^+M`{rA1ZijaVN(csV;UN-}mE_iR zLMTsl;G2k0QX0JhfIIt}R?-6f@>;%&mnK5-aql6~f7qMe^12cwqA>7A7I|o*QAjco zqtAS-OEC`!jxNP!AT87Ikx9%A<)9O$Eh$_7T@fhyI-|5ge= zjKGNBifczYgEyD8F#E~n2*IGnsGm?*wgSL0k?*!!edV~EHbUETOLf3`qy=qUlqs=% zV%7RSvm(sWGtA5V&)7BXgtkC=%J-RMUth$>jLdrh8Be@WO%BAr>3+HqW@>Eyx4`HK z^}l(s4^=@Y#-EkgSuXO`owxvhC?$>%es?zM2?KoxxzcS+etRs39hJ4#EuYRjYB zQul_kdjrV_3gk|=Ef6O0W0AZCSZ6WBIQ#UO3Flq0auL+K#qtcTX50;^67KZSDLeb@ zbBB1K?M~NuhoGhi^{3x`1%w{QJFb1FTff@k!$X^nF678czuh&eq08S#(@W8H#|pK9 zeoQ(OfYxNP@ObUXoBtAua7Z!3dmOHZ0nPny#yFU9-OQ@wTk?$*_&;TRt86>t)BSrl zC2>-CNU4Db1-J<5RpoXZih#85-y9Y$=IhPUMLs$zVwcttx z;uUgOf3_<5e@GjjX&E=MJdz&dm1rB-5=z;^+6wBe9@-AVl_R{ODhX9s8L;Fk*`*{k zpOL!q`is2BiJBRI1VuraumlSrcjP3!#@qZ*lxXeORohoglK6gQ=^k;g~plXI;Kkl5(S{mS_(IOE~4+;qH&qDNDN+DH7ZNT8s8dcA*#R{Qj7OZ#(s@idDC{mH{@iO^!BONDu}WSI4f!d*(ntyp)FrU@!1dq zYz}YDRbojMVwYb5Q_Bk9LnW{KS^^bn_4SH@SMh#&Vhxf+Vm)vnD#y&6(2qa=sDEv- zFBcDg0pJX#Xgag21!3Jzdy7LK1`MS{XrRnhunsx2Y4cW; zDTCa5TTq3UQI+WI60X+TfhDV^K0Ye={DT1=RY0>|P&;EcRb@V&`t?b2BpblndxA63 zLnffVi_@dQ7WDm+9@btRlt}{8_8k@oR8D7-@5}?^Y7bB<{TUmy&SB_Yc7>*b>tqU! z_JE~RLN!1;{}7>&wq+C)XXmkbmQeRWap~Vfb$SE+a}9*w=Eq?K{Vz^IcJj=1b+y2S z{IvR{o>2Al766YV4Kgn%b`ksY_MOg=sJEk_zK^oTXk=;h)d zEC9duDC@rf@A#6?0Oqt@p`uXy7$9)~jM*#v zP6U&E$J;xV_IxV9eEMlo4ZP(W8FEvG8gYy^@jJz&ttWNg%B=f*0gl;Fl#ZMxVkQrN z??8%&6CJAnjCdLdW{E3`T3TU&c962?Jxaa-Bi1G?Yh^_(a~#i7&P`@?&ks*d_kE%9 zW<7uLdKVha&d@Sk`u_d0zrlub1M~y+y;F(rc-L3%u`_r?2E6}WcEK=CN=)(Tl<=M`1>a@L}1opdNa z9{XSO{ut@l7S=mFyx;E%fib6X)n)<`JgBb#UDMYzWfF}{VA^^`fQfu}yV`)wz`*8I zOgVr$TiSVn??XdCG|L2H-JZWFmGjl+d4?Pnn(5UfYo`UfT3(7A@}TKEzV_X4o#baa z3q%?D(gj4zF)Y&x(l{fJ4`T~&hqLk-K+s2SYO{&tEtZyQXXvaj=OKrde$0nhh)=Uc zxjLU|eY~K4SfFP@aOw)=$51IC=2vtvcF#_1>4T^G_srx04tS-gsy8iiHu&<&Qr*nV z7;s5Gi1*gBJoDmG8+Cg39?Phv$xIMDo8x031%}B~PIjNTJhL+hTbhZ+z9KF?AhzFZ zIc;kL8)uSXrWoE>>&$IqwN?$B+*x(dZ!NYez3Rb-#JiwU_ObSziE^y9p$g|l0k28^BNwye+r zuV12WGswpVJq~7ytP?DsyhnpjOIp>H?;%p2?;kXsu+sR%hd)2XE#C1y<%KJYYP=d@ z0|&ehYg+7_O&u*r-04go*1R^;jw59Y#;bvP&Wqvpjf?N$L8q^yByzjyLTC2h%?QML zLaiUdO93WNwuIqrOp4~CMlGEylJ~CAY{{d9H$T=5T+J1g?m2N}4Wd!jvrCB##R+Nh zSg_c$!Y$=TA$zl=iTR^;0QEYHNyPoauikHF(OU)3@R44XrX!?x_F$M(CQtr{DUq|W3VbT%9M8qp>S(Qx z%?s%p#j3Z-s{Z>V|H^Gx)!Wfb-22!uIlQalwuBz95XKHvVipd9^>L1mEq>bO?Xc~< zz}LHe(MopuG^YA0?)$i<9`&SyDyD_-Wc>)_+qCFm;TQR@-vI(hn-e*nWbpI(N&}yD zp+EgVzBVaabVpB?*^v;n!p0u()e6lZ^7eUkPu-ogUTMc#B*GzTM+E<>rhN=p^q4ib ziv^W5fE)2Zy=hL|3`#^%mUJI19vXp=8e}#H6D09RB0hh1z&I^8c#2dxV4p72M%m7U@Dga+sM;w z&+>Txlx!0Dv&tDH@Btp9CkF#)S?_=vf2mAx5_QQtOxh`+uv}pG4(j2O4UvWeWtPeq z#!ATp9k8T09GXrM*8?WoW6ApP64<)F#+{*-QI9ZmKU5P~?@ez6)g=}zr1NyJ)|VkT zxyKr1(SCQQ$Lcg_ZS#laq#Bm##;J}^%AXCcwCOa97*8`V<0$6_X-Ml7o#CIkykX`^ z%g~zkR$T&Nx-BzT))_?q`pypCUmFQSlBsdNd7rYCF}%`v zS=l|YSH0T^3Q1uI87Q9?PHD!tYfkBOuXT3-Oa@>sn7j>xjJq{p5(uvLon>jIeOt23{67 zA{m7uBgPlV#isYt*-c}>Jc7F8CI+7(M)9ppaIkmJ%nIA4lAJdZK^|o0CkIe0`yv~w zp4gr+kc+~u@Hkt(bi9I-gu3>o<`WYMTf6S`j%$k-)lVn55*M=gDq+F&pFr%m{T2WM zlMzQgObF<8+}7C0tb~fSaB49di)$R~oGt7)gJyd1-ts^?ZNKoS9LErCBk2jJ0whAm zI!t)@Mcs@i6~%rwm_6ndyQidB_OzW+oKJfIo4&RI{wQ{WR&m56nmk-rxmj#d5R^=u zG>@;r{$GwSu-3@qjAPVQn5!!#*9kiIXD6;Ec99Y#`eg3P?n9Jh|t<+MJvKq{@!!1J}{-FY0^W3oV& zAj{8VoP6GQ5%qXc$MJ5Bl;yC6(mlo6Uc$UF=f2L#!RC*S@x(#Jd9oEfXhs-f%dajuHZJp@8jCsoU^sDNubd1b(@Ub{!56P7gDJ@sLPCeT*c}76aY&?# zrPK|@+7-R6120VnR<$UH!-1aFtf&}QuKK?#k}>X_MDqxKtH>q!hWPM2t%y#-sJhdZ}0KzN^{Nj>Zl z7++9wIdQ?pu?a>)T^%*vJY)dG&KkN=KMMRtilsX1H@jY4)UlN5#! zoZr!)1~xTdh>@X}csJxOM1kCq;&oR-oV0tQh^>RPbZVnnN+#_je<_D*qvvP#{yp^0 zaBY+EZD`IAMU_aLqP_sRS&pM9n;oVvn6nU_h>JevjBuI7O-dt&ZQMObHa%+SYJ#+E zzaOdE<$viC7%upAC#PNb#T{V0On;tGwc%Ge&f=hk7u&))WH^TxOi3lw+t~s_2-7fQ z3a%ls2aZo3voFKD7`>#36|`kmt5I-v6WAysHb;DLr`TAhw2HYB% z8Bg9BKjn(av@H)*?W|3EXzd<5nVZ`BtFK-hOAWcwGU5k-Fd)cPQm5v+IbL&^J53&n&%}_ z>U30w{OwUQp1^{(1YB7!JPy4I<=>|m9ur~&PM!yxkR)Pi%4TWJ_iN<0MwprwI|0;X z5^QMDU882q@xw1+DNbb}d@5n*ReRpoqH@2R%SS&5bsROMx5~O;x`dJ|WeOn2%O0VJ zLAu19wgK@t7_@7n8EhNk<4ygTQ{Qg!TWeB1EXoqrl`PNaa&<=+Enyk? z(Es76vAxq_9yRxY!WM^6E=Ls-+an+hTXZ53aVRsUU%_HJAU9>>U1(9N{7zK*b)foD%cK5L9)UOh+VJ zKK{b-0fNOwA~{vx>u)x?Yys4}9dDijeOi#r!!hv%C&{adw+Wcd(?ngyyy4`Bxe~hwXojR!|iq?P8xS^lj!c z15rHaJq>U>*+a7Ieb}sSuRcJ-(>z*1)+uLDIV+wpam%Ei7QS^sgD7Lz3D^Z#O)q-9 za7h$)`{l^p?iYOPCVsTq{v3ho-v6y2zzTkrAQP!V_VW^Zc;bAIzB=qhUEA-PwY0A^v z<$GV@byW|Rq1ZpB4``6y9(D`@;D7DP*x?|jx^At%wnzw8$f40_3YAXpe#3NQ(J-M@ z#ETOajq{y9@2*ilLQkL3SZDUh)!|+2PCB}?Y)_6}<&tZw8{Fa(Jn>0ghA~Q*f3|;* zCI-cqt)%14>6Gh1@obR*X<0MW??N4B9A$r%O3*>HA~AZ6h>;1MyxbTzwF^E6&K;8F ztVlHSW~vKXjvIesP93MB@|k5tdzpp7vsQi7dWFBq6gz#Nka=;OwqWceTtVGHS&Stx zh#6OPwIQa|N~=1|v~CX7WD9rGY#CVh{6)9Ex;z>8O#F!*381s1y(Gxwe&_k+QHJW` zo{*f$)#c(4KB#OV*Fhi_*cp6rC^>k9l2(_mLb2B6heHjqzPS?lRe%nlqgnM|D$IW zqrpT$*Qjba)3SnOAeh8jqGak7^B6L(PB9nxA&UA_mPVp>lbVbuQhtX6z7hARK+wIk zPh(qa1AGeFo`bdTEcw+6?qYEsN81`@$;fo`CzjE_)r3IVf)90z$nZ`={rNT7Jj3!A zwo+E>sYVqhnTeX`&>z+vg$7d_Aeau#X!x;r~JAOD{{!^OJz8*Y@Gj3k^rh^+J!Zgoy;CA{(K0#ybPPgk_?M7b2v{EP7gIfkK=51Y_~KU27XI2us!XtYQ7BejMeK`UemqsK`D!%1=tgD$y@0 zRU8;7E*$*xDxCg|`EYM(*x0hJls=!8m4gk8|`ZP*V#;WQ{M)!Ta8db9;{T=n> zxD+>OM2oKsHRuzj-PFU18^&qHyY(D9?+D;ND`# z5nU<+`IKT!NI`86Q+WZj^lJjHs{*jj=A1hbToq<|T$okCfo^VQ4dc${+J}_=q}rym zoEyQhr@Ouw*OKrXT0x`^bGu*s-$UBg>L_SV&o)xl4+Wu?gBVdQ>w(c=@au6IhQ& zxv(jFvMWZgywwncQy_=``zM%fF|eEHo^z%0CR%f9 za&KNUY-5JssmMYs#4LF!k^I}~yXW-dK0()oqPQW^;%j2#q+jLTI2taN?7;t$ zIH@kA#F2zfHx_r-nS7~ZyYDt}TSOG#?}`nflJ|$ks4(WhAOQKT{Fv5OK$=86*Wf_F zyx6;~U!`W|I`xTI*RS`M&TW*eDqx+!f&leDJ;`8i{uj1RRuXIDAz2Gru?4&QR0YA? zSwLWvSD8`Q=;jy0wuquCZa96@T{X6w$8%&B5XD0-KFn>Suh$rw6b2};#dAGfU(j$k zTfwY;NCfjo1ok|IjRSL_WA*Vm>CKu=)zCH8J2*M%T( zj42&LiF_jg765!XAQi+ZZka7hcG-#~xx*bi^b-z@wls1oB&NdbKdlbL$2uvtTiVZ9 zPZcjmE$6yC45yxvgF4#J^{lCO z1(l7I;dXcTxS9C5&2$y=IV-{2aX0iU%ym|>^h&C;L9S7su8Vk_^dH32XSA1ThPa|8)6Y{eJ?T^@swE|I&X9#+@>970$>1=bx2c0Y4a+A@;}uBqp40 z)_G9VZ>F2z$0$o(H?i-DcSC4bv0J;J9*EL5I*7P~t<#IYrA zo1}g;Skc&n%FvItI3k&g$e-Fb zrcBgu-|D^Hw+tJw@dZ$F0L&bwB643-Q=7XwZ z>+)&H&X6^hYPX?i>k4(n`=1#ebBt^Pb5pS?D!@c|k~*1;dME_mQywpE-ek{){)_Y; zO0J?h5@6x>TqR(iQZSckCEnK+w$?%>T-+$|(X{j(%RVe01NA{st)IF~fD+FdubCk- zJ~l}#s+!?)Yc2rYAAeF~!aau%f&L;rNQ_%vZ`JP-sypLdau1> z66lxucMYv=HktrmVtu<%m%S_65i%S27(~geOH~mv&x-3a%%$ivPN|W|z;{3-9?6Wk zhhiP!j=*Sed(TIi?Nlc|vWVh4sat-bO(O6ySpN1M^!ETW(S=}1iNw(`^eDcI>yv#! z6P8sT1xBKC2_31muO}kth&gqxc)#PN^0Ep@4cS4NFN@FzG%Td1N>``9=z8la?j@3W z-og4V3U&c}-k&OGowx`TqdJ`bzZK`9G+VVYk~L4PXVN^vHw^Qt_O2P%t%8U(rR^SKXg?4ckoc}@1SDc&N8{wV-X)TAmAaw!hr>L+VOlF4e500>SQ(o z5Am}7bpYGZjWrE{8zo$1YdVHQU8xkWe+$G>qP0N5vZs%K1x?WV2>e;g!v3P#jiCB~ zz>?;60Na|JJQ2vmb-FSqRy{iG3*xT1p;OS5_HLkXZKW&mtZ&qLQ5A+jY#Y)-hkd|E zG4*H1nKaKfol*?!cZOZlIoI1^b})gEdp(IJ*uCF)JgUCe1|$2grEpeWTlS_<&udfv zad>a5G`TP+J+#o&{|*S+&>Fu&n`oTq^pNG8)%W=FXOmKhX(KIIJys#&H7`%Fyj`$K zMCAC^lRu~Ux`0@b4x291jn=K=aX_k%@nC}kxVBsYL?RqdOEuuFqUmKPnHLbdlVf1nD$oAF&=jSeraU8u zb)NojWFdvoYy-=PK5lHG=^$qCZ3Ey=zc?pFX^+jOX9BXL3s!`FsH~4mQtDu;jl)6k zQ{8!I+EJfTbL|B2!lbOZuC+av1TqMjGU58~#fR zhf+^04tKNT=HS~Mmys8MWEkd=YWhnaZS3WBvm5$f91tIz&#gjuc+pkN$ROUK0V*WZ z1eY~(g%HV#?-4;4gf7BeXug!K@R=9{hKRhUgz8ngd*S1d*p}JG+zo;fPnS^(|0Kxz z4{Oox?Y&9!`*j{(XjKR(p6h6m^nBPLG0&H|d(djZjF;8Y9j`?3!06?Q?C-lg$mA_t zVU0kcY7M??8WV_aFeHK@9gfDqu9*1B596MY&e0yG;4VC@xCt3Qlz^}X*&CifgCswv z2g)868Xb5XQUhi)AD;EA*7pyYM3MbG&lUtpcL3JZ!S>v!Md8z2|jlfCtRrRMvL_|J&XC7vx#gwH(Jb+-=)GW4VmA=rcGy$ zwT*UALZ+=-&qmzSe0+&v3NA#XjE_G)Q;D;92?sx6@U1EP$K za0S(r^*(3pN3ZSBYJMLR1)Jgv3PTKTGc6Q9LX>tX7JqXDM-m5$(eKDI_!D#oq8pY| z<8Pvl9TiDVkK{6SVFkl`wr~8M#V#V~EKBr6!5?&;FtmgoGFRF_uUChPS-Qf1=Eq8) z#Z48+cQ0*_Roqk;X-s{DARbZ2!e^#Eo{EpeQgsp3&j^cdiY5&KF?7ROiw2UdO+5#$ zx(gi+>$X@jYkUo(3*#IKZAb%`MawqJDeBXPW4QC+@huB_A0>jsn)A7Q6ng)9&g1~S z$GZY(FB7nQr};e;p0eVQ*r))OfybR@vZQsKO@?4X&ACL)tW)# zyE0^M>UeSN)}foF>QDyLO8nk(RW@-H$ta_B4&l3ife#|+J8)?X;r$fxFmWStX)BMW z3)gS$(OUkl^w9hKiNzlU^lr(ox0#&eigi^7a$v^68Om?XpHX#DdH@Q%&(RmQGqNOOXG8jf+Ca=1@T;MWlkoU%Wo*gH`B#W|x68XJCzc$( z=ER~;UGFdA6@TApb_NElYc2r~YxYnqb<5I9NAOC~LQ^{sakcKL?LLHe2fK1s5!bG~ z)kjA$)E)3#JUOJeVx~lSR~0h*Czp69qGnC?o3PoI>CbFAQh|JzJdm*K$&_i%XC9xQ z_GCUby6W6ZByYFtD4#mm5aLi{1OK}69aw{slk1Eqs4AF&#wyV`b zU}Oa{NoPyAj`nU=p|U!MIXiaZxVm8t!5`6xl;Y&kE@6T1N6{VF_o>mQJ9Ny{@AfxV zDdsuH>omIK$ zbRHz859CBz&l9Y1e*Vkh536+TU*NsB!er_(FB7pnw#OV8Dl?c5BYJ#NiYMz2=jGm8 z3U1F0eD?jv*D83aWPQ!tu2+n@m1AeEu&H!=`!!^#{R|H_K^|A><%2H+`>5fsZI1Yztnvdu} z`x4iidH(uRfb!v-MS;*!QX)5q;ZyS!E1zC*3p) zZFB-JYZTOp5nt4tBEnutI5^*y8VdDz|^n+#yLH=+rBjqv0_$ie%@_O;R?~xgu-X?8;6<`T{@vIwO_OOyBw3`8Yud%Is zOakH$SVKfZE6AR-&5cO$tQ3?|NP`P(11c>q9BR8LAeCfPR<>oq5%Jgl>qHoX;kf|6 z$Kfi`N;NT$18{=KD<=+FVcqE@0*gDb(Cbgo{-zCvG83H;<(mq2T`}pMi4y1oGZPQ} zSSAm>sd$>ucBSL~YyqGL!6vZ=5x!VCB!}A|J zSmuVqN*dVmI}S%jtX5_H+$Ho%ZHQOY{_OuS9)R&;bMSbQj}kX|I?4*~rlupd;{G&6 z4GFw|>!R1of2sKn7vc9CXHL+digkr`qEfs~v3zP}G|i#~=cnGr6Ffe2*#Hi0*eutG zmW20#Pa8nA2RAs9{RJtMvM>`E%$gw@r#kbA3V`Kwu7iv4Xg$rd&RyOFEX_p%GvR=> zZeXM>5ho_Ym<#I)SkZbVm>DWZKH(|?yJ>rxlAX>nYZJJYOT|F>7u^G4GIC~bX&?jU z-$J*FG}bR8kV$4!q&&BQ-W-fcD<9NaI=a#}01o*5oySayY13RDd4Kf z`}|_?1GbC>f}1nW%mY71s+*V`0)x?+z0ShaU~)f4qk*{G<=wQJfEGWcixJaYuL(K_ z;0Kp(U>Nq;b)MWm?ShJRsVMcjqEN_XK^<&ZU_+*#&7#^*wdww&Wu7U5{y9H)L&l|$ zAV-($lI=|IMIFKc&1IfoiZzYy7J8?mCB1L#)*MA+Yr1PwhPOc*O z;N|r2o-Dh+EYuq$D^MR`z!?oRpe7yk%XN*z&t{vJal(uW(|0ohuhCgq0UjKGYGMTstNnu zVCUjwP#>Cgp@DdChNDq9SmAoEQ3^e-(jtC$ln&HWY=`A0Hs*BYf;6U5o8Sll$ziDf zPewOvY zba&u1ktwd$q2R?;y|>E-_&5V)tSYn)sgRW4=&cPpjU16~EBs`%k2+p|{6?YwFl}-& zE`!?4UQZB42iKQmnBW(b?w3$0*6i3I{&5hW)fsEgF@8mf;&gps(<9$oarjAw9bYd3 zF5oC$5KXlBV%ZIpZ)8!o!tMZ(C(5p8Y3DK!eu^3KBxf<@ZAG`??02a_ucP^*!X!v; zb+%0mSz4uAB>w`Lx5-SJsg*U#iK;`cP@#OYeN;qsjE;S}@yv`j&auK0!!~&Y!I+?U zy^E&Ez88cB+(H@Aa}Rlq2ltJ!@GJiOb>=T+;U_u>05U%fI{lyi6Q)kT(bXIqC_%Qb zv(N8Rk3vTx5?GoJJMDVa1cBy$K;s}8WPyANpa7>}S`7m#;{d=1xs0>={_}pkhMefWM7g7I9`I)RD z+I^JU64^d;TO)X4f+Lr87hfLs6!l``2^{2qB)*Io{!rSA%3#V;nw2t{uNaf{mK91%V#3 zx(Ou(D`4VNXz;kR8M;n$SOdMyq&7}y>}|HJ1aat0WDI%hq<>gsIS!_4&X?r4(0Yqf zxGhRJsFjwMJlFTPPV%LobxoVKUfH0FhxCV6DT?e>dhbU`g2e)haJ+}iXAM3QY$c-A z)Y^Sgb4a!(g1M06EN=s@FQy5+%^| zDcz`vjP3|?oyHVTlQTBzRzq5bq^*m=-h=a103&G_;Wy)+j2n7@Hisixo?{k+Q}QVp z$GSk!YTHr|u_nv~&c|Egx(8x-JWpo6p_N}!NT>V_9ud#WC6dyh|L|=e07gV8vfamc z0l*7t%?nS^%e?(U9JW0SD!KkruHEunZ*I%v3A~AS0p9j~IE}xw{_S_$y1x9dOtD&| znZ=jx@bAJ?Quu^<q2)|TQiV#9qS|;?z9h1lIU7ha$8u&Vf ze!#z9ug90?Ix@$_xx|9FJc|CNOT632i%D=6H=aWz^e|EZhNcAVOG*i4bbt|{D}S13 zg^{fGao;anKe>#lLaI0$+ksRDwIaGTopmzvblo*V*&(s}JTwyqjm5r9M9LnjD{pMm zFr{Bk`=^rk&+QjKy{pgre<+~U)iVq1>>h|<$Zy5d(R%pKvJi}Czwu09^lygg69E_s zNhbUI40v_pIRVu~v@#cL{!ilKnAn$4iWqe&hkb=5S&~J{zM!g)DE-6TH@VDM>OgLkbpxTGQ&sBaT`aLVXL+92{X_nHXUSNpY6B2t~05h4b@0l{H?DFZgU;$v{Y z7#IYQ(D7p_IKJYOYY%a+bJz4U1VMsuGRkf-yU8~_n|BL+Cx$yP_1Z@~18!(QtP`i@ z&s8Kh)b1$_iRBRZpMk5G?Ltn%4=XmDQ5K2$GmSo&!xN2XrW#VXbX-4&EOYzQ&E&*1 zB>WqaAYMOcJi?BVhsBjV<0w2zkyy81wJT?Xa4b|9zIS3-y3>Er`GF-Hg4DZH64P{o zoc0_IL8VZbRYYJ{+sXCwb*#y*=*O?Yjt~A>km&{o5?v>^_SZ*&1(w)Cm*7)v2P@fO z56Y|&Ku7#xbDp{rQB|YREy_OjymE$f9$4m?qA*;{YSu-v-?q4Q4ptBX%nUhRUqQ#a zy@38F7kBs|S2EL9Tt}J1(}i-DsF9wj1sP`Vio9iS8DaOrZC4RXFO;_Ui|}#`Z9BE+ zr*bug-ujwtcp$7$#typs?SE%0Zd4tr6k~Qv1OjJ!wPDRSF*p*J|IFanLRnJI<%nf~ zY5i!g#S~O8@$MtyFRk#kKZddNktTpq!%zc#3xDbZD{$fXX98EBVhRv52;jsqD&TQu zGMem*DzTtTQ=ywM~%{WUXvJ5+sBM4t5rLN0h?d^P(iXS z8(qgcKQ1I3FV}#lAn(LC$@WI^nAS60A87Mbcfwl4204F<`o}5uPQtxhImOw7iXz?!^RDhX0!e0um4LM99aet z4Q-1Nc6uqU(<_LB1Tk-;{8|(@pJXE=`X8s``Gt2vaO0lyi;Q_{b_R9AX(-S?+%q|8 z`*0b$AP`NuGvxN14D73YlzZfw(f-PMqTZ4x08v1$zg3AXKR8DR4YX5X+;Jhli*!m5 zaoV08+*DEm#;a_Y`3c2@cWbdr`RL0wgfI$h^BKjhd19QI%pFpYr(q2H4Ag$)(7lV% zc)5X@jkMUAx5JLs|DQtrH(Pe@82h=iTolfHfoi+bkvnHTM=Qx^XtL@iY6D`jvi{{< zJUvkD9r>|1a0S2ruI?*w>zK2a*K8zXl^w3pc-_UpR{BZ;qTn0uJS z{=iC0x%nFLO7Ir(|5Df?>Wd5o%HwG7IZrvHjIa2p4XDEB)fWDuVyVn7=&>xjYLmeF zn&!w5O>+i+2{)5-b=&-c%3XBaW1&1#dk((|O4cg2&xvC$fzQDkxLHVjiJ+1j_` z7XKYq!wxO{$QHpAB`$PiFYs|{CSFUaSyaMZm1{3>UJyph0P!N`PbA3ef~k@~&hd+z zAaYM`ut+CbCW%huDf12heEY!SZlQRWb@%Gw6QX4lphjY>q76Zl|5+eu0%6ju!DFk! zgFLd|5~2Y=Rp5>RShvO>@ndF1%}k|Mq|j*!?+Y~69WJM2_09G|LFFAv6HKwzy(A9V z*MDda(vsmG?7HaCSpH6b72F4|GHKqo^8APKs{ZGB+nhzwDV2y*8lf4#qx zSyI2eC%Xv)TBkF(h&By1I{w_tq*cTs=sWYtT4811UwtL*|kSG0pkdQBG^Qc$q*R?rk>z^Lcon{VtX~~%kRB^}f zV~@Titfc1=8Iue8qRt+8+Uwv1SDdV31uCat5Nkj}i7=Zc>ZS!`q`}D55GseRYSVf1 zFoA;kMqm;j8i!yB(r)jSUw#D6lvVJMCM0^dp=-3p%^xpmmAo{k->u3J&NY@;Jh6bD zwEE13{f==*d--Dcg)pOFbxif|y6s_;h-ib=FaB&yz8$AufXYLcBCQswS=$asb9$AG zJvA#2 zN9=sEAR6VR1C%oye(tn49X#q?->&dnSPfTSbOpV}>_%wXVhv08iO)0eX0|gqF?LUM zzua1|q>s7CG5FKS!_&>l>4n^A|F4cf_{Y+O_F>M*9WT#rmD4l2Ur5}?_AOWL$PzC1 zV5#~|z|Fej>qnYXHg^hne*$0NtXUsYa#4ALV>=zVW8$1G$4kN{uaQL+qW6*yU`8zRmRTcnN=%Xe0}rA`3^SJ zcmaxc?e!QTPbT6M?N<5Ry70jo%Xy2I(ou&$wB0T`TLD32#Yp zp({6XCB93r7Sz+}RvXjPW6$pJ!Jr*25Wx}}Z|d%ZO$M$F+)0H#L+8F&`xfQwbK_2D z4Yw(F(3{S#GII2(#+4)6*&b}*U~n%Q2?*-*%T@yT63KC8?#|%Jvg?WYSg8HDuR z54?XX$nz@+u4FU>$=sC2jsBU6w-a^u3>DE=^1|#DcF~NRZAcX< zNna5G8((Pu^sJfC?@MkLbrj1}#C>IIXdE{j8NFO*MKXX}W6eO~)HBHFR#G^s>6s_7 zDuXp3#(zmeP{5R^@VCCwKbOqm0eWUxVXh8*d8!03Y;CRWAH@`}8e2FL-e-ArROv}O z(+b-&2QPw!M#!r`{uAN8;qR`74pm!f9<#Ly8b~Spvwua6?z6plmcCKo3>aU~ja55p z5IoHw9&;GrchRcS5#>S223&-Q0aSX9*Th$(wW&19IDyIcyP2jXdk+=&lfr-Cq`V5F znJkQba`|mb6DQS43#b16$NP#{*lvmi#53R^e9*6!I`8WH&3bt*Ctx+M-nmD~Wm&nl z;>1|Fs|g+H8S-p6b3z-&5OBIZzcj>JM&CmYQ+!#s;>>*d*(cYx?yZ_RS;CCStN{HX z@WtBSvAWqE;Ig^z(`vL4tIkcNuy| zpe*16`bQ>!rmF$ku_=MpSQaQY5@t(cF(X2K?S4FTY^0frgK(d)`FKLwV>?>~hYq9V zd$n0<5>b+7(+7Zo&j3=N0f5YM6)E1R(O8bOUmJwdst;obxOFnOOK5=-M?Ew2PG|=U zd)sSl08^!H81e1db1&#^%Tf{~(zLnL-~+nKTLXPr5H-h>W548A!1fnjQT6c7jGrn? zI51pgU&UsFe2dktjWZ_dfH!YBs~qJ*ha&c}XXQ^39V+umG~HkgK)4aVdPbKL^(a#H z@VS{rlP*anZrfiic8ny3&~!%DYkz3$+@Vd3Ol4%M_IcIR={o6@{!AlvO16G>jwsC| zqhs4a|7kSAhmm_so4R7?PRQ8Cb+h-Vi$o3`_TPxAtjTMw>&$)_iWIjcrn(Z3`ZIah zp`pkmG^iZZ0_mu?KXg*D;d0j!Ki;G#fn5|gdDjIo&kah37gGh+XFjkE0%GcNn#LNCYc0RIL4iFx*Er5E z%%JR{j^R&1po6%*y)=adqoX&v(uVsUh~3DrihX|DX*EU{r{_mMxYI4q4$S4{U{kNW z)U$atlBuCyHN$}+^K_{+y4VW+<=*y|DSlzq0Hr>WmV|H+(0`*f@k8fu0bb880@9n@P@4EadW zfZn`?)5*D3in2p;K)HjZ1lwz9)dD_-=KWmS2}KvPK5Q~wbcVVn-R04!NbezweAyx2 zX~_SSN=pd?Dr}@W=OI z0hdlGYkESsp5fs-J@G0WDg!p-a2wgbsA<1w6(%R0j;Frod1nw<@PO3|L`&fZB=M!zSPl|L1o)-ICs*>JV9pTG}b1FNhX-B-Ic z`q^joT}yKLpu^_YW-`EX4aMg=^{9-2#~eF|tBEYS3B&r8x@2*=_n*`L9KY4{6{n_D z{tGSo2p#CW^sN`?M!{4)jo7)_yfO4+&$=Bvt%{ND4 zUWL@>{f^>-`KRg(ErX{AMuJbRL}MEvUjxaF#IJSuw;CmeR_OlMwWTU^zMnI~EEa~s z*^lf*z3!pK6U+Ug9z|hWeT4eO@N|9Z)kIU(4Pq%OS`l%{LGzTnGf*Vx1yV`(69P|} zufdQouuy#`t?yKOqjsH!>66b{>p|EnaqBfn$WZ)$kk8ehcNf;5@K=t)2b1I*qWq)z zA~d=k`pm>*cB(_Kw$M-sKLX35WTxb&f!5kb&goWoa8tB~P-;J_>%gD3EEA6T1LY@f z3&&C8I96AVWi0}8UYYdD2we5|XzX;#n69o@bE1jmHGl%QN@5j9XXFX)@ zToU@Pzy|Zh z*%N}%!pZ7s7#nJ-zK$i|eMDuD<4mQ4C=H1$O!*q)acfSricFjha0k7&hk+{vRi8Dg z?9(4&01*J!ofYBPI}DH$!_phdbfgfFZG9LpUwh=FeFq$4N$SPQ(MOgc?_VuYSN(FN z27B#H(&`h$UJ)q0sd=qm^0Lt<1`!cK6;?QuE;-qkgfkQ+#dOCN@$e3YY@@Z!XK*3} zwiI$^JIlkk*0zB7>d*l*q}S#>=k^nc3`2F+eKh@UTLXSYjqd9!h{+*TFOl6Yjy7-s zREdlcXF1Kyu;?qeqW?~KD`wch3r?U&ewqf{PWF_^;R@jn-%#@54FW*jQhbu15=fZQC+{kHJ!gm>D(t~Us zc{eZ-;%DnF%;1f$5lB8X&;t5j;@%D_AL%aSZ_!De!-W!dXPOgc`QUl)?L_r4j1Zg& zcsFqI_WoSlv%Q@7XgQHiURbVy)f6>;1k43z=rk5u_+NXfmcDMAScfM5S~v0Wxk!PC z0g7tTLE%g`#GJx@-{8t>X&Z1BDUgP|Cq$p0d3E?|vZB#n!CwqLN`WxHmD!skyf%0} z&b?7~TOz|rN2NVzX!gs=@7=CX`(jW?0SN;8ye*VK1z5oc;THBUcs;8uq2iXDsOx0> zbCyp6EUN{iolYIWY#K&3ymLY7hWNMIq76lB>COq4e(!8f?L`-MW$G3a``Z6mm$!=J z>@ZS!pAYdA^6EI-VGCLHr4*!88}?x{?8+xJU;E9xl*lEjVeOL1Dr&YL)LsQAKgm&U z;EWmM0SLCk7QGe`+2*s`TYUx;FP4ppjxT7f@+vf5g8>ne8IB7CeW8CmOdT1EN(!R3hE>u&`RA@au z_+Q0yCq(X)*!6pjDr2ixaL~GTyvi!kku4UpZ+;*qY-+3zRnr`kIce}-i?EU5bfbto zxhqy^Nu}q^Cpx!x8eTe(qW!A3Ze7%4)@vH?F(M$qFlpHy7d>!Aj;#@f;m;YNtwoNJ zK?7tF2db6P`ovkCOeysM68O6GmLek2TUgRRTYeg`_LIlU=J1$98jdRA^rCs%CF>@2 zVP1ZJYoUd1+k-gVBwt{SQfF&u*f$yN&S})el~PdIed25^`%pjs5;cd@I2@9%gXMY& zeQkX^USP0)`jyg`s1jnr&>Hh@T5vP4&fE#qVH z*QJWa3)iiA)XG~Z(XdQay7sfJD1R0ZI;b@g1BoM>Lb8G(x1`Z+wob7+sPELD@LZGv zHiKpw&>5b1|SDiRsN`Y8Uqsrytit{!t^R zPFO50av0I}%OZzP?|CV%Cr%hifasgpx0}9DkbpUhJ&$f!`5mLNDt#z9J(|~xQwDvE zWzA6u1K_F;9q?fg42xe4pFO9Qc<(lJ{v)*Rqzj67jo5f2v_F5A&VYHi8?Y7j5@mBV zu)Y+c#wZu<@*^aT3zU`vT8a*m+|F}^*kke?k6pdeQpUwc!Y1YyMooeW0ig5mzKxFk zEDdlvXamo~Ls!&Vll$OW?>Ml4bC`~UeP^+_l@N%r?rK$_riIY$ExMi*CSyV0Q4n|& zKC?bRk`Z%JuYt2=?B1400-DfbWj1*+nILY$*j}V1j|b^9N14$UNXjA4uzTxdHpby2 zq6OEy|G+i90E+NM~V-caOiM@QaDL}iC#;Jm;Z7>SLu?(V6vLs~q z^%bLYGTrEC_O>$g~L<5SdiQ37t3KR8%MYYzVYE63T1nSGzWn|N4;JwAc!p8d{QA1v0gHSmXHf>ZUaTikn|0 zPmR@2MsQ+0aT7skfnYIHS89Ji@n z^c6iLGDx+(CE<2$e&}$Kv$508*~d5zbu?&~Ttgk%IH=pbSO_iNGr@@!mOqo^HFYzW zYUHF}J?!oTvAlW7=?jM{^{WUqsJ#z5VZ7uOqWshp6zMFTp{0&g#?SvssnW{bzM3DS z%&2WUVn4dob8YJE8%ze7O`24nDRAEJhC&$TI4;Z&3xRehAke(Ebj~HFR}#jBF?+dG$qT(C%IvkP)@5 zEno0``MLO@CFCN0Zpm29Sd)rS2!uDTD~;r=4at@N#F8BM-A?#GmftoVX;U7xnJ!-+ zx!F3&YZN|cxPqX;dq^k8yQP}qNFPj?aWBgFsAm9_OHd&>-dthVlDfXy(yF%6@)Bt~ zy&Jb@i`32x>+G$^!MLtTLio5AT`?6stp?M0&-Bv35@l_YPmlunT7M(^1B&lbxL3T& zB4zZ5mmp8F-sDY7-Sf8(e{|4Q)BENXOUuNc5Vk7A9N!M<66bMub<{}yTN9M6)Ugkf z!z>Vp!sVQeNHlW0JJw=GY!Z^@sz0c*yM9%AgXkjZ)yz2G840(@^&%-6u{Rm89~WTj ztyMg1I8yS%_2cyk`_FqJE5`Jz+cN9?!vL;Q((tx=Lzkcoc2elSBU(;94D@pQ*y zr-`|)Fu53TN^3(DvyS8Msswr zJu!{>5y&Zr=OBxKFIf?PJe6|=rd^#pi;)?bF`FhT`YZQ>88ne%k8YevC6;E0b4tBP zZ~s)E#_;+~m9wxDzIc@;6hYg}(lFw-0V16XG|SaT+eu`dVhXFnDxih_aFjawpbre) zuU62LwydfOb1_pe-35~wiZ5->0Or5UGiSarWU~|QO#hZ@+@TnC;rx5n zyiS)v?`mGw6PBDp)xKV|%Z}dsRqhk2c(gUn$LCu77Ow0injPP%eV|pZ!&Gk?beCv* z{HfGEVKg*F>iK_w2@XqfxaJ*G|C=&*8qpg_nwbEwGd*+%%b#QceuRx2^lClPQ`G@fu{ir6?AGv;H^>Q`w$T^{VP)le^!-7t`VE0T31TYfUNeNzBB|LE> z3ESJ8erm{3uRwkWW0GmEK{ur+D8@c@ppQcEl#ryP{Dre@I>G)L?Nk~lnDzmefypTu z4;`90NY2EvJv;SS6c&vyDZxUFpT=SykK~mi9EJd&=o}!?0gt;KT+slNF zU`BOo1?2h%>#*F!?k4qN*dzmwrmw{=yK)`T_VuLRxjPYnl9G4UaWxTFzMkwtW4tO z0_cU8VdO;~L5U5>8<#7%?RH!b<(D)&pP=cEFOCp?KTT3vCxh}!0dhMZk~Qj!R093Q zd57rfv7{e@!y|*lCBIhyGmQzwTVlzT{NsW0`~tR(|j-I>wMRB4rCL&91R>7@qQMUIs-%$Ck4CCI#-WuGvySC}2R znRQ(o;@zu+qlJNcR$r{gIpOv0K3M}2RdeslW8JiS$p1;!fJSek2tIJ6kr>&+s##NZ zOOf9AJH_DHX4#5GekoT`V`y$Ku{}c6Uer$A1GQJLV6=3yKnJY7xCDutD2vDaUd{ql zI=YWk&A%WGYlu<12A&q7$Cb3rt5;ul_-D0Pp{LT450#UdZ{to3L1d}LYK417a?LtB z5(gWPE(JpHxHu)|zn6B(q?cI_doYrHLJVX`s+;%cx>EqClQVR4a>1Z?FxMr;phh>z z^Sre0Lo(_Cl?kS%FQ=?{{QNr+44p+fB_^mamQ>+@!-!NG94?*`E{F-Ygb>Tds$iFd z?F9y^Itm9vJAE0@hV!6FyDYaF5-SEu|3yzKEc4bR8@Oth`8}>Ej-VBSg;5r8H)S$B zNb>@UagbZPTom$#aFjHCw?H)Z748NJtt;5ee_{yHw+WuIuq!@Pz;#q2tR_UhJM$_} zu#U(1Dx48(-Oy8wwJsOEVc7^$9$nLRsd;SC@NA#Ku8-+33l1<~%ZNIeh4lB?w@%cP z* zi8waDZvhOkrWf=A7B-JoV~IMoEWb=U%TzT8Jk<6vYd;Z7RaN*5&z&-Ci%^vP_us$9 zburQTGw|0QuNqM(?+IOh)TO4U{9`gL(wY@iU%>DCjDvNdr?~vb3$S3dFQYrfKINx6 zLfQ?-@wVuF!z|B;-4#h3oUV>-ax(SPj6Ix4Z$ro7knBT4M0k`Ip}vFMvosNeGD}fG zVH;ba2ZAie&SlSgL-XE^%(%k!P2i{|Qx|whi+X}CwZw}}R3~+TZD6>Pa*4m^Su)=K z_hiiAr3nC?-8sus$bDJ%EB|k+dhK(KWFUdhAh*p=y1G@k+Clg{w~;`%D6rQ(w{cR9&2fz;-rAwNZ-_WM&X}?6Ijw=Vys@ zcm=p7PdD}}>%Af%BWFpuprI&Rv|pQa&K<==nWxC}36o0XWnDl`Y8PLJA@HF28BFZK)q@N_8NqAW`3|tEC+oe6(V`?@>9W6uk$?q8OiXwlT zrgs@RZ{3x$)_iV~eN~&g87tm4rUDN=Ptk>`n1#~ z4lnN*Ugg12CeEr#=;Rzs`mbq72M9@qaT2dr93C*97^xejJ+J(z7tYArF1rB zOEW={M@!Pa2-mG=ivOY-M?COlrL)!N#o%1mRV1j@tzusI7K)f68zP@&tlVNaGe|*r zs5@xyY9FI@stx>*Ryc~eJl1q5or+u3^pF5Uu~i?AhG%!Ca+W(k@W=dH=4tJH>JA75t5#@+7p~BO(z>>s9 z^Ly_{=OU?)E|$qwLD{{K6Y#v$D&O;3U8ly?Hb;7-(S`W9P%D$MbZR1|{I>sglCv|f ztl-aB)TgGTuJsAHHo}(}?S^J?Q-J}#@?(bGq}+uT)I=TUR8v?|e&tUPu~LGw2)%C@ zqGkG^skr@r$<=8jlRivs0@W6T{-5k1Wu&mT^C%re=EM@mW zloe1(SULq>TnlZ~2jR98YdB22byDvq=dUJ={IeqmFZ+*%0=eo8p4!V7umhNk#{dbv zOQ3{rASPU{HFC}!4!nM*!<>#IsgUs~dJ=bKq5Xzq;S|J3(2by(wjo2tEmm`-8c>^` zMV9Yo)5SJ4%ru=9HQy8yNzHb&s;~E0;(xc*D{!Y9W5h4HrAILWl9AdxaLlFRJ66YJN zO^wfp=M==mw+33m;LLY)T@)B5;~pR>)Z-NfSvoAyPcPFW-#AV)Ym&5}m>eRFt;185 zQRcb$nBXx1I@JQ>`>b71Wb_5Cq`hbVvk!8Wi_XaagcBZVZ}|a-uicS414$)i8{Tp zF9I;*Awo}(n5}8eLlN7wPkvREjJka+Fyv1JX0E|IxA1NcCVSV6E>V(FRt#!fMg|HF zp^YAh2pfBMZWvCW+M2-L)qJgJ?!Ij4{?hDlQplBbs&%cBF2L+dCX`w-)B^HImQOFaXaNW zmfTr?DYlNjKXe;kUNr6+K+-T!9TRGArow&TrL{)p_NrmIBJ4*a_;2lPzfAYksSLiZ zfy1QFWX>wb4r4H39UuLDBLHnf)5c}gL*dRPP%x^d`-L4zCp1cZCh9FoDv<#i`#gd>lI;e)Oby~Kwh}g^GAukz_HoM z)dAq5-d8jAuwN{?Cny5e-;Tstcc3!?jAfB6)bA;58+)nSqG_=q(P2EbR`0s{`KU-F zk-=FrDcDMSOcT`;`ow`Neysk1TH-h#JQa4A18|*Fm%}lKWN zg)J5fXJ>T(uIQ7lIzYGf=2BzfupM~XFd{~J$4TYU=QIdPJt4XJM@F+eE*`1@c1z4Q z;|HSg!SbulW2=};L(Vg*H}+}mIj|GD1!ch8!Vk-Au-e89i|RsC0I>2>>2~u*>27laHq_=yMeJaS7rIJ0R(|g=MMt zC?PyNGP)eGeMKE2?{7|)tB(_@5-WptmBY^z)5VTyqhJ{`+mKfXHn~2lg`eZa6#}o# zj@pX?9VjPxLe%}C1gU&$1+6=;nB~~k@zjR8{`mV5E7`Ni`;;J?XbM6tKw&F255Oqr z^F74ZS&~y!SR!Zeg^2a*mVnjwl+ws;%Ig10z20jNjlF-g1tWv`8ZAZT0d9OXqo9T4 ztt&TBC&N2H62)41hIk{Z%a++E@-N>_o_dIlDTj#62MR#!U0A3>H<^jdx}6ajC@xV3 zA=Z;%9|e_sM&|2dA+-FC(X&YPPAVtWd$pwX7L{xT&?+5`QLgd`rPHJkTyv0tkQ?E` zdg1wPjtpl@=2J{n&q~>7z3I`aic9F#3V!03)V(!r0K30T$lhQhz>p572I%#C{^q*9 zEXfNrGcHQ*KMQ+wVm17%6PrEm(E?3QcrbP^C?ksv43P`f3baVMk+u5aB}eRx_f|_m zv*>yfnXY9^`h5u^n7Kh2Elwb|?o_)+@(9>WO>f!Iu`T1Lx38mnaaIecDE0!E4-&7) z$(;m%*XhudbZ3QSo_9buVbi2iYL$?kFL0P&P{U2~iYR&-AAgWq^zSXtqcX>>z5owX zut*(nBbcD|=gEfnCc1S?+~q4*C;u%`7P7&cU?GYnYlr2RNonnmcC4SQ$Z^8nY|+Dj zbiTt@>yEhz6*nc@{~#EoiH&DFdm}ZAtM`0So+F4@1A+%QRgm(n@id)+)eowJ+3Vrm zJ`vv_*O6Ht!QIg)p2Lup50fj045Tj5yHRZi(bO{`NAGo@Lx3%zCC^jKZ9FsN1P#b) zXGBY8*J266w`X#4d`94n;4lfq6>HL7dAAnF{5tLSc_3ux_@QQ<*>8y(^9I2siX39H z)y=8P%=$d;cuGXbYgVCfLPcu@8NJ^V~T#^7kadSDS!6RKkRQc~+f z_mZhH0zHy{mzik?fI8Ar;&)tIa15yeE2Dd9=AqZCRWX?fiAcveFDno%zk>Y^`#7Kq zEI7jRwSD!9TF797?C16w`f^Yc+d54A3g*B_C)CcAPlRgSN{i@Y6RKDo0=W{+cZYj* zl`ED_P=xL5%U|99V-6;2r})NtvF#VyEQ1jDpTdSS7rVnUG*31NU>vT>X}&SytyyHW zAygi?XY~}^w`}o+x#Hd*TKI*Of0X9W!lh{$Q-sN*VdxH4FD{yv{KbMn^x?2z-gE3G zh)j-=Y+d@HF{-2=`1E1)W2kMc2+VyKa;nd*I`eZ-SeBd#Sd6@kbOXfx@^d}i!G*c4 zB?B-%8%@S#rbBy8QQLKg%LpX{q@kgj*jX=zE8M3*oFgm+(K{+2K|KPje&GcS#6J+o z0kDy{Hs>1^IbZ&TTCw_iER*uANKzc60#kj4ST=P^UD!H5i^I#rv@TZgIpI{I*Csd; zFjs_WFmG78Pf_WF@-mC783zAzPMed$YV<%G{nUtcZrWGaw%vA|34{1xB1h(~q4%zi zkFVT7&^YC~eM58oqikVlvbf(3ld$}T)nCUU0<`YsVolg@4jh~|(QC{R7o<>1-KgVfCW4~WeRPM8yg;?XFczj5t11chD?xxW%ZHka5BM5K5Z;PL+tujeqaZUVpH4XS`+`zX62O26qcJ z6Rm|YWf+j$J7JRnyg9)OAGJ$M+5cN8TwDs3FE(1{VZ2;`fut^0Zw_rt3b)1>zW->6 z+fc2_JOyM!un`}Q) zJhh&>`zad-8{nRgrm-Zy2=w=70SYThR8wj=;RQ}cz~jO;vxDhVD85t3BH+o|TwxXV zi9ak(y3$uEt@SXq8RR<-K`PfJIO@C|r|UR)*)cfAu1iuVHq7RaatI<*{ELob$C$U_ z12mFaxDZZ~$P5)C!J;%XATontW}P8BYQG#1U;Hqm3eH6^u8%MP3Ka) zUI6yO3J#Y9kl*l{mvlyCMmXh-9&D$mRO1TA|>|u zDukWx*Ww=&K5fq8W%WpzCM+G?xYWo51K|!!u5<1T_BSWe!7?Pz-1ham+$_~O4%X#P z4^wR4a02Ww>rahP6B{{{Ulpt6<@LOzDMW;BnL-la$6nIaR3Qg2nzS(^8%(l0mReD1 zRD%!8&YY#t9)so8e&(IHGI+kNc@GH~quojo-OZj(QK zuvAu#nM8BglR^wDAO2Hal~11iLON$x`mh6ov<5*Z-FLxI@<31UY|b?r7&9kp%`d`N7&YJ$pKp2y zXRyCAlbL4%7!4h`-*s1-;tG^<|AT3w6A1MSOZ;FDak4)F&%U{_Y!nr`m|)J#UMNsQ z>2glm`ZP&c$52}etAJp9{}S54d*`O18QyLkeK)nHYfAgU^OL;?(QS?zOH=E^^Qc$d z9bSKmLAg<`YzRCJ#x7ti=~&$*L+T1ffL{NP%o@EDnnMwMflMZeEb3ct=vA zfiE?0p0wjV3oWeuSpClG^On)Bc4g4{lKxe|}Di*|FfvY|pV3 zF_Pln2o5%uV~EZCB~F>1(m}7E`LrN)|KErW_F_1|X9nIZTa%%caJ@R7HNi@q5MJ$# zRyY4ZH*n=lMIMLFL%DWG;CmnPP@6bWnREY2*vVP)!H~`f4N}RJ+pn(%f1SLGzlLyn zs7uPEuwH=YC}En?x27vgE-lh|ZzNyxop`rrSp;uo(;T(hMoGm+mzB?lap3VR>z>L4 zW)s_pbrJJfS}TosEYe?T|Fw;7(IaW2;ri`(bZq}$Udkh&KV5YD;6%{y!Vz&iDFXco zEoJ*t>Tfm{1Dl4CsG=iZ_BzuODKG`TR!iC27thnQrpJ9JAk)$9#+pe)#DZxb*(?c? zh?QDxn*zi@hdYp}tI>UJ8WCb8n*$ZpyfbAw#c;n?`c|Ie4cBMUz=wLp0G!}ZJ(IR5 zio4x`)5Wt+XF?Z|S+RDM10a$m!EbG@M3K^lbXgWb90jS+@2`RjyvbiX#v!<7Q zxJ@hMz;@Q{Jx0)8@{)kNM}Ns9PZzN9mI|XGqv@zl9;5w76e7YQn2AikO|55tWKGpQ(VS~|^e zPBM7+Iwc8VO5DZ(6CA7YC^J`QbVP`yDFOVTVLcy0R3Fd4dg4-DKtb%n7Au}W5w89w zM<;>y8$WD&{$p?yS#wb$sL*j6qwqVDl=4(Sb2r(4O{$58$oSOI$qQV2I-uYxvm&JX z>0aoQ^hVT~M|8{&yuD_omVxLWz5>k%!AE>Y>8N#1){dX&ZF8}z9diX<9Bfch(J-&v z3Gx=UFAkg~NuGW(d)N@(Z?u+2#~GaLqWuN>rgN3pjbXK_DY6Y4e8m180X%Nj+gHl> zBFZ!~J2Rber~OHzjFRoto*Xt!fLWP~s_FRchucuA2{uL9~#o&U}uPmnxqu3BO zlsVXGG+%hSqtp_uiiCJ{t&EGt+=o|nM(pGXyITW)MxXSRnQe$vC@sM%zu$2cz~qz9 z$LW*0|F=9Wnl8$L%fXfVybQi^KAL{)KLyZw@1?lUvJb}@`1K0}r-1lE2k{(!EH%ST z0QJ`9SG$;xmQ5eRm!QhkCra(0W!D|oM_xXr4fh6MEaeWhg7*e+G>Jt0CaE~!oJ9Hq z0$f`0IIE_h^@U8ij>qUo*H#mAZx(gy;nLXmWP^T&+dH8^ zX!Fj2L>25JwtHip9XuG$$Y-HQ&0o*aqg_aIIVIqu)->w;2UB_CgH+$?BTA-PvZ8_F ztJ|ICwi}%-b^Er_Q^1|FMvL}E%#-6tcRChcN!F-xT7U=Q%FJILi_obRnI}omo7VJz z$LiBE^|4nPdu0rISrl4-8&-DpQKrRoqgD#@shz(sCwlHA{plG%!N58-&g1mI4MHWVaJnOjBOBgC{hSA z`c!Y%?r~)t5D#<)pV0sC^MTJqJuuyS^)mGX*CTFmo$hDSF-@{?k}y0H>s|ldY>IwS z+H}`Kt*@LKNp;YFHx!uxQN#Z@UCqLdvUZkXj*|fMR5s|O>`3&sn-?jALt-2OtHAE9 z2CISlxW z8xgOS1i-rC6Bm1OU-QG~0UK`K0*@iasiiz``(qfXl@HZHgidN`-^HvB@>NFR zM_4j-`BO)sCr0eDHOJl9&~cGSd8jdC!cjTsc16(JF8otk zTapB;RbL?d%#AHW6baoqAcx^e35cN6JC~5B{lQCA^epkz!DpA6|-ChY6bAevU zaSjWkv3+w_AQs8N9zHUrnB(QHm=!Y~g?A#HRygL&nDh22y+`;rHC|`WRqnw|^|8`L zJYr57lzv=sr$0_n&O&(BVsOXjy6CMdgzkLWND?p6H83Fl-xEC~9+Oh?uR=~_aRqTB z@gjXviCZLvDA|DcGpMqyi=I49Tr8WjR|G=weSk{A$;b=}n`@JT?N{+RR7^3ozB`X= zrc(pcC7NqIMwggNhMHz}(=aMnBH33<41hMa#D@)+0#ZLU_RtwS@-a zgo3RZt1|%v);(4Otp-{9IVys_xuVYpTCR<~3&!WSZWvl-WyTGWr~PlnKmZ8@T!tJ; zJsF>6s+SSieAouuV-&edQejv3zQUWfDsq6X!eNtDFzw>x_1(-L=ocjwAhYr92#4e8VO_v2zupUO#i{jx%{&Z)-|=ODrSUT z4Op5m0^vw=Rd{Z>X|Hm8@+i3-c|xod#74gm|B-#wm>aX3OCWQV7wzUf01=W83++FY zN8n&i_5&4)MVmru!{%@kbMjl`o{T zk$l8+HF!F`^;ZtQC)vpB9B`~Bl2zs+`)E_G?AwYmBF*8bMXp+djO1+YubOIP z0?pC6MPE$=();7PKGvx+!lSV;5);i&Rk^k77$~g}jAYKKgOcRd#`pHnI$ZLH{4OMd zIqeowvdw(*;mmW0vkO`u!Ul)pmYCaPKENn3pIC+zd)f5>Nj7y%M3($*+F;BVj~twz zUi-SHtciP*TlMZgt^8?H2#I$&z1}!D=Dor4j)&2stV9|Rrm}S~TvC4to%}5D=N-X} z2HN#42F;f?NC?tWY`NlJ;F=bk{FbO(F^X^HcKE%gJh{Me1!6FKMwa1~DRgJSV*mAX z5lXVA^;6!FastQ?d;W3RVm4bTL1x!uZw?lHB=}JwdnwSB}pv*SIGO9DDzAC@%QpQRTMH~COwJb8U*HvFl1RP!XQU7V-y*iy9= z(WgQ930a$`pG3w3{lN%CBla_^X>})KFD#-Fq>vv}wS~rdla+9677p1zPXQ2u3E{C5^M!_VojM|7 zw9mCcP|emfR}!zXO_mG40!LAc62hfDh+=11&(mS#G>LKwzZ0sr!+^7hhO?XlC!m^* zih-0IS~>Klu_=9{>iTIL_u50W9dHL0)YhEWDyg7G!cx{{^hXAIi_p77ol)F#RrSt z^)_B!GCIVs*H%}!!T0u6EbL{8$FCEbVB@|$G{Fv7kqYS%YLPOJy?$Kgwgea*(CA{8 z8=iEs6f->V_4S!t=l^)ifC*eAH(85h91E03IwGq>o`5z(GTHJzUvsCqxg*7fhjnp> zbcaUu3B{&ZzrK)9v*0&fN{ic$kN>S%m*5oIK8er%XD$H5`UZ?H?A8Wwilvh+XZ4#K zYj@yqPBLcMMITMnGVZaIsYEiiXwde((GELWzEpTzBMh2MV$DZ9H-5d0xu2wtY0m&k z3U_+s{OTIO`!_Z{$68cf4_6>C?_sv9-!5xUS$@rpC0$mcvQv0b8(%-?Zl3SO=b4H{ zwYn%~$O%9`7D)q`*uALTlnO{L9L52!v)`MzagJy-YPRUFby^{YdiVNgBgblV#=6q) zfj3yi!o7iYVJIeFtc5jyD_O1Y081D;7_gaq%ta%hkk-2}JV`ARq)=Smc`P-O#>4q; z9UN{YLQ_{9vkBl6g`OhFJ8HA^3MHFnPy2X|w5ut$gWwxiIGw5vP}v)(e~-XwvCt;T zKG$VLK4TUS%SPzl9N?(VJG5iukQBYK;>BGl8tE+DuW}Nq@uFx??YbxzYGDoZ@-pc% zo*3?dvm2(rcZl8|Pu5hwOlIV%Q?m)3Z|b!;Q?By|uwr&z;Y^N|NIh#5N#1`eC17k6 z^(bbk|NWoLy5C20D7`JO+=mk5%_I{^?{PomqYOdJvF{64Pmd8g_+iuZncbnu(A1>= z+&&Alr~Cij1L3L~3B`$rh#Y_S(hL_3T@4EMRpZ*3JIZq12ZM!dB_8DG87+bCsINI* z-48F%;~p)j_i&aSeJHTZ$OCY{w5Wh&{$`?px5iKV3|G>ejNS_C4f}M=f~HxY&7s!& zUv`TZl&h#?E?u#0WvRj!Ane>c&rUuJf)&^xlgEF#ia4|*EnTQ8uwg1qo8tf~)SjY= zu?DZnI5Dl~1*8p0C=&QP-u){}0{a)6p2_F8u?>{^i6om}gMd7N9-vs^3rv59R1)JF(KFQ|AWzyKwSZ6ELm6zloz}Y%Qx%F}y2O#pl`Cz>!kv#DHI}JCiBMqX0RAuL zA!npr#coNVO2PTD*i=#2280-qes;uLEu=GY4l%Xb2GlatOD|>)&X_hk+F^YK?lL14 z)P4=;RwB!!-{+prcm{&WSWba*MVdKat3Jzelvl#{6S6_ncf&`2c8{;Q`9EC#mCs;S zXroHglTUr=5Ey-R25{-!M&nu4!ar6-&1sQEn7~9d<*1v+ znTj-W{}R;an|JBN9zi&TD3}=6sYPKVN}6{<*=837bU>o(h}73e65{G!biO(`x*gsB z_F7x)xRLjis+lK^?RA(zKCS-2BZ@j}^Li}R7@d0_6iaSQY_cDled;yJ=Yl%0#^={~ z?O7{H)U?cr;Q+3lc(p%96EaGt{w$l9V7+G2a&(k*c_rn8i2ThHi)TY0)zeAf|AKXP z6oe37Cxt>JNB#imI&j9rg^RZV4KN-wx@IrN@$&2OThT3~{Q-bvqTz`RTyQJqu0j-n z{L(93E?~8zF5}uMz>uq}NRe!0Nl&!tyCLCkh4-yW`zOuqs|WkjGKgadTtTh$O#{Ir zIsYi70_my|u7;VUj!;{KwQGsC1!w7wnYbz`koK()yCV(@gzN*Footz~`SfY`-FB7VN3at{8KS-2-&-n~Q`c$1F!d-+gdT zm{PatgVh4}M9JS6C>+7BJH)M~byY6xQ2Y=53>^^bp@nNYOcFH`y z6^xuCIZ2J}xq((Fg&t@eH)IQp#T)Wg&Wd1`g7!Gce^*XqTbBr4T8#%~^t5@G5S)c> z8|Nf_h!FpO<*$JpLrJ-J^3*n5a5M+IVek#HHX$o!V(1D37Ta2o7E}syr-KJovQGR` zqsf%yLnk#Zo*+blzX;vE$oPI$o4ym1!;GRCmT<|E!JvEh-H-0l183~1+c?7$cG&&O zT|wfCe_YvBuuieh`Tm9nYP;TU79bWrLIM;{H;L-%@Fq_sBrm|5+E=(QPX3Q)&d*%| zeNm#+N!T0aS)w@m`zFG^f(!k`%A2dcFid_6t>WX6?cao}uuVSjc{Z-0dC_x>gX}MI zQh)+sVCUNakid4%pA7D`oz*TYo*+R%abAJX#9OY&GsLN}^axo#0xm4M=Jk)DPYVKpvo$1tSS3Vq!d@1GlYEAiKpiuT7JZswmL7LA#+ z&_+SuBdhC#!bcp~TH!X_Mxo~75ZPeDhpCP0Y=Eo7nhc7PpS;yMK=@19*B=Qqpl7`F zd(^~)++r4ND ziM0)$F(UUEAH5=&9Gdh?{a8bONiRcp1S6PtErob{cR@-RVipmcHrR1yM#Kc#II)<7J?g7C65no)vog9zw zE+KCp-Q>X!^@TL1Z_oiX?+-I5kBs}t%zj=QA>@JcR%Jd6yOiPMhgBZ8K#Zn!aRF@* z%TFF5B>nFo+Dp}35ITfWtvg?6&mb)StRl}ynd#O($zd){${?hT)r7J!Z#gJ$lVU>5ZSCklkIgqsjMC~crbRo! z_@4Q;V5sql+#DXV#=DS!G44*q$YKaXwP+$zQB(u(;9zV>H6JuxW;wB=G)?5pE9|yp z(NwcSXc*UDP@?bf18aYV)KZAhlSB1bbcgS$&r_lc-u8z%5O zhS^Zo={*Yr+9d3ENIA6>Rnt!H4uh+R)r})gkQl~$WhUWu{W<+fo#4U47&6Rppdm!~ z;O%!etW6m!^gs@`JQ6I(vP%?>wEX-{v#4=Oat9+P!zV`iR+f8cG$JMScEQS6PA@6b zYeR#<)+bJ$voht2ip^gFh{m$2*#P2~00OUZb){3WxOf*H=VN_A4`WCZAE?2rPO0%Y@b~1$7JSUbYmJ_OY`QQvOG{)OgA->59`d!i z>TeQEG1<}ZBfiKF8Fz!vlH1t5L!*Z-4X@n3xG?-HtxeP9BR8oqFd*YqA0>9 z=p^{x12hPRuiSO<(HR-x2>;q6AHf)?=Nn8S;gCXVcHG8>Dnr2og07CGeP4F8exqe& zSUh?h(f^%M`T{-8;t!_H(D+ra(5L`Bj`$3td6lCWhs71gJdz4B{GO{u^2MxZ8pkemp>tA+@Eqx z*Bt-iG6+*R2kSb(clH)qyVKHRtBy!wWOBq9pUexmemJiaZznlpsC0DSDzH~$&=Aiy zqg7n%vETzSj17=zbBcB4Nt!vk(;709UxmUrQB)H_yQPWPgISgbc+FQhUie$1_);%U zHel1+fiJ~%jM6*TJNX3Ta3vljVubRo;OMVV&?*H7#>LKqB`l<=7}2CQt6i+h7VdGy z5~BVt`#L4BMX;RodAG!S!e1%d7-h)dP(fLI4BLBZr_{g50oOA#`M9gW@yj#h1Oz7p zN@KF~%CWjBGU*8c$9oXJL2XNresDS8k)IRgb-BSvFcv&UAUs-TU5)3 zEuP~P_=9Erm2My)qIg0|5sGU*-O)V@4jJ)>GY=dYm%~?noOPg_<#HrZ-lT=%Hna-N zTX|ZIzn^}YNP}TLr6_+wlJORyTPiP`O=CR}W9KG0G7Sb*UOc+A0g0fPO7}=|77HJr zWwN~eY&{B1Cgp!KvM}2WubYxmb~+rOz?&3FH$g$?{b-^Q04PtsrB z+yz=#KA^Ly?Auxr6pQ3zs2ej`&zp)O5*iS#Ge=r*s5BZu9LTY;hRmkwj?3(%Fl&z` z;B!r}XCEUBzSt3v7=uQD1U7*j!uW3*#$*K1Dks(+n)~tZSTTZ>a>7z>GuuWhK+vdq z8eHlGtVV3mod=N?6HM|z&X^a;y*=3-zAaQ?r78HUTpq__GSN`Z{Znw(D&*9N$%y28&svJYx zdQHC8*|XsKCg?5udG915Yg59CpJhdB#<^sB>bc~&J85BL`ip06>>^|4)@X*nt~=0_ zy^{OX{}Q92O!xYFg=yDGyP|>dl|657I}x810l!3YH+31^9usT3dSQq=05fRqhqc(n zzzkZcD^`n}eqjs5su5HfJZY1t1;DYX(r#<ymsDx9%ICz=zNZ1^SlBr zMP1y@b9ISP-`BR{`?-DmI^lotOVOim-s|v&G(7c(b4F>CSkNJsu@DwVl18RagJm|l z>AsT$!@E zgj!5_batl$l!{a3ic;^@*h9VPsidLwBsZzjV~5fyR%0K!lhv8+6%tz0ikQ!MCLrD% ze;5PzEV$qnbqHwZM{5a|38m11oFB%pX_EV7b&#HDE92y*OW(8BuxMR8!+A9B6-4N* z>ke5L<;gXXAwREcbE=PkO?_yl=7~o&0;R}FL+fTX8mL3EjA7m%pA$hM_7y@fYEN|M zP5t#{s&iqU_Rj(}N)OhzvkD&DzCmVJbvWm$|IH>hj_ z^ANoUuR)Dtz&cu$$4xW0^Z1a^q&Spgb;6Pn6o&c=z>$m0@K)?{^L%ta(|m=jW}6~q zN@4tA3ExZ;e+zqJM7Rn*SEKXJf&&~v8~cQ2dhpSV%%KU&ZM+xb=MgTobh}pzi|WtE zJf&nkEYO@CYp$RYOX8VB~U)H#dx6@{Vi>c9TM_7FB<+g?fr@*BBYj4^@b$=I){ zkMcE2@B-dh3C6oPoX0Um>8CcN^d4VW+Ovn$@=|@3B z!riAC`jW8+tw{XWyFO5@@2vS54FxB(K3usj`F%wnhPtb^tC`=+dh2c*sT<8fx4ji4 zP&w57_>9B)=TJ|E5<=JAd^S42x~!3!t%IeKUO8-p7A@BM6Qwj#>pDWP^-pCCT<_?? z!5E6>UA301XpX18<}R}<&0RCoajDKzDGc7Xm+e+CMw#N#TN5i7N_ZuB8y=c9beuzo z856bB%J@g%SA6i^U9ANOgmEbBXnTZ`lLgqZVl_77yTjTXUeVUm1d9z&P0L$+t@p%? zzU&L~#bW1)KMlvChLJ4gu1q3_h?xPlH#&=n zjW?(=V-}9`1@HB<-t&}SL0t-V9wzkoUkkH>Gc;W6g6E(oFSCFmQn1CIIM20NxvRm)z+=D_ zya5Ntq=zxwjl;!JiGjUCnSSH5kJT`C7+|c_{B*pU=Jt66St=!Z!fV#)3JF3PkqaIVpnuDABZdq5Vvw!gVK^pWr#X3J0$H z#YSXGOZ@N0SwJPjs02A68?!WPclvaZo>1}Ol7s4=5SM#+;k!BYMF=C-ar;K5^ENe^U&k73 z56t_o@CaCMlpzB3m}!*4X!Z(f(LC9MmtgvRh9}#lNjDS66>}V0i2uo>^sCclbgdE^ zM#44WKVwkm99I#<690o?Z8TKnC_!n4yKWnb09^(x5K~;%EC5 zi+k|2I$Ep+CR#6g$6$tq+Vy;<|1bC-nGznS5ECs8@&{n#B$yuH!-Yx6qM{bTRcOo) z^=<_G9ll$)SE&nu`n^kO^{HC&JNYbkw(OVL7CCM4z39_0F*!sy4nVAnCREqkx}Dj<3`me5JvD5#Yj`M> zg96{3oR+C?_Sz~(atx*G_GG!*G^vS9Q(vX@R~|@ zy0SHYRR94`Tj1+CB?HE5<}9F$XbN(*L$>8Eb$5j3cmsh@VJz@nomq6t%o0+VO@$!h zcn$*XUQ5qdO#>Q; z4tch21W1RnkonlZ(CpG^wvQ|Q-MXB+P``=xQ^*369*!U7-BLR60UoXrV zx8@YRFX=aL4H260?DjuY7NQcG)wuo_mz}B5wGKnN!NS(5(wgS5F zqDsj}59Iv_t^Gm=rMz3K^J*$e7XN_h`B~;(g6!B`@%YHHYG~x|9Bg6{1W5$uZ(+_boAFFbL4(yLjs@5>!z2piqj%= z=aJ!0EAIM2@hqZ1S806WM9LIMzi$_CKS&?UxA13`w$X_I14ObYz`&RWngO8(@5;*= z^ihR(278oO$(MF&pF5mTl=%NpBf-?iMR#|7MPSQ|6WW-x0u{aLCf|7>1DY};#>iw@ zY#5ha({5@-0WAs1qvxx;a#wC#r;?(~rc5l%m<(SMd9g^)tnN0j_dho`VcP7RD(OM~ zuFG714i&sXjNEi4yG;~?xKFsDKURGjaK8#IV1wHS76Cp7z~1-N$}rtidr1&iQ$IcB zbf}-vNx-F5q&gehJk=)xG!T-s_ zh}%~>+DY>j=_+*k=w4$wLWo;cFuCQyMr|tM?u!_B5*~~m|LmorhfDN*YP)~ak5i~- ze+Ut*2g#Uzk7jlKEVB&Dren{UP3-xd#V$e11!PoDbc=(Np;W2Sh$v!R>cnYYt6<5` zF3s$eH)KY@G9*<0;R&aaeJpe?oen$Am^?+>v|;W(xXfmCSckqWoCMW~s`|4ceKD?d z2dv>(*s<@Py2PxcKV9(LRaVsZM4kKEi8^>%{6_9DV$3H|XM+4rOu)7a;HNn#(R1Pc z(tJl+W2AetRV3a*t5r$U2hnN4p~vkp47Kl(pykVhX?ZSjD4_x1m?E!u57!IffPHlE z+$G1A7`$!UOeIEc*PL-evvwk9V(1t^3Z?Yt|&Y$^{c8=A;+l ze|=%@BW+n>k=tJWo8#3(-H0~d#LP>oCBhW2w2R#*aA}jeSyS)kvltaqx=qd6gK%%+@PbnXimw-xI=L7hVh9{a%A)* z9YU-=RG0>R2I|-D+R@R~$;60}22lb0v;9!|dQ1XuCh}0|n;00^UgK=wnI*Od?j~B~ zsffV^=29ESt&D|e)6TPtuetjR~wrpBEBA2l;+SYuB;1Xmp^{kmY)5_eg2 zK?=$RV1h}8=gxA#rF-G!7RK7eNlB~49`=dJA}~4}Gs*A@(R-jOIS~G62xT|{twme; z*wrPEf3@~t{aULwX=sSL_Ql7ztY+Yz(~^ zIt3Sg$R!=>1B~lk80yroC7b>Hu+eCfy@D(NSgvMmGN2HLQ`ANHO%&Vw6TIoYI4+8CRu6T zlpUSgh{K_Qd#GkuC&!NZZhL(I2!q0`@%qKS^hNT^+X_^}EZJp$&JwEPQa}or2kM)J zhUQx^K4ggJ1LxjqD2d%2XK4O*{@`5dHP7hwab>6C934g91f(LGw7v3KciibRB||6& zWAys5Y;hzEZWT%C*vzcyAqjF=*_TjbgnZ0Wd#`+8tKD^EHPa?&&W$Of8eQbt(K*>q z7X*S;;W*lEny-H!qaPFH^8Jpu_YT+a7v@v9_+F=~ml8gm@!zXqN6e;_RLW$HO`kQN z>7qbimdoX`mE2d!hXPow^Q9f*OPKMGxF;eKxl`Uy>NT3k-WGh_%wT2g*O$+hAmjq_ zzD-`GA=%}fpU=JzND`2PCt?dxnC_^sy)1WO=xD-&$BnO-Ux)++U{5S;>NpCu3CTGT zV%acBX%EK=MHmxuOs-<_Tbe^;rX$d2g zI71(+X&^gOHZq6@U>3iKl#T)LWN~Qm|LHF{t`kHtSGGE86=ZD;$B&aKE|yaPh(Yjy zX-f?YHj*`%;DS1P8Gwzjc8y`xTrTMEgKq_k9f&Vvqv%ZMj)4}l;e}#Lmi{!JX0iZ9 zN#~2}*xl<7^;SneaE1W{iN~(k{|mzhnmLKT}13L4n1AA=a();+uvnOS1Jt_ zU`qz%+;wpYQ9B66jUjEaPtrPIAO((xBB+O?m`#_T`Jl0846ok+gWQb1BV1|o9%uI! zlj7dcWv?VYzLP{^H^+Zzy@8?@fV);Pq`JiEXT^y0Ly)46<v@saNBz94tT6}_tkW4vlD=WZ=(DjQlZ$CLs=Xw( zta{X`4HwkD3)M!HOJ-*j34M^0prwhq2$v~vfFu(@WiaeLu57QLUZ)MrWp99jq%=Tq zV%HLbHfr1xh<0YcnZWA7b*+SxN4oiYsn^=*R<{r<38Lrr6BR=32;pp^fh(UP4E;AB z2OwF~%;|?NjHx;BF?JRcu@}(2UPlxnof}gq#Ch}wfL0M62!#Jb z>2wMVZAZPyP57}q4pjcidGj zzm?JWw?gpBq-h+a|71qXA-hr#)fiApeyOH2<`PTpUI9++WJ1`^R z^QIlY7;g^KwN1(JPb9$6{ic~>w;{3~l8)=_%Jas%t^iKMc=HrXWT7z}xV4QvFd^il z%)Yx}OE_yB-1}zU&P};TH6%0`iG-b`0>s-UL|!A5-n^*Nf?Gao zNP5>7l3k)FlCZKv?cNqkhz(np-xfW<%!<8(2{XW8sn>DvA(?Ved+$B~%bP(Q^Gf#O zPqb(CM2Lt8(m_QrSc%k`In~Tl8hUG;$YYzMDWZq{Mv%6$LZ`PMp+)=C6paLXHnf=GUv3G0*GxoRsFk0ozwIMxN zC7rHO2I($bw`q6)mgwrg8t_&)!C|Pdr-ZsmvWRc7iz|aGegEHk)}LjX`1RPd7#vlk|^H8@4%nsU7OY8u_ArwYk=z4a)Abz#)57iYmFKk=GN zDoLU^=cIhDJdT7Yi`zC%Q+q=Z zI3-t!9d%A7b&&q1O8g1L?K07#CuXE*%!dC87H`GdgwDf`KDPx%DNP|(?opsM*V_-K zmyP#K>6X9>?5(77UM)PZtK;3?>Au#<*k3D$)#jDdw{%PL+EGuE5PjFSF!d%>7-)H$ zU9;a$R7EViQ^IS}QV7wZ9Lt^a>sL@=33#+8=s!PHE3unlx`=eV=e^;S)ir#^QAp+z z-%$tAHAZQjfa2}TeNRC~U#Os-Hk3GTG_BJ2X11l=9hEa+qAd!0nZWov z=~JonMCq?-SL%hMS6DAG1n-GOR?)N0p)d~fjbl<)#J?OB!EDyW;QwXr`|CA+rO8Br z+#ETr>9U#I%H96HB$2^p&L})J7VLU9R;F@_E#}o~`M)>r5Mib=H^2GQeulO;euTpr zQq9t;Iu5ggUqf440L4vQvH_{f1Ru3)G83yG1QA&IAh<|nJH`GRd-11jyvCcp5Q#ZI#+b0su;G3jijwoia!qptx zL=+Sb-XgG2L6XZG-t;h(_=hp79<3J$5uBN(Hyh6uh?HhEIe|a~o(-Hl=(5SkvBS~w zz}iK})z_MUHKZXBo0&O#Pr&ODi5^aNWR#)?ns5vEOHmY^8FiKhh$ZMEc_nyjf|!vI zXWNAph!^nYv=oeGU;?Nc#CqaT*g3#@$RB|9wdSCAi%F@p2~W>3e%qqZp*HA-=M?}3PDh{~79>D;QkJ^J7#N zFn?vdGfVn5cwFfVl&vzUB$RSZWhVUXc#1M-6_CzjSCt=3TEnz_dNUsRXXwz!r!>Jy zkiA)~bx7YlxX_Up$K+hoyh=3w-|fkT>)7>KJDQcuz#(xXd^e-$IR-Zuv?nYez;)NI zpF@(a@HuTFa8$2RZ*C!07s~9!m$&^*{qfod+p%-?qnB9pz};*_9x4|qXX!x|)3epG zMbLnL%jpOs=&~31Nx8#2YI|~@p@qGk{qMprwAt^OF^bI9S*`c4`hp!kA1-`gd6j7a z{*I(n2WH@PwU^02#UH|LWr&p2D&Bb} zt5=4ExtZ$f-%t*bd6dqSjRN&2>Pv7iL!?hgj75DrH73Qm794Om?&Ftztw_fE911|S zU{boHC7k6oaJ&fED;wSx4LFB}&IPbq88A`%303W&-3b^+M4++b$< z(U`yLl?mH+^1=VInb-R{K4i%lp8X69G2f+FsVVy~FxlPWHxgM0H4|qu*}dSh`(hgO z%Yx4|YmU1)%A@n4vIH6(;g}d0qpDf+DbM(g2Yo!4Hvc+^@6lMwp9r+bOp1S6fy-9I zQVy?$@VtA{Z`h>3$zRUho8a{U~$dFXf40S^@=(l66aw zgJ{cB{}D=*)~ZVXs&|4GF*2JtyX9i3d=5E^H2553`Bwa70!$o`VW@u=CsVHLmG-D} zscq6?G=epV2O1y`?(U#I&(d2?Qzn2IvYFZocIA0ovmtgOLR^4t(acZU8jQU5@h@up zZIi9eG%CDto)p1L^^$%-h?%8Ja(G)1WUOQ>Q~pF-lh9^gZ|Pk~cIC^sBWvmSoDx^# ztcx#$ZveKvl?MD~%NnkNB50MM3T;I^1thptad&0mtvX6a%9l^5Q;RQ;y zx4!BTtyC987R5C)W%b9P*L~aDiH}T?QEOau7>?dq{logwxi&C{3DVXpkS!2l8C)8wli3=_I|!CEY7nCP);@(r zM_UJVZ$42v!%}gNu$+ORXXZ1@&NYM)TX@GlHZ^tAi?X@&&TLA_-2dIDI5eN)8&k_4p{UG^u*xU^tASg zMg9RW*uei!*;Tk*uNQy+R%@veOoqrMU@2ZEnfyyQM*bj;_C}mP5dbn+nb?tiP*O0eiMBFEizCToOxbN0j+%s@~xy^;X&W3EJ$ z1lDuA7Oir38UPyBdza$4s1hRHC+ikT5QQ=}oGMW5Gl#P_Z?7$zQk1I)OV%{V>=}gj zM$DZh>@#-&3&{4yLHlQItyaES!JU*oF!?V;?&PGU{OKFLG6&69+Gj>7m05HR*7(?e zVwgTahb)l?P|D5@S2~-bJ=Y9D&h|>M>$c^?5D7OhpR>d>8r4r`!r;xGuWjXpO&^lB zJp2C%w{PoE6ZAnYD zTSu)0XYiTBFIREW8}8{vU=+lD+H`XZ6+Z?tL1NOw;S+MM3EeL9(Wp*}hyb&oOzZx3 zP#UHGctsg!puq3Kk^ql`OsvBQp+cwEx!n$ZKi=?T?fdaJFv(TtaK%eNAS^yTr^9NE z>PY?zYJm#O$R5cSRuhKR5fx1+9{^*;Yt4q;Y*~~k+E;x$^>{zOvV0y=H|F|Ooh6Ew zLTxIQbwMBgVp~T~TSMKUqisG8Zh)D6r&tl9-YAeb;9Ps<-?b=7NwuAcP#GMBSa|nC zG&zn|xL=RCiv+5cb8Nph=>IOQDre&nU|EpUdVye`%T4p&xAYL@+iOrRewX>@15=Gu z)OVE0-DnX~! zBEdC4Lv9p7d)c~?cF!&gR%x)ID_g0e^J$)@YlUnoMq8Ac8r$k3+|>&}4>HTI%M>Xf z1y(q}5dgo%1$y;fzv$c|u~?>Nw?5G*+D*{N z!ph80>7p63@sl5eo;1t~@bEJ;tshVKZpxd> z;%n<3M;5KxJ^2|6PK>S1q>J!i&~eg-z{9EC4uZlWL_A_Wuzmjq6@H=#~su1$b!gOa+3#G}5Kz-&3D1<~mN= zI6(;^wJ!2>x2)Mry(&st{jF8EnoiuQ-T#(XmH=Cnuu9EoUk_>$XtEihu_2~YTP&U% z0`jD-m?V6zEL2RLEkQUNJr=Z`&iumzoh~UFUMbY!*i}<$lGU`S|Lug$5YV*}#822i z4^uFxdF74lxNcB`;SJNSQ^EZ?&GITg_0hLnsWY@!DsI72_X6o1NBpR_I=31-R|PO` z9`lx`%|GiP&;<=rRdQLV+vC8urj&pWF{RdiF^sl*rXZ4uZJHklg-xY2x_IXhV%X8! z>sKQvP5h8)t~G}$13w~}Ad`$LUpBi`eCS}X#UQtU_^Wk0D*TFUKrxi-O}J1F%qnm~ zeY{bC`E05RO2y)547W%Nk)%!l@3qzqWJo87##;rhelQ7@0+*JMqShS07EF+wH}$6T zJm0>E_A}biBU1hcrThcPh4adL8xg}unXbcox#rWi(|MIK(*m#_<<@8U{B?S72ZdPF z3bR!Qhc-Is7Ar)|WynyTAldFAnw`wE-IIYsG75{?SZCc(u9_O<@u0#_!wN;1vNmkq zW}tNt$W0Fq$~i;WC0D=yn5nZY8EybX7yxjUGT3fk%EavzX%1mJu@!Wo8mS0^1foVG zQ%zZEz4u!r$ZIFnBC{l5VIPJE&^X7A}1|EE!53ZbgWA7H)C6XEPUY&SL66FcJ@dhrA0= zI^cgn3@-&{4eTGin;!wlaeMk;Mu2r^L{J0wvZUgd0}t>W6fN4~fjHHsD3n^w_OTy7 z2L!0accdrJrP`f>M{@0Gz$2O=&?iQLs&+RCu;-SgIz8F&Fqn=M3V*Y zPJqbtbt_m6IwGq8GOX^xtkL~14#LE)ssYI$=1Sf8)&V)JSn0f1ezLEY9(wuJxu>|2 zNR_N7|Bzr3%Jdyp*$`Jlq=p4{gGg;ls=Z~$(oGi;PAUM?m~-?SPi|4l8n!#ciKKo^ zTzdy*8dQQ~1CZ^W+;_k0j85zWaq1sFrq|N09$0dK?vvSP2DM7DOB#aF{;G}RZ3Om? zU2s}l|MZQUhx{U)IBE*bi@W?Ae-$!CNihF*EG{8uq+;t%x#z`FsjOo(R1gEkV3J4X zo41tpr$^2Pi3aUOM^V(aYrf8@5zn<>Vn8@AUvPsq5CJPL-0nSC-pJh$(dajS{WqNm7&oY7&hHvWhhALXbQ2f!&R$d`Fhx5ek?JDOJ%F4FZv*Hxh@eCzdgRi1 zi9C5kDCAc6Iq!0m2|NfLlKdn}eooY*Z~*Ob7Sv{K)?+(I_)Yc7g7?9#ivPzNQ|NJX z!nNXA7n$6I6RV)VaEx;KdcE@l?Maeu)9aZ1L+lDB(bFe z7g=U3caC15FYt_eX9J!UPPwy$x|>7 ztYeI00@m9QV3tDK`O1iCo#LcM#k4GH#=Iye(Rt^a^jhNgSjBa>NH4|45zspXwlSol ze-fHpp1~rGqwKwk?BLW>2+GDJLXDp~YQ745!7SeoP{;T_8UgI>n-6*?{MDZoUWMRQ zWvA}ivHQG-Z#PYXf)zWAprF0Se@GLuxt3B>Bb9EP7gEa!G=x{sC1?#CE3L%~DKAKv_d5fjR zy^(l~nrhq@ESCN-x^ynhRc+67^0}k7Sal)f@)=coJnfxaK2M$ImX$zl#FA!yx^=Ao zFmHCPN*3mHcaVx~edjcMO>gRlXD9Q(%^vbX2O@hkAuPExH_XN0&v6V= zbMvB0onq!^;ZH1!=aLx|r3mn$Yk5WC<+(-+BZgYuP6ISq%97QdP_h=m&YBG}IqSKP zIBa+hn`SwNoOI!%^sOr!hxvfH8Po=Ss0dreb=^H|T;W&tb$F`R%RNavZOCFQ)Tx!* zh2j-9c4>tnF_KiGq$>0f;cm8A96ycz8gPIgNp2$_DbCuWJk;@#2 zZWd2m?mLCWG&ORUu63Eo=79|UFmr@gLqX8D2Yk4R9o9Q!s0wP}2uWINmmB7OF_(R! z29N5(fV%ck%AgsVrna*Fp>7}bxmD7{Qzzwrotywbv3HzUm*8W(&mZ+ZXirMpaDEZ7 zm}2V+%2em{RI!a(PkWKDB~cAVI{&)98xtqV$Fqio3q~L^k0%`$UEv@4KJ)nP0GtK5g07BGw-k&?&1Es{+g@)+%(8U46@O^ykT2SfUtNqf@Q_7k zWgwjMvO)BX8()cosiXq$rU{4$u`)B+Rc^oTZkWPVA{Ennq2IN6c5<|)G|8!D%-U0? zdRjX}y7qa(+hwlXZH!NGs@OrPnAHd3wp|)S5jen^=UqKS_)$b>wd zT!X6KJb={I9Wh8M38=tAW8y&)s}<}anLf9*7pnljIVfoEmlVb1 zlNjnXocoXZYY>V-$Fw&a-i!cAK(@aSh7B@CftLpK35!LemVpywL&!TTe6|#pL}+`} zp89qY(+1R&l{(N75wvv#?xbR#jIY3tu>u@EYu=%iU5k#N>s_XtTVsai$z)!e3uL-h zXg-+Julcc?nB9H)of&+bmtoVvyLAm4_KX%L;yUW-?UVfH3|7=Jsy#AY{-8)gITxLE zWvewN7EIh7=RL5}kF~Oc$fM^Fp)?Vih=Mh)=nQoCmc^YS8LGOBEod1Yhklvr<8XZv zQE6eESCh#YZZY9fd=A3#Y`EOaHRWkfXYu6I;%uW(js8BMfzr9miIjtWkI^a+Dkz5d zo|yf!xzpKAnS@O1bconGaWY~ss#tYwoWKRO>EA^1nVwt-FSo}8)O(5hu2C$2l;h)W zi<#4LL=bQ?S+BJ&2Ce8We&*r2cf2-Ph7Odpo+_z4ip97AVTH;hglRCsM!T>Om%N;+Nj&%V}~ipz<@3(@GN z0_uVZFOc1VdeSP{cM|>J#{T&8HOF3m5J_4L%Un|nQN)J!&7i*6H0b9G5(mpKX1X=O zJ1}w$lYVM|vYJZkpHE6p_vPmW;GKoxOE)YVh->L75!D^3$UH6I{5v^h%`Mn-pKZ=a zETDeHIy4&6F!hR|%B=m3ab1v8UMhNSiq~k~oIk_1qeM6ksX=!g$)omwhR^;T z^^-+aq?F5)=W7Eo&x3#Vd@?p2L9=4;IxVFM};1B6g@lRPs>xj)l zB~mQDSFWru_y@!}xmM&QZRdLB3|%D0p6=X(IRaFcFmabMgByS>DG}y$4~zoeF+>O} zl!Rqv_NuF7)kT^K&swM`VYiCI7o zvsIW=IkfOnCzX6vH7kh@Trf5mck>3?t4Pi;{Wx>SLP>*wpvBAJ%|Cug6(SXd;3c_C z28lTt?&4(kd$^dm?<{NpwieNc@^=QBl$073F?m^{KqnT8*Wk{>KEK~Pve{Fr(A}}n zh)?k$QgRJ2A>+K?TMd9IR@LMgBn>h?`~pZv`iJdAzNs_i4+WDnW2^MFu&&GprTU}R z)7YLGb6UFUJ#s?wX>cJ|Ju*22D~a!Bps>o+?-h`Z7nM+-!zL_N?oHl5Huw`#3XdAq zIFy*c!8FHu z1Z|h)BF``~s|NrMv+SblC)uDF@pd9=RoV<=^kT?BRdpqj1Z%PCF`aw#U5L?&FBLAm z->DOPugIeRb^{0Io^`KV14%|bv|+6Ydk_l+6?MAc|i3InoTTs!f*bEi=23Sss|KWmv=#Pozo}_B$`)W zuZ74a{2=Rfq)^;1H0sf`pCvPj&y$Gc(Sr4VMy^_N$!<vtdcW;(&kKRS~Si z#ZL0#Wbq0{-pA#^2Oa6J^DLp_x6|AEd(H9l|GH-5lhe%u|L}ls(v^F3{ZE>^c#J@A z>J^rdFC}ZxHran3_k$yhbeUD8OkPDNI(4#`n4&G?O>)ij0+t!4ML{-b!!BHwn!z7 zZlG;`XfPC~%bub`gyYJOKE`I^Svm1MqiQ?@Vbwidv`W$I>1Zg8vZ~yJ)@R+Yj0q35 z{&Jq2-xt-%L6+TwyS$g<#@|YH^c|k!G)E_=Y8lkn+}%iKvW2+cII-k|$op~arF z{y>@%O^?xasy0|;SG-zgABo^J=BUHIha>h3H|7_KD=XllqQ^EqQdoIN_TLiibg7pq zFtAx=V)>Sb-q3X8zYMPQ9i2k}p~Jr5qd?+RN}7IV4)bkc(SH~)1IZHHkemu zJeQ|^=NR-HBMU@Y zMBL#GNLz_K6hgF{2-5DLL7$(a_KvoC1z_v-JZU0*xZb^Ih5jN(;F7=+UFtQ+gb5l4gT|JUf?)tr7<_Lr+ z+1FWBZs6{bdpI(dKthXf00DG>Nm#l6W!50Hk`!*%WqIQ3?Zj^mqpVRKV!C1RbfKB5 zi}!wds+&E1EUS0&Ap#H1^2Y5|&=Pp|2o<1uQh?Ks`x33-y%aN$g6`kXvgHy)pRw(iJfP#ycZ_?TAlvA9DppE6p=MyqoBH=|1F3wXdUmv@y`=u?xgxk>rI3tZ zipUP$N3k68PN{X{ZR?O+tM`{3}!l^wnlhSK_g(= zj`;Mbr_0E|z4y4$NfHJnVEN^NL5``KOb8mm1~ZWvl3Vr$u^e)|ckMfxzs{Tzxe{${Puu9YX%Q2N`>5N3 zlgdsbc7pRS%g48ZRIOG&0@hPMEWxHtAC_Wz7k~L^{hOz&idrsnZr#6gPE_oEa5s4) ztCQ9D=P^g~n}PH`<=`#HRnUuDz!Hu&_Ovt(XkWHX)V;&Z)?ToM+f-$4JzBzP@)I}F z!~>p39sgq*zazQta-MQiL_Zoj&*cN+Kx<915_zc6I)32QEkw44wg&=6#WCpU#eD=@ zU*EvxgFtfec8;DjG=z%5hpoK1AYr8P=`X$1vx-9O2Jq|BN2A{BX+_|kg#htwl9G879A2YNGdEU2NP$?>o#Rwlj8R_7sRcgW+&z7jV4`7I6sY(Q^5Oc! z+l8*t67c^$b!*W>{9~@OGpb7I@&d2q!OB*(x-*B=1vXf4myy-Ap-L|nadk|qx@*U7 zKb_T}JnxMWT~z_@q*RcMPsLdsFE@Wg$@2*z*0|mneD-2O=_D-`SIw=> zMsfF;INB8E9j2Sv*%bJZF%;u!>K!WVg4z*te3h7kcA7K+-4hM$%IN8~>orJsP*puX z><*2lIT&#wWOrtZHEwRaI)3;oc78hm_Sb2E!qW8Y9bUFTt;;%^&JTKAo_s*fmuQg! z(TXJwGGaQ+JgbioLE>e=C*#82VSyh6F_bZRs*2u-j`!F42^Sh)4q2w15#Ao5+gM24 z4(JJ3^Co0QqCeZ1QjzT+NsX!aG4y$$1%b>`rS;cwEb9?Nlsk)^FO-|xjUr)=MoByH zi%4ZIRuN2*(Pw_=u2Qw5M|~vVWZb(HugSm^3H)yaETegbi{P`VL2M@Ni4H{-1b)5V zGbN+e{(qh-nY_89SlJoNVnW1AW%Ikd`dP)Rc^6x984t^nw5->>SQ|9 zFoD@Y)PR$+t)6{Z4EAFY8heXsuIQfEwTo^2V^v0XyQ1O?TNN@!y>BIIY1xWEOx7>@F zql#U_9k>hsjOiIBH$55r5pqmltQ4s^NZ~y2d@|UJ!gNM;p;8{~)jbJ}TAU z%^Aht)q^E1%c4CfQ*NWoBs$S#qB2bBsuf7T!%XTFmjSKnF#zUCsJ#{sFt6ebviCij zI=ZU+MxI)>uMi51#-E=W4{zv}O(+rSGRcIzR^SYm^?S;U8H}Q%Y7sv-52S>X=X(o7 zh1btjh_;mJU5>|cd_%=suhb&a20sjNIWTeUXSnHedD%dVR`RSU@`NKgWxGrSepe2Z zyUm5Jkr8tPp*408t0tznYn=s(K5Bg2#FX%NsZ=F)+4+ZWa=JwCLW z>Nsq3IF$N$xaf~665UYO<0N0QR6hb;fS;-edwW&?z;xeQ4Gg4)thK5)2zv9Tb$w3|eV9Xc+LoHI8D3z*M49ln^LBXZ=RBTl<>5f*NoWWy^M`ugU= zm!GU>#AQ)EU8)f%jyS`XrIn?-J4Ns5qeU!$u(`D@}zDI zj_LK)x^-wf%LhXb_|8GxD@HGE>Q5P^1tEPN;w^U4u4xmy(cphz4N?=}SL+s1~2#sMJP zndwY8>yRaer*xuoGv;sPip0#cO_b~MhIOgrR&n@vy>&nh_nX9j*YJn`hqEO|;K(C~ ze&$>NJ2Ha%`@~2n;wMFVBNv6)M)E#!6~JLA4KmC=yCFIs1ekZhkw~gElgHkCv2+Sj zT3~by1FHS>n1s4jNbAS<`&NwLlfB^un!`@}T|H%iSto)VbdV=S8&2MzOUG;cma)g+ zCeoKh%5+ACHYC~0=Re@~8CjOKe&0&d`h@Fdu6>=m;U#O^Bu{JY>*1ZK(8UaWfH@V4 z4kF)tKZsh}%&XZYK(o>`O5d!|{pQ>v;=}c758f>*%g%OgWU|1`hp+byZD(r43C}Rw zU1nS?b;0z_RizFz8}G|{8SW{?G6f1a45#X3h=+}sB#T2|DrufK9(TS-5^0hE+z-AX zM-uX08x66$ zvpumJC4J~D0lE*r-ai*1rp=!<7o#EA7@-~E46s^n!&bUQ@g=_19dZ$w6+OnQmlB#T zG%jjA9V1EAKz;6jJD4+~r@l_#HN(<&;o?U0d816QT#;!zxk|2ZniV)iEJV z!$GZQL$p(HwNREA7}A2qhKI)#TPxPUx#Bjd7-~=&jTmxQByA|DjCtjW5&M5na*sZ3 zjNjZ86GYUzLUg|o4{qSpmg87;!KFfaEzt`#@|gLAC^X=bQXt3;gLV>Qb+PUMkKk2z zt}j_e4Uj^{P-$+@VS_+Hs_42TFEzmAWK{Sbhi4D>N7lK!d59M6EUWpfhq9L@)x4!% zHhdnke|yy9+!#x(L$jet#`+8>cdF}mmNvRykp=}I{@zGH6F}=-wYJ*?tpa@u^({K^ z&ODQ$51|#h?VCLbAXX!JOB{82g-`Z!ax!tdlZp3fT`ZvuccgUb$`SU}U4nL@NI+0Y zRA~-z-6lMQdC&5*!9UUH=cmL_PCo5+!u_ZAXz2p)xD0~0W2S)L1j>BRM=H+H8%uxiSOzJad+1<$a|G87YYAyl+v19EQ zL6x?yVr;yp6?&vh+9Kt^wgOcOG_jtwC!{lZySSj8nD7cBh5q^d?*7Z28hPAK8MBc_ zW)3ra=Hp0ZKeZ)FE(B-sCmM>uvuAFGR{|KZlL#e$&Fv_vXZ)3c&5MysgTj9`^$ufXZ9+n3m->LLG^i>93%_Q{(E z7V{?9B%qGRU^Sa&w>IoK(EUo6{l`i=_k{Q~ayWpID2@ICLHY^BV^D`t`GCQWr5_D4 zVz34FoDGs<2rYa-6xTCjEAo;ou5f}%^GTonEo&Dk^Y8BDudYJV{nf||Cif#-dgw9( zqMIj*1gjoRdCVk^u%s@2DS5{t`bt<)sH}#TE^!>)~_IEMz%oYaoSaqn`Ntj8%N!>xUva&(gd{rZ1b_G>kH|exi#*zJY!l zZZNKt`SfZtI=RFxSj?AYRJD$%$Henu~>(zz( z>9fIaqvQTj9Pt%W$^pA9JYvvGiBlqLD3pphXm8~^ADXl|G;S}av^kUC&U}WGwJ*p= z0NKySucFfOqBeMzj%uDdUoeLKQoR$e{xk6BuOX*mAP;AHjVm_R6!30B^d0!6OQE%q z6Vu#6*am=gj~F@5y}=SzYMT=D8G>^=v|Dzr-W`Ke%OB6}O4MaK21T#SdlK8JeUm*t zliz-Dz?9Rw+|zRINYsh4-#jQ^W)sz8clz47`I$U2zisckvN0?oBI@-&H$8h76`eAx zuG!47{ z{PU#4FQ67c&OxVX#?gopTSk(cZkDZj_}zYu@hmNS(Fzcf+;D0qIeQXo(|=roW3gsv zfLr*q5(4h!P{))jEQ*>>r!BEahA06G8b>j$2KaqMRV3c3W7@VF;M4yyf+|f!2Ge?Z ziZ{Z$^GjVr-5mlij7`gkpKNa_t$Q`w@DhZ@|F5~6!gEOA)mRIlL85Hfr52D80wBVa}zPkoyo zOM+?dL0>i2SwfmX`9Fuo%gFuFR5tXzk|$WpN6Ka?YbO4_1LJ^>-6L(!a&7>Ly&y{y zoEdhDb;ZjjFJ$gpM2PtWD&y&FCZtLA`>!A3cseC($pX1>hwX3avg|BxPdzTP{0mF# zI4Abp@VUvXQ@Tk<0I1m&x3*%(LwW6?b1Q5tk-ItNWlYHr!lRpQwWZILcB_7gL;bIQ zJ5)LoOQObHN5nm9qt;Ut@@3nP#mWeubcvlKhRhYRk@2T*W z11xUW6+=9>N7WUCg+z(jl=V`bN(?u1>EA!G8C-r)p1O6VcMA{+c%1e$s>GiK-nTe| zrw;48#RA+h=~cBGW@0*cLEI-tnt;k`wS;Gk2l(b%3$RZ)eSAq3TxzR;$i3z*0U3w( zEvmTa%0=6u>17ai(!EzJh0bD1e_+>J6ljRl#gUqPxIjtuPH?9U@NnOw>I7NyRLmjV z2_1BNEY|HT%x3}UHVA6rT(Txuy(jXqU1^V7s5 zjSk#^M7b0x;^b}m3>%Wyh_HOiL=^-EJyfhp8_IIujs4X&_a_}W0D8%S;pT@sO%z6% zi>~OoQ~DFDy`Wkp%KH>(;o%ewWzLGNl!9u3LuKO< zdSaS$v2Vw0LI$&HljPdY@Q!0wON~vL0PVCF9o<$9MX7wDN~qI!CrGKLQ`?7#^nDp7 zZf^eyG57AsB9*t!QzD2t zCl;P3g8PBs;vty%YbwWzqmehTE6dcJ4u0}fS|UYKJsf9JnMv&>^;NB0sMjq^-gvg+ zKWkbQ<_z6?H*jiclWZDkHgtT6r#X@4`)AB|@z%8o{QCQh2gC z7C413*J>gZya_Ye5xhi>dRpm$kQYz)OmyZj8ceZ({p#;&*iHCvG%vh45W0!WwV4~Q z^kRV@%CdOVKHeb7he!wH$76P?#T2JI5pUV(*?DWVA(`a8Q|3=r7EHQZBSw(m8;BIk zQ(S>520Fe+GmG`x510szn1xRH$S_-fbPWb05ekfyc(PCA%qw&*rwcp6=$JQ%1wB2r zZ*|gFJRhk&=}+J_FPt`oM(C;aX>m~%B_VoToNFjJ_U6Tx6LKe*nbjWdUZ$*7CvtQ# z%nn%QQ>;1(F@*_&Ohv~;7Jl`X0>CxBo68q4!nalfWNX5#ys-c} zI`yU9GI8(~;+3K*mFD6S8a+TWihfFN44+^2LmOkGSx@NxP6Bs@lFqJo<@x! z_eueuj{OPKdW${dFPc3{)d8@tcIEn~_7bpG`^-uPVJm%HA*c7r2f`)8 z5c!j(x-J$*L3?bz^0&|@#C=~DDA7t|&>psMtxKd6ULPe4uMnuR_cl(0WoH=uKbqp} zi~O2IX{MCeU@f1Cw+}+w5qu27us+ZX8wsD@8wiiVZk>)5=wFxU8j%-%n3#@$f1;si zJ(S$JfHCZ?lLUqC*QjX&Lm2Q?aVFH-Zi8o*HHJMNfIJG%un3E7l8KV-CG%4Qtx5h# ziRO4Z|BQ}%vMA=uETS`N%Z8hRgw&G2&N%sWrDY? zEI&4ZP_4r>aH}T!gvaH8KL4za9)Bs|yM&|;7*;B{lx6;AEFnML$q$2ro%40Uuq6Gk zPX1nediKCq>abcC3+t98d2*xIC7F57pFie**xdT4 zZWyYa@`n=Ufqetgfw@h%71;ldfQvtt{F#~A2&d=Qrb4QiAsjh@>e2#gu5rXc-oXEF z2_$G{IUHC z@7GNdj(QpkHjheIgn|phROvPt`9b^8vTR>psz?nM8DlK8kJts)9|ZRkmw*~lnS6x? zUt>2|J%3wU;FK6{c#453^22tdc`~98=MpFXlX$~95rUfqBaoPvSW_3X^ z9jyVfr%BdxMayC|E&D->w_Tq(jWR+$bRLu_^vNKdbm)Q>Zc|&!mEq6gC;@XIb1Oi4 z1s|K~3l5*g0q@NuMZ=EMHSHe?oC5T!C+z3Jbm~XW5VKXx~jFY|vvQgIoY}wGH=p%vB&y0c=NlJ9?&zGL_y!Ay~iP zvAwmkjut5wPf|NjQhm{XDEavX&fV8j%!+1K~NGsp)Z_G^zg zxl!&XN~1V+0~?2lI4z7+Fa+)=kUEU(nM3&r8(Y-Cv}49?gX`P_Z{m`q-VoNO-|q>f z5c@)W=LMo6cz4}>W<0}D18`$+re44=M!G@ivUIecs&&Nn0(xU0o*DQoTnk%~dTB+= zupff~Pr(=*XZu^+@Y(!>1H~p~%-!mYD9o+cB*aAD)v!HOvbm?)9ncTZy}x^wv2jF@lO5w@&<&Q>xYJ8#7&st7 zHl>U&5*^auw#&;WfnMRB{eqiB?u9h{(e2cZUNGM^)E7or4p<#SfvKv zTa;ARW3bWOHc0@(CtL*66VdJP!CoZ&DA@;x;mRVmXV*dt&8k(6+N`p7;`vP^9ex-hyBLOZy)EfVe23Sr37MNOOllgaMoL?`BOh##y^o zGqAxwlb~7cwm7_#oqYupKiAu*KAQ(?~U zJftKklC-bKi#cSniwSSIq6a7Sv;X}{ z*5FrX@KAiBu4MMG@7zh;tZeG2K4iD)V@Ht%f8PeVsaT4xBEryW8K0iHTCZR`QffMVZwm^RJUrh08 z|5j623_Z5u2X1s*5WIx?MDck$yNPnwf-Z;cidChh9uw^1_ra5AJ=8-v*=)wg32d`1 z8Iy#TQ~tv-gK4Z!CI=(m0FgMVA*J=TDyY*|vhUftkK9R$h+c~$p~?Yx&Hw*)pcNI7 z!U3;rI2ooILuH^`%mC7dH{XA=36X9Pvl8~RfA9^gPSp_G{K9m4Z$ockn5AHRt{WaS zsxeTQ)61*_#@m)d1eaF40E~PC$&&0rv~ODB$fyy=>^~omVr&-nGf7Ir!eNC?U#wzH zP#wi64}-QZqyWBw>dwwD_Ox9jvFe*o2HF4q?1!0FE#YMwEigh^ZJXlW3{=oZXJ9Gj zSF9FIILWW+T&7|Re1#glP`vc*ivmQ=v69q#U!Z=o8N+3I6b(+>LJRD|=fMb0djIK?R48It2s5?-1S0^WZM~=yX&LtNwz)W|N z^4UR0`!?Do%x5QoUs~97uXlthOgH2v4D2f^3LeFUvVEN6+|FF57Yb0uRIU_()P+MlI>3-O!z@}t6-ZH$M>H^R}N5e1nKu&JX z=7z)tyhyY47(S>+6=M7J>$6FF*Y_CMqwwBTm{nFSC?)dEJY!%VYN|V=iI&&gORSj^ zIf_G+9dlF-r?4qd+hH2oS*ATno8(z}x{Nj!A4KeH-W|=^Iq9N0h-&9dJ4+D&z7snZ z|9Rd``IcY-&hHq<$5xa2su_iy-d0Der3E|h3C8(Id&5Pk&eUR0a1s8XKRJr$mi=f3qNVsaTzUEm@@> z2Y?#Iw2DcIuJ-mSbDP!y2_UJxwpKFgFY;rch-_s+=%DC}#HdW2zpJWsJs%TqSx>%L zNatM;KZ3H%w{wK5GwN#&mIm1{7{5dD8vPe@Bg7$xb4#r&dZkR2)@Otj8BA-cFqRo( zuTZIntA-EKZVpDXRoq3rNhnhk&>wR5tW%Qk zk8Cku(o{F*_7FWLbpMAW%ze9svIs#w1{KzQR|OvD5sRtj3Lkjk?}1w9L{5z_t853h zMpJ~42hXlOSJ9XiVw?==vom3=5{0pt@U>_?(uE6A)NRCtb749|q1lTMff~$XjICsk zYzU2S=IP~}(j_BWV&_!xb2Qg@P$&{HZ6Q~X%A#o=ocwCs zT1b)swODwu4Z|bSdq@LR^mjjX;CpSJ8CZ900+sYg-454b`i3?e?`ldYGA{l784Bn4 z?NyAhQ5>8)`Uba7vetdQL^jlStbS=iNf3 z%g}Z%Gek3&K6;Qh9gbHduDTjuoj%B(BPdjPfd&E6&y-92!)r6)6_HujCl2v(V`@Fs zRuS0;**KAv;Q8GI6dHnlR|A% zM#Om=r1kb%llqLM%6OUVc~O|-Dh*HRCJX)pkN&BBwMDSjz6WeUzSyp7qi=!lk8pZ8 zIhSt2l7Y4I+I%dstxX_Qb0X4iR{&oSg=oz2fi`g)EyQvbY7rACSAuk&(12AhYwDwn zkQpetpO4+d9nD}XK4CeP`ka!9a&D}PU8Jdf6B$|=d8g{4!yI{&syJu7ZC~0jO}5bC z0|ew403KyFOjIy8Z$a@mSnEl*Vxdv*E=j)_@=VPUk^875XImYH!5MxG;LWosZ67(Q zG@cM_ZbwE(5qXw@^G{ zJ>`d|?xB6Z7W5YgsPO4sCsd?Zf%7r_pRQai!`>tnl9^{0+S2?Tp-k`|#cmeV>RzB! zw~tj6cv~F0ZUAlKotT{r!~%>?j}HHBoOYSov1Bl*Apm=?MyJEsmd9)b81V$^*s;<4 zMElB+X^*f&*n`0G6X0qv-wI*){Zfzaf|5)kYzgo&^`D`#Y;HXQ8gIWaS!!? zh_ZYs6ixtWWoxvK1Nx)j6{#)8Ws>K7x@Z!mwN6&Bn}j5likRB&5{n@E3*1rcI#8a6 zh8%wAe3@E&BPVG>I|a@~`{UGznVBLw%k57{v+bh~C#i}n$X83mkpTL((FT(dnj>+5 z#hkcwM199d;xhl4S#uwzKu$cqCcMj>J|6t$Yd{S{5IAtx)JSEJRng+KO6m=_S-WBH z`_pDRVwtDJ>^VS&-B59~v)xPCVoO05e|@8F`sh?2ZBC)BytQ@9 za(ij;5qA&c{Lon|J~MWGXW?Q}+f40BUAS=FlLp{yGhM3}gX$4vaiD`h-PgjeKYg9A zk!j*sv+aTIXoG(Ww877iEqDhhm95W!LmwqFY+4hkJ71d`jm!hEBR}V-Uzu5MVx8&S zI*<3!O%x`>gNK`f&*8b`8V2oL_Itvz0kv_9qM<3=J3W=SdkqsYEWazFMau;f+ORha z5pA3!ZmLPO(1GJ#Pw!*VoK&thm`zHTaoF3pLLUN!VQk;> zUp=#_U_LL!mm+E7=BBC`#jx+)5_V9ky5ssQVY`Y@oN?XMKv9Ibvf!t4dADNVV}12R z{QkFlwPi!VEw|_>+dGF z4BRnk9+dYMr>`aGKEX&{=V4|wV)lf8{tX$XTF53hS zIP1e?kTRDxKPEYZL;5z`xPWcYdRZl`Vf+_=NNpJo;6vNNI&y7Rt5MU*x%TfDi?306 zIbv#7eih#8-VK(LXRg9~aa%iZ&x_ztOoD>;HGt2pBd$IA)1uC_Xey}c)Ufd09sSr6 zshnVtXMC0*gj1PJ%3>Ar7Z_{P5Y%5(f}rOqUtDr>YXlI#yms zor{9(z3fTy18Rf6pxS7?2e{pHcZ~uFnS|Sm0utu&D!JCtD;p-0!+RBT3T@>GmNAx4 zxbFf8cAJ0)=|Hk0Pi1tLFQgpj$umi9dO#7E249iumsSyTT1Q<>zJ#Id%_Yx6DQsq! zB=`98hWK8P4rHj(19}Yt~kAfT!Zj3jkO?1qJqY{@AcC% zfrCNjXUm}RrsfMD1q1gYci%V-QO$d}B#b%zd@)eBj`XK^&%3dvau|Wz$`X61=1621#i9IZzv=*&A3uS`T*-gKLQ0S zo*fJc<&?CWP7OT3>e69FsG1#ZTR;u`dk4aH!wQi{2i=EzPE0*3o7{g*1Tm#jSvJr=ZSK(2<<-PD}Rf18Z&W^afQ@`oJy@^BZO8xHwd?0lI(u&j2 z4$&WuElm0xX$@55QgqOoj?NTFpWH(w3EgBbB}dp+u2(KqwDJ@(x!vI*wcdVU<=mK7 zTGKBEVKI2^AUzRI*=KL;jwnbA#>g!LKT=zOn)DX7QLnG`WMYDY4X_%4gog-A1TYv71T`HkAq zxH1BxNWXWlmUhPvAPpJ`!JH5S=7u;qvt0yhq#s3_U|QM#H>H1&fvD~#o74AMlkPG8 zR@`jHKF>w5iyvWz#@bb30EQXdW?BN??*^|bw2Hl(@&{gwR z>vx%4dyJkAb^|PXcmVVkp=I}pV-W|$3!0ef5z)yk;m8nVR;H!KI4&kpi%g>>5*#>R zBlId^6?V&o+k5?yi!yBz1DP(g2OLBUPD4@rai2*pCdq-5Q{qDz=_ZgDMx6k378-rz zbO0$mCjPb<^mTQRW-d3;Z@hFIA6|xT3`8VU7q@o4j!E6JxNtio00u5j8mqx{{6xyL z!reNg_menjK3mKn-UKY)<6xG5(MMpfHIU0yEL-3vgg?mDkXrvOkz`vR(a4LflXEVG z7;pb^ob>re*5Aa4_?py=AU$nLI22F;GxFU`s{L>Ul?Z(!1})ExghtPOUBF_Be0*cj z#)e_EH)p`7h{%XPpkGFnW5ashEGYhzG#)pA!Uy`1zd=U>svcS`3p}M;~aP6YU=Q;}LSPTVqeV>ta17#P0O= zyHG+Kx}hUW&f%743;+tm*O0KaYtu+Vx5`>jwtf$}p1dT{*#CczYmc=(OaSmxl(+h= zW~xNjwW}J#v@68@cHVCncmR-DQgZpFq>4nyCcP+_PqXj`eOoOm+Cu!Z{<|Nb+L?K` z;U`A-N#O8Zuos>}8>`uQ|5bQJ51S@T?3^D1ahz|!wER>t$Xlo`;Y(sOJn(!^rdQ10 zb9TC*U#CX)U%?*Jy(x0%ji#jI+YaN?=bj}A z@bQ423G4t_2^QZ_6fV5mL2TPNNFfF1kMtn0So-61BCe?(m|qZZVi)x5NnOOOG%gFG?Q>z$Pf7wBLA8JOTr5{%y+2?B-v_Et+Xx!1HK9EB}jA zw>pQ(ZNVPCIKl zF5%;KqH+#_40T$6CX)z9H$6Yz4bhJLNkmq5bCSYKEYx(fSWW zaw6vD`WGb!WPzr{c~2Q!Qq*7BjVq5c!BW<&m;8HaXvk$vCSx-^AWjqhv@AzV2p%xF zYD$`BI&<%fZIVs<>N;e(+J2x=4g%zBYXR@A3F&woV{_Y|k6-X_{Yr0g5I3kQW2_lQ z8{KJF-zH<&awd3Zt7nZK#fEs>N%pfyMsg;YSRFj&)nPc)6daz5V~9*|n}?>9{-1jM zx^rZ(wz;h)9Xq6JHirjAu^m5w1`T|N{>h)&gf!_P?BwPcnXUo5&>0K*Cvz;{eY+g# zGKg>a7G%rnPUH_9fxk?00`fxMQMjQwf((ndyvIsN@nZHgLa+YnC(}}{Xy1D37#=$5 zX;CFf{eWmN`)JO0j;#szm`ml1=NIPe0{MlQDlRc~5czTxD%=aP%a&`^F8x|w(g$+K z;iv_J|0>z?i&G{j>@kffU?RHqWt=CSoKDayj`!Gk+f<@+31sWLjOAC7g|(qr`hR+s z3tvhEDCagh`Y}#;ht`AR5J~l!?n}XKk|Ne$Q!151gkCdLYpqNOqi$4}SzEg*- z3ympFDsi73m(N$m&7~bDEenhsoW~)@-WtL5&+1 z;-;16rS44VZVBojI@b^r@=e2<(+xkWh{>gmt=GC!GmJGp*g^Eg&PU8)kW2bvnSrfD z7Tz4~4q2w}D0gm?u5afCQU9>RaW&L35kLk#SYGNrDiU=~hGbuTmDWm*DO6wF)-^|c zx9?q67%6ox@lm>Tp>dMxP5zTj|DydjA0H&Qc14UlWtT1&t3R)hhetSU8{*UAHBH<8 z5%Q)kuT@D{l*e&ClT+rmCkH(-GdL*1@!HY`>@aCAu35xQp&>@QVMP8tU>`W(GcymA z4M^ZY<$_CL+`V)Ypzrp{d%so~7X22;$Nh!y$=S`+!3}^M?dIL5&Lvvtd6%uL)NFE- zq}NuX3s5ND{-HjER>Ddcc_c6}&9l)vXBGWle2OG*K!!yX}ei8`+s zBQ%4B{?>u5f;GqeH=KH+g9Lc%l&G=JjbJ5d-4w%QlYcJDe{&n_lnEIB-ZtDBf%-ce ztZ=)j@xqBJNh2FpUO1Pz^ZN#`lYB|yp%l%*Z^Jy1fy2nIn`}+UgK={)h#+HUbjS2R z4yqTPvea%bYD@wAszFwfb5c3N7doFbJ#Nr9A8dG~!;EYjEpH;$ za;klmXP=AYii^WEFftzG*#1`O00=pf|BY4EQVtZS$m3EbqRx zO!J}%yHqyho@EQ^@;S@x*JVZLkU{Rymr+a1ie zNK`_Vv$z5sSriKIH9^7^R0B33y{WG)H4@v$qK6`Z5Z2Q!z8XvdysM>CEHTu!uKI|7 zFQsrRsO1neamX&lJCwC}h^`a9?GXN0MPUe$53K zV1TS>V-(#|0lR01`-y!D)*gW-#4ZVw^&2R{yX1h}{mFlE$@A5f;`on5D9qE*Sv#Eo z5mdn(HP)ZJDcy0-2ow)Bzj_2Oe+3{dsgGAP?aE*&_Rlw+4i;~*GpPUQmq&TN`N#0V z5t}o4aHrvzCQ#00B6#5dU`tPbx-{2mPR^^501UvpRK>fLq*f@RAC!EvPXw`^o2K{A z1wM-fRDF@W^4~7G4^%%C*8#q4*QOqLl&P&%15kR#w@~>m+V3N|5#PJ?GHA@cDEv{5 zm)!(z0guTGl92oSv_i^qTM$-x6yZXtroi3Oz`^2p&%baQR6g_5EJe#5~<~``$Q8%Wth1qYhn}H-XURO@3%3n9{FOt zOY9(wopCWC2cVbZhviC>#AN>DZJwE!R?cD7p$%AvB~N|0oP*vVG zZfwC~_eKY_+$UgaoDU+}wC4-ID+u0q$_$&wYG8qGB%s;=WV*BYU|#||NSgh}u!kUq zc${JcRRCT#11JMQ1ng>B$3=k%E;SzZcVmuFw)k@d4Z9 zIGaH=5|R}X+!z?K95%g9KSzSgmQks>qkD|*z}M?qY3tZ^k4x4ZMbuUm2dHC}7#{U> zwBM<)BftM8n)_>`JgR+j86eqvf?;pk`%6-2@6OYIt#-sL3jP1`7S)93eg3_X<4DQ1F}jI0FXjaJ3(8>a1^$=lG}Tnc^50f>ojF|iG{f2 zMZeLRz7|%w%IZJ7M&2bGhc=4Sznul_N0ItTUt$xeUQ~YxHYc$tPc-dmXtGoQQw|_n zrcyw)K7_s?`c}?xkFm7Kk0yK-mgk&ZgbH~NHK*y1rtzhhn>NhzOnDPb{nAtf{-_mM zJ|S?#w<_VjHSpkm;!0N)=mzzjecn9qF?M|=u-h7=>zL9QCS)b`hKr!91Bab8X)wS! z%QtdAYfuY<##Yj=x81+EP73~+qETeZ%F!g|KU*pZUYLJp3$JLq*$8;J@iB3Y=iBLR z09aYg5H91TNnuBwf96O!$EAnX=8ObbLBTa~LxWOb=1^~&?L)SCfvQ~khW)nML@dxR z?1O<%H9G62I$(Yj@sP7)xhT%{94B8v8VR<^Ml+s6!So_2&F0WbP9Duj=TdJW5Ct?z z!(H+Y26$>Hh-wpe9EhWEgfzstRqSBd_`UHCIU=O3nWJtxL6Ybf;vVSKr7u&T-8hWu zgJ0jDAh_et5yEfiez3J!d?nv`)J$T>af6(`Vm^b#Qh&Sa(4`?!m#0UwMpq-JCsy)x zLn0swAC(qUIrmrlZB-95Ar;7Rr3Vr}3>?+Mf4us4K;! zUdd2^$CTM#|NS_rE?>($s|Mzr)^33O0`(nBiIRGesPc?3n$eOz7tj~7?))9fC6#*M}pz4r`dpr91@&_)E>lCy=uanv;kfa<3#_LXBRZ?s$07^F@< z3-X=FDYxf2W-HfL+>c1lcIFm*VxSi;HXL|PMPKA=<`3lONEsr%hpTC8Czr!l=Yv6v zR(Ceik)73T76ZKb)q#W30&wFt(5~lple7rv7vOlbYU3YfdVg(W4P<&oRKz_yq5iK28 z*$I?Tg!R;g(qzaY*xu{t#wlNuPMKVtbdzBt&T4Bte)xC{!4X=!W;D(?Pz6cM;M5oH zmZBh4xz|<^vdEC?rPQC6fy7JNnZt^Uv0$rYgbWrcUAN-!uF(wY&`j;SqveG^7Sox! z{E{7bhezaq*xxV$GQ5S#iqRx#( z*dlYvmubWp0f*CXXc9h{Vg2Q;Z~B@klam+Y07=KRxAll|qF6XD5PC+mAGnHJ_rePY zn_)2siCE~JG}ZD^-Q2d|OkDAY80Cnk6~#%Sbeoe{K8?27+chL4+G_0F2$(D1&KeI0 zkg`HcWAoBILwr~@&OiphK&GdOgObO3%cbD0%){(2bw$^y=*uM?{vV*81`3TaIi-5! zOUU6%C?6UyYR^j#x=Du7g>~jC#rZl1^`bymUaN`Lk=`An9(j`SSc9po!hXWZ-H6Jp z_hisO?)NI^cm4LrlU+FMi(0O31dI~kd17PnC?RseLP86JIu3D)#Y zFYQ*GX%toeQ_l@CUAfK(x)jtP;;9WGc6mnPrY)hhp;WwfaK+aMf^_wX#Wt)V-_u3f zyuNG-dV`B86UY~4>)T_@(Bx^Kf)-#yz3Rhg-Ez=Ea-_n`GEL2V$(y@LgiSJ$WJRT=Kpz711qx)x`rMM zavE_2C#~Q~T-gpSZ7~O9T&N@JiWgxLGbOn=}%v5dOfOPAwn1- z8c3F{?YV3cB2s*5((bS? zj+s@bg#R}ow6AutD~KeuEReYg8%4uUB|u0M^LPx&hm+ob{Y!1vK5tuC^B*e9K}<_p zkg<*o*C<@VCq1RmN+al0hvTzTz=PF^U+bnoLX>%Ufy)b zMdiN|DKdPj7SJrx`vX}pi*A2Zc>!QUxeHs9uCxZ-R2=KT`M1BSAfo!GBZHTp6pHm{ zjP0VVt{@R3lvb<89TjEY)aS8x2#*HV{4m8%ym#yAITeCc|A_NZ}$4H-CS~9vLPemw~;pt=$Ro|pu+Dz-+^hOA>$7NS5aZW56*B-h#wj}_<@E!gRL3k>*sDm!v*mJdmu_(Pl zqzqD_%^TZs6kz@vRQIxazJ|jV3=+s)++Qua$MWvO)CA}+dzLrBn$Rcu=m{)u62J|v zCFSuF1;VR#2kk!PN#3}NmR(0msnYc994G%%wRfE-qV*JpY(q7W;Y3Xk7o)oAD!_rE zl9BpVHK9b^f6t)3x^?q_0UNf@S@0wGxExJAQzONo6o_9*nWMb6hW7v&+dE-l-zL>AxuTaaN>wc!93F>H|*iTR3 z$ZTuLhZ-vmOe_G$O34Ew9too|hoor6Veg1pC#|pagTtlt0V7CxTOdu%#~G} zc(PaTX&btihdflAE#H9pel=$N-J2khuTJ|0NYN!OEd9B z6?VPE_0C@@@|Xt6$;dz$F!29Jama2_C11(;VgUXM>eB2Ii)@-hNmskt8&LwW6<@BI+(3!XbRGQayUYapK_g?=-G>uYE)H3 z7sgmY56vUmaE8tuq&o|10l-8!U>>eJ!wOv0nvByrcdrIApvH_1sk`CRrpuM$$pAobmVc4G`oGk9ekh>gS$s zyh0YLZ5yViZLMwu#!}Es$QFF7I|$$XINnLZkA|(~tOnweL?vlh=#ujQS}j+-*={Bn z&!x}+Is7APayJw%5{jxOoBp_~z?qMOsuETL!9K#Gq(UjaLtOnxHD5RX=GWU9@%qEU zUlil@fLU^h+fZ8}hTzph<{@s7$inOxY2J8&qQ0AUL9l6St6+83 zG9g!4Wm;@)quhPu5C#9QF_TBJ)o?JW>jd*+aUE5NHfc@&g}hhgayUh(_CmlO94veO zS{&(`GQ82ml%@4lem*Wcs@W9c$!40^-WgAQaxR}5@O)N7-Nv=L<2e2|N5KJKLDO3> zW{aXN?Qvtn`E!eM+(nN zXa0YdCD2N&NO|oxMCh6LcKFMXX4%@|&?yLmH5Yw$F%&qnrzvMrL!#OIHhTp=_B%Lk z-4vh$5K*s574d`(@I_!(1-on{AtVtTxs^I{Skn-xW3MpPz=myXn<_ipq@+8jvSEVrXH*ye(o`t(TfO(Uon>x%%LXNv2!ZJ6lwS zqS?nHTxE0GMG(um%PhEo?-A!*$VrH#h%7X`ahh_jlHm3LE%E(HrdhMHQI&1?41YFPsHCgP{>Gl` z$=E7>_!90EA$$|29sH?fpfLS68!L?V&zN zAyEZM*@&xGDkK`wh#6r;0TVxBCJw0?i7w3BRJ-aneugCqs+9_~(8_aGi#RW>Zo5n@ zdlJr50mMkx@nBv3c-HGonHn$*Je1zKi`?O?G$#tkH5pkHWY(O)=3{jtq)}rz38M2| zUJ7e}@(@mMYnU5N{s`m*dQQd#o>iae>DvVf)-9|egbnH(4R%*Yef!CzWp<)2sVB4{ zE4Uhp6P%3cGTn$J;gJXThfI>%=<{bh4H+>zqm=LzA*PCBdIvIjH09rP`tB07w1z?| z)z}tRTm39;ep*#@Bm;#>Z|G%+hztG^`6#i^*OCp6-6o;4g<3H)izRDgq=e0iMxMY24H9+(n@* z1`K2bp(jb}7FVVF{D582h{nV9(1Ipq8nS@paFr}=)^OLcx(wG6q$hRMoWXUVKs_Aw zb@ZgQz(bp3Jb)DIp|b^59Pc?P4y)sALfa&Agkk!nayIH5>Lpq!`u3Mk;v|-W|j@nkCBCVKE( zf^$D{m)~HGwaET&r~QkIQC8@$lZW096}Z%IxP*lkeFvWlRv8!a&X1^$-KX|5rh^cM zv|T}!65|4BEBn_Xs*n>%J=R^;TXrCJtu}yre|G#Ve#usC$r5yEa{L+JgHqOJ*7Ba z_G4LG;xiTT9AK$>kNV1l^l`WP;8aTnQ=dhH8-fIGxPQ~FZ%W8=?`-aC7Pjt+zC(3ERgnUS`G+LyHj){dJcI>v@YB{FH^$#Kwf{^U--I^8HB9TCOd>Cqy{; z{Q`_PP(1JBC>Sr!=cXD;39J%HMVH(m5+>l=*O%g=7C5_H{ zeT|EgnQ09~1wx*tE-Bc?)g*o!USTU_vEU7%erhxSkXCdcJDYYL02N`!g7U`k7uddX zIbX{e?tYQ^I+6#4(1t?*@wl!cgv9{!FM3_)%#WJkb;R7n|7N8vohS$ZM&?>F4!X4} zkg4Vfe;JME@(hcK zK}pr8Ngz#6RL0A6mJEDn3RH~&6)LWw+yOrBCaljAF9M^F%sbo z#P+o;sEh)kV`qkzhQvf}IYPSfpLux1%tkgPefeVE_QN#{dCZrDZ?&!l8lYSwYpqJ$jIYGP#PMlxH zvf`T++`!%$K|QJOL8mSa$6(}@9xgpl!AmhX$>$4O<~JUcGy-@37aii`&t)a>-#z4z z>lB~@Bw;RR$1aFkqOS+V#Lq!`z6`m3Q)&>vM#c=(>WfSsg&ljI9?Z*>caESUWBb8r zUhHDyyq~i00z;D>Vznez*?Q5k|7vD$&EeSx@OWxD>5jw0ffoAupkjS@_2=y3j@U@! z(=+#JDh3~Y{VgnmM(}STYAO&k1tPGsi_V%1JYoOaH{|FOHfEnnhssBP_PlgyOJ;cz zD*_KJA&>->q{w=WeE3@qB2z%-x%g7d%Evr7XwKLhAMod2%&O%c4Az81WkaXFB`bwK z*XYgH8KBnK0g)HXCzs53COoZn8w2sZ>KS>5cux64F2)+AxMq!`)5|E^`o;6mRz0Pe zQ11He%y>E+pNNnKOyw!iG$>t!;-58$a7OndmP%e5(>VSj&8Lh8bq_ zW|(8!qL`o^RQ_@(!jt{(DvISHQ_1BqnlvJub1WJakD3b<*7;-Fjb1925xbBaw*CRp zcw>{iBj1KyZ8kf|}-zrO9tB%Q;jo`!Hgzl+?YD zT}_h|YtiZ8AsI6sR0BDf9wjF*g;P({-+}6Rx-CPPQhEstOEbew=@!Q|=QAb}?c1ly z;5yRx>l;qPvevbOe-%i_@5HMC^#&4gvF)@gTmiL*x1mE!l#;!84Tk??yF2|F z+|x3@Z+5q4sHn#=eyn+(Y3MoPSCA4_bUTz)!+Cg)mZ$p&h;-iJ1ei1PI7Y zH8B;1S%G5+vDw}em%NE$N-)P*TT2aX5QOfJ{q&TKBe!<*Fn#lngB#-~?y)QYVp}vd z?!>wN0q1XkL1eX_6VApV7|}f8sU?t-YIZtXe0EN28)M<1*ow+>LGIhMQO`#TRi{aRZNh6v%z zB6!MV8j>$+8o}h<@_nTyjM?cYU46q3LZKX34oJ97y)LlajiedWf73DJf2d(C7omYn z84|g&s6<3}!_RR$Cz-h$p^uU0=s=6E1A-Q=!8(yS8Kc$#>BTN1GnC+edNO z(f~d2<~&r9L9YZW=SEmy0@_G1gmP*G$YbqB@~}P3MKP?+svAvy7dzChfo~kDP<#6+ z+K)eZ#j0x0LvgRiILEd_;@X6kxbu@ZGGeOKdZZYA1B_+*=gF477YH?j=+6@v=6KUtFW&>o3yE=Zu;o&K*?N{Oe{3E8FO51NOt~Aq zglHu1w(OyQgvY(fS)~&nVQ0dFO~vA_j74+J=xbN*>T~nlc<2edb-@^vs<$8{d>Hvi z^AIylrNku}5M$0T*o}IF5wXY(vd^_n3=hgl8GC|}FGB?XFgc`zvNZ!!PHW9}8m(5W zW%@_k%jM5)4AYQ%Ac2!`rQn=he#~Q3k&&X~^%v^QN-G}x`bKF0i~=i@L@Q5&%er4^ zh^4_D=5t;=J~-7U&1r;6co_lI$~8|iE%3Jf(5ksKencfK5t1+g=b)9=nN{GBKag@B z=LNgGUsWek6}TbgDpSJe$N#<*mhtwrxn++)Q;2aM%Wc=WUM(dh;8@h~@n=&nOEn@|P_} z;mXxThuLD$6@%N17YB{7Eqeu=J50HWu31i!XQF4;A4_7}{SKngsDTEoN zwu!J93r>zeUZSRA0v8S1lq7$20cCQOfndpHBFaNdGVu~5Ax^qR3aMEpuD@>fBVST{ z?r^r2qhi2zca6Y%ERrmgP$Lp%Cx!vh6Wd*yw2r!&!VjVw6<*>2^>v!`4)vaAFET)9 z$1MEtvsF!}!s!|b=9JLg!+c*0?70jZPjR_XFa&qI2^-Ssbn)r39Et3)br$HVfxMqb z{H~vuD^p&gBZPCR5t%1U^I3clo)fg_)NWuT<0*96)I1?%-2UIfAtt!~vEJdF{7@E` z1mJUoHo6RaB72@iqlD_Zv#Y0hmCOLv_*#?~`Cy$*1vZ`z@Hcdl+&$^*aZ6x#o|n1@ zk{VdJnSI~F4}!wmQ9si0GJ*Pkd#lHXE)P+zB%LRrhI|p14aNRVR8`_lVv59-jDj@p z5turXKy-J|Je|a}k*;wGS$N^!JdeXt1lj_S%w~bVO#AZp4z^dyK>tMy9(tDHj4AK* z4gPcobu@$1$8r#0F~gPPR)q}2cLu@BLy*DoRcS{M_5CwO76yOdC9oCR%j{7yApF*P zT5e;xB}SitW+6}Xvf}rAqCy#GrnDXyGHv)LQVXy_sfJk5fCqrny{&vual{6Kx6Fvk z9OX@XXKNfzfHs(f^~KU^V^}jL2sp1*J;~Y^Oz#_qYI<5zKw!Zo(XE`(LWR{x+@|Nh z+P&g-+}~w_k380Zbj5MD&7#&burEb|$t<2hS{Y&U66^N!Ls*LIq*QJHVZlg*jk+Ix zihK`YY-qeB8*8KS-eIV78JsX^TupX*f!Q~^2L!tqptfHm7E0%YXJ{hsC0 z>s5sKv8=xlcNze`^hH1Vvy%8|o92y1I~x_D<1I|8$3SxK*74zXRamQ_Y8T&UoZ*9; z73oRckzn@Y!|s#>{s);8479)-VondIpTp;<154}^q~OQpbUd!=q5&J=cxEY9_cO$x z&fYP5&o9{M4Kw7-fGX;(P}~B$F@2xg&&F4e$iUijGz~X7^2~2DdT^OKK0Y3)rmn2y ztgs8n#&2tb3OpnhI@djQyVO#ZT&PvBu6l@kjmE1Plak((w}$1-Vy~A!3t^ z6khFPXJ(WgVO`!|qpIjWE5}Gikj4IinDl;Cz?eZJrqmTR&hi5RgbTq+oLLw7uE!Vw zS+@jq)_$ckJ2=#QrfH#p#=5eEs8zL|rrZvc|s2cS-D;U?#o9a)3=~8L|8zw z?6zc-MDnG}1DV(90rTTx2-X;rp6@4;Meh*t=kYR5f)Ub`pcIJ~eip$-y2|Vuk}<0Q zP^|I*l_`8VrXf^w9U0^V7&s}ukJ^NBy~+7cK~$`MQ3bCx1aILEkq0KK2w&DmRwO8Z zd-5x7*A~#F1DFLZX3}<1va71=>gVTfD4vZL%uObVH~aSk3w5t00_4E_1{u>lJ{xqX z4H#zKN4Nd-GB;Gp+^JmJ9akg;(S< z-8MNUDjmbi$0u}!lZ>WUrL`Aze)0DdRjX}0NXtj>C?72<{Q@U|uPHuf-_U8W|{7M_; zYG!<2I_PG5=JW~QtigKhzeQJt(8^_*a34=`AYrm%2qkIPnsecfsr1k0?OaEVPbg3T^~9E$xtrw7Q2I>>Hko~16Etx zGK8cm3ud(h$1yLK`4l$xE~&qubPA=AJqO3?0@XTmQ7nivzHN~9V5MIjDn`wCL8}ZB z(%@7Lir>yN{U8gh$loFy1w)EKY@ShCg=7U?yL}`r%=>eW_^JMWpb}kB4K+w;wa)3@ z!MMW1qAIa@H{7qL8=X{_*Hq(Io!;mrhK$++^zPxEm!-E!I(seW6Z0&YXN~={)F3Kw z{>Ap^T%_vlkYBOq%XxE0*282?9#c2NMgj{_r3LMc00Dxc7zR-b`!z3H-3uHx-#BcOcsa}+dJ+n zb&jdT0z5`gqtbldg!<|o#WuYb_CnJ0(5H;|&tXVuHuBzT*M?}oD8f}$RfUkC3oGHk z=x{9i=hvDy1%RESm`A((SO;prr`8c+;0wTQTM+_r$J$lM-vH@7Hx+iOfV}K~8J91u zO9nUuy2l0tu>f%eIgUu+eUFx%eN(TUZMU)mBB!FlWt^Lj1~+B3z_Yw~&?W}hMuqkXoc|zr1nox-XnHHbz87bv{vpLo z3e$DaO^8pW@?T3N!{T%I#3&7Ykh0LDRG5)O;+qiSfGq9i%|Oy9f3b%~5vAW2hD{Ki zqXzmcShi1-H$rRKBTU4?1;w86`b5yL)U7I zxT!|Lrhk>6ybQZx6OsTo!!Grl6crJDv`E9;_YPNzEyT-DCY;Pe$lI>(j-*aP! zYSC>jv!b2<6a|KVB=1KHEuYy|22@m$zP!R;l&AtBPO+! z-R7TkJ)MFNtmzqZIJG2&vh!ww-t6IW0&A=qkIw>}Vqdmqut?B{4Zx9}%JFzL&^z}M z2D(50@<7suehZQtLz`Q`3JXUkJ{E#w6kcj*ec8;Gd^H&{GC>+_rZ^xugKG6`=I9`^ zEI|v1*zr^{aa;1En5G4*3L^A!b2a>7e2=_^bSjgo)VAW|q)N}xN-!98NvUPn;tu7p z(|6`xv=ohXJl@$NGzs}{Kw$L5Vv&~8jaJKAsV1~dekPI5ISDuoCY&waUU+OY$HfZcIx$n{$&wy8X<|i?@N)325mr1NVXEM6K#Fa1|EAymeqLJ$W9v@AJ-Ta=C)Fk*`PHwRL3C7+lxd>7WA(fbnEkS{8p~ zX(an1+G^@--gEHfEwLceH9KjDW+Yb$cG2cIU_=?}>w60Ie#Ch! zBA#2w&X7A5s3_6?uc_&&Rx-bRn0XCkxBYqwCPC+oSZ98g-2*|9^`Vm{j-o%n7sq>J zDoSuPkf+UVAtD*_B5xP#_5v%FtgQ~9Mw`3gIwBi7Ey!gCmdksm!z#{z6AxeM{@GxD zeqe8k@o_rQfmkPNpI;FYMgXns3qy#Fr8uBg<kk_nif>r%90Pk|048x zSQk^)PwMYsI;Bcu(j+U{8HBCA9_>2?3SpC4NXwU)x91;4N5uDLcVWfcoA6{_ka)Jv zpbnQe6Vs%=s+YT0;xt9e@}DK2jjqO%RP}p6c(3IiI(#QJOpbyPG-dhK3wwyYavuD3 z+R5|7M0O)*eWK0U1v@`URt&*(X_;-&w!6s1!qgRaD{0efeRto|Re`!|wOqV^_@TOX zq=hbxnX`-j{^pkZ*WwaUH>pds@?_7BnY-bEtSt}(&Ld_r$ocl*ZaO??MdgkP(=Ru< z*YtOP6%Np~+^YrAJ#-MLG-}8&PF3&T8WwJaMDaDZdbo`|y6z8Aym zb-(a7%~M-icV{_fMVnzwJa{it-M@LGvO@fJ3#wd|!LA7mOgGQD+kSxx3?T>{2JN@Mvz=DFK|b0R z`D<|f7=L%N2`&bH(kvOOs4AJbBNrQpfC9VSydLR#!O`#Ef#WEg@VlVm>&r>+Vslkv ze})?~7^A#k2#O!1f48W(ROkqCs;YAvcV?A4Z%51Mq&Q>@*-_EYFSg*)I>i6c#d)Y2 zbNZd2o?cisCSLd*na-ID?RVC;6Fuo!Qo?Diw^`+TY81PyF=F`ie_Sp*cio}%F4iZ% zE3%iZnvF*J9&*oQG`r6O4QnY$*pCx!}1`eN;Ip^`Xf1(n{0G*SF?&F3G zWpLFJ5Lnxthg^4O)zeEgCyTGBXY}!lt6QIUm^HF@XG~VRogWm^0IJ?Bw@_@=`xk|# zDjmi6BRa>8v8WsIt{k6iJ;nzpl*XtO7erw3nj`}uW7$zsT~E9B`dwK zL9=F4AV#bn%v| zJlZRkFa;v<1!zP93mPziQq{ctrZXr7%7F%}0xwEJ_G-tb*-M?xGu6Z|v|c3bFB@vN zAXA#Ob_(ipj1aYkKY6T~B!b@i-g@e}R6S%H40blXN6g)Rho|HAGXLJrNXL6hxG~dm zlm^PtSW~2r`O8Olq{PvW%R`Q;leEO{{>gd2}%X!(25WK+YfgI#B-PnYYSAG z?DPKmXo#j-#&A}2>q-4>Ey!X%+83HW(G9cH=lzWB$hZ-|IT{U*Q1^N zw;!ynn9KhPaxxcxFrDEM8Ag(}5a4ab{AErgV=PQVSnT6$9=&<+N`hw9Wm&)J(P)ks zyi(ZZR5RWkDargGt!N-%Y!_>g#yv}5Zr{= z;H+%r(#KF$jV9A26nMA2x<}iI%O<0iQ`2KuidauQ{s_oqxH!Q5V9V{8-)L$~6-^`m zr|hZbo|a0|#xcEQelN;+7?r@xO-VQ`2d<%$Qe3`*R>f&YMm@^gIn1%HeHtZ7fszfh zmlXpmkO52qyN9VOoGnz)U9vBo8w_%XIKM_KB1Az)g=eUb+r`Jp+q&~ns@b-}(mYu%qvCHW*r*B`*lDgdbC7ZW{ht#eBy!#p zNwF+=fIa5w2%GHIxV{GEGE9b}hCyi~161^jX^8$RZLqC)56A_XXbWO`P@KBfsZt`nlHSXaqFX0ps}2>%uk zJ9s>xSFEGdvHwSv#~qSYA)J?Gj_lS_2w!x=#M$2QGJ(O|H_jh+_ss{Yu^6YMmzkUv z_5J7MD(J$Li-!!|3-mxWDY6oXKkS6J%4SmQSZ;y&EQJpMys0)MEf5QM5(^ydGN6c% zwiZ}_LOGR|flaz_rnG6*8fRZ>*#z{?${XKJ{|RA6=fTm~V4pKxZO4)+lmi$B|Mt33 z`VAnvfP~4WH$gsFJ+nn@+=GP4vum{sN%wjv-oxt4!YCP~U~a55FWMA+smm7Q;%Fq# zOQpa{2@N$Nd@ya@7b)HU#*wG$y&0;3@j#yi?I}e)D1L(RnMfIv4M{9>D>Z}Gy}C^~ ze}R*V+XZ|)=@_E+82|k8aeV$sv_j9#uCYg8mjf|ilw`xH33^^;gDzmMChl&h{5{c= z!lg#WWwQ4Ks2*h$Exp2HWg@{vHd~eA+ceR0{hH>qPMzF6S|H|Ag3yLoB>+)u|) z4xjKeRVv}QV1 zcZ6XzB0kq!p+-Y#)KAWsWx$x|bDJW8m-QUT?a) zd2JyU`K1Ic60IV-v1abuWjkISx*z5@0QY}B5={fV4k%&L2w; zXG`!O1Le>9Lhwe&IYoFBlR9NvjVTy zZU>wK8G}0j)ODitGdQJM=T-xj28*jN6XUt81dDzxcu!EA>g;^+pxin zuZ5!XI}H;L>;43&KDC>tC^zDb~#dtV1*mbMT>U z@_giMR$+!FBEbneQ3g@9yuFQdWsmgIid>!=fqCe&+W9Vru{${%!f$gMI*~)#<}KCNvm+B-A2Lxt8L~xVX9rxKZsKaVHW) z!1@015O}MThzGu#JbIE+F)4C#REawzO>@@V-rVvnp}sHkM+U0Kp&*H2 zxjpReh!6s8^%(BBF>Y_{`Xa0Ei&PBzlNeVf6OGZDhFMFnnP>`(-s)O*rfZIj#m8=t zKYZLN+nl%$3BH!4XgAs8T-a36GO9BU8Tm)R)Y+e+yY@m0*ltaA%9=f)?ZIH8F;`ii z>%$X29_pgRI7Wm-p8?V$0N9bRdsIuoN6__+?mZdY5Zb@9jVnz#B!@A&*QQ*8VB6UW zz>pI#pH0p0D-@u^;ercOU2N+ftkG1AixFF@>=y5>UNfZJ$dCVx#8 zXB(oudX{(F^TW_?1C+BcNR5m)%+DEd6=A2EGdD`8cQO4JbH`2$IySs>s6noum4kQe z1!me79^eBr+=FMQG|QJjQ?<5VA$ki>sS`fbOw^aU?7f`mScdBHVxI-XXH5Ws8!eg$ z=KTC{T@&K8COA54td6I`mZy_79~;LiAr0o&Vw=ItHu zqAb^WE4RgS_OgU`RM5h79RPWHk`oXSOd?iPL!W?1huY&Bm?n`^OpjpG(}SS|(UDm1 z5}QbMnnqBcf47hp`ri)@mQ{@y-{lL}vlza??uvHX4LZr%zGHco&o{%hucUGUPuiwojc2OCg01Lc2$XycUa4zqp_ z2Os7oQsT)k+jTY{WtHPIfwYUFo{z7b^-YxFF9ZyO+j}6o5ksG?^k9_gyeALfd>43; zc_nEYR)oPv(M|d~v6z>AJ;`6WdHJ+-f4Sr!#RrN4C(zBA(jAb-w6?s&@MO0#L&yqu zY_`}>!b<*x^PbTTI zUq)JPPxAmfK*YaUP)zsHZnU|~A7?ey>S=k6Uy@F?L)%CY}Qbp;?K*>_mxn;H+@ znM8GJ=ScgihO<`e=U`vos9>?2PD;VXxMeIpja9V2%K zLnNzqWAn2!-s~5@9=E*T%VQ1W?gE2>8ARUFo4+jP z!5N@%a`By&*^kWQrn|s+Rf9EiD*FgE7JOE$osVEw5xxd=WXHesD04AJt~QVlP%cM4 z5^So;iRy=YXRmeP8qM$)%{9jD?{iOaKauv+17*;sm$eUIG#PIWr5AJ#v<$+23G$FE zA;-zaq-p!E9)jNpq|w=r2kw$uGn&7%9$3cbhF2_AXD&C@{9j&4C8*$;cS zg?U1<`i&=OYDhqLX9ivb`hi=Ut1fAY;pDg~E>Z}BN8UidaF{5rWXVDeSfkc>s` zUls6K|A$Z&(DC*! z*rbvpdga#PK z8)pS8&?29h1F_hp)IFSX&I(bcOJQTU-3z|lx7v6IyxA?K=ZvkZIuydpHgrJrBOg9| zpM17X6rn+HED;>dFa3Qf)V8TXUyi;p9_${DH&ka#OTf3Gtc%5`J^>Mj`r+T^Y6{ZS z<}JxB_-+^3_*a9*nv!}56z;lu;`PoLM_o{oMlMYKmHN$du%8y4|C%C=Tu|0Uzf`Im znHKhd58l#bSyIcLmpmK$ih%9N$7xZ7j}`%b0H86*D&XK$nJ(lw=XMq=YR|RaQfwSg zoM1*Wfb7`oEB-a=U67@dC^S0q5lLG6g{iK@s-&ClU6E_&Y2B?h_ZzD}>^B>1gncyd zJR1s_l{KNXw9=uiD|`$j-5w`o_b)_6tLzN? zlmie>?ePt#G|Fe7yw9j1rI1PU0X{BdKU--4SG{KjN=@1EEGJmvflyK4$~IV8DUdyK zgS$8=3gRz}fAFEA@>Rz?p3kx2gTsf-T+84*vYWuc5dvY#jNC7X%+cs4r!c1eABc*q z6}h`@uY7=>9xO;6NZ|ircrc~lyoQPsr9U4EP-h>BM5T`hKFeojZ z(c}>Mv;?RJBG-yOb+N$5f2-OA1@WU{$@6XLeEr&=v>8Lc;E@k1;#%K{;C^BVPE0kN znc80yfup?6jI3Q%@~0~#d9NX*Ts9A>Zb4Wu>eD|Mr7no*^tbdy1AWA_;Pj$iRbn;h zDCQ-2@~(#7PGKBL*+w7N_e!VmG!&xvXwxx^9b3AyC0`ZZhxD7mp?YL@s+vn zu>b59?!3Cicp^)Le1{Qp@a2*Ej*mAovn=K3+{6lB*&`IP)Z9?J2H zlXNLhdt9~H0!_qISl#XQ%XQ)RxqU>GV+s7(OE1c%M158@*~X7@=j}^jL{=spJ$m*V zKfDzp;0vV$svU1Bm(jtag#vB%(+NmOfw_d$&)@s^*vIz{1%`RwE+vU{+K16_b63jJ zg8s+|$tpOqOB@4U>>xNy-?Hwqd@BNq{O|qm;u=>*yfS&KdyNpncGn#4+zI;Z_;RVN z3mr(i7%i;2L0deg=SDaMCxcCs8utl(4;nS$2H1V{jNO zd-0$ucb8@)%xt!9Jm1Y8!mR@OT#`#In-FH1VS$$xT2n-Bsla=7OZHI>+iulhMoWD` ze;FhH@?)5JWcpg`o{;WN8}i50U>&`l`Z!|;YQB6tir;ZRoFcS}P5qq44w#8=J9gUQ znDil#c@MUz3+_12)>Pk)%qv^q!K{EgX)bjR%WSF}Sp|?WeO>!#w5vCK!FDX0=c~#} zcBM2isU7?zImQ5>7a@V!V41Qf`>RU!{3>HYD(VTJHWuHYw4mqTc^$P9Z|k1Sbe&%c zX-pu(s7z@yFD{KuY^}`a7a5S?(aEG|y-f7c1Uu|}{Or(Sb2SPQOULj3PD#I~dY@mx zMgk}`QVSw6l{BL4g5cSQmVSLAyN5taKY@d0E}xrN!!S^u^A+?uGpv?5(;Sl5$hV9o zsZrjie|{06^*vG7zNZ9l^M~3@z6R}S1Hq}X%Cj%HtQK2 zo?Iq0PA!!R7ImV4dsuics;_s6L4q-~oKD~1m&f8tSXxH9UnhPh2iyx&Aq^uJFH)L= zPr}~b^WERu-7GXx=GAAsRH;>L8lhX|pF)*H?V`TZ0ZUx)Vdv6bY;1{b-TQxAYA&!v zrG0$-(7e5J2vrs-M<@Tt)>=U_)@&k}BmYVsGEh~xh5&ioB4>{FU23Rsnb-zyq6XWZ z- zez+jHh&}EPd$+L!go}VJVqvkgqrf9GgFj!1yPX8l!CdUvVCil9+LScANl*a53wi+$ ze2}i&2e_QOh7~1acQreus#LyiovucaJ=rd_N*M2jp^U!_W#$_NK{9n8vb2p@ZATHK zIFq^>7@OG`JkcYGhNUlM4fiY3wpw}y`JqY*M%>V;j!N<1DF5Nk(&}*=gBs(&j^8O(j z&|Z~q+9%*0-gJIUPiVu#*OOIMM%s0t(3P)lOOjIShFBiRU?g6nAnD2tas1<-82QnN zymd#nIlN9b7FDBkMOe%|JM-T)L@Lxz+D4F}GCQcL*-pSRn@`8)&l{pvyPI){e94r2 zm~p3D{oy8eB(P**=7IAk9Jh<4m*z;oShR0~?Ahf#_XiT}(>(F9doNcfkU&j_4RQqU4dcd8LNSIM+u%*0iSHlsCr zVkB!Oqt@Cr&jyNpggjPd1kHN)sYSULg2Bufju?4{;7Kf-Rb#YV5lmCn)mQEq#lak! z3PF^vgtz5h*Hma9k+8k#JcNOOjZvVfpP>+alf2PVPDI3r+>0a8cjopk8yQlk~q|ijWw@LfUlTd%^nx_-+TNb)#OH(R+0L6g8J>QZ3GojgjU~H-0Uz!J0 z3Y4w|y-d3P!?vqc{uz3Zrm1JZvf%I8=o%+(L*B~7+$;NqkWyQr=)OTSJ*Do}cph5l z2M?IZ$zE03pQlU2+YOve{k{n#hPRtN=J6F)fEYuN$GENbJr%YI8xJiAypXl<3 z3y)&Xj%bzD^O`d-msu4tu_4xy*BFua|8Y8sMwpCW{ovL4P0nL~Ob7xX0z@iEcSZ*8 zgDg=z{w|UCXkWX2nOo?1#c#oN+q%kV`x)?nL%^(K$v*gTk42XM+S_Q;+^r0A$N+ zr!08gn{CdAuedstI%HX-HkXftcr02rAVU?-jy(#vfm6hX1v6TVlwT+ZDEEy#N}8c$ z0n03&K?CBvPuOF~=6>EVf>v;C^tmV--Z4Qt{l+*}){*JL-4Z6ZA+Ft33`83@s)Lpd zttxF@IT#s}m)l$Z8+71gS3UKOBbj*m;&kgXsRbavcNPZA zi*eUus4t6Xi^cdUdHI?Bt}du>4bZ*G#8WOY_Krty;YH~{D}Uc+%mr36&lyb?gZ(+Pbj-3iE(&LncJ!Zfw-zM_pf*kR0f#Ma8MN}@n{GK=2SXL$ z*buTp1F~(oN2p-i%<01&eTGaJ7;NmtNUmMM{Q4&benDHnUx*2r{BT4U2CnfX?JE#ubP2iTJz44<9$0WhZ? zw;j2h%kda7&5-?(6d$tPZGoaIVgPnOTZaEF1wG9@#0ZyjL|ku@W;tT*Yhmh6-M>_m zXlIe+;t5Hwlm3J;;%jmHgrA`uP0ieH9OK`AB+3wc2Cq;;yEM-#Jr;Swe194kQO}@s zA~lHgsFSQEKb)6i(m01Nm%7ra2YDt}=N^YjlTB=qZb#(7@ zp;&vxBWRnl=Z{F#zcn+HSqRX2>f;=9f*6G#en}*L--FCC%9;xlKnz9ed3mrr_jCb* z94wjc7?06j%~QrgWlY~L{%?`uivq6^%;*HiaM@6Vi(M7#YgOUETp0Vd62J0Dl&dL7 zB%_6~Or$rfuK!KvkI2hbrv!{AD9eA1#?fE6nM4QsA zc0o_-@_zq~&Z`!(=;vmelavb%v`oqfY@yU%Xg57MB9u%*wCm6NA+(j9F8j+SQP*Tl z!qY(Pqs3P{k1<3%KMI)5QhW=egc#fm)z{7}FTB+Z2K#H)lhd^%DM9B(Um{!S*0L~f z?u0^{Y7@|z?vjG}J19-A(eJY?hDapFOg#m8B5#UxACKnuMKtXFY5hj&rC|o%f{U^> z&U3kBiX->j+CiyFFvGLjZfdC?ZXJpI5M1hVkwKaHr9G5##9S@R!as4AS`D5dH6b%X z{Eto+`bp!-G&l`)ZS)mDJs?I^lUv73;nwHFZ|PVBnQ%4b zsz4ce3vWI(@@c#cQ!(P^*Z-UAQ{I!uEEY^VE{b0|T0IFpyt?pOJ2PnKhIX<_$H8Qy zcM9^J(*oj1$Jb6R@_kCQ<^T%M6Tnj=CR<1OO{m^BbohY=t@jdJBe@S8nw>V%ZYPs6 z-{|C3Xo4f!Ku4ndxI0CjL^8K+v#83g`Gw0yv%g>b1{V(2pAXqn!hvb%q=MAK1-l3C z&~F@&BLg(}&<&j;%{6!(?$s7N)6kdWLZuwqi;2sQaZdK*4A_ot@tl8L9`U)5`f;&Y zwj7`5u)sGD(*wzk#5%r*z*2Ep-lx{mI+|feXoSk6XgOxac}Xytjk8%#eiYdA*Y4jt z_gm(cvsu7iOEdszhL9YXpgm1N+7)AwF=PCpOnFUuZdpGzzM(5Xnn&67%$t(J_z#&; ztLr7GXW$5p3T-fFt{bkiQ7T9HXBkxJ(7+8yh zyDlM~)2~;z_T=Q2;5v7NSBXF!`cr4RI}0Psn`@~y)UQ0v#{0u+#~dFI{B&yvQx8u0&XX@w!yQ@)yB18O>*0Bmr@%R{do z3+C?RueW9SF*fMn!d)`?{t`wev(QTl+4Zjm%EGl*MZSWhr+1x=EJ=@R^SI+?yD1T2 zZj!Rvybe8$u(KYZ)eK#EB&g)4# zVGT7{MREA-10V91G|>3o-V#|eqB-|Q9|jnMM-&{iwIK0zth&- zKy!kWyk#dVq($^-TYmnHX-^30u8YY&FR3dAL3GG*Xch>jNQa}F0?MSe+ZqZUWy`^< zw>TTbAar6rTA=qkn{d_+kwpKQJ7=}#$DJWWveKmG5q%tFu;oq2YCrv5phI^h?Vs~Q zO8c-aqNHn)d-s<4+m~AP@Y$$YY6bE$^^eZPNHnV1^?IeMN*eH#x#A7tTWM zyM+@XSqKY+2Y=D9*oU2z*vlZ9X{FkYV`d?l@3Tz(s2A2lvk|w0=K-=lb-FrgaumH5 zD}10LxiguOCbL@H!f^Au+`R)N*XrO$TiwMe_Lf}@=OEx{73f_(ocs}h$?v}?=1;JY zH`+~iSe?+)Tp}WEP;w13bIUQpz3!SwL~pA?Bra`D6a_l5+@_^ZDZm#%$Jf_j#-vvd&_JJf7ZY{YN?g79h(j{-24 zQ$EgCXo#4XGu1zQCd2%CWF@JhTYVkMiBQO<*IJUf(tJES(anD>J+nyC0)3LL8!z?X zFD++7>3(~*8new+UPu$>VK39~+vdVyUf*x1v~Amt)Cu89r7|hCtti zAjOi)u3IK$@on{75gM;nf2ZsTg(uEwOvY1-4ZcrnYQ2AhNw_=luu@KM{Kr{!c}puN zhVSNO_I&iLY-aKzfq{QRv6D4-L-!H-X%GQg-=5I}Xz85?afZ7!UlWg11N<1 zNz_=Nok1S{%v-ecs6F4a_9(1GdJM-zSd|A3V{}{;ma90`j%K00K<9*GmdS0dewTiY zzEA4S>2G+ZR!9RD$ELeGjM(OJy^9B`f+%c}~S3F^EB zUNcl~-S%gL33)#ReQ*TwlCKpP{H-V-9%s8))@JtdZhiWM5y=ZM0tZE5EUfeny@>UA zfkXn&BTE=;PZ@G`XydK}TR0#RvgT#$co0rda64w7!YK~H`|nW8AaA(kpzvo0qUr_f ztJ_S@9{XUOVkTD>1fk~|I9ZL|mV)(1sBrd2r!l}~cL0hZfU{pZR6qQ(M0VOM7&@+h zpUe_jp3E@VO-A+f(IG$Oiim#r0(n{4q2BV(eP4>BY>h6r}$_j z0fKQy8##x`VrzuR$QU4KOSRAy-}+OxhawyLcBe<#g4thX zDCXnaPw{>nJ)ClanhDOpH{HtiHmT(-TSE2QmTt$9PspX;)~lcc4K-%`qL>-##b_kl zOn)7{Y~m`bue_U(AH?H=!3*coKV}+D##^BecF42hguZP@!D7a?yR$4`n7-i4Kk5*Gs0QD@21F&Si0GNVVLFsTFI`d zO*Tx{H5RUYL1wmdoUNDJM)J@PnJ*(&I8oKirTF_H7hxHs>NQZ7?PX1DD@c@oA@a)b z=H=kly8xEc$J)+xNgaD<(9~J(6NhbmGem`^tPC+2w{LKlt$I;hizq0m|7Yo`R5J52oQh3{>Iy{6`zlShQRI7W z)es)(u+Hg03VGMBB7~TChPEPkh@HsfL`r2=kg7;8l@y7qWN=M#QfzV55A;$G>|X^h zs3$^)=lNnh(nY})+i`J~ZG6ou8x{_xBAtToK}R0F+=)%~Qxz628r}W-$-f+mQ)*86 zdW%LT3k!oKaFuu5dUon%xuThI!JP$!RLfGm(0!<9G80wbcYo>3Vz5-%I;(Oq?<#mRHATNa|eSt&fC3uu)0^O zmCt6OiehtSF28>#LWd?Gwkks=ctQ$ipyLj4#-5BO7Q3g`h#!@L3}VX>9B+)Z8# zw|%gM=fLUUR;m91o0#Q8HxW+s#<`)pnZ%YsZ#jIePeH=fcNTxJ`C#HV#sLS2W-@AI z#P**^gDdss4yV2NvP%t587n`vSSi$wgRfdc>L5XVWBt_&Y1G}b&74yl#i(P@X1$TR zGc}!#DUhN!N@g*s%5oFOy=NY9)nQtjUt@zZdO{kWDo&t(yxRq&zjZ*Vq-Y@|uDH&P z(Ro{KY$z;4&|G1kln<@CTh}|`mO5Fe`scQC*l|qTv-ohna}>X4M$PI!1D!OU6V1+r z#tVg$mcB5(X(MK%Hxjy4c>BZPvOSBnghrnyZ8y@aMY;$9E3|PGf*Yu=M3u#h4)J&& z9Ws~6KCbjJ^9W9EXFXwB#R z_sYIkcKumS!WLz{o?d*0-|=f$6i8k(;48pWHP>ws!>Ve|4yF$q2Z%r%Dck9H|A4hyi3JZ+}O2?$`G_Qr^BQ|?7*l(kAY!~2AlxFQ3_hnQ~R|a z(T-f9;YAClXAttizWaV{`MaBVcr45%YvV@V$c58j{ossbndsUap4@D1)oa6bT_`Xu z*%{X!4{j1U;}rmHRhB|JyMa0A?%EM}o%)K*RW3T-oKv1t-b zm0(;J*r*vFQJxA;1{qzBTf@HmPc(0kEuqDS0t(v?{QmD-1{lQ-_6^Dbion*rCBC2} zD}NBzkT@)sU}7-VWqBqk1yQHCZ-!4mGsGiHs?vKK_(TX#^ebmTPc1xToGT2V>8#qs z`yPc>84A!VSxJN9{Q+O&K66tjICJ~e#DZp>t*%?ifUst{*-L`JjyuXV93@MAl7gq} z;>O`xPRP38d`52@-h(8H!M1Kr1gvVcMM`Jtvr!b<1Ukf(n5XH2i9E{OQErWEC--p^ zTZWb#L6LvSN%Qs#pxiCv?@<8MG-pMOA3_TUlo&1nxaS~xu9l?mRGe{3h2u>*!Tk?k zawURZK*(6~pscYvW=jA6;t1=`IZhI*!u~&+YPx<8cBFCzNoa7}Orj}@Kx)tX0lwBk zg#_bu{!#tbPMem3J^?fBz5+*t4biO#F2%0xfYcQzkdN27mIArLa9vE}ZHzbKo%^6= z9_`Ggt#w?Qj&xYFu8m4NzWu;5@eGUhG^BjQ%-3%Ug6UQ#o1AS9}q$jdOM6IcVvt11VVDFDSe^78zHfJ^`ovn z272oQTK0N;S+_4O$&sezy>|LadbotBBKKJEaku2;^YPr;VIMnk$@t431*cDaq~MTC%+&>baehmIwrLMG)_khZhVLx`ga$()CEB zygV8Pqx7BW^{?;5rXAJ*`N6#l%P7ZN?@=*f4?#B$k_5k)7 zd^fLo67K^aCpo3+t(mJB9*A7q!k&U@t@7_3GEbW#9J}FQWei+zWk)ZYau^HC`?v1it?M%zAVE8(C| zb|x3)n{_R}g0%~~I+;HIX!xkH<&ro+d&r^D;}8G7C1mVVyWVSR|6B24L7|D342y~O zj^&C??h2$%;ROi2V|lkJHXcV@?Sog6di#7e9I5*P4Q65l@z@}lbqY9ngJ(2#J5*DWUv#V5gTYA4^5~xBTPbHZ>gYG} zYGecLf~`=O@6KiKmO08(0+3M{P6IqB;2cbPa0GaN(rIl?*RI=sVP9T(?v~Hk7y00$ zXl8Y=<#mskp<5`X`JT9yjQ?CRj5Z1Zi^C=|)2^F@rnD5ufB=VlxZSn3)_@!nv0CMO z=g}ssLJw&fOAv8WhRg?Vq*QsXz-+fNqd&A%2B^x}4Oa&aA+8THmahW0%ud4kY4JGi z8RzA38@eMx(+kY<8t4)PucWE>($<}Rs_yPzQLy+={ibK%GB&v_M8)wW$`D6IaSPZs z{Ag_f3kQnLDev$<_STuqJjmf(pCTes)=l|lhiJNVlo1U4h$FZ;b>KLv1<%CPUS2+5 znz35YR-spMce56ABKY47LT^x%;YU&NW^31_AIHaab?4gE&i3k74EqRrHTV=R^WZXD za3AD9PJO!S`n6K4Q6mOlCiS4kJgLsDg?~M1@}!^)0C{tQwFE%1T$O%PDfz!#tIRNu?uI5A zDH|pNVsqQ)x%?rxKZr6@mms~{V$+vo6Wjb|V<3fn{=td7$mg_5#`?n>Op_je?Dmah zfI(mFpTt9_e<}Yh?9I~A1uvCEkSsK3j+MdYZ8J8vb&0A$mxX&+36*veOBakRBl1yL zOuCz?9hh5xW0IQ*m;?Fm&Wv)& zs2{eUCn65T=9tx{=b2VQ?XKLRnup~+^d69Zi)-vil{sUy%Dyr8r92ibmW3Yt=Ds4Q z{EzA9G3=#hik9l`E%aS_HwNQQ-Yf$c7Rk|nq|U2=h-i7SKHH*=Vhy;qB0mgG8Y`g5 zVQqjedkgC;3EwTG5DT!E83}YU74zWQXjVN2g#<+7xzFH-4$Eg1B2ik9Uch-KH()*h znkL7$Bqi-7Za21=0nyy9THCo=FG)4g#x~8s(B)3M)8;uX9Hw((c4)_t;wq6_@H0h0D%O1=rU$d3H@BsqP^uP zu_hjKc0LT79n@(E*sYYdJwYIW(1!AGt2#$1C+jORnO*eFabX&ih1k!tm_v$pu7-qR z@qb)@xZEj#le#945Cu#KmNalMJR4RD?uqtff^Ir!s?md5Lp-ET+mum|oXCXL-6*51 z#I?^Bb^(X(^{XlCd$Nt{_U!c;jTxf5@$@y=NDS555hEOjcF>yDx9t5u9|BVj?F9>U zN%k=kn~;UZCBG(0jp;cdi(+4grl(kF>2k_c;&JiucJW<}b6^GH1se-500aZ^ZnYQH zJXjr0^78qBx!(q(j`$}X%zP*&2|+b6c+X?(xpmqu#5;EJV}JCtQP2UO>m6)Wz;91p z2CI6J9VxU)BO8xg@hUysR#icpbQGRQ`@(v52bOBSe_+qST=6ia7771IFX&Shlp z3ZZwpBImt1=gib!8tUZLD z+bFtI`n&mh&PdqQI=ht)KRVHk`LP&Q6rA;q3cF~ViHGVKRuMiehb6OD9b#Gsav_U1 zA!fX^Fz9@tVq|{&F&#M82?b~~DZO3EueWkL^$EKNQ#A1@(l$Y;P$_Zu0weBn$!H_M zdEIGUa$Jk);AXUR6PE7n@D;RHZtFV8_*@Vq2$Qv-W!p;}4kb~(*co;#O~g`3pAuv^ zTJdqz+a$qkjl>Q*?-1Gca~<2xaOue_D~#GiSJJ%Dlk`}v@9*Oq%lw&)CaZa+yR3V3 zIIV#|n23ru3ss6($A=-k+%6rRQ#!*%jF$$}>UiZ!a$=0T#vrtLdxJ+)H2p`qCDBl{ z+@pVG-~vp)cE*Rv9((cRH9f@>5hanB#D@KohJy^DjNwIxvW@wKs=>+{I1Gf6Ax}Mw zy+GL4DU>2t8b9ul_iI_GT_q#w>Gw8Qb$S-G2g8_8ut|?{Y4BpNML?2WAJ#s+UqhB7 z5F5vZY(*+=_E-e+?jr%M@G|(2HN{n1D}HT%%&`IILYGuI3FpebGiKKR%K7h6pWwqJ zg#C@OZK^}MnTUuisbLm6tcb3dNBq%U4(2shoQ8{A?lY(fzqjG=%YauZf52*OejzoW zVGQMO;NuhjuJ|jp)SN>vO5c%`_)(#4zrD~G{r~<#eU8xkRYZ5W#k|v?phCnsO-q8i z!e9&c?7?=C5`tCS^71SX(*MPSh$g8*var$q*3-EvN%)J~Rv4dBAc}%(y}`j0yd7G+ zgbQXBnmnwP$=w1V+pCcfm=vz2iO!K9J!c4iK;U?i=bmAtv$V3Dr93=c0`6R7-KZuZ zkp^)&ym^)pQUaj1zytk{!*B4^g9jCsTJ;(PbCrp_@xzuz1{|}8t6LCe`ve=p?_emu zck7;w4@?yU=fW%SN%gT-La)-nj#YO>#`hR&ae=CQWxNiNd{sl6%#+YthwfFm8yhRp4To5UYCch@7oHTz$lB09 zl0ryvusLg3dUO|}&6trb_z!s6EnH6V0`VXxlZv*l`}^(kP$@$*dAb6D112?E)xuSH z)xJ}PzghT<(2*ddgr%z(=K^)famERi=fXXtYU=g0;qeM!^CN2f+im}>f&wi^7e9N%e;6i&haO zvn;1{oIB_(3k`$FNP4s9A2ubmZlW2uQSBWI?hF1ZR%Nw-Za`phF$cJ`)Q6Qd#XhnL zdM|q)t#*|aglDC@8@wdz>a4@M2<7uy>h%wgR=>F^?K(Z`IwOb8;tzE2vybK6_>y(PZuqd$YK94%GyisLphQQ}H?~ zlj!VWl$1Hea~4QDJ}kq+AFb|8mG7^?X9sh}QmtqdBKnJaZ+^i{wSJt5^skR%OQ&c? z=cw?a`I&Se4-#wjN9%2nI+%RFQ_Jum5~+lKqG+AK#$NK;;OZI<%KFml610I+0nB;I z$>M#{fT|)QO9?;vW zj!)<7OC(m^H&8gQF)moFa!e?bIGOJ;uSC01Ge)ChXRY6bj)*yugmDFv zhx3Q~8SWoe4cayA6t@lWB=?8ze>yyZ;uaT!Qfd3$XYWC$wa-ETdK{U`&HiD+!0Rdz}TTEjo;VHGBtz*e0{=33rYQ zJ%}^D&P$kqKig;ia?`>~*+KTm1LJZyIQnkr+lq4VHO~#U?`LU}1#_g^Rh=+;*LNX; zT^lVvZ`66~C3dkYPC0@)>oBW#X-AyAR*`!lD{5-Yj1VEVqb8x{JjOYS5?n>!=h7)= z1>^eS55$G)yuzhSup3zw_d(&Ogt|@fn**Pc10=H{NecD|S#Y1Ua)f~n!GDmX33Flf z=@I)XAFPXgq<*du3pb`Kw&+!3RG*5U=!R$Nx0-Q{ucBY{73s*!2 z96L@+*`<9f-ua2`WXT5Q*-BXE5NN}?Dcl5Bya^mbU21aya8j{OK-xyWhzNP|#6wrG zPNhOfWJ*06%+QQJZy}4qCf?o(byRZYr8eon{YJ~^fE?Q0ZZS~2a8QfPDH>a$7Hw&) zg?Y8-V|0M6+<}JL%OifcQg#|IdG=3?On3rnkR6T*C)p_u)tR1%twvb(CkGnU${XJP z;rh5&i>$xqU@=uiO29~BH*qHU{kQd?Ugzp6`YUpXi)|4i^7hyquiL_E0dPoJ7br;N z<`PPZ5B@1QBP;+rExxAz&J+Occ~6iSjl;7y1Qc;KgpMj97W=s~VawO>@oxwZG@ZC2 zV&aSaCJ8k@3z6jKNN zx-=-7T>TDCBmYa<5#Cu1!HvmW!tWetIo1PpdCq*3xcVN;3dUQ-{X^sXB`Zn~6W|Y- z97+J<+IXr?R6u|PTKl<%T#Y2Ktnd8=3-9iR3Y1TR$x$LXxT41%uITOJV3<+pRN6O(OBM^ z`lFIk3525+Kb49B;IyMQsj>0x4)AXII-RxKm70v&4#z#e#@$*5>M>_M&fX#Y@p{3odyiAj{u1aphZdtUANW`{uE{#-h=r$RPT@qAgjj0^z!qlea?% z&G`+%$gNA#=-oUX>I@9eZ&k3N{EDAtX5Nt%8Jb;$H5h4;9Z}Ro6bI{C2ZLxXI?J^(WcGVpRb z9c)}fKgP%^+;lx@1<7lmL)(JWa#;I7@gl?_ zybUQnlRG*-{?KNu$sr{C*_4t}T$N4znm$FwM{!v7Fl^ITqlAsvfMnX3xVycN1w{T; z-E?9O9AB{c4*Up=iHosh508EzjhMuInt{$qQ+y;0#{saNQxR=p)Y7SmOA=>ClUIM) zd~NCG)-Cy?*p-=v03yZhOlEE$+I2Vp&k8)eJ=IBXlHVf{KaxBR$SYfB5W{ zdGfk9#yUboB4fsCR8a3W3SSa)lqC0O-ibB$kNHjexmSdhZwm+VYPZMxHQQ=(^*P6V znoeHFVVq(hRKibsz(L>;?q^iNtTJ?UDhv8IqIj?=j%cYr?&xgqy&DkyLGqXKAXLjbJf zlr2Qxy?YwI5?mXRH~npw#pnJ2CPC-RK084wTcQ!HH@Sx@8>u753-NzguQ^NBWtU(a0u?!K++B z#8(vcfJuT9mj*hxW(04eO}FBQ+LLc-?jjPZF)#5H;*|2cG$45`MIdv4@0ilg{}fQ% z?4*SP!)i*10b4MoemEdp-L9wR3`u-Pm7hcsPf=} zU~b%f|7WQWbEDg4gftNdIigNS zJ;Q;FP-YQ3U5uZKq-_pjwJI4;B_5W1t_~guBq%J>I*5(hku3lwts*P|KNF=a`4o7q z+n_uZH<>xNb^CdA%RWo_lnjJ*RkBJ({^NVYcIAN#2Q@~jQT$w5cHjtxDOHa*CePP0 zI5PuBRGt$9ZDT>-QHpH%naW{jNs zF4)5Oul>kfKC=525@Nx2mab}461RI44kNrFz)PCjxDBd%U{^=iZ++2tO?YU)dzBU^ zVe27jSr?NZ4V+NV2VD4e;UUBC0M2?99!R2<0Y9e$YdBEhQMqvr!DfG+t-YPk!cQ2@ z%nah?0Kk&LDe`$%BkPT9PJrcc?Q#GkK-|B25bN)*#8l$#w*}?yiI15ZD^i1cB5MyC z^`hVXbt4wS$`f~a38FO9sV6l7^c78LbuQ$c+_O?VY5;{1zxh4}RgQO(tt?20|Hgzj zx=LSA`%S6@Hk)`uG{Dp&RT*wLM6|1aaRVuJ{-2a@2TtaWLw^VK`Cx}|>9_2t80sdY zz+W4ccK&YsADsZ6qG0+=Y!W@QkK||Rddq5^2qprCm7U=4(snS^C=|4)wno3h)Q>gZ z^7W~6SVMhoeGY0o|JZJByM=X>ljEI=X#I-t5qy=k0G{=N#3 zV?!cphe@MA$P^mVLy85ia<2XlgV?qhF4?c-jZ4`Sj`|1_=(#08!Ee3bo+Ex}ES@VO z#{P#ZfPLRhtErDdQI4I*Epff}Ynn~@KgbUBSoVpnIS^R9K+|Bsd=bY24n>=*#h||k z!malYnY#ouyS`dD{UV*#ibIXxIf4LA+~&3S8I6vXqfh6(=;+{Ta&c|UpylCRkl7Vq zymYXr8y*(chG>7E;ZUbiQf98;Z~?7hs5z*nn6DqS5gP-N^N5Q(MJFJ-wJ&-gb3uJ0 zsc;RAfLVNscRh+2;QvJo?#VFJXEezh2)*&-npA>Mva5oa_KCDyOT*gH;+~*T7G{z; zi(Bc*_*Wk;Q@@H!nXqwTiS&>i6)E$$NTegXmK2>;VqrinAcbTD~bV%4e2 z+gqVYkS0Sg8N=6lBJgB=Wmu@w*|(k$4RL=rH6<&U1CdT!$VWD@Rp2kpJl1Na?=}I@ zo!5AQNMJx*-jwVld*4+baWPfxIx-|f*4EW06gR@tMN#)1mmG_<8+=n^$ihtljIs`1 z?`b~J-;%}2;@4e?NyYv*(;IDdjGeV-v^9CP$cR@ufOBsqI`4O3#B}=q)viN!8g=tu zk88@&{?qb`mq&WoEEe8AoecL#rkXv&CAHp!nLO9LLvawQC7w z5!)M459E{Q_H_!_6-(l^<}h-y$vPx&ih**~PG-j2G_Tw+^mN`?CcmrI$qa zJyV*@A^18#S%G++8U&UpZUct5H*D!)mC_x6Hd${idL>Q8=_yAB7CIZu->f z%H4cq=m-J=$3Dc6)^!|QZ=)h9X^FnFTHE}yHI)p;!oe=K;IMtjQmPmP-J>6-LB2JpEN1H&7gfFncF&ofK{ghr*hi0aJ(62Q z&NqJHx66*uR!FeJRcy0Vz1(IwO*8mBD6q@kz$Hx~DKc~ppnXnJ01ry>v;folm=Tq; znr>ir#+^gVIiE=XsqV()OLyd=z6+%2 zm(~Fx69U=)>^56)cIRmODbRmMeBd?wdh+O>cuDpY#*PpV?L=YH+cKucTXNP;3!^)* z>JKoe(kD#RBb&KB9jcGJgCt4HJx|+!#^S-nqFbkc#MyhB-r4tWF%D!HpL>C3nZwzd z3~bASc3|=h%p6f_aBr`YCDVgwk&xA8#I*o&tiK*oZ9+DP{Ef$-?;_3Gulki(n3qnJ znmYpgAyC2eIOuA!rR5o}MB|Gm*mQA*IX&itA$pLY+KA+;Hmw7;S*rntNi8E55afTb z1{Ovb77w1%)DoUYPMNkBEA=J(Ot)0W$!WR9UG~3WVYY>y(ml9P4Iv^xMB}P{%Lc_w z%;87dW!W6{&TZ;8?Ce{3*qpWH&>v7WX;>ROik%@>V!!lzm?5qNg%*fW+tAU-ApaLy z&`4w)Tcb+qZZcIkQc>X-05BG`vnEqY<~Az)XY+TZn{uQJS%$(t?cq>Pu~oP)e465y z&n{uaq-ThAC`OWUm?@@bB3X2XO^K;#x&7`*-pwJ;pY=?bkCA$o!Nwp==RW%~myi;x z7@mq|%cUo+(B0SHwyRB*@?|0y)%@r_(P3>{AguQ8-Te_HB(3=Y)=|^7r=5^WWrxzm z<1Zd->T*X{yGMCxPLwXtgi+xSbnca57Bv+^WX^=i2_cwi1E5>fmo5!xR2q2~`b21W zfDO-`S1Yrt>3aG5D$iDlB5TH2#sEF9;uuyWJ{8W1Y|CfXlky@pR?b3csHFghZ)=b` zP;BV{Sj4z&f+aMk#hk}Uc_?1)U_@maV+1W+dtMn3cD%<3xEr-MesWWZO zIV+U8uX(+iHh4J@{bpZi1MJ}g}GRBJA8Dd{D@~NO-XzyY|%gU z{=P%g?aWfUne_K(HpYk{)M{*2(-;lr3NLq~={R<_DH#;s9(na3OJpGvW0grIBNHim zy;6lw5M74*j|Gt2NMRob{r(N~)|d2wl2rQ{4^ku*B!6Qcx5IV9fC=p@OdHb^)^zK_ zc%!mHlpwHCI_!pVXYLCpvBu$!bUpU?+*?2+K3Z=g@w>Q)Fjq&p7Kd8oz6HesTpj1S?E6%`09h!7 zVf4n96{sx;kwpgXC32~V^hF0H)|}vXNf3g>pyo4B!ch77|6d7AluE)WjKbyl z4V`WIh@x-2c!_ZLifL412!0VfkI{lKpM3xgFj?u$tsc2?LE^}$jOCTZpb9h-kSx?Q z=23o$NK-`5&uo23>*H+!X!W~VK{9ZxdueoPp(03b^QkCxLdSn~O+YsoA%_0p{DQ5U z2wjVMp$|bg7W*=iYQ%3GrmxV(Rx@ZZ;t$e@8`*lR-`JZYW&PX{ifQ$>8L#6>!)&ug zet+u;H?uU#LcPgpfOa|LHKhv`T~9{0ZhPmmPcZj5Xs+T?-dwrHgUoE|7+RU`3CbDC z2#hd%!$6c{ir55QET2(#R?TB+Ua0wc@@WyN9|lSbb-q2xb9?kwc^H+Xh1Gl+WrDYS z1-Y50uH(?{KJTJ>OVEfr^EO~{$3|h^{$U@)M$O0Uf%jx^1kKxOkgN^{SRlDWENN9$ z+{5Cs3R?6N4SCo*t}o+!d@r}33z_Bk>~ZPL|mgL`8L}PHvO@LPzmdVL;r2fL~b2Nv6+i*l%}dmXM7#O z%WO8E4K1tRGNHVVjlN9u#kHfxWH@l=D57u*jDayYbin3~9hnvXfWST3Xj`5$Z+Yu* zYhmaKJ_Phd;I7-SZO;_{eZLDUd3zmlTL*>Ay@es8f7^@tTzF+uT`ln+Els<#?G(sL zaH%0_D&lb-f}2*$@xX-IBqN&SJP(pilq1YpLqj1*&s##116vy?!+ET4;eCJ#Oogu>kp$os^<{fQ2;?FxT(Ccu4*ip6D%$m zbOlwTf=MO;R*V+jHwayyitpV@fifkcnfUMEN$03gz3%NA7MKf4x_*e`oejZMuovb$ zL7Un3zv*Qg)RzP}h|yoZ4d!2zPWN&fB^e>CgOM?&pRvfgMk-yi;`M>dZQo}dz^j^p z>1&el^h7SE1`DM+XQ^jCUzdC#5z;su*sGX3{Wio;R6I(~Vg@LvP+1}bM=d=DWxr5+ zuVM*v?p~#L`xv_Ea($cxsvG{RCP@&8EzyyXY9oWcV$)GRO|$Ke()N6ke9v$hbsHoam-q; z>~6sBxdpW0IzGeRIC~Vcmf|^vW#F7w$RDP4Rm_$%wnn{8r<_f1SC3B>8+Hu9B3aF;v-swS+^w#pNv3WuhDe^i09h9T;+$?&{dp$ zzyW!PNn3$eNmvFYydQ3q z(h=rBQ72SZ+F5nS<9=rc#Og3b8AOJK+(Wx*DKJ!EIG)XSpiJ<-RCYCV+JIqv!nFyk zjn_ec4ZQw{iEyHH{ccK}j&qQ<5}HU$@SZ72TEklahD;D+bm4i0mm3{mz3|IF2`~A! z8$fi&hoPk`8&Y)aXTjI{R`!) z7`-I7s}-WeGfm7jr(lXye>=_^5)eChn&&{)?fplJ!{k&J4{K;~QVCttfWLql@nE&R zrhcQt(q`3R2LgHUlg#hbq+bJEqddZMgW|OiiA#7HQkD>=x`76;xtC4)XwA7U{|fjg zIF3An0@vP30DZwkX6)^f+@hn}!m0h>qP%5vs!8rvtf7a z6lIo#L*+&-;Z7aDA)6+Ad3Ni;+a7~UfS-9Hew{6^Y9i0GPaX|`cpJnvu}B|TJ~g-$ z&6Q^A(mf_RcwH_Rxh(2_679(9IhB1u%O}Gmz2yc2PiTiJo-tDyc5#^$8qq3IUVs}+ z5d3jOH7pJS4^3hh;7IsQ@YlaCo#9Y`O}W3pjB531rR|U_i7F#y?w-EqIZ3R6p}2bz z#siDFo%j6guqQj%sp`pG_x4`kpXF$4}(A%wC%tAaKc@G8lQEW`M|)V4`^y zFKsTKp%HWu?XR^wK1U+mcNH?*6Q{GsJ*KlGPi#jJP|}96N^$mTv46mHL5G1!hJo<{ z_**?bCbJTtjQh81)-jQw{(99S!`USTD2%QV4|6Qg-(IKihmP%+a#7GXq0iKiU zc*Z4>sZ5$wIx1NU{T*B%$ex?^k;9XHI;%R}z0h|1WMlI_9#H*OxK>5~8D;48#HeQl z!d*rq8>hqCccswK>i#$G7yYJza*d<7ez?Fzi-qWUbUz{+ zFhHP|__SjQo~}P!FDu*&oP^KKM1YnTa14#d@?!!Y@4OfU0y<0H2ByUKd3fX&NT^%t z)_WBiMkU4Zp-&9J#3cp z5bsNz3@w=-C>Bn}NX1jA3P%pM%Iz1H!JHU0{!4c4IfUIu$Icmc1@d6F?-IxDQ!ijX zY=guRic6+Cop*{iL;E6HOLecsT6)D3MtE$Qub#w;@R3fN{!RHdoP4Ip0*yS##C!*6 zvso*0)x!Qio`;c!djwD^t$Pv?x`kwD6HMFjnYy>o%0C&2nDDxjFcZeBM6%U+A*!;g z>{4n=Ja%LUMKLiQzID8)#2>Bz{A9)DgTwcgs(^=v;vU~tF?Ud&bUp56^)9@>-J?)Q zqTUwYD)=L+w>J(X3uokdTGGcxm~YX69%k>s3Yx`L7*WJ-l6#JK$xVIvK$Ei(aK&XV zRj?xt_5|Hlf!qhoMrSibr}Z+vbUq_S$;{q9_a~z1S5pSsoiTODcy>ivaA?p1pS}W0 z0?`t1iNT=mgrUcFtcaoPIy_FH)^S=$Y4c_WqtrbLaB{w3qN~$O|4ZIbhAjAZU*#-= z^`y)+JM(}Rcq8nmkRaZpJ-Mj9Kfo^extZoxFPNK5b0hnkio`_2E$_?QL+0#T<`k}? z>dR-#n5uXK$rPK6iqAxsZ#Wq`U1o z-MX@S`^jx4mu}3*?7}+@YGqZU#ny_EO%~j$4oUOZ=9aAMWR8_%iZzNnHf8X8owJQx zgdZ*%Fmo~JpWrS-Jq#~j4V6QExk%H+a4GztuWG8Ivd1piokHTDKIq7&1Eijq4@Ix4vsqzujnN3AM@joQqW|pg5 zIr-PZfHHX#HD4+y6n3+9@A7z<;7w%+q~5Vxu*R^-APbR4=ewX1+XrEt``Go;lD-dWX~>Vh?n-_DReR>t z($S`a3=WYeGAAezUn~M4xlRf;ech#7_T^AJag`wXls`3emGm|cQ2#bGhb&;nn$kyV z_-%MBmw0LfFx7uIIWl4$sDSQ*crAm?s&tp$B)7Q494cHl?~}6xsSc~6yqC+Q^jmIr zg?9?D(x%i%)@AN7RV5!(eUq-7*+sUa>x{4tLIv;1>4pQbm>Qi+dUDD&?7P1g;hO2v z8qxRjOze{ZdrIbsf>V=z9CK{OT?a{3pF(`8 zpo!I3?@9uTK7G4vtk$9_^fbQ{GzVMrVHoB5sKS__#9X%d>XRX%=DF3zkPZ+OGTMOf zhN_aPRKbDYzZ#;|O}V2=i!R~rBJ|%xKa`O(8RNQGkQ?wuQ^&HAs0)_{dSH;6xtc1Uw3n$s(MF4F!K(>VZwe2Qv*TNMBCA`UKHj@)-?qq6tBMRRdB9KNffV-tqawAxiEYW6j< zqkE@^dlYGk-;GzD_eD=QVMX}eV)K%(&WT4r;tWRhb_M9b8R&?;%8j;BTR{L_k8)s7 zR-jM$v@V#=MmPdMs~U1B&l3{#xQIhQB^$)0QBzrRCyb+mDdcA}4`Yeru#AlvGQ@I7 zbydy2`fG>xvx3gpa7j{C?OAqesTnZUF=@#Hi&H&mBSWfFB(ytlI_~8mwzckR1yAYy zkL6b`={6-hLXh*G;7)wF7lI;`dZ|L>4DJdLY%i^2yrr8l47vr0_@>!o zq7SfcaNs4ODRhZ{ncgdYVMtRju0K!@zekFMstnkt593z=RZZ|M@(UZoy1&oxBuUgw zHpG$hby@Xb&6HUB+^NqLW)nBd_&qjHdH3)dk#8q(` zo0>M{NOfm!!qKk(Y(f^tg1dxU0e^U42wb^}IcRFEQLGWt+yY{sRA^s@aP;6l`ulcQ zG4V10%&TWn$P$;FTnI6dnL!&S6o^^!6s^)rya(ljxPRZhm?W&^QR2)4(2if>`X zYwAjd9=5gJL{OM4aqLp;uYTyYL3}Ej%%1=C zo0M|QLhQcD046U?QNcNnU}9X$?(;aOK&j1?9TY*cPzgVo#bN#BpnZdr#D*eGDqdMQ zJEUr-;7fE((rM0OjT&*vE^Drx4i!n|vZFbyM(V&(CRhldTE7Rc!nD!%E=h3>;TaBq z*_j2Xy_*(|Jt=`qp(gKoAOV4Yq%!>Wb$;%c%=I6{m~2yLE3l4ca|DtU6khk7A8JpY zefnOG#1ADxjU6DLs1gbd7o%%r&{BZtH>)mu1NSo_!*BiIHW)zZ_m`>+I889$4W6DnDg1 zWX|4@L#Qs&p?sr7)|;U_YwvxQZi4_2ZH}a}yE`BCWrPUX9`_)XjMIipq5w<@V)2~Y zxQslK3hhGK@fWH3XbNZXN$gr2Lxz6TNX)KU@E!zi+GT!6P8(M)*Ku~p;MNP-Bco&~ z5SshMwIO1%Qv1&;3AP(B z{y-y~MPnV;I&lgZviS)H&mc1h%}RJaX;X64h7xnfX|~LPt`|jabxB*@6vT%+`VGAfI65ma|NbZCq3iitR8p8B47oXJWNO&{SO{HF>CmS(0sDrzZT#Efv zB*raP(`?g%ci!E+c~JORgt6c<6(#7|oT+$8=P#nzPCz|KoE8h#i9$o(r-Fp812WSG;_M7Iw1bZt+*%R-C(l=Mc+B=?e~ZfCPaPhSS4? zGT!B%&U*9j=qP2Av_KnsfaRM;1mlKqOc08C`UZcNXGLUYlk9S9U?JOSZ~A7xETP!- zla$0BkR**vW?eW&0-4=rhp4C)P(SmeHUBB*oN9>a==Ft)p{bGZdI$Kipos{OG+#gjcO8l!Qk9QYov>#&{L{PHdk@PBLVU4sH#Lq`sZ@(!d)h ztYpxftE`xUX=)1nFc(AB^%HxqWDA@ZdsMZy4CQKy%`aMufw#~>$mzM7P`Rxy*nCkS z|4}U;Ad^Nsiaha65D?Q%-4LLq1-O617C9x6LS^$tXLQl>rJWuKbyR&B6~?yDESsUANN|g5HD3c*9^pyV~SR8lt$=DT~WNdga|iw z@%(qjHXk|_oESw)`csgnH6~9f2qewgN^9kQPnXIlAq&1KIfS_i)m%uiU13*XU$@-`ru(cieB3lPO0sEaL$q(8$w8;$r8Jw zxIi{L8~+^)3)s6Qu>9X7FZ}kf#zzFsnzbn}$rVZc$Fw3vExo)-+jeg>6~07p=QpA` zrc^LBB2%uWxj}-m(-qmTXyeHy)+it&=ckCDmD6SU_?X3uaTzws%%%KPdC^QtD8Psb z)vs$$Y<9%;twrrtm6!C={E6=hMzeDKqmn{%GTh!;q0^C!yFM|RRY5j;ge-cHX9oMB zi(-I4HwR$p#ko^~1)3SsN>!BO6?-8C0rlr6&^=-tLJW7{MYAv&TNYD{fkBALT{`ot{sXBG`+odw zT_?ZFmpEkCHa)W*&&Sts2PAtKifXd28J#yj-5H!fzllM))=`F9?i}7DD*YnL4+xUz znIXa_Eo%+ujE11hktC1o+tvA6_!^rRYrAF{V*7`lS!61dO|jbqbg&F%DY4s6b$KUN z8nAB|lR&wf|L#pX5q&95HveNg8T8Hu6o5Dj3#BFDi|+1<=b00NP5s%$A4qm2SHXMb zrWF8sX0)6~z#j0$8;g;w+X}HF{!KUUA(Ng&a6Ky_{!OpVGo@5eFcCqR@V<^B9cg$M ze5?%q-Lu@6fk0{`dSwsgfWk}Jg0KnC%e0@CA1{qrzH&`zF*yQ_z+#Ogmirko@gIeD z@J<|_S87iDM&Dtw4qT&~Y99>A7>{}P@}D1%9wys@+idC&lYyiB9K55>h9RsF#J4J77D3>faAjm}#)4L=JBkZ8J+FwjHQr4h z2cS8MXs1VC&CcpsdM&^VgS)-a@j=7FCKYlF1#vKRExhlQQ#JIcsV=y6;WL=#?QuNjsj+`stFo5f=k6`B3?uomGRL;Nbtr>-Mk!I|E4-9fbg0Q&%KeLkH60Nb5o zruo{2LhJ7g<692Ep<_^r@2@ZRO7|@SD_YG_71ROR1~D7CILRHSR32$(iwBI7GJ}pf z?>odkyrDKhyKC16Ha&$Q{UPPj{&m4cMM`gGmSUOB5h9aTo9>DT7F)51ehvxK10_Xy zg7Wo-WBUfo+$C(oa7ATk328m=9^;7=vc#<)_=`r+8Prx7PR@nOkE0PoLR$0TRb>LD4tz1t(_V8kFZq;&w6%|iJ!wR zT8)1R=Hzv?#*->n73F!vud*G%F(^Gbg|4RDHGC<_B(r?lLI|DlyK|@;{??2WUWtJ> zuqNbua)BSkq;op~?%s4gW-}258UgtqU`0b4qTbLYU~9K}u9YH!yOV-6{F$q>VzEnc zodtNT+=2%w!!qt`n}dwrQ}f++$Qz0L3TBK`myom z*f+J3jfA)HsuBye=4vAHh_sQw!i$S%>@32w>Vj$wXjMP>j5)&to`OD;WjmDsL{}V3 zV9K{SUO@n)Ai+fR8doqrxhzC@;6VL&Zm0}Il$J&*YtIEm3UEuN``);#D0L5I+cjn) z!b{*wx+=*E?z6KMn|v}iRvh0}X}>+{;scIEXxwvQAh&I|PLQGCS--otpuD-;Vy6(` zahNoW$y4}EJl$VMU`F*7xv_^Z-hpmI%#mLAkue*FK}_gc{#Ut81;B+zhCaz&^}-LC zg`nmez-txYJ~dA1NUq4jUiFnD*N9)+%X@w7TU z0~Ttz`sQ?l0|7tu1|B;?bTGRc?zwwLyw_wsaA;RjsXzvN>#_OhKN})5*KF( zpEmqFH+Z}iS($RBqi)Z(h&^|!ud8^S$HA?6gTBkWDC4Z2sQ1V13Z%LLJFf|ZXwH8! zM?b_GuD*x5Bc@dLFCHg+rIuGMU4UB#0e(>k@O17Nd zfuQLK4b61k)f(d|-Hpm1ailvXTKz4SZAYzP3!>)rVwgX11#kf;VE1yn=?$|J*Aov{bLhrwZ!||`LlR4;|@}5E~V{O`he`B#ja)qiHm~%+EAOB&FYMPA_Cj1!7x)SKV926e~c|M3G8WqTEmvvz9ekUMO&GLH!O&>JrGZ$vO@N3 zL?Y@pd}{~dvFhtq;Z?%41vYT^6s(pdvXpp!7``7(NM#?W@ItR-iexUesOB6r*ReI5q_BvblQGaC$eaU8h^ z>t<4E|IeURfZ@GES|L~22)k;1vBy5P2HIImf}QO{yweHvL{vE{jzLC5*xkA_^MhV3 zq}|gB*L*41yfR&;TRVxS5d5uR&c$X1(4&}672swLl{N3Ie z_gT0p(aJ!GPoqb0D}vBA{RTbw<0O(b+J>YQgj-%Y8NOzMNO{DDYH!EXDiv_$+v&jw zImWZby{Jpxjkg6<5#GvP46X>LqFoYd&(l%y4VAotdn)_-t@JH`hdHo6B1t>S*kKm` zyp7K;U2+c-UpD|=Bk35*au`(28zs72(mK*m>J z1u)fCM(A*%RF8Nuu5udFx|jX$CJvP(1HVn>L-2y{Ps6ZU+irhThvhOa{@Jso z9ITIQeYJ7lE|DcgscA@#tTFv^!T5q|LYX= z82QlNh0f;#Bz8%9hRvy43cf$)O5dUpcMtMg#RCNm!20$&-M1v2_+XiYTOPMrYz*?9 zmxoyCKeUe@QqFsYiz?MFK&SYEm^yP}=V@Gjs_nuw$Ip?pB>U_vGA~PY-^>`-;Y9h5 z<=j-&9i*+AAownDNdX)SSgtb(kC)9m!3)po^63=Lusnnyb9J7FvN!g@!>lN&%? zKsi`o&dvX7jw1ednkhyAviQ0}-;I{Nq^QflO!+aMTZg0@4g%9aHfvc2t?ERR=qqKRSHA(E|9>mc2;?EkE(_thmNIMCK(s`2>P&Sfl;f>Cp8!C0ky%wuuEO~HIoGXwaK}nlOx3e`wB8rS%n;vV0qEgB2mi)O6_R8zpWXW5vQXDjH z*V$2+heY@s4t&iR#YkB9<|_BirtfQ+h)w_bK0VL$bG4T{9#*Zbqv;w%^9kMn8d)TMikd(=ee`T)*5<;DQf#Uz(hz4=Z-Kz|O61#C= zOZ%?2cL|OmhSOh$->M5`?(0UKsMTLil^SW;H$qx<-Aq)eo;q|52DjG84VVeDQZ+sK_e(y z+)@rCk8NLXiby#t%Q-u$(bQX8Yn)>C|6pAeuci>e9Rg`PNLqG^h zw1RRyuw(G}jghDiopWdKSQW& zxhNp_DIBgk-nkkttR61dcV3vIC(ybXkH-A)(AhEQQQZTM5Gm+GbadwraRU&E1#8w2 zAcR08r+#zaAJlHQ^o&poRN8k0Alkuq4ROvk&=b2(aK~BWf1p(jK;WIEjw*8Xj5jM? zr$fF!8j%; zT~yJ#qNrs>L?9VL&}Ty;U0pm&Jkl9KDrUS^aY8Ne;C%nF3E6+RXxZuzM#3`lg9lHq zt0YF;-pvp^vJ3U_M>(r%pjiYr22+89uyb7Fnv$INu;%(@U}e<0Rx|E@r8j|@;;}JU z>5ffg#fFK;?R0*9X!I%ees+IWqt|;ZgvZI_86BxmAC@x6DWpm^TK0dHopAM@UCCrH zE`nPRJ}{&ZxfBxyB@K!+8Ewr!NoIG?(GpJYj5|amviZP!cTPM0vfa}z>6v!F`C!TiLwu9e`;6Uri5b+oORNY08=@ujHo1;#!WG^Ru(V-fSIN! zJ0FpImo*pgfdh!hEZisWr;tAm2ZuHs5|MA5R|Jwl9_I$;T_fe%s<0bz&hpcBSu$XD zux@+rt<7YgsM3YT9vxbRXb5@!UbQlnKX(McMdMCB!Yr?9P`M6@wZ9rXk)N;%sXUEB zpfrD@gFFbx5mPfiOHqye>-k=%U&B23JW(L`awK{o_P@`j=OBBAL&1_(GL~t| zl;qgZ^!;K;igSDgDP&x3>8Fd5s0sr9)6z7P#CiS{O4*BM}>+1O}1$~)^P8&Jjg`A=OFH1 zk^PL$p#r;iM_&d9kD_YrVfrjUHhHni1Uc`) zJ0SzKbJ0cEdE$83hMwr-lRY(#%`xM48+Wv>E_Ow#>svoXh4NXD1sbcxhi%n@Dy<1c zcZShOqHuvpl-lz2^4KgZ?zzMg7T~jEh-2F$r33;IHikz26E9=P)Q0gLpKf!n20JA- zHqyPU_AK+Od2~|~<4+X_Vn?DRqi5<9-RYE7=&3FAsK-;`T>t^!W8^diE=1L-aI zfUf_Am##myh9PpQUjVJP^0|Jaqm}UU@rj4RnJig830!?(G#ERTMnMe{KNXO9*WS&+ z?_rk73?Q*I7|;C5$YaPYe8Tm%bar9CR4DNLM)CGv*=@pu$Q<4wR}6N~=@Mf*bD7^; zANWbzPQissm8Qd3BLb}y*NtsnIlvO}6Srikj2f;FR@Z&89ypg14eM-2{SRISgT5k0 z1Xq8Xh#_htc(~&AbO0sn7ymN%i9z^2j{Ip{C!Y`d=@E&@iApGgvE&v|S=fH_`OF{? z$&`f>PlG1%17|=~-XCemf6YXoUf~W1NENc|tD3r}pn|79gZ}O7)Ua*n$MDoF7vMg= zgInWx|Cy#Ml*{N@QE&zePo!26rg1ebq?)bIWY7X5E3A?J|5cHF1VzQ~l zSQmz|rvV>RuDj4QSz_Gj`Gc?1XxP8r)!@CYVg4GiAG`s-*adsE+FqLSaY{SMc9{o{ zu_5v)(>2Yov%$0%Zx<2KCp{v2a@*fKnF;Upuq73FqDA9@KqdypK2Ylv9i`-W*zIHm z!Gl|aSS~2C%in{EQ}ME+=v$qx6{Y*in9Tu!q*xwVMU?=K0$Inn+Mw1jsG=1I z5?7DpBHs6#0sZoKcdNUg;-u5hgkTu0ywfx_#$d_AKo2~*Ys&ue)XSko-7Y)k$s!JM z9cPUA>OgB|ppW+(3I@8?F4SjEBfYbembCx@%1DRpFuNsbKwS_0`pGb?6J^vUc#&e= zU2QGhSK3*#;^GuP2{}gyE)`|iRZ|!mfVeQEnZx%|<2RK%b-AV^lSli$F5C`P^-XSa zsXMGu@mOigOV%KYWaPIhkbf!VRnO+6lw4++g2L9FYscG%1}>droBOvRNvvqIoau%A zw$JJ2pEHr4{b}Z65MUd2s;9h>=!M6(6)8UYO4}Li&D1o^UMG5f161lPpWDN@emISj zb+%^W`aDZaBloD&ejHOTQlKZ`QEx1-J6SBiYn9PQKJ{o&q*#NN1Z{DHXD@xZw+{GS zoot<^?nGM7nGpO&=e#`CBz{X{RGLGt=t}@EN@vYlcBJ3?L6?84=%GZ;IwUHEuB-r; zN643yG~`=4K-Uz07Eu-(|H>F(lJJHMW5ZWO&)0u3z6`ooQ(Kkdw&6PS!pywx<9g=i zRWM?JpQORyIexSX6*cfkF;jM!KPpdi6T{GXyo4Xe#*uI2MY{(W#^H;DTW+8=8NEf& z*GZL&T!j>2q2+qDFvE(0$86q|{SjGG=Ryn0qXt{YtwBAN#UT2SujwDfJ||M+Qa`a+ zdeO-k;Wp`6#q(bMTLn?O?Y{%U3jwDBO6t{z79wCpzNRp_gRcr3N9Rx1Dc3L1??hK~ z4r3_|kL5`1#KghoshwT2kWC0|d($L^A9X&~cC;_@x4VCqf}ZWc#4A9Hfyo(iY~+wK z-*Rkq#WMelUtN^8xyQ)2fGiKCF?5j56@so_h${q~@OC2Hg+dxJAOnFo+O=uEp^z?r zlN6qVLysDxQXFeD;QaC$`xf0Y*P1?zp|DLW-1)l;9hQqmLa4y8=QJ!Kd;A%lZOofX zYj=gPxkQqt^%^n>K?{int7Kyb%sU&>ZRN2wC~n-ICK8kEruYaYScpv;&s^Ypj5)bPrx6D09sh?+ ze2ouP`joMD@XcluOdd|@feCEbIcz+=lV2Mh%lIWC9nr)luyzf82HrG*--%vbzl*|e z9)FmoIB(BQz9X*dOYL4?U%2@3MtsLe`;X!Cmn)-0ui|t*%EjX6B{L&C!&>(4&wbQ zfbNv~b5GIG=T^st+647xZgUqrCJXKOpp*9HrDJG_d*lLKP zmO;io2U+_F3ueE>*ycqFg4ITl-caK-SaNzA5j9#R^2{>BkV|Pllg3dddwFh=lRUHr zKwcQ-36)`p&y5| ziN!iLf@(^Y8MbbxbT2?G_DLB}DB$G{RYESFz@<1&@ ztgvi&$4}j_XnHdTc_o-w^#xL8#(lWM(LDK&a1Abo>Pk#Iy?OSARY%nU?2d7D|8+50 z2_K{@*E2oqVCNq%WI3T$vs0JQn=YF`lpKjGW7JWqpe%MotuFD{nBspzdm*u!JXN>8 zK8yq$6|qH~@>vQhgwPt5{P^O(BEoO$M0F{cp*CMdl+hh#dg=QPROT<+)aKtw*9eot zg$RPMzZ{f>-m*=4G;m zTg_~_Hd~8(o2U?E;@Fx6FGQCYfR7gB;i9fac&z6#u-@s_<2h?%#v9e834N0{{mUuv zBYnx@4VkpB7w`fO)>W<>FxjAwiI{RKPOJc?9Q|Ei7A4@9wJxmwWzL%Y;J;^=Os{cU zmyUmTBlftW&4i7w?rV!xAi>-jM&9LxYcjVGLlwULtQ2K&VmRy0mn zmGd=!8mtF?$CxY*PuquKxBe5jXtCG#b$Jsce58Qc-%X z6XJqk%i7IQ`}9TkhsL6UGX!x z`{kMVQJf%q5mgAgKeZ_mUd-ta;5naLKo)qg@LeKXBjC(W#eX z&^g$G73^Rl`9AHb2J9`^bnul(|8`-Wb=I2qrQ|~w__TB81SYjaQ0UVRc8_j+xZoN_{#@6M*rXfwP zr;U1gj#^TH3RKbNh&XLoA;MgYlTuFE1_N@LHx`A^fuuha*A%3243aLXHz1+$f^U0B z4q}{Q@1ZdGMDOK$J!b$hK+eC_w8zi*ORHidf;Dlgvr1e#QG)XGzDlf@;C?jrwE$bR zKn03#3-F6%1Rkof8e}^4Se$hZ>ffcm7GmOuz$cu2MqRNHgDnMj4HI^)LCB>-J&**6 z;1yR4P!h{yn8+a@yZ~<}P3I)X%fPX7G-45y8pvnXY0~@KMDR`8YeLo@f--= zM>_)sX_At0p)#wOKCA0}76!bMKwbGQn#Bx7q)*-^fJ-rOC+_15L|pp}AS*>;?S}S->4O77_V%TJYAr(> zCh-f607nz8@i!{Ygi5TN$`f_H0zw-k``3?-fk2dgyhvZg?A19BOXCrQX@}0+2_K>v z8zfeV_KutxLK;H8$9?Oke;9l|hH16>mEACT>_BW@J71>Xuua83&L0MT`AWe{lJ)&! zY#d~l9-b%z$0*alQ>bMni3gmn8gECX!=eyo!orepeqhp5z$I(9k^ubgIJrD1(@;!e zs2Yl6*rq`5TMogFRDd~^2K=l*$tPvmHscYx@E-bb&r6DaKkWZA4YT*Dj0MJ@F*X0* zrf=u^m4HvxW%tf6u+*~ZrGX?)9OR7X-}4cMisx_G>I~+I|3WQB2SEPCAO)mOM%qSb-ASe_kn0mw<4IP0biG z`dW~=`KpOg_n>5;Ig=zXd1cI*>X4pm?63P1-l+&I+7iNqkPWzsdWUB8ryQ@bM~mJz zkmiuo#{YQtPky-pS~(O{)0=uFHYDOWgNIk-b&kPK4ejF|R_C_WaGfT%#~EN*KP?pY zAEm&Xxs(Uc!(FgTk&ohC8jM-FICV=&L__n@Th0Sky!w9^-`aK0du#Dp$3Q$WwkdUP zZV$T(%qT!v)w@u35!u(%HSofT+X{QX=k9CuZ$pSL zK(gJx06xRahPDuZ@Xis&-k)wh50c92szF&H05p>J9i9J{^74+oA6U{^(Uq|#7u9}2NF!eP=^@7P|GB9p z%0-fydd$!!96D>O_2n+NgwB7)wa+r`!F8JYkjk4SRxMhG9t!Mtpul-#A%{?RPCT9* zN&21est0bwr}R--2{X4t`YIn9s%Bfn?NV3$^j+B9j3gL0spqm20QQupPW)9sSo6s( zxI2c7AGTk$aWHvkS}@x`I?n-lGO(ND@V=+lyYC9r`_t^n9UeL{=qVg=4N2~Y76nI& zZg1HOp$(ED=R=JYAA;SB)``P#z8KN1?)MJ5R3pv|BQK!SN9d!!kikfVT5O))ov|AO zu=K304DN;&_=Gv72Ck69{#EFx&X%dn&7xjT&%uK>&DH)}R0+RRsyhSVx$a2)=T>FB z-X9}0O|s}qWuDUI>29o^f}l7N!$1g!ft}f9eNEA4AF-B&T|k>FJ9~nB!3tN;G6mts z#L))kxkMJGJ~FOU{i_b$CdlJILZ8}}pI`m2y$moGZves&)OMD{A)4erkM(8D0p==} zF%H*FtKsi2@r{1yTXsW_XmlPGr+t)Z;{1GWd81h(ONs)7_$Md=rXSV9f~gF5r#^$B z)X)3uU8jKpCJb&hI20AT@-apwbw!+*E27C5?eChzKSFS`a|C{!`cU(YcLel4MM81n z%|O?0r_#6yO{OulgwNy4nDG+)qf$%>g+5lb78I$oh{~mEw_;=p9Y6p_k&a6YXN5=^ zJVRhn7uuMe zO!Z4x<;^g~Q$)fY@E;KzXj>>8X+S>5z#HppJ^zHQ0eok@<5DZNW(j?_{xLkFTA!`t z{T?X`h?>)l~pGWNghfW!mj);lWnsm zKj%$U=hzK!w#=(M@HA3!-m_-q_~R>Z8VIbmeWk4qyu zl)E7nqXBU;+V?39C}F%`>z8JAry&LDv?;XoOvZc%YZ2bk3?^0s1|%<%f8|47t$HjE zwVb%fD)~jqme|XzSu+`fO!b>X82`?MV=M(S znf(BuQ$FQ-*M6w4V`?9kzLdx@~Hc zF%_Vy%2;5tgEa($yPvoX+ZP(e0;Ff`u0eCptZpfyJD5qBCL6TH`?PyKIq^b0S@Bog zaeF-8a(X>H!?I^_MF&>_uC1njR zKa$wdCp8#s<4=`5R}*M)P^8b@dS9Y*9c1NxZ zz!T5pNM9L8-@tiWVu`4Rok`X!+zFJr=;tsY^`=pN2kXfR=E|)iJAz+hG{>Zgm8C&3 z%~?rq-`8!0|Ct3{z0T;xx)52DRrhUR2_BkwvuxlP^7TrgK@Btbl`JX3wta}Ijnqov zoDVwnDd=05;EqBkiTqwzXX=q@pCMEzDs=1sQ5+X{38LRxPxvEO58ZML!f^;CMmnGK zdcud~@H(JLCp*dgQBixAJZi^gGvbE~!|pWBN9_iNn^Shr(&&r0wVrM`8(Z!en-Yb4b)dl4cqEK$C znHE~BIC{Q36RT1P%I(-8@R@bwI&(-Lh@|8df@^PW$bQ={Cuxj!fi(ojS=1_jCJcyH z_aT6xva{GT{&0u2y|YvS$DIUf{eD(u7MpHh9^H^U!^O($x=*flv-qk>dAWxyQpZH2dX z3x+uo=}*qM-*^aBw~H^l3VC07c?LYd_Z_@jtbUxq*tVao)PNQWnpT+2-u*3>y%%hZ zjHmSNuEL;u)O5hw{E*T5Eg)QN_@W$lH&9il{b&<$4bT2P#9h)Bss_QZT~FCu&q7{q zy)rPS<<<#}Jn{M|ZQHtQp)b(HR^}MP0|K~{#1Wg>PLQGk-0I{{jf+3E=jDk;vNid( zdHjZ2kM^PB1nLo)#8vutg#lrmGOIsWf97mob%Q~D`xAhL(LpBEM^FN)pkE=n^#zp3 zs@D?-=;kt+Tjs#w3hB3660SUOOe~idl!1KL{0G|z_bC!{c)uS5_qZnKP~YstDUY7~ zU-y3>Md#%3+^W`|z9hYy%U<3%CRD-A-Z@KzP15H6>xs!V_9l|luCwBe9uG@S|Lqjz zxo{(x)yqbgn-`r@`WE?7I%maA6IgKpyCzG4_0*%5eCEcfz}en>2?bT=be&b&0Qvk1N=4sO19vr>Qk5OSX+nOz(xo7EiH{O%Ol zF?t9P<;33}W$&ixwWRf+IxOQ6dtd3@jy`IM}a zfWz&8Mt2n>|Kt(?iLPpCFacNWeN7R7$2B_=9cj_XtM{aKWtDMS%`#BXy$LwR8fCS{ z^L}w#E#mh8K@3aFY?pK(@z1<{Ni1aNX}4}HrShXS17sF!ur7i+>z#ej&f30W5Dy?;1vBBR-&g0dXnYQ5k&Ixu07qZMEUso?sboO{+RkB#;q+c!k z;5zfGQ5|LsC)*K`Zc(bI2>`%dPoym)$clpEK9r|akP?Ek9`^X+1$vq5D>$R=9A|nC!|;3 z&oT%i_16K6_iJB!L(Qn6Q_bT)y{PUGqg+y_V_skJM88D3f38tq0=;;!>SGoD6XyP~ zk*{@_yZk7b&)v=&prK1@g4VEr3SLS5E-0pDH{RrVY{^Ob<*h}X+c_Ds<7y4YwATjz zs^W=9NYN@dZjsaJ>hzbNG&ocHhdSa1hIc3H`(*T zAG=X`MhqOEO@j~_Bmru+mqqrQed z#66Nud>YO&Aj1 ztXGEheBZ3zN`E^?qk=MUdA7~2ZpX5C0`bR!P(9>^?v)opi-eGg^C(d$0N5Y82qguw zFl`@sGGC55MKdF4W5cYLfgK@Xb^^Q2LBr-I8|h@vk`Hm7*X70AHui!-wNK?nT&Kp3 zRuHghsO*pwqc|B`A z>6!|Vs&N?|9t%!&zJ_u&;%<}zE66F53Zl2XpG1@LJd?-sDAvDZ1u!K?R~ic8B5(iZMg!L7O2HKF@011RP^SV+2B~ z{MT;})DxMM+vpd{h%h_fxr7(K23kV)0Ux&2O9P5$h4YN7RWmc^Y{jKA0N=!Y(0P>( zmSwBW6YhP~1Ff)XNRbWCF$Ef$0j-nCilJX8~T~C5J$KAl&6u6b0+(h4b`6XSg{-ED?JZFo%gnVE?)Q zB>=BWSB8Z?R^E+60Zls2v5~{dCkZsIq+nt7$MJN#SrebEC_^_G`KY`gBX@ed8Zq*H z%&h2HR8W8r6W(e}mER9TdC$QFJ>d!_7fmj`ul@Hh#^_kXt^-g@57n!~uH#t~T#+N+ z#APIga3%`n%{i$lUM2|B%qz>{*x>LCEvE(Z78>&;iBmaNF>h5ApG_84GmbC&YJ19$ zLm4pcpFQuHDj`S}-5iB0wKGYbmHJSdJXN?{`1LrMTtON&{7&o^Z#tM*@v)$AP4W~d zK088D4BPA%snp(48u!uJV8HUTj7^6yeSrx*4s0^X{K_EUH85Hax{@S)eLm zA|{SD0r(PhWK*pg8TYU7V=NB5GEYOWSHQAeIJ!$F#d`g^xwWn-H1NSkTDFIY>?oHC zmlGafo;ViQBL)023t#}|E*JKAZUDBwWz729dob;%$BZ|U)M^SddR^k)Yv!mUaJ324 zgFcQ^IKOonDfNAY-r9b%TP{_+L&AJ<%x^?H(h!;?6+Bfkhg_zx$I0^M(CL!SjHM&c zx(-0RQF?A?w2bC?oKJG{o36YvXj^o0jA!s-$=fSz|+{AW9jt&BDM~cP;Ao%CE{u51E<}=e0qkicbAVd z3zA|t0?Rbe|Ih`o8XphUQJde9?W)}1#@6FJacr)#>=}eArQ6+zz4&=}Feg#{& zWE?;*pI8x>GLW}4mi%bq@DKAHOfZGEcft-o7G-SPQ&f1ZO5fXaE7@`&r4eGn7s%;0 z3AWC5oRay(^ef4qKw0n#=f2PjeEwY(v(y>Ox17UAYX(d;FSazvr=V_H`X*C%X=}-r zB_DvXTEzf_*`_8jYIW4p8nYf^Y7*3YnJmoCL^6~LAoou566?#lyT<861ArAQtwt zEjKQImKngi*!$CukH;wmrO62llb@qh18=doGEU=5p1EfP_C(_vgLtQD8@~gYF1LeH zQ&x~-NsIq80h7rPFqwE&lh~YwC2w?0i~6UFZ!gA|f^}v4Rb^VY?-9ENJcY_Ol8-6k z-Svm>0I4Ri;pDoL*wU9!~HF93~l-Otn?2R!TT)%Tn*;&~VrEU2rJZ zPlZOf`(y#j@A9O`|M+REq1NXMw@|)&#ptYHNO+^~63y%B!@A%}*;y*H0n)xw5;_TP z*_G|80a+r={y}Hg+W0jr7V{D;8LN8tFgd@M80WUv9ocA^6JcP(0aU!iKz;8_0%KW0l}E%gwPlJu>tpnCE!33c)=Q9h zVca$kH<{Dw0UZ`6aVDK(wrRFuWcUU)N+{~a5KlBR9U zc;@HRq%ikxV9Xq{l6#vj@a?M1gMWcJrot7=3)1ft<=7Mw8ut*dmgc_TH_eVDpT%{v zO?Mjp1b0*B)_z)z2B^5CEuY73SSU@hKFZ!92r+f64F+DB_&6gV&1GDf#%TgQ0>Fl$ z1yI-OzOaz47w0~zk9!IakZ#$%y-jb4F&ya+Zt>srNXx+L-+LzX$~uN~O`!c9bZKkr zGNy_yQ$ci!%a1RXYPyN!ecUfozvD6?7%c&w;K7sS(nP8BwT^a&St8U_6=2d+04i%z zsP#}cNolW(^X>0QERqj@P{Y^~Q`Ph3w^hx2K*9F&R*WceP6g{R2Qp^uJoxkYG^Yb_ z5vhOMOXRuC-$2!*#G)dwhDNG8k#0g1rYRuD4JYW#& zTR3f#fzfD|Q3V8>qhVdlHnX61Wu6G@Qhx=Ws7O-hjfx(vsRt2Fe^hfSR`(ZFZHS*u zd;p_wQq2PM#0^7(B=69t?unDkif_S4h>3z4On`xS5fpc&b}5Ls=WP_0M}^XjZS=T` zLDk{DOIXE*%anujTb$sumP)q$X9*NHf0PUi_?7;7TzYC^;9|j)pniUBj8+&2q6VBy zZ_#fnYF6$c0N#oE^jOuq0pK zU>k^P`p1BMnTuV+`Qm1|eDHD%At~W=KZ!*B$IFA{S|R53N3g0?XHJf7NyLyVGEf|aDlJOutGPAT7IG^IFG!-rL4RFzldm=2E z0?F><20&JNk4Q)*C9wOQC|6vDB6L;N;MhrHy3?+yFR5#4oBs{)trNkxBI5QxB`+&aQoBx$uUc*Up$8(y$>rn@7%Ik+yDgo zp%ZCdqS@A2Dgt?tV`<0}r^vLKR)^I3XzF2lp90_2rhLJV#>GKYCLN-kNs7yM;XxHx z15EnSh6i7heR9pzp3I&1L}V>ZA5fksnlmN(McGC67;?EKBeBE#@OPcgrx^lCH(MiS za7*G5@u!h2{z$cLQx;$Vv`Ea|m<#O-mW3&hE`WXjr3b@Dua*}0Z#X@nr`oPb9KsgX zM;I;6RH4QVIu|aKXQfW9Ur0^vk#4xCFUu;Y>5m+C4m3>S3>`4al#Ls zYK(;)ffxs$zm8Wx9SN&~HX8X^8>}iP)D@dNSLG&5c*zbVmbXUve4#tI0>FHX=u1kU zS@?_edxVG`H$#j&ufiTVcKL=IaN*T0FyH7X2K_u$)ZBeyR9YJ#ta9MWKOtAHd!~g4 z)O_P>a{GkFPnh6DZcDUT0AU+t(lL5>JWBBwcB>-Yr}o( za*9b4dC)Xd5Efz{2th-&U`+yzLe5H}XDq%slHR;PhI{a$t2%hpoU1a(RkJh;%$T*&+HmnQnCHBk0!;+uVq(SoM4{OPRnIk_#!y&*_j|K2+1=m;&rT+@<RF?%c?!;7kshV(poc9gs`Cy}M(q zR^#-6(}#bax>!B^(OdQw+ev{$Q=$t3qZ#gO++#VNZ*Gfz16?{?;mQ>%7Qm+Pzj+c3 z2%`DOk8>{iE`z5#Fj^YjfL@=}kAIC%MNuSbtz47RAfli$Di<2n%M(+A*-vh7 zQiO{Y3{_T(TDCh$7gosX!D%#O1=83=_VDIdWO%Qp;Q|Vgx`$rt%Vcj72GB^eSy++W z{S|9WyP{UPdi(1^&K3q_O3s=uEF@?!iD4vRTL^kv>$$)P>%AEi3f&u~kr+mjV>d+s zD{T$x5#|^xG1ZCo6Xa~wJ1E<3;ZlkalV$;I9MQf4UKg;1B9M_gr#Iu#2R-LnHazLV z`LypLB$~CgliEGS8D$YpvDkLgL}V|h4SWy&n!O@ir4+KfE6d&D$J%kZ9B(IU8EFz| z&S2((LfJ+1wM6l{_2*p|ZXP&+!AGVna{WFdneA-i?s}SdA$Iy;O-FM&@2HB#)xfPB zj_bPG`+8Wlv*yC8lbNaYadQAQ`B_dDlIy8%Y{99Rq4ENThyI+8cEKcP5Oi1|5YT?U zt*a-I^CVkDAV&;fDlbrLeYx*(>RFDf_^;HD7>D{WJoTGF;?}U|a_mknmAyn?qu7^f z*9UWq@Zs8Tkn^1)oDHMo>p>GS-f0e2mx^+4&V^W~-ySa>k!5~Hr4F18co~P54?h5G zQgIEq0@Ru#dKQ@jR`2e7SCw}{2Ro%ZSV@1S^a3c@(ddd#&sw!;c}uu8PDNympo5#{ z{7KbJ)m{Xnxm;^2lZ+Ge<_xeE1x-qavnIBfLTA@*dPp&X?SKA}DiqX{Grz{0kzID~ zLeZgC2opHF*PkDfz(H&_4@*<&+DQLJ=VBOASmEiJB0AICbc9p2n3IAv&}ulz~i0 zP#(cau2tX_D(U}vZaFy#*74s&L2pvMEG?G3AefU8=0>P$xDav~B2&)vqg#U^=M*Xh6JWB3ASL z^`$Lj_|8gz)W*fi57hxQ80>J!GO$7`%AwN}g-uTGWI8v747H8y7m)Xffj|f$Z{^Hq z4NIm(y5HlgvEpGC{@3EVzVn9Px(9dJ{>rP9X&f|TfyJ$#t8BT+H6H<* z*gHhr<*f}`go{oRlC(xetV*T)Ewx5FgUG?K%Q(KKWzDr%;{HWYXH9O{nX|3dGSd2# z50YiDU%VRLNS0qCdFYl#*4Om4K-g=`y+^ZcxUKGiX;R4+ODGUb$+e{0bU=t1m+u6v zDZRreVihaP)2!!r-c|_%)m|m?my6%ioMODIx#?FChal2ugrkl*Q?LuU6|E6n-N{$( zTE|dcs<)i`D{$ZdsID zH`zJ`_${fysGSUP$Y{JLE&Zqn$5FY%IOPXi_FVkMG)V;KENQGf=h*G7NKUf08#OXX zQ_B!DWCEM*4gqY&e!wjVtr2Fr+qi&`V$*B%fYi=EgA^;T_R-sDj-aA51z)qF| zw;e@_u8dHwd_Z^i?~S&!A#t9LGB2(A5QB#wR@$;RbReT?ih@c+XmapS%b^nc`$j^z zDyWK;;*M^<(ja+YTOoe}Jb^vIB+m?R`Rym2|wCe{N^pHVU&>_;`gW%%Z+;5g}Rq_$d*VrfvJpHOGW}=4M6oW<5~V*g<5t|vh0%Cc z?^r^kFk!`6tdnibN_~sX-+_n#$IvA8P7hcDFth{&^}=eF7%raW^Y#kG6sZ)-nu{Vt zk^r*|V`N+>B&5hU9o|2=U-x6+0M+hV!@qifik6qUF{BI4RW?_Ql%aI)0>ymwpOv28 zuBq%*Zu9(jdfaMMbVmc&V1f+Gsz$7x*mgNxI_qxgOn zC9dae&SZwG#Py$cFJa5CzF zD}WKp?B%W)(BFgO_$YrjGL+<4YchIU3m8*schWwa7HTuDWTitCz^}6hp>V}4uu2qy z<;o$d>%jpc)B3;YnW;elDJqyTuBB$bsnAY-F-Z{DMk?ZRSyb@WF#c2J2^K&UMe4&E zqb(0_WwGHn>i)qWACS_Z;6WYVg6E35aO)*Ktz?HmLB2e?!_K?oPhbkO34IW$c^`!f zx_xe51LdD9JGwd~j+NiMDG=R8zp(m!j5TukBu!nLYRVkAnOhO*wP-?oKZe3Ka(1w; z$VKN>I3A5FaflV;*oIdr<;ioaFqVZGjR{E?aOqiRVob{6d+2dJH#C{_=KZ>S*?qq5 zYLCBE?LgC~lz@F;JAw=qc4FEjQ5CSYu;&Z1@uT8(UdiILYjezHl|_De>a>GYs9CU| zw5hI9_)j35-tsG@Z+VKGG8LHnb{E!P+UD^(g=RXDSDJuPG9JQ$py2pE- z_}f^foqaC1tniv`HEXlj8GM6nX?g2I2nX{~O0|owM)?WGO?4=xRCt9s#3?RoPxsm5 z&5HhW<5p*eG{1Ki(y|7gkgw>^@7$SbQ{n4IQSzQc!I_Eh$|m*_eM$FkGL$>rOOjM9 z<-#ZD9{@M=CO7Do!mB&D+?BVd=zTRhM?(z7`I#EEaMl2gc-qkXpF<*3$#ItNZ6bed zV>CgrdVq8&Re%jGb4;C1cgg0hSZxZmJ%}u%>AKq|35)+!SQ|w5d%3);?^1c{({Te% z;l(psugv^GI)0ApSm0siQLccrEyLSt-V1?cM_y@Zwk1>{jbo#c5(q>PM9vYhC=K^}%6#|u_COv+6Q8FDCP<>Drhj$-0< z00Fe(RlW(g0Va@ zO`d=AE?CGX^$v7p<}xn*c+A1XEHLGhv^T4%;9AqU#bWol%eZOh__ka`(VDBeDZpg} ztbWCOD)9)SZ>t%%v1UC`3+dR@cZ02Zo65CzR1}uEYYQ3QHGSz|tCiU$5q4lWPcgCh zc~}_2AJ4BYSYl`M$4~Du(^wE|z^l`o^icmy?bSAudPsEoF zbak87cdX^Och`@oK^>qnohxva%}iKgpSBvrCk3 z?>r)|dHqj%6KS&7nWS*pxU~HbLqU2NI+V5Y{#`CDNoN=qo)2>M;JuDt2wMWZ!N8!Ba@Z2E!jV zq3*}tNem(<3;&KMG;!6c*m%bu9X8+Y-l0xez^d{|bd~Eek#nIz7%8wSx z55NM|{D;b3?-Law!Hg!@RqgDo#~GIYuX`&#r~7S=5{ zjJ6%WAoZ>s+hBIMWQd5wogkE=tfCA5!VUI2j8$)aTHWv^8*ZQ+39gtl(3Devv3r40 zC~0+MTWU(bo2QCi;Hx>Fo97)+d~gzan-7u~;CmILQiyhqSrR*-LJ7o0Ayl}{{*19Q zD<{B)8H=t6553CFF<+!zg)^n}-w&B)QLbfV!+3|=7$3sx5tJeLO+MdUQ*L_>+Elx( zQIqN+Ii)SJ9uk?+`d=?3Rmy*V`L#HlL?36xKk9}W$sx$5`2SdYk{zre(C01T;}_`V zS!B%}GxTQ(BVrqrfxr z=r(9zVNF8@yV==R;k*tbCW!cerb0QK8n0aJH245XMPfuiZ53&-()HATt8p7~y0=dB z_Tp8OT*~{LHK(T;l}i(xi_^JMvAkncT@#&WqQPQaih&6+SrxlLeaWZ+~7vI#D;AFq;0nI!uP-!V{e50ANF~V)vHTv8_=`=IGNHHc)EvP zszWqXy#~%Jzbs6N*t8kDGBo~ey6D-YKP+lpLGivbFh(x9Iv|c;G-J0bxh?xiC6SU( zE|po=7|&BVt~A^PkfzZRMWgD&wvsLhHad7GHCv~#f;Vnc>l;CPGTwH$TQ4MMmyueT zLK;mD27j1EXHd$mGp`b_mwD&Du*p9Rg;+zW6B4$#X>|W3g|uN7BIHlgFxT8Ha-3xCS4+zmmhZo1qnSo{6%Wf~wNsDLj z3YEs(X}1k}wh4jHP_iCc6p;HhoS@62ldE=jqz-RjuYd7v>MMKqhqF6Fh1e@s)u7VC znFjIiV4$k=bLY#T*nKRooUzRo__#|}A1>9k+feHv)FhiT#?$0BFT&losrHT6+a8sR zYqR(_lD`I_#$83AQ-R!6&?u_!D61^D*Sd`9 zwsPfTs<5j&zjK_342#Gd@Fs_1Le5QOq)>vg#esUHVaTCpJB%JGH}o{u?*P8o70c41 z7FgLS(dq3Sg+zYzBJ=odUJO5gJZwb`tV7~nw&ys-*n%n*g7sW8Fh`dycP`97;EDdU z2n4*&N(5pSN%6A;{1*zEgDZGwwgLtR$>HM`le73fJXUWEjnq|8#@gB%fO%+}(n!$W z80{6{RPt?oQFkK&(RR(BY-C3c*)IR}-M}aL+yIeY6y57pgHf}L=l7#%B)Y<4#-(E? z`mkum2J_DZ_vM#WAws&}i8|2210?Az1~zMbJ*QE9l0NzsRc@CUVwuE!0Zt=T8IIL}FkG2ecDee{Ykv=&lpyz!Rinr7oTiYS1&RTAGY=&bINVT%P!d!5@A z1sq4-3ubGTJ3OZ&j#{uSR4Zet|k#+?2OPQKeM4IwFb2mBgsl{O%W zq@k$G84fd>kXs||Oj5NUK(#^Cx(t^lfPV-*hf_jka?uLr)trr`b4vkjn*tw*12F+>Q!+10l=Ml3?pKdK(hRJC+1z%;79KD)xe zPEaO4t~#9fu9HtQVJdv6k-(6oP^erO-aR!kJIB~;u#Z4pM-#`O4yjmQ!p|Rkd zq!fSP$4tcHl^L8rz?7$;(l1W%H^Yy@Lf?HA&#!S;^KTS_P^tPGezsXU(9>7|>OuX4 ztAz&7gUeljAfH$T}IK>EFU0D82&P61{BHjiF=K<*5Zrhar} zQK8<`&(k7gtS@<5DDbkbA(2_FOsxVLPVukZSVNb3-Joz`nzBzi|0BD_0*H0`RUEjQ z>}p4iN@;oQHoEU4HUV~d3s5P;CzRLJ016$9;6rEzBZ za%%I?O5*Z)Srkq2u%-t_HLy1TFc=D{b|UIubD+Z}iQm z-`+&BWleoA(f4u#6;N4Em=(HCK&I=mO>7V~{u}4e<<L$Rxa@@z)rvdc9gd ztofIHeYHy2tOtWCh@$Pvyn57AMz~1BS=ZWInbPaixTE01C&|T|R`><77x<=z%wZhv zt7tW6$%Vnd|JSIu1LjjWSH(lh(!4;q)NC#>uYRTq_hdila|;uT_2mB_f8f1>VulfL z4wZz^bybK&8?hlVA}u7FiXn0edAf>=A`)-U@2~e*gv&u>8BUB%FVl$~v+YeWg52eUHgdJ z*~zZ14BXUU%&>tJJ}Nzmuo;rKqK_`HOz5;Y?G{Y1xu>T(6$8%mkxB~B>thi@CTBD5 zY_`4ZwLmEWq&d;$pgsu7dJjs^&Iy{v_X2{An8Pbevx4w`% zH%Gm^DLO+9O=7yqIW01%ZFSGwqNyG3<}z01!TMx9%E+Avul%C$KDH2TUbG@01$h|_ z)EnB*W>G=3Ber)K95?9YXv6Ad&5S->f=>}Y;y2IYQWG$nvm*%WVqpdiNmgy(5daVY zYgYTq&0aF&3KT+tt_Z>NYcwN<&(nwA_6=gGhC;$k!zEsC{O_(@nr5YQQR>3IsPAwA z;(l({6p6Hv*PuC^=d%qdCDoZB6)lp~_hr`{Ve0Ub`7CT>kU}}v_~+0wcg<#)sJA98 zF(%wj!h%?4SC$cqTB9znD4WV z4e)Y%B@t*D6n{;j4L~(FlYud)V0lJLSJc8sFV^D?z}7#OHTuxZ+z&eZKpi?xTjJAF zWg9JPtB8S@ox*srQ+b@T*L5`EYf_1ewSGmsW;vg~ z8G6ViAAqHL#=!7TTrP5;emA}=idty=2F9oozhgcUuR6|k?@JR=ID6PM#IG64RloCU zpE5q5f`31u8oO!xUz25gl9wlkBz70RenP>Q*4nf@QXC3{Rna}EGJ@R&yz;@lyM}n* z^-V7$Jd(uLU4tDSdk@a5(+5=kOqoQ=k~SO+>}|cOq-7?u^Z7y`cVqNCrYwx<;?*>4Cv=AZ zFN?xwBcj-qfE|sx=!`*AA4x6)BOqKmp!mnKm<44*$tSrtx&~KrGs8f)OPt8S!!DA{ zvYJC^bmx0zw6dqE7;O7q6qhjN+bj6EuR@dG3Fseh`61;5ga@)Eq~1*3d2G50rVb}p z^qM#T7)*((m3*WfFKxB!uy5l?oxqvd9r@`cay#=W^&vOUHSyRRHKQ4GO|rODc2jIG;W!dOPDm8iNkTKZno+?p>(-S&G|gEB{2j`>HD~_Y)VBXk zJlPHIK{P=tFF7OX=bI>sEx0Q)lx~bV_O~_DlkJtUCxHsRy#QTA1hDzTmnvOVVgz}N zhHpHt`5?tn_0KTs=u+1)@AoKrwn$VkWo*4MwO#*>t6AjWWKeH=OZqP4F$ zJda^0LAqi?9D2*6u}S?y7@v z0-UXwjeVnW&RxBgiR{=3eQGp}q8N_N9mYlY#C7>zrYwze{J*$l!2%`<`X9SG-KnL2 zPl?T?8xiBzu>Hys-a5B5|B_X`O)C_Ss6qQ;k(D70;fBNd-@H-&d8;+ZS49+vn!rRg zI&b?u$&yvXWSzt-psmyk@;JCnqJVU+?9iy7!cZ|JU z{GdK-$l)OR0PggR7Zr6}(ei++nBr|l+$R#{QjjlOG9n+c=gDR`Lg?=%aWFsIgpwbA z%DcW^1g|GU8&C>uB+eZBI3Fc!!1GuHjidYs`wCXBSl6ZXqwT$i*Ql>y8=10IUj5uq zYX;R6IMp7F;NRHajLE0X5@Z;E)6tDw_&9kTB(qwWP)nqo5SKtnll+gu8KTmI)-ZIT zN(1!wi`%LIHt}lYk6|MPldemdTE`q}sh zKXx4a+jD=K{UVV$UCsa4{3XtR05L$$zsk=1Rx@im+2_M0C2Kb*3x=nJ-3TTM5z5Wf z*6t0&|D=;^+cI6k@??1oX-Oymtr;J{E%qVE8YW&}Sm+qeya0qC9_{QOnWZe}_d0(v zwq4??YHzA&I&T>NtxdZbNDdkV^v_~SPzQyjQ#DVO9n)ASODNAnMd+fgTX{15(*7nj z326u4&;uzG?IbIr+*dicjZI1n4BU$g6ETq9!7}X@K9fm9^BA{v_^n#c-^H}b44zhh zrO#@l(;Ezw|He_=S+I5Zk5ep8e;@MWrKTNABLIerT@Jx`TBT)&!g(Ep*T&SoHNn%n z>Gdi%%9c9(=Hl&Bs^arEb?>@QM$hbGf+Lz+CA?NW}vK-Z=2Hn*^l5BDG z)0(i_+vjG4`OlW!Ep#3@elusu?M9RDc9O)Ve zs1LW|GadK$BptbG5VuzF$OmqSjs`HmUQTmau7DMQ1s<|#4Z%I3<))3C)=xHWlx1LF zgheUhf>v{}Pd`P20#z0%;dRwKS3dA+T~4TUf%tl<;NX#J$S6MCBv^zJk25?JV|cnm z4>C;t>;qF#!=20d*`7(+pvM#m^nXKo)Nczrfx)(uqkokZ$wD+CVP&#G63-HqyDQw~ zwew79-=SDuE@)i-iElF%`2H!KenT{2Kf|tQGRZHE@=RLr_P~uz?}^2 z$Do>8d$aX>DyuL5uBF*h9Kaxk1}7!xCg0*APt4D4)uz0L>cwt!tIn!z_dn+ATV82Q z+?Y!T1Ob*}5O7rLG9s8X#IdRw{ife&RLP+MFY7ArH2X>mX6jGLW}1kg|Ml$-^k3cB zmg_XI1s3njGnlgRf%#8V=R|C>MC z_r^@PxE%L}`*WqQ9ZFN~y&Jh9QbBt$FpRaO_;1?6PE?i8?`i*VplV+D1i-!x&aPpf z8XtoeFWp3ToiCrR8bGca46#8G_`bYoAbtL6Qr8 zJ2Kxv{5b<{LGmB@grd(aG0vcw+n!+UzrXf{Egi}in0YyaDQxfg!_9{8i`8G-%|rgaPcnnOc`9d-`P z8m<1On@S+LZOR5HO5k5Q-A)WMd;Quq> z8J391qs(}nTvD{U(`0By#!sfSVuDoemB?5+W;UuF*z0NlK(uKHvEcQGLILno@yaY> z!r;g$716sKxEg68buf=b^S#qm37mqt9NuzsWc-lmdVkH~25CFz<%A_Dgc1vA&>9lWW^FU8bM&QZz~ClH3Iy9PA-xdv}zFgB9WIr<#;R z7Vn2ZQ`$|EP?R2xNLut}|F?)$L8(aM8h_XC&Fd7F9K6-fJax;mHxA#3TFmL)24TTH zP{EeUFr_QwCC~e`O_Ex#IoSuE);IA=)C}hr&rf!{gJzUbqS>-`Zv4RJXQ~v1-CdpQ zS}|hkXz#YthC(nc4Q3b1T}*|?&iVVwuv~Vw9vyS0+Kqhk0IPp%57jLe^R3UvIef;02j zJI4)smNhF;Vz<6#u7DKW_O2^T36HZ&bYE0~!3B^3f@tYJg^mMPz=a?xVzcN&b~h{s zpa9tIH{A%kH3sdeNZDdALF)_j`90T=LsAtAMc@9 zXs^y;p>3@hUm-)~>-`XjPvQ=W-CA#EX`dakZET~nBE)VLF;-eS0*ouZKKJ<52U09s zK2o%qc6ogEUG+vFIe_kuS-cb1gehIUlsDdim>H^knWad^vlD2jky-0Z+jtNP4WO45 z!YlMvmiL@M{}i|Ps>4!D;TcbBfjr+3^}WFg67Hv0;E1($RZry-M+lZs=9$ZK&gVvU z35F{m2SZb=4?kx6&zN?19tu)5vt0Qqo#t57U=@EP7?GqSq!2FKEM+qaXWTtbb%o?Y zOTWLRFz~U%)$nbr;|or2iIf2c!I2#{kRco{z3GBjJ7*NHGQUZ8TI%(J%3s(4m?xF< zmJZK7Dhdb4LF0MGJFz7NSgbp|89j8jv!<_B{w^3M8Q?#ev_QN1Qd^3DAu+5PV;Hv? zfINGg^84KqgaP9dX%$KI6XGv072=WNL0-?WjEeXvRwZ1GJK1hQuMU&P^I1=UKg>Saa0AvjDISEY<_V4@!Mc<;~1r08T##N04PsG z^l_m!_&xK1YTui3DCKJZ=aI|N|FJAn2&N@QBIdzb7+j&Hf$>~_I8{uKcx;ELb>1r~;oLeB$ zpK7HmO-HJkY`m0P+&7RJW2t=}OKnf@BoOUq9^UtN@iad7B93!&UjO$4RXHUayz8~8 z^3h|eC))*>xf9QQSlh-+2&qo|p*?Cc|Ic!zbD69o)djF+RQg@qqQvX|JrIfM_Gq;* zJ|M2ka;`60*D9y|>hYrg!bKy|Hc4{kf_Vc*LCe=XYb#fO_&;u7~#1rWmSwo zEbi&0C8Ra0dof)V95ao&neVMOjUhP%-aidhySom&w}6El#hkRozG;2 zSyY@fq;0TZMR0=uKRexe#;HePBjR>2Lj^;o37lX2#K22(*yz5nAAHGH(6^oOTH`e{K&aZ^!DN^x@E=Y4n0K zZ5T~&&%PN>7`ul88F%M6+h7>2+VG#Dts=NUFFo75L zzD_9NGn(&Si5$;kURiX-<-hB?p%ys-z`AJcu~1tJy5V-FPjZ0FLfd!IljMPp$gy&GOU zp+-uUXoV0Nme|1TJ;po)Ac3qmvzOb*-uyu5?bR5^c`8i7=KBley z#?Tus8i0X&3nvcz12e0foJ_f{1s^QpY0f1pcy6Sb9)9QaU!D%a#I<4k#Hi1C_)f|} z5LFH%+fV8~ej246`#wBJ+BLdBt{g)(>MysCh4Ucb{6TUd-OfOR9`}IbL4Ap0>(0P? zsI@;Ev|obM&7vP6=i>~-l2|#Em^ZWKJNa z+q@l}%=04TPmGb{*)kd@BB8b#^!VqLqxrwYD_?uEykV>wHT{>v8j^nN-ftdwKJid| zSLMkk%gsjxR^oRsCvPJKTIOXlI51KIXMA0>n7i0`qT4&_kch)g=!O^+97)F55-dvP zX-KyD?q&eq_P+g<*+ra6v(91tOH()udQq%&!B2B(+@$b`OmqDKYN7)qH-6>0FL9B9rb|BQkPr~8M` z0Enzv6N0(5Mg68>ck*OilM=hL4M!do@z6WFGH*_5>AX3@Wf|F%J3y+s^0$D@Pxi`t zBjWfPdfuhZ$Ohg^EEhiQ5slXou_ zRBEzDT>oQ<+m_bRNamj$VRLQ;SDs*QLPjLqh43b$rXgFNxAS_;K$%;4GErP^!^|Nl z(I%i_;UFonJ&3JbNLOPrz`q=ffm+srsrL$MZOX-%3Pn?{ppT>`bQ}7!bvYD%fE{)y zm?b?~sJiE(5I{i~#HtE3vd~PlU0|ZY{ zNrI)LqRRY_fW0|>l#7juRm*O_rfg3844+B7h}NY-d{xFpn$*cr9sA~7@jssLMsbbh zhhrj$(cKcJsLxJNZ^sH|H3Biv|N4A}A?pwzG=~e3p0Y-^kW1}jHg;{}7MK!L-xq}P zUlKIjCjER!#T@(~GaIrGrsRV@DHxne5oXW(EfCT{BZzp?)>PnRih@^Bwp>LO{OfhS zs^J5uc~i?r!}S)qH5=I=k^8gnMCVW^e-)h z0L!D9^8d2znT>Jx@zAT(PUJ<|nybf%B-P+rxVXvh-2Dn&rV1At)G0>Abi1I4kJ;w_ zkA&TqDw|CgQ@;j#AiL-S`8Pe-k}%ur&K-1DhzB}ERb%r>w-HKf;NB_?ISthj@9wm% zC#7K>#HyZY)_(IR7xIIcU}99yd-^Suj4j>CP}VrbLpY+OtrB_4PS8Nrkj~|RgMZ2C z-)iGx8A%N7W6n!(S|rGFf2Pl;FQiVf=;Eyec+FdL0GOL2y7j5j#(S!)Wb zbpOF8Lqz6|nV}ryPiZ;%uE9R5uLqtg=s!QiYV}T{&mA`9kS3!gK5#sJQ1$bf+FyzK z=BhBgYRo(DHS4rMwio@Dy?0f2>j;zJil3%YK&Io#&P^bR>cwHitNwpX4T9xRGk%Yy zc<^ORS&S*%f9~6er0=IA{mzopQMo2mz7$JOlh59QVU5aoQqz2Pi%%X$ene6z75<%$Z&ZCj7^H@Pxn}>-ZVYsMd&5-@O6AMW|*hYD&r|TZ+?#w-aL+4 z{|PM<+^2oFG{Ru6^>Wmfym2Ih*^w#XM}vgaSu4X_*hz*wBDTmBE45^E1#|k+m8RZF zaDB=t#;bBh$$7b}&mFdRwS<-Ht;{jz99+y9=b_9L`a`|a)uhm-+h$39Ip`9$yj*USl*nvQwd6slmUT zgFC#PsymzXu;yen?%?OliQMLmD>RFu)%G|e-jY(v+u*CW z?a&XGdY2Rm*qz)^6x(n*;U{xKgGH*mJF_3>;MQs0T|aD795vs+l&_>GxhUX>IcVg@ z90btP-c^0_I@Bh5I^(|&BiTxR9&jn-b)KP^aL56(Bm8L@BOAm5*ZH!|>%0;Haj$oT zNv@BpaI5;!1yft=PyEpvfOExDJ9>0iSeH|>j0RD=RCzHSao;Y(EASgjux|yAT#Knk zg=M$$kC3*mcQO!1KXlu;%x|uO#&6Sr zoJ+$BH0M-~^q;3t$TdET5};~HWy#JSoXF`KMoX*&ZJUIr${#$fcH$(A>@O15r8UIs zjF4=Z&jWMWI=bkvd=KdbsuC+l3h6`K8~uRY3_>(clHdPU$38*ivn6ph*lni9SZIy#ID{D0Ng}L3A?UuE)qS0=Y^oNxZ+}z z&eRYKYMn!pA)z7e5qSXvjwmwqeRpXAGaP@$o#GI<4Q|T**X_xOVdOn!DJkB)>#V$l zQALyu!xjY~dEXE3cU1UQw=7V>hvQmTv3mkrY(%9b?xpKtATC}GaQ|8PB%n93t{2$F zsr!3siz>bwrDN#U-cd}D!~nSo<;%^)NHNLK_7#w>wwZ;D))E-6p03+^-{m@}78eNZ zD`_E=rJ6y@Dora%N5==&f&ec^?85ImULpQ)>4xksLQbsaF4spXap*_RmhNWsBuChJ zf7o_?xRX6oMPK8ioZSjDTQH##WC4TY|82-K`6>&BQ_0YXf=}?%PbpE7=WJ{*vqGsQ z4?9NC9%wdJ#50gaKnJe~JpjUO=TiZ^8rZ#sz{5D#eCYQtctJYQ{P&@{JNr_>zz3ab z4ZQqdnX@ml`fvs#>vXK;np2yq&1Yx!)Djm}_uU@#d2WlAPdX5EQjj6Ow-}QFGLKk| zWNbqf)X@jVzDchWdDB*-m3`GT_S$W-%xuP0Wy^^+fDt6Zee-kEs-DmE%eLVF)}MzDTy0S0wsEeSs*Ug?ylOt-QBi3yZ?kYyBfY*YC&yGVqe&ejFv9liU$ znJyZyuy3D(Ekv-?T*PmoT^e#X9Zuxec?>@HY5muc4elJ;&_WI`8j%IZ>Q2F-8L9UN zz{Ro@m1FpTPC0RLde-qh_~A8JbtqCqZmhlO$(CO{6C-QxR=4+l!xl(cgieU&#!Q4V zsRF)6;W)9EemC7)AU#$Rw{;o=KY=X<(q1MPuLw)pT0sll-Vj7}?lATpOuj z&i!umeMmGqF#3=;&y`{0Cr`}n6l5woq_V&XV=F&IYET2y_tc_RND>L4x@Gti}OQhOLMko=@n2GINfgpYFm4NKV2);#Hh%dDTcn3`+um0 zFVUgK7OlHSN9rvfIxPQ|6=wNM8hDRzGGls=?KLd}#PuSsKkIqtG_~#@%7|-@WZCKU z*r-C~B{t!1%HYMhXU1MO4kqZqSo5vJb4jk*fj#j+*qj}1I^71&NT0$T5(tDG{ajoI ze=c%GPH)K6aagIn#ro(!SyWzZpz&76(_U_c!9kXF+UIoZ6rVaG%)p3`{5R%qO%}cn z{#lJlvzKt1_V_QrW}sHhl#W>hH*FjScgE;=04CTK?<^R&*14I=`;nG#thg1spIg!#od+44-3N%VR18{(q! zUgyVO$2D@D!J74jH*4cujABFKNGa0!Rlt0qvZ4n%oY_T_b{ngL|D%nZlSy$1ksxR8 z3nI}95$n|C2_)uH2?VoxVKh6~k9qBLSQJg~olMk%VI1ETBJD}>k1{OYn>n^_)TUBM zaQS$Qe@`|t6*>NneTV+jF}9YcEB6el)Z6yM8!6?2>TQ~Xo}FB&!14iAzE)|&Hyv-P znk%nJB~~FEa1_$SadPb>V_T8cDdsSe!jjfj7gIo-)7MPVWM4Ww)!Ww#^E+C-k(#IR z($6GVJNixQ6^^OKFzOUL@^z-ZnEI8Ue+)AU*VCIRUR{fF(xDw?cFgATFT;rV+2JFo zk?HNZtsPpV2^<+dpJV%w4kneu+Qhyi*F*cb9%<$ipB#~)6g!j|m$onGlAzg!^`fZ+ zv|#XMe2i%97f;Zo9HQJlt`(P(1vtHtB^=a@k~3-nzt%gefefQOS*c(48x|RxmHcwE zMw2HF$z&opYrK%vS*>ey9?@nwXa%?-F@1QH`Du)WrGc(mDd*}FAz7)?nU~-|l$L&2 zumTSRUjSc;+oc&Q>zyec#f<<`)zy|SRE(7LX4GT~Wn$BRg{WV%S5|)$fnKS6c>j-U zBB)1o8v|%YvGz%X%5-uj=4u@`lsLEPPDI$1AEOQk(^4DEkpFZFfWB+AcGs^y1Ajw$ zlgqYy$WYI9Wwa^cLRrTJEcv1=G+H2Ct8`>M#58?_ed5!6+C`hm7%^@ZT~(D(7iS-5 zM)?LOta_Q-0L>4y3MWiNfxDPqnt4t!kuDODSJx=Njf0gA`dbSSsemJ@tuAb!Tyc<^ zm3r#0GL!CU3tqr&r2vf(drn@C&lL;cFYT|T<`71=r$V}r-(+Qi(KeS3Z!t73m7xe^>PF0l|6?9oejv@@xkAj73oN_*6kJO9o z4i=oifufp48?ox-S-i0+v)Gj3uJg2>12fYKw^2d|kZ!RW@Ja)Jz0_Rn0>*D-PxvHn zaOo&0$&FA-$zWsIUm5yYP1!3A0Ql970Yu;>Sh*oNp0t91X7Npr#X+wl)&=xUV>;KR zEEu6+I%R~GFqz6htjDJ$LWd{F`rH-*!&1gj_oIcn zH!K@8t#h^BY8$aAVFUv`&gJ)lVBu9A6~>Xh#NeAzfc)wHL~k=eGrVOHWUXI!pr-L@ z{K&@+McWfSo-VQS!34LuiZ*fa0glTKSSB6` zyTwL5c^x&E=(E9iPkKj;UA!)f# zHoTWb((z~_!Ob*viGgKL&lLUX+x71CVBIC0%YpDiq!w z0&Fhe4@env{e17kPGGv1eWs9YR|ZL)^|#aafhnh5!v8n+N36L~Nf({}8b|gy9z(E_ z+rtyxrSNNC9s~rQm2LZy=g+N9_kwa$0>FDS*OqhM;R~L%vxQcHRpM}sng!BvK~PCS zH*v+~`#yX`Lm22RE+Rl$#{d0SHUa&jofu(8GEW;-u1z*H{sjs=yy*h)4Mu#%9$C>m z$gWHnb_DLdmWs+Vtz!BP8NDGdv?Gh1TJZiiRG@{PSR*XC^ejT=2_G|bz$^+WsJ;0F zr33%AK=`^=TQpK;b@^x^yA&AEGy3adTo9|BZ)}%)CV#;01hd%crM_2t#uawQy{m$OE$5yK1!i#HXX4}yvRu*-@m7RhYf~a2rq}n?VmZVN9^IMAn z6^^igXLFG{eG-CxXHpIlq_{E>^;M6Gs zx=H92V_3RyHh%>N$sEU-*WMCTK*j$S8V{KrTpx4lm?3BhYuEM)_FqFG*xHb2V2vSY zM1`U)J^?k+SJJh=l(aFD?*chIYV1bWAl%?OZu%whZ1B;e1%OjY26&`{j32xZ zB$3&X!Esn~C_I?KiN;2)xhlpK>mpW^?(n5dzXSj=S1k)&g1N%mGdbh?I6K2oQLOGedR!gtFZW=wAu7k0 zK;a9-9u++-3q$9I%+hGhfvEl8EQ|CZi|@NKgxmB%&vQq#NK4l>rJyw?rQE?7^U
B#ztaJh8AZysChr2_bepS;q^zCWkJ;)cDAt=_ zk-9Z-9>A#av1-x?6QNMlack|(@^2!T^lwH!1Oi5Vk%W2V>nB?(hZL@w@))RV5KCyE zu#?{#s{RSMc>>4JaKb8+4?NFS5eq6S|3wT%9nDeiD6r zMd>Etz%4H9->M^=J!=5VrG&d0F0TKh_k@+q^lF_qVY$%~DZVC5H#0bULHInYI$lT= zXhnae7f2*HGl0b_YE+yt!_eCi@PLI?efz&a-_9-67pg%Pt)!WM0`v^yUlhM1S_Psj zQB0iW*tS#2rTX+dYuZO;HT%H-=->liJQA<|pQ_f!Gfw>p+HD)B_pu!slUdqv{RYk} zxP;EfB*c5|h;QOwvRKXFWi*&5yA}Pt6R1~(ptC1zM^yx!WQ@jvLbrQBv*RORaoqf3 z71ADNGZu978+zV01cv?*uN@jeahh%Mk#?xjiy_0c$znL<_Sl=)39C18DSoV$-Uqq` z;0fRN;Yuoz=RSV<3FZ8viL6xLTqUHL1uhyi!tQbYvCuo^(0!mng#TQ|cMjo;2GD~&0-P1M(5iuABfeA1jA<**Ts4SZQ=;bDM~-+8M99_4O?TW zHsL|0p>6ARvZ}eI(Ny|GRhUZ)di-R<+TbP3cRgq?8gMQ@;xxf%c0n;c5s75s0LZcFJ4`!4)j9awH?plw*fjMLz$NLZyQm0aV}4R)3)Xk5=lXj)oPB&7N**CH1BRpOo|M{d;-NN zuf8J7*Vp93+dkM;eM3U&hw&T}4v@yofUP933o;T3d_lk|0$!@Us9^w<9z+*VVe_4W z8ff6U{2!_PFB7dj>UGje#}w-wNS-%_G>_M6RCU*7s-X|(Ru*-a=42~jX%FMA6}<0z0tz_n!IsOnTE`tx`_ty^EWar2*spofuii`0LEait!0U ztTgDQXKl$6q|~9#ocvl{6VPbm43a_e7dd18Wol>a`7nj zh0<{_uI9a=V9cZ)iH>GdQ!x`d{VS@1&a!o$s~>dv<5Y4O-|dhz-koDZ0O>g5j+5?I z`ZLVIlhYXA$Sulks@61w1!2<;laRt;A4lNkib--K2ZwU7iNk>)cFzd@RowHh#LBFPT)=z- z=N$*hYzjUKsfN2g7iZc@0HWx9?R6F;3%pgR3jQB?N5*_Jd(#DRm|z%k_j!}kfQz7+ z?$mb{rw(H>k@+Q-F4$smX-S@PuB#TEl=r_Gpkx)yj5UUvg5MY{n%`jDO` zT;bJoLDtQc;1YUIu&D)eqEPkL$JRo(IENrnz%A<)WOX-_CatvX==oVg{Fy*bSZu1{ zVr9u(Gyx30`Z0YV(whZ)i~X9Yc#KtL8#kaXxY9-rh~1UQTuj@Uuh8}hsBI~$(?Nc1 zkF?%^?h`=?aN*P@Mt#Sj<4~6(HbkTVW zi3u5F^eas<5pc-h=kgk++lPC#3x0f7%KuiYIc?vj=7F(eGQ(J0sQXC;=H+V~yp<*Y zqLC_4n>3D3)|x{bb&kgI3q-3lr(e?XxmJ#Tu`BmS&xGCx&SG~AhTgmm3h{@uy~%Jo z;0uy7rF4{h9i@Mc0{>fSS_6ZMO_@14eRfg97hZypHtoV`B5VpP=+n)@8nZDti6yRT zlr~%CMt4ll_dIr(z;uAMoqwW&y}yMu1$vXcAe;s0Rpw`LD96rg1D@*Lt%X_ulKzq^ z$qLo`b(=GkHsRkWsD~quEt3w<%%M(Y1zJ!o|Cri*swyjj!_w+tA?e*usdze+!pAFOsR0|c1W*Ptb~Y%UC_ zAZvgH7_gna9P*SIvtJ4k-OLMRa?J7y9N&!OrYBh?g1NH5y*5doY9%gJOkNn$XZ?jn zkJ$pPQu`@7-WC;9|MDK#Gnxz{UyO$e)f$EordLYE%Wr?P@Rb$Tq z8)PRfOsLj?JQXvA!7guNNd}&jKa?pJj*@8`7q@@S%fJ{CNk}7W`lSK9qqe9-2>=PI z2J(yC8GPKX)(Mo4a%gUr0$G}|LWq=bOV6{7#Y~X`@eN#CrJjY#b6BXkB>68%1btCA zIy|b`3J>i44pK8=%%yjSBTLR08KWh5f^rYFg4d{PmxI_&e^U_ymS`s;R1lv-%kprj zL+wh-wH73C4N+;6hpF@!Gc~ek4*h+)?%xGHV2QmmZEs5q76!m@_^uZC6oq~ihM|)G zATk+$E+rr^Rxo5;zFs`l?xtJ4GHL)` zenQdw)vrz@_)eHT^l{_>>dYZ$y31b>{xRLW)uV;JT7)M63EE;k2_S5}?F$v7pvaML z!pjSawR}{(wZ2DcKE~qM`n3#djcTup0D zXwgN-Pb2BH56qr<{a2-l1wLGu)FhP3} zyx*RAPh(!TTjgyQovHW3qZ^Lkca49NrL<9!vW;|HzLN!>LKSB@*azy(6v@4xw_rZ9 zHIMBwM6ULActeo|wc=ok%OZRIs$2M8mVe3>%D2S3>P^b0`a2L>{riw6%qbu=t08^D zlvNz`?~qKPDcOOA1-R8Wq!+nOlz>7gfc@kqt0rp(O*(8Fl=^Z%=>x7%Q$#)Dh!m$$ zW%L4`U;!X~q1ss}9-ABH9-w(5O#{!Djp(4Q1t`$;hhDxm$n>51&Wb_;+0yit5Gf$A z4YLL7C#1#4Y!Au!N3rvrLvG4^sE%$?KDGz7RftE>O!-!jrM)YI?HLZ+Ynoa?iNQ|0pWFhn1(geU z5VvkZMLM#ydMb)@NGK^bLm#ntovB=NXh}_bf&ji}MAcYyx=k%UeMT~glx*3{!4Jo% z`f#?CmH{s+r4g&yX$r-rDht=_7FnY6fMYSxfYkQd&j9kJh-6Mh$Z#jxl{Z@a$KC#! zwJ`4-He)2)u0PQH-SSEBUS>MSVTHdE)jx@U$vBA6fm%uH%k6)XQfZt5wPP}q5?OUp z?t!2$hm@=_Ib|*{|KS*0*jOoK9Lewq6Lvz2V@-w-!I>OD-aP=fEisZxI}?MSBho7# z%!i!K18%Vg2$MNb{KJwgZzUC3x3PDw?+EJ)_bFfg1CC#^X3JFUCD>6317YxixOej` z`yMA+&gx3r_pAx)c%PRUpvxAMLkv!)krj^+f2X7zZB8fX363JsLkHZgV zMpea&MR>`wP6=WJg>|f!p>1e^baa?E#hR2LDu*wd%_PY~GKtrioia`9oH`@lKTQi2q-x`J=EYeiO!4ws8 ze3}sz&N%y$l}z*PvgRd6PxX8%Kg7jE_IBTR5UZJLp0u!nwg8)-(jat|D>4T2E=hYb z+xr+%el=?CLx4sT3uex(-3(bL9HR_GfnooRa6Q&+y^{pDB&OzL*fFLuKQxHcPTvBw zHav3Ys8}Ij7?1Z18_*SeHCd z!nUjbJYq()6+En9zy#J^WV3trp~3K1USDMzNm*6X;k@Z43i3F2@4zL{RWi6!(leFA z61{4thj}@EFBy)@c(DGJdJcgbX<6%m%*#!kg?v^Gz|!UqXs z`W+~ScXD;v=Egf6fDOu%sxYDzk7CX;KuHm85k@sPTU3&f(|IQiHY;olAP&c%okpX6b6b@ z6j`~h>h3!?^eOJb&ZlHwD&`oeQX~|X3Z=+>b~Lj|m=b&$b5JFUSvaR4TVD@b_N~p( zNOk`#7MSTuZ9dM1|)ksR#c=K2vUWxK?`1_y7z$1EkU7cJfNicen zGiDboR=(`Tag$P|Mmn58+R>K~IKJFh7KrIpgXPu>iaP zDWmUsy?s7k)z|ZJi6=uzt#b$N|U_T7rzA2xwiax*sW4aUZ3}qZ5`z^XjX9 z!EmNZ&pe$ei(N!tq3x@HS>Ybx($9(mit0F3yKEO!>6)`gF*@6TDjKa*0$Ml=ps+I- zYr%fC2$vct1ASz-uMXR-3KNRwdQ*(Mqast7Z_r4ngu5>E^v<^~rZD;4+$=wRTzIa} z;9~J*Ix$!d?8r!t0%os@YM$h|V-Koy2Nc?FT?|K^fD-A}S6RB$Mh^0s?XA$8sS z2$%f9^FM44T~i|G1Uuybe7bTT`PjTRVOEI-f}&yO5|e$;?|2ioJ+a{ zMB2i3HA7Mx8YB8!W%bAnlJPk3c_5{XHjc`FZe0~hkVX+5r8ycC5SYOzkrGl2)Q zxdFe5#(A;}W%F8bsXiBzVH*byU1!mMWWN2rxCv z6aPyhrWwL1lTpM`)N>I%p3RPiSPtE8FUk^U@c9IVF$*!@t`eMzMb#mi9p3 zx;Yytqf-FdI%K36iaCL?=n4Pehyc%@ekJlA{BNY<2KOI!M=e91K9cu)qN;QcP5mQE zp^VqJn`jR`Sn@@D?9^F_80@L^#^C``N!?4SdB|N8ekC!k`|fGnI0Z}L;j;x z%D{DiE@F^AnuI2cd+V(c zozlT>)CmdGIyxxEV-BG+magW6308Ljw=(pESH8Q+-gr*JD8pWjg8q4Xy|&D+%52f~el~T(9>%yF=l^_s%=a-?X72=+5hcD&JAP{gaS>2{cJop&(H!rRqtj31 zYmlP)qzkfmL$KLNeWg_r^Qc=$yi;((egzWgCBkW9EFt|WF2%KIw{rCs1JCkC-)jr$ z6Y37!xQrN77ZK;v)5HjcI3y}WmKj+%ytEYy>n~1sq`~?oXL5TnBkJU+6aI(sgY&tP za(JgIU9P-^!F(3UI@%`&DzMdHTj9Y&07dIvrm34_kuH15} zEnA279l1~8Iv)mj?uvef$@B)O(6M*jy5dM4B;SJS=zTp8IKMSg2bOQ*5JN3u*^km= z^Y}DAlU@+pN3*irzT37F^4d>c76L)-cp)L+JSgNr}FZZBeSVL~Ehg0u0%d~n*-0_m+aH`9@_ z$XBdTA>0zV%T71Hf%UFB2|2ShUpG^XUd#mpbs2FD1K70fXY_F%l(8?*74naTRH;}7#11dCp`u;!~pmKT9AvkAXf?B`F$~w z=~oS^b0NYFq`s?=FL%1W_)a(yI^GFCs)YJijA$kr!9J1+yg)F~VOUI_s!19*eowD) zy8%h}gku+L*uF^^7x_jGAku3GeV-sq>rAuO4=N<#Yy&K7=g~}AS3*1M(smmIXSeVj zfO4_yEisFXyt(hcV!2?YHs=?zsf`Yp=Vr@bwGexbP8$A1#+FxNDL$UgZmT=%zqEt} z_dbD~BA+bO+PiG%5fw8Vo zZ0(BO9JTWrj}Y4J_uIJ;i?4jU7LRhPGb(l>p+tJfIV=SFTO9kizoAb@&KfFa#Qkk_ z#ZcelOA*a`Sr93?;RmMg#|IR&%W7DUhu1Zk*)uTu_AoF}lp2NLms=1buf|w{uWkB% z!4~$gsE02_owp&Ir{sshF-l@bR?jn-!+Zfl&mXzKD~@%%(}76|;V%qQF3oMh7HAVb zz7wOj4!um@PzZ{uuaWC9%T*wFW!d7HpB(^4K)AnDDqv7VqcZ zv}2BRV~(5PH(rtx@w0(~Z1um7RY444OBF( z8w})-Du{4s?0nK2I*XVU2oF1qb|PXjJu1pUCiZ5UmW3#X<|UDT1M~9U)#J4Gkr9jf z7BxsA%ZyZkh}o2FEG}CL$(dvy@!KE_r&Dy(XWy@ciSc?ZH&eUIzW%Xf{vJyh!|;Za z0qAin6T|RvisVbGV4UJd;n=e=su*f*AJ_kr-Spppy%*=d@2&j+0BY%YqkoIU?G->%1(t##1m-%5L zlnsdmR%WXNgoF(k7 z)&wtt4B3Feb!}i%)xibn0!WR*FQoFT5wkG>ZX;JWx&h#nvTy&oI5OXk-@e`2OwYVB zf8#Ja{HR~I8~QJ?Ng5C@HxtgSS6sg0ZQIM`R1UmTH}b>COyXS-4Pk**78;3u=UHn= zt$iFnoVN>1i$+5N@_7HmSM?3>)jcd8^#|kMli_vJOS1Ztjsy|M6*5!A@r;;bT;6xw zgzmmoz{)mEsEW7gKMXgiAp0h=6!Rzl% z&Io)ed{${CamysCB6e%@@quh6a*?{wVTja@C{9jor1m^_J7N+uV$Bu=Smwz4tT}ss zCK);$#`&^GgH&Sd-_^X4T$8?z8M0)DpTbZ0$xWrNdOCo-1L-k1Zs)dAjZ<7@o?47D zbc^EEV}4V;2R=_6Bg&)f9o}It9Sk)YGke(Z8Ne2f5Mv1OPn5QK$Soe%daW}ThP*>* z6VlKnE8VL`ekc$Vt~zQ5AJfHn@wo#ZW<+8f$jS`Oz%esZJ3VF*eAO>L03`i?S$zB* z_f|I^UELeN?{UF$F_4u68nQ0zI}P#;i=5L(P!MArARvM-1uJI9B@h;LAEQ;=iuUe| z=3t{EjnUh11tqQadE)%c z|4-i4eAL%;tUfCE<|)1#x7(c@1g4Q4@8tva_0nlK$kmf}@@j`pRT{z!QO~D_{zd@o zy0zQZ0w~jk+wCo#%Cceo@ zVsC?5V4W}XXFn)XF4Zk%}BRbpEjVwi_yZBJ%A zpvV&Txo|iDEkDdk_NsX`<+c0*t0z%DkovxO4A1%14oXn6L)?=;K9$wEx8aEgDK5uD z0ziWJ#-}M5KsAET7^vUyZOOd4CWx!g24j}9IzteTbU`A1E*ZqE@t+R^%$9*JdXU4p z&{gZQ<6W{E41;j<7zP}GiT@|P=~xcH|L_|;_x%whf0R6YB{B{W7aZnE7ZPJDiU&^< zWkpxOr(}<{1lwN$HcKfvNDGZ-#DC*a`%E)|p?Y2wtzyiBqI1CNN*gQ8%BIL&mwm=U zJZm6HimmdBIS~odPe)}&kDZWbhc&e-S7)Dr5E6=qYTkta4cUVAJ^d-KY6mX9+=)g* zW#mEXb1Yr}hektLC*tr5U%zTJUS)0SSX+XUS%4A$vsz;~>P%KV#Rv>YU0t~6L2*s4 z0l$~?t#0j;Ta4vAG7AVjMu^X{+~L}HJE`@&N`b!>!5QOLo~)FVSD^_DnmH22=pk&_k~)|rOvyI-*teDiF+ zVy1lc4N%vW35!NTCS5B%$jk1BAM}DR{PA5qd}+Qb!joWYRvFS^R3uL@zs0sDfv_L; z8%Q&fD)H6WHxL!)HVS1e$Wq2e5}xQi_5LipC_}oMZn($HC>OTl@U_dyqh`esL&K}g z)VQ7zZ+9J8OhiqplcdVIKP2Q*{8V8g`9<*3Sp94RA`}Y6lDk*LG3_|<4wNp^Sj(fJ zOWm3p=ClV5p&dYoJ;sAcv}?jmzdgRSBtFvz?!}-xrM%~Txa{&C;rs$NdChK4q;5Vx zVY-cA_P!ZQGisxU+{UJB&{7nETaGJ6yySf2(UqSMn=vzX5% zd*$nN7Cn%pC$AhZJ^`(e-A{oiOzqR%i<}ZlX#&r2dZZ@-!9qAlAk(iWy4R zDBKUB-67AGvOM|QJ1Q{Yb_KuUW{gdmM) zu*CWPyQ>EXpdgXb&YcAd@A*+OX|0RW{3*9mvus;b1F$RHQZv53whMvTDaxiUPVJ8c zxRb7q<{i^OvT(f4<;4(o>pEyF(H>;pn}S`K$a@scG{rLyLtsdIBY;){@@#OliWg>O#-Cb|&)4L{pN!{mun78L#+_~W(G}+D8)75;mqbEW0 zZ>aZkp=ve9|EF0}^m?`!;;1n^gcoi)n*v$n=1jI}Ink9|Y!MZnsl?Mn_;__|6d3h~ zUEADB9D(dTb49I#R!mc4-Lx(>&?w@|?PZW$Z6=Hi?cj+_T(l!32C7@?Z9nGmk*wZa zApX(rmib2GzIxPX7@E4!+zWkXtzL9r0To?GM%{E``>kB^?uir1X0arTqGFx#cf#*- zR9A1q5D*?*yUc1&~8`N|I-M9wGAN)qIV)ySnJBY%0@o$Sx`3B^i*o$mce^hhtsC@sNmw}pBZIrp@M>$DFFKM|>!0ur%#|1~9oR@a=P&az8=r?B*o&oLVycg_-U1#=# zxmaGP!*X)pCc~_Rzu;|?0h7u8&#ll zr`2a`F@TuCp!@@4^X5aJu@spc>s%=~hA@cIuGLG6><#m_5X(WmH-pUurOIdIVmrKt z6M&`bK34Op3BA5{fmYqGIA!#B-;;T17^Hc1tN_V1@caZ4a%9c|0idm(H8K}z@2z$< z-}PoJ``9})&)4N+F&~{$9SsChaBW-XckYUT&OZ?VAn%LO?o(iSO*sCwCd##sr8;v) zxJ(7VD=5AUEO1KPJISK=-bH5F00w|xj6S}$&abN9m+B{6-K>@AMGlwt8+15+tadzH z4y|BM;>)ohyA_X$&p@D^$?B*XFo>Uvo(G(zg40f729zQ$g_fh%M#%ATc9s7k**^|# z!E!F6P45}3FOpZWk}Z1Lba_WYL*6ff3wBYORfRSfxZ0zB0i(IBtUECeM#IbraW)TL zuXC?ulJ$|(sl!?oGXmed)8|<#U{n%02<6b8r=OY>47+6~+DI2U9*(Bi_JeFzE{wlK zoz+wqA$=@WoX~=&qbk|T#h9v!6x-od{!VF#Y0?YlXL-t)HG3UOGdw}N2l;42aa70u zCO6GzhF4Qt57Vah-gHP$N5SAbQc9BRsf!0S%QuMRIw-Nlo`?N-g84)sIvDB4OPuYi z0d!@++_dkErf&$s1yg1pmJHYusI+ex7(ldGfPC&uPS&GSQ?A_6JCwm=y|EKu%+aj~ z`z>(P-gz50B`63Qy%8~!QE_lYM91$_cuYed*G$iTAQW71yP7S%*}(jn6R_I!1#9jV zc$JI|EUA0~$IE-BwLsmf{*7!Xo4uekrMmTv6Du3aj*u)I}!n0yJGj z&a{N7Bowt*D`qj?mv+Eg*Br^jld2RPM|2jT8<^LnTUXE(mJsC-VVl3j<*~9A6Ae?; zZR--snXAGOCG_Z-PqNSZRng;Dl z;hEpY+#${n=tV<0WLSvnU6$86n%A^pp%+S!f%CTn+tq$yG3>o?)d@7S^fSShLJxgQw`U@|lw=@H3(~P6usqughqLc=@QHpa8 zT6juBjb|1y+$shgeZIwFC#Bi8cvW`N8o)eKmXA~+@=4U5`$_6~oL%O8qM`~Y)VAQW z`xEUw^6!g-eTYtp+Jqy+xoLxJKi|yT7ojH9?8sJUj}co5TP1pw)2~Q}L)jrJb4fwfWZ~x$sxgYL;G z1CrHZ_xDknC+zYoOauocCI36@0i!@Qg-@A(*UkDSTc*H<`vYx_qul*qWd`H@4_e_M z^*G&6Knk39UHt$bIniea0#FIMSI|w0P@EuIz{T(-Kk+@A$xBIf!P!t{M$z(^5*2^% z9h7-bUE25KP>|Y4?j6Va^R-!u0DQ6H{p-N02~Cqk_+;9px;-v`%8FQ68a;dn@=kT( zwdlwgx@!dev<7ka8w>|Ki2dFBQ%~j~2WNB@yRy2wy4xWCBD?B0uQ_5o6|K=#zL6KY zsZcckFY##BvUSA3i)cYr*aoS9!pWOVaZ5KM1%1G(Rh<`kP5E(81?Gi*jzWCnZkE<8YiE2A% z^4|D{>q0pYRoguRH(1+{+arN&M)FL=zuqFc*@)gjj-v(LBF1=4@@zP&qTo;Mw5BaV zVL#hxe~GgX-6TSZ?MLRFFj`YT^4VMY_S}!44LpE73gsROgTaPp=~F2cJYDF$yzCkW z%wivZv=y`e!%zOW`8mYykC}2#<*~Bz z4%VmuSREv(aZ8>hneIvDF=YS}%zS&RxMxPsTcpxJeb0N?x%PHmgfCa~yVrA{oVK~3vC z(T;drxSe_@eLe>g=$^$oT6j5N_F~pT3~wCGV&ihhaPFuPdd z85a`Qg+?`bDKlX`2#zO(-=;L_+NL+&_t{c@+eW5qUNo52QnAN_HYY+2SW3y-;xn&x zD)!vlHXF_GY4x=wZh&gc-WU)}ta#wSNXO`UOP)|w@G!onWxf+73 zexdO)6sTK#Y<-fF%7KqrCAdy%a}QO+2si@9r`J1wM96@Ohlq_@{_WOcTKoa;oZAD| z^QnH$nOW5YWYhsUK=dskUS!indT=vN=H6Fh<@W{ft6{3Chu=2$$H0TFQUW7dCn-xrTWa3Z%^~oW+B3C!c!Z~!i3ZQ@X`Gf zOS=V}wAeQ(7!B`Jm4Pu=*Lk$`mLfHHxFw1k^{^#YnK9leVM&bYkkXKXi#Oacl+8cT zQh+B8X^e-7ot8ItApOy3@jI_Bh41N_4D8q8xk#)B46x3N#M%c91hPheZu_5H}Htba)6~EATUgUZOe}Kn? zo{QDw&VJ)*z^OXErNG#T$hV%TDHm;J6$$1nvi5Z8^z+h%_H#FwQ^>EO!lIV?RTbnR zY)g(N|A%r1iYK~hp28}$An#JY0W_T(=G5Av5Y8yhC8SUysCLu`6Bao&g<*0Sdz)vx zuKdVYuffn}@TZ{KS$V%B4R&$VHS0Xd7h3|wUw4BvWH{^21R53*V|Hk;Fen0EZR{!H zJ)haT%x%KGYnpCTjG(-tfHWT8uzkNu#*NNxUXy7N(j7hH2*M&)ec%vU3WA!;a47Pp z62zeQy4rk+HSbBXe=&MbImA=uYIwd#*p{0YA}Hg{a*Eky+M+Bx>p8P8B1$qKY1k48 zUL1DFIZS3$*sCB7d$fJvxVTE!@}xG2@S?S5^djMPJnxp$`muJ7FrlJ4vxs50`D}sK z($uciw@BKY)6$=wae-@eP1Qo`7?aakyL zH~WXu0?B)j_$v$zr<)J)vlgdAOqI*T#-QJzOPzY4-O&abF%#9(r=DyI)Q@V$#`6<- zjHZQl_j&S@cpldp<@7hXeb-D*cW>~1X%FX=8jVOhBsY^^rq0_ihY89G^U?@VUup|u zJ$2UtDfBGD9ad4K3bmxL7YKQJ3lar%rIi)dZeSB)lZhR4wF)*vOJ-qI{#wN%lI%Ln z)LeFuQ(GyHy@B*w_~QnpW90wfoqerXjJHHGMG-5N;Jv&HEauGo4mMT@N7y*wLx*MT zd;q)2F-lAoEbeWgmU~7V+mt+)^2Q{x!xO(Y_Qd&tRnW*#em!;LKKto@aje4I(bn%T z!lhUvH#C0NvCMk>0kq8sLoj=;NyLL7ll@&VDtW0CQ(65}?9Bf$7770yBCh5-Qr*udi@HC5Q>+2h85+S=trfX)=CbJN0`_`2F-RJ2p`I zb!m@=5{7b$V~+knmI`^&&Xr0xhK?uC)YteT&~>w!dZwro-0<=^qAN*SlDt>n|Hjz*?^GujrIEAn z)4Da&`&-eo7q)}U0%YV2k^5k21}6M5@wt6KRl$o+ITQ9xDNX4O)ols{1G~vv&Ah^Q z6zD=3jUEK_e-C@y+pl;*&B%sF{Qx6k&ae+uaO`%PpccG zn*nLtMJP&jU4m=H2{3oQwg5<7PU<9~+4ACbA?oy;a}fXkYioAS8A24!48D+kr!t=Q zxo}sp3}#2rJdYpcSJi@I!*9@d`tzM8!~_2X*@Hiv7{2axe5fNfVU4uk`dt z@DzHth$dk}dGx#H9SOw}Z`>+#N#!oDAm^rG5Ir6o$6FY7*7X;e%O+9>oUhWnz-l#_ zw}uxSa6)B9y(Zuq9k;^Ju*#=SAg=&;q6$JX7KnU<&7|;z;UX)7&gR#1#{pnBt^W2oYPB}EAzu!nzXJlVD~90p|6eWYxo}$ z;j;|IYJsuL(aO#9Z;FWhsb~_V>Fa{fsm8w$q%(%KKi}8Cg7O(bya?n-fe+AFUGIpZ zI1iC})D}O!d>27$WGqI7mS?t4Yn$%&I$^QhhEi0pc*Vc*c8 z!*#BskP?=&PBt}fihd*6r+1jkr_CfDI zd#DvCo5Mp4($v|s**JLkNYmhz(4R!3^0g$Z1%;P8xgb3o2Yb~ShAFC(uSQVL9O z^BGYvKj3y@Y+p2b;#)&sIMDX9W608Ob`bLW$VpNCCot}6W4epS{CzK$ln+HZBW(Ez zu0^ZVqcBv0@9We^t8g{m_4^2iz$4Nv|6m1r)Vcxp6_rZ^8T_(K zDTNI7LhaXkMnrZBb|lnDxD%fsSc(;WMu2NnTKMb*`gs}|M-vb>;B4+69Bm(U{eYXN za73%KXxH2=lH5L&6uWWce2<$xjFF{`P=YzJyvnuJ3BBZ4&Ifi zFt+9EIRL9+lR<<7Kb!gIyC)qd~NmDJbwHr(Y&Y|ZoShAdd~qW{VHCHlk*q;TEzhro$|Aq3%>f^9IO1? zXjysH5H(<$cwB1DbY#}j`Zi;Q7mFWfD?Ts2u$s?)QOlCo`E*&Q3Bm^mS6e;#4=~e> z{QaR+yS<*nMz14-gSj9jRJ)>Uf$OuZ0PNL-8aCo;U2`2F2?8{0z{6^+f9JaxF6K3& zX<(jd0wn|SHqn9#cRw>^o3Xmgx0IiXxk~1&5DK6~>n49t$)vk8nQvV&g=TfG{BVTp zkt$@z&^b}n_t!wwH%`+$n~+OwbYT_VUn(wZmFJwQHKqjqprKNlP!wz4AQJmSURsAcCnx5j6LSDk`ud&P5I7om0S46iDM9a^+(Tx;DfF#+a~yqz3o0eS5c zZxIKtKOFTTU;#|lf8DIp07$vL@PS-1$O=l8Ps6P;hpbg5g2-QGXXT?~9FxgC#@G8r z+R<>&R+e_C_y$myik;f3P~0|4OZ(B6p@j|_l{*m^qv!-jOfv1m3Jf9pJ*XwNlIbTQ zZM(fKh`z6@$^4`cdT^fST%5iH2bh4X9oK-+Kut`cm2TRIjB?3-+8|c8kk<{ZB5L#YW6kYsfo6+ubiBqVQ8^ zyiSTj=#9mQbWH7CGfJ0(6y2*dKsnsc{HY@@WBamer8S~d^ZeI#kR^lRuA;y1nM`{; zCbkBHQ~(I-%A1d)TGa%U?Rdpj+vDH5fsrivQG5O-oqR)hHPLKfs(*ExYHjQ6!qIp< z%cmD4N6Z4{AQX!i@LqM{?))wH6iX#t8rt}3tvUU@YMnZVDBOdgG+usI8agk~f+^)4 z`lP@Gtvgh?sTW5N2rB*qH<8I4!BTMvI zm_Yv(ET6Si73j{lvx!S3!Ul{c4__>z-T+cZHZizMUgt6rJzef^@(r>xT0VF-QLueQ z{YJ`c2fN#!5c0&bJO>Aek?}4cXhdNATzY^X_c1=@0_C&}jAkq|NY%&b30l6(l|5l< ze@HpAoY>7dt?0ykQ$%23;ddnIMNW^m4##Ua@=Zthr>stKu1oMH7POGil&3A*BxL7x zA33r5-$n>g45$m?RYl1CyaH6-ed7*8^BUFgoSm)+p61>eQvDQo2O@J$m^nV=JnBlI2AC%fi8%w3~cW7S_tX5giQ#@^JP<9AV{zhhx-T7<1UAXJqdH z)!!e*$pZ29J4bWlJDTl>dDt3WOi#!hc{{||x=yk}89-wF`D`NxmNRJ^#}D_I8cl^K zI|G$my|f)Ipz8-IRDTI7WCbmqOl6EUw2H%2s6_UmFZIp{>a|d^qUrVM4}}Ro{I)gl z3)t7rBCtmp1F21ZrMlXEMuJvYiE&_%#HeXl=ro*$^A}fH(^IIV5#C12c6nPh__Vsf z1~V#Obvjq$79WP0IGy%4`9;#+j&t1fRxcp5iB5^A>^=T%+)xQ!hiH_`<5E9JM*paQ z1+1m7;b~@z;&_ynb=V{KwxEZy=5+f+Oz)MyX9^zDNtsO`YS{+!WwVbzZuZfsNf?uH zSbKf9J+;)r8f!73(lRFAnOC%cU>XF?Fb8#6vXjN-$b0Qv5FcQeY;x|Gcyctz^;a{t&T%xl4j)i6iF) zg!bAdpg>@QoUhF$C#BI{c}YSJ8vr5_wjmnVQ#kfGKCJVqfx0TW;m#8UAjPBA!MA#& z3F9I7d?F5H#x5cXPyEcUzo$}2mYN45b&q>^qT-V3Qq_(|A-#T@8lC-NP-XG%@*9bk zhcCOyiFy?V=e8ynBPeQ^+WnE`*3tYX*`BeCLf0GGL}0ewD7GtMeL3_zbi6u ze8S=2zPKMk80>+y&cepohtt05AjP^|N|B)V1_gEpKtoP1IeL{lAo>hT`Md=7CIU}w zLweL0dk0N<1LhgpQ|}4<*B!aHUt&h1M4;2R6I+V%E#yerbxkNDUu9y{_$!E?c%GlO z{1Brj<1wqwkGP@p-80kHy_W0r))zW%iJ9jc&s0g1TM9(PFZ7x*28hAg0e_f!nQasf zxJ7WF?e)r#=V}p;+$FHbOD9wbQs`;*vNS&KS^=H$<--npiKzu9a)nXxfkdEdedK?5 zgSVHegZe6`jwHPnfgzq*X5L5gn(Bv4gxe>i_bHJ_l&Dlcb z=$~t02zcZcao#gbPUGF{8Mu)jAb!mT`Ewu`;*RD@(6l)uja)CP`{5^r*xmP_$rb-u z(uC`2%~@w)TAE-9vG5ha{X=TNX$p_`4w*N#vY`4eDOj`)G^fw9W`6GIKMMX5LX zwC%6Z_kn6V&5%icJSwv)btW|X5-Ek-y|Avj*7Bf_z)qdykGY3R51GNU(`%Hm&DSWO za5_A4j?$|ORC$^8R`WwX8vqAjPk+0%cVXSXmcO7uh#eKE%+6D-6*g_RH+WShA0 z+#8V13&Q6)SJ^o&w<8Lz?H3B3P74uDI$egM&BVAQ?abPrXTrOb$H&wQB1QKD+pASh z>xBROLFFtJQJKhanuKh(+r`z{?Nn*86I{bM_^^p+OcZdSZ=nv2Zpg;IpY4f~w7bZ! zah$bdpEHeg0Oc%aeoMWc+?nlcXQ&ao`J!*r+8VS}yX6>^~ltnQ+?qV4gMb5DQ zdu|xNNb0u{OqYnsPC)412qViKb@wD06MOIcL*GJ*j%3*Rs>GfAXzWN)LNxnv9nsO+ zu-clgo^@GN;drm$9o(2pYSXxNZ;e(;fm;>t7&L-ec0Z=n-x(JcHm2>^eG^Ip-gk(k zJ755|_uh6^3Ba|i!bXO1=Rg`sVNZb!4ElG`fwjO!SIQSobGm{cdC-3m{NTt&mna(o zqyuxTksa@fs;0`BeCGbjm|W#I>xPF8&aNjd$o7eFYmtI9QRg@1K%?7wy-P)35qiM) zjkwRl30eHUsZ{*!ZRKSO=4D|Xi=7R)3tlmuIgTU#KjzjZsTk+U!}S=INw~2|?Xgga zX>G{NqQjL)rcpdp$OQF^S<>HMoGy$q^t$cs$IW{jJxU*EgrPLVh&n=Aws?QoRV^(4 zH|G9&G|(1PMgfS%4pJ>z-2*VNYS&3%(mh%7b%?eLX#+1c;xNIaOMPiow(~||XjjVV z;snq}%%Pa>Pz6IPgWd~axA1a*E3hXjgsEe2iOAw=SKAixzp(!1H*nQ22huE>RlG%* zv1R&r*%GNTRRie{lAESem^e6sCv5LpvVinBZ({g8S)rjDtLCB3szdC1Bc=f~MTXzS zC0v+)u9q?e&W}{3XURrM2f(>FIvybP%}2&74Y5|^0I=<+`X!?A%oOIiwlZL0=kX6L zCtN)S2GwCw$qTRa$9B64sDiv z^WMn^1Wh>>;HCZ{5h@tW8|c!leCWh=<~nLV#cTGBsidPYl1pmP{zmYd#|@Z=DvH6W z)}4xrrqknpip*o~seu)Pcf}^uXh;~S^RW?#MQV3!qa%NPI%75 zbCk`IMO1J#ljjMY0*FW*yhC_|nM-r~Y!-L{0jQxOQo*k-sz`>^?H=hjY+JEcc(Ha7 zzuiI54l7OV(pXm)G+l!~+Nb-Q_limwU$dFeKAEEEP(r%`J=ET?0-&EDbOuw-1DNj;6ya7YP z_7V1)i=i8DG)_)@Y<`+0!L3E}4;-52xu#|?T#~rLL9qLyBGi5IND8l#8hmM{0p6F9 z0&+m)v`arROAN@bqg10{_fjTq7phuXuMpaG(47}p8nc2l+57r!C{>ULT%h*;*UQyb z#nfRsLI4~m@ZFv8uoapQ6whe2ZQ#a@wH>s;nP$l0@$zh!_F(oIIKuU-LIg*uz&5Tb zjK9xq7Y+yW%@as`U9M9oFVu2P_Lt#3zQ%8bjvOl=fC>02k2uv{rAqgCoW0WM4Ci+u z+qnpSyOu0YG={hht4(Wn9oV8~rt%mWfk~7;0#LQTBtsZa$oM?!WvCN!%*B=>H*d(W=xF)sOSi!3K>yUt{7!B) zzFlqE6GW8uFO|x%ql3%_(oOZ@iYU-Ii=&e&>z|p3mfzrR!@;HY)&ZJ0VFO3X|BCe=3C zLl8_eY^wh3*ib<9D~2Zqs)TOvH*dwF)hdBg=|Ovkb5v;OEF`Ftz&uJDK?oW~K`EZC zfuAi{(EN>0-JhGn`o_?KpJEpfGG=>@k2A^YryuzZ|9d=75ffa?=xT+H!|WRMCZM+U7{_ zwJ~QS0z3Zg%wH|ku}$;u%*h;-`;PTeXim>-n)8>%>3iB{s14&!G_RZB2#6tv4f z(Y}!_#+_>`Wd3&bO*r;NDC59c9(0`q=*<~dQj&ClP~!fFELZ%WfJCSb#K`z;yE_ia z{o6>MJPKHbTj!Crwpjn=f28wnEXAB=WkS2kw1kA*&%>y(ZisGGa$i}fs8Q)Y!!qc# zOyfP74{r;&MEH4@A+YNSm6XRSgwA+HRN?5*AH0)}ZQ3-!gYju0u`aOUcOsL?0YYW7 z+o3+bw_!@A+!fJtb(A{*fgmf>m=oAL*U1<1HsO>0{xs_WRUQhs(^d`>xw8?EjM0B( z%xv!uYTIQcO0cvqkUC!@AZBobkK%m5oSoRLGELhV*l9*GKVHaY}1R zMS)4-6---WxzQ?zt4`4NLJCS?FW9YqJEqPT54@dh=Op_kWFh^acF)7eObc$1^ zlMbPqwOpAt8kcN>8m+Sl+Uq*{)B^fwBRD*FjY3{1 zaFbl?M|FKFF@!v8e&@6($pV#>xVduOneY}_AGlciSL;xxo?Y`RF2;ZD9qUjvG6k;{gf!X){2CC&w(sHz&wLw>?&^?Tr4)0ep0o@Fnz=d0559|G07E)_F@tppoh}Ti6=DTT%_eH}MeHh0@w%r~cXUs@)@@_gpIS zv0f+4DGMz$;Pt%%Mqg~dyc36g;f(r~Ix}x?f|t#BQL1sQgx4dqr60fB=pc;~3YZ9k zEs6amT~m}Oz_+;TOckHOq4>$cAU9-S0URdY`0%l=u3Ah|IrKJ&tjxY{pl5&A9#jQX z9>LO9pTutT*hZW3Q}Sq*G;*ybp`hf^(qg^Ku7SZvhc81#VKvq8VU`5Ph(jZYpgs_p zJz;z3^4z~VpPqKJN6SCWAm-LPO<&xNV|d&HY=a4Xc1T8vkj-t~7Ta1|tmTT7Q^u54 z?%i;lJ+-FV7)?tVX0&7?5k$KJ{j@G8AUbmBg&d+Npb_z?$9)3>xuKK)Snv?|*%JkozO#hFj-k z@NdN{t}8yKP(4_rj*b;T?RYW$RsBd6C1Mxfdz1ICc;ZXYQd1S1OO*l|CC`KZaWaTA z*Zmg(fT~AH1y%5y4#dRn9uysjF6ET;q&GcnoLB}>1I&H~@BeF%Qp(ZBMs#r`JO>r- zZ#3QB@b*D1#{uorp8&V^cwlgos+ZXYJ54T#$>sZe-eUM@(Yb)Fs`IS4@!N z`=_Pk-p5>eaRCPFZx#3vy4~&D0S*dbbEDVD?v9n0z^t4DOtTaaE)l$FTIS!YLDIW< zxGO+RiWX%0g~7ha6UEmL%EuBzYoiGKNA?|16wl8+0B@8=Skl*c<49|O0lw72C(X`7 z_l`*2>C-8!kxR|iZJ4GVWkpF|zaziSfp*dPmcCHGbqj>wcnHlU7b*}+!X)$|Y3sVk zyoVhM5(Jge>4p3_yim-}6=r`GzK>@SqoXKXPAe5B+OtM%^^%}zLb87bdq5TZlWHF5 zZVD7XH7?`i%frrvxV|T(TAQVblWuH#;2~qwxGq-r@L!KRbT^QjRo_OKEx5{BO`egr z4J4$E4rEd8vlfr=Rs2*7DKGSn6iog)Z`rm{SBbhrZ=madWL!p1>htGz(d8CnvTE(aHL#dNNTErek&T{D3M zOB0SHgcuGdP`!(rbULsrbGf4ffU^dNk2Y=Oz?^;}7cApcIzQWEW$(3QWUZSRUYlE? zdkVS=s}!6p&X^SNnbt?z@+-2Mip=r^aIYECDFt@aqkiaWFfU>nTKtjnlP2i~!```m zw6zq{6R)Ac4apnN{R<~4-t~bEXTJIUOV1zS{kyyQ-Peq6S@ZY)l6?c#S9Xw`6~C+_ zpFI~YQLcC{J2mdzdkVc{o>%MwMV9jfGIl9X zjBM72K)_Lqf3DXKxrxa`)ClE6GI~5KDbT|7?)_y+L!dl0UM%>|FpM;9N0MWAuC=I*)B(hH-8kmdowC}(`xhY*fd_rFDcg3~tA!D{Pt;mU$bv@6Uq z&GyVXY)2nY3eh`-%bb-zX`&%k)Ke8+kv zvE8DuJrd~B8`3aAHXb!?P zuj=`R8L#KLxJ^mC)KlPBhhe^)65hs)^68LmhQDNEFh>eP=(9j{B5dSz(@)V;N) zyVhP4yG_7A4Kn?&4NidZVE43#FKRMHC0*>sTnBA|a%l2MX}4*)e)lpxWmRZIHv{>1?2>f-|lj zUc{N(qLcNbRTQYqC!XT}v#h#|Aq06y5lT}|5j+VA<~TL8iQ0i5-;kzb5aUacSQ z85w^AT}v5#spZL5fErR@sj=TjQvC2l-qa0%y0sut_9BflxVCaq_`>dM(Q7U4d@sVe zYK($`SA7Ub-7l3aSZ%F~M+R{MgFc7NVTBsEoW69@}C6Aq_~{jG1`bc7s1M z*v?Q-+2--Mu!Ca&gg9+(VYD0I&K#~6NSUKMEmvgx!lJKc>mk@io+S8%89vKyp3DD}ZQ<}nTU-J8NVViOZoSWYd1$jOJs}s1mx5IiKZ=I*& z9E9}9Hp*0_-QN^i>@IO#2eqt}xfe?<_4l~?khh<~7e0P$M5um$`>HW0-W#R)k^CUB zgL&BHKsHOEvv6RMs|RlHw>IeciJDLA-&pZg-vhz;lFVOs1>30ExIn^KXofWRiJsdP z{HJ{rK1jf+?Iu^E-HTpwVhftTLVdx4VveyO!uvG~3X%b7J$(1O65%SU_j}BYklBPf zN-NqgM1xK<&c5CTz?|1Hklg8kUS|qV8o$jE9??qyaARdsUuASq$sfPZWFd?^L@l7- z+;81bP#n?O#f>s-P>Qd4VsZ+P!}WmU>uYU+i!@K=cGAs`n!?aOMrsR0^8{j6h-9yy z)iO7|WInzXhBpr>_5bB_i_%l26VO-&f8UbJ?qn^xw^xiw_fWMgb2AfZJXC*Csq}vw zw7+_{=$|uPFC9e=a6pk!Z=d)d>-{irR45Q_)KguM9q6-Q{p2wu_FBtWYqdx&0I+0j zIw4>ty#E_k08Fxvz(;F`tW5BWNjZ0c&}4XL9lj{gr-tyap1b=q2%~kp4kUG|Lp9?E zJoJ~b@xD}2+V%rtGV%kPLEC5D>k}7vQclx&o<4Gq*2E2bYU!xUpCujUJT)v1>n*f8 zJvGhfKsyB^VhY9tU%!?1Ze{Pa(wu}TbUKnkyB=51oE3`^ToeE=)x;VIZ(orHp39F% z-E=q4-GykE%Ak5Sp3B1wQ;vId!rLm~3(^|OKvJR$+b5AWY;i96$eVGPb8A2Q$H{!v zx9aKF>o**;@(X3hUQDf)q*)rm>D>|mwBt3x3Y`l5BdjIr<7*`J5$_uJD!`7eprs|AG!v=Pc`Ei85bg|k=>3W~-UewMAGWMI>$yvWC`b0m=)*qFe?nO?QFPdOS zkJ(9{=mA}7n%RxUfnq)i8_k4BRno#m>deX{vadE)RJ ztP&t>CfL3Tg~%2BB>y;Ul*8jR2=j4SU{kOqtXp+Iu%AIA-hSux zEQndyGDe=-;^ZlFhtMCMrI#mdgIT^Xe=y&H~sxH06tr=PBz9Ni`3{quyo| zO3`lQpMn!G{ub$vM(0IlH-I~auFT9CP)KuDmG&bA%^*^FWoY57jLsW2 zj50DE4ZoGl+i*r{4N;)LIP(hGpTss9XC+O`UQNf4mvzR^3jy?dGd9iurm2i}=9%{x-{n9@)4I{BA6FZeAu3!1~B1=oyZ@rM< zD^u=sfopj5m(%2BNctgLdTR0ysZY{bNqRAW+K<+mL3gRZe!vLEWYmg;_y9_{@fiT5 z&v{=8TOIBAWZH7MoOsjd%sIG*-Xm?dFHu-HsV7o7+J8sO=_-Yst#2q+fAZ~wc>O%u zdn9xzsv<&t4bwjj8~o(gZG5&>a=b~5s;nMM3`FuT?@PcitY6<(<1io^Fe_)bQ$M?Q zTB;(7sOuB5%WAAF-k*p$$`$T1!{EAr2gq>RJYOmy%VZ~`hvhM+9)=dJq>Fk@e4D!Y zy}dEV-ywLy7RM?%ntv<^I}0rG2w{pziqU$>$7(&Rp}a$(@L3l`1MtT!On`lf8FqW& z$NepdS$@9d08|MppUESGR8qE>u=|kCl66qJ@r2aj|Lj*zY3Jh{X+XoNsUKo)DF6$? z1Dq!A%YP0NaUCM_FYujnG74qr?QDI>is!p0pj1xsD*x;klohxxGmV9UnR%_#Y{t?3 z22X{I5^bj1VSr#jazgDkIXr<|Fko4xfMIgCsSE+@qY+@qLlFi0p22kX^!2SAZ#J*5 zr1)T=I28VRoljWZ_Arit?E(bz(?O~BZM-NF1q?OexFNKu00c9vF_4mcll*;}VOS$E zi?8ZdB%~ORs>dzmKHeMp3clpxL@H*#rgPYFX6dz1x&LrDy>-V@lgL%{LAvR)+AH|J z_1(>8D6jXWnYYk^h3wRmMvZ(9)l{OaJ(&l@q#5s8E}v1Aq?6{8@oJqM!kf|`>?lVc z<5avx1=Mv#dIbbI%~KNgnG*tU118(UzIRzTua3XH`0Drs%+=Si`O5=YKSRS12 z>aP}M7^bJypOiW+IPi_BvSnp0xy2jZkWOJc>ARGV5!cipK zXnR+8)_^7p&GH$GN3JNeYzChImA022CML_V?->^^I9Juzp8YiFKe=&s0nw$Re+bwCzt>@>~qPp2Vy0S;94StpgVI&Q*LVe}P zwQtK?5=Z_)HMAI%CLSk^`I=|_2=G5{p3AkXm7Z-D5hIrzs^BS#w0qM@31tB5_PE(n*Ea-IlVyEDm*o;_s-WSqPt41t zaQt^h&xNjra~E^MCn)S{P@)rN|4lSB@WGdflR5Tb3W0H*`F^cQQ?F&@q1vyGbq5AN zjgxM3R@Kb8L|ee3a@URkH?2uvIrN;DnOKrzs6JxAhTVTjOxtL(JlNx8P2+Mv2&AWkyN*Yu{cn+dN=q@T27R5O(YIH!9fv_Sp?-va^ zbrUIMsesz>YLh6jTj1 z>KKf+WoQwNgm*cS4yWdhbIgypa$V4cfq28@4~$Db&m_;>!au$&y7_cphLnAm_ImZR z9|3(qBS zg6LPWb=I6mS&)cHLu{z0U&e^T(&1jY*gAdmSZf%m!BhEG5K;u?7GZ3npJ3KYl)Aj^ z1)ffJ>lQ-pti6PssQZ`1oP+i`NgFwNQjxIzjoI(eK=KtNvaLDfpjS+9vGCH~9Dp&y z0>GiLZ$~>V`<4+Ct?;iK8;~AjP+x#w9LUwqL5GMfoAvl6=#` z3>kR+&8q~X%C97&Uy-}Y5SuePFP z)MACITC5zmIdShCyk3IkMA_J4h9|kPuHIix&<-h=oJhf)LC_mWA5z_0z6M;lEP(W1 z#%jl=!Z$o_iEEw{N6#};{IJ1ph!iWd2GF3cs#$`UN!7sc$^C^QMH6tZ6+|B9Rm5M` zZ6eBYstlEA2SAE{ld`(T4$n^G+M=F*ADjS9kBa%nWfaekDFO#j)dNxODZ|je!hkWE z2x?5)!lE2$YK8OiJj0u7w+SKLVv!3yyXfkp=#*J1T>Fb*=CYcev+8m?Lge7=O^N zFe;oQr`!WD@1$W|2Vfo&lylY13c*u=i6+8`B?OxHv8mY$;MfPsD~K=R($M}J>0=Fn z3P@i`IVh37@RfFSiwTiw(EF(^n-5NeF`}5PpH>*H+;%~8d4xxW3y!*t2Wd5io*LxI3MvQsd1F}^`U)8NB&^e(Q6s&3}wshX=oZ+LUvv{iBu1Swvi^!FWe z6)q&9_Lt)aV5@NoWcPBkirdvx?N=y}cZLqg7)H(qG%^9_a~)Vv z>798d4Gb-AE-V>$-052g$P3 zbEf&?cuCqBzt0Im2=S=%7jU_MnvGS@5Jamv(hUYECup&!OI10zguy|LqsRhz3CFtk zgn3;Z_xzW#Bg@Dpa9!=!FWyUV+QFjyyK?yAT+Gs@nLxBusNgJWh-78)Pc0Q^K%x z>{=l`0(rLrMWNDgq*B(YLW4nH{W%8jH&YYr}_npJ51USe6W>u*guc(fSn#OOoy@zLrMO z7Pf%wop*LlilkXJd+ZOA<`;!R$`Nuf!nnRU1w@2t`Wj#=Zzzp>1V;k8sd%(_IY3lv zZ%pHTPaU1A(zuGZq#-OB&5DlhL2S>@7Ow)@P;-5XqtwHV^&lUPtGY7nhg%mwIO<)A z8(E{z-!7-?N^@7KW39I~MBT7mTg~~Nc@vs2x92i#&?Z;2-rkCjPb@F{^@@s>Y5gqM zs<2cuIg_cvKn;cpno3UtPP@ndJEq-=gbkCEI@Pcu)|#r_`+mui;6+$g<}we43C~NJ1Z-6yDIA zabSB6;oDtU3C{Vxms%hC2nc;y2$wvYwT>oDLKG%a-YP7pQ^+cA0ruY3&&#lUX6&5Y z)8coGMgh2BOHr+@`LTow0U_0=^v4;94qByTitgqw}Y+|TGqE?U64 z0He|}zjIleg#r`xiqWy9)R$~i3l5dO!&Lq*Hh!*paQTSKSg(Tz3Z#u<%F;>}mit%L zL%2q4BKTYCp%_w*KBp18f(S0z2TSF`^GR493hCzyoQX?7uILTMc4C~ zPlq`QZd|}_WVKw?oh4lnvT>a!Pt`>ef(1sP@LlXlLia36btiv>(u-JQa*`p%p||8w zNF16Vu^Zz597|XzS~E2Ff}rX4y+fXZyc;IYtqukL*8AK)?AX^E_I#!)%yVC#i6yj7 zqxTn|PlZ}XjspbJ7)9}&%mZm5cZ*niVD^G{U|Ew(moOItnk2bZX^_1zkd}}SXaW$@ z?gmXkhtPEo2|WElT>kPAHB6%QMY7sltt-DbK!7ZRHMIu5V)Bgfx?#|bUi^D*ge7nZ zAYS6Gqb2k??=nBjFOpBN*rJuhP{guOD$Pm~63&WScoyk(_6cM}`%E7f7%@??!r1Yy z2b{o=`mi2lV%q=0VlY?!cNp9r07}8ywEQJGk8Vz1z}m>jE`8$J1avjP@`~9jdw+u( zJxPLKO{By2Htt^)Z`E6h{4ozqe)llBxq4lm0P6&yCj1Zj534G?{fn#dC$#6V@QS4x zA*UQ^!X?{G7@<8CSF_G4Oj~H;y<)<6%qRV~f)N0!`K}}@1!-|RbmEd!QzNLodW${U zWm(Doos`w&Mj`E@B9M5u+eD2^r{QY74%L6llEuC=ffCUf1#6S|4gwYJdA%t+q|-k= zh}?tUjv?qDrss8DjaOr^f|&|WhwNo^COcRE(6^)@^?FWST< z)2DO=Bv$61=`Qyi1p|tZtn|D54-yd2Va4tZ=PaEJBKA=w}?Mf1_eNGYpxMTX)qpiuV}r? zZuCA6^=FxOvK4NQ@&7q#xmCy_%4SBsy*aBe-}t4r$C_rqhD8qu$qc**`?t zhT}f%o-l=QC#1ktmpsybA?=mYag`0+Dw^9L#YiijAU_P=^J=`3VJJNY?>a!#gBn`D zvFQ4&{!lC5jQ5#$zu%&SZYAu)oW>Hmt!+LF_69M}4UUYNrfXy=9NWhxQ}ioFBuEi~ z5W;`Ugq08DQF1( zV)6%V<(Y*1FY*&1=!P@;&?PN`qNyEQgEnRNDI zh?*&!Q zGkj7UZ_@i&)L{64BFnJYKkyw~oR)3EH!BOUqfKA&P2J9iM z-m4dE4VaXfV!1?Hmd2b4BA|dlGPCeGE||klsHsO<2Zcu)tsA!sE{ITaIuQ0`u285( zz!pe(huKuU`9XgXGLprQ9TkgLk_aQwC6(Ui8_cDB?0#m8c%G-#Qi1rihxLk&D1!^d z`>fafJ?%yU&nH1Wy{}aJSubb){UGN*FIFWd;b;Z|Wb^e)KQ~mMR*MxRo$;T{?89Z= zFb3bvlfCAH&A+YbBPo<-!_4aREmQqVAAb4+;aH?=XcV{#_EN$TpUk=%EB31Y z6&RpOvLheoVKa9*YJNT(49&8Z0IKZ&3xjA1JT`fl^X4ukX&x;DtP~hJ5^XVP(nc6B z4e3K_D(ou8HF8ZW2a&BoV2a^Cy3Xc@H*fuvup`DePcrcnXd?-(KB~uui7XYzxkcd2 zEq27g)sul!D>cYK6g924$&x7ejr*t=@G&_(fW~QB{ z|B?~Gwt~=xB_DH5*7cOT&~ZKPZN7S=1Lh*ZBMAfN53*NDrY#~l+>RmfyVA}2MuS)1 zj1YE)#5(5E6vbTP*N|n5BqJZ%TbdU&PutvyT|#|;{Muz9w=s$#vO?GCuieaxPupey+hI-6Op{z-4&+KaQJspcl#VB<&M;J(>_t{NZqBF`NlY)$MjG$+O<&CEv>i_=R_ z1}{J6D!wu9Qx00=cy?!0GC#0m@msXO_6ny2oV5h>(&Chz$R=n>kv=F zE#&ii)2sGQU9}HRtFu1|)ZUCrnos?c4tL7GOgH<31gb1i)G$5si!31m;@?uyJONXU z_6k-I3!tT7XIDmjlAYI<%g7Zb|II}8@4LwXRNF;7j;@53?AdrA^iuLTs_h)JNY*r` z{C-y$P0r)cXHFJrp`>tRX|cUO#tGvW%TxOSVHwlQGOEyRu^zmepKV^AT>tuR-fl&v z6=8Ec)`ILSn}qKvXm}C~CQ|E*>j8U^qKHrQWMN$mJ=s z#?BZ3N>@0q-`+6z5EMQ7pamSqbr}*JJ?|+w1)r70L5jN-SULd*!>t5$?tgzmquexP z78Jw(QFyqe-}T?=(eXRfJMq7rcNm z@#1-iPiBfz&4N2A%~ zB(f%BT7SGHndN5p#0&_~^UOEab4zX>L~y7x!zoHf&vB`S0d~`#10Bbd=tEk1Q(NO4 z@i}{=Kz!3#qTb4Mk*l5@qej_L>HO~(8bBS&#EQvP4yX;N#l1a8;`SxJpTD9ue7Za} z&KY$o_t~wrOlw*5Y%zCPY>U8An`=9T=IoEM`9O`&J@CZ&HZ!Ki@aEIJqAD5WFMDM3 zrSv&A55E){rD``Uc|v17P)6VlU9ujXRq}^Dz1{meupkj88fWsO__#RvCBW?4-r58Z z7{Z*`A0oG0&iRT-IlvdRcgJ)Mw8LbT^qh1vQn+C_JoXcQ7eT#Lyz;|9rTtf`+^iho ztTmdnv{cO2;M-ozK7-njKZHOjP|)#^-QHxWZrjb|6r+@fTYuRz&l+{n3=XIg*n1|p ztfXs#%fn|4E|G0(|-cSg5?Npj0;ShyvB;n~3nT9U01{ zxdOIX@=bLoCM379)Y_k=l62ZsT2&Ty)#R5)*HZkSye1{Z>j2{>F)xKdMDs{$d6ds{ z^|Xz%$dV$^*7CxtBpi}v;^q7-iKWkj=4C)|Po-l}{mA0^demX6J}bh@vB+=In&pc| z5Jeu_O>>@tEIc3zxbU5gdh+4JovIkgYclePM4Evu!+LZp17!z!42XFM@OT!TAUTZg z|Ax3E#^C4YS?&>KIng#LG~ia5Cm(5|!Y1)pgmmXv8mYwe8XZ4u7|v)<)FrygIR+M> zZ$Ml~K;oJC`CMUv?LKscNrK0SD1QU(9q(k-a_rP?8%MG}5?lkZb+Ox?(?`3M7Hecx>LaG|Bq{fA= z2||*%=;N9vTo)vR#jZ>o&=2bxfz-anzsEtHOG&uONC%z?tjN9@#DsTksW$$@K4rt) z9u9@jeFAZ~QIA1`SwenX?Hu(Pg35p1a?Ge0owrn{NF2Q-uIUyB7ldIOr$`KqP{0us zw^Z>|5k+&Z-4z2QaQTjqFRDYC)J9fB-J_Q$tKw6BbC)Yhs_rZ)@m`~H<;gWB^;cv= zni*?Y?*1vv0}^_d7F^r^;TQUR;fwlgn%%XaT%ESGa}i~`p#Dz<0%)7!vxCp2&LA*- zsX$Jf%VtR@##|$c$MYA%-K%DOh*ybNZSV6u6Zh*}_bN(X zr@FHdmXY+hgX5EKJ!NaA+0D>bixCT#4C9!tU5CukqIyVijwM2+ zePCYMP|YSFbXS&R*ozb*FvW-Rg@Gufh6nmkYr2d$L}y62_JsP8iT2`JzQPNbku~?) zKK!Jw+a|98ALlLFSO?w&1~(HaOdCn&ZY4~!fSA<%Y^TTg17^ST#@FXH8}ILxM{~&# zSR#g$i_ z{=!2FpopYG}p9aQ@+p{ z?$;l9>lB^niGoZB16l)vKB!H1T0qj9?chxi)Mp$*>5yk#Qj$3TeO^}oIaSae_L2C9 zgM+36VkTxKXveawwTgxyZ1fyB?XpQ|(A|(s-c}#P06K~TKfmc%BtcE<%TP8rDp#~$ zVV=k{B6Io@sfM$BD-6kDYJ|o05210Actf>U*B?r6vA})mQ-UH>6cT+&RG%ZvMJSJ< zg3<^Mi$~(Sx2P@mqlL|*OtZyDZ0IY`SbVkN@oDaFqD{nqv=dII4sLC_KoRPoEE8?( zqQQhIq1t&`&)8O(IAmh)%59hEGE9apNqMTgamYG3PW@Ko`#E#eE zs6R=?Lhp6fxEU1;XCVzmE%>M;9GF=~!!^K6Vx{$2!qor1F#ewC6Wcl=CGg+IXT$z} z+^gc@?NIS^s;4U8gN#N!_eOrfdkM2jKIdE%&g>129Cxj)TF8;<)kbU&S+m$^cNirvGzfmZJG!9Fp`yvI7fk1}F@QKl zcR}iQPlg16xzmLW(?vHn(+u9ZPaGWV)&)vBHfeGBrp;h=-l}=_z$1(f*1R3@2l-vv z6S5mMog5w>p(e@V*lvMQ{>GQkvDiV?Rcj5zl*ubR zRjz1=ee)LMjo7Bg4dP_&x%+}LsU44k9$7%)YdeZbg3uDQ7XO&f8(4O>ENQC#Y-df^ z69%6+t~J!^Y4?A~$%Bs>yK9dMO80PkvG@be04L0>*v+8j69@WupT*#cAi?RL=#&os zz{VjeUN3{4NfJvF}svdOc2kHqSv04Eg$DQR-cx z9haY6!Ft|WB2wK&;{$@5w2NUBYFDfS>_1MYR&~K9zJoQs6EtsjZpW*wZ z>tW?5pYv>%%7GC6e`IV5&8`w}KRaxKS%KO+Voi?2!Bs6uaH<-})BpMBVGk^ML2(pN z_w(9g+%Nr{gvwz7Om&a7MlsP18iA)K@?mZNm0s^O9YdOrA7lW4t)G>b<>~1rcIEx_ z4@~F8*J!7p%}3iHh@mxiwh3F#Uh`S+ihS|t?te!4$Nc4p6i>M*Ra(Xc_*2NbGr&~_ zqFPf`O6*?1SADI-Xe}hi?o<*KYV)+eoDIkqqALgCG1>;~gD&&nyD~lk+|M8n-}>?q zfi){X1>c1TgsG&PyBuhsYMdGwT}?+I%J7phI#}k~ z>t|Voemeggu$Q4-ZQiKlIa1v#^r6jH5NtU_jX}6Dv=+>L7Uw$Hv2|@+?mfLybwZMPL~H4&R2d#VL;+XU|AOa=PwO@i{3=608tR$N_&&Y> zYRR_Mr}mhn=>*@WgV8|-J9IgFZScRbX4gx1h)GTPwe#i`|1*!V-Q7U}45Mwu9Q?;E z`H-Cw#9e9wl&B$3lZxE>8=%lAOaC(1JBPU!pTa)yFjT1cT8!`A74O9ECVQ!mIMjwJi@-9KcvW7n|*K&SHfHPT+kHFPy;3v@0t`axhmXI996vl?Vzza^gS7FU3`r;!crUHyGrb&pBM!OSG{ zHXu*}PW#I7ne<;HRHOg51SnmETB71`MO5vCM>!&=wl7E0tv{Nn;i&aP>u)`ukxdA^* z+^=L}SghD&WkA`PfRk;ls;H2G?2FE02OFtYEII@>0#^TOo!$Y~9FZvrUhce}s$e7( zmG&(-?=}%&Q=K!&Ey+`NQGDBL@&}iAA;D2i{`gOZ8IQgh{RTod!@t$+=Sh&>{t-5$ z8PwYH=+~@Sh#ckY?b|b{9|;ARftPty8pQ$QtcDf@RIByME+#p-aTR*jm}$)WoB3dz zt~kuZ*7gDdpR8*Xw-s)^8eO$NQH_Rz|B#Y^f>3)o;>b4Lx|;`7@CokuZBaCAF78_O zJ0MnbuDT#_z1S^F_Tu)ARUhAk4l{J31#-LhFufFV-VN~@>~m|Zwxs<+fxit$E=cbI z*9YR-0Fi|a_?JK(+;a+DrQv{Jw3;mJv7YZ?PhuA0U+iCHojuWl>1`MMguMW=OUr%o|-6GQJQyt$;R*mUrNMNn#eQmSj>gXa&1h=jMY02XQSioGt_8Ztyzs($N*ui!^uT^Wsk$;Cq?xg}ia zgRMC|x2Fa^#jD*YroZYRV{Nus2lNS~) zuAV*M%(^QOnzlG3RLVB* zFpTq&qT~rV<@63r7EPO<|0LNYf$CoP+#YQ_UH7P=>FY7>@Dh7HUT^8G-#l z#z%7C+RAx8c~CPfi}Al8WW>Lb_=LqclG7q#OnLSb)FZD-QG5+FkJp+-BrkWdB5Z9j z?7L+xG1;>?U;8agtW<+(l6MZ+M_9y!M*9tdb|A;EJVV}sap(RkiBQ$3qM zMAM1u5k?ro>c@xa?y}ZkmVU7@wynldT0!SB5L<>VQfy{5KC$`X*l-R9>4;fL*Cw=Q z5g&4rAdC*NKlI$Iilr_B*vVL<7brkXp-UDDG;u*^eI<@})0<2v?7HjSu%xb?{9MX| zb_!xW6A2X$1j(^1$z%D_n!uv>KFYX~S)gW;W+Z(~z<#*1HT`2JtJ@Ova!y**Dt59f zPXc&QAUIn}2@Z?|^Vnun3a?+P_m2Pwhn<&K;T$9=L((9O>%d&M<8-3uhOSLuSl)2I zax%-5WxvFdCNvE9iO$_CB_O!y-AuLL(LQm*AXZ6&+Jmxxq_pcf5U%Ke86xU2YUEnb zk=GgBPdggeBg3|Wy#QMMv~V~LrGG}naj4;POBJlGQjrS^VRt@JXcUCt{7N~IQa8id znk>XV9CQn-;KqMToS^s2-zavHy4*nM*V*!J_b&{dTwa=SI=kf)zQPnm%}dFGNvGsPZ4L!X4CY)}vthZ7Kd z(tuJeqc5yWgl8Vuhuy)8EV)fW%*kzEO+x|q>@;2@Y}c8!Vq45&rU3rkF^%kLB~?Hg zJ5JuO1nxY|bNoc4Zf-t}b58hjRVO$%2QNAK?zGVG>G@f*?YEcvVHOVp7#Km|fLe7Wo;X;2Sx1iUDP?{kn>S5B|muF~=`KHuE4H|BRG!21I zb!jNKG(xtVvO`7qQQmwQd~&U7^O)9+Zl`s^Vp-g59$hq_GwG@OzuPP#=PS%8fMb_s z@2iOTrTKcq@MZJbTCH`Jt8qZQebTnX`4LSf@-SvUpQMleE>%b!Yg}h3#-Es1;(Hz{ zZCu3uV=G#64vCeqR&-#;r$BSp^*hpX_;qjWLK?r70X<*^Y|~!s_uV?7Fw$$xLRQzp z=;f2MIb_-r!Mi{QZ)O;!yfc-*QVjKm=#=uH@gCq#CGuX%Jb8~|A@qai-n$?d{kyeclq-S@hjez+| z295bW|4j)6RxZEfvpRE^^?){^E65A7Ol!1x8BwGD%4f+Zwl*oI=cx{Zwh#S4Ch^x{ zlWSbD@4;l%nltm`+cg`|w6?OT_FXE4RRxeuCKpeDvfS2Fjc*Wu0gBsnTs}+!bA(>* zm@BBSDw*LN;GgoZxf#REFo#eBV97E3f-Cx&Cky3oJEgV#sQ21%EOwYVR37mFGp405@C=tWg)=R~ zU2gB%ZWgF0+JEuvsaKPa#za~CqI4CNMVMSGzZ>2%#x<=$jW8e;i$ZF~a7X>TkF!w@ zz_#$okR25P#T>-5oYgoyafk->gOkhjDKtO&Vvv$vk7M|(KuCg*fS@*Sq!r%BJJTcm zteTIOEQG1Ax~AO&IfsDT$=>XIuku1o1;Vyq@aP07*1(TS!Lq%Z7GR_OMHywF+y+x! z3o!@L{+s$i^k;xf3h<2@-Gt2pPct|r2ttuedDRlX&3Boz0Ro}&j7)hg5S=ab;*5;s zxQ5Nau6WQKRrML$(R#?%3>XYEo=?;ZOYhqZvaEm}Qa{T z@bP~OFkpWCi*}u?`IPN2+}6v(jlJ2Bld&BrzGEfmBw@8MC)e=^TUHV)s5(vhB#G1!# zMwlg|cY%!YFq`vRYHi}%^@+1KuWCIpKgrdg=2|Law26FO!-t!aB`&Dh8-j&>2XfT? zjZAeLgISolEv5U-t%J2Wn)qr-(`2K9&|ThN?#071^)YP${=7uje)t#J(n=|Yh~iy1 zgCjz2(%y``d1~qgHHKbDdcsqw4gKSSQwIsb;SESSCw|2iS%J1{-7b;g&?*)A5zsXh z1C#P&ekz5?D@gN#YgAW(gqCu7>!Od8i}l@R`oh@JK7y`$1bf^R(PFz&B6 z$~TQE({Mn5+GT`drH`W{1A+^MGcr+jazOegD`bNQz7X+3obO8q#&dry&qDMx`%4aN z($B(}=HIvypVj&6>ds1~3#~h_ED9SJL6XK`H7Q@b0X3R=mQ=8FKI15#`#70|*iwt| zt>)pJ8;NgF_8{Alt~4aM+fDHp4OzhPP+qThF{}MfQa2gvOWD~7mB2rwLrU`DII3ddJlNx&0@qO3}S%G(BQ&3ld zC+pF}tumzf-Y}#~?Q6n-KLu^|sFW-{ufPkZyTS0zs$D55))&fZ2Y!P`T)GEQTDeC} z2E||pebn{BLZCGH&ers@2E|?#Xj4};(Pi|#^TR3ClK#ufKCK9AH{^^h&DiLf}wB|@N>(0h;n6btm&3bg|CO~ zZEddFH81zf(UZlc+rLu2>gpW;_rTJwn@;xHp5THQsuy^5IT6e7wCY3Mhj^p5L>*&1 zn<>rEXOn!tjH0vwW~^)1cph|#9TCL~vwuiLkESp3j!r2D8${yO7?-zRlkOQw(jBJ* za%?gjVZ&1G4&oE_8V)gVwWbtliaCb9kBfBu1((2dr z3XU)MF%&#qKZw|Hmd<~k9y9H%Dfcwl`MGqa80$?^?i18eQ8sTED~hfA=NnmqPpWcU z?eRCjN~0&4s_E~7oPK~Mp|su&Am4@yGmn3g#AAdXKc(KE0xq;5Xa8_TW;z`NnO0Fl z5qYd(5Qjxk_R>5Ice2#;sUd3z#9`1|DUIZEe`tdj)>}l-SmZM-5bD z`4i1lIbiiuGPFe+05pq-GO#TO01_pjjOJg(=a3JqO_AIJ6?b5HNxRSMV#DJE&>WR2 z9k*JO=7EL1t! zd9@{raWu$;+DH)2V0Bi%odisPxzkwKRY^)4j<7d8wA21~22atM@3DQTVe;SleQfP% zM49{ufkpW@ZihW_B?49tvQH8gN%&EC#}F|x`S^@V*?gp6F@6@LvC{3-Xji&>D2~^q z0WHQJ4PNZd3bOj1XB0THe?eE-=tZfYn5El>NT`t>rFRVa(#*s>1=?}354{E{Id{@f zZ1U8yz4*drq{uA5JG57lDCVzS9 z`>a|ij_z1H@q4loeoWA5Lq`u!Vo%p6DTB$aV@^tomSY^>y1!HlqMQQr<)wBU_KH(C)S4R1A&0bcLGIag)G>@m1E=_4F-{ZvogXfeDL zH)5m$6uQ$^pw=PymD3sKN4g6&u;t5L$232>wx3F4^p5CAGTamm1pVB+r%6(Mf{65l zr9_p^@B)yni@Z)TZ%ayltM&76JB9;nMcU*BFHQS93-v{2e*px*7m>@tS6#?MadGxZLLB zKqsh1NhD69S~84z!IWCec9q+R2H1M2#gqEVnjcO9ItJ~y`)Ejj8O^*Q?sYipWe=sltN|Fx}z^t;O<4v{>L=!9f-{A$)ZQ&wUV1?B^oM&ZkAxEeii<1(&pLPw#{>OJub;6X}leG=p9~d}3PF?_lrGgi@$MCNS?qlD;8d2l&p%VAx%J0%v+oJLJMa~TR?K_gMlr2r`HEHb)}k0;Z!JX zCz28A8A|4=pDLQ~pPENmn1(g%H@fs45UW2o98CYdOm&L7iqzamWrx;w%VwcNVO`j@+;Ra$ZvT4U}_RQ1EBSa^$`f< z#4g-^X=+pEa15a;gf(f@%>xr}Xr`}QmrJ`BXL-DdGuE2=iVzlW;Av9L>UoXM$*Q3e zxag|*=-j_Daiq%yqws?^DoVF~z!fI;HObRc(u<{h^NH6-W6pLhppJA@UY{QU9zMM)yE< zs(_-2;5>a?NV^dG<@wiF#hs;i8p_<*>fKujfaI0Bf15>oY%C0K_D$wnnO$B=fCQ_; zfGhL@8-P<=`eU&sQvA+9+jd2h^eG2hNPol%dy~ z3)1K6@L{+mpCzzw(@m7r-Va59&Hb*(Cab`^C_k`k8mraCHoMinUMziWD~(TJe?VFb zLo!qnh0OXbK-0gJ_Na!5n<1hQRATD?cS`%8aa;pV{u^0_dDu zRo;G5t61r;50p~jm*)lQeNYKV?Kc}ZcPiFK25Ow1)u4Wbza0b`_1Q)@S44LmGp1SW zByr$>@28>nW>l1WS1Y~Y$@0dFX-v~purfKCO?z#9W=DUP9yUmj2t!ux zsMiz-gjxxyPXO&3*n3LDTbMnq42OBGGdFv)n9XJ$4lT?QammUSi;DE}-6T%ierE{R z24o>;dfR=0#GXLa`yzo{WcLzuS+6K!uRCDYdG?xe2K_Ab)xlG2quVgIYoSkq3ZqH%o)IUVU803&8_#kA zBgIEXaApLpH!HQHTdI()O>l6{05241yzy29n`V1}cC>qb(}BfNcve5d^kQQ*f8`yN zwz(H5fR-=p7)B}e3sn0qLPQ?HPPYh|F@5Z z5?^PJwS^@nTcgcv2vV%Xmy5MZhRO2YRqVFL1+~Ge6;$cO5n5tSz5nN|yD$$J%Ceb! z)5}sCp{|_u1Q)=73X5GkUOA>FrQoWdkp<+XjuE5@JBp>qX=?hRMIM$}se27#fDdvx zN~2ocs$^IJ0qCsD_2HLhE!o;zV7)|im9mC=ofj!%Sq61zhCo`I$A%KSwJ#Gwry9Ug zX#IsuB&+%vcyR+kGScYnQkB7CoQ>-c0bh*4=_G;w@4HQj9F*OPPX?!mv~%%TMS_yj zo14{TfeEaJ(3QH%UKlPXwjAlk&NI>lzdPiXU##OHj zO8~G#+$+SC6ep3-giuG{1$#{d%c88dq+14H%Q+I^EgbzJnnSw}ghW@d?{>YnXJh{t zcHZ7%#-cW+uK#I{71t@fMU1;T6(60=+N%wvaD_6#Nd07oTy+FwCUXZ#>QV<{T4iLZ z6F=V`53L_`-GS)ZNv^oI(!c4IM_m8YG7hdv>+Jh^g7n{iyQCzKIqt2zo)I5wMre9% z*K)9e`zLwxEOUi)$Ib%&eUv1&f)u`PEBk`08Vs4ZZbyF#uwM0P%rlBg1>}PHLtVqs zHtUmhLB`3*I5`@S34{nKa26YN0FQzPWu*SPED8}r>g4m-QUm}*3)r4r2SjZFz`?yC zhte&y@;cL#;^VWQ8aYBLa7xDgURj>Tf47iW2zg4L!<^7Y!@&=$x%MXpBV%-0R0bl# z^Z4Thfozje0(dB|9o@uMPqCo;KY%A)Hfq88gUpVxZ(Q06;g&B+WNf#ooYLt1%?OqM zZ4(eeo6+w_SlW)D1;HTPSei8ugG$wlC8G3 z%cqC{BqtL03ZviYzUe$)3Yv8wDvQpRY5uI#kZ`9~OqReFE47|`dZdr%m2C|$(S33x zF{Q)o`hzV?ieXydGE&1eybwg)r*3Do60Tf7q-e`Eo~QauxyE`}X{pKuLII;u^sR|3 zrKroo;XtK*Jbf2m6NQfCk3cTx>mhYP{cR-gON~AOGl4WyOS+bVYsbWl*YC8!w?p@I zGfsn>D6>`?XcHeEEM4Nvw7D98t9z8Fc$%xNRuzgCNLE0 z)1(55I+-+=fBwVB>o{#WY4-2~aR2mk%|fS$8_U2)v8nHP4E?xt7cydND z!v%3LapiD0gZGO1^t{WM)7;73O$+KLx3sA~ap0AsD4*KCr$#fukS|W>6B-XTtSGi$&$%*;34MnNExP1Zhlu-et7zF ztgicRI6=)qT(HYr7p6+RSi=Lve7o%ud6}6+1H8`D;DwxxNag=lz{wsvX3)JZ{WT%& zb#MXI7slN87cQ(9uhvNbHAP3769p|DSf6ixMzYI0o+&!aT+%SlFFLPU(2M&s7f+-{ z%T#{80$$50$+xfMKwfe{Ye5{|BT+Qa(P0na!sK>%jF)O9e*umr4ejm;##RNFtgY0c z)Y`8!14$lmbRb)8b`pGZ){`~)4Z23JC_Ry1t%1d@z!{lM=ia5sYNY>>Vn)9+fbU0j z?vE#F!3ScC_2NpaT+l}Nx~HchIcA0UcHs={`;gTJNM9r*KpepZ+A-&mbie{e`f=y= zcK4tzHtPwT{<)|xW?F7qLsb}@I4xDA(<;Bt1K&6ZK|z^f62PMTr1wMVGueI$)IK`6 z@544$C#MYiU9Q=_9(u~Zz z6BqY*hv4?&G@!75{GTrIxO(^~a}CE2QT~x=GFuJcG%d@g7BNtNO-dvDq0y}Ij@*O4P!Gp0I^gG&i zSwI4AS!K+Xcfq(|rD;nI;|!3%7GfAE|Om$4$W4TdiR3|`%&UMj`j9tOL$ z#|D6A77dC+uj(!;#uP0t;{7?(2nu&1xIKJBp4xkgsS(d-0Wndv1a- zwy_>!b1O3lN}5&8VzO?0vcTExTeN`m;E)sZHo{C!RE1o*W3A~L)^Bze60*dre)S-R zA+wm%&#>!1Z>`dvo8}yls87o!-0E99niS1!)(6=U~~KVot4z(f!vHmsX=W(~}zeDum_&PR*_(2k5!O6E+vK z5wCp5#7Kxej3bzyUpk_uOddJ7n!W#dZ2v>y2%prh(rRo$c;itARDi
g1};1%q|* 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�|>0&WLX=yr){}*cKx&1jFk*D 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 + + + net48 + + + TRACE;TRACE;UNITY_2017_1_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2019_3_OR_NEWER;USE_BURST;USE_NEWMATHS;USE_BURST_REALLY;USE_TERRAINS + + + + + + + + + + + + + + + + + + ..\.ManagedLibs\BTKUILib.dll + + + + + + + + diff --git a/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs b/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs new file mode 100644 index 0000000..799a6a2 --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs @@ -0,0 +1,66 @@ +using System.Net; + +namespace NAK.ShareBubbles.API.Exceptions; + +public class ShareApiException : Exception +{ + public HttpStatusCode StatusCode { get; } // TODO: network back status code to claiming client, to show why the request failed + public string UserFriendlyMessage { get; } + + public ShareApiException(HttpStatusCode statusCode, string message, string userFriendlyMessage) + : base(message) + { + StatusCode = statusCode; + UserFriendlyMessage = userFriendlyMessage; + } +} + +public class ContentNotSharedException : ShareApiException +{ + public ContentNotSharedException(string contentId) + : base(HttpStatusCode.BadRequest, + $"Content {contentId} is not currently shared", + "This content is not currently shared with anyone") + { + } +} + +public class ContentNotFoundException : ShareApiException +{ + public ContentNotFoundException(string contentId) + : base(HttpStatusCode.NotFound, + $"Content {contentId} not found", + "The specified content could not be found") + { + } +} + +public class UserOnlyAllowsSharesFromFriendsException : ShareApiException +{ + public UserOnlyAllowsSharesFromFriendsException(string userId) + : base(HttpStatusCode.Forbidden, + $"User {userId} only accepts shares from friends", + "This user only accepts shares from friends") + { + } +} + +public class UserNotFoundException : ShareApiException +{ + public UserNotFoundException(string userId) + : base(HttpStatusCode.NotFound, + $"User {userId} not found", + "The specified user could not be found") + { + } +} + +public class ContentAlreadySharedException : ShareApiException +{ + public ContentAlreadySharedException(string contentId, string userId) + : base(HttpStatusCode.Conflict, + $"Content {contentId} is already shared with user {userId}", + "This content is already shared with this user") + { + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs new file mode 100644 index 0000000..280a32f --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs @@ -0,0 +1,112 @@ +using ABI_RC.Core.Networking.API; +using NAK.ShareBubbles.API.Responses; + +namespace NAK.ShareBubbles.API; + +public enum PedestalType +{ + Avatar, + Prop +} + +///

+/// Batch processor for fetching pedestal info in bulk. The pedestal endpoints are meant to be used in batch, so might +/// as well handle bubble requests in batches if in the very unlikely case that multiple requests are made at once. +/// +/// May only really matter for when a late joiner joins a room with a lot of bubbles. +/// +/// Luc: if there is a lot being dropped, you could try to batch them +/// +public static class PedestalInfoBatchProcessor +{ + private static readonly Dictionary>> _pendingRequests + = new() + { + { PedestalType.Avatar, new Dictionary>() }, + { PedestalType.Prop, new Dictionary>() } + }; + + private static readonly Dictionary _isBatchProcessing + = new() + { + { PedestalType.Avatar, false }, + { PedestalType.Prop, false } + }; + + private static readonly object _lock = new(); + private const float BATCH_DELAY = 2f; + + public static Task QueuePedestalInfoRequest(PedestalType type, string contentId) + { + var tcs = new TaskCompletionSource(); + + lock (_lock) + { + var requests = _pendingRequests[type]; + + if (!requests.TryAdd(contentId, tcs)) + return requests[contentId].Task; + + if (_isBatchProcessing[type]) + return tcs.Task; + + _isBatchProcessing[type] = true; + ProcessBatchAfterDelay(type); + } + + return tcs.Task; + } + + private static async void ProcessBatchAfterDelay(PedestalType type) + { + await Task.Delay(TimeSpan.FromSeconds(BATCH_DELAY)); + + List contentIds; + Dictionary> requestBatch; + + lock (_lock) + { + contentIds = _pendingRequests[type].Keys.ToList(); + requestBatch = new Dictionary>(_pendingRequests[type]); + _pendingRequests[type].Clear(); + _isBatchProcessing[type] = false; + //ShareBubblesMod.Logger.Msg($"Processing {type} pedestal info batch with {contentIds.Count} items"); + } + + try + { + ApiConnection.ApiOperation operation = type switch + { + PedestalType.Avatar => ApiConnection.ApiOperation.AvatarPedestal, + PedestalType.Prop => ApiConnection.ApiOperation.PropPedestal, + _ => throw new ArgumentException($"Unsupported pedestal type: {type}") + }; + + var response = await ApiConnection.MakeRequest>(operation, contentIds); + + if (response?.Data != null) + { + var responseDict = response.Data.ToDictionary(info => info.Id); + + foreach (var kvp in requestBatch) + { + if (responseDict.TryGetValue(kvp.Key, out var info)) + kvp.Value.SetResult(info); + else + kvp.Value.SetException(new Exception($"Content info not found for ID: {kvp.Key}")); + } + } + else + { + Exception exception = new($"Failed to fetch {type} info batch"); + foreach (var tcs in requestBatch.Values) + tcs.SetException(exception); + } + } + catch (Exception ex) + { + foreach (var tcs in requestBatch.Values) + tcs.SetException(ex); + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs b/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs new file mode 100644 index 0000000..70dc731 --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace NAK.ShareBubbles.API.Responses; + +public class ActiveSharesResponse +{ + [JsonProperty("value")] + public List Value { get; set; } +} + +// Idk why not just reuse UserDetails +public class ShareUser +{ + [JsonProperty("image")] + public string Image { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs new file mode 100644 index 0000000..ac764cb --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs @@ -0,0 +1,13 @@ +using ABI_RC.Core.Networking.API.Responses; + +namespace NAK.ShareBubbles.API.Responses; + +/// Same as PedestalInfoResponse, but with an additional field for publication state, if you could not tell by the name. +/// TODO: actually waiting on luc to add Published to PedestalInfoResponse +[Serializable] +public class PedestalInfoResponseButWithPublicationState : UgcResponse +{ + public UserDetails User { get; set; } + public bool Permitted { get; set; } + public bool Published { get; set; } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs b/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs new file mode 100644 index 0000000..3200deb --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs @@ -0,0 +1,236 @@ +using System.Net; +using System.Text; +using System.Web; +using ABI_RC.Core.Networking; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.Responses; +using ABI_RC.Core.Savior; +using NAK.ShareBubbles.API.Exceptions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace NAK.ShareBubbles.API; + +/// +/// API for content sharing management. +/// +public static class ShareApiHelper +{ + #region Enums + + public enum ShareContentType + { + Avatar, + Spawnable + } + + private enum ShareApiOperation + { + ShareAvatar, + ReleaseAvatar, + + ShareSpawnable, + ReleaseSpawnable, + + GetAvatarShares, + GetSpawnableShares + } + + #endregion Enums + + #region Public API + + /// + /// Shares content with a specified user. + /// + /// Type of content to share + /// ID of the content + /// Target user ID + /// Response containing share information + /// Thrown when API request fails + /// Thrown when target user is not found + /// Thrown when content is not found + /// Thrown when user only accepts shares from friends + /// Thrown when content is already shared with user + public static Task> ShareContentAsync(ShareContentType type, string contentId, string userId) + { + ShareApiOperation operation = type == ShareContentType.Avatar + ? ShareApiOperation.ShareAvatar + : ShareApiOperation.ShareSpawnable; + + ShareRequest data = new() + { + ContentId = contentId, + UserId = userId + }; + + return MakeApiRequestAsync(operation, data); + } + + /// + /// Releases shared content from a specified user. + /// + /// Type of content to release + /// ID of the content + /// Optional user ID. If null, releases share from self + /// Response indicating success + /// Thrown when API request fails + /// Thrown when content is not shared + /// Thrown when content is not found + /// Thrown when specified user is not found + public static Task> ReleaseShareAsync(ShareContentType type, string contentId, string userId = null) + { + ShareApiOperation operation = type == ShareContentType.Avatar + ? ShareApiOperation.ReleaseAvatar + : ShareApiOperation.ReleaseSpawnable; + + // If no user ID is provided, release share from self + userId ??= MetaPort.Instance.ownerId; + + ShareRequest data = new() + { + ContentId = contentId, + UserId = userId + }; + + return MakeApiRequestAsync(operation, data); + } + + /// + /// Gets all shares for specified content. + /// + /// Type of content + /// ID of the content + /// Response containing share information + /// Thrown when API request fails + /// Thrown when content is not found + public static Task> GetSharesAsync(ShareContentType type, string contentId) + { + ShareApiOperation operation = type == ShareContentType.Avatar + ? ShareApiOperation.GetAvatarShares + : ShareApiOperation.GetSpawnableShares; + + ShareRequest data = new() { ContentId = contentId }; + return MakeApiRequestAsync(operation, data); + } + + #endregion Public API + + #region Private Implementation + + [Serializable] + private record ShareRequest + { + public string ContentId { get; set; } + public string UserId { get; set; } + } + + private static async Task> MakeApiRequestAsync(ShareApiOperation operation, ShareRequest data) + { + ValidateAuthenticationState(); + + (string endpoint, HttpMethod method) = GetApiEndpointAndMethod(operation, data); + using HttpRequestMessage request = CreateHttpRequest(endpoint, method, data); + + try + { + using HttpResponseMessage response = await ApiConnection._client.SendAsync(request); + string content = await response.Content.ReadAsStringAsync(); + + return HandleApiResponse(response, content, data.ContentId, data.UserId); + } + catch (HttpRequestException ex) + { + throw new ShareApiException( + HttpStatusCode.ServiceUnavailable, + $"Failed to communicate with the server: {ex.Message}", + "Unable to connect to the server. Please check your internet connection."); + } + catch (JsonException ex) + { + throw new ShareApiException( + HttpStatusCode.UnprocessableEntity, + $"Failed to process response data: {ex.Message}", + "Server returned invalid data. Please try again later."); + } + } + + private static void ValidateAuthenticationState() + { + if (!AuthManager.IsAuthenticated) + { + throw new ShareApiException( + HttpStatusCode.Unauthorized, + "User is not authenticated", + "Please log in to perform this action"); + } + } + + private static HttpRequestMessage CreateHttpRequest(string endpoint, HttpMethod method, ShareRequest data) + { + HttpRequestMessage request = new(method, endpoint); + + if (method == HttpMethod.Post) + { + JObject json = JObject.FromObject(data); + request.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json"); + } + + return request; + } + + private static BaseResponse HandleApiResponse(HttpResponseMessage response, string content, string contentId, string userId) + { + if (response.IsSuccessStatusCode) + return CreateSuccessResponse(content); + + // Let specific exceptions propagate up to the caller + throw response.StatusCode switch + { + HttpStatusCode.BadRequest => new ContentNotSharedException(contentId), + HttpStatusCode.NotFound when userId != null => new UserNotFoundException(userId), + HttpStatusCode.NotFound => new ContentNotFoundException(contentId), + HttpStatusCode.Forbidden => new UserOnlyAllowsSharesFromFriendsException(userId), + HttpStatusCode.Conflict => new ContentAlreadySharedException(contentId, userId), + _ => new ShareApiException( + response.StatusCode, + $"API request failed with status {response.StatusCode}: {content}", + "An unexpected error occurred. Please try again later.") + }; + } + + private static BaseResponse CreateSuccessResponse(string content) + { + var response = new BaseResponse("") + { + IsSuccessStatusCode = true, + HttpStatusCode = HttpStatusCode.OK + }; + + if (!string.IsNullOrEmpty(content)) + { + response.Data = JsonConvert.DeserializeObject(content); + } + + return response; + } + + private static (string endpoint, HttpMethod method) GetApiEndpointAndMethod(ShareApiOperation operation, ShareRequest data) + { + string baseUrl = $"{ApiConnection.APIAddress}/{ApiConnection.APIVersion}"; + string encodedContentId = HttpUtility.UrlEncode(data.ContentId); + + return operation switch + { + ShareApiOperation.GetAvatarShares => ($"{baseUrl}/avatars/{encodedContentId}/shares", HttpMethod.Get), + ShareApiOperation.ShareAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post), + ShareApiOperation.ReleaseAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete), + ShareApiOperation.GetSpawnableShares => ($"{baseUrl}/spawnables/{encodedContentId}/shares", HttpMethod.Get), + ShareApiOperation.ShareSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post), + ShareApiOperation.ReleaseSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete), + _ => throw new ArgumentException($"Unknown operation: {operation}") + }; + } + + #endregion Private Implementation +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs b/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs new file mode 100644 index 0000000..932fa6c --- /dev/null +++ b/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs @@ -0,0 +1,10 @@ +namespace NAK.ShareBubbles; + +public class BubblePedestalInfo +{ + public string Name; + public string ImageUrl; + public string AuthorId; + public bool IsPermitted; // TODO: awaiting luc to fix not being true for private shared (only true for public) + public bool IsPublic; // TODO: awaiting luc to add +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/DataTypes/ShareBubbleData.cs b/ShareBubbles/ShareBubbles/DataTypes/ShareBubbleData.cs new file mode 100644 index 0000000..2650773 --- /dev/null +++ b/ShareBubbles/ShareBubbles/DataTypes/ShareBubbleData.cs @@ -0,0 +1,80 @@ +using ABI_RC.Systems.ModNetwork; + +namespace NAK.ShareBubbles; + +/// +/// Used to create a bubble. Need to define locally & over-network. +/// +public struct ShareBubbleData +{ + public uint BubbleId; // Local ID of the bubble (ContentId & ImplTypeHash), only unique per user + public uint ImplTypeHash; // Hash of the implementation type (for lookup in ShareBubbleRegistry) + + public string ContentId; // ID of the content being shared + + public ShareRule Rule; // Rule for sharing the bubble + public ShareLifetime Lifetime; // Lifetime of the bubble + public ShareAccess Access; // Access given if requesting bubble content share + public DateTime CreatedAt; // Time the bubble was created for checking lifetime + + public static void AddConverterForModNetwork() + { + ModNetworkMessage.AddConverter(Read, Write); + return; + + ShareBubbleData Read(ModNetworkMessage msg) + { + msg.Read(out uint bubbleId); + msg.Read(out uint implTypeHash); + msg.Read(out string contentId); + + // Pack rule, lifetime, and access into a single byte to save bandwidth + msg.Read(out byte packedFlags); + ShareRule rule = (ShareRule)(packedFlags & 0x0F); // First 4 bits for Rule + ShareLifetime lifetime = (ShareLifetime)((packedFlags >> 4) & 0x3); // Next 2 bits for Lifetime + ShareAccess access = (ShareAccess)((packedFlags >> 6) & 0x3); // Last 2 bits for Access + + // Read timestamp as uint (seconds since epoch) to save space compared to DateTime + msg.Read(out uint timestamp); + DateTime createdAt = DateTime.UnixEpoch.AddSeconds(timestamp); + //ShareBubblesMod.Logger.Msg($"Reading bubble - Seconds from epoch: {timestamp}"); + //ShareBubblesMod.Logger.Msg($"Converted back to time: {createdAt}"); + + // We do not support time traveling bubbles + DateTime now = DateTime.UtcNow; + if (createdAt > now) + createdAt = now; + + return new ShareBubbleData + { + BubbleId = bubbleId, + ImplTypeHash = implTypeHash, + ContentId = contentId, + Rule = rule, + Lifetime = lifetime, + Access = access, + CreatedAt = createdAt + }; + } + + void Write(ModNetworkMessage msg, ShareBubbleData data) + { + msg.Write(data.BubbleId); + msg.Write(data.ImplTypeHash); + msg.Write(data.ContentId); + + // Pack flags into a single byte + byte packedFlags = (byte)( + ((byte)data.Rule & 0x0F) | // First 4 bits for Rule + (((byte)data.Lifetime & 0x3) << 4) | // Next 2 bits for Lifetime + (((byte)data.Access & 0x3) << 6) // Last 2 bits for Access + ); + msg.Write(packedFlags); + + // Write timestamp as uint seconds since epoch + uint timestamp = (uint)(data.CreatedAt.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds; + //ShareBubblesMod.Logger.Msg($"Writing bubble - Original time: {data.CreatedAt}, Converted to seconds: {timestamp}"); + msg.Write(timestamp); + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Enums/ShareAccess.cs b/ShareBubbles/ShareBubbles/Enums/ShareAccess.cs new file mode 100644 index 0000000..af6b417 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Enums/ShareAccess.cs @@ -0,0 +1,8 @@ +namespace NAK.ShareBubbles; + +public enum ShareAccess +{ + Permanent, + Session, + None +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Enums/ShareLifetime.cs b/ShareBubbles/ShareBubbles/Enums/ShareLifetime.cs new file mode 100644 index 0000000..64962aa --- /dev/null +++ b/ShareBubbles/ShareBubbles/Enums/ShareLifetime.cs @@ -0,0 +1,7 @@ +namespace NAK.ShareBubbles; + +public enum ShareLifetime +{ + Session, + TwoMinutes, +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Enums/ShareRule.cs b/ShareBubbles/ShareBubbles/Enums/ShareRule.cs new file mode 100644 index 0000000..192aab5 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Enums/ShareRule.cs @@ -0,0 +1,7 @@ +namespace NAK.ShareBubbles; + +public enum ShareRule +{ + Everyone, + FriendsOnly +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs new file mode 100644 index 0000000..29c9a14 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs @@ -0,0 +1,103 @@ +using ABI_RC.Core.EventSystem; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.API.UserWebsocket; +using NAK.ShareBubbles.API; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace NAK.ShareBubbles.Impl +{ + public class AvatarBubbleImpl : IShareBubbleImpl + { + private ShareBubble bubble; + private string avatarId; + private BubblePedestalInfo details; + private Texture2D downloadedTexture; + + public bool IsPermitted => details is { IsPermitted: true }; + public string AuthorId => details?.AuthorId; + + public void Initialize(ShareBubble shareBubble) + { + bubble = shareBubble; + avatarId = shareBubble.Data.ContentId; + bubble.SetHue(0.5f); + bubble.SetEquipButtonLabel(" Wear"); + } + + public async Task FetchContentInfo() + { + var infoResponse = await PedestalInfoBatchProcessor + .QueuePedestalInfoRequest(PedestalType.Avatar, avatarId); + + details = new BubblePedestalInfo + { + Name = infoResponse.Name, + ImageUrl = infoResponse.ImageUrl, + AuthorId = infoResponse.User.Id, + IsPermitted = infoResponse.Permitted, + IsPublic = infoResponse.Published, + }; + + downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); + + // Check if bubble was destroyed before image was downloaded + if (bubble == null || bubble.gameObject == null) + { + Object.Destroy(downloadedTexture); + return; + } + + bubble.UpdateContent(new BubbleContentInfo + { + Name = details.Name, + Label = $" {(details.IsPublic ? "Public" : "Private")} Avatar", + Icon = downloadedTexture + }); + } + + public void HandleClaimAccept(string userId, Action onClaimActionCompleted) + { + Task.Run(async () => + { + try + { + var response = await ShareApiHelper.ShareContentAsync( + ShareApiHelper.ShareContentType.Avatar, avatarId, userId); + + // Store the temporary share to revoke when either party leaves the instance + if (bubble.Data.Access == ShareAccess.Session) + TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Avatar, + avatarId, userId); + + onClaimActionCompleted(response.IsSuccessStatusCode); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Error sharing avatar: {ex.Message}"); + onClaimActionCompleted(false); + } + }); + } + + public void ViewDetailsPage() + { + if (details == null) return; + ViewManager.Instance.RequestAvatarDetailsPage(avatarId); + } + + public void EquipContent() + { + if (details == null) return; + AssetManagement.Instance.LoadLocalAvatar(avatarId); + } + + public void Cleanup() + { + if (downloadedTexture == null) return; + Object.Destroy(downloadedTexture); + downloadedTexture = null; + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs new file mode 100644 index 0000000..9551717 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs @@ -0,0 +1,13 @@ +namespace NAK.ShareBubbles.Impl; + +public interface IShareBubbleImpl +{ + bool IsPermitted { get; } // Is user permitted to use the content (Public, Owned, Shared) + string AuthorId { get; } // Author ID of the content + void Initialize(ShareBubble shareBubble); + Task FetchContentInfo(); // Load the content info from the API + void ViewDetailsPage(); // Open the details page for the content + void EquipContent(); // Equip the content (Switch/Select) + void HandleClaimAccept(string userId, Action onClaimActionCompleted); // Handle the claim action (Share via API) + void Cleanup(); // Cleanup any resources +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs new file mode 100644 index 0000000..a5ece85 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs @@ -0,0 +1,122 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.API.UserWebsocket; +using ABI_RC.Core.Player; +using NAK.ShareBubbles.API; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace NAK.ShareBubbles.Impl +{ + public class SpawnableBubbleImpl : IShareBubbleImpl + { + private ShareBubble bubble; + private string spawnableId; + private BubblePedestalInfo details; + private Texture2D downloadedTexture; + + public bool IsPermitted => details is { IsPermitted: true }; + public string AuthorId => details?.AuthorId; + + public void Initialize(ShareBubble shareBubble) + { + bubble = shareBubble; + spawnableId = shareBubble.Data.ContentId; + bubble.SetHue(0f); + bubble.SetEquipButtonLabel(" Select"); + } + + public async Task FetchContentInfo() + { + var infoResponse = await PedestalInfoBatchProcessor + .QueuePedestalInfoRequest(PedestalType.Prop, spawnableId); + + details = new BubblePedestalInfo + { + Name = infoResponse.Name, + ImageUrl = infoResponse.ImageUrl, + AuthorId = infoResponse.User.Id, + IsPermitted = infoResponse.Permitted, + IsPublic = infoResponse.Published, + }; + + downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); + + // Check if bubble was destroyed before image was downloaded + if (bubble == null || bubble.gameObject == null) + { + Object.Destroy(downloadedTexture); + return; + } + + bubble.UpdateContent(new BubbleContentInfo + { + Name = details.Name, + Label = $" {(details.IsPublic ? "Public" : "Private")} Prop", + Icon = downloadedTexture + }); + } + + public void HandleClaimAccept(string userId, Action onClaimActionCompleted) + { + if (details == null) + { + onClaimActionCompleted(false); + return; + } + + Task.Run(async () => + { + try + { + var response = await ShareApiHelper.ShareContentAsync( + ShareApiHelper.ShareContentType.Spawnable, + spawnableId, + userId); + + // Store the temporary share to revoke when either party leaves the instance + if (bubble.Data.Access == ShareAccess.Session) + TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Spawnable, + spawnableId, userId); + + onClaimActionCompleted(response.IsSuccessStatusCode); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Error sharing spawnable: {ex.Message}"); + onClaimActionCompleted(false); + } + }); + } + + public void ViewDetailsPage() + { + if (details == null) return; + ViewManager.Instance.GetPropDetails(spawnableId); + } + + public void EquipContent() + { + if (details == null || !IsPermitted) return; + + try + { + PlayerSetup.Instance.SelectPropToSpawn( + spawnableId, + details.ImageUrl, + details.Name); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Error equipping spawnable: {ex.Message}"); + } + } + + public void Cleanup() + { + if (downloadedTexture == null) return; + Object.Destroy(downloadedTexture); + downloadedTexture = null; + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs b/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs new file mode 100644 index 0000000..d35810a --- /dev/null +++ b/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs @@ -0,0 +1,230 @@ +using ABI_RC.Core.Networking.API.UserWebsocket; +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.Player; +using ABI_RC.Systems.GameEventSystem; +using NAK.ShareBubbles.API; +using Newtonsoft.Json; +using UnityEngine; + +namespace NAK.ShareBubbles; + +// Debating on whether to keep this as a feature or not +// Is janky and for avatars it causes this: +// https://feedback.abinteractive.net/p/when-avatar-is-unshared-by-owner-remotes-will-see-content-incompatible-bot + +public class TempShareManager +{ + #region Constructor + + private TempShareManager() + { + string userDataPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "UserData")); + savePath = Path.Combine(userDataPath, "sharebubbles_session_shares.json"); + } + + #endregion Constructor + + #region Singleton + + public static TempShareManager Instance { get; private set; } + + public static void Initialize() + { + if (Instance != null) return; + Instance = new TempShareManager(); + + Instance.LoadShares(); + Instance.InitializeEvents(); + } + + #endregion + + #region Constants & Fields + + private readonly string savePath; + private readonly object fileLock = new(); + + private TempShareData shareData = new(); + private readonly HashSet grantedSharesThisSession = new(); + + #endregion Constants & Fields + + #region Data Classes + + [Serializable] + private class TempShare + { + public ShareApiHelper.ShareContentType ContentType { get; set; } + public string ContentId { get; set; } + public string UserId { get; set; } + public DateTime CreatedAt { get; set; } + } + + [Serializable] + private class TempShareData + { + public List Shares { get; set; } = new(); + } + + #endregion Data Classes + + #region Public Methods + + public void AddTempShare(ShareApiHelper.ShareContentType contentType, string contentId, string userId) + { + ShareBubblesMod.Logger.Msg($"Adding temp share for {userId}..."); + + TempShare share = new() + { + ContentType = contentType, + ContentId = contentId, + UserId = userId, + CreatedAt = DateTime.UtcNow + }; + + // So we can monitor when they leave + grantedSharesThisSession.Add(userId); + + shareData.Shares.Add(share); + SaveShares(); + } + + #endregion Public Methods + + #region Event Handlers + + private void InitializeEvents() + { + CVRGameEventSystem.Instance.OnConnected.AddListener(OnConnected); + CVRGameEventSystem.Player.OnLeaveEntity.AddListener(OnPlayerLeft); + Application.quitting += OnApplicationQuit; + } + + private async void OnConnected(string _) + { + if (Instances.IsReconnecting) + return; + + if (shareData.Shares.Count == 0) + return; + + ShareBubblesMod.Logger.Msg($"Revoking {shareData.Shares.Count} shares from last session..."); + + // Attempt to revoke all shares when connecting to an online instance + // This will catch shares not revoked last session, and in prior instance + await RevokeAllShares(); + + ShareBubblesMod.Logger.Msg($"There are {shareData.Shares.Count} shares remaining."); + } + + private async void OnPlayerLeft(CVRPlayerEntity player) + { + // If they were granted shares this session, revoke them + if (grantedSharesThisSession.Contains(player.Uuid)) + await RevokeSharesForUser(player.Uuid); + } + + private void OnApplicationQuit() + { + // Attempt to revoke all shares when the game closes + RevokeAllShares().GetAwaiter().GetResult(); + } + + #endregion Event Handlers + + #region Share Management + + private async Task RevokeSharesForUser(string userId) + { + for (int i = shareData.Shares.Count - 1; i >= 0; i--) + { + TempShare share = shareData.Shares[i]; + if (share.UserId != userId) continue; + + if (!await RevokeShare(share)) + continue; + + shareData.Shares.RemoveAt(i); + SaveShares(); + } + } + + private async Task RevokeAllShares() + { + for (int i = shareData.Shares.Count - 1; i >= 0; i--) + { + if (!await RevokeShare(shareData.Shares[i])) + continue; + + shareData.Shares.RemoveAt(i); + SaveShares(); + } + } + + private async Task RevokeShare(TempShare share) + { + try + { + var response = await ShareApiHelper.ReleaseShareAsync( + share.ContentType, + share.ContentId, + share.UserId + ); + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Debug.LogError($"Failed to revoke share: {ex.Message}"); + return false; + } + } + + #endregion Share Management + + #region File Operations + + private void LoadShares() + { + try + { + lock (fileLock) + { + if (!File.Exists(savePath)) + return; + + string json = File.ReadAllText(savePath); + shareData = JsonConvert.DeserializeObject(json) ?? new TempShareData(); + } + } + catch (Exception ex) + { + Debug.LogError($"Failed to load temp shares: {ex.Message}"); + shareData = new TempShareData(); + } + } + + private void SaveShares() + { + try + { + lock (fileLock) + { + string directory = Path.GetDirectoryName(savePath); + if (!Directory.Exists(directory)) + { + if (directory == null) throw new Exception("Failed to get directory path"); + Directory.CreateDirectory(directory); + } + + string json = JsonConvert.SerializeObject(shareData, Formatting.Indented); + File.WriteAllText(savePath, json); + } + } + catch (Exception ex) + { + Debug.LogError($"Failed to save temp shares: {ex.Message}"); + } + } + + #endregion +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs new file mode 100644 index 0000000..8aecac2 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs @@ -0,0 +1,11 @@ +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Constants + + private const string NetworkVersion = "1.0.1"; // change each time network protocol changes + private const string ModId = $"NAK.SB:{NetworkVersion}"; // Cannot exceed 32 characters + + #endregion Constants +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs new file mode 100644 index 0000000..86d9758 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs @@ -0,0 +1,38 @@ +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Enums + + // Remotes will ask owner of a bubble to share its content + // Owner can accept or deny the request, and the result will be sent back to the remote + // TODO: need rate limiting to prevent malicious users from spamming requests + + private enum MessageType : byte + { + // Lifecycle of a bubble + BubbleCreated, // bubbleId, bubbleType, position, rotation + BubbleDestroyed, // bubbleId + BubbleMoved, // bubbleId, position, rotation + + // Requesting share of a bubbles content + BubbleClaimRequest, // bubbleId + BubbleClaimResponse, // bubbleId, success + + // Requesting all active bubbles on instance join + ActiveBubblesRequest, // none + ActiveBubblesResponse, // int count, bubbleId[count], bubbleType[count], position[count], rotation[count] + + // Notification of share being sent to a user + DirectShareNotification, // userId, contentType, contentId + } + + private enum MNLogLevel : byte + { + Info = 0, + Warning = 1, + Error = 2 + } + + #endregion Enums +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs new file mode 100644 index 0000000..3ee6e81 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs @@ -0,0 +1,27 @@ +using ABI_RC.Core.Networking; +using DarkRift; +using UnityEngine; + +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Private Methods + + private static bool CanSendModNetworkMessage() + => _isSubscribedToModNetwork && IsConnectedToGameNetwork(); + + private static bool IsConnectedToGameNetwork() + { + return NetworkManager.Instance != null + && NetworkManager.Instance.GameNetwork != null + && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; + } + + /// Checks if a Vector3 is invalid (NaN or Infinity). + private static bool IsInvalidVector3(Vector3 vector) + => float.IsNaN(vector.x) || float.IsNaN(vector.y) || float.IsNaN(vector.z) + || float.IsInfinity(vector.x) || float.IsInfinity(vector.y) || float.IsInfinity(vector.z); + + #endregion Private Methods +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs new file mode 100644 index 0000000..708427a --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs @@ -0,0 +1,200 @@ +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.ModNetwork; +using NAK.ShareBubbles.API; +using UnityEngine; + +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Reset Method + + public static void Reset() + { + LoggerInbound("ModNetwork has been reset."); + } + + #endregion Reset Method + + #region Inbound Methods + + private static bool ShouldReceiveFromSender(string sender) + { + // if (_disallowedForSession.Contains(sender)) + // return false; // ignore messages from disallowed users + + if (MetaPort.Instance.blockedUserIds.Contains(sender)) + return false; // ignore messages from blocked users + + // if (ModSettings.Entry_FriendsOnly.Value && !Friends.FriendsWith(sender)) + // return false; // ignore messages from non-friends if friends only is enabled + + // if (StickerSystem.Instance.IsRestrictedInstance) // ignore messages from users when the world is restricted. This also includes older or modified version of Stickers mod. + // return false; + + return true; + } + + private static void HandleMessageReceived(ModNetworkMessage msg) + { + try + { + string sender = msg.Sender; + msg.Read(out byte msgTypeRaw); + + if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) + return; + + if (!ShouldReceiveFromSender(sender)) + return; + + LoggerInbound($"Received message from {msg.Sender}, Type: {(MessageType)msgTypeRaw}"); + + switch ((MessageType)msgTypeRaw) + { + case MessageType.BubbleCreated: + HandleBubbleCreated(msg); + break; + case MessageType.BubbleDestroyed: + HandleBubbleDestroyed(msg); + break; + case MessageType.BubbleMoved: + HandleBubbleMoved(msg); + break; + case MessageType.BubbleClaimRequest: + HandleBubbleClaimRequest(msg); + break; + case MessageType.BubbleClaimResponse: + HandleBubbleClaimResponse(msg); + break; + case MessageType.ActiveBubblesRequest: + HandleActiveBubblesRequest(msg); + break; + case MessageType.ActiveBubblesResponse: + HandleActiveBubblesResponse(msg); + break; + case MessageType.DirectShareNotification: + HandleDirectShareNotification(msg); + break; + default: + LoggerInbound($"Invalid message type received: {msgTypeRaw}"); + break; + } + } + catch (Exception e) + { + LoggerInbound($"Error handling message from {msg.Sender}: {e.Message}", MNLogLevel.Warning); + } + } + + private static void HandleBubbleCreated(ModNetworkMessage msg) + { + msg.Read(out Vector3 position); + msg.Read(out Vector3 rotation); + msg.Read(out ShareBubbleData data); + + // Check if the position or rotation is invalid + if (IsInvalidVector3(position) + || IsInvalidVector3(rotation)) + return; + + // Check if we should ignore this bubble + // Client enforced, sure, but only really matters for share claim, which is requires bubble owner to verify + if (data.Rule == ShareRule.FriendsOnly && !Friends.FriendsWith(msg.Sender)) + { + LoggerInbound($"Bubble with ID {data.BubbleId} is FriendsOnly and sender is not a friend, ignoring."); + return; + } + + ShareBubbleManager.Instance.OnRemoteBubbleCreated(msg.Sender, position, rotation, data); + + LoggerInbound($"Bubble with ID {data.BubbleId} created at {position}"); + } + + private static void HandleBubbleDestroyed(ModNetworkMessage msg) + { + msg.Read(out uint bubbleNetworkId); + + // Destroy bubble + ShareBubbleManager.Instance.OnRemoteBubbleDestroyed(msg.Sender, bubbleNetworkId); + + LoggerInbound($"Bubble with ID {bubbleNetworkId} destroyed"); + } + + + private static void HandleBubbleMoved(ModNetworkMessage msg) + { + msg.Read(out uint bubbleId); + msg.Read(out Vector3 position); + msg.Read(out Vector3 rotation); + + // Check if the position or rotation is invalid + if (IsInvalidVector3(position) + || IsInvalidVector3(rotation)) + return; + + ShareBubbleManager.Instance.OnRemoteBubbleMoved(msg.Sender, bubbleId, position, rotation); + LoggerInbound($"Bubble {bubbleId} moved to {position}"); + } + + private static void HandleBubbleClaimRequest(ModNetworkMessage msg) + { + msg.Read(out uint bubbleNetworkId); + + ShareBubbleManager.Instance.OnRemoteBubbleClaimRequest(msg.Sender, bubbleNetworkId); + + LoggerInbound($"Bubble with ID {bubbleNetworkId} claimed by {msg.Sender}"); + } + + private static void HandleBubbleClaimResponse(ModNetworkMessage msg) + { + msg.Read(out uint bubbleNetworkId); + msg.Read(out bool claimAccepted); + + ShareBubbleManager.Instance.OnRemoteBubbleClaimResponse(msg.Sender, bubbleNetworkId, claimAccepted); + + LoggerInbound($"Bubble with ID {bubbleNetworkId} claim response: {claimAccepted}"); + } + + private static void HandleActiveBubblesRequest(ModNetworkMessage msg) + { + LoggerInbound($"Received ActiveBubblesRequest from {msg.Sender}"); + + ShareBubbleManager.Instance.OnRemoteActiveBubbleRequest(msg.Sender); + } + + private static void HandleActiveBubblesResponse(ModNetworkMessage msg) + { + try + { + // hacky, but im tired and didnt think + ShareBubbleManager.Instance.SetRemoteBatchCreateBubbleState(msg.Sender, true); + + msg.Read(out int bubbleCount); + LoggerInbound($"Received ActiveBubblesResponse from {msg.Sender} with {bubbleCount} bubbles"); + + // Create up to MaxBubblesPerUser bubbles + for (int i = 0; i < Mathf.Min(bubbleCount, ShareBubbleManager.MaxBubblesPerUser); i++) + HandleBubbleCreated(msg); + } + catch (Exception e) + { + LoggerInbound($"Error handling ActiveBubblesResponse from {msg.Sender}: {e.Message}", MNLogLevel.Warning); + } + finally + { + ShareBubbleManager.Instance.SetRemoteBatchCreateBubbleState(msg.Sender, false); + } + } + + private static void HandleDirectShareNotification(ModNetworkMessage msg) + { + msg.Read(out ShareApiHelper.ShareContentType contentType); + msg.Read(out string contentId); + + LoggerInbound($"Received DirectShareNotification from {msg.Sender} for {contentType} {contentId}"); + } + + #endregion Inbound Methods +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Logging.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Logging.cs new file mode 100644 index 0000000..4f55048 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Logging.cs @@ -0,0 +1,32 @@ +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Network Logging + + private static void LoggerInbound(string message, MNLogLevel type = MNLogLevel.Info) + => _logger($"[Inbound] {message}", type, ModSettings.Debug_NetworkInbound.Value); + + private static void LoggerOutbound(string message, MNLogLevel type = MNLogLevel.Info) + => _logger($"[Outbound] {message}", type, ModSettings.Debug_NetworkOutbound.Value); + + private static void _logger(string message, MNLogLevel type = MNLogLevel.Info, bool loggerSetting = true) + { + switch (type) + { + default: + case MNLogLevel.Info when loggerSetting: + ShareBubblesMod.Logger.Msg(message); + break; + case MNLogLevel.Warning when loggerSetting: + ShareBubblesMod.Logger.Warning(message); + break; + case MNLogLevel.Error: // Error messages are always logged, regardless of setting + ShareBubblesMod.Logger.Error(message); + break; + + } + } + + #endregion Network Logging +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs new file mode 100644 index 0000000..2097211 --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs @@ -0,0 +1,36 @@ +using ABI_RC.Systems.ModNetwork; + +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Mod Network Internals + + private static bool _isSubscribedToModNetwork; + + internal static void Initialize() + { + // Packs the share bubble data a bit, also makes things just nicer + ShareBubbleData.AddConverterForModNetwork(); + } + + internal static void Subscribe() + { + ModNetworkManager.Subscribe(ModId, HandleMessageReceived); + + _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); + if (!_isSubscribedToModNetwork) ShareBubblesMod.Logger.Error("Failed to subscribe to Mod Network! This should not happen."); + else ShareBubblesMod.Logger.Msg("Subscribed to Mod Network."); + } + + internal static void Unsubscribe() + { + ModNetworkManager.Unsubscribe(ModId); + + _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); + if (_isSubscribedToModNetwork) ShareBubblesMod.Logger.Error("Failed to unsubscribe from Mod Network! This should not happen."); + else ShareBubblesMod.Logger.Msg("Unsubscribed from Mod Network."); + } + + #endregion Mod Network Internals +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs new file mode 100644 index 0000000..8f2d85d --- /dev/null +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs @@ -0,0 +1,130 @@ +using ABI_RC.Systems.ModNetwork; +using NAK.ShareBubbles.API; +using UnityEngine; + +namespace NAK.ShareBubbles.Networking; + +public static partial class ModNetwork +{ + #region Outbound Methods + + public static void SendBubbleCreated(Vector3 position, Quaternion rotation, ShareBubbleData data) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId); + modMsg.Write((byte)MessageType.BubbleCreated); + modMsg.Write(position); + modMsg.Write(rotation.eulerAngles); + modMsg.Write(data); + modMsg.Send(); + + LoggerOutbound($"Sending BubbleCreated message for bubble {data.BubbleId}"); + } + + public static void SendBubbleDestroyed(uint bubbleNetworkId) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId); + modMsg.Write((byte)MessageType.BubbleDestroyed); + modMsg.Write(bubbleNetworkId); + modMsg.Send(); + + LoggerOutbound($"Sending BubbleDestroyed message for bubble {bubbleNetworkId}"); + } + + public static void SendBubbleMove(int bubbleId, Vector3 position, Quaternion rotation) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage msg = new(ModId); + msg.Write((byte)MessageType.BubbleMoved); + msg.Write(bubbleId); + msg.Write(position); + msg.Write(rotation.eulerAngles); + msg.Send(); + + LoggerOutbound($"Sending BubbleMove message for bubble {bubbleId}"); + } + + public static void SendBubbleClaimRequest(string bubbleOwnerId, uint bubbleNetworkId) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId, bubbleOwnerId); + modMsg.Write((byte)MessageType.BubbleClaimRequest); + modMsg.Write(bubbleNetworkId); + modMsg.Send(); + + LoggerOutbound($"Sending BubbleClaimRequest message for bubble {bubbleNetworkId}"); + } + + public static void SendBubbleClaimResponse(string requesterUserId, uint bubbleNetworkId, bool success) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId, requesterUserId); + modMsg.Write((byte)MessageType.BubbleClaimResponse); + modMsg.Write(bubbleNetworkId); + modMsg.Write(success); + modMsg.Send(); + + LoggerOutbound($"Sending BubbleClaimResponse message for bubble {bubbleNetworkId}"); + } + + public static void SendActiveBubblesRequest() + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId); + modMsg.Write((byte)MessageType.ActiveBubblesRequest); + modMsg.Send(); + + LoggerOutbound("Sending ActiveBubblesRequest message"); + } + + public static void SendActiveBubblesResponse(string requesterUserId, List activeBubbles) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId, requesterUserId); + modMsg.Write((byte)MessageType.ActiveBubblesResponse); + modMsg.Write(activeBubbles.Count); + + foreach (ShareBubble bubble in activeBubbles) + { + Transform parent = bubble.transform.parent; + modMsg.Write(parent.position); + modMsg.Write(parent.rotation.eulerAngles); + modMsg.Write(bubble.Data); + } + + modMsg.Send(); + + LoggerOutbound($"Sending ActiveBubblesResponse message with {activeBubbles.Count} bubbles"); + } + + public static void SendDirectShareNotification(string userId, ShareApiHelper.ShareContentType contentType, string contentId) + { + if (!CanSendModNetworkMessage()) + return; + + using ModNetworkMessage modMsg = new(ModId, userId); + modMsg.Write((byte)MessageType.DirectShareNotification); + modMsg.Write(contentType); + modMsg.Write(contentId); + modMsg.Send(); + + LoggerOutbound($"Sending DirectShareNotification message for {userId}"); + } + + #endregion Outbound Methods +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/ShareBubble.cs b/ShareBubbles/ShareBubbles/ShareBubble.cs new file mode 100644 index 0000000..5ab9139 --- /dev/null +++ b/ShareBubbles/ShareBubbles/ShareBubble.cs @@ -0,0 +1,363 @@ +using UnityEngine; +using ABI_RC.Core.Networking; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using NAK.ShareBubbles.Impl; +using NAK.ShareBubbles.Networking; +using NAK.ShareBubbles.UI; +using TMPro; +using System.Collections; + +namespace NAK.ShareBubbles; + +public struct BubbleContentInfo +{ + public string Name { get; set; } + public string Label { get; set; } + public Texture2D Icon { get; set; } +} + +public class ShareBubble : MonoBehaviour +{ + // Shader properties + private static readonly int _MatcapHueShiftId = Shader.PropertyToID("_MatcapHueShift"); + private static readonly int _MatcapReplaceId = Shader.PropertyToID("_MatcapReplace"); + + #region Enums + + // State of fetching content info from api + private enum InfoFetchState + { + Fetching, // Fetching content info from api + Error, // Content info fetch failed + Ready // Content info fetched successfully + } + + // State of asking for content claim from owner + private enum ClaimState + { + Waiting, // No claim request sent + Requested, // Claim request sent to owner, waiting for response + Rejected, // Claim request rejected by owner + Permitted, // Claim request accepted by owner, or content is already unlocked + } + + #endregion Enums + + #region Properties + + private InfoFetchState _currentApiState; + + private InfoFetchState CurrentApiState + { + get => _currentApiState; + set + { + _currentApiState = value; + UpdateVisualState(); + } + } + + private ClaimState _currentClaimState; + + private ClaimState CurrentClaimState + { + get => _currentClaimState; + set + { + _currentClaimState = value; + UpdateClaimState(); + } + } + + public bool IsDestroyed { get; set; } + public string OwnerId; + public ShareBubbleData Data { get; private set; } + public bool IsOwnBubble => OwnerId == MetaPort.Instance.ownerId; + public bool IsPermitted => (implementation?.IsPermitted ?? false) || CurrentClaimState == ClaimState.Permitted; + + #endregion Properties + + #region Private Fields + + private IShareBubbleImpl implementation; + private DateTime? lastClaimRequest; + private const float ClaimTimeout = 30f; + private Coroutine claimStateResetCoroutine; + + private float lifetimeTotal; + + #endregion Private Fields + + #region Serialized Fields + + [Header("Visual Components")] + [SerializeField] private Renderer hexRenderer; + [SerializeField] private Renderer iconRenderer; + [SerializeField] private TextMeshPro droppedByText; + [SerializeField] private TextMeshPro contentName; + [SerializeField] private TextMeshPro contentLabel; + [SerializeField] private BubbleAnimController animController; + + [Header("State Objects")] + [SerializeField] private GameObject loadingState; + [SerializeField] private GameObject lockedState; + [SerializeField] private GameObject errorState; + [SerializeField] private GameObject contentInfo; + + [Header("Button UI")] + [SerializeField] private TextMeshProUGUI equipButtonLabel; + [SerializeField] private TextMeshProUGUI claimButtonLabel; + + #endregion Serialized Fields + + #region Public Methods + + public void SetEquipButtonLabel(string label) + { + equipButtonLabel.text = label; + } + + #endregion Public Methods + + #region Lifecycle Methods + + public async void Initialize(ShareBubbleData data, IShareBubbleImpl impl) + { + animController = GetComponent(); + + Data = data; + implementation = impl; + implementation.Initialize(this); + + CurrentApiState = InfoFetchState.Fetching; + CurrentClaimState = ClaimState.Waiting; + + string playerName = IsOwnBubble + ? AuthManager.Username + : CVRPlayerManager.Instance.TryGetPlayerName(OwnerId); + + droppedByText.text = $"Dropped by\n{playerName}"; + + if (Data.Lifetime == ShareLifetime.TwoMinutes) + lifetimeTotal = 120f; + + try + { + await implementation.FetchContentInfo(); + if (this == null || gameObject == null) return; // Bubble was destroyed during fetch + CurrentApiState = InfoFetchState.Ready; + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Failed to load content info: {ex}"); + if (this == null || gameObject == null) return; // Bubble was destroyed during fetch + CurrentApiState = InfoFetchState.Error; + } + } + + private void Update() + { + if (Data.Lifetime == ShareLifetime.Session) + return; + + float lifetimeElapsed = (float)(DateTime.UtcNow - Data.CreatedAt).TotalSeconds; + float lifetimeProgress = Mathf.Clamp01(lifetimeElapsed / lifetimeTotal); + animController.SetLifetimeVisual(lifetimeProgress); + if (lifetimeProgress >= 1f) + { + //ShareBubblesMod.Logger.Msg($"Bubble expired: {Data.BubbleId}"); + Destroy(gameObject); + } + } + + private void OnDestroy() + { + implementation?.Cleanup(); + if (ShareBubbleManager.Instance != null && !IsDestroyed) + ShareBubbleManager.Instance.OnBubbleDestroyed(OwnerId, Data.BubbleId); + } + + #endregion Lifecycle Methods + + #region Visual State Management + + private void UpdateVisualState() + { + loadingState.SetActive(CurrentApiState == InfoFetchState.Fetching); + errorState.SetActive(CurrentApiState == InfoFetchState.Error); + contentInfo.SetActive(CurrentApiState == InfoFetchState.Ready); + + hexRenderer.material.SetFloat(_MatcapReplaceId, + CurrentApiState == InfoFetchState.Fetching ? 0f : 1f); + + if (CurrentApiState == InfoFetchState.Ready) + { + if (IsPermitted) CurrentClaimState = ClaimState.Permitted; + animController.ShowHubPivot(); + UpdateButtonStates(); + } + } + + private void UpdateButtonStates() + { + bool canClaim = !IsPermitted && Data.Access != ShareAccess.None && CanRequestClaim(); + bool canEquip = IsPermitted && CurrentApiState == InfoFetchState.Ready; + + claimButtonLabel.transform.parent.gameObject.SetActive(canClaim); // Only show claim button if content is locked & claimable + equipButtonLabel.transform.parent.gameObject.SetActive(canEquip); // Only show equip button if content is unlocked + + if (canClaim) UpdateClaimButtonState(); + } + + private void UpdateClaimButtonState() + { + switch (CurrentClaimState) + { + case ClaimState.Requested: + claimButtonLabel.text = "Claiming..."; + break; + case ClaimState.Rejected: + claimButtonLabel.text = "Denied :("; + StartClaimStateResetTimer(); + break; + case ClaimState.Permitted: + claimButtonLabel.text = "Claimed!"; + StartClaimStateResetTimer(); + break; + default: + claimButtonLabel.text = " Claim"; + break; + } + } + + private void StartClaimStateResetTimer() + { + if (claimStateResetCoroutine != null) StopCoroutine(claimStateResetCoroutine); + claimStateResetCoroutine = StartCoroutine(ResetClaimStateAfterDelay()); + } + + private IEnumerator ResetClaimStateAfterDelay() + { + yield return new WaitForSeconds(2f); + CurrentClaimState = ClaimState.Waiting; + UpdateClaimButtonState(); + } + + private void UpdateClaimState() + { + bool isLocked = !IsPermitted && CurrentApiState == InfoFetchState.Ready; + lockedState.SetActive(isLocked); // Only show locked state if content is locked & ready + UpdateButtonStates(); + } + + #endregion Visual State Management + + #region Content Info Updates + + public void SetHue(float hue) + { + hexRenderer.material.SetFloat(_MatcapHueShiftId, hue); + } + + public void UpdateContent(BubbleContentInfo info) + { + contentName.text = info.Name; + contentLabel.text = info.Label; + if (info.Icon != null) + iconRenderer.material.mainTexture = info.Icon; + } + + #endregion Content Info Updates + + #region Interaction Methods + + public void ViewDetailsPage() + { + if (CurrentApiState != InfoFetchState.Ready) return; + implementation?.ViewDetailsPage(); + } + + public void EquipContent() + { + // So uh, selecting a prop in will also spawn it as interact is down in same frame + // Too lazy to fix so we gonna wait a frame before actually equipping :) + StartCoroutine(ActuallyEquipContentNextFrame()); + + return; + IEnumerator ActuallyEquipContentNextFrame() + { + yield return null; // Wait a frame + + if (CurrentApiState != InfoFetchState.Ready) yield break; + + if (CanRequestClaim()) // Only possible on hold & click, as button is hidden when not permitted + { + RequestContentClaim(); + yield break; + } + + if (!IsPermitted) + yield break; + + implementation?.EquipContent(); + } + } + + public void RequestContentClaim() + { + if (!CanRequestClaim()) return; + if (!RequestClaimTimeoutInactive()) return; + + lastClaimRequest = DateTime.Now; + ModNetwork.SendBubbleClaimRequest(OwnerId, Data.BubbleId); + CurrentClaimState = ClaimState.Requested; + + return; + bool RequestClaimTimeoutInactive() + { + if (!lastClaimRequest.HasValue) return true; + TimeSpan timeSinceLastRequest = DateTime.Now - lastClaimRequest.Value; + return timeSinceLastRequest.TotalSeconds >= ClaimTimeout; + } + } + + private bool CanRequestClaim() + { + if (IsPermitted) return false; + if (IsOwnBubble) return false; + return OwnerId == implementation.AuthorId; + } + + #endregion Interaction Methods + + #region Mod Network Callbacks + + public void OnClaimResponseReceived(bool accepted) + { + lastClaimRequest = null; + CurrentClaimState = accepted ? ClaimState.Permitted : ClaimState.Rejected; + UpdateButtonStates(); + } + + public void OnRemoteWantsClaim(string requesterId) + { + if (!IsOwnBubble) return; + + bool isAllowed = implementation.IsPermitted || + Data.Rule == ShareRule.Everyone || + (Data.Rule == ShareRule.FriendsOnly && Friends.FriendsWith(requesterId)); + + if (!isAllowed) + { + ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, false); + return; + } + + implementation.HandleClaimAccept(requesterId, + wasAccepted => ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, wasAccepted)); + } + + #endregion Mod Network Callbacks +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/ShareBubbleManager.cs b/ShareBubbles/ShareBubbles/ShareBubbleManager.cs new file mode 100644 index 0000000..2aeaec8 --- /dev/null +++ b/ShareBubbles/ShareBubbles/ShareBubbleManager.cs @@ -0,0 +1,424 @@ +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Core.UI; +using ABI_RC.Systems.GameEventSystem; +using ABI_RC.Systems.Gravity; +using NAK.ShareBubbles.Impl; +using NAK.ShareBubbles.Networking; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace NAK.ShareBubbles; + +public class ShareBubbleManager +{ + #region Constants + + public const int MaxBubblesPerUser = 3; + private const float BubbleCreationCooldown = 1f; + + #endregion Constants + + #region Singleton + + public static ShareBubbleManager Instance { get; private set; } + + public static void Initialize() + { + if (Instance != null) + return; + + Instance = new ShareBubbleManager(); + RegisterDefaultBubbleTypes(); + + CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); + } + + private static void RegisterDefaultBubbleTypes() + { + ShareBubbleRegistry.RegisterBubbleType(GetMaskedHash("Avatar"), () => new AvatarBubbleImpl()); + ShareBubbleRegistry.RegisterBubbleType(GetMaskedHash("Spawnable"), () => new SpawnableBubbleImpl()); + } + + public static uint GetMaskedHash(string typeName) => StringToHash(typeName) & 0xFFFF; + + public static uint GenerateBubbleId(string content, uint implTypeHash) + { + // Allows us to extract the implementation type hash from the bubble ID + return (implTypeHash << 16) | GetMaskedHash(content); + } + + private static uint StringToHash(string str) + { + unchecked + { + uint hash = 5381; + foreach (var ch in str) hash = ((hash << 5) + hash) + ch; + return hash; + } + } + + #endregion + + #region Fields + + private class PlayerBubbleData + { + // Can only ever be true when the batched bubble message is what created the player data + public bool IgnoreBubbleCreationCooldown; + public float LastBubbleCreationTime; + public readonly Dictionary BubblesByLocalId = new(); + } + + private readonly Dictionary playerBubbles = new(); + + public bool IsPlacingBubbleMode { get; set; } + private ShareBubbleData selectedBubbleData; + + #endregion Fields + + #region Game Events + + private void OnPlayerSetupStart() + { + CVRGameEventSystem.Instance.OnConnected.AddListener(OnConnected); + CVRGameEventSystem.Player.OnLeaveEntity.AddListener(OnPlayerLeft); + } + + private void OnConnected(string _) + { + if (Instances.IsReconnecting) + return; + + // Clear all bubbles on disconnect (most should have died during world unload) + foreach (PlayerBubbleData playerData in playerBubbles.Values) + { + foreach (ShareBubble bubble in playerData.BubblesByLocalId.Values.ToList()) + DestroyBubble(bubble); + } + playerBubbles.Clear(); + + // This also acts to signal to other clients we rejoined and need our bubbles cleared + ModNetwork.SendActiveBubblesRequest(); + } + + private void OnPlayerLeft(CVRPlayerEntity player) + { + if (!playerBubbles.TryGetValue(player.Uuid, out PlayerBubbleData playerData)) + return; + + foreach (ShareBubble bubble in playerData.BubblesByLocalId.Values.ToList()) + DestroyBubble(bubble); + + playerBubbles.Remove(player.Uuid); + } + + #endregion Game Events + + #region Local Operations + + public void DropBubbleInFront(ShareBubbleData data) + { + PlayerSetup localPlayer = PlayerSetup.Instance; + string localPlayerId = MetaPort.Instance.ownerId; + + if (!CanPlayerCreateBubble(localPlayerId)) + { + ShareBubblesMod.Logger.Msg("Bubble creation on cooldown!"); + return; + } + + float playSpaceScale = localPlayer.GetPlaySpaceScale(); + Vector3 playerForward = localPlayer.GetPlayerForward(); + Vector3 position = localPlayer.activeCam.transform.position + playerForward * 0.5f * playSpaceScale; + + if (Physics.Raycast(position, + localPlayer.CharacterController.GetGravityDirection(), + out RaycastHit raycastHit, 4f, localPlayer.dropPlacementMask)) + { + CreateBubbleForPlayer(localPlayerId, raycastHit.point, + Quaternion.LookRotation(playerForward, raycastHit.normal), data); + return; + } + + CreateBubbleForPlayer(localPlayerId, position, + Quaternion.LookRotation(playerForward, -localPlayer.transform.up), data); + } + + public void SelectBubbleForPlace(string contentCouiPath, string contentName, ShareBubbleData data) + { + selectedBubbleData = data; + IsPlacingBubbleMode = true; + CohtmlHud.Instance.SelectPropToSpawn( + contentCouiPath, + contentName, + "Selected content to bubble:"); + } + + public void PlaceSelectedBubbleFromControllerRay(Transform transform) + { + Vector3 position = transform.position; + Vector3 forward = transform.forward; + + // Every layer other than IgnoreRaycast, PlayerLocal, PlayerClone, PlayerNetwork, and UI Internal + const int LayerMask = ~((1 << 2) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 15)); + if (!Physics.Raycast(position, forward, out RaycastHit hit, + 10f, LayerMask, QueryTriggerInteraction.Ignore)) + return; // No hit + + if (selectedBubbleData.ImplTypeHash == 0) + return; + + Vector3 bubblePos = hit.point; + + PlayerSetup localPlayer = PlayerSetup.Instance; + Vector3 playerPos = localPlayer.GetPlayerPosition(); + + // Sample gravity at bubble position to get bubble orientation + Vector3 bubbleUp = -GravitySystem.TryGetResultingGravity(bubblePos, false).AppliedGravity.normalized; + Vector3 bubbleForward = Vector3.ProjectOnPlane((playerPos - bubblePos).normalized, bubbleUp); + + // Bubble faces player aligned with gravity + Quaternion bubbleRot = Quaternion.LookRotation(bubbleForward, bubbleUp); + + CreateBubbleForPlayer(MetaPort.Instance.ownerId, bubblePos, bubbleRot, selectedBubbleData); + } + + #endregion Local Operations + + #region Player Callbacks + + private void CreateBubbleForPlayer( + string playerId, + Vector3 position, + Quaternion rotation, + ShareBubbleData data) + { + GameObject bubbleRootObject = null; + try + { + if (!CanPlayerCreateBubble(playerId)) + return; + + if (!ShareBubbleRegistry.TryCreateImplementation(data.ImplTypeHash, out IShareBubbleImpl impl)) + { + Debug.LogError($"Failed to create bubble: Unknown bubble type hash: {data.ImplTypeHash}"); + return; + } + + // Get or create player data + if (!playerBubbles.TryGetValue(playerId, out PlayerBubbleData playerData)) + { + playerData = new PlayerBubbleData(); + playerBubbles[playerId] = playerData; + } + + // If a bubble with this ID already exists for this player, destroy it + if (playerData.BubblesByLocalId.TryGetValue(data.BubbleId, out ShareBubble existingBubble)) + { + ShareBubblesMod.Logger.Msg($"Replacing existing bubble: {data.BubbleId}"); + DestroyBubble(existingBubble); + } + // Check bubble limit only if we're not replacing an existing bubble + else if (playerData.BubblesByLocalId.Count >= MaxBubblesPerUser) + { + ShareBubble oldestBubble = playerData.BubblesByLocalId.Values + .OrderBy(b => b.Data.CreatedAt) + .First(); + ShareBubblesMod.Logger.Msg($"Bubble limit reached, destroying oldest bubble: {oldestBubble.Data.BubbleId}"); + DestroyBubble(oldestBubble); + } + + bubbleRootObject = Object.Instantiate(ShareBubblesMod.SharingBubblePrefab); + bubbleRootObject.transform.SetLocalPositionAndRotation(position, rotation); + bubbleRootObject.SetActive(true); + + Transform bubbleTransform = bubbleRootObject.transform.GetChild(0); + if (bubbleTransform == null) + throw new InvalidOperationException("Bubble prefab is missing expected child transform"); + + (float targetHeight, float scaleModifier) = GetBubbleHeightAndScale(); + bubbleTransform.localPosition = new Vector3(0f, targetHeight, 0f); + bubbleTransform.localScale = new Vector3(scaleModifier, scaleModifier, scaleModifier); + + ShareBubble bubble = bubbleRootObject.GetComponent(); + if (bubble == null) + throw new InvalidOperationException("Bubble prefab is missing ShareBubble component"); + + bubble.OwnerId = playerId; + bubble.Initialize(data, impl); + playerData.BubblesByLocalId[data.BubbleId] = bubble; + playerData.LastBubbleCreationTime = Time.time; + + if (playerId == MetaPort.Instance.ownerId) + ModNetwork.SendBubbleCreated(position, rotation, data); + } + catch (Exception ex) + { + Debug.LogError($"Failed to create bubble (ID: {data.BubbleId}, Owner: {playerId}): {ex}"); + + // Clean up the partially created bubble if it exists + if (bubbleRootObject != null) + { + Object.DestroyImmediate(bubbleRootObject); + + // Remove from player data if it was added + if (playerBubbles.TryGetValue(playerId, out PlayerBubbleData playerData)) + playerData.BubblesByLocalId.Remove(data.BubbleId); + } + } + } + + public void DestroyBubble(ShareBubble bubble) + { + if (bubble == null) return; + bubble.IsDestroyed = true; // Prevents ShareBubble.OnDestroy invoking OnBubbleDestroyed + Object.DestroyImmediate(bubble.gameObject); + OnBubbleDestroyed(bubble.OwnerId, bubble.Data.BubbleId); + } + + public void OnBubbleDestroyed(string ownerId, uint bubbleId) + { + if (playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData)) + playerData.BubblesByLocalId.Remove(bubbleId); + + if (ownerId == MetaPort.Instance.ownerId) ModNetwork.SendBubbleDestroyed(bubbleId); + } + + #endregion + + #region Remote Events + + public void SetRemoteBatchCreateBubbleState(string ownerId, bool batchCreateBubbleState) + { + // Get or create player data + if (!playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData)) + { + playerData = new PlayerBubbleData(); + playerBubbles[ownerId] = playerData; + + // If player data didn't exist, ignore bubble creation cooldown for the time we create active bubbles on join + playerData.IgnoreBubbleCreationCooldown = batchCreateBubbleState; + } + else + { + // If player data already exists, ignore batched bubble creation + // Probably makes a race condition here, but it's not critical + // This will prevent users from using the batched bubble creation when they've already created bubbles + playerData.IgnoreBubbleCreationCooldown = false; + } + } + + public void OnRemoteBubbleCreated(string ownerId, Vector3 position, Vector3 rotation, ShareBubbleData data) + { + CreateBubbleForPlayer(ownerId, position, Quaternion.Euler(rotation), data); + } + + public void OnRemoteBubbleDestroyed(string ownerId, uint bubbleId) + { + if (!playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData) || + !playerData.BubblesByLocalId.TryGetValue(bubbleId, out ShareBubble bubble)) + return; + + DestroyBubble(bubble); + } + + public void OnRemoteBubbleMoved(string ownerId, uint bubbleId, Vector3 position, Vector3 rotation) + { + if (!playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData) || + !playerData.BubblesByLocalId.TryGetValue(bubbleId, out ShareBubble bubble)) + return; + + // TODO: fix + bubble.transform.parent.SetPositionAndRotation(position, Quaternion.Euler(rotation)); + } + + public void OnRemoteBubbleClaimRequest(string requesterUserId, uint bubbleId) + { + // Get our bubble data if exists, respond to requester + string ownerId = MetaPort.Instance.ownerId; + if (!playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData) || + !playerData.BubblesByLocalId.TryGetValue(bubbleId, out ShareBubble bubble)) + return; + + bubble.OnRemoteWantsClaim(requesterUserId); + } + + public void OnRemoteBubbleClaimResponse(string ownerId, uint bubbleId, bool wasAccepted) + { + // Get senders bubble data if exists, receive response + if (!playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData) || + !playerData.BubblesByLocalId.TryGetValue(bubbleId, out ShareBubble bubble)) + return; + + bubble.OnClaimResponseReceived(wasAccepted); + } + + public void OnRemoteActiveBubbleRequest(string requesterUserId) + { + // Clear all bubbles for requester (as this msg is sent by them on initial join) + // This catches the case where they rejoin faster than we heard their leave event + if (playerBubbles.TryGetValue(requesterUserId, out PlayerBubbleData playerData)) + { + foreach (ShareBubble bubble in playerData.BubblesByLocalId.Values.ToList()) + DestroyBubble(bubble); + } + + var myBubbles = GetOwnActiveBubbles(); + if (myBubbles.Count == 0) + return; // No bubbles to send + + // Send all active bubbles to requester + ModNetwork.SendActiveBubblesResponse(requesterUserId, myBubbles); + } + + #endregion + + #region Utility Methods + + private bool CanPlayerCreateBubble(string playerId) + { + if (!playerBubbles.TryGetValue(playerId, out PlayerBubbleData playerData)) + return true; + + if (playerData.IgnoreBubbleCreationCooldown) + return true; // Only ignore for the time we create active bubbles on join + + return Time.time - playerData.LastBubbleCreationTime >= BubbleCreationCooldown; + } + + private static (float, float) GetBubbleHeightAndScale() + { + float targetHeight = 0.5f * PlayerSetup.Instance.GetAvatarHeight(); + float scaleModifier = PlayerSetup.Instance.GetPlaySpaceScale() * 1.8f; + return (targetHeight, scaleModifier); + } + + public void OnPlayerScaleChanged() + { + (float targetHeight, float scaleModifier) = GetBubbleHeightAndScale(); + + foreach (PlayerBubbleData playerData in playerBubbles.Values) + { + foreach (ShareBubble bubble in playerData.BubblesByLocalId.Values) + { + if (bubble == null) continue; + Transform bubbleOffset = bubble.transform.GetChild(0); + Vector3 localPos = bubbleOffset.localPosition; + bubbleOffset.localPosition = new Vector3(localPos.x, targetHeight, localPos.z); + bubbleOffset.localScale = new Vector3(scaleModifier, scaleModifier, scaleModifier); + } + } + } + + private List GetOwnActiveBubbles() + { + string ownerId = MetaPort.Instance.ownerId; + return playerBubbles.TryGetValue(ownerId, out PlayerBubbleData playerData) + ? playerData.BubblesByLocalId.Values.ToList() + : new List(); + } + + #endregion +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/ShareBubbleRegistry.cs b/ShareBubbles/ShareBubbles/ShareBubbleRegistry.cs new file mode 100644 index 0000000..a0eb759 --- /dev/null +++ b/ShareBubbles/ShareBubbles/ShareBubbleRegistry.cs @@ -0,0 +1,32 @@ +using NAK.ShareBubbles.Impl; + +namespace NAK.ShareBubbles; + +public delegate IShareBubbleImpl BubbleImplFactory(); + +// This is all so fucked because I wanted to allow for custom bubble types, so Stickers could maybe be shared via ShareBubbles +// but it is aaaaaaaaaaaaaaaaaaaaaaa + +public static class ShareBubbleRegistry +{ + #region Type Registration + + private static readonly Dictionary registeredTypes = new(); + + public static void RegisterBubbleType(uint typeHash, BubbleImplFactory factory) + { + registeredTypes[typeHash] = factory; + } + + public static bool TryCreateImplementation(uint typeHash, out IShareBubbleImpl implementation) + { + implementation = null; + if (!registeredTypes.TryGetValue(typeHash, out BubbleImplFactory factory)) + return false; + + implementation = factory(); + return implementation != null; + } + + #endregion Type Registration +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/BubbleAnimController.cs b/ShareBubbles/ShareBubbles/UI/BubbleAnimController.cs new file mode 100644 index 0000000..d51e880 --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/BubbleAnimController.cs @@ -0,0 +1,230 @@ +using UnityEngine; +using System.Collections; + +namespace NAK.ShareBubbles.UI +{ + public class BubbleAnimController : MonoBehaviour + { + [Header("Transform References")] + [SerializeField] private Transform centerPoint; + [SerializeField] private Transform hubPivot; + [SerializeField] private Transform base1; + [SerializeField] private Transform base2; + + [Header("Animation Settings")] + [SerializeField] private float spawnDuration = 1.2f; + [SerializeField] private float hubScaleDuration = 0.5f; + [SerializeField] private float centerHeightOffset = 0.2f; + [SerializeField] private AnimationCurve spawnBounceCurve; + [SerializeField] private AnimationCurve hubScaleCurve; + + [Header("Movement Settings")] + [SerializeField] private float positionSmoothTime = 0.1f; + [SerializeField] private float rotationSmoothTime = 0.15f; + [SerializeField] private float floatSpeed = 1f; + [SerializeField] private float floatHeight = 0.05f; + [SerializeField] private float baseRotationSpeed = 15f; + [SerializeField] private float spawnRotationSpeed = 180f; + + [Header("Inner Rotation Settings")] + [SerializeField] private float innerRotationSpeed = 0.8f; + [SerializeField] private float innerRotationRange = 10f; + [SerializeField] private AnimationCurve innerRotationGradient = AnimationCurve.Linear(0, 0.4f, 1, 1f); + + private SkinnedMeshRenderer base1Renderer; + private SkinnedMeshRenderer base2Renderer; + + private Transform[] base1Inners; + private Transform[] base2Inners; + private Vector3 centerTargetPos; + private Vector3 centerVelocity; + private float base1Rotation; + private float base2Rotation; + private float base1RotationSpeed; + private float base2RotationSpeed; + private float targetBase1RotationSpeed; + private float targetBase2RotationSpeed; + private float rotationSpeedVelocity1; + private float rotationSpeedVelocity2; + private float animationTime; + private bool isSpawning = true; + + private Quaternion[] base1InnerStartRots; + private Quaternion[] base2InnerStartRots; + + private void Start() + { + if (spawnBounceCurve.length == 0) + { + spawnBounceCurve = new AnimationCurve( + new Keyframe(0, 0, 0, 2), + new Keyframe(0.6f, 1.15f, 0, 0), + new Keyframe(0.8f, 0.95f, 0, 0), + new Keyframe(1, 1, 0, 0) + ); + } + + if (hubScaleCurve.length == 0) + { + hubScaleCurve = new AnimationCurve( + new Keyframe(0, 0, 0, 2), + new Keyframe(1, 1, 0, 0) + ); + } + + base1Inners = new Transform[3]; + base2Inners = new Transform[3]; + base1InnerStartRots = new Quaternion[3]; + base2InnerStartRots = new Quaternion[3]; + + Transform currentBase1 = base1; + Transform currentBase2 = base2; + for (int i = 0; i < 3; i++) + { + base1Inners[i] = currentBase1.GetChild(0); + base2Inners[i] = currentBase2.GetChild(0); + base1InnerStartRots[i] = base1Inners[i].localRotation; + base2InnerStartRots[i] = base2Inners[i].localRotation; + currentBase1 = base1Inners[i]; + currentBase2 = base2Inners[i]; + } + + base1RotationSpeed = spawnRotationSpeed; + base2RotationSpeed = -spawnRotationSpeed; + targetBase1RotationSpeed = spawnRotationSpeed; + targetBase2RotationSpeed = -spawnRotationSpeed; + + // hack + base1Renderer = base1.GetComponentInChildren(); + base2Renderer = base2.GetComponentInChildren(); + + ResetTransforms(); + StartCoroutine(SpawnAnimation()); + } + + private void ResetTransforms() + { + centerPoint.localScale = Vector3.zero; + centerPoint.localPosition = Vector3.zero; + hubPivot.localScale = Vector3.zero; + + base1.localScale = new Vector3(0.04f, 0.04f, 0.04f); + base1.localRotation = Quaternion.Euler(0, 180, 0); + + base2.localScale = new Vector3(0.02f, 0.02f, 0.02f); + base2.localRotation = Quaternion.Euler(0, 180, 0); + + centerTargetPos = Vector3.zero; + base1Rotation = 180f; + base2Rotation = 180f; + } + + private IEnumerator SpawnAnimation() + { + float elapsed = 0f; + + while (elapsed < spawnDuration) + { + float t = elapsed / spawnDuration; + float bounceT = spawnBounceCurve.Evaluate(t); + + // Center point and hub pivot animation with earlier start + float centerT = Mathf.Max(0, (t - 0.3f) * 1.43f); // Adjusted timing + centerPoint.localScale = Vector3.Lerp(Vector3.zero, Vector3.one, centerT); + centerTargetPos = Vector3.up * (bounceT * centerHeightOffset); + + // Base animations with inverted rotation + base1.localScale = Vector3.Lerp(new Vector3(0.04f, 0.04f, 0.04f), new Vector3(0.1f, 0.1f, 0.1f), bounceT); + base2.localScale = Vector3.Lerp(new Vector3(0.02f, 0.02f, 0.02f), new Vector3(0.06f, 0.06f, 0.06f), bounceT); + + base1Rotation += base1RotationSpeed * Time.deltaTime; + base2Rotation += base2RotationSpeed * Time.deltaTime; + + elapsed += Time.deltaTime; + yield return null; + } + + targetBase1RotationSpeed = baseRotationSpeed; + targetBase2RotationSpeed = -baseRotationSpeed; + isSpawning = false; + } + + public void ShowHubPivot() + { + StartCoroutine(ScaleHubPivot()); + } + + public void SetLifetimeVisual(float timeLeftNormalized) + { + float value = 100f - (timeLeftNormalized * 100f); + base1Renderer.SetBlendShapeWeight(0, value); + base2Renderer.SetBlendShapeWeight(0, value); + } + + private IEnumerator ScaleHubPivot() + { + float elapsed = 0f; + + while (elapsed < hubScaleDuration) + { + float t = elapsed / hubScaleDuration; + float scaleT = hubScaleCurve.Evaluate(t); + + hubPivot.localScale = Vector3.one * scaleT; + + elapsed += Time.deltaTime; + yield return null; + } + + hubPivot.localScale = Vector3.one; + } + + private void Update() + { + animationTime += Time.deltaTime; + + if (!isSpawning) + { + float floatOffset = Mathf.Sin(animationTime * floatSpeed) * floatHeight; + centerTargetPos = Vector3.up * (centerHeightOffset + floatOffset); + } + + centerPoint.localPosition = Vector3.SmoothDamp( + centerPoint.localPosition, + centerTargetPos, + ref centerVelocity, + positionSmoothTime + ); + + base1RotationSpeed = Mathf.SmoothDamp( + base1RotationSpeed, + targetBase1RotationSpeed, + ref rotationSpeedVelocity1, + rotationSmoothTime + ); + + base2RotationSpeed = Mathf.SmoothDamp( + base2RotationSpeed, + targetBase2RotationSpeed, + ref rotationSpeedVelocity2, + rotationSmoothTime + ); + + base1Rotation += base1RotationSpeed * Time.deltaTime; + base2Rotation += base2RotationSpeed * Time.deltaTime; + + base1.localRotation = Quaternion.Euler(0, base1Rotation, 0); + base2.localRotation = Quaternion.Euler(0, base2Rotation, 0); + + for (int i = 0; i < 3; i++) + { + float phase = (animationTime * innerRotationSpeed) * Mathf.Deg2Rad; + float gradientMultiplier = innerRotationGradient.Evaluate(i / 2f); + float rotationAmount = Mathf.Sin(phase) * innerRotationRange * gradientMultiplier; + + base1Inners[i].localRotation = base1InnerStartRots[i] * Quaternion.Euler(0, rotationAmount, 0); + base2Inners[i].localRotation = base2InnerStartRots[i] * Quaternion.Euler(0, -rotationAmount, 0); + } + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs new file mode 100644 index 0000000..ad2b4f4 --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs @@ -0,0 +1,51 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.ShareBubbles.UI; + +// The script 'NAK.ShareBubbles.UI.BubbleInteract' could not be instantiated! +// Must be added manually by ShareBubble creation... +public class BubbleInteract : Interactable +{ + public override bool IsInteractableWithinRange(Vector3 sourcePos) + { + return Vector3.Distance(transform.position, sourcePos) < 1.5f; + } + + public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay) + { + // Not used + } + + public override void OnInteractUp(InteractionContext context, ControllerRay controllerRay) + { + if (PlayerSetup.Instance.GetCurrentPropSelectionMode() + != PlayerSetup.PropSelectionMode.None) + return; + + // Check if the player is holding a pickup on the same hand + if (controllerRay.grabbedObject != null + && controllerRay.grabbedObject.transform == transform) + { + // Use the pickup + GetComponentInParent().EquipContent(); + controllerRay.DropObject(); // Causes null ref in ControllerRay, but doesn't break anything + return; + } + + // Open the details page + GetComponentInParent().ViewDetailsPage(); + } + + public override void OnHoverEnter(InteractionContext context, ControllerRay controllerRay) + { + // Not used + } + + public override void OnHoverExit(InteractionContext context, ControllerRay controllerRay) + { + // Not used + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/FacePlayerCamDirection.cs b/ShareBubbles/ShareBubbles/UI/FacePlayerCamDirection.cs new file mode 100644 index 0000000..e2c181c --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/FacePlayerCamDirection.cs @@ -0,0 +1,21 @@ +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.ShareBubbles.UI; + +public class FacePlayerCamDirection : MonoBehaviour +{ + private const float lerpSpeed = 5.0f; + + private void LateUpdate() + { + Transform ourTransform = transform; + Vector3 playerCamPos = PlayerSetup.Instance.activeCam.transform.position; + Vector3 playerUp = PlayerSetup.Instance.transform.up; + + Vector3 direction = (playerCamPos - ourTransform.position).normalized; + + Quaternion targetRotation = Quaternion.LookRotation(direction, playerUp); + ourTransform.rotation = Quaternion.Slerp(ourTransform.rotation, targetRotation, Time.deltaTime * lerpSpeed); + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/FacePlayerDirectionAxis.cs b/ShareBubbles/ShareBubbles/UI/FacePlayerDirectionAxis.cs new file mode 100644 index 0000000..78acf94 --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/FacePlayerDirectionAxis.cs @@ -0,0 +1,22 @@ +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.ShareBubbles.UI; + +public class FacePlayerDirectionAxis : MonoBehaviour +{ + private const float rotationSpeed = 5.0f; + + private void Update() + { + Transform ourTransform = transform; + + Vector3 ourUpDirection = ourTransform.up; + Vector3 playerDirection = PlayerSetup.Instance.GetPlayerPosition() - ourTransform.position; + + Vector3 projectedDirection = Vector3.ProjectOnPlane(playerDirection, ourUpDirection).normalized; + + Quaternion targetRotation = Quaternion.LookRotation(-projectedDirection, ourUpDirection); + ourTransform.rotation = Quaternion.Lerp(ourTransform.rotation, targetRotation, Time.deltaTime * rotationSpeed); + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/HexagonSpinner.cs b/ShareBubbles/ShareBubbles/UI/HexagonSpinner.cs new file mode 100644 index 0000000..64c8499 --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/HexagonSpinner.cs @@ -0,0 +1,71 @@ +using UnityEngine; + +namespace NAK.ShareBubbles.UI +{ + public class HexagonSpinner : MonoBehaviour + { + [Tooltip("Base distance from the center point to each child")] + [SerializeField] private float radius = 1f; + + [Tooltip("How much the radius pulses in/out")] + [SerializeField] private float radiusPulseAmount = 0.2f; + + [Tooltip("Speed of the animation")] + [SerializeField] private float animationSpeed = 3f; + + private Transform[] children; + private Vector3[] hexagonPoints; + + private void Start() + { + children = new Transform[6]; + for (int i = 0; i < 6; i++) + { + if (i < transform.childCount) + { + children[i] = transform.GetChild(i); + } + else + { + Debug.LogError("HexagonSpinner requires exactly 6 child objects!"); + enabled = false; + return; + } + } + + // Calculate base hexagon points (XY plane, Z-up) + hexagonPoints = new Vector3[6]; + for (int i = 0; i < 6; i++) + { + float angle = i * 60f * Mathf.Deg2Rad; + hexagonPoints[i] = new Vector3( + Mathf.Sin(angle), + Mathf.Cos(angle), + 0f + ); + } + } + + private void Update() + { + for (int i = 0; i < 6; i++) + { + float phaseOffset = (i * 60f * Mathf.Deg2Rad); + float currentPhase = (Time.time * animationSpeed) + phaseOffset; + + // Calculate radius variation + float currentRadius = radius + (Mathf.Sin(currentPhase) * radiusPulseAmount); + + // Position each child + Vector3 basePosition = hexagonPoints[i] * currentRadius; + children[i].localPosition = basePosition; + + // Calculate scale based on sine wave, but only show points on the "visible" half + // Remap sine wave from [-1,1] to [0,1] for cleaner scaling + float scaleMultiplier = Mathf.Sin(currentPhase); + scaleMultiplier = Mathf.Max(0f, scaleMultiplier); // Only positive values + children[i].localScale = Vector3.one * scaleMultiplier; + } + } + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs new file mode 100644 index 0000000..b6e1eda --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs @@ -0,0 +1,91 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +namespace NAK.ShareBubbles.UI; + +public class ReturnOnRelease : MonoBehaviour +{ + public float returnSpeed = 10.0f; + public float damping = 0.5f; + + private bool isReturning; + private Vector3 originalLocalPosition; + private Quaternion originalLocalRotation; + + private Vector3 positionVelocity = Vector3.zero; + private Vector3 angularVelocity = Vector3.zero; + + private Pickupable pickupable; + + private void Start() + { + Transform ourTransform = transform; + originalLocalPosition = ourTransform.localPosition; + originalLocalRotation = ourTransform.localRotation; + + // TODO: Instead of LateUpdate, use OnDrop to start a coroutine + pickupable = GetComponent(); + pickupable.onGrab.AddListener(OnPickupGrabbed); + pickupable.onDrop.AddListener(OnPickupRelease); + } + + public void OnPickupGrabbed(InteractionContext _) + { + isReturning = false; + } + + public void OnPickupRelease(InteractionContext _) + { + isReturning = true; + } + + private void LateUpdate() + { + if (isReturning) + { + // Smoothly damp position back to the original + Transform ourTransform = transform; + transform.localPosition = Vector3.SmoothDamp( + ourTransform.localPosition, + originalLocalPosition, + ref positionVelocity, + damping, + returnSpeed + ); + + // Smoothly damp rotation back to the original + transform.localRotation = SmoothDampQuaternion( + ourTransform.localRotation, + originalLocalRotation, + ref angularVelocity, + damping + ); + + // Stop returning when close enough + if (Vector3.Distance(transform.localPosition, originalLocalPosition) < 0.01f + && angularVelocity.magnitude < 0.01f) + { + isReturning = false; + transform.SetLocalPositionAndRotation(originalLocalPosition, originalLocalRotation); + } + } + } + + private Quaternion SmoothDampQuaternion(Quaternion current, Quaternion target, ref Vector3 velocity, float smoothTime) + { + // Decompose rotation to Euler angles for smoother interpolation + Vector3 currentEuler = current.eulerAngles; + Vector3 targetEuler = target.eulerAngles; + + // Perform SmoothDamp on each axis + Vector3 smoothedEuler = new Vector3( + Mathf.SmoothDampAngle(currentEuler.x, targetEuler.x, ref velocity.x, smoothTime), + Mathf.SmoothDampAngle(currentEuler.y, targetEuler.y, ref velocity.y, smoothTime), + Mathf.SmoothDampAngle(currentEuler.z, targetEuler.z, ref velocity.z, smoothTime) + ); + + // Convert back to Quaternion + return Quaternion.Euler(smoothedEuler); + } +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/ShakeSoundController.cs b/ShareBubbles/ShareBubbles/UI/ShakeSoundController.cs new file mode 100644 index 0000000..1a0ed25 --- /dev/null +++ b/ShareBubbles/ShareBubbles/UI/ShakeSoundController.cs @@ -0,0 +1,85 @@ +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +namespace NAK.ShareBubbles.UI; + +public class ShakeSoundController : MonoBehaviour +{ + [Header("Shake Detection")] + [SerializeField] private float minimumVelocityForShake = 5f; + [SerializeField] private float directionChangeThreshold = 0.8f; + [SerializeField] private float velocitySmoothing = 0.1f; + [SerializeField] private float minTimeBetweenShakes = 0.15f; + + [Header("Sound")] + [SerializeField] private AudioClip bellSound; + [SerializeField] [Range(0f, 1f)] private float minVolume = 0.8f; + [SerializeField] [Range(0f, 1f)] private float maxVolume = 1f; + [SerializeField] private float velocityToVolumeMultiplier = 0.1f; + + private AudioSource audioSource; + private Vector3 lastVelocity; + private Vector3 currentVelocity; + private Vector3 smoothedVelocity; + private float shakeTimer; + + private Pickupable _pickupable; + + private void Start() + { + audioSource = GetComponent(); + if (audioSource == null) audioSource = gameObject.AddComponent(); + audioSource.spatialize = true; + audioSource.spatialBlend = 1f; + audioSource.rolloffMode = AudioRolloffMode.Linear; + audioSource.maxDistance = 5f; + + _pickupable = GetComponent(); + + // Add source to prop mixer group so it can be muted + audioSource.outputAudioMixerGroup = RootLogic.Instance.propSfx; + + // TODO: Make jingle velocity scale with player playspace + // Make jingle velocity only sample local space, so OriginShift doesn't affect it + // Just make all this not bad... + } + + private void Update() + { + shakeTimer -= Time.deltaTime; + + if (!_pickupable.IsGrabbedByMe) + return; + + // Calculate raw velocity + currentVelocity = (transform.position - lastVelocity) / Time.deltaTime; + + // Smooth the velocity + smoothedVelocity = Vector3.Lerp(smoothedVelocity, currentVelocity, velocitySmoothing); + + // Only check for direction change if moving fast enough and cooldown has elapsed + if (smoothedVelocity.magnitude > minimumVelocityForShake && shakeTimer <= 0) + { + float directionChange = Vector3.Dot(smoothedVelocity.normalized, lastVelocity.normalized); + + if (directionChange < -directionChangeThreshold) + { + float shakePower = smoothedVelocity.magnitude * Mathf.Abs(directionChange); + PlayBellSound(shakePower); + shakeTimer = minTimeBetweenShakes; + } + } + + lastVelocity = transform.position; + } + + private void PlayBellSound(float intensity) + { + if (bellSound == null) return; + + float volume = Mathf.Clamp(intensity * velocityToVolumeMultiplier, minVolume, maxVolume); + audioSource.volume = volume; + audioSource.PlayOneShot(bellSound); + } +} \ No newline at end of file diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json new file mode 100644 index 0000000..76dadf3 --- /dev/null +++ b/ShareBubbles/format.json @@ -0,0 +1,24 @@ +{ + "_id": -1, + "name": "ShareBubbles", + "modversion": "1.0.1", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", + "description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n- Adds basic in-game Share Management:\n - Directly share content to users within the current instance.\n - Revoke granted or received content shares.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ABI Account settings!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).", + "searchtags": [ + "share", + "bubbles", + "content", + "avatars", + "props" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r41/ShareBubbles.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", + "changelog": "- Initial release.", + "embedcolor": "#f61963" +} \ No newline at end of file From 0720ab508dd960ed55e5e624cad8d0f321e9ff25 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:07:01 -0600 Subject: [PATCH 054/188] ScrollFlight: fixed error spam on launch, added reset on exit flight option --- ScrollFlight/Main.cs | 25 ++++++++++++++++++++++--- ScrollFlight/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ScrollFlight/Main.cs b/ScrollFlight/Main.cs index e446609..98fdabd 100644 --- a/ScrollFlight/Main.cs +++ b/ScrollFlight/Main.cs @@ -23,6 +23,10 @@ public class ScrollFlightMod : MelonMod Category.CreateEntry("use_scroll_flight", true, "Use Scroll Flight", description: "Toggle Scroll Flight."); + private static readonly MelonPreferences_Entry EntryResetOnExitFlight = + Category.CreateEntry("reset_on_exit_flight", false, + "Reset On Exit Flight", description: "Reset Scroll Flight speed on exit flight."); + #endregion Melon Preferences #region Private Fields @@ -39,14 +43,29 @@ public class ScrollFlightMod : MelonMod CVRWorld.GameRulesUpdated += OnApplyMovementSettings; // thank you kafe for using actions } + bool wasFlying = false; + // stole from LucMod :3 public override void OnUpdate() { if (!EntryUseScrollFlight.Value) return; - if (BetterBetterCharacterController.Instance == null - || !BetterBetterCharacterController.Instance.IsFlying() + if (BetterBetterCharacterController.Instance == null) + return; + + bool isFlying = BetterBetterCharacterController.Instance.IsFlying(); + + if (EntryResetOnExitFlight.Value + && (wasFlying && !isFlying)) + { + BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier = _originalWorldFlightSpeedMultiplier; + _currentWorldFlightSpeedMultiplier = _originalWorldFlightSpeedMultiplier; + } + + wasFlying = isFlying; + + if (!isFlying || Input.GetKey(KeyCode.Mouse2) // scroll zoom || Input.GetKey(KeyCode.LeftControl) // third person / better interact desktop || Cursor.lockState != CursorLockMode.Locked) // unity explorer / in menu @@ -58,7 +77,7 @@ public class ScrollFlightMod : MelonMod private static void AdjustFlightModifier(float adjustValue) { - _currentWorldFlightSpeedMultiplier = Math.Max(0f, _currentWorldFlightSpeedMultiplier + adjustValue); + _currentWorldFlightSpeedMultiplier = Mathf.Max(0f, _currentWorldFlightSpeedMultiplier + adjustValue); if (_currentWorldFlightSpeedMultiplier <= 0f) { diff --git a/ScrollFlight/Properties/AssemblyInfo.cs b/ScrollFlight/Properties/AssemblyInfo.cs index c659f5b..3aeb6cb 100644 --- a/ScrollFlight/Properties/AssemblyInfo.cs +++ b/ScrollFlight/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ScrollFlight.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file From f72a25a4c7eef6ddfa745222800a6d5c2c6d0ac2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:07:07 -0600 Subject: [PATCH 055/188] Update References.Items.props --- References.Items.props | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/References.Items.props b/References.Items.props index b618151..95eaa0f 100644 --- a/References.Items.props +++ b/References.Items.props @@ -196,6 +196,18 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.dll + False + + + $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.Example.dll + False + + + $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.TobSupport.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\PWCommon3DLL.dll False @@ -584,6 +596,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Hands.Samples.VisualizerSample.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Interaction.Toolkit.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Management.dll False @@ -616,16 +632,12 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.OculusQuestSupport.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.PICOSupport.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.RuntimeDebugger.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXRPico.dll + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.PICO.dll False From 6fb1e0658d5f594218d6cc3d4e5431134ae69e76 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:07:24 -0600 Subject: [PATCH 056/188] Update NAK_CVR_Mods.sln --- NAK_CVR_Mods.sln | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 01ced9a..ce1db86 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -81,6 +81,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchWithSpacesFix", "SearchWithSpacesFix\SearchWithSpacesFix.csproj", "{0640B2BF-1EF5-4FFE-A144-0368748FC48B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComponentMonitor", "ComponentMonitor\ComponentMonitor.csproj", "{FC91FFFE-1E0A-4F59-8802-BFF99152AD07}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShareBubbles", "ShareBubbles\ShareBubbles.csproj", "{ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -243,6 +249,18 @@ Global {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.Build.0 = Release|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.Build.0 = Release|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e7b2ad4c31da5cb9ffe202ee3dc463dd0dca8011 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:53:17 -0600 Subject: [PATCH 057/188] ShareBubbles: move to additive scene --- ShareBubbles/ShareBubbles/ShareBubble.cs | 4 ++-- ShareBubbles/ShareBubbles/ShareBubbleManager.cs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ShareBubbles/ShareBubbles/ShareBubble.cs b/ShareBubbles/ShareBubbles/ShareBubble.cs index 5ab9139..05726a6 100644 --- a/ShareBubbles/ShareBubbles/ShareBubble.cs +++ b/ShareBubbles/ShareBubbles/ShareBubble.cs @@ -345,8 +345,8 @@ public class ShareBubble : MonoBehaviour { if (!IsOwnBubble) return; - bool isAllowed = implementation.IsPermitted || - Data.Rule == ShareRule.Everyone || + // Check if requester is allowed to claim + bool isAllowed = Data.Rule == ShareRule.Everyone || (Data.Rule == ShareRule.FriendsOnly && Friends.FriendsWith(requesterId)); if (!isAllowed) diff --git a/ShareBubbles/ShareBubbles/ShareBubbleManager.cs b/ShareBubbles/ShareBubbles/ShareBubbleManager.cs index 2aeaec8..fd45f81 100644 --- a/ShareBubbles/ShareBubbles/ShareBubbleManager.cs +++ b/ShareBubbles/ShareBubbles/ShareBubbleManager.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.IO.Instancing; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Core.UI; @@ -7,6 +8,7 @@ using ABI_RC.Systems.Gravity; using NAK.ShareBubbles.Impl; using NAK.ShareBubbles.Networking; using UnityEngine; +using UnityEngine.SceneManagement; using Object = UnityEngine.Object; namespace NAK.ShareBubbles; @@ -233,6 +235,9 @@ public class ShareBubbleManager bubbleRootObject = Object.Instantiate(ShareBubblesMod.SharingBubblePrefab); bubbleRootObject.transform.SetLocalPositionAndRotation(position, rotation); bubbleRootObject.SetActive(true); + + // Move to Additive scene to prevent being hit by shader replacement on VR switch + SceneManager.MoveGameObjectToScene(bubbleRootObject, SceneManager.GetSceneByName(CVRObjectLoader.AdditiveContentSceneName)); Transform bubbleTransform = bubbleRootObject.transform.GetChild(0); if (bubbleTransform == null) From ef2be62605083b39b2fdf339e2c943c5c717f510 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:54:19 -0600 Subject: [PATCH 058/188] Make work on Stable, Add response on claim fail --- .../API/PedestalInfoBatchProcessor.cs | 16 ++-- ...stalInfoResponseButWithPublicationState.cs | 13 --- .../PedestalInfoResponse_ButCorrect.cs | 10 +++ .../Implementation/AvatarBubbleImpl.cs | 55 ++++++++----- .../Implementation/IShareBubbleImpl.cs | 6 +- .../Implementation/ShareClaimResult.cs | 27 +++++++ .../Implementation/SpawnableBubbleImpl.cs | 61 ++++++++------ .../Networking/ModNetwork.Constants.cs | 2 + .../Networking/ModNetwork.Enums.cs | 15 +++- .../Networking/ModNetwork.Inbound.cs | 15 +++- .../Networking/ModNetwork.Main.cs | 21 +++++ .../Networking/ModNetwork.Outbound.cs | 42 +++++++--- ShareBubbles/ShareBubbles/ShareBubble.cs | 81 ++++++++++++++----- .../ShareBubbles/UI/BubbleInteract.cs | 8 +- .../ShareBubbles/UI/ReturnOnRelease.cs | 4 +- 15 files changed, 268 insertions(+), 108 deletions(-) delete mode 100644 ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs create mode 100644 ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs create mode 100644 ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs diff --git a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs index 280a32f..00b9f41 100644 --- a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs +++ b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs @@ -19,11 +19,11 @@ public enum PedestalType /// public static class PedestalInfoBatchProcessor { - private static readonly Dictionary>> _pendingRequests + private static readonly Dictionary>> _pendingRequests = new() { - { PedestalType.Avatar, new Dictionary>() }, - { PedestalType.Prop, new Dictionary>() } + { PedestalType.Avatar, new Dictionary>() }, + { PedestalType.Prop, new Dictionary>() } }; private static readonly Dictionary _isBatchProcessing @@ -36,9 +36,9 @@ public static class PedestalInfoBatchProcessor private static readonly object _lock = new(); private const float BATCH_DELAY = 2f; - public static Task QueuePedestalInfoRequest(PedestalType type, string contentId) + public static Task QueuePedestalInfoRequest(PedestalType type, string contentId) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); lock (_lock) { @@ -62,12 +62,12 @@ public static class PedestalInfoBatchProcessor await Task.Delay(TimeSpan.FromSeconds(BATCH_DELAY)); List contentIds; - Dictionary> requestBatch; + Dictionary> requestBatch; lock (_lock) { contentIds = _pendingRequests[type].Keys.ToList(); - requestBatch = new Dictionary>(_pendingRequests[type]); + requestBatch = new Dictionary>(_pendingRequests[type]); _pendingRequests[type].Clear(); _isBatchProcessing[type] = false; //ShareBubblesMod.Logger.Msg($"Processing {type} pedestal info batch with {contentIds.Count} items"); @@ -82,7 +82,7 @@ public static class PedestalInfoBatchProcessor _ => throw new ArgumentException($"Unsupported pedestal type: {type}") }; - var response = await ApiConnection.MakeRequest>(operation, contentIds); + var response = await ApiConnection.MakeRequest>(operation, contentIds); if (response?.Data != null) { diff --git a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs deleted file mode 100644 index ac764cb..0000000 --- a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponseButWithPublicationState.cs +++ /dev/null @@ -1,13 +0,0 @@ -using ABI_RC.Core.Networking.API.Responses; - -namespace NAK.ShareBubbles.API.Responses; - -/// Same as PedestalInfoResponse, but with an additional field for publication state, if you could not tell by the name. -/// TODO: actually waiting on luc to add Published to PedestalInfoResponse -[Serializable] -public class PedestalInfoResponseButWithPublicationState : UgcResponse -{ - public UserDetails User { get; set; } - public bool Permitted { get; set; } - public bool Published { get; set; } -} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs new file mode 100644 index 0000000..4741a34 --- /dev/null +++ b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs @@ -0,0 +1,10 @@ +using ABI_RC.Core.Networking.API.Responses; + +namespace NAK.ShareBubbles.API.Responses; + +[Serializable] +public class PedestalInfoResponse_ButCorrect : UgcResponse +{ + public UserDetails User { get; set; } + public bool Published { get; set; } // Client mislabelled this as Permitted, but it's actually Published +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs index 29c9a14..60dc79d 100644 --- a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs @@ -2,7 +2,10 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; using ABI_RC.Core.Networking.API.UserWebsocket; +using ABI_RC.Core.Savior; using NAK.ShareBubbles.API; +using NAK.ShareBubbles.API.Exceptions; +using ShareBubbles.ShareBubbles.Implementation; using UnityEngine; using Object = UnityEngine.Object; @@ -36,8 +39,10 @@ namespace NAK.ShareBubbles.Impl Name = infoResponse.Name, ImageUrl = infoResponse.ImageUrl, AuthorId = infoResponse.User.Id, - IsPermitted = infoResponse.Permitted, IsPublic = infoResponse.Published, + + // Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE) + IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId, }; downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); @@ -57,28 +62,40 @@ namespace NAK.ShareBubbles.Impl }); } - public void HandleClaimAccept(string userId, Action onClaimActionCompleted) + public async Task HandleClaimAccept(string userId) { - Task.Run(async () => + if (details == null) + return ShareClaimResult.Rejected(); + + try { - try + await ShareApiHelper.ShareContentAsync( + ShareApiHelper.ShareContentType.Avatar, + avatarId, + userId); + + // Add to temp shares if session access + if (bubble.Data.Access == ShareAccess.Session) { - var response = await ShareApiHelper.ShareContentAsync( - ShareApiHelper.ShareContentType.Avatar, avatarId, userId); - - // Store the temporary share to revoke when either party leaves the instance - if (bubble.Data.Access == ShareAccess.Session) - TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Avatar, - avatarId, userId); - - onClaimActionCompleted(response.IsSuccessStatusCode); + TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Avatar, + avatarId, userId); } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Error sharing avatar: {ex.Message}"); - onClaimActionCompleted(false); - } - }); + + return ShareClaimResult.Success(bubble.Data.Access == ShareAccess.Session); + } + catch (ContentAlreadySharedException) + { + return ShareClaimResult.AlreadyShared(); + } + catch (UserOnlyAllowsSharesFromFriendsException) + { + return ShareClaimResult.FriendsOnly(); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Error sharing avatar: {ex.Message}"); + return ShareClaimResult.Rejected(); + } } public void ViewDetailsPage() diff --git a/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs index 9551717..e6ad17e 100644 --- a/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/IShareBubbleImpl.cs @@ -1,4 +1,6 @@ -namespace NAK.ShareBubbles.Impl; +using ShareBubbles.ShareBubbles.Implementation; + +namespace NAK.ShareBubbles.Impl; public interface IShareBubbleImpl { @@ -8,6 +10,6 @@ public interface IShareBubbleImpl Task FetchContentInfo(); // Load the content info from the API void ViewDetailsPage(); // Open the details page for the content void EquipContent(); // Equip the content (Switch/Select) - void HandleClaimAccept(string userId, Action onClaimActionCompleted); // Handle the claim action (Share via API) + Task HandleClaimAccept(string userId); void Cleanup(); // Cleanup any resources } \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs b/ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs new file mode 100644 index 0000000..ebb309a --- /dev/null +++ b/ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs @@ -0,0 +1,27 @@ +using NAK.ShareBubbles.Networking; + +namespace ShareBubbles.ShareBubbles.Implementation; + +public class ShareClaimResult +{ + public ModNetwork.ClaimResponseType ResponseType { get; } + public bool RequiresSessionTracking { get; } + + private ShareClaimResult(ModNetwork.ClaimResponseType responseType, bool requiresSessionTracking = false) + { + ResponseType = responseType; + RequiresSessionTracking = requiresSessionTracking; + } + + public static ShareClaimResult Success(bool isSessionShare = false) + => new(ModNetwork.ClaimResponseType.Accepted, isSessionShare); + + public static ShareClaimResult AlreadyShared() + => new(ModNetwork.ClaimResponseType.AlreadyShared); + + public static ShareClaimResult FriendsOnly() + => new(ModNetwork.ClaimResponseType.NotAcceptingSharesFromNonFriends); + + public static ShareClaimResult Rejected() + => new(ModNetwork.ClaimResponseType.Rejected); +} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs index a5ece85..fc29336 100644 --- a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs @@ -2,7 +2,10 @@ using ABI_RC.Core.IO; using ABI_RC.Core.Networking.API.UserWebsocket; using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; using NAK.ShareBubbles.API; +using NAK.ShareBubbles.API.Exceptions; +using ShareBubbles.ShareBubbles.Implementation; using UnityEngine; using Object = UnityEngine.Object; @@ -36,8 +39,10 @@ namespace NAK.ShareBubbles.Impl Name = infoResponse.Name, ImageUrl = infoResponse.ImageUrl, AuthorId = infoResponse.User.Id, - IsPermitted = infoResponse.Permitted, IsPublic = infoResponse.Published, + + // Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE) + IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId, }; downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); @@ -57,36 +62,40 @@ namespace NAK.ShareBubbles.Impl }); } - public void HandleClaimAccept(string userId, Action onClaimActionCompleted) + public async Task HandleClaimAccept(string userId) { if (details == null) + return ShareClaimResult.Rejected(); + + try { - onClaimActionCompleted(false); - return; + await ShareApiHelper.ShareContentAsync( + ShareApiHelper.ShareContentType.Spawnable, + spawnableId, + userId); + + // Add to temp shares if session access + if (bubble.Data.Access == ShareAccess.Session) + { + TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Spawnable, + spawnableId, userId); + } + + return ShareClaimResult.Success(bubble.Data.Access == ShareAccess.Session); } - - Task.Run(async () => + catch (ContentAlreadySharedException) { - try - { - var response = await ShareApiHelper.ShareContentAsync( - ShareApiHelper.ShareContentType.Spawnable, - spawnableId, - userId); - - // Store the temporary share to revoke when either party leaves the instance - if (bubble.Data.Access == ShareAccess.Session) - TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Spawnable, - spawnableId, userId); - - onClaimActionCompleted(response.IsSuccessStatusCode); - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Error sharing spawnable: {ex.Message}"); - onClaimActionCompleted(false); - } - }); + return ShareClaimResult.AlreadyShared(); + } + catch (UserOnlyAllowsSharesFromFriendsException) + { + return ShareClaimResult.FriendsOnly(); + } + catch (Exception ex) + { + ShareBubblesMod.Logger.Error($"Error sharing spawnable: {ex.Message}"); + return ShareClaimResult.Rejected(); + } } public void ViewDetailsPage() diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs index 8aecac2..3450ed7 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Constants.cs @@ -7,5 +7,7 @@ public static partial class ModNetwork private const string NetworkVersion = "1.0.1"; // change each time network protocol changes private const string ModId = $"NAK.SB:{NetworkVersion}"; // Cannot exceed 32 characters + private const float ClaimRequestTimeout = 30f; // 30 second timeout + #endregion Constants } \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs index 86d9758..ac7cecd 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Enums.cs @@ -29,9 +29,18 @@ public static partial class ModNetwork private enum MNLogLevel : byte { - Info = 0, - Warning = 1, - Error = 2 + Info, + Warning, + Error + } + + public enum ClaimResponseType : byte + { + Accepted, + Rejected, + NotAcceptingSharesFromNonFriends, + AlreadyShared, + Timeout } #endregion Enums diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs index 708427a..a9caec4 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs @@ -150,11 +150,20 @@ public static partial class ModNetwork private static void HandleBubbleClaimResponse(ModNetworkMessage msg) { msg.Read(out uint bubbleNetworkId); - msg.Read(out bool claimAccepted); + msg.Read(out byte responseTypeRaw); - ShareBubbleManager.Instance.OnRemoteBubbleClaimResponse(msg.Sender, bubbleNetworkId, claimAccepted); + if (!Enum.IsDefined(typeof(ClaimResponseType), responseTypeRaw)) + { + LoggerInbound($"Invalid claim response type received: {responseTypeRaw}"); + return; + } + + ClaimResponseType responseType = (ClaimResponseType)responseTypeRaw; + + if (_pendingClaimRequests.TryGetValue(bubbleNetworkId, out PendingClaimRequest request)) + request.CompletionSource.TrySetResult(responseType); - LoggerInbound($"Bubble with ID {bubbleNetworkId} claim response: {claimAccepted}"); + LoggerInbound($"Bubble with ID {bubbleNetworkId} claim response: {responseType}"); } private static void HandleActiveBubblesRequest(ModNetworkMessage msg) diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs index 2097211..be0b2bb 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Main.cs @@ -4,6 +4,7 @@ namespace NAK.ShareBubbles.Networking; public static partial class ModNetwork { + #region Mod Network Internals private static bool _isSubscribedToModNetwork; @@ -33,4 +34,24 @@ public static partial class ModNetwork } #endregion Mod Network Internals + + #region Pending Claim Requests + + private static readonly Dictionary _pendingClaimRequests = new(); + + public class PendingClaimRequest + { + public TaskCompletionSource CompletionSource { get; } + public DateTime RequestTime { get; } + public uint BubbleId { get; } + + public PendingClaimRequest(uint bubbleId) + { + CompletionSource = new TaskCompletionSource(); + RequestTime = DateTime.UtcNow; + BubbleId = bubbleId; + } + } + + #endregion } \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs index 8f2d85d..a09e0e1 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs @@ -51,20 +51,44 @@ public static partial class ModNetwork LoggerOutbound($"Sending BubbleMove message for bubble {bubbleId}"); } - public static void SendBubbleClaimRequest(string bubbleOwnerId, uint bubbleNetworkId) + public static async Task SendBubbleClaimRequestAsync(string bubbleOwnerId, uint bubbleNetworkId) { if (!CanSendModNetworkMessage()) - return; + return ClaimResponseType.Rejected; - using ModNetworkMessage modMsg = new(ModId, bubbleOwnerId); - modMsg.Write((byte)MessageType.BubbleClaimRequest); - modMsg.Write(bubbleNetworkId); - modMsg.Send(); + // Create pending request + PendingClaimRequest request = new(bubbleNetworkId); + _pendingClaimRequests[bubbleNetworkId] = request; + + // Send request + using (ModNetworkMessage modMsg = new(ModId, bubbleOwnerId)) + { + modMsg.Write((byte)MessageType.BubbleClaimRequest); + modMsg.Write(bubbleNetworkId); + modMsg.Send(); + } LoggerOutbound($"Sending BubbleClaimRequest message for bubble {bubbleNetworkId}"); + + try + { + // Wait for response with timeout + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(ClaimRequestTimeout)); + Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(ClaimRequestTimeout), cts.Token); + var responseTask = request.CompletionSource.Task; + + Task completedTask = await Task.WhenAny(responseTask, timeoutTask); + if (completedTask == timeoutTask) return ClaimResponseType.Timeout; + + return await responseTask; + } + finally + { + _pendingClaimRequests.Remove(bubbleNetworkId); + } } - public static void SendBubbleClaimResponse(string requesterUserId, uint bubbleNetworkId, bool success) + public static void SendBubbleClaimResponse(string requesterUserId, uint bubbleNetworkId, ClaimResponseType responseType) { if (!CanSendModNetworkMessage()) return; @@ -72,10 +96,10 @@ public static partial class ModNetwork using ModNetworkMessage modMsg = new(ModId, requesterUserId); modMsg.Write((byte)MessageType.BubbleClaimResponse); modMsg.Write(bubbleNetworkId); - modMsg.Write(success); + modMsg.Write((byte)responseType); modMsg.Send(); - LoggerOutbound($"Sending BubbleClaimResponse message for bubble {bubbleNetworkId}"); + LoggerOutbound($"Sending BubbleClaimResponse message for bubble {bubbleNetworkId}: {responseType}"); } public static void SendActiveBubblesRequest() diff --git a/ShareBubbles/ShareBubbles/ShareBubble.cs b/ShareBubbles/ShareBubbles/ShareBubble.cs index 05726a6..6a0d5a1 100644 --- a/ShareBubbles/ShareBubbles/ShareBubble.cs +++ b/ShareBubbles/ShareBubbles/ShareBubble.cs @@ -8,6 +8,8 @@ using NAK.ShareBubbles.Networking; using NAK.ShareBubbles.UI; using TMPro; using System.Collections; +using ABI_RC.Core.InteractionSystem; +using ShareBubbles.ShareBubbles.Implementation; namespace NAK.ShareBubbles; @@ -308,18 +310,62 @@ public class ShareBubble : MonoBehaviour public void RequestContentClaim() { if (!CanRequestClaim()) return; - if (!RequestClaimTimeoutInactive()) return; - lastClaimRequest = DateTime.Now; - ModNetwork.SendBubbleClaimRequest(OwnerId, Data.BubbleId); - CurrentClaimState = ClaimState.Requested; - - return; - bool RequestClaimTimeoutInactive() + // Fire and forget but with error handling~ + _ = RequestContentClaimAsync().ContinueWith(task => { - if (!lastClaimRequest.HasValue) return true; - TimeSpan timeSinceLastRequest = DateTime.Now - lastClaimRequest.Value; - return timeSinceLastRequest.TotalSeconds >= ClaimTimeout; + if (!task.IsFaulted) + return; + + ShareBubblesMod.Logger.Error($"Error requesting content claim: {task.Exception}"); + CurrentClaimState = ClaimState.Rejected; + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + + private async Task RequestContentClaimAsync() + { + if (!CanRequestClaim()) + return ModNetwork.ClaimResponseType.Rejected; + + CurrentClaimState = ClaimState.Requested; + + try + { + ModNetwork.ClaimResponseType response = await ModNetwork.SendBubbleClaimRequestAsync(OwnerId, Data.BubbleId); + + switch (response) + { + case ModNetwork.ClaimResponseType.Accepted: + case ModNetwork.ClaimResponseType.AlreadyShared: + CurrentClaimState = ClaimState.Permitted; + break; + default: + case ModNetwork.ClaimResponseType.Rejected: + case ModNetwork.ClaimResponseType.Timeout: + CurrentClaimState = ClaimState.Rejected; + break; + case ModNetwork.ClaimResponseType.NotAcceptingSharesFromNonFriends: + CurrentClaimState = ClaimState.Rejected; + + string ownerName = CVRPlayerManager.Instance.TryGetPlayerName(OwnerId); + + ShareBubblesMod.Logger.Msg($"Claim request for {Data.BubbleId} rejected: " + + $"You are not friends with the owner ({ownerName}) and do not have the permission " + + $"enabled on the Community Hub to accept shares from non-friends."); + // Display in UI + ViewManager.Instance.TriggerAlert("Claim Request Rejected", + $"You are not friends with the owner ({ownerName}) and do not have the permission " + + "enabled on the Community Hub to accept shares from non-friends.", -1, false); + + break; + } + + return response; + } + catch + { + CurrentClaimState = ClaimState.Rejected; + return ModNetwork.ClaimResponseType.Rejected; } } @@ -341,22 +387,19 @@ public class ShareBubble : MonoBehaviour UpdateButtonStates(); } - public void OnRemoteWantsClaim(string requesterId) + public async void OnRemoteWantsClaim(string requesterId) { if (!IsOwnBubble) return; - // Check if requester is allowed to claim - bool isAllowed = Data.Rule == ShareRule.Everyone || - (Data.Rule == ShareRule.FriendsOnly && Friends.FriendsWith(requesterId)); - - if (!isAllowed) + // Rule check bypass attempt - reject immediately + if (Data.Rule == ShareRule.FriendsOnly && !Friends.FriendsWith(requesterId)) { - ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, false); + ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, ModNetwork.ClaimResponseType.Rejected); return; } - implementation.HandleClaimAccept(requesterId, - wasAccepted => ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, wasAccepted)); + ShareClaimResult result = await implementation.HandleClaimAccept(requesterId); + ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, result.ResponseType); } #endregion Mod Network Callbacks diff --git a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs index ad2b4f4..d389a41 100644 --- a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs +++ b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs @@ -14,12 +14,12 @@ public class BubbleInteract : Interactable return Vector3.Distance(transform.position, sourcePos) < 1.5f; } - public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay) + public override void OnInteractDown(ControllerRay controllerRay) { // Not used } - public override void OnInteractUp(InteractionContext context, ControllerRay controllerRay) + public override void OnInteractUp(ControllerRay controllerRay) { if (PlayerSetup.Instance.GetCurrentPropSelectionMode() != PlayerSetup.PropSelectionMode.None) @@ -39,12 +39,12 @@ public class BubbleInteract : Interactable GetComponentInParent().ViewDetailsPage(); } - public override void OnHoverEnter(InteractionContext context, ControllerRay controllerRay) + public override void OnHoverEnter() { // Not used } - public override void OnHoverExit(InteractionContext context, ControllerRay controllerRay) + public override void OnHoverExit() { // Not used } diff --git a/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs index b6e1eda..9c2416c 100644 --- a/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs +++ b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs @@ -30,12 +30,12 @@ public class ReturnOnRelease : MonoBehaviour pickupable.onDrop.AddListener(OnPickupRelease); } - public void OnPickupGrabbed(InteractionContext _) + public void OnPickupGrabbed() { isReturning = false; } - public void OnPickupRelease(InteractionContext _) + public void OnPickupRelease() { isReturning = true; } From f5c6a924721ffd081373c586be57fe0ce3f9c5e1 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:55:12 -0600 Subject: [PATCH 059/188] ShareBubbles: bump version --- ShareBubbles/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ShareBubbles/Properties/AssemblyInfo.cs b/ShareBubbles/Properties/AssemblyInfo.cs index 06006a8..8a1996f 100644 --- a/ShareBubbles/Properties/AssemblyInfo.cs +++ b/ShareBubbles/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties; namespace NAK.ShareBubbles.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; - public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc"; + public const string Version = "1.0.3"; + public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler"; } \ No newline at end of file From 3d42301f2495c7cd90d98729ba9d8c3ed966b4d0 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sun, 8 Dec 2024 17:04:42 -0600 Subject: [PATCH 060/188] ShareBubbles: added notice to readme about missing api stuff --- ShareBubbles/README.md | 6 ++++++ ShareBubbles/format.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ShareBubbles/README.md b/ShareBubbles/README.md index 27e11cf..f56f04f 100644 --- a/ShareBubbles/README.md +++ b/ShareBubbles/README.md @@ -2,6 +2,12 @@ Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. +### NOTICE - 1.0.2 +**Some features will not work as intended until ChilloutVRs API is updated.** +- Bubbles will display "Private" label regardless of publication state. +- Bubbles of private content you do not own will display "Claim" button regardless of if the content is already shared with you. +- Unshare button will be accessible when viewing Public content not owned by you. + ### How to use Open a Content Details page and click the Share button. You can choose between placing a Share Bubble or Sharing directly with users in the same instance. When placing a Share Bubble, you will be prompted to configure the bubble. Once you're ready, you can then Drop or Select the bubble for placement. diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index 76dadf3..a17aa7b 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -1,7 +1,7 @@ { "_id": -1, "name": "ShareBubbles", - "modversion": "1.0.1", + "modversion": "1.0.2", "gameversion": "2024r177", "loaderversion": "0.6.1", "modtype": "Mod", @@ -17,7 +17,7 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r41/ShareBubbles.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r43/ShareBubbles.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", "changelog": "- Initial release.", "embedcolor": "#f61963" From e027829103f6cc9f52e75fecd54209af10adff41 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 27 Dec 2024 17:55:39 -0600 Subject: [PATCH 061/188] TwoWayMute: initial release --- References.Items.props | 24 +++++--------------- TwoWayMute/Main.cs | 24 ++++++++++++++++++++ TwoWayMute/Properties/AssemblyInfo.cs | 32 +++++++++++++++++++++++++++ TwoWayMute/README.md | 15 +++++++++++++ TwoWayMute/TwoWayMute.csproj | 11 +++++++++ TwoWayMute/format.json | 23 +++++++++++++++++++ 6 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 TwoWayMute/Main.cs create mode 100644 TwoWayMute/Properties/AssemblyInfo.cs create mode 100644 TwoWayMute/README.md create mode 100644 TwoWayMute/TwoWayMute.csproj create mode 100644 TwoWayMute/format.json diff --git a/References.Items.props b/References.Items.props index 95eaa0f..b618151 100644 --- a/References.Items.props +++ b/References.Items.props @@ -196,18 +196,6 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.dll - False - - - $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.Example.dll - False - - - $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.TobSupport.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\PWCommon3DLL.dll False @@ -596,10 +584,6 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Hands.Samples.VisualizerSample.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Interaction.Toolkit.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Management.dll False @@ -632,12 +616,16 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.OculusQuestSupport.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.PICOSupport.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.RuntimeDebugger.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.PICO.dll + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXRPico.dll False diff --git a/TwoWayMute/Main.cs b/TwoWayMute/Main.cs new file mode 100644 index 0000000..8d6de3e --- /dev/null +++ b/TwoWayMute/Main.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using ABI_RC.Systems.Communications.Audio.Components; +using HarmonyLib; +using MelonLoader; + +namespace NAK.TwoWayMute; + +public class TwoWayMuteMod : MelonMod +{ + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(Comms_ParticipantPipeline).GetMethod(nameof(Comms_ParticipantPipeline.SetFlowControlState), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(TwoWayMuteMod).GetMethod(nameof(OnSetFlowControlState), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnSetFlowControlState( + ref bool state, + Comms_ParticipantPipeline __instance) + => state &= !__instance._selfModerationMute; +} \ No newline at end of file diff --git a/TwoWayMute/Properties/AssemblyInfo.cs b/TwoWayMute/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0c53769 --- /dev/null +++ b/TwoWayMute/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.TwoWayMute.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.TwoWayMute))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.TwoWayMute))] + +[assembly: MelonInfo( + typeof(NAK.TwoWayMute.TwoWayMuteMod), + nameof(NAK.TwoWayMute), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/TwoWayMute" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.TwoWayMute.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/TwoWayMute/README.md b/TwoWayMute/README.md new file mode 100644 index 0000000..8732b10 --- /dev/null +++ b/TwoWayMute/README.md @@ -0,0 +1,15 @@ +# TwoWayMute + +Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you. +Basically- if you mute someone, they will also not be able to hear you. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/TwoWayMute/TwoWayMute.csproj b/TwoWayMute/TwoWayMute.csproj new file mode 100644 index 0000000..4e44ed3 --- /dev/null +++ b/TwoWayMute/TwoWayMute.csproj @@ -0,0 +1,11 @@ + + + + net48 + + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/TwoWayMute/format.json b/TwoWayMute/format.json new file mode 100644 index 0000000..db2dbc9 --- /dev/null +++ b/TwoWayMute/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "TwoWayMute", + "modversion": "1.0.0", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you.\nBasically- if you mute someone, they will also not be able to hear you.", + "searchtags": [ + "mute", + "communication", + "meow", + "car" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/TwoWayMute.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/TwoWayMute/", + "changelog": "- Initial Release", + "embedcolor": "#f61963" +} \ No newline at end of file From 19d7eb1c7c0be76dbd8bfb36b8ec0e456309871a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 27 Dec 2024 17:59:40 -0600 Subject: [PATCH 062/188] TwoWayMute: added newline to readme --- TwoWayMute/README.md | 1 + TwoWayMute/format.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TwoWayMute/README.md b/TwoWayMute/README.md index 8732b10..560a022 100644 --- a/TwoWayMute/README.md +++ b/TwoWayMute/README.md @@ -1,6 +1,7 @@ # TwoWayMute Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you. + Basically- if you mute someone, they will also not be able to hear you. --- diff --git a/TwoWayMute/format.json b/TwoWayMute/format.json index db2dbc9..a2ed4f6 100644 --- a/TwoWayMute/format.json +++ b/TwoWayMute/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you.\nBasically- if you mute someone, they will also not be able to hear you.", + "description": "Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you.\n\nBasically- if you mute someone, they will also not be able to hear you.", "searchtags": [ "mute", "communication", From 3d6b1bbd59d23be19fe3594e104ad26e4ac0adcd Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:01:03 -0600 Subject: [PATCH 063/188] BullshitWatcher: lame --- BullshitWatcher/BullshitWatcher.csproj | 11 + BullshitWatcher/Main.cs | 244 +++++++++++++++++++++ BullshitWatcher/Properties/AssemblyInfo.cs | 32 +++ 3 files changed, 287 insertions(+) create mode 100644 BullshitWatcher/BullshitWatcher.csproj create mode 100644 BullshitWatcher/Main.cs create mode 100644 BullshitWatcher/Properties/AssemblyInfo.cs diff --git a/BullshitWatcher/BullshitWatcher.csproj b/BullshitWatcher/BullshitWatcher.csproj new file mode 100644 index 0000000..4e44ed3 --- /dev/null +++ b/BullshitWatcher/BullshitWatcher.csproj @@ -0,0 +1,11 @@ + + + + net48 + + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/BullshitWatcher/Main.cs b/BullshitWatcher/Main.cs new file mode 100644 index 0000000..aa46305 --- /dev/null +++ b/BullshitWatcher/Main.cs @@ -0,0 +1,244 @@ +using System.Reflection; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.BullshitWatcher; + +// Slapped together to log where bullshit values are coming from, +// instead of creating the same Unity Explorer patch for the 100th time + +public class BullshitWatcherMod : MelonMod +{ + #region Initialize + + public override void OnInitializeMelon() + { + #region Transform Patches + + Type transformType = typeof(Transform); + + // Properties + PatchProperty(transformType, nameof(Transform.position)); + PatchProperty(transformType, nameof(Transform.localPosition)); + PatchProperty(transformType, nameof(Transform.rotation)); + PatchProperty(transformType, nameof(Transform.localRotation)); + PatchProperty(transformType, nameof(Transform.localScale)); + PatchProperty(transformType, nameof(Transform.right)); + PatchProperty(transformType, nameof(Transform.up)); + PatchProperty(transformType, nameof(Transform.forward)); + + // Methods + PatchMethod(transformType, nameof(Transform.SetPositionAndRotation)); + PatchMethod(transformType, nameof(Transform.SetLocalPositionAndRotation)); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3) }); + PatchMethod(transformType, nameof(Transform.Translate), new[] { typeof(Vector3), typeof(Transform) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3) }); + PatchMethod(transformType, nameof(Transform.Rotate), new[] { typeof(Vector3), typeof(float), typeof(Space) }); + PatchMethod(transformType, nameof(Transform.RotateAround)); + PatchMethod(transformType, nameof(Transform.LookAt), new[] { typeof(Vector3), typeof(Vector3) }); + + #endregion + + #region Rigidbody Patches + + Type rigidbodyType = typeof(Rigidbody); + + // Properties + PatchProperty(rigidbodyType, nameof(Rigidbody.position)); + PatchProperty(rigidbodyType, nameof(Rigidbody.rotation)); + PatchProperty(rigidbodyType, nameof(Rigidbody.velocity)); + PatchProperty(rigidbodyType, nameof(Rigidbody.angularVelocity)); + PatchProperty(rigidbodyType, nameof(Rigidbody.centerOfMass)); + + // Methods + PatchMethod(rigidbodyType, nameof(Rigidbody.MovePosition)); + PatchMethod(rigidbodyType, nameof(Rigidbody.MoveRotation)); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddForce), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddRelativeForce), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddTorque), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddRelativeTorque), new[] { typeof(Vector3), typeof(ForceMode) }); + PatchMethod(rigidbodyType, nameof(Rigidbody.AddForceAtPosition), new[] { typeof(Vector3), typeof(Vector3), typeof(ForceMode) }); + + #endregion + } + + private void PatchProperty(Type type, string propertyName) + { + PropertyInfo property = type.GetProperty(propertyName); + if (property!.SetMethod != null) + { + HarmonyInstance.Patch( + property.SetMethod, + prefix: new HarmonyMethod(typeof(BullshitWatcherMod).GetMethod(nameof(OnSetValue), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + } + + private void PatchMethod(Type type, string methodName, Type[] parameters = null) + { + MethodInfo method; + if (parameters != null) + { + method = type.GetMethod(methodName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy, + null, + parameters, + null); + } + else + { + var methods = type.GetMethods() + .Where(m => m.Name == methodName && !m.IsGenericMethod) + .ToArray(); + + // If there's only one method with this name, use it + if (methods.Length == 1) + { + method = methods[0]; + } + else + { + // This is fine :) + LoggerInstance.Error($"Multiple methods found for {type.Name}.{methodName}, skipping ambiguous patch"); + return; + } + } + + if (method != null) + { + HarmonyInstance.Patch( + method, + prefix: new HarmonyMethod(typeof(BullshitWatcherMod).GetMethod(nameof(OnMethodCall), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + else + { + MelonLogger.Warning($"Could not find method {type.Name}.{methodName}"); + } + } + + #endregion + + #region Validation Methods + + private static bool ContainsBullshitValue(Vector3 vector) + { + return float.IsNaN(vector.x) || float.IsNaN(vector.y) || float.IsNaN(vector.z) || + float.IsInfinity(vector.x) || float.IsInfinity(vector.y) || float.IsInfinity(vector.z) || + float.IsNegativeInfinity(vector.x) || float.IsNegativeInfinity(vector.y) || float.IsNegativeInfinity(vector.z); + } + + private static bool ContainsBullshitValue(Quaternion quaternion) + { + return float.IsNaN(quaternion.x) || float.IsNaN(quaternion.y) || float.IsNaN(quaternion.z) || float.IsNaN(quaternion.w) || + float.IsInfinity(quaternion.x) || float.IsInfinity(quaternion.y) || float.IsInfinity(quaternion.z) || float.IsInfinity(quaternion.w) || + float.IsNegativeInfinity(quaternion.x) || float.IsNegativeInfinity(quaternion.y) || + float.IsNegativeInfinity(quaternion.z) || float.IsNegativeInfinity(quaternion.w); + } + + private static bool ContainsBullshitValue(float value) + { + return float.IsNaN(value) || float.IsInfinity(value) || float.IsNegativeInfinity(value); + } + + private static bool ContainsBullshitValues(object[] values) + { + foreach (var value in values) + { + if (value == null) continue; + + switch (value) + { + case Vector3 v3: + if (ContainsBullshitValue(v3)) return true; + break; + case Quaternion q: + if (ContainsBullshitValue(q)) return true; + break; + case float f: + if (ContainsBullshitValue(f)) return true; + break; + } + } + return false; + } + + #endregion + + #region Logging Methods + + private static void LogBullshitValue(string componentType, string propertyName, Component component, object value) + { + MelonLogger.Error($"Bullshit {componentType}.{propertyName} value detected on GameObject '{GetGameObjectPath(component.gameObject)}': {value}"); + MelonLogger.Error(Environment.StackTrace); + } + + private static void LogBullshitMethod(string componentType, string methodName, Component component, object[] parameters) + { + MelonLogger.Error($"Bullshit parameters in {componentType}.{methodName} call on GameObject '{GetGameObjectPath(component.gameObject)}'"); + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i] != null) + MelonLogger.Error($" Parameter {i}: {parameters[i]}"); + } + MelonLogger.Error(Environment.StackTrace); + } + + private static string GetGameObjectPath(GameObject obj) + { + string path = obj.name; + Transform parent = obj.transform.parent; + + while (parent != null) + { + path = $"{parent.name}/{path}"; + parent = parent.parent; + } + + return path; + } + + #endregion + + #region Harmony Patches + + private static void OnSetValue(object value, Component __instance) + { + if (value == null) return; + + var componentType = __instance.GetType().Name; + var propertyName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Replace("set_", ""); + + switch (value) + { + case Vector3 v3 when ContainsBullshitValue(v3): + LogBullshitValue(componentType, propertyName, __instance, v3); + break; + case Quaternion q when ContainsBullshitValue(q): + LogBullshitValue(componentType, propertyName, __instance, q); + break; + case float f when ContainsBullshitValue(f): + LogBullshitValue(componentType, propertyName, __instance, f); + break; + } + } + + private static void OnMethodCall(object[] __args, Component __instance) + { + if (__args == null || __args.Length == 0) return; + + if (ContainsBullshitValues(__args)) + { + var componentType = __instance.GetType().Name; + var methodName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name; + LogBullshitMethod(componentType, methodName, __instance, __args); + } + } + + #endregion +} \ No newline at end of file diff --git a/BullshitWatcher/Properties/AssemblyInfo.cs b/BullshitWatcher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c78d2a8 --- /dev/null +++ b/BullshitWatcher/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.BullshitWatcher.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.BullshitWatcher))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.BullshitWatcher))] + +[assembly: MelonInfo( + typeof(NAK.BullshitWatcher.BullshitWatcherMod), + nameof(NAK.BullshitWatcher), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BullshitWatcher" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.BullshitWatcher.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file From 94515efbe3d56b1da7e4b19e065f6ad70ce075ec Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 31 Dec 2024 05:21:00 -0600 Subject: [PATCH 064/188] LegacyContentMitigation: initial commit manual "multipass" in legacy worlds --- .../Components/CameraCallbackLogger.cs | 69 ++++++ .../Components/FakeMultiPassHack.cs | 228 ++++++++++++++++++ .../Integrations/BtkUiAddon.cs | 86 +++++++ .../LegacyContentMitigation.csproj | 14 ++ LegacyContentMitigation/Main.cs | 67 +++++ LegacyContentMitigation/ModSettings.cs | 24 ++ LegacyContentMitigation/Patches.cs | 125 ++++++++++ .../Properties/AssemblyInfo.cs | 32 +++ 8 files changed, 645 insertions(+) create mode 100644 LegacyContentMitigation/Components/CameraCallbackLogger.cs create mode 100644 LegacyContentMitigation/Components/FakeMultiPassHack.cs create mode 100644 LegacyContentMitigation/Integrations/BtkUiAddon.cs create mode 100644 LegacyContentMitigation/LegacyContentMitigation.csproj create mode 100644 LegacyContentMitigation/Main.cs create mode 100644 LegacyContentMitigation/ModSettings.cs create mode 100644 LegacyContentMitigation/Patches.cs create mode 100644 LegacyContentMitigation/Properties/AssemblyInfo.cs diff --git a/LegacyContentMitigation/Components/CameraCallbackLogger.cs b/LegacyContentMitigation/Components/CameraCallbackLogger.cs new file mode 100644 index 0000000..ecd37be --- /dev/null +++ b/LegacyContentMitigation/Components/CameraCallbackLogger.cs @@ -0,0 +1,69 @@ +using System.Collections; +using UnityEngine; +using System.Text; +using MelonLoader; + +namespace NAK.LegacyContentMitigation.Debug; + +public class CameraCallbackLogger +{ + private static CameraCallbackLogger instance; + private readonly List frameCallbacks = new(); + private bool isListening; + private readonly StringBuilder logBuilder = new(); + + public static CameraCallbackLogger Instance => instance ??= new CameraCallbackLogger(); + + private void RegisterCallbacks() + { + Camera.onPreCull += (cam) => LogCallback(cam, "OnPreCull"); + Camera.onPreRender += (cam) => LogCallback(cam, "OnPreRender"); + Camera.onPostRender += (cam) => LogCallback(cam, "OnPostRender"); + } + + private void UnregisterCallbacks() + { + Camera.onPreCull -= (cam) => LogCallback(cam, "OnPreCull"); + Camera.onPreRender -= (cam) => LogCallback(cam, "OnPreRender"); + Camera.onPostRender -= (cam) => LogCallback(cam, "OnPostRender"); + } + + public void LogCameraEvents() + { + MelonCoroutines.Start(LoggingCoroutine()); + } + + private IEnumerator LoggingCoroutine() + { + yield return null; // idk at what point in frame start occurs + + // First frame: Register and listen + RegisterCallbacks(); + isListening = true; + yield return null; + + // Second frame: Log and cleanup + isListening = false; + PrintFrameLog(); + UnregisterCallbacks(); + } + + private void LogCallback(Camera camera, string callbackName) + { + if (!isListening) return; + frameCallbacks.Add($"{camera.name} - {callbackName} (Depth: {camera.depth})"); + } + + private void PrintFrameLog() + { + logBuilder.Clear(); + logBuilder.AppendLine("\nCamera Callbacks for Frame:"); + + foreach (var callback in frameCallbacks) + logBuilder.AppendLine(callback); + + LegacyContentMitigationMod.Logger.Msg(logBuilder.ToString()); + + frameCallbacks.Clear(); + } +} \ No newline at end of file diff --git a/LegacyContentMitigation/Components/FakeMultiPassHack.cs b/LegacyContentMitigation/Components/FakeMultiPassHack.cs new file mode 100644 index 0000000..31924db --- /dev/null +++ b/LegacyContentMitigation/Components/FakeMultiPassHack.cs @@ -0,0 +1,228 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Systems.UI; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.XR; + +namespace NAK.LegacyContentMitigation; + +public class FakeMultiPassHack : MonoBehaviour +{ + private static readonly int s_WorldSpaceCameraPos = Shader.PropertyToID("_WorldSpaceCameraPos"); + + public static Action OnMultiPassActiveChanged; + + #region Properties + + public static FakeMultiPassHack Instance { get; set; } + public bool IsActive => IsEnabled && isActiveAndEnabled; + public bool IsEnabled { get; private set; } + public Camera.MonoOrStereoscopicEye RenderingEye { get; private set; } + + #endregion + + #region Private Fields + + private Camera _mainCamera; + private Camera _leftEye; + private Camera _rightEye; + + private GameObject _leftEyeObject; + private GameObject _rightEyeObject; + + private RenderTexture _leftTexture; + private RenderTexture _rightTexture; + + private CommandBuffer _shaderGlobalBuffer; + private CommandBuffer _leftEyeBuffer; + + private int CachedCullingMask; + private bool _isInitialized; + + #endregion + + #region Unity Lifecycle + + private void OnEnable() + { + Camera.onPreRender += OnPreRenderCallback; + if (IsEnabled) _mainCamera.cullingMask = 0; + } + + private void OnDisable() + { + Camera.onPreRender -= OnPreRenderCallback; + if (IsEnabled) _mainCamera.cullingMask = CachedCullingMask; + } + + private void OnDestroy() + { + if (_leftEye != null) RemoveCameraFromWorldTransitionSystem(_leftEye); + if (_rightEye != null) RemoveCameraFromWorldTransitionSystem(_rightEye); + + if (_leftTexture != null) _leftTexture.Release(); + if (_rightTexture != null) _rightTexture.Release(); + _shaderGlobalBuffer?.Release(); + _leftEyeBuffer?.Release(); + + if (_leftEyeObject != null) Destroy(_leftEyeObject); + if (_rightEyeObject != null) Destroy(_rightEyeObject); + + return; + void RemoveCameraFromWorldTransitionSystem(Camera cam) + { + if (cam.TryGetComponent(out WorldTransitionCamera effectCam)) Destroy(effectCam); + WorldTransitionSystem.Cameras.Remove(cam); + } + } + + #endregion + + #region Public Methods + + public void SetMultiPassActive(bool active) + { + if (active == IsEnabled) return; + IsEnabled = active; + + if (active && !_isInitialized) DoInitialSetup(); + + _mainCamera.cullingMask = IsActive ? 0 : CachedCullingMask; + + OnMultiPassActiveChanged?.Invoke(active); + } + + public void OnMainCameraChanged() + { + if (!_isInitialized) return; + + CachedCullingMask = _mainCamera.cullingMask; + if (IsActive) _mainCamera.cullingMask = 0; + + CVRTools.CopyToDestCam(_mainCamera, _leftEye); + CVRTools.CopyToDestCam(_mainCamera, _rightEye); + } + + #endregion + + #region Initialization + + private void DoInitialSetup() + { + _mainCamera = GetComponent(); + CachedCullingMask = _mainCamera.cullingMask; + + _shaderGlobalBuffer = new CommandBuffer(); + _leftEyeBuffer = new CommandBuffer(); + + SetupEye("Left Eye", out _leftEyeObject, out _leftEye, _leftEyeBuffer); + SetupEye("Right Eye", out _rightEyeObject, out _rightEye, null); + + _isInitialized = true; + + return; + void SetupEye(string camName, out GameObject eyeObj, out Camera eye, CommandBuffer eyeBuffer) + { + eyeObj = new GameObject(camName); + eyeObj.transform.parent = transform; + eyeObj.transform.localScale = Vector3.one; + eye = eyeObj.AddComponent(); + eye.enabled = false; + + // Correct camera world space pos (nameplate shader) + eye.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _shaderGlobalBuffer); + eye.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, _shaderGlobalBuffer); + eye.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _shaderGlobalBuffer); + + // normalizedViewport parameter is ignored, so we cannot draw mesh on right eye :) + if (eyeBuffer != null) + { + eye.AddCommandBuffer(CameraEvent.BeforeDepthTexture, eyeBuffer); + eye.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, eyeBuffer); + eye.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, eyeBuffer); + // notice how we pass fucked param vs UnityEngine.Rendering.XRUtils + eyeBuffer.DrawOcclusionMesh(new RectInt(0, 0, 0, 0)); + } + + WorldTransitionSystem.AddCamera(eye); + CVRTools.CopyToDestCam(_mainCamera, eye); + } + } + + #endregion + + #region Rendering + + private void OnPreRenderCallback(Camera cam) + { + if (!IsEnabled || !_isInitialized) return; + + if (cam.CompareTag("MainCamera")) + { + EnsureRenderTexturesCreated(); + RenderEyePair(); + } + } + + private void EnsureRenderTexturesCreated() + { + int eyeWidth = XRSettings.eyeTextureWidth; + int eyeHeight = XRSettings.eyeTextureHeight; + + bool needsUpdate = _leftTexture == null || _rightTexture == null || + _leftTexture.width != eyeWidth || _leftTexture.height != eyeHeight; + + if (!needsUpdate) return; + + if (_leftTexture != null) _leftTexture.Release(); + if (_rightTexture != null) _rightTexture.Release(); + + _leftTexture = new RenderTexture(eyeWidth, eyeHeight, 24, RenderTextureFormat.ARGBHalf); + _rightTexture = new RenderTexture(eyeWidth, eyeHeight, 24, RenderTextureFormat.ARGBHalf); + } + + private void RenderEyePair() + { + _shaderGlobalBuffer.Clear(); + _shaderGlobalBuffer.SetGlobalVector(s_WorldSpaceCameraPos, _mainCamera.transform.position); + + Camera realVRCamera = PlayerSetup.Instance.vrCam; + + RenderingEye = Camera.MonoOrStereoscopicEye.Left; + PlayerSetup.Instance.vrCam = _leftEye; // so we trigger head hiding + RenderEye(_leftEye, _leftTexture, Camera.StereoscopicEye.Left); + + RenderingEye = Camera.MonoOrStereoscopicEye.Right; + PlayerSetup.Instance.vrCam = _rightEye; // so we trigger head hiding + RenderEye(_rightEye, _rightTexture, Camera.StereoscopicEye.Right); + + RenderingEye = Camera.MonoOrStereoscopicEye.Mono; // bleh + PlayerSetup.Instance.vrCam = realVRCamera; // reset back to real cam + + return; + void RenderEye(Camera eyeCamera, RenderTexture targetTexture, Camera.StereoscopicEye eye) + { + eyeCamera.CopyFrom(_mainCamera); + eyeCamera.targetTexture = targetTexture; + eyeCamera.cullingMask = CachedCullingMask; + eyeCamera.stereoTargetEye = StereoTargetEyeMask.None; + eyeCamera.projectionMatrix = _mainCamera.GetStereoProjectionMatrix(eye); + eyeCamera.worldToCameraMatrix = _mainCamera.GetStereoViewMatrix(eye); + eyeCamera.Render(); + } + } + + private void OnRenderImage(RenderTexture source, RenderTexture destination) + { + if (!IsEnabled || !_isInitialized || _leftTexture == null || _rightTexture == null) + { + Graphics.Blit(source, destination); + return; + } + + Graphics.CopyTexture(_leftTexture, 0, destination, 0); + Graphics.CopyTexture(_rightTexture, 0, destination, 1); + } + #endregion +} \ No newline at end of file diff --git a/LegacyContentMitigation/Integrations/BtkUiAddon.cs b/LegacyContentMitigation/Integrations/BtkUiAddon.cs new file mode 100644 index 0000000..7a11187 --- /dev/null +++ b/LegacyContentMitigation/Integrations/BtkUiAddon.cs @@ -0,0 +1,86 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Networking.API.Responses; +using ABI.CCK.Components; +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using NAK.LegacyContentMitigation.Debug; + +namespace NAK.LegacyContentMitigation.Integrations; + +public static class BtkUiAddon +{ + private static ToggleButton _currentModState; + + public static void Initialize() + { + // Create menu late to ensure we at bottom. + // Doing this cause these settings are "Advanced" & mostly for debugging. + QuickMenuAPI.OnMenuGenerated += SetupCategory; + } + + private static void SetupCategory(CVR_MenuManager _) + { + QuickMenuAPI.OnMenuGenerated -= SetupCategory; + + Category category = QuickMenuAPI.MiscTabPage.AddCategory(ModSettings.LCM_SettingsCategory, ModSettings.ModName, true, true, true); + + ToggleButton autoButton = category.AddMelonToggle(ModSettings.EntryAutoForLegacyWorlds); + autoButton.OnValueUpdated += (_) => + { + if (CVRWorld.CompatibilityVersion == CompatibilityVersions.NotSpi) + QuickMenuAPI.ShowNotice("Legacy World Notice", + "You must reload this World Bundle for Shader Replacement to be undone / applied. " + + "Load into a different world and then rejoin."); + }; + + _currentModState = category.AddToggle(string.Empty, string.Empty, false); + _currentModState.OnValueUpdated += OnCurrentStateToggled; + + Button printCameraCallbacksButton = category.AddButton("DEBUG LOG CAMERAS", + string.Empty, "Records Camera events & logs them next frame. Useful for determining camera render order shenanigans."); + printCameraCallbacksButton.OnPress += () => CameraCallbackLogger.Instance.LogCameraEvents(); + + OnCurrentStateToggled(FakeMultiPassHack.Instance.IsEnabled); + FakeMultiPassHack.OnMultiPassActiveChanged += OnMultiPassActiveChanged; + } + + private static void OnCurrentStateToggled(bool state) + { + if (state) + { + _currentModState.ToggleValue = false; // dont visually update + QuickMenuAPI.ShowConfirm("Legacy Mitigation Warning", + "This will change how the main VR view is rendered and cause a noticeable performance hit. " + + "It is recommended to only enable this within Worlds that require it (Legacy Content/Broken Shaders). " + + "Shader Replacement will not occur for ALL content that is loaded while enabled. " + + "If this World is Legacy and already Shader Replaced, you must enable Auto For Legacy Worlds instead, " + + "load a different World, and then join back.", + () => + { + FakeMultiPassHack.Instance.SetMultiPassActive(true); + OnMultiPassActiveChanged(true); + }); + } + else + { + FakeMultiPassHack.Instance.SetMultiPassActive(false); + OnMultiPassActiveChanged(false); + } + } + + private static void OnMultiPassActiveChanged(bool state) + { + _currentModState.ToggleValue = state; + if (state) + { + _currentModState.ToggleName = "Currently Active"; + _currentModState.ToggleTooltip = "Fake Multi Pass is currently active."; + } + else + { + _currentModState.ToggleName = "Currently Inactive"; + _currentModState.ToggleTooltip = "Fake Multi Pass is inactive."; + } + } +} \ No newline at end of file diff --git a/LegacyContentMitigation/LegacyContentMitigation.csproj b/LegacyContentMitigation/LegacyContentMitigation.csproj new file mode 100644 index 0000000..d8860da --- /dev/null +++ b/LegacyContentMitigation/LegacyContentMitigation.csproj @@ -0,0 +1,14 @@ + + + + net48 + + + + ..\.ManagedLibs\BTKUILib.dll + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/LegacyContentMitigation/Main.cs b/LegacyContentMitigation/Main.cs new file mode 100644 index 0000000..8db0910 --- /dev/null +++ b/LegacyContentMitigation/Main.cs @@ -0,0 +1,67 @@ +using ABI_RC.Core.InteractionSystem; +using MelonLoader; +using UnityEngine; + +namespace NAK.LegacyContentMitigation; + +public class LegacyContentMitigationMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + #region Melon Preferences + + // private static readonly MelonPreferences_Category Category = + // MelonPreferences.CreateCategory(nameof(LegacyContentMitigationMod)); + // + // private static readonly MelonPreferences_Entry EntryEnabled = + // Category.CreateEntry( + // "use_legacy_mitigation", + // true, + // "Enabled", + // description: "Enable legacy content camera hack when in Legacy worlds."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ApplyPatches(typeof(Patches.PlayerSetup_Patches)); // add MultiPassCamera to VR camera + ApplyPatches(typeof(Patches.SceneLoaded_Patches)); // enable / disable in legacy worlds + ApplyPatches(typeof(Patches.CVRWorld_Patches)); // post processing shit + ApplyPatches(typeof(Patches.CVRTools_Patches)); // prevent shader replacement when fix is active + ApplyPatches(typeof(Patches.HeadHiderManager_Patches)); // prevent main cam triggering early head hide + + InitializeIntegration("BTKUILib", Integrations.BtkUiAddon.Initialize); // quick menu options + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private static void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + Logger.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/LegacyContentMitigation/ModSettings.cs b/LegacyContentMitigation/ModSettings.cs new file mode 100644 index 0000000..0fdef02 --- /dev/null +++ b/LegacyContentMitigation/ModSettings.cs @@ -0,0 +1,24 @@ +using MelonLoader; + +namespace NAK.LegacyContentMitigation; + +internal static class ModSettings +{ + #region Constants + + internal const string ModName = nameof(LegacyContentMitigation); + internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = + Category.CreateEntry("auto_for_legacy_worlds", true, + "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); + + #endregion Melon Preferences +} \ No newline at end of file diff --git a/LegacyContentMitigation/Patches.cs b/LegacyContentMitigation/Patches.cs new file mode 100644 index 0000000..8fb82c5 --- /dev/null +++ b/LegacyContentMitigation/Patches.cs @@ -0,0 +1,125 @@ +using ABI_RC.Core; +using ABI_RC.Core.Base; +using ABI_RC.Core.Base.Jobs; +using ABI_RC.Core.Networking.API.Responses; +using ABI_RC.Core.Player; +using ABI_RC.Core.Player.LocalClone; +using ABI_RC.Core.Player.TransformHider; +using ABI.CCK.Components; +using HarmonyLib; +using UnityEngine; +using UnityEngine.Rendering.PostProcessing; + +namespace NAK.LegacyContentMitigation.Patches; + +internal static class PlayerSetup_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] + private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) + { + FakeMultiPassHack.Instance = __instance.vrCam.AddComponentIfMissing(); + FakeMultiPassHack.Instance.enabled = ModSettings.EntryAutoForLegacyWorlds.Value; + } +} + +internal static class SceneLoaded_Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(SceneLoaded), nameof(SceneLoaded.OnSceneLoadedHandleJob))] + private static void Prefix_SceneLoaded_OnSceneLoadedHandleJob() + { + if (!ModSettings.EntryAutoForLegacyWorlds.Value) + { + LegacyContentMitigationMod.Logger.Msg("LegacyContentMitigationMod is disabled."); + FakeMultiPassHack.Instance.SetMultiPassActive(false); + return; + } + + bool sceneIsNotSpi = CVRWorld.CompatibilityVersion == CompatibilityVersions.NotSpi; + string logText = sceneIsNotSpi + ? "Legacy world detected, enabling legacy content mitigation." + : "Loaded scene is not considered Legacy content. Disabling if active."; + + LegacyContentMitigationMod.Logger.Msg(logText); + FakeMultiPassHack.Instance.SetMultiPassActive(sceneIsNotSpi); + } +} + +internal static class CVRWorld_Patches +{ + // Third Person patches same methods: + // https://github.com/NotAKidoS/NAK_CVR_Mods/blob/3d6b1bbd59d23be19fe3594e104ad26e4ac0adcd/ThirdPerson/Patches.cs#L15-L22 + [HarmonyPriority(Priority.Last)] + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.CopyRefCamValues))] + [HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.SetDefaultCamValues))] + private static void Postfix_CVRWorld_SetDefaultCamValues(ref CVRWorld __instance) + { + LegacyContentMitigationMod.Logger.Msg("Legacy world camera values updated."); + FakeMultiPassHack.Instance.OnMainCameraChanged(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.UpdatePostProcessing))] + private static void Postfix_CVRWorld_UpdatePostProcessing(ref CVRWorld __instance) + { + if (!FakeMultiPassHack.Instance.IsActive) return; + foreach (PostProcessEffectSettings motionBlur in __instance._postProcessingMotionBlurList) + motionBlur.active = false; // force off cause its fucked and no one cares + } +} + +internal static class CVRTools_Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(CVRTools), nameof(CVRTools.ReplaceShaders), typeof(Material), typeof(string))] + private static bool Prefix_CVRTools_ReplaceShaders(Material material, string fallbackShaderName = "") + { + // When in a legacy world with the hack enabled, do not replace shaders + return !FakeMultiPassHack.Instance.IsActive; + } +} + +internal static class HeadHiderManager_Patches +{ + // despite the visual clone not being normally accessible, i fix it cause mod: + // https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix + + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformHiderManager), nameof(TransformHiderManager.OnPreRenderCallback))] + [HarmonyPatch(typeof(TransformHiderManager), nameof(TransformHiderManager.OnPreRenderCallback))] + [HarmonyPatch(typeof(LocalCloneManager), nameof(LocalCloneManager.OnPreRenderCallback))] + [HarmonyPatch(typeof(LocalCloneManager), nameof(LocalCloneManager.OnPostRenderCallback))] + private static bool Prefix_HeadHiderManagers_OnRenderCallbacks(Camera cam) + { + if (!FakeMultiPassHack.Instance.IsActive) + return true; // not active, no need + + // dont let real camera trigger head hiding to occur or reset- leave it to the left/right eyes + return !cam.CompareTag("MainCamera"); // we spoof playersetup.activeCam, need check tag + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformHiderManager), nameof(TransformHiderManager.OnPostRenderCallback))] + [HarmonyPatch(typeof(LocalCloneManager), nameof(LocalCloneManager.OnPostRenderCallback))] + private static void Prefix_HeadHiderManagers_OnPostRenderCallback(Camera cam, ref MonoBehaviour __instance) + { + if (!FakeMultiPassHack.Instance.IsActive) return; + + if (FakeMultiPassHack.Instance.RenderingEye == Camera.MonoOrStereoscopicEye.Left) + SetResetAfterRenderFlag(__instance, true); // so right eye mirror sees head + + if (FakeMultiPassHack.Instance.RenderingEye == Camera.MonoOrStereoscopicEye.Right) + SetResetAfterRenderFlag(__instance, !TransformHiderManager.s_UseCloneToCullUi); // dont undo if ui culling + + return; + void SetResetAfterRenderFlag(MonoBehaviour headHiderManager, bool flag) + { + if (headHiderManager is LocalCloneManager localCloneManager) + localCloneManager._resetAfterThisRender = flag; + else if (headHiderManager is TransformHiderManager transformHiderManager) + transformHiderManager._resetAfterThisRender = flag; + } + } +} \ No newline at end of file diff --git a/LegacyContentMitigation/Properties/AssemblyInfo.cs b/LegacyContentMitigation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..afc8905 --- /dev/null +++ b/LegacyContentMitigation/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.LegacyContentMitigation.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.LegacyContentMitigation))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.LegacyContentMitigation))] + +[assembly: MelonInfo( + typeof(NAK.LegacyContentMitigation.LegacyContentMitigationMod), + nameof(NAK.LegacyContentMitigation), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LegacyContentMitigation" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.LegacyContentMitigation.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "Exterrata & NotAKidoS"; +} \ No newline at end of file From d4dc8fba4445ab15fd02bdbd1beff9c9357c67d6 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:14:39 -0600 Subject: [PATCH 065/188] LegacyContentMitigation: prepare for submission --- .../Integrations/BtkUiAddon.cs | 2 +- LegacyContentMitigation/Patches.cs | 5 +++- LegacyContentMitigation/README.md | 18 +++++++++++++++ LegacyContentMitigation/format.json | 23 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 LegacyContentMitigation/README.md create mode 100644 LegacyContentMitigation/format.json diff --git a/LegacyContentMitigation/Integrations/BtkUiAddon.cs b/LegacyContentMitigation/Integrations/BtkUiAddon.cs index 7a11187..db20f11 100644 --- a/LegacyContentMitigation/Integrations/BtkUiAddon.cs +++ b/LegacyContentMitigation/Integrations/BtkUiAddon.cs @@ -41,7 +41,7 @@ public static class BtkUiAddon string.Empty, "Records Camera events & logs them next frame. Useful for determining camera render order shenanigans."); printCameraCallbacksButton.OnPress += () => CameraCallbackLogger.Instance.LogCameraEvents(); - OnCurrentStateToggled(FakeMultiPassHack.Instance.IsEnabled); + OnMultiPassActiveChanged(FakeMultiPassHack.Instance.IsEnabled); FakeMultiPassHack.OnMultiPassActiveChanged += OnMultiPassActiveChanged; } diff --git a/LegacyContentMitigation/Patches.cs b/LegacyContentMitigation/Patches.cs index 8fb82c5..e2dabaf 100644 --- a/LegacyContentMitigation/Patches.cs +++ b/LegacyContentMitigation/Patches.cs @@ -9,6 +9,7 @@ using ABI.CCK.Components; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering.PostProcessing; +using UnityEngine.SceneManagement; namespace NAK.LegacyContentMitigation.Patches; @@ -27,8 +28,10 @@ internal static class SceneLoaded_Patches { [HarmonyPrefix] [HarmonyPatch(typeof(SceneLoaded), nameof(SceneLoaded.OnSceneLoadedHandleJob))] - private static void Prefix_SceneLoaded_OnSceneLoadedHandleJob() + private static void Prefix_SceneLoaded_OnSceneLoadedHandleJob(Scene scene, LoadSceneMode mode) { + if (mode == LoadSceneMode.Additive) return; + if (!ModSettings.EntryAutoForLegacyWorlds.Value) { LegacyContentMitigationMod.Logger.Msg("LegacyContentMitigationMod is disabled."); diff --git a/LegacyContentMitigation/README.md b/LegacyContentMitigation/README.md new file mode 100644 index 0000000..0236abe --- /dev/null +++ b/LegacyContentMitigation/README.md @@ -0,0 +1,18 @@ +# LegacyContentMitigation + +Experimental mod that manually renders both VR eyes separately while in Legacy Worlds. + +The mod will automatically kick-in when loading NonSpi Worlds & prevent Shader Replacement for all content while active. You can also manually toggle it within the BTKUI Misc tab for experimenting. + +-# There is an obvious performance penalty with rendering the world 2x & toggling the head hiding per eye, so enabling the mod outside of Legacy Worlds is not recommended. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/LegacyContentMitigation/format.json b/LegacyContentMitigation/format.json new file mode 100644 index 0000000..65e88b1 --- /dev/null +++ b/LegacyContentMitigation/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "LegacyContentMitigation", + "modversion": "1.0.0", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Experimental mod that manually renders both VR eyes separately while in Legacy Worlds.\n\nThe mod will automatically kick-in when loading NonSpi Worlds & prevent Shader Replacement for all content while active. You can also manually toggle it within the BTKUI Misc tab for experimenting.\n\n-# There is an obvious performance penalty with rendering the world 2x & toggling the head hiding per eye, so enabling the mod outside of Legacy Worlds is not recommended.", + "searchtags": [ + "legacy", + "content", + "spi", + "shaders" + ], + "requirements": [ + "BTKUILib" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/LegacyContentMitigation.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LegacyContentMitigation/", + "changelog": "- Initial release", + "embedcolor": "#507e64" +} \ No newline at end of file From e5ee4631c7158388c51efb316ed0cdf2abca6048 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:15:22 -0600 Subject: [PATCH 066/188] LegacyContentMitigation: bump for updater --- LegacyContentMitigation/Properties/AssemblyInfo.cs | 2 +- LegacyContentMitigation/format.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LegacyContentMitigation/Properties/AssemblyInfo.cs b/LegacyContentMitigation/Properties/AssemblyInfo.cs index afc8905..e460687 100644 --- a/LegacyContentMitigation/Properties/AssemblyInfo.cs +++ b/LegacyContentMitigation/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.LegacyContentMitigation.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "Exterrata & NotAKidoS"; } \ No newline at end of file diff --git a/LegacyContentMitigation/format.json b/LegacyContentMitigation/format.json index 65e88b1..34a41ec 100644 --- a/LegacyContentMitigation/format.json +++ b/LegacyContentMitigation/format.json @@ -1,11 +1,11 @@ { "_id": -1, "name": "LegacyContentMitigation", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2024r177", "loaderversion": "0.6.1", "modtype": "Mod", - "author": "NotAKidoS", + "author": "Exterrata, NotAKidoS", "description": "Experimental mod that manually renders both VR eyes separately while in Legacy Worlds.\n\nThe mod will automatically kick-in when loading NonSpi Worlds & prevent Shader Replacement for all content while active. You can also manually toggle it within the BTKUI Misc tab for experimenting.\n\n-# There is an obvious performance penalty with rendering the world 2x & toggling the head hiding per eye, so enabling the mod outside of Legacy Worlds is not recommended.", "searchtags": [ "legacy", From 39da88d3d3355da5024ef435ed594925e7f03238 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:17:16 -0600 Subject: [PATCH 067/188] LegacyContentMitigation: fix cvrmg embed color --- LegacyContentMitigation/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LegacyContentMitigation/format.json b/LegacyContentMitigation/format.json index 34a41ec..480d0fb 100644 --- a/LegacyContentMitigation/format.json +++ b/LegacyContentMitigation/format.json @@ -19,5 +19,5 @@ "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/LegacyContentMitigation.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LegacyContentMitigation/", "changelog": "- Initial release", - "embedcolor": "#507e64" + "embedcolor": "#f61963" } \ No newline at end of file From 3a00ff104a3db2b29b387f57ff99cab8c5e07a10 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:46:06 -0600 Subject: [PATCH 068/188] GrabbableSteeringWheel: commit --- .../GrabbableSteeringWheel.csproj | 17 + .../BoneVertexBoundsUtility.cs | 401 ++++++++++++++++++ .../SteeringWheelPickup.cs | 203 +++++++++ GrabbableSteeringWheel/Main.cs | 61 +++ GrabbableSteeringWheel/ModSettings.cs | 24 ++ GrabbableSteeringWheel/Patches.cs | 49 +++ .../Properties/AssemblyInfo.cs | 33 ++ GrabbableSteeringWheel/README.md | 14 + GrabbableSteeringWheel/format.json | 23 + 9 files changed, 825 insertions(+) create mode 100644 GrabbableSteeringWheel/GrabbableSteeringWheel.csproj create mode 100644 GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs create mode 100644 GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs create mode 100644 GrabbableSteeringWheel/Main.cs create mode 100644 GrabbableSteeringWheel/ModSettings.cs create mode 100644 GrabbableSteeringWheel/Patches.cs create mode 100644 GrabbableSteeringWheel/Properties/AssemblyInfo.cs create mode 100644 GrabbableSteeringWheel/README.md create mode 100644 GrabbableSteeringWheel/format.json diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj b/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj new file mode 100644 index 0000000..9855f81 --- /dev/null +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj @@ -0,0 +1,17 @@ + + + + net48 + + + + ..\.ManagedLibs\BTKUILib.dll + + + ..\.ManagedLibs\TheClapper.dll + + + + + + diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs new file mode 100644 index 0000000..2864f49 --- /dev/null +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs @@ -0,0 +1,401 @@ +using System.Collections; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Profiling; +using UnityEngine; +using UnityEngine.Animations; + +public class BoneVertexBoundsUtility : MonoBehaviour +{ + private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate"); + private static readonly ProfilerMarker s_processChildRenderersMarker = new("BoneVertexBounds.ProcessChildRenderers"); + private static readonly ProfilerMarker s_processSkinnedMeshesMarker = new("BoneVertexBounds.ProcessSkinnedMeshes"); + private static readonly ProfilerMarker s_processVerticesMarker = new("BoneVertexBounds.ProcessVertices"); + private static readonly ProfilerMarker s_processConstraintsMarker = new("BoneVertexBounds.ProcessConstraints"); + private static readonly ProfilerMarker s_jobExecutionMarker = new("BoneVertexBounds.JobExecution"); + private static readonly ProfilerMarker s_meshCopyMarker = new("BoneVertexBounds.MeshCopy"); + + private static BoneVertexBoundsUtility instance; + + private static BoneVertexBoundsUtility Instance + { + get + { + if (instance != null) return instance; + GameObject go = new("BoneVertexBoundsUtility"); + instance = go.AddComponent(); + DontDestroyOnLoad(go); + return instance; + } + } + + [Flags] + public enum BoundsCalculationFlags + { + None = 0, + IncludeChildren = 1 << 0, + IncludeSkinnedMesh = 1 << 1, + IncludeConstraints = 1 << 2, + All = IncludeChildren | IncludeSkinnedMesh | IncludeConstraints + } + + public struct BoundsResult + { + public bool IsValid; + public Bounds LocalBounds; + } + + /// + /// Calculates the bounds of a transform based on: + /// - Children Renderers + /// - Skinned Mesh Weights + /// - Constrained Child Renderers & Skinned Mesh Weights + /// + public static void CalculateBoneWeightedBounds(Transform bone, float weightThreshold, BoundsCalculationFlags flags, Action onComplete) + => Instance.StartCoroutine(Instance.CalculateBoundsCoroutine(bone, weightThreshold, flags, onComplete)); + + private IEnumerator CalculateBoundsCoroutine(Transform bone, float weightThreshold, BoundsCalculationFlags flags, Action onComplete) + { + using (s_calculateBoundsMarker.Auto()) + { + BoundsResult result = new(); + var allWeightedPoints = new List(); + bool hasValidPoints = false; + + // Child renderers + IEnumerator ProcessChildRenderersLocal(Transform targetBone, Action> onChildPoints) + { + using (s_processChildRenderersMarker.Auto()) + { + var points = new List(); + var childRenderers = targetBone.GetComponentsInChildren() + .Where(r => r is not SkinnedMeshRenderer) + .ToArray(); + + foreach (Renderer childRend in childRenderers) + { + Bounds bounds = childRend.localBounds; + var corners = new Vector3[8]; + Vector3 ext = bounds.extents; + Vector3 center = bounds.center; + + corners[0] = new Vector3(center.x - ext.x, center.y - ext.y, center.z - ext.z); + corners[1] = new Vector3(center.x + ext.x, center.y - ext.y, center.z - ext.z); + corners[2] = new Vector3(center.x - ext.x, center.y + ext.y, center.z - ext.z); + corners[3] = new Vector3(center.x + ext.x, center.y + ext.y, center.z - ext.z); + corners[4] = new Vector3(center.x - ext.x, center.y - ext.y, center.z + ext.z); + corners[5] = new Vector3(center.x + ext.x, center.y - ext.y, center.z + ext.z); + corners[6] = new Vector3(center.x - ext.x, center.y + ext.y, center.z + ext.z); + corners[7] = new Vector3(center.x + ext.x, center.y + ext.y, center.z + ext.z); + + for (int i = 0; i < 8; i++) + points.Add(targetBone.InverseTransformPoint(childRend.transform.TransformPoint(corners[i]))); + } + + onChildPoints?.Invoke(points); + } + yield break; + } + + // Skinned mesh renderers + IEnumerator ProcessSkinnedMeshRenderersLocal(Transform targetBone, float threshold, Action> onSkinnedPoints) + { + using (s_processSkinnedMeshesMarker.Auto()) + { + var points = new List(); + var siblingAndParentSkinnedMesh = targetBone.root.GetComponentsInChildren(); + var relevantMeshes = siblingAndParentSkinnedMesh.Where(smr => DoesMeshUseBone(smr, targetBone)).ToArray(); + + foreach (SkinnedMeshRenderer smr in relevantMeshes) + { + yield return StartCoroutine(ProcessSkinnedMesh(smr, targetBone, threshold, meshPoints => + { + if (meshPoints is { Length: > 0 }) + points.AddRange(meshPoints); + })); + } + + onSkinnedPoints?.Invoke(points); + } + } + + // Constraints + IEnumerator ProcessConstraintsLocal(Transform targetBone, float threshold, BoundsCalculationFlags constraintFlags, Action> onConstraintPoints) + { + using (s_processConstraintsMarker.Auto()) + { + var points = new List(); + var processedTransforms = new HashSet(); + var constrainedTransforms = new List(); + + // Find all constrained objects that reference our bone + var constraints = targetBone.root.GetComponentsInChildren(); + + foreach (IConstraint constraint in constraints) + { + for (int i = 0; i < constraint.sourceCount; i++) + { + if (constraint.GetSource(i).sourceTransform != targetBone) continue; + constrainedTransforms.Add(((Behaviour)constraint).transform); + break; + } + } + + // Process each constrained transform + foreach (Transform constrainedTransform in constrainedTransforms) + { + if (!processedTransforms.Add(constrainedTransform)) + continue; + + var localPoints = new List(); + bool hasLocalPoints = false; + + // Process child renderers if enabled + if ((constraintFlags & BoundsCalculationFlags.IncludeChildren) != 0) + { + yield return StartCoroutine(ProcessChildRenderersLocal(constrainedTransform, childPoints => + { + if (childPoints is not { Count: > 0 }) return; + localPoints.AddRange(childPoints); + hasLocalPoints = true; + })); + } + + // Process skinned mesh if enabled + if ((constraintFlags & BoundsCalculationFlags.IncludeSkinnedMesh) != 0) + { + yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(constrainedTransform, threshold, skinnedPoints => + { + if (skinnedPoints is not { Count: > 0 }) return; + localPoints.AddRange(skinnedPoints); + hasLocalPoints = true; + })); + } + + if (!hasLocalPoints) + continue; + + // Convert all points to bone space + foreach (Vector3 point in localPoints) + points.Add(targetBone.InverseTransformPoint(constrainedTransform.TransformPoint(point))); + } + + onConstraintPoints?.Invoke(points); + } + } + + // Process child renderers + if ((flags & BoundsCalculationFlags.IncludeChildren) != 0) + { + yield return StartCoroutine(ProcessChildRenderersLocal(bone, childPoints => + { + if (childPoints is not { Count: > 0 }) return; + allWeightedPoints.AddRange(childPoints); + hasValidPoints = true; + })); + } + + // Process skinned mesh renderers + if ((flags & BoundsCalculationFlags.IncludeSkinnedMesh) != 0) + { + yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints => + { + if (skinnedPoints == null || skinnedPoints.Count <= 0) return; + allWeightedPoints.AddRange(skinnedPoints); + hasValidPoints = true; + })); + } + + // Process constraints + if ((flags & BoundsCalculationFlags.IncludeConstraints) != 0) + { + // Use only Children and SkinnedMesh flags for constraint processing to prevent recursion (maybe make optional)? + BoundsCalculationFlags constraintFlags = flags & ~BoundsCalculationFlags.IncludeConstraints; + yield return StartCoroutine(ProcessConstraintsLocal(bone, weightThreshold, constraintFlags, constraintPoints => + { + if (constraintPoints is not { Count: > 0 }) return; + allWeightedPoints.AddRange(constraintPoints); + hasValidPoints = true; + })); + } + + if (!hasValidPoints) + { + result.IsValid = false; + onComplete?.Invoke(result); + yield break; + } + + // Calculate final bounds in bone space + Bounds bounds = new(allWeightedPoints[0], Vector3.zero); + foreach (Vector3 point in allWeightedPoints) + bounds.Encapsulate(point); + + // Ensure minimum size + Vector3 size = bounds.size; + size = Vector3.Max(size, Vector3.one * 0.01f); + bounds.size = size; + + result.IsValid = true; + result.LocalBounds = bounds; + + onComplete?.Invoke(result); + } + } + + private static bool DoesMeshUseBone(SkinnedMeshRenderer smr, Transform bone) + => smr.bones != null && smr.bones.Contains(bone); + + private IEnumerator ProcessSkinnedMesh(SkinnedMeshRenderer smr, Transform bone, float weightThreshold, + Action onComplete) + { + Mesh mesh = smr.sharedMesh; + if (mesh == null) + { + onComplete?.Invoke(null); + yield break; + } + + // Find bone index + int boneIndex = Array.IndexOf(smr.bones, bone); + if (boneIndex == -1) + { + onComplete?.Invoke(null); + yield break; + } + + Mesh meshToUse = mesh; + GameObject tempGO = null; + + try + { + // Handle non-readable meshes (ReadItAnyway lmao) + if (!mesh.isReadable) + { + using (s_meshCopyMarker.Auto()) + { + tempGO = new GameObject("TempMeshReader"); + SkinnedMeshRenderer tempSMR = tempGO.AddComponent(); + tempSMR.sharedMesh = mesh; + meshToUse = new Mesh(); + tempSMR.BakeMesh(meshToUse); + } + } + + Mesh.MeshDataArray meshDataArray = Mesh.AcquireReadOnlyMeshData(meshToUse); + Mesh.MeshData meshData = meshDataArray[0]; + + var vertexCount = meshData.vertexCount; + var vertices = new NativeArray(vertexCount, Allocator.TempJob); + var weights = new NativeArray(vertexCount, Allocator.TempJob); + var results = new NativeArray(vertexCount, Allocator.TempJob); + + meshData.GetVertices(vertices); + weights.CopyFrom(mesh.boneWeights); + + // Debug.Log(vertices.Length); + // Debug.Log(weights.Length); + + using (s_processVerticesMarker.Auto()) + { + try + { + Transform rootBone = smr.rootBone ? smr.rootBone.transform : smr.transform; + Matrix4x4 meshToWorld = Matrix4x4.TRS(smr.transform.position, smr.transform.rotation, rootBone.lossyScale); + + // Fixes setup where mesh was in diff hierarchy & 0.001 scale, bone & root bone outside & above + meshToWorld *= Matrix4x4.TRS(Vector3.zero, Quaternion.identity, smr.transform.localScale); + + ProcessVerticesJob processJob = new() + { + Vertices = vertices, + BoneWeights = weights, + Results = results, + BoneIndex = boneIndex, + WeightThreshold = weightThreshold, + MeshToWorld = meshToWorld, + WorldToBone = bone.worldToLocalMatrix + }; + + using (s_jobExecutionMarker.Auto()) + { + int batchCount = Mathf.Max(1, vertexCount / 64); + JobHandle jobHandle = processJob.Schedule(vertexCount, batchCount); + while (!jobHandle.IsCompleted) + yield return null; + + jobHandle.Complete(); + } + + // Collect valid points + var validPoints = new List(); + for (int i = 0; i < results.Length; i++) + if (results[i].IsValid) validPoints.Add(results[i].Position); + + onComplete?.Invoke(validPoints.ToArray()); + } + finally + { + vertices.Dispose(); + weights.Dispose(); + results.Dispose(); + meshDataArray.Dispose(); + } + } + } + finally + { + // Destroy duplicated baked mesh if we created one to read mesh data + if (!mesh.isReadable && meshToUse != mesh) Destroy(meshToUse); + if (tempGO != null) Destroy(tempGO); + } + } + + private struct VertexResult + { + public Vector3 Position; + public bool IsValid; + } + + [BurstCompile] + private struct ProcessVerticesJob : IJobParallelFor + { + [ReadOnly] public NativeArray Vertices; + [ReadOnly] public NativeArray BoneWeights; + [NativeDisableParallelForRestriction] + public NativeArray Results; + [ReadOnly] public int BoneIndex; + [ReadOnly] public float WeightThreshold; + [ReadOnly] public Matrix4x4 MeshToWorld; + [ReadOnly] public Matrix4x4 WorldToBone; + + public void Execute(int i) + { + BoneWeight weight = BoneWeights[i]; + float totalWeight = 0f; + + if (weight.boneIndex0 == BoneIndex) totalWeight += weight.weight0; + if (weight.boneIndex1 == BoneIndex) totalWeight += weight.weight1; + if (weight.boneIndex2 == BoneIndex) totalWeight += weight.weight2; + if (weight.boneIndex3 == BoneIndex) totalWeight += weight.weight3; + + if (totalWeight >= WeightThreshold) + { + // Transform vertex to bone space + Vector3 worldPos = MeshToWorld.MultiplyPoint3x4(Vertices[i]); + Vector3 boneLocalPos = WorldToBone.MultiplyPoint3x4(worldPos); + + Results[i] = new VertexResult + { + Position = boneLocalPos, + IsValid = true + }; + } + else + { + Results[i] = new VertexResult { IsValid = false }; + } + } + } +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs new file mode 100644 index 0000000..0807a2a --- /dev/null +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs @@ -0,0 +1,203 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +// TODO: +// Fix multi-grab (limitation of Pickupable) +// Think this can be fixed by forcing ungrab & monitoring ourselves for release +// Add configurable override for steering range +// Fix steering wheel resetting immediatly on release +// Fix input patch not being multiplicative (so joysticks can still work) +// Prevent pickup in Desktop + +public class SteeringWheelPickup : Pickupable +{ + private RCC_CarControllerV3 _carController; + + private static readonly Dictionary ActiveWheels = new(); + + public static float GetSteerInput(RCC_CarControllerV3 carController) + { + if (ActiveWheels.TryGetValue(carController, out SteeringWheelPickup wheel) && wheel.IsPickedUp) + return wheel.GetNormalizedValue(); + return 0f; + } + + public void SetupSteeringWheel(RCC_CarControllerV3 carController) + { + _carController = carController; + if (!ActiveWheels.ContainsKey(carController)) + { + ActiveWheels[carController] = this; + carController.useCounterSteering = false; + carController.useSteeringSmoother = false; + } + } + + private void OnDestroy() + { + if (_carController != null && ActiveWheels.ContainsKey(_carController)) + ActiveWheels.Remove(_carController); + } + + #region Configuration Properties Override + + public override bool DisallowTheft => true; + public override float MaxGrabDistance => 0.8f; + public override float MaxPushDistance => 0f; + public override bool IsAutoHold => false; + public override bool IsObjectRotationAllowed => false; + public override bool IsObjectPushPullAllowed => false; + public override bool IsObjectUseAllowed => false; + + public override bool CanPickup => IsPickupable && _carController?.SteeringWheel != null; + + #endregion Configuration Properties Override + + #region RCC Stuff + + private float GetMaxSteeringRange() + => _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier); + + private float GetSteeringWheelSign() + => Mathf.Sign(_carController.steeringWheelAngleMultiplier) * -1f; // Idk + + private Vector3 GetSteeringWheelLocalAxis() + { + return _carController.steeringWheelRotateAround switch + { + RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right, + RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up, + RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward, + _ => Vector3.forward + }; + } + + #endregion RCC Stuff + + #region Rotation Tracking + + private readonly List _trackedTransforms = new(); + private readonly List _lastPositions = new(); + private readonly List _totalAngles = new(); + private bool _isTracking; + private float _averageAngle; + + private void StartTrackingTransform(Transform trans) + { + if (trans == null) return; + + _trackedTransforms.Add(trans); + _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); + _totalAngles.Add(0f); + _isTracking = true; + } + + private void StopTrackingTransform(Transform trans) + { + int index = _trackedTransforms.IndexOf(trans); + if (index != -1) + { + _trackedTransforms.RemoveAt(index); + _lastPositions.RemoveAt(index); + _totalAngles.RemoveAt(index); + } + + _isTracking = _trackedTransforms.Count > 0; + } + + private void UpdateRotationTracking() + { + if (!_isTracking || _trackedTransforms.Count == 0) return; + + Vector3 trackingAxis = GetSteeringWheelLocalAxis(); + + for (int i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + + Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position); + if (currentPosition == _lastPositions[i]) continue; + + Vector3 previousVector = _lastPositions[i]; + Vector3 currentVector = currentPosition; + + previousVector = Vector3.ProjectOnPlane(previousVector, trackingAxis).normalized; + currentVector = Vector3.ProjectOnPlane(currentVector, trackingAxis).normalized; + + if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f) + { + float deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis); + if (Mathf.Abs(deltaAngle) < 90f) _totalAngles[i] += deltaAngle; // Prevent big tracking jumps + } + + _lastPositions[i] = currentPosition; + } + + // Calculate average every frame using only valid transforms + float sumAngles = 0f; + int validTransforms = 0; + + for (int i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + sumAngles += _totalAngles[i]; + validTransforms++; + } + + if (validTransforms > 0) + _averageAngle = sumAngles / validTransforms; + } + + private float GetNormalizedValue() + { + float maxRange = GetMaxSteeringRange(); + // return Mathf.Clamp(_averageAngle / (maxRange * 0.5f), -1f, 1f); + return Mathf.Clamp(_averageAngle / (maxRange), -1f, 1f) * GetSteeringWheelSign(); + } + + private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition) + { + Transform steeringTransform = _carController.SteeringWheel; + + Quaternion localRotation = steeringTransform.localRotation; + steeringTransform.localRotation = _carController.orgSteeringWheelRot; + + Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition); + steeringTransform.localRotation = localRotation; + + return localPosition; + } + + #endregion Rotation Tracking + + public override void OnGrab(InteractionContext context, Vector3 grabPoint) + { + if (ControllerRay?.pivotPoint == null) + return; + + StartTrackingTransform(ControllerRay.transform); + } + + public override void OnDrop(InteractionContext context) + { + if (ControllerRay?.transform != null) + StopTrackingTransform(ControllerRay.transform); + } + + private void Update() + { + if (!IsPickedUp || ControllerRay?.pivotPoint == null || _carController == null) + return; + + UpdateRotationTracking(); + } + + #region Unused Abstract Method Implementations + + public override void OnUseDown(InteractionContext context) { } + public override void OnUseUp(InteractionContext context) { } + public override void OnFlingTowardsTarget(Vector3 target) { } + + #endregion Unused Abstract Method Implementations +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Main.cs b/GrabbableSteeringWheel/Main.cs new file mode 100644 index 0000000..1ced7e0 --- /dev/null +++ b/GrabbableSteeringWheel/Main.cs @@ -0,0 +1,61 @@ +using MelonLoader; +using NAK.GrabbableSteeringWheel.Patches; + +namespace NAK.GrabbableSteeringWheel; + +public class GrabbableSteeringWheelMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + #region Melon Preferences + + // private static readonly MelonPreferences_Category Category = + // MelonPreferences.CreateCategory(nameof(GrabbableSteeringWheelMod)); + // + // private static readonly MelonPreferences_Entry EntryEnabled = + // Category.CreateEntry( + // "use_legacy_mitigation", + // true, + // "Enabled", + // description: "Enable legacy content camera hack when in Legacy worlds."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ApplyPatches(typeof(RCCCarControllerV3_Patches)); + ApplyPatches(typeof(CVRInputManager_Patches)); + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private static void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + Logger.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/ModSettings.cs b/GrabbableSteeringWheel/ModSettings.cs new file mode 100644 index 0000000..26a56be --- /dev/null +++ b/GrabbableSteeringWheel/ModSettings.cs @@ -0,0 +1,24 @@ +using MelonLoader; + +namespace NAK.GrabbableSteeringWheel; + +internal static class ModSettings +{ + #region Constants + + internal const string ModName = nameof(GrabbableSteeringWheel); + // internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + // internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = + // Category.CreateEntry("auto_for_legacy_worlds", true, + // "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); + // + #endregion Melon Preferences +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Patches.cs b/GrabbableSteeringWheel/Patches.cs new file mode 100644 index 0000000..c6a828c --- /dev/null +++ b/GrabbableSteeringWheel/Patches.cs @@ -0,0 +1,49 @@ +using ABI_RC.Systems.InputManagement; +using ABI_RC.Systems.Movement; +using HarmonyLib; +using UnityEngine; + +namespace NAK.GrabbableSteeringWheel.Patches; + +internal static class RCCCarControllerV3_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))] + private static void Postfix_RCC_CarControllerV3_Awake(ref RCC_CarControllerV3 __instance) + { + Transform steeringWheelTransform = __instance.SteeringWheel; + if (steeringWheelTransform == null) + return; + + RCC_CarControllerV3 v3 = __instance; + BoneVertexBoundsUtility.CalculateBoneWeightedBounds( + steeringWheelTransform, + 0.8f, + BoneVertexBoundsUtility.BoundsCalculationFlags.All, + result => + { + if (!result.IsValid) + return; + + BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent(); + boxCollider.center = result.LocalBounds.center; + boxCollider.size = result.LocalBounds.size; + + SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent(); + steeringWheel.SetupSteeringWheel(v3); + }); + } +} + +internal static class CVRInputManager_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRInputManager), nameof(CVRInputManager.UpdateInput))] + private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance) + { + // Steering input is clamped in RCC component + if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()) + __instance.steering += SteeringWheelPickup.GetSteerInput( + BetterBetterCharacterController.Instance._lastCvrSeat._carController); + } +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Properties/AssemblyInfo.cs b/GrabbableSteeringWheel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6441f79 --- /dev/null +++ b/GrabbableSteeringWheel/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using MelonLoader; +using NAK.GrabbableSteeringWheel; +using NAK.GrabbableSteeringWheel.Properties; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))] + +[assembly: MelonInfo( + typeof(GrabbableSteeringWheelMod), + nameof(NAK.GrabbableSteeringWheel), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/GrabbableSteeringWheel" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.GrabbableSteeringWheel.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/README.md b/GrabbableSteeringWheel/README.md new file mode 100644 index 0000000..08942df --- /dev/null +++ b/GrabbableSteeringWheel/README.md @@ -0,0 +1,14 @@ +# GrabbableSteeringWheel + +Experiment with making rigged RCC vehicle steering wheels work for VR steering input. +~~~~ +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/GrabbableSteeringWheel/format.json b/GrabbableSteeringWheel/format.json new file mode 100644 index 0000000..480d0fb --- /dev/null +++ b/GrabbableSteeringWheel/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "LegacyContentMitigation", + "modversion": "1.0.1", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "Exterrata, NotAKidoS", + "description": "Experimental mod that manually renders both VR eyes separately while in Legacy Worlds.\n\nThe mod will automatically kick-in when loading NonSpi Worlds & prevent Shader Replacement for all content while active. You can also manually toggle it within the BTKUI Misc tab for experimenting.\n\n-# There is an obvious performance penalty with rendering the world 2x & toggling the head hiding per eye, so enabling the mod outside of Legacy Worlds is not recommended.", + "searchtags": [ + "legacy", + "content", + "spi", + "shaders" + ], + "requirements": [ + "BTKUILib" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/LegacyContentMitigation.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LegacyContentMitigation/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file From 5c8c724b5883098fc24d92a6c72cc1a87ee7b437 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 7 Jan 2025 02:36:44 -0600 Subject: [PATCH 069/188] RCCVirtualSteeringWheel: renamed mod, fixed things --- .../SteeringWheelPickup.cs | 203 ------------ GrabbableSteeringWheel/Main.cs | 61 ---- GrabbableSteeringWheel/ModSettings.cs | 24 -- RCCVirtualSteeringWheel/Main.cs | 40 +++ RCCVirtualSteeringWheel/ModSettings.cs | 31 ++ .../Patches.cs | 22 +- .../Properties/AssemblyInfo.cs | 16 +- .../RCCVirtualSteeringWheel.csproj | 4 +- .../Components/SteeringWheelPickup.cs | 36 ++ .../Components/SteeringWheelRoot.cs | 313 ++++++++++++++++++ .../Util}/BoneVertexBoundsUtility.cs | 4 +- .../README.md | 5 +- .../format.json | 0 13 files changed, 445 insertions(+), 314 deletions(-) delete mode 100644 GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs delete mode 100644 GrabbableSteeringWheel/Main.cs delete mode 100644 GrabbableSteeringWheel/ModSettings.cs create mode 100644 RCCVirtualSteeringWheel/Main.cs create mode 100644 RCCVirtualSteeringWheel/ModSettings.cs rename {GrabbableSteeringWheel => RCCVirtualSteeringWheel}/Patches.cs (62%) rename {GrabbableSteeringWheel => RCCVirtualSteeringWheel}/Properties/AssemblyInfo.cs (71%) rename GrabbableSteeringWheel/GrabbableSteeringWheel.csproj => RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj (86%) create mode 100644 RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs create mode 100644 RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs rename {GrabbableSteeringWheel/GrabbableSteeringWheel => RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util}/BoneVertexBoundsUtility.cs (99%) rename {GrabbableSteeringWheel => RCCVirtualSteeringWheel}/README.md (95%) rename {GrabbableSteeringWheel => RCCVirtualSteeringWheel}/format.json (100%) diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs deleted file mode 100644 index 0807a2a..0000000 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs +++ /dev/null @@ -1,203 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.InteractionSystem.Base; -using UnityEngine; - -// TODO: -// Fix multi-grab (limitation of Pickupable) -// Think this can be fixed by forcing ungrab & monitoring ourselves for release -// Add configurable override for steering range -// Fix steering wheel resetting immediatly on release -// Fix input patch not being multiplicative (so joysticks can still work) -// Prevent pickup in Desktop - -public class SteeringWheelPickup : Pickupable -{ - private RCC_CarControllerV3 _carController; - - private static readonly Dictionary ActiveWheels = new(); - - public static float GetSteerInput(RCC_CarControllerV3 carController) - { - if (ActiveWheels.TryGetValue(carController, out SteeringWheelPickup wheel) && wheel.IsPickedUp) - return wheel.GetNormalizedValue(); - return 0f; - } - - public void SetupSteeringWheel(RCC_CarControllerV3 carController) - { - _carController = carController; - if (!ActiveWheels.ContainsKey(carController)) - { - ActiveWheels[carController] = this; - carController.useCounterSteering = false; - carController.useSteeringSmoother = false; - } - } - - private void OnDestroy() - { - if (_carController != null && ActiveWheels.ContainsKey(_carController)) - ActiveWheels.Remove(_carController); - } - - #region Configuration Properties Override - - public override bool DisallowTheft => true; - public override float MaxGrabDistance => 0.8f; - public override float MaxPushDistance => 0f; - public override bool IsAutoHold => false; - public override bool IsObjectRotationAllowed => false; - public override bool IsObjectPushPullAllowed => false; - public override bool IsObjectUseAllowed => false; - - public override bool CanPickup => IsPickupable && _carController?.SteeringWheel != null; - - #endregion Configuration Properties Override - - #region RCC Stuff - - private float GetMaxSteeringRange() - => _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier); - - private float GetSteeringWheelSign() - => Mathf.Sign(_carController.steeringWheelAngleMultiplier) * -1f; // Idk - - private Vector3 GetSteeringWheelLocalAxis() - { - return _carController.steeringWheelRotateAround switch - { - RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right, - RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up, - RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward, - _ => Vector3.forward - }; - } - - #endregion RCC Stuff - - #region Rotation Tracking - - private readonly List _trackedTransforms = new(); - private readonly List _lastPositions = new(); - private readonly List _totalAngles = new(); - private bool _isTracking; - private float _averageAngle; - - private void StartTrackingTransform(Transform trans) - { - if (trans == null) return; - - _trackedTransforms.Add(trans); - _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); - _totalAngles.Add(0f); - _isTracking = true; - } - - private void StopTrackingTransform(Transform trans) - { - int index = _trackedTransforms.IndexOf(trans); - if (index != -1) - { - _trackedTransforms.RemoveAt(index); - _lastPositions.RemoveAt(index); - _totalAngles.RemoveAt(index); - } - - _isTracking = _trackedTransforms.Count > 0; - } - - private void UpdateRotationTracking() - { - if (!_isTracking || _trackedTransforms.Count == 0) return; - - Vector3 trackingAxis = GetSteeringWheelLocalAxis(); - - for (int i = 0; i < _trackedTransforms.Count; i++) - { - if (_trackedTransforms[i] == null) continue; - - Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position); - if (currentPosition == _lastPositions[i]) continue; - - Vector3 previousVector = _lastPositions[i]; - Vector3 currentVector = currentPosition; - - previousVector = Vector3.ProjectOnPlane(previousVector, trackingAxis).normalized; - currentVector = Vector3.ProjectOnPlane(currentVector, trackingAxis).normalized; - - if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f) - { - float deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis); - if (Mathf.Abs(deltaAngle) < 90f) _totalAngles[i] += deltaAngle; // Prevent big tracking jumps - } - - _lastPositions[i] = currentPosition; - } - - // Calculate average every frame using only valid transforms - float sumAngles = 0f; - int validTransforms = 0; - - for (int i = 0; i < _trackedTransforms.Count; i++) - { - if (_trackedTransforms[i] == null) continue; - sumAngles += _totalAngles[i]; - validTransforms++; - } - - if (validTransforms > 0) - _averageAngle = sumAngles / validTransforms; - } - - private float GetNormalizedValue() - { - float maxRange = GetMaxSteeringRange(); - // return Mathf.Clamp(_averageAngle / (maxRange * 0.5f), -1f, 1f); - return Mathf.Clamp(_averageAngle / (maxRange), -1f, 1f) * GetSteeringWheelSign(); - } - - private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition) - { - Transform steeringTransform = _carController.SteeringWheel; - - Quaternion localRotation = steeringTransform.localRotation; - steeringTransform.localRotation = _carController.orgSteeringWheelRot; - - Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition); - steeringTransform.localRotation = localRotation; - - return localPosition; - } - - #endregion Rotation Tracking - - public override void OnGrab(InteractionContext context, Vector3 grabPoint) - { - if (ControllerRay?.pivotPoint == null) - return; - - StartTrackingTransform(ControllerRay.transform); - } - - public override void OnDrop(InteractionContext context) - { - if (ControllerRay?.transform != null) - StopTrackingTransform(ControllerRay.transform); - } - - private void Update() - { - if (!IsPickedUp || ControllerRay?.pivotPoint == null || _carController == null) - return; - - UpdateRotationTracking(); - } - - #region Unused Abstract Method Implementations - - public override void OnUseDown(InteractionContext context) { } - public override void OnUseUp(InteractionContext context) { } - public override void OnFlingTowardsTarget(Vector3 target) { } - - #endregion Unused Abstract Method Implementations -} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Main.cs b/GrabbableSteeringWheel/Main.cs deleted file mode 100644 index 1ced7e0..0000000 --- a/GrabbableSteeringWheel/Main.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MelonLoader; -using NAK.GrabbableSteeringWheel.Patches; - -namespace NAK.GrabbableSteeringWheel; - -public class GrabbableSteeringWheelMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - #region Melon Preferences - - // private static readonly MelonPreferences_Category Category = - // MelonPreferences.CreateCategory(nameof(GrabbableSteeringWheelMod)); - // - // private static readonly MelonPreferences_Entry EntryEnabled = - // Category.CreateEntry( - // "use_legacy_mitigation", - // true, - // "Enabled", - // description: "Enable legacy content camera hack when in Legacy worlds."); - - #endregion Melon Preferences - - #region Melon Events - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ApplyPatches(typeof(RCCCarControllerV3_Patches)); - ApplyPatches(typeof(CVRInputManager_Patches)); - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private static void InitializeIntegration(string modName, Action integrationAction) - { - if (RegisteredMelons.All(it => it.Info.Name != modName)) - return; - - Logger.Msg($"Initializing {modName} integration."); - integrationAction.Invoke(); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/GrabbableSteeringWheel/ModSettings.cs b/GrabbableSteeringWheel/ModSettings.cs deleted file mode 100644 index 26a56be..0000000 --- a/GrabbableSteeringWheel/ModSettings.cs +++ /dev/null @@ -1,24 +0,0 @@ -using MelonLoader; - -namespace NAK.GrabbableSteeringWheel; - -internal static class ModSettings -{ - #region Constants - - internal const string ModName = nameof(GrabbableSteeringWheel); - // internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; - - #endregion Constants - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - // internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = - // Category.CreateEntry("auto_for_legacy_worlds", true, - // "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); - // - #endregion Melon Preferences -} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/Main.cs b/RCCVirtualSteeringWheel/Main.cs new file mode 100644 index 0000000..79fae31 --- /dev/null +++ b/RCCVirtualSteeringWheel/Main.cs @@ -0,0 +1,40 @@ +using MelonLoader; +using NAK.RCCVirtualSteeringWheel.Patches; + +namespace NAK.RCCVirtualSteeringWheel; + +public class RCCVirtualSteeringWheelMod : MelonMod +{ + private static MelonLogger.Instance Logger; + + #region Melon Events + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ApplyPatches(typeof(RCCCarControllerV3_Patches)); + ApplyPatches(typeof(CVRInputManager_Patches)); + + Logger.Msg(ModSettings.EntryCustomSteeringRange); + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/ModSettings.cs b/RCCVirtualSteeringWheel/ModSettings.cs new file mode 100644 index 0000000..f07436c --- /dev/null +++ b/RCCVirtualSteeringWheel/ModSettings.cs @@ -0,0 +1,31 @@ +using MelonLoader; + +namespace NAK.RCCVirtualSteeringWheel; + +internal static class ModSettings +{ + #region Constants + + private const string ModName = nameof(RCCVirtualSteeringWheel); + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryOverrideSteeringRange = + Category.CreateEntry("override_steering_range", false, + "Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?"); + + internal static readonly MelonPreferences_Entry EntryCustomSteeringRange = + Category.CreateEntry("custom_steering_range", 60f, + "Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)"); + + internal static readonly MelonPreferences_Entry EntryInvertSteering = + Category.CreateEntry("invert_steering", false, + "Invert Steering", description: "Inverts the steering direction"); + + #endregion Melon Preferences +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Patches.cs b/RCCVirtualSteeringWheel/Patches.cs similarity index 62% rename from GrabbableSteeringWheel/Patches.cs rename to RCCVirtualSteeringWheel/Patches.cs index c6a828c..c4824ce 100644 --- a/GrabbableSteeringWheel/Patches.cs +++ b/RCCVirtualSteeringWheel/Patches.cs @@ -1,21 +1,21 @@ using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.Movement; using HarmonyLib; +using NAK.RCCVirtualSteeringWheel.Util; using UnityEngine; -namespace NAK.GrabbableSteeringWheel.Patches; +namespace NAK.RCCVirtualSteeringWheel.Patches; internal static class RCCCarControllerV3_Patches { [HarmonyPostfix] [HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))] - private static void Postfix_RCC_CarControllerV3_Awake(ref RCC_CarControllerV3 __instance) + private static void Postfix_RCC_CarControllerV3_Awake(RCC_CarControllerV3 __instance) { Transform steeringWheelTransform = __instance.SteeringWheel; if (steeringWheelTransform == null) return; - RCC_CarControllerV3 v3 = __instance; BoneVertexBoundsUtility.CalculateBoneWeightedBounds( steeringWheelTransform, 0.8f, @@ -25,12 +25,7 @@ internal static class RCCCarControllerV3_Patches if (!result.IsValid) return; - BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent(); - boxCollider.center = result.LocalBounds.center; - boxCollider.size = result.LocalBounds.size; - - SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent(); - steeringWheel.SetupSteeringWheel(v3); + SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds); }); } } @@ -42,8 +37,11 @@ internal static class CVRInputManager_Patches private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance) { // Steering input is clamped in RCC component - if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()) - __instance.steering += SteeringWheelPickup.GetSteerInput( - BetterBetterCharacterController.Instance._lastCvrSeat._carController); + if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat() + && SteeringWheelRoot.TryGetWheelInput( + BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue)) + { + __instance.steering = steeringValue; + } } } \ No newline at end of file diff --git a/GrabbableSteeringWheel/Properties/AssemblyInfo.cs b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs similarity index 71% rename from GrabbableSteeringWheel/Properties/AssemblyInfo.cs rename to RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs index 6441f79..c82b5de 100644 --- a/GrabbableSteeringWheel/Properties/AssemblyInfo.cs +++ b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs @@ -1,21 +1,21 @@ using System.Reflection; using MelonLoader; -using NAK.GrabbableSteeringWheel; -using NAK.GrabbableSteeringWheel.Properties; +using NAK.RCCVirtualSteeringWheel; +using NAK.RCCVirtualSteeringWheel.Properties; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))] +[assembly: AssemblyTitle(nameof(NAK.RCCVirtualSteeringWheel))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))] +[assembly: AssemblyProduct(nameof(NAK.RCCVirtualSteeringWheel))] [assembly: MelonInfo( - typeof(GrabbableSteeringWheelMod), - nameof(NAK.GrabbableSteeringWheel), + typeof(RCCVirtualSteeringWheelMod), + nameof(NAK.RCCVirtualSteeringWheel), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/GrabbableSteeringWheel" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -25,7 +25,7 @@ using NAK.GrabbableSteeringWheel.Properties; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.GrabbableSteeringWheel.Properties; +namespace NAK.RCCVirtualSteeringWheel.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj similarity index 86% rename from GrabbableSteeringWheel/GrabbableSteeringWheel.csproj rename to RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj index 9855f81..8920c39 100644 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj @@ -2,6 +2,7 @@ net48 + GrabbableSteeringWheel @@ -11,7 +12,4 @@ ..\.ManagedLibs\TheClapper.dll - - - diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs new file mode 100644 index 0000000..d809150 --- /dev/null +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs @@ -0,0 +1,36 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +namespace NAK.RCCVirtualSteeringWheel; + +public class SteeringWheelPickup : Pickupable +{ + #region Public Properties + public override bool DisallowTheft => true; + public override float MaxGrabDistance => 0.8f; + public override float MaxPushDistance => 0f; + public override bool IsAutoHold => false; + public override bool IsObjectRotationAllowed => false; + public override bool IsObjectPushPullAllowed => false; + public override bool IsObjectUseAllowed => false; + public override bool CanPickup => IsPickupable && !IsPickedUp; + internal SteeringWheelRoot root; + #endregion + + #region Public Methods + public override void OnUseDown(InteractionContext context) { } + public override void OnUseUp(InteractionContext context) { } + public override void OnFlingTowardsTarget(Vector3 target) { } + + public override void OnGrab(InteractionContext context, Vector3 grabPoint) { + if (ControllerRay?.pivotPoint != null) + root.StartTrackingTransform(ControllerRay.transform); + } + + public override void OnDrop(InteractionContext context) { + if (ControllerRay?.transform != null) + root.StopTrackingTransform(ControllerRay.transform); + } + #endregion +} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs new file mode 100644 index 0000000..cc293aa --- /dev/null +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs @@ -0,0 +1,313 @@ +using UnityEngine; + +namespace NAK.RCCVirtualSteeringWheel; + +public class SteeringWheelRoot : MonoBehaviour +{ + #region Static Variables + + private static readonly Dictionary ActiveWheels = new(); + + #endregion Static Variables + + #region Static Methods + + public static bool TryGetWheelInput(RCC_CarControllerV3 carController, out float steeringInput) + { + if (ActiveWheels.TryGetValue(carController, out SteeringWheelRoot wheel) && wheel._averageAngle != 0f) + { + steeringInput = wheel.GetNormalizedValue(); + return true; + } + + steeringInput = 0f; + return false; + } + + public static void SetupSteeringWheel(RCC_CarControllerV3 carController, Bounds steeringWheelBounds) + { + Transform steeringWheel = carController.SteeringWheel; + if (carController == null) return; + + SteeringWheelRoot wheel = steeringWheel.gameObject.AddComponent(); + wheel._carController = carController; + + Array.Resize(ref wheel._pickups, 2); + CreatePickup(out wheel._pickups[0]); + CreatePickup(out wheel._pickups[1]); + + return; + + void CreatePickup(out SteeringWheelPickup wheelPickup) + { + GameObject pickup = new() + { + transform = + { + parent = steeringWheel.transform, + localPosition = Vector3.zero, + localRotation = Quaternion.identity, + localScale = Vector3.one + } + }; + + BoxCollider collider = pickup.AddComponent(); + collider.size = steeringWheelBounds.size; + collider.center = steeringWheelBounds.center; + + wheelPickup = pickup.AddComponent(); + wheelPickup.root = wheel; + } + } + + #endregion Static Methods + + #region Public Properties + + private bool IsWheelBeingHeld => _pickups[0].IsPickedUp || _pickups[1].IsPickedUp; + + #endregion Public Properties + + #region Private Variables + + private RCC_CarControllerV3 _carController; + private SteeringWheelPickup[] _pickups; + + private float _originalSteeringWheelAngleMultiplier; + private float _originalSteeringWheelSign; + + private readonly List _trackedTransforms = new(); + private readonly List _lastPositions = new(); + private readonly List _totalAngles = new(); + + private bool _isTracking; + private float _averageAngle; + private float _timeWheelReleased = -1f; + private const float RETURN_TO_CENTER_DURATION = 2f; + + #endregion Private Variables + + #region Unity Events + + private void Start() + { + ActiveWheels.TryAdd(_carController, this); + InitializeWheel(); + } + + private void Update() + { + if (_carController == null) return; + UpdateWheelState(); + UpdateSteeringBehavior(); + } + + private void OnDestroy() + { + ActiveWheels.Remove(_carController); + } + + #endregion Unity Events + + #region Public Methods + + internal void StartTrackingTransform(Transform trans) + { + if (trans == null) return; + + var currentAngle = 0f; + if (_isTracking) + { + var sum = 0f; + var validTransforms = 0; + for (var i = 0; i < _trackedTransforms.Count; i++) + if (_trackedTransforms[i] != null) + { + sum += _totalAngles[i]; + validTransforms++; + } + + if (validTransforms > 0) + currentAngle = sum / validTransforms; + } + + _trackedTransforms.Add(trans); + _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); + _totalAngles.Add(currentAngle); + _isTracking = true; + } + + internal void StopTrackingTransform(Transform trans) + { + var index = _trackedTransforms.IndexOf(trans); + if (index == -1) return; + + var currentAverage = CalculateCurrentAverage(); + _trackedTransforms.RemoveAt(index); + _lastPositions.RemoveAt(index); + _totalAngles.RemoveAt(index); + + if (_trackedTransforms.Count <= 0) + return; + + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = currentAverage; + } + + private float CalculateCurrentAverage() + { + var sum = 0f; + var validTransforms = 0; + for (var i = 0; i < _trackedTransforms.Count; i++) + if (_trackedTransforms[i] != null) + { + sum += _totalAngles[i]; + validTransforms++; + } + + return validTransforms > 0 ? sum / validTransforms : 0f; + } + + #endregion Public Methods + + #region Private Methods + + private void InitializeWheel() + { + _originalSteeringWheelAngleMultiplier = _carController.steeringWheelAngleMultiplier; + _originalSteeringWheelSign = Mathf.Sign(_originalSteeringWheelAngleMultiplier); + _carController.useCounterSteering = false; + _carController.useSteeringSmoother = false; + } + + private void UpdateWheelState() + { + var isHeld = IsWheelBeingHeld; + if (!isHeld && _timeWheelReleased < 0f) + _timeWheelReleased = Time.time; + else if (isHeld) + _timeWheelReleased = -1f; + } + + private void UpdateSteeringBehavior() + { + UpdateSteeringMultiplier(); + + if (IsWheelBeingHeld) + UpdateRotationTracking(); + else if (_timeWheelReleased >= 0f) + HandleWheelReturn(); + } + + private void UpdateSteeringMultiplier() + { + _carController.steeringWheelAngleMultiplier = ModSettings.EntryOverrideSteeringRange.Value + ? ModSettings.EntryCustomSteeringRange.Value * _originalSteeringWheelSign / _carController.steerAngle + : _originalSteeringWheelAngleMultiplier; + } + + private void HandleWheelReturn() + { + var timeSinceRelease = Time.time - _timeWheelReleased; + if (timeSinceRelease < RETURN_TO_CENTER_DURATION) + { + var t = timeSinceRelease / RETURN_TO_CENTER_DURATION; + _averageAngle = Mathf.Lerp(_averageAngle, 0f, t); + + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = _averageAngle; + } + else + { + _averageAngle = 0f; + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = 0f; + } + } + + private float GetMaxSteeringRange() + { + return _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier); + } + + private float GetSteeringWheelSign() + { + return _originalSteeringWheelSign * (ModSettings.EntryInvertSteering.Value ? 1f : -1f); + } + + private Vector3 GetSteeringWheelLocalAxis() + { + return _carController.steeringWheelRotateAround switch + { + RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right, + RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up, + RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward, + _ => Vector3.forward + }; + } + + private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition) + { + Transform steeringTransform = _carController.SteeringWheel; + Quaternion localRotation = steeringTransform.localRotation; + steeringTransform.localRotation = _carController.orgSteeringWheelRot; + Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition); + steeringTransform.localRotation = localRotation; + return localPosition; + } + + private float GetNormalizedValue() + { + return Mathf.Clamp(_averageAngle / GetMaxSteeringRange(), -1f, 1f) * GetSteeringWheelSign(); + } + + private void UpdateRotationTracking() + { + if (!_isTracking || _trackedTransforms.Count == 0) return; + + Vector3 trackingAxis = GetSteeringWheelLocalAxis(); + UpdateTransformAngles(trackingAxis); + UpdateAverageAngle(); + } + + private void UpdateTransformAngles(Vector3 trackingAxis) + { + for (var i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + + Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position); + if (currentPosition == _lastPositions[i]) continue; + + Vector3 previousVector = Vector3.ProjectOnPlane(_lastPositions[i], trackingAxis).normalized; + Vector3 currentVector = Vector3.ProjectOnPlane(currentPosition, trackingAxis).normalized; + + if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f) + { + var deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis); + if (Mathf.Abs(deltaAngle) < 90f) + _totalAngles[i] += deltaAngle; + } + + _lastPositions[i] = currentPosition; + } + } + + private void UpdateAverageAngle() + { + var sumAngles = 0f; + var validTransforms = 0; + + for (var i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + sumAngles += _totalAngles[i]; + validTransforms++; + } + + if (validTransforms > 0) + _averageAngle = sumAngles / validTransforms; + } + + #endregion Private Methods +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs similarity index 99% rename from GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs rename to RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs index 2864f49..093620c 100644 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs @@ -6,6 +6,8 @@ using Unity.Profiling; using UnityEngine; using UnityEngine.Animations; +namespace NAK.RCCVirtualSteeringWheel.Util; + public class BoneVertexBoundsUtility : MonoBehaviour { private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate"); @@ -201,7 +203,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour { yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints => { - if (skinnedPoints == null || skinnedPoints.Count <= 0) return; + if (skinnedPoints is not { Count: > 0 }) return; allWeightedPoints.AddRange(skinnedPoints); hasValidPoints = true; })); diff --git a/GrabbableSteeringWheel/README.md b/RCCVirtualSteeringWheel/README.md similarity index 95% rename from GrabbableSteeringWheel/README.md rename to RCCVirtualSteeringWheel/README.md index 08942df..b396266 100644 --- a/GrabbableSteeringWheel/README.md +++ b/RCCVirtualSteeringWheel/README.md @@ -1,7 +1,7 @@ -# GrabbableSteeringWheel +# RCCVirtualSteeringWheel Experiment with making rigged RCC vehicle steering wheels work for VR steering input. -~~~~ + --- Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. @@ -12,3 +12,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. > To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. +~~~~ \ No newline at end of file diff --git a/GrabbableSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json similarity index 100% rename from GrabbableSteeringWheel/format.json rename to RCCVirtualSteeringWheel/format.json From 5d77eb61a5a344756d49ac7927834fcd9cbf5d47 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 7 Jan 2025 02:37:14 -0600 Subject: [PATCH 070/188] LegacyContentMitigation: fixed culling issue --- .../Components/FakeMultiPassHack.cs | 2 ++ LegacyContentMitigation/Main.cs | 1 + LegacyContentMitigation/Patches.cs | 13 +++++++++++++ LegacyContentMitigation/Properties/AssemblyInfo.cs | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/LegacyContentMitigation/Components/FakeMultiPassHack.cs b/LegacyContentMitigation/Components/FakeMultiPassHack.cs index 31924db..fcc7169 100644 --- a/LegacyContentMitigation/Components/FakeMultiPassHack.cs +++ b/LegacyContentMitigation/Components/FakeMultiPassHack.cs @@ -207,9 +207,11 @@ public class FakeMultiPassHack : MonoBehaviour eyeCamera.targetTexture = targetTexture; eyeCamera.cullingMask = CachedCullingMask; eyeCamera.stereoTargetEye = StereoTargetEyeMask.None; + eyeCamera.cullingMatrix = _mainCamera.cullingMatrix; eyeCamera.projectionMatrix = _mainCamera.GetStereoProjectionMatrix(eye); eyeCamera.worldToCameraMatrix = _mainCamera.GetStereoViewMatrix(eye); eyeCamera.Render(); + eyeCamera.ResetCullingMatrix(); } } diff --git a/LegacyContentMitigation/Main.cs b/LegacyContentMitigation/Main.cs index 8db0910..5d1c1b9 100644 --- a/LegacyContentMitigation/Main.cs +++ b/LegacyContentMitigation/Main.cs @@ -33,6 +33,7 @@ public class LegacyContentMitigationMod : MelonMod ApplyPatches(typeof(Patches.CVRWorld_Patches)); // post processing shit ApplyPatches(typeof(Patches.CVRTools_Patches)); // prevent shader replacement when fix is active ApplyPatches(typeof(Patches.HeadHiderManager_Patches)); // prevent main cam triggering early head hide + ApplyPatches(typeof(Patches.CVRMirror_Patches)); InitializeIntegration("BTKUILib", Integrations.BtkUiAddon.Initialize); // quick menu options } diff --git a/LegacyContentMitigation/Patches.cs b/LegacyContentMitigation/Patches.cs index e2dabaf..2b151e8 100644 --- a/LegacyContentMitigation/Patches.cs +++ b/LegacyContentMitigation/Patches.cs @@ -6,6 +6,8 @@ using ABI_RC.Core.Player; using ABI_RC.Core.Player.LocalClone; using ABI_RC.Core.Player.TransformHider; using ABI.CCK.Components; +using cohtml; +using cohtml.Net; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering.PostProcessing; @@ -125,4 +127,15 @@ internal static class HeadHiderManager_Patches transformHiderManager._resetAfterThisRender = flag; } } +} + +internal static class CVRMirror_Patches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRMirror), nameof(CVRMirror.CopyCameraProperties))] + private static void Postfix_CVRMirror_CopyCameraProperties(ref CVRMirror __instance) + { + __instance.m_ReflectionCamera.ResetCullingMatrix(); + } + } \ No newline at end of file diff --git a/LegacyContentMitigation/Properties/AssemblyInfo.cs b/LegacyContentMitigation/Properties/AssemblyInfo.cs index e460687..5969d71 100644 --- a/LegacyContentMitigation/Properties/AssemblyInfo.cs +++ b/LegacyContentMitigation/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.LegacyContentMitigation.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.2"; public const string Author = "Exterrata & NotAKidoS"; } \ No newline at end of file From 1282b2ca48de225844149189d92c453177f459ab Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:40:32 -0600 Subject: [PATCH 071/188] RCCVirtualSteeringWheel: tuning & prepare for release --- .../Components/SteeringWheelPickup.cs | 20 +++-- .../Components/SteeringWheelRoot.cs | 80 +++++++------------ .../Util/BoneVertexBoundsUtility.cs | 2 +- RCCVirtualSteeringWheel/README.md | 5 +- RCCVirtualSteeringWheel/format.json | 20 ++--- 5 files changed, 57 insertions(+), 70 deletions(-) diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs index d809150..7981d63 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs @@ -1,12 +1,16 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Core.Savior; using UnityEngine; namespace NAK.RCCVirtualSteeringWheel; public class SteeringWheelPickup : Pickupable { - #region Public Properties + internal SteeringWheelRoot root; + + #region Pickupable Properties + public override bool DisallowTheft => true; public override float MaxGrabDistance => 0.8f; public override float MaxPushDistance => 0f; @@ -14,11 +18,12 @@ public class SteeringWheelPickup : Pickupable public override bool IsObjectRotationAllowed => false; public override bool IsObjectPushPullAllowed => false; public override bool IsObjectUseAllowed => false; - public override bool CanPickup => IsPickupable && !IsPickedUp; - internal SteeringWheelRoot root; - #endregion - - #region Public Methods + public override bool CanPickup => IsPickupable && !IsPickedUp && MetaPort.Instance.isUsingVr; + + #endregion Pickupable Properties + + #region Pickupable Methods + public override void OnUseDown(InteractionContext context) { } public override void OnUseUp(InteractionContext context) { } public override void OnFlingTowardsTarget(Vector3 target) { } @@ -32,5 +37,6 @@ public class SteeringWheelPickup : Pickupable if (ControllerRay?.transform != null) root.StopTrackingTransform(ControllerRay.transform); } - #endregion + + #endregion Pickupable Methods } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs index cc293aa..b8008b8 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs @@ -65,6 +65,7 @@ public class SteeringWheelRoot : MonoBehaviour #region Public Properties private bool IsWheelBeingHeld => _pickups[0].IsPickedUp || _pickups[1].IsPickedUp; + private bool IsWheelInactive => !IsWheelBeingHeld && _averageAngle == 0f; #endregion Public Properties @@ -75,6 +76,8 @@ public class SteeringWheelRoot : MonoBehaviour private float _originalSteeringWheelAngleMultiplier; private float _originalSteeringWheelSign; + private bool _originalCounterSteer; + private bool _originalSteerSmoothing; private readonly List _trackedTransforms = new(); private readonly List _lastPositions = new(); @@ -84,6 +87,7 @@ public class SteeringWheelRoot : MonoBehaviour private float _averageAngle; private float _timeWheelReleased = -1f; private const float RETURN_TO_CENTER_DURATION = 2f; + private const float RETURN_START_DELAY = 0.1f; #endregion Private Variables @@ -115,21 +119,7 @@ public class SteeringWheelRoot : MonoBehaviour { if (trans == null) return; - var currentAngle = 0f; - if (_isTracking) - { - var sum = 0f; - var validTransforms = 0; - for (var i = 0; i < _trackedTransforms.Count; i++) - if (_trackedTransforms[i] != null) - { - sum += _totalAngles[i]; - validTransforms++; - } - - if (validTransforms > 0) - currentAngle = sum / validTransforms; - } + var currentAngle = _isTracking ? CalculateAverageAngle(_trackedTransforms, _totalAngles) : 0f; _trackedTransforms.Add(trans); _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); @@ -142,7 +132,7 @@ public class SteeringWheelRoot : MonoBehaviour var index = _trackedTransforms.IndexOf(trans); if (index == -1) return; - var currentAverage = CalculateCurrentAverage(); + var currentAverage = CalculateAverageAngle(_trackedTransforms, _totalAngles); _trackedTransforms.RemoveAt(index); _lastPositions.RemoveAt(index); _totalAngles.RemoveAt(index); @@ -153,21 +143,7 @@ public class SteeringWheelRoot : MonoBehaviour for (var i = 0; i < _totalAngles.Count; i++) _totalAngles[i] = currentAverage; } - - private float CalculateCurrentAverage() - { - var sum = 0f; - var validTransforms = 0; - for (var i = 0; i < _trackedTransforms.Count; i++) - if (_trackedTransforms[i] != null) - { - sum += _totalAngles[i]; - validTransforms++; - } - - return validTransforms > 0 ? sum / validTransforms : 0f; - } - + #endregion Public Methods #region Private Methods @@ -176,8 +152,8 @@ public class SteeringWheelRoot : MonoBehaviour { _originalSteeringWheelAngleMultiplier = _carController.steeringWheelAngleMultiplier; _originalSteeringWheelSign = Mathf.Sign(_originalSteeringWheelAngleMultiplier); - _carController.useCounterSteering = false; - _carController.useSteeringSmoother = false; + _originalCounterSteer = _carController.useCounterSteering; + _originalSteerSmoothing = _carController.useSteeringSmoother; } private void UpdateWheelState() @@ -191,27 +167,34 @@ public class SteeringWheelRoot : MonoBehaviour private void UpdateSteeringBehavior() { - UpdateSteeringMultiplier(); - + SetSteeringAssistsState(!IsWheelInactive); + if (IsWheelBeingHeld) UpdateRotationTracking(); else if (_timeWheelReleased >= 0f) HandleWheelReturn(); } - private void UpdateSteeringMultiplier() + private void SetSteeringAssistsState(bool shouldOverride) { - _carController.steeringWheelAngleMultiplier = ModSettings.EntryOverrideSteeringRange.Value + _carController.useCounterSteering = !shouldOverride && _originalCounterSteer; + _carController.useSteeringSmoother = !shouldOverride && _originalSteerSmoothing; + _carController.steeringWheelAngleMultiplier = ModSettings.EntryOverrideSteeringRange.Value && shouldOverride ? ModSettings.EntryCustomSteeringRange.Value * _originalSteeringWheelSign / _carController.steerAngle : _originalSteeringWheelAngleMultiplier; } - + private void HandleWheelReturn() { var timeSinceRelease = Time.time - _timeWheelReleased; - if (timeSinceRelease < RETURN_TO_CENTER_DURATION) + + if (timeSinceRelease < RETURN_START_DELAY) + return; + + var returnTime = timeSinceRelease - RETURN_START_DELAY; + if (returnTime < RETURN_TO_CENTER_DURATION) { - var t = timeSinceRelease / RETURN_TO_CENTER_DURATION; + var t = returnTime / RETURN_TO_CENTER_DURATION; _averageAngle = Mathf.Lerp(_averageAngle, 0f, t); for (var i = 0; i < _totalAngles.Count; i++) @@ -267,7 +250,7 @@ public class SteeringWheelRoot : MonoBehaviour Vector3 trackingAxis = GetSteeringWheelLocalAxis(); UpdateTransformAngles(trackingAxis); - UpdateAverageAngle(); + _averageAngle = CalculateAverageAngle(_trackedTransforms, _totalAngles); } private void UpdateTransformAngles(Vector3 trackingAxis) @@ -293,20 +276,19 @@ public class SteeringWheelRoot : MonoBehaviour } } - private void UpdateAverageAngle() + private static float CalculateAverageAngle(List transforms, List angles) { - var sumAngles = 0f; + var sum = 0f; var validTransforms = 0; - - for (var i = 0; i < _trackedTransforms.Count; i++) + + for (var i = 0; i < transforms.Count; i++) { - if (_trackedTransforms[i] == null) continue; - sumAngles += _totalAngles[i]; + if (transforms[i] == null) continue; + sum += angles[i]; validTransforms++; } - if (validTransforms > 0) - _averageAngle = sumAngles / validTransforms; + return validTransforms > 0 ? sum / validTransforms : 0f; } #endregion Private Methods diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs index 093620c..f55f241 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs @@ -52,7 +52,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour /// Calculates the bounds of a transform based on: /// - Children Renderers /// - Skinned Mesh Weights - /// - Constrained Child Renderers & Skinned Mesh Weights + /// - Constrained Child Renderers & Skinned Mesh Weights (thanks Fearless) /// public static void CalculateBoneWeightedBounds(Transform bone, float weightThreshold, BoundsCalculationFlags flags, Action onComplete) => Instance.StartCoroutine(Instance.CalculateBoundsCoroutine(bone, weightThreshold, flags, onComplete)); diff --git a/RCCVirtualSteeringWheel/README.md b/RCCVirtualSteeringWheel/README.md index b396266..8d9f529 100644 --- a/RCCVirtualSteeringWheel/README.md +++ b/RCCVirtualSteeringWheel/README.md @@ -1,6 +1,6 @@ # RCCVirtualSteeringWheel -Experiment with making rigged RCC vehicle steering wheels work for VR steering input. +Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. --- @@ -11,5 +11,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. -~~~~ \ No newline at end of file +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.~~~~ \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json index 480d0fb..3f0654c 100644 --- a/RCCVirtualSteeringWheel/format.json +++ b/RCCVirtualSteeringWheel/format.json @@ -1,23 +1,23 @@ { "_id": -1, - "name": "LegacyContentMitigation", - "modversion": "1.0.1", + "name": "RCCVirtualSteeringWheel", + "modversion": "1.0.0", "gameversion": "2024r177", "loaderversion": "0.6.1", "modtype": "Mod", - "author": "Exterrata, NotAKidoS", - "description": "Experimental mod that manually renders both VR eyes separately while in Legacy Worlds.\n\nThe mod will automatically kick-in when loading NonSpi Worlds & prevent Shader Replacement for all content while active. You can also manually toggle it within the BTKUI Misc tab for experimenting.\n\n-# There is an obvious performance penalty with rendering the world 2x & toggling the head hiding per eye, so enabling the mod outside of Legacy Worlds is not recommended.", + "author": "NotAKidoS", + "description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n", "searchtags": [ - "legacy", - "content", - "spi", - "shaders" + "rcc", + "steering", + "vehicle", + "car" ], "requirements": [ "BTKUILib" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/LegacyContentMitigation.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LegacyContentMitigation/", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/RCCVirtualSteeringWheel.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", "changelog": "- Initial release", "embedcolor": "#f61963" } \ No newline at end of file From 621321c498938fa4d3778cc4a9629135bcd06f76 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:41:37 -0600 Subject: [PATCH 072/188] ASTExtension: Fixes for 2025r178 --- ASTExtension/Properties/AssemblyInfo.cs | 2 +- ASTExtension/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ASTExtension/Properties/AssemblyInfo.cs b/ASTExtension/Properties/AssemblyInfo.cs index faa1dc6..5c617e2 100644 --- a/ASTExtension/Properties/AssemblyInfo.cs +++ b/ASTExtension/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ASTExtension.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.2"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/ASTExtension/format.json b/ASTExtension/format.json index 55315d6..5ad14ea 100644 --- a/ASTExtension/format.json +++ b/ASTExtension/format.json @@ -1,8 +1,8 @@ { "_id": 223, "name": "ASTExtension", - "modversion": "1.0.1", - "gameversion": "2024r175", + "modversion": "1.0.2", + "gameversion": "2025r178", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "BTKUILib" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r35/ASTExtension.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ASTExtension.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/", - "changelog": "- Fixed an issue where the parameter calibration process would sometimes result in the mouth pointer being misplaced when generated.\n- Fixed IsGripping check for Knuckles controllers not requiring *both* hands as intended.", + "changelog": "- Fixes for 2025r178", "embedcolor": "#f61963" } \ No newline at end of file From 40bc88586e8b09fdc69c8023c5b851ec7e48931b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:42:05 -0600 Subject: [PATCH 073/188] RCCVirtualSteeringWheel: Fixes for 2025r178 --- RCCVirtualSteeringWheel/Patches.cs | 3 +++ RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs | 2 +- .../Components/SteeringWheelRoot.cs | 1 - RCCVirtualSteeringWheel/format.json | 12 ++++++------ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/RCCVirtualSteeringWheel/Patches.cs b/RCCVirtualSteeringWheel/Patches.cs index c4824ce..b9745d9 100644 --- a/RCCVirtualSteeringWheel/Patches.cs +++ b/RCCVirtualSteeringWheel/Patches.cs @@ -24,6 +24,9 @@ internal static class RCCCarControllerV3_Patches { if (!result.IsValid) return; + + if (!__instance) + return; SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds); }); diff --git a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs index c82b5de..616aa81 100644 --- a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs +++ b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs @@ -28,6 +28,6 @@ using NAK.RCCVirtualSteeringWheel.Properties; namespace NAK.RCCVirtualSteeringWheel.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs index b8008b8..0154bd5 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs @@ -27,7 +27,6 @@ public class SteeringWheelRoot : MonoBehaviour public static void SetupSteeringWheel(RCC_CarControllerV3 carController, Bounds steeringWheelBounds) { Transform steeringWheel = carController.SteeringWheel; - if (carController == null) return; SteeringWheelRoot wheel = steeringWheel.gameObject.AddComponent(); wheel._carController = carController; diff --git a/RCCVirtualSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json index 3f0654c..438f31f 100644 --- a/RCCVirtualSteeringWheel/format.json +++ b/RCCVirtualSteeringWheel/format.json @@ -1,8 +1,8 @@ { - "_id": -1, + "_id": 248, "name": "RCCVirtualSteeringWheel", - "modversion": "1.0.0", - "gameversion": "2024r177", + "modversion": "1.0.3", + "gameversion": "2025r178", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -14,10 +14,10 @@ "car" ], "requirements": [ - "BTKUILib" + "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/RCCVirtualSteeringWheel.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/RCCVirtualSteeringWheel.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Initial release", + "changelog": "- Fixes for 2025r178", "embedcolor": "#f61963" } \ No newline at end of file From ac9af46bd69ce6517e249439eba078ebfd71dfff Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:42:27 -0600 Subject: [PATCH 074/188] MutualMute: Stole name idea, now it's MutualMute --- {TwoWayMute => MutualMute}/Main.cs | 6 +++--- .../MutualMute.csproj | 1 + .../Properties/AssemblyInfo.cs | 16 ++++++++-------- {TwoWayMute => MutualMute}/README.md | 4 +++- {TwoWayMute => MutualMute}/format.json | 16 ++++++++-------- 5 files changed, 23 insertions(+), 20 deletions(-) rename {TwoWayMute => MutualMute}/Main.cs (84%) rename TwoWayMute/TwoWayMute.csproj => MutualMute/MutualMute.csproj (86%) rename {TwoWayMute => MutualMute}/Properties/AssemblyInfo.cs (74%) rename {TwoWayMute => MutualMute}/README.md (84%) rename {TwoWayMute => MutualMute}/format.json (55%) diff --git a/TwoWayMute/Main.cs b/MutualMute/Main.cs similarity index 84% rename from TwoWayMute/Main.cs rename to MutualMute/Main.cs index 8d6de3e..d02924b 100644 --- a/TwoWayMute/Main.cs +++ b/MutualMute/Main.cs @@ -3,16 +3,16 @@ using ABI_RC.Systems.Communications.Audio.Components; using HarmonyLib; using MelonLoader; -namespace NAK.TwoWayMute; +namespace NAK.MutualMute; -public class TwoWayMuteMod : MelonMod +public class MutualMuteMod : MelonMod { public override void OnInitializeMelon() { HarmonyInstance.Patch( typeof(Comms_ParticipantPipeline).GetMethod(nameof(Comms_ParticipantPipeline.SetFlowControlState), BindingFlags.NonPublic | BindingFlags.Instance), - prefix: new HarmonyMethod(typeof(TwoWayMuteMod).GetMethod(nameof(OnSetFlowControlState), + prefix: new HarmonyMethod(typeof(MutualMuteMod).GetMethod(nameof(OnSetFlowControlState), BindingFlags.NonPublic | BindingFlags.Static)) ); } diff --git a/TwoWayMute/TwoWayMute.csproj b/MutualMute/MutualMute.csproj similarity index 86% rename from TwoWayMute/TwoWayMute.csproj rename to MutualMute/MutualMute.csproj index 4e44ed3..267718d 100644 --- a/TwoWayMute/TwoWayMute.csproj +++ b/MutualMute/MutualMute.csproj @@ -2,6 +2,7 @@ net48 + TwoWayMute diff --git a/TwoWayMute/Properties/AssemblyInfo.cs b/MutualMute/Properties/AssemblyInfo.cs similarity index 74% rename from TwoWayMute/Properties/AssemblyInfo.cs rename to MutualMute/Properties/AssemblyInfo.cs index 0c53769..6db6d18 100644 --- a/TwoWayMute/Properties/AssemblyInfo.cs +++ b/MutualMute/Properties/AssemblyInfo.cs @@ -1,20 +1,20 @@ -using NAK.TwoWayMute.Properties; +using NAK.MutualMute.Properties; using MelonLoader; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.TwoWayMute))] +[assembly: AssemblyTitle(nameof(NAK.MutualMute))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.TwoWayMute))] +[assembly: AssemblyProduct(nameof(NAK.MutualMute))] [assembly: MelonInfo( - typeof(NAK.TwoWayMute.TwoWayMuteMod), - nameof(NAK.TwoWayMute), + typeof(NAK.MutualMute.MutualMuteMod), + nameof(NAK.MutualMute), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/TwoWayMute" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/MutualMute" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -24,9 +24,9 @@ using System.Reflection; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.TwoWayMute.Properties; +namespace NAK.MutualMute.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/TwoWayMute/README.md b/MutualMute/README.md similarity index 84% rename from TwoWayMute/README.md rename to MutualMute/README.md index 560a022..599d292 100644 --- a/TwoWayMute/README.md +++ b/MutualMute/README.md @@ -1,9 +1,11 @@ -# TwoWayMute +# MutualMute Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you. Basically- if you mute someone, they will also not be able to hear you. +#### This will work even of the other user does not have the mod installed, as it modifies the BBC Flow Control system. + --- Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. diff --git a/TwoWayMute/format.json b/MutualMute/format.json similarity index 55% rename from TwoWayMute/format.json rename to MutualMute/format.json index a2ed4f6..e815ba1 100644 --- a/TwoWayMute/format.json +++ b/MutualMute/format.json @@ -1,12 +1,12 @@ { - "_id": -1, - "name": "TwoWayMute", - "modversion": "1.0.0", - "gameversion": "2024r177", + "_id": 246, + "name": "MutualMute", + "modversion": "1.0.1", + "gameversion": "2025r178", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you.\n\nBasically- if you mute someone, they will also not be able to hear you.", + "description": "Adjusts the self moderation muting behaviour to also prevent the muted user from hearing you.\n\nBasically- if you mute someone, they will also not be able to hear you.\n-# This will work even of the other user does not have the mod installed, as it modifies the BBC Flow Control system.", "searchtags": [ "mute", "communication", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/TwoWayMute.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/TwoWayMute/", - "changelog": "- Initial Release", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/MutualMute.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/MutualMute/", + "changelog": "- Stole name idea, now it's MutualMute", "embedcolor": "#f61963" } \ No newline at end of file From 6d30fe1f41340c942a903f0b0ce2f25adf865992 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:42:53 -0600 Subject: [PATCH 075/188] ShareBubbles: Fixes for 2025r178 --- ShareBubbles/Patches.cs | 2 +- ShareBubbles/Properties/AssemblyInfo.cs | 2 +- ShareBubbles/ShareBubbles/UI/BubbleInteract.cs | 8 ++++---- ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs | 4 ++-- ShareBubbles/format.json | 10 +++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ShareBubbles/Patches.cs b/ShareBubbles/Patches.cs index 4f705e3..162c75b 100644 --- a/ShareBubbles/Patches.cs +++ b/ShareBubbles/Patches.cs @@ -1462,7 +1462,7 @@ ContentShareMod.init(); public static void Postfix_ViewManager_Start(ViewManager __instance) { // Inject the details toolbar patches when the game menu view is loaded - __instance.gameMenuView.Listener.FinishLoad += _ => { + __instance.gameMenuView.Listener.FinishLoad += (_) => { __instance.gameMenuView.View._view.ExecuteScript(DETAILS_TOOLBAR_PATCHES); __instance.gameMenuView.View.BindCall("NAKCallShareContent", OnShareContent); __instance.gameMenuView.View.BindCall("NAKGetContentShares", OnGetContentShares); diff --git a/ShareBubbles/Properties/AssemblyInfo.cs b/ShareBubbles/Properties/AssemblyInfo.cs index 8a1996f..07120d8 100644 --- a/ShareBubbles/Properties/AssemblyInfo.cs +++ b/ShareBubbles/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties; namespace NAK.ShareBubbles.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler"; } \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs index d389a41..ad2b4f4 100644 --- a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs +++ b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs @@ -14,12 +14,12 @@ public class BubbleInteract : Interactable return Vector3.Distance(transform.position, sourcePos) < 1.5f; } - public override void OnInteractDown(ControllerRay controllerRay) + public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay) { // Not used } - public override void OnInteractUp(ControllerRay controllerRay) + public override void OnInteractUp(InteractionContext context, ControllerRay controllerRay) { if (PlayerSetup.Instance.GetCurrentPropSelectionMode() != PlayerSetup.PropSelectionMode.None) @@ -39,12 +39,12 @@ public class BubbleInteract : Interactable GetComponentInParent().ViewDetailsPage(); } - public override void OnHoverEnter() + public override void OnHoverEnter(InteractionContext context, ControllerRay controllerRay) { // Not used } - public override void OnHoverExit() + public override void OnHoverExit(InteractionContext context, ControllerRay controllerRay) { // Not used } diff --git a/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs index 9c2416c..b6e1eda 100644 --- a/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs +++ b/ShareBubbles/ShareBubbles/UI/ReturnOnRelease.cs @@ -30,12 +30,12 @@ public class ReturnOnRelease : MonoBehaviour pickupable.onDrop.AddListener(OnPickupRelease); } - public void OnPickupGrabbed() + public void OnPickupGrabbed(InteractionContext _) { isReturning = false; } - public void OnPickupRelease() + public void OnPickupRelease(InteractionContext _) { isReturning = true; } diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index a17aa7b..be28ae9 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -1,8 +1,8 @@ { - "_id": -1, + "_id": 244, "name": "ShareBubbles", - "modversion": "1.0.2", - "gameversion": "2024r177", + "modversion": "1.0.4", + "gameversion": "2025r178", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r43/ShareBubbles.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ShareBubbles.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", - "changelog": "- Initial release.", + "changelog": "- Fixes for 2025r178", "embedcolor": "#f61963" } \ No newline at end of file From 3660b8f68349f7f7cd4beff1148c80c2fd017791 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:37:15 -0600 Subject: [PATCH 076/188] AvatarCloneTest: push so i can reference in an email --- .../AvatarClone/AvatarClone.API.cs | 68 +++++++ .../AvatarClone/AvatarClone.Clones.cs | 103 ++++++++++ .../AvatarClone/AvatarClone.Exclusion.cs | 141 ++++++++++++++ .../AvatarClone/AvatarClone.Fields.cs | 67 +++++++ .../AvatarClone/AvatarClone.Init.cs | 128 +++++++++++++ .../AvatarClone/AvatarClone.MagicaSupport.cs | 34 ++++ .../AvatarClone/AvatarClone.Update.cs | 181 ++++++++++++++++++ .../AvatarClone/AvatarClone.Util.cs | 20 ++ AvatarCloneTest/AvatarClone/AvatarClone.cs | 134 +++++++++++++ .../FPRExclusion/AvatarCloneExclusion.cs | 38 ++++ AvatarCloneTest/AvatarCloneTest.csproj | 9 + AvatarCloneTest/Main.cs | 72 +++++++ AvatarCloneTest/Patches.cs | 87 +++++++++ AvatarCloneTest/Properties/AssemblyInfo.cs | 32 ++++ AvatarCloneTest/README.md | 18 ++ AvatarCloneTest/format.json | 23 +++ 16 files changed, 1155 insertions(+) create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.API.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Init.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Update.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Util.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.cs create mode 100644 AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs create mode 100644 AvatarCloneTest/AvatarCloneTest.csproj create mode 100644 AvatarCloneTest/Main.cs create mode 100644 AvatarCloneTest/Patches.cs create mode 100644 AvatarCloneTest/Properties/AssemblyInfo.cs create mode 100644 AvatarCloneTest/README.md create mode 100644 AvatarCloneTest/format.json diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.API.cs b/AvatarCloneTest/AvatarClone/AvatarClone.API.cs new file mode 100644 index 0000000..89835bd --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.API.cs @@ -0,0 +1,68 @@ +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Public API Methods + + /// + /// Sets whether the specific renderer requires additional runtime checks when copying to the clone. + /// For example, Magica Cloth modifies the sharedMesh & bones of the renderer at runtime. This is not needed + /// for most renderers, so copying for all renderers would be inefficient. + /// + public void SetRendererNeedsAdditionalChecks(Renderer rend, bool needsChecks) + { + switch (rend) + { + case MeshRenderer meshRenderer: + { + int index = _standardRenderers.IndexOf(meshRenderer); + if (index == -1) return; + + if (needsChecks && !_standardRenderersNeedingChecks.Contains(index)) + { + int insertIndex = _standardRenderersNeedingChecks.Count; + _standardRenderersNeedingChecks.Add(index); + _cachedSharedMeshes.Insert(insertIndex, null); + } + else if (!needsChecks) + { + int removeIndex = _standardRenderersNeedingChecks.IndexOf(index); + if (removeIndex != -1) + { + _standardRenderersNeedingChecks.RemoveAt(removeIndex); + _cachedSharedMeshes.RemoveAt(removeIndex); + } + } + return; + } + case SkinnedMeshRenderer skinnedRenderer: + { + int index = _skinnedRenderers.IndexOf(skinnedRenderer); + if (index == -1) return; + + if (needsChecks && !_skinnedRenderersNeedingChecks.Contains(index)) + { + int insertIndex = _skinnedRenderersNeedingChecks.Count; + _skinnedRenderersNeedingChecks.Add(index); + _cachedSharedMeshes.Insert(_standardRenderersNeedingChecks.Count + insertIndex, null); + _cachedSkinnedBoneCounts.Add(0); + } + else if (!needsChecks) + { + int removeIndex = _skinnedRenderersNeedingChecks.IndexOf(index); + if (removeIndex != -1) + { + _skinnedRenderersNeedingChecks.RemoveAt(removeIndex); + _cachedSharedMeshes.RemoveAt(_standardRenderersNeedingChecks.Count + removeIndex); + _cachedSkinnedBoneCounts.RemoveAt(removeIndex); + } + } + break; + } + } + } + + #endregion Public API Methods +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs new file mode 100644 index 0000000..8fb54ec --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs @@ -0,0 +1,103 @@ +using ABI_RC.Core; +using UnityEngine; +using UnityEngine.Rendering; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Clone Creation + + private void CreateClones() + { + int standardCount = _standardRenderers.Count; + _standardClones = new List(standardCount); + _standardCloneFilters = new List(standardCount); + for (int i = 0; i < standardCount; i++) CreateStandardClone(i); + + int skinnedCount = _skinnedRenderers.Count; + _skinnedClones = new List(skinnedCount); + for (int i = 0; i < skinnedCount; i++) CreateSkinnedClone(i); + } + + private void CreateStandardClone(int index) + { + MeshRenderer sourceRenderer = _standardRenderers[index]; + MeshFilter sourceFilter = _standardFilters[index]; + + GameObject go = new(sourceRenderer.name + "_VisualClone") + { + layer = CVRLayers.PlayerClone + }; + + go.transform.SetParent(sourceRenderer.transform, false); + //go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + + MeshRenderer cloneRenderer = go.AddComponent(); + MeshFilter cloneFilter = go.AddComponent(); + + // Initial setup + cloneRenderer.sharedMaterials = sourceRenderer.sharedMaterials; + cloneRenderer.shadowCastingMode = ShadowCastingMode.Off; + cloneRenderer.probeAnchor = sourceRenderer.probeAnchor; + cloneRenderer.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity); + cloneFilter.sharedMesh = sourceFilter.sharedMesh; + + // Optimizations to enforce + cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + cloneRenderer.allowOcclusionWhenDynamic = false; + + // Optimizations to enforce + sourceRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + sourceRenderer.allowOcclusionWhenDynamic = false; + + _standardClones.Add(cloneRenderer); + _standardCloneFilters.Add(cloneFilter); + } + + private void CreateSkinnedClone(int index) + { + SkinnedMeshRenderer source = _skinnedRenderers[index]; + + GameObject go = new(source.name + "_VisualClone") + { + layer = CVRLayers.PlayerClone + }; + + go.transform.SetParent(source.transform, false); + //go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + + SkinnedMeshRenderer clone = go.AddComponent(); + + // Initial setup + clone.sharedMaterials = source.sharedMaterials; + clone.shadowCastingMode = ShadowCastingMode.Off; + clone.probeAnchor = source.probeAnchor; + clone.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity); + clone.sharedMesh = source.sharedMesh; + clone.rootBone = source.rootBone; + clone.bones = source.bones; + clone.quality = source.quality; + clone.updateWhenOffscreen = source.updateWhenOffscreen; + + // Optimizations to enforce + clone.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + clone.allowOcclusionWhenDynamic = false; + clone.updateWhenOffscreen = false; + clone.skinnedMotionVectors = false; + clone.forceMatrixRecalculationPerRender = false; + clone.quality = SkinQuality.Bone4; + + // Optimizations to enforce + source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + source.allowOcclusionWhenDynamic = false; + source.updateWhenOffscreen = false; + source.skinnedMotionVectors = false; + source.forceMatrixRecalculationPerRender = false; + source.quality = SkinQuality.Bone4; + + _skinnedClones.Add(clone); + } + + #endregion Clone Creation +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs new file mode 100644 index 0000000..d5136e3 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs @@ -0,0 +1,141 @@ +using ABI.CCK.Components; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + private readonly Dictionary> _exclusionDirectRenderers = new(); + private readonly Dictionary> _exclusionControlledBones = new(); + + private void InitializeExclusions() + { + // Add head exclusion for humanoid avatars if not present + var animator = GetComponent(); + if (animator != null && animator.isHuman) + { + var headBone = animator.GetBoneTransform(HumanBodyBones.Head); + if (headBone != null && headBone.GetComponent() == null) + { + var exclusion = headBone.gameObject.AddComponent(); + exclusion.isShown = false; + exclusion.target = headBone; + exclusion.shrinkToZero = true; + } + } + + // Process existing exclusions bottom-up + var exclusions = GetComponentsInChildren(true); + + for (int i = exclusions.Length - 1; i >= 0; i--) + { + var exclusion = exclusions[i]; + if (exclusion.target == null) + exclusion.target = exclusion.transform; + + // Skip invalid exclusions or already processed targets + if (exclusion.target == null || _exclusionDirectRenderers.ContainsKey(exclusion.target)) + { + Destroy(exclusion); + continue; + } + + // Initialize data for this exclusion + _exclusionDirectRenderers[exclusion.target] = new HashSet(); + _exclusionControlledBones[exclusion.target] = new HashSet(); + + // Set up our behaviour + exclusion.behaviour = new AvatarCloneExclusion(this, exclusion.target); + + // Collect affected renderers and bones + CollectExclusionData(exclusion.target); + + // Initial update + exclusion.UpdateExclusions(); + } + } + + private void CollectExclusionData(Transform target) + { + var stack = new Stack(); + stack.Push(target); + + while (stack.Count > 0) + { + var current = stack.Pop(); + + // Skip if this transform belongs to another exclusion + if (current != target && current.GetComponent() != null) + continue; + + _exclusionControlledBones[target].Add(current); + + // Add renderers that will need their clone visibility toggled + foreach (var renderer in current.GetComponents()) + { + // Find corresponding clone renderer + if (renderer is MeshRenderer meshRenderer) + { + int index = _standardRenderers.IndexOf(meshRenderer); + if (index != -1) + _exclusionDirectRenderers[target].Add(_standardClones[index]); + } + else if (renderer is SkinnedMeshRenderer skinnedRenderer) + { + int index = _skinnedRenderers.IndexOf(skinnedRenderer); + if (index != -1) + _exclusionDirectRenderers[target].Add(_skinnedClones[index]); + } + } + + // Add children to stack + foreach (Transform child in current) + { + stack.Push(child); + } + } + } + + public void HandleExclusionUpdate(Transform target, Transform shrinkBone, bool isShown) + { + if (!_exclusionDirectRenderers.TryGetValue(target, out var directCloneRenderers) || + !_exclusionControlledBones.TryGetValue(target, out var controlledBones)) + return; + + // Handle direct clone renderers + foreach (var cloneRenderer in directCloneRenderers) + { + cloneRenderer.enabled = isShown; + } + + // Update bone references in clone renderers + int cloneCount = _skinnedClones.Count; + var cloneRenderers = _skinnedClones; + var sourceRenderers = _skinnedRenderers; + + for (int i = 0; i < cloneCount; i++) + { + var clone = cloneRenderers[i]; + var source = sourceRenderers[i]; + var sourceBones = source.bones; + var cloneBones = clone.bones; + int boneCount = cloneBones.Length; + bool needsUpdate = false; + + for (int j = 0; j < boneCount; j++) + { + // Check if this bone is in our controlled set + if (controlledBones.Contains(sourceBones[j])) + { + cloneBones[j] = isShown ? sourceBones[j] : shrinkBone; + needsUpdate = true; + } + } + + if (needsUpdate) + { + clone.bones = cloneBones; + } + } + } +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs new file mode 100644 index 0000000..0ba69b0 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs @@ -0,0 +1,67 @@ +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Profile Markers +//#if UNITY_EDITOR + private static readonly UnityEngine.Profiling.CustomSampler s_CopyMaterials = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMaterials"); + private static readonly UnityEngine.Profiling.CustomSampler s_CopyBlendShapes = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyBlendShapes"); + private static readonly UnityEngine.Profiling.CustomSampler s_CopyMeshes = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMeshes"); + + private static readonly UnityEngine.Profiling.CustomSampler s_MyOnPreRender = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.MyOnPreRender"); + private static readonly UnityEngine.Profiling.CustomSampler s_SetShadowsOnly = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetShadowsOnly"); + private static readonly UnityEngine.Profiling.CustomSampler s_UndoShadowsOnly = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoShadowsOnly"); + private static readonly UnityEngine.Profiling.CustomSampler s_SetUiCulling = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetUiCulling"); + private static readonly UnityEngine.Profiling.CustomSampler s_UndoUiCulling = + UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoUiCulling"); + +//#endif + #endregion Profile Markers + + #region Source Renderers + private List _standardRenderers; + private List _standardFilters; + private List _skinnedRenderers; + private List _allSourceRenderers; // For shadow casting only + #endregion Source Renderers + + #region Clone Renderers + private List _standardClones; + private List _standardCloneFilters; + private List _skinnedClones; + #endregion Clone Renderers + + #region Dynamic Check Lists + private List _standardRenderersNeedingChecks; // Stores indices into _standardRenderers + private List _skinnedRenderersNeedingChecks; // Stores indices into _skinnedRenderers + private List _cachedSkinnedBoneCounts; // So we don't copy the bones unless they've changed + private List _cachedSharedMeshes; // So we don't copy the mesh unless it's changed + #endregion Dynamic Check Lists + + #region Material Data + private List _localMaterials; + private List _cullingMaterials; + private List _mainMaterials; + private MaterialPropertyBlock _propertyBlock; + #endregion Material Data + + #region Blend Shape Data + private List> _blendShapeWeights; + #endregion Blend Shape Data + + #region Shadow and UI Culling Settings + private bool _uiCullingActive; + private bool _shadowsOnlyActive; + private bool[] _originallyHadShadows; + private bool[] _originallyWasEnabled; + #endregion Shadow and UI Culling Settings +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs new file mode 100644 index 0000000..41bd84b --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs @@ -0,0 +1,128 @@ +using ABI_RC.Core.Player.ShadowClone; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Initialization + + private void InitializeCollections() + { + _standardRenderers = new List(); + _standardFilters = new List(); + _skinnedRenderers = new List(); + _allSourceRenderers = new List(); + + _standardClones = new List(); + _standardCloneFilters = new List(); + _skinnedClones = new List(); + + _standardRenderersNeedingChecks = new List(); + _skinnedRenderersNeedingChecks = new List(); + _cachedSkinnedBoneCounts = new List(); + _cachedSharedMeshes = new List(); + + _localMaterials = new List(); + _cullingMaterials = new List(); + _mainMaterials = new List(); + _propertyBlock = new MaterialPropertyBlock(); + + _blendShapeWeights = new List>(); + } + + private void InitializeRenderers() + { + var renderers = GetComponentsInChildren(true); + + // Pre-size lists based on found renderers + // _standardRenderers.Capacity = renderers.Length; + // _standardFilters.Capacity = renderers.Length; + // _skinnedRenderers.Capacity = renderers.Length; + // _allSourceRenderers.Capacity = renderers.Length; + + // Sort renderers into their respective lists + foreach (Renderer render in renderers) + { + _allSourceRenderers.Add(render); + + switch (render) + { + case MeshRenderer meshRenderer: + { + MeshFilter filter = meshRenderer.GetComponent(); + if (filter != null && filter.sharedMesh != null) + { + _standardRenderers.Add(meshRenderer); + _standardFilters.Add(filter); + } + break; + } + case SkinnedMeshRenderer skinnedRenderer: + { + if (skinnedRenderer.sharedMesh != null) _skinnedRenderers.Add(skinnedRenderer); + break; + } + } + } + } + + private void SetupMaterialsAndBlendShapes() + { + // Cache counts + int standardCount = _standardRenderers.Count; + int skinnedCount = _skinnedRenderers.Count; + var standardRenderers = _standardRenderers; + var skinnedRenderers = _skinnedRenderers; + var localMats = _localMaterials; + var cullingMats = _cullingMaterials; + var blendWeights = _blendShapeWeights; + + // Setup standard renderer materials + for (int i = 0; i < standardCount; i++) + { + MeshRenderer render = standardRenderers[i]; + int matCount = render.sharedMaterials.Length; + + // Local materials array + var localMatArray = new Material[matCount]; + for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j]; + localMats.Add(localMatArray); + + // Culling materials array + var cullingMatArray = new Material[matCount]; + for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial; + cullingMats.Add(cullingMatArray); + } + + // Setup skinned renderer materials and blend shapes + for (int i = 0; i < skinnedCount; i++) + { + SkinnedMeshRenderer render = skinnedRenderers[i]; + int matCount = render.sharedMaterials.Length; + + // Local materials array + var localMatArray = new Material[matCount]; + for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j]; + localMats.Add(localMatArray); + + // Culling materials array + var cullingMatArray = new Material[matCount]; + for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial; + cullingMats.Add(cullingMatArray); + + // Blend shape weights + int blendShapeCount = render.sharedMesh.blendShapeCount; + var weights = new List(blendShapeCount); + for (int j = 0; j < blendShapeCount; j++) weights.Add(0f); + blendWeights.Add(weights); + } + + // Initialize renderer state arrays + int totalRenderers = _allSourceRenderers.Count; + _originallyHadShadows = new bool[totalRenderers]; + _originallyWasEnabled = new bool[totalRenderers]; + } + + #endregion Initialization +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs b/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs new file mode 100644 index 0000000..7ae5855 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs @@ -0,0 +1,34 @@ +using MagicaCloth; +using MagicaCloth2; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Magica Cloth Support + + private void SetupMagicaClothSupport() + { + var magicaCloths1 = GetComponentsInChildren(true); + foreach (MagicaRenderDeformer magicaCloth in magicaCloths1) + { + // Get the renderer on the same object + Renderer renderer = magicaCloth.gameObject.GetComponent(); + SetRendererNeedsAdditionalChecks(renderer, true); + } + + var magicaCloths2 = GetComponentsInChildren(true); + foreach (MagicaCloth2.MagicaCloth magicaCloth in magicaCloths2) + { + if (magicaCloth.serializeData.clothType != ClothProcess.ClothType.MeshCloth) + continue; // Only matters for cloth physics + + // Set the affected renderers as requiring extra checks + var renderers = magicaCloth.serializeData.sourceRenderers; + foreach (Renderer renderer in renderers) SetRendererNeedsAdditionalChecks(renderer, true); + } + } + + #endregion Magica Cloth Support +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs new file mode 100644 index 0000000..42cef73 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs @@ -0,0 +1,181 @@ +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Update Methods + + private void UpdateStandardRenderers() + { + int count = _standardRenderers.Count; + var sourceRenderers = _standardRenderers; + var cloneRenderers = _standardClones; + var localMats = _localMaterials; + + for (int i = 0; i < count; i++) + { + if (!IsRendererValid(sourceRenderers[i])) continue; + CopyMaterialsAndProperties( + sourceRenderers[i], + cloneRenderers[i], + _propertyBlock, + _mainMaterials, + localMats[i]); + } + } + + private void UpdateSkinnedRenderers() + { + int standardCount = _standardRenderers.Count; + int count = _skinnedRenderers.Count; + var sourceRenderers = _skinnedRenderers; + var cloneRenderers = _skinnedClones; + var localMats = _localMaterials; + var blendWeights = _blendShapeWeights; + + for (int i = 0; i < count; i++) + { + SkinnedMeshRenderer source = sourceRenderers[i]; + if (!IsRendererValid(source)) continue; + + SkinnedMeshRenderer clone = cloneRenderers[i]; + CopyMaterialsAndProperties( + source, + clone, + _propertyBlock, + _mainMaterials, + localMats[i + standardCount]); + + CopyBlendShapes(source, clone, blendWeights[i]); + } + } + + private void UpdateStandardRenderersWithChecks() + { + s_CopyMeshes.Begin(); + + var cloneFilters = _standardCloneFilters; + var sourceFilters = _standardFilters; + var cachedMeshes = _cachedSharedMeshes; + var checkIndices = _standardRenderersNeedingChecks; + int checkCount = checkIndices.Count; + + while (cachedMeshes.Count < checkCount) cachedMeshes.Add(null); + + for (int i = 0; i < checkCount; i++) + { + int rendererIndex = checkIndices[i]; + Mesh newMesh = sourceFilters[rendererIndex].sharedMesh; + if (ReferenceEquals(newMesh, cachedMeshes[i])) continue; + cloneFilters[rendererIndex].sharedMesh = newMesh; // expensive & allocates + cachedMeshes[i] = newMesh; + } + + s_CopyMeshes.End(); + } + + private void UpdateSkinnedRenderersWithChecks() + { + s_CopyMeshes.Begin(); + + var sourceRenderers = _skinnedRenderers; + var cloneRenderers = _skinnedClones; + var cachedMeshes = _cachedSharedMeshes; + var cachedBoneCounts = _cachedSkinnedBoneCounts; + var checkIndices = _skinnedRenderersNeedingChecks; + int checkCount = checkIndices.Count; + int meshOffset = _standardRenderersNeedingChecks.Count; + + // Ensure cache lists are properly sized + while (cachedMeshes.Count < meshOffset + checkCount) cachedMeshes.Add(null); + while (cachedBoneCounts.Count < checkCount) cachedBoneCounts.Add(0); + + for (int i = 0; i < checkCount; i++) + { + int rendererIndex = checkIndices[i]; + SkinnedMeshRenderer source = sourceRenderers[rendererIndex]; + SkinnedMeshRenderer clone = cloneRenderers[rendererIndex]; + + // Check mesh changes + Mesh newMesh = source.sharedMesh; // expensive & allocates + if (!ReferenceEquals(newMesh, cachedMeshes[meshOffset + i])) + { + clone.sharedMesh = newMesh; + cachedMeshes[meshOffset + i] = newMesh; + } + + // Check bone changes + var sourceBones = source.bones; + int newBoneCount = sourceBones.Length; + int oldBoneCount = cachedBoneCounts[i]; + if (newBoneCount == oldBoneCount) + continue; + + var cloneBones = clone.bones; // expensive & allocates + if (newBoneCount > oldBoneCount) + { + // Resize array and copy only the new bones (Magica Cloth appends bones when enabling Mesh Cloth) + Array.Resize(ref cloneBones, newBoneCount); + for (int boneIndex = oldBoneCount; boneIndex < newBoneCount; boneIndex++) + cloneBones[boneIndex] = sourceBones[boneIndex]; + clone.bones = cloneBones; + } + else + { + // If shrinking, just set the whole array + clone.bones = sourceBones; + } + + cachedBoneCounts[i] = newBoneCount; + } + + s_CopyMeshes.End(); + } + + private static void CopyMaterialsAndProperties( + Renderer source, Renderer clone, + MaterialPropertyBlock propertyBlock, + List mainMaterials, + Material[] localMaterials) + { + s_CopyMaterials.Begin(); + + source.GetSharedMaterials(mainMaterials); + + int matCount = mainMaterials.Count; + bool hasChanged = false; + for (var i = 0; i < matCount; i++) + { + if (ReferenceEquals(mainMaterials[i], localMaterials[i])) continue; + localMaterials[i] = mainMaterials[i]; + hasChanged = true; + } + if (hasChanged) clone.sharedMaterials = localMaterials; + + source.GetPropertyBlock(propertyBlock); + clone.SetPropertyBlock(propertyBlock); + + s_CopyMaterials.End(); + } + + private static void CopyBlendShapes( + SkinnedMeshRenderer source, + SkinnedMeshRenderer target, + List blendShapeWeights) + { + s_CopyBlendShapes.Begin(); + + int weightCount = blendShapeWeights.Count; + for (var i = 0; i < weightCount; i++) + { + var weight = source.GetBlendShapeWeight(i); + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (weight == blendShapeWeights[i]) continue; // Halves the work + target.SetBlendShapeWeight(i, blendShapeWeights[i] = weight); + } + + s_CopyBlendShapes.End(); + } + #endregion Update Methods +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs new file mode 100644 index 0000000..c6fb033 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs @@ -0,0 +1,20 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + private static bool CameraRendersPlayerLocalLayer(Camera cam) + => (cam.cullingMask & (1 << CVRLayers.PlayerLocal)) != 0; + + private static bool CameraRendersPlayerCloneLayer(Camera cam) + => (cam.cullingMask & (1 << CVRLayers.PlayerClone)) != 0; + + private static bool IsUIInternalCamera(Camera cam) + => cam == PlayerSetup.Instance.activeUiCam; + + private static bool IsRendererValid(Renderer renderer) + => renderer && renderer.gameObject.activeInHierarchy; +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.cs b/AvatarCloneTest/AvatarClone/AvatarClone.cs new file mode 100644 index 0000000..88c8112 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.cs @@ -0,0 +1,134 @@ +using UnityEngine; +using UnityEngine.Rendering; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone : MonoBehaviour +{ + #region Unity Events + + private void Start() + { + InitializeCollections(); + InitializeRenderers(); + SetupMaterialsAndBlendShapes(); + CreateClones(); + + InitializeExclusions(); + SetupMagicaClothSupport(); + + Camera.onPreCull += MyOnPreRender; + } + + private void OnDestroy() + { + Camera.onPreCull -= MyOnPreRender; + } + + private void LateUpdate() + { + // Update all renderers with basic properties (Materials & BlendShapes) + UpdateStandardRenderers(); + UpdateSkinnedRenderers(); + + // Additional pass for renderers needing extra checks (Shared Mesh & Bone Changes) + UpdateStandardRenderersWithChecks(); + UpdateSkinnedRenderersWithChecks(); + } + + private void MyOnPreRender(Camera cam) + { + s_MyOnPreRender.Begin(); + + bool isOurUiCamera = IsUIInternalCamera(cam); + bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam); + bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam); + + // Renders both player layers. + // PlayerLocal will now act as a shadow caster, while PlayerClone will act as the actual head-hidden renderer. + bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer; + if (!_shadowsOnlyActive && rendersBothPlayerLayers) + { + s_SetShadowsOnly.Begin(); + + int sourceCount = _allSourceRenderers.Count; + var sourceRenderers = _allSourceRenderers; + for (int i = 0; i < sourceCount; i++) + { + Renderer renderer = sourceRenderers[i]; + if (!IsRendererValid(renderer)) continue; + + bool shouldRender = renderer.shadowCastingMode != ShadowCastingMode.Off; + _originallyWasEnabled[i] = renderer.enabled; + _originallyHadShadows[i] = shouldRender; + renderer.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + if (renderer.forceRenderingOff == shouldRender) renderer.forceRenderingOff = !shouldRender; // TODO: Eval if check is needed + } + _shadowsOnlyActive = true; + + s_SetShadowsOnly.End(); + } + else if (_shadowsOnlyActive && !rendersBothPlayerLayers) + { + s_UndoShadowsOnly.Begin(); + + int sourceCount = _allSourceRenderers.Count; + var sourceRenderers = _allSourceRenderers; + for (int i = 0; i < sourceCount; i++) + { + Renderer renderer = sourceRenderers[i]; + if (!IsRendererValid(renderer)) continue; + + renderer.shadowCastingMode = _originallyHadShadows[i] ? ShadowCastingMode.On : ShadowCastingMode.Off; + if (renderer.forceRenderingOff == _originallyWasEnabled[i]) renderer.forceRenderingOff = !_originallyWasEnabled[i]; // TODO: Eval if check is needed + } + _shadowsOnlyActive = false; + + s_UndoShadowsOnly.End(); + } + + // Handle UI culling material changes + if (isOurUiCamera && !_uiCullingActive && rendersOurCloneLayer) + { + s_SetUiCulling.Begin(); + + int standardCount = _standardRenderers.Count; + var standardClones = _standardClones; + var cullingMaterials = _cullingMaterials; + for (int i = 0; i < standardCount; i++) + standardClones[i].sharedMaterials = cullingMaterials[i]; + + int skinnedCount = _skinnedRenderers.Count; + var skinnedClones = _skinnedClones; + for (int i = 0; i < skinnedCount; i++) + skinnedClones[i].sharedMaterials = cullingMaterials[i + standardCount]; + + _uiCullingActive = true; + + s_SetUiCulling.End(); + } + else if (!isOurUiCamera && _uiCullingActive) + { + s_UndoUiCulling.Begin(); + + int standardCount = _standardRenderers.Count; + var standardClones = _standardClones; + var localMaterials = _localMaterials; + for (int i = 0; i < standardCount; i++) + standardClones[i].sharedMaterials = localMaterials[i]; + + int skinnedCount = _skinnedRenderers.Count; + var skinnedClones = _skinnedClones; + for (int i = 0; i < skinnedCount; i++) + skinnedClones[i].sharedMaterials = localMaterials[i + standardCount]; + + _uiCullingActive = false; + + s_UndoUiCulling.End(); + } + + s_MyOnPreRender.End(); + } + + #endregion Unity Events +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs b/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs new file mode 100644 index 0000000..5681761 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs @@ -0,0 +1,38 @@ +using ABI.CCK.Components; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public class AvatarCloneExclusion : IExclusionBehaviour +{ + private readonly AvatarClone _cloneSystem; + private readonly Transform _target; + private Transform _shrinkBone; + + public bool isImmuneToGlobalState { get; set; } + + public AvatarCloneExclusion(AvatarClone cloneSystem, Transform target) + { + _cloneSystem = cloneSystem; + _target = target; + } + + public void UpdateExclusions(bool isShown, bool shrinkToZero) + { + Debug.Log($"[AvatarClone2] Updating exclusion for {_target.name}: isShown={isShown}, shrinkToZero={shrinkToZero}"); + + if (_shrinkBone == null) + { + // Create shrink bone parented directly to target + _shrinkBone = new GameObject($"{_target.name}_Shrink").transform; + _shrinkBone.SetParent(_target, false); + Debug.Log($"[AvatarClone2] Created shrink bone for {_target.name}"); + } + + // Set scale based on shrink mode + _shrinkBone.localScale = shrinkToZero ? Vector3.zero : Vector3.positiveInfinity; + + // Let the clone system handle the update + _cloneSystem.HandleExclusionUpdate(_target, _shrinkBone, isShown); + } +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarCloneTest.csproj b/AvatarCloneTest/AvatarCloneTest.csproj new file mode 100644 index 0000000..2e58669 --- /dev/null +++ b/AvatarCloneTest/AvatarCloneTest.csproj @@ -0,0 +1,9 @@ + + + + LocalCloneFix + + + TRACE;TRACE; + + diff --git a/AvatarCloneTest/Main.cs b/AvatarCloneTest/Main.cs new file mode 100644 index 0000000..21a34bf --- /dev/null +++ b/AvatarCloneTest/Main.cs @@ -0,0 +1,72 @@ +using ABI_RC.Core; +using MelonLoader; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public class AvatarCloneTestMod : MelonMod +{ + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(AvatarCloneTest)); + + internal static readonly MelonPreferences_Entry EntryUseAvatarCloneTest = + Category.CreateEntry("use_avatar_clone_test", true, + "Use Avatar Clone", description: "Uses the Avatar Clone setup for the local avatar."); + + // internal static readonly MelonPreferences_Entry EntryCopyBlendShapes = + // Category.CreateEntry("copy_blend_shapes", true, + // "Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone."); + // + // internal static readonly MelonPreferences_Entry EntryCopyMaterials = + // Category.CreateEntry("copy_materials", true, + // "Copy Materials", description: "Copies the materials from the original avatar to the clone."); + // + // internal static readonly MelonPreferences_Entry EntryCopyMeshes = + // Category.CreateEntry("copy_meshes", true, + // "Copy Meshes", description: "Copies the meshes from the original avatar to the clone."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason? + } + + public override void OnUpdate() + { + // press f1 to find all cameras that arent tagged main and set them tno not render CVRLayers.PlayerClone + if (Input.GetKeyDown(KeyCode.F1)) + { + foreach (var camera in UnityEngine.Object.FindObjectsOfType()) + { + if (camera.tag != "MainCamera") + { + camera.cullingMask &= ~(1 << CVRLayers.PlayerClone); + } + } + } + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/AvatarCloneTest/Patches.cs b/AvatarCloneTest/Patches.cs new file mode 100644 index 0000000..b4cec17 --- /dev/null +++ b/AvatarCloneTest/Patches.cs @@ -0,0 +1,87 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Core.Player.TransformHider; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.Camera; +using HarmonyLib; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public static class Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))] + private static bool OnSetupAvatar(GameObject avatar) + { + if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) return true; + avatar.AddComponent(); + return false; + } + + // [HarmonyPostfix] + // [HarmonyPatch(typeof(FPRExclusion), nameof(FPRExclusion.UpdateExclusions))] + // private static void OnUpdateExclusions(ref FPRExclusion __instance) + // { + // AvatarClone clone = PlayerSetup.Instance._avatar.GetComponent(); + // if (clone == null) return; + // clone.SetBoneChainVisibility(__instance.target, !__instance.isShown, !__instance.shrinkToZero); + // } + + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRMirror), nameof(CVRMirror.Start))] + private static void OnMirrorStart(CVRMirror __instance) + { + if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) + return; + + // Don't reflect the player clone layer + __instance.m_ReflectLayers &= ~(1 << CVRLayers.PlayerClone); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Update))] + private static void OnTransformHiderManagerUpdate(PlayerSetup __instance) + { + if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) + return; + + if (MetaPort.Instance.settings.GetSettingsBool("ExperimentalAvatarOverrenderUI")) + __instance.activeUiCam.cullingMask |= 1 << CVRLayers.PlayerClone; + else + __instance.activeUiCam.cullingMask &= ~(1 << CVRLayers.PlayerClone); + } + + private static bool _wasDebugInPortableCamera; + + [HarmonyPostfix] + [HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.Update))] + private static void OnPortableCameraUpdate(ref PortableCamera __instance) + { + if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) + { + // Show both PlayerLocal and PlayerClone + __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal; + __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone; + return; + } + + if (TransformHiderManager.s_DebugInPortableCamera == _wasDebugInPortableCamera) + return; + + if (TransformHiderManager.s_DebugInPortableCamera) + { + // Hide PlayerLocal, show PlayerClone + __instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerLocal); + __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone; + } + else + { + // Show PlayerLocal, hide PlayerClone + __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal; + __instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerClone); + } + + _wasDebugInPortableCamera = TransformHiderManager.s_DebugInPortableCamera; + } +} \ No newline at end of file diff --git a/AvatarCloneTest/Properties/AssemblyInfo.cs b/AvatarCloneTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3334b28 --- /dev/null +++ b/AvatarCloneTest/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.AvatarCloneTest.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.AvatarCloneTest))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.AvatarCloneTest))] + +[assembly: MelonInfo( + typeof(NAK.AvatarCloneTest.AvatarCloneTestMod), + nameof(NAK.AvatarCloneTest), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarCloneTest" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.AvatarCloneTest.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/AvatarCloneTest/README.md b/AvatarCloneTest/README.md new file mode 100644 index 0000000..cc12a9c --- /dev/null +++ b/AvatarCloneTest/README.md @@ -0,0 +1,18 @@ +# VisualCloneFix + +Fixes the Visual Clone system and allows you to use it again. + +Using the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load. + +**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/AvatarCloneTest/format.json b/AvatarCloneTest/format.json new file mode 100644 index 0000000..6634e9f --- /dev/null +++ b/AvatarCloneTest/format.json @@ -0,0 +1,23 @@ +{ + "_id": 221, + "name": "VisualCloneFix", + "modversion": "1.0.1", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes the Visual Clone system and allows you to use it again.\n\nUsing the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.\n\n**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.", + "searchtags": [ + "visual", + "clone", + "head", + "hiding" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/", + "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.", + "embedcolor": "#f61963" +} \ No newline at end of file From 0042590aa6b47da9b3656d016c8ff39df2c253d2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 02:57:35 -0500 Subject: [PATCH 077/188] Move many mods to Deprecated folder, fix spelling --- .../AASBufferFix/AASBufferFix.csproj | 0 .../AASBufferFix/AASBufferHelper.cs | 0 .../AASBufferFix/HarmonyPatches.cs | 0 .../AASBufferFix/Main.cs | 0 .../AASBufferFix/Properties/AssemblyInfo.cs | 0 .../AASBufferFix/README.md | 0 .../AASBufferFix/Utils.cs | 0 .../AASBufferFix/format.json | 0 .../AASDefaultProfileFix.csproj | 0 .../AASDefaultProfileFix}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../AASDefaultProfileFix}/README.md | 0 .../AASDefaultProfileFix}/format.json | 0 .../AlternateIKSystem.csproj | 0 .../AlternateIKSystem/HarmonyPatches.cs | 0 .../AlternateIKSystem/IK/BodyControl.cs | 0 .../AlternateIKSystem/IK/IKCalibrator.cs | 0 .../IK/IKHandlers/IKHandler.cs | 0 .../IK/IKHandlers/IKHandlerDesktop.cs | 0 .../IK/IKHandlers/IKHandlerHalfBody.cs | 0 .../AlternateIKSystem/IK/IKManager.cs | 0 .../AlternateIKSystem/IK/MusclePoses.cs | 0 .../IK/Tracking/SteamVRTrackerManager.cs | 0 .../IK/VRIKHelpers/VRIKLocomotionData.cs | 0 .../IK/VRIKHelpers/VRIKUtils.cs | 0 .../WeightManipulators/BodyParts/BodyPart.cs | 0 .../DeviceControlManipulator.cs | 0 .../Interface/IWeightManipulator.cs | 0 .../TrackingControlManipulator.cs | 0 .../WeightManipulatorManager.cs | 0 .../Integrations/BTKUIAddon.cs | 0 .../AlternateIKSystem/LICENSE.txt | 0 .../AlternateIKSystem/Main.cs | 0 .../AlternateIKSystem/ModSettings.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../AlternateIKSystem/README.md | 0 .../AlternateIKSystem/format.json | 0 .../AutoSyncTransforms}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../AutoSyncTransforms}/README.md | 0 .../WhereAmIPointing.csproj | 0 .../AutoSyncTransforms}/format.json | 0 .../AvatarScaleMod}/AvatarScaleMod.csproj | 0 .../AvatarScaling/AvatarScaleManager.cs | 4 +- .../AvatarScaling/Components/BaseScaler.cs | 0 .../AvatarScaling/Components/LocalScaler.cs | 5 +- .../AvatarScaling/Components/NetworkScaler.cs | 0 .../AvatarScaling/Events/AvatarScaleEvents.cs | 0 .../AvatarScaling/ScaledComponents.cs | 0 .../AvatarScaleMod}/HarmonyPatches.cs | 0 .../AvatarScaleMod}/Input/DebugKeybinds.cs | 0 .../AvatarScaleMod}/Input/ScaleReconizer.cs | 0 .../Integrations/BTKUI/BtkUiAddon.cs | 55 +- .../BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs | 9 +- .../BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs | 9 +- .../BTKUI/BtkUiAddon_CAT_DebugOptions.cs | 9 +- ...BtkUiAddon_CAT_UniversalScalingSettings.cs | 31 +- .../BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs | 35 +- .../Integrations/BTKUI/BtkUiAddon_Utils.cs | 77 +++ .../AvatarScaleMod}/Main.cs | 0 .../AvatarScaleMod}/ModSettings.cs | 0 .../AvatarScaleMod}/Networking/ModNetwork.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../AvatarScaleMod}/README.md | 0 .../AvatarScaleMod}/Scripts.cs | 9 +- .../AvatarScaleMod}/format.json | 0 .../resources/ASM_Icon_AvatarHeightConfig.png | Bin .../resources/ASM_Icon_AvatarHeightCopy.png | Bin .../AvatarScaleMod}/resources/menu.js | 0 .../BadAnimatorFix/BadAnimatorFix.csproj | 0 .../BadAnimatorFix/BadAnimatorFixManager.cs | 0 .../BadAnimatorFix/BadAnimatorFixer.cs | 0 .../BadAnimatorFix/HarmonyPatches.cs | 0 .../BadAnimatorFix/Main.cs | 0 .../BadAnimatorFix/Properties/AssemblyInfo.cs | 0 .../BadAnimatorFix/format.json | 0 .../Blackout/AssetHandler.cs | 0 .../Blackout/Blackout.csproj | 0 .../Blackout/BlackoutController.cs | 0 .../Blackout/HarmonyPatches.cs | 0 .../Blackout/Integrations/BTKUIAddon.cs | 0 .../Integrations/UIExpansionKitAddon.cs | 0 .../Blackout/Main.cs | 0 .../Blackout/Properties/AssemblyInfo.cs | 0 .../Blackout/Resource1.Designer.cs | 0 .../Blackout/Resource1.resx | 0 .../Blackout/format.json | 0 .../resources/blackout_controller.asset | Bin .../BullshitWatcher}/BullshitWatcher.csproj | 0 .../BullshitWatcher}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../CVRGizmos}/CVRGizmoManager.cs | 29 +- .../CVRGizmos}/CVRGizmos.csproj | 0 .../CVRAdvancedAvatarSettingsPointer.cs | 0 .../CVRAdvancedAvatarSettingsTrigger.cs | 0 .../CVRGizmos}/GizmoTypes/CVRAvatar.cs | 0 .../GizmoTypes/CVRAvatarPickupMarker.cs | 0 .../GizmoTypes/CVRDistanceConstrain.cs | 0 .../CVRGizmos}/GizmoTypes/CVRDistanceLod.cs | 0 .../CVRGizmos}/GizmoTypes/CVRGizmoBase.cs | 0 .../GizmoTypes/CVRHapticAreaChest.cs | 0 .../CVRGizmos}/GizmoTypes/CVRHapticZone.cs | 0 .../CVRGizmos}/GizmoTypes/CVRPointer.cs | 0 .../GizmoTypes/CVRSpawnableTrigger.cs | 0 .../GizmoTypes/CVRToggleStateTrigger.cs | 0 .../GizmoTypes/Unity_BoxCollider.cs | 0 .../GizmoTypes/Unity_CapsuleCollider.cs | 0 .../GizmoTypes/Unity_SphereCollider.cs | 0 {CVRGizmos => .Deprecated/CVRGizmos}/Main.cs | 0 .../CVRGizmos/Popcron.Gizmos/Constants.cs | 7 + .../CVRGizmos}/Popcron.Gizmos/Drawer.cs | 23 +- .../Popcron.Gizmos/Drawers/CubeDrawer.cs | 15 +- .../Popcron.Gizmos/Drawers/LineDrawer.cs | 18 + .../Popcron.Gizmos/Drawers/PolygonDrawer.cs | 15 +- .../Popcron.Gizmos/Drawers/SquareDrawer.cs | 15 +- .../CVRGizmos/Popcron.Gizmos/Element.cs | 11 + .../CVRGizmos}/Popcron.Gizmos/Gizmos.cs | 363 ++++++----- .../Popcron.Gizmos/GizmosInstance.cs | 0 .../CVRGizmos}/Popcron.Gizmos/LICENSE.md | 0 .../CVRGizmos}/Properties/AssemblyInfo.cs | 0 .../CVRGizmos}/format.json | 0 .../CameraExperiments.csproj | 0 .Deprecated/CameraExperiments/Main.cs | 170 ++++++ .../Properties/AssemblyInfo.cs | 32 + .Deprecated/CameraExperiments/README.md | 14 + .Deprecated/CameraExperiments/format.json | 23 + .../CameraFixes/CameraFixes.csproj | 0 .../CameraFixes/HarmonyPatches.cs | 0 .../CameraFixes/Main.cs | 0 .../CameraFixes/Properties/AssemblyInfo.cs | 0 .../CameraFixes/format.json | 0 .../ClearHudNotifications.csproj | 0 .../ClearHudNotifications/HarmonyPatches.cs | 0 .../ClearHudNotifications/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ClearHudNotifications/format.json | 0 .../ControlToUnlockMouse.csproj | 6 + .Deprecated/ControlToUnlockMouse/Main.cs | 308 ++++++++++ .../Properties/AssemblyInfo.cs | 32 + .../ControlToUnlockMouse}/README.md | 0 .../ControlToUnlockMouse}/format.json | 0 .../ControllerFreeze/ControllerFreeze.csproj | 0 .../ControllerFreeze/HarmonyPatches.cs | 0 .../ControllerFreeze/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ControllerFreeze/README.md | 0 .../ControllerFreeze/format.json | 0 .../DesktopVRIK/DesktopVRIK.csproj | 0 .../DesktopVRIK/HarmonyPatches.cs | 0 .../DesktopVRIK/IK/IKCalibrator.cs | 0 .../DesktopVRIK/IK/IKHandlers/IKHandler.cs | 0 .../IK/IKHandlers/IKHandlerDesktop.cs | 0 .../DesktopVRIK/IK/IKManager.cs | 0 .../DesktopVRIK/IK/MusclePoses.cs | 0 .../IK/VRIKHelpers/VRIKLocomotionData.cs | 0 .../DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs | 0 .../DesktopVRIK/Integrations/AMTAddon.cs | 0 .../DesktopVRIK/Integrations/BTKUIAddon.cs | 0 .../DesktopVRIK/Main.cs | 0 .../DesktopVRIK/ModSettings.cs | 0 .../DesktopVRIK/Properties/AssemblyInfo.cs | 0 .../DesktopVRIK/README.md | 0 .../DesktopVRIK/format.json | 0 .../DesktopVRSwitch/DesktopVRSwitch.csproj | 0 .../DesktopVRSwitch/HarmonyPatches.cs | 0 .../Integrations/BTKUIAddon.cs | 0 .../DesktopVRSwitch/Main.cs | 0 .../DesktopVRSwitch/ModSettings.cs | 0 .../DestroySteamVRInstancesImmediate.cs | 0 .../Patches/ReferenceCameraPatch.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../DesktopVRSwitch/README.md | 0 .../DesktopVRSwitch/Utils.cs | 0 .../DesktopVRSwitch/VRModeSwitchDebugger.cs | 0 .../DesktopVRSwitch/VRModeSwitchManager.cs | 0 .../CVRGestureRecognizerTracker.cs | 0 .../VRModeTrackers/CVRInputManagerTracker.cs | 0 .../VRModeTrackers/CVRPickupObjectTracker.cs | 0 .../VRModeTrackers/CVRWorldTracker.cs | 0 .../CVR_InteractableManagerTracker.cs | 0 .../VRModeTrackers/CVR_MenuManagerTracker.cs | 0 .../CameraFacingObjectTracker.cs | 0 .../VRModeTrackers/CheckVRTracker.cs | 0 .../VRModeTrackers/CohtmlHudTracker.cs | 0 .../VRModeTrackers/HudOperationsTracker.cs | 0 .../VRModeTrackers/IKSystemTracker.cs | 0 .../VRModeTrackers/MetaPortTracker.cs | 0 .../VRModeTrackers/MovementSystemTracker.cs | 0 .../VRModeTrackers/PlayerSetupTracker.cs | 0 .../VRModeTrackers/PortableCameraTracker.cs | 0 .../VRModeTrackers/VRModeTracker.cs | 0 .../VRModeTrackers/ViewManagerTracker.cs | 0 .../DesktopVRSwitch/XRHandler.cs | 0 .../DesktopVRSwitch/format.json | 0 .../DropPropTweak}/DropPropTweak.csproj | 0 .../DropPropTweak}/Main.cs | 2 - .../DropPropTweak}/Properties/AssemblyInfo.cs | 0 .../DropPropTweak}/README.md | 0 .../DropPropTweak}/format.json | 0 .../EzCurls/EzCurls.csproj | 0 .../InputModules/InputModuleCurlAdjuster.cs | 0 .../EzCurls/Main.cs | 0 .../EzCurls/ModSettings.cs | 0 .../EzCurls/Properties/AssemblyInfo.cs | 0 .../EzCurls/README.md | 0 .../EzCurls/format.json | 0 .../EzGrab/EzGrab.csproj | 0 .../EzGrab/Main.cs | 0 .../EzGrab/Properties/AssemblyInfo.cs | 0 .../EzGrab/format.json | 0 .../FOVAdjustment}/FOVAdjustment.csproj | 0 .../FOVAdjustment}/Main.cs | 6 +- .../FOVAdjustment}/Properties/AssemblyInfo.cs | 0 .../FOVAdjustment}/README.md | 0 .../FOVAdjustment}/format.json | 0 .../FuckCohtmlResourceHandler.csproj | 0 .../HarmonyPatches.cs | 0 .../FuckCohtmlResourceHandler/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../FuckCohtmlResourceHandler/README.md | 0 .../FuckCohtmlResourceHandler/format.json | 0 .../FuckMLA/FuckMLA.csproj | 0 .../FuckMLA/HarmonyPatches.cs | 0 .../FuckMLA/Main.cs | 0 .../FuckMLA/Properties/AssemblyInfo.cs | 0 .../FuckMLA/format.json | 0 .../FuckMagicaCloth2/FuckMagicaCloth2.csproj | 0 .Deprecated/FuckMagicaCloth2/Main.cs | 49 ++ .../Properties/AssemblyInfo.cs | 30 + .../FuckMagicaCloth2}/README.md | 0 .Deprecated/FuckMagicaCloth2/format.json | 24 + .../FuckMetrics/FuckMetrics.csproj | 0 .../FuckMetrics/HarmonyPatches.cs | 0 .../FuckMetrics/Main.cs | 0 .../FuckMetrics/ManagedLibs/.keep | 0 .../FuckMetrics/Properties/AssemblyInfo.cs | 0 .../FuckMetrics/format.json | 0 .../FuckOffUICamera/CohtmlRenderForwarder.cs | 35 ++ .../FuckOffUICamera/CommandBufferManager.cs | 144 +++++ .../FuckOffUICamera/FuckOffUICamera.csproj | 0 .Deprecated/FuckOffUICamera/Main.cs | 73 +++ .../Properties/AssemblyInfo.cs | 18 +- .Deprecated/FuckOffUICamera/README.md | 14 + .Deprecated/FuckOffUICamera/format.json | 23 + .../FuckVivox/FuckVivox.csproj | 0 .../FuckVivox/HarmonyPatches.cs | 0 .../FuckVivox/Main.cs | 0 .../FuckVivox/Properties/AssemblyInfo.cs | 0 .../FuckVivox/VivoxHelpers.cs | 0 .../FuckVivox/WindowFocusManager.cs | 0 .../FuckVivox/format.json | 0 .../GestureLock}/GestureLock.csproj | 0 .../GestureLock}/HarmonyPatches.cs | 0 .../GestureLock}/Main.cs | 0 .../GestureLock}/Properties/AssemblyInfo.cs | 0 .../GestureLock}/README.md | 0 .../GestureLock}/format.json | 0 .../HeadBobbingFix/HarmonyPatches.cs | 0 .../HeadBobbingFix/HeadBobbingFix.csproj | 0 .../HeadBobbingFix/Main.cs | 0 .../HeadBobbingFix/Properties/AssemblyInfo.cs | 0 .../HeadBobbingFix/README.md | 0 .../HeadBobbingFix/format.json | 0 .../HeadLookLockingInputFix.csproj | 0 .../HeadLookLockingInputFix/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../HeadLookLockingInputFix/README.md | 0 .../HeadLookLockingInputFix/format.json | 0 .../IKAdjustments/HarmonyPatches.cs | 0 .../IKAdjustments/IKAdjuster.cs | 0 .../IKAdjustments/IKAdjustments.csproj | 0 .../IKAdjustments/Integrations/BTKUIAddon.cs | 0 .../IKAdjustments/Main.cs | 0 .../IKAdjustments/Properties/AssemblyInfo.cs | 0 .../IKFixes/HarmonyPatches.cs | 0 .../IKFixes/IKFixes.csproj | 0 .../IKFixes/Integrations/UIExKitAddon.cs | 0 .../IKFixes/Main.cs | 0 .../IKFixes/Properties/AssemblyInfo.cs | 0 .../IKFixes/README.md | 0 .../IKFixes/format.json | 0 .../IKSimulatedRootAngleFix.csproj | 0 .../IKSimulatedRootAngleFix}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .Deprecated/IKSimulatedRootAngleFix/README.md | 14 + .../IKSimulatedRootAngleFix}/format.json | 0 .../InteractionTest/AutoArmIK.cs | 0 .../ColliderTest/AvatarColliderStruct.cs | 0 .../ColliderTest/AvatarColliders.cs | 0 .../ColliderTest/LineColliderTest.cs | 0 .../InteractionTest/GrabbableAvatar.cs | 0 .../InteractionTest/GrabbingAvatar.cs | 0 .../InteractionTest/HarmonyPatches.cs | 0 .../InteractionTest/InteractionTest.csproj | 0 .../InteractionTest/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../InteractionTest/README.md | 0 .../InteractionTest}/format.json | 0 .../JumpPatch/HarmonyPatches.cs | 0 .../JumpPatch/JumpPatch.csproj | 0 .../JumpPatch/Main.cs | 0 .../JumpPatch/Properties/AssemblyInfo.cs | 0 .../JumpPatch/format.json | 0 .../LateInitComponentHelperHack.csproj | 0 .../LateInitComponentHelperHack/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../LateInitComponentHelperHack/format.json | 0 .../Components/CameraCallbackLogger.cs | 0 .../Components/FaceMirror.cs | 100 +++ .../Components/FakeMultiPassHack.cs | 0 .../Integrations/BtkUiAddon.cs | 0 .../LegacyContentMitigation.csproj | 0 .../LegacyContentMitigation}/Main.cs | 0 .../LegacyContentMitigation/ModSettings.cs | 52 ++ .../LegacyContentMitigation}/Patches.cs | 4 + .../Properties/AssemblyInfo.cs | 0 .../LegacyContentMitigation}/README.md | 0 .../LegacyContentMitigation}/format.json | 4 +- .../MenuScalePatch/HarmonyPatches.cs | 0 .../MenuScalePatch/Helpers/MainMenuHelper.cs | 0 .../MenuScalePatch/Helpers/QuickMenuHelper.cs | 0 .../MenuScalePatch/MSP_Menus.cs | 0 .../MenuScalePatch/Main.cs | 0 .../MenuScalePatch/MenuScalePatch.csproj | 0 .../MenuScalePatch/Properties/AssemblyInfo.cs | 0 .../MenuScalePatch/README.md | 0 .../MenuScalePatch/format.json | 0 .../MoreMenuOptions/Main.cs | 0 .../MoreMenuOptions/ModSettings.cs | 0 .../MoreMenuOptions/MoreMenuOptions.csproj | 0 .../Properties/AssemblyInfo.cs | 0 .../MoreMenuOptions/README.md | 0 .../MoreMenuOptions/format.json | 0 .../Components/NAKPointerTracker.cs | 0 .../CustomComponents.csproj | 0 .../NAK.CustomComponents/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../NAK.CustomComponents/format.json | 0 {Nevermind => .Deprecated/Nevermind}/Main.cs | 0 .../Nevermind}/Nevermind.csproj | 0 .../Nevermind}/Properties/AssemblyInfo.cs | 0 .../Nevermind}/README.md | 0 .../Nevermind}/format.json | 0 .../NoDepthOnlyFlat/Main.cs | 0 .../NoDepthOnlyFlat/NoDepthOnlyFlat.csproj | 0 .../Properties/AssemblyInfo.cs | 0 .../NoDepthOnlyFlat/README.md | 0 .../NoDepthOnlyFlat/format.json | 0 .../PickupPushPull/HarmonyPatches.cs | 0 .../InputModules/PickupPushPull_Module.cs | 0 .../PickupPushPull/Main.cs | 0 .../PickupPushPull/PickupPushPull.csproj | 0 .../PickupPushPull/Properties/AssemblyInfo.cs | 0 .../PickupPushPull/format.json | 0 .../PlaySpaceScaleFix/HarmonyPatches.cs | 0 .../PlaySpaceScaleFix/Main.cs | 0 .../PlaySpaceScaleFix.csproj | 0 .../Properties/AssemblyInfo.cs | 0 .../PlaySpaceScaleFix/README.md | 0 .../PlaySpaceScaleFix/format.json | 0 .../ReconnectionSystemFix}/Main.cs | 0 .../ReconnectionSystemFix}/Patches.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ReconnectionSystemFix}/README.md | 0 .../ReconnectionSystemFix.csproj | 6 + .../ReconnectionSystemFix}/format.json | 0 .../Main.cs | 43 ++ .../Properties/AssemblyInfo.cs | 30 + .../README.md | 14 + ...vatarDisablingCameraOnFirstFrameFix.csproj | 6 + .../format.json | 24 + .../SearchWithSpacesFix}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .Deprecated/SearchWithSpacesFix/README.md | 14 + .../SearchWithSpacesFix.csproj | 2 + .Deprecated/SearchWithSpacesFix/format.json | 23 + .../ShadowCloneFallback}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ShadowCloneFallback}/README.md | 0 .../ShadowCloneFallback.csproj | 0 .../ShadowCloneFallback}/format.json | 0 .../SmartReticle}/Main.cs | 0 .../SmartReticle}/Properties/AssemblyInfo.cs | 0 .../SmartReticle}/README.md | 0 .Deprecated/SmartReticle/SmartReticle.csproj | 6 + .../SmartReticle}/format.json | 0 .../StopClosingMyMenuOnWorldLoad}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../StopClosingMyMenuOnWorldLoad}/README.md | 0 .../StopClosingMyMenuOnWorldLoad.csproj | 0 .../StopClosingMyMenuOnWorldLoad}/format.json | 0 .../Interaction/CVRPlayerHand.cs | 42 ++ .../CVRPlayerInteractionManager.cs | 214 +++++++ .../Components/CVRCanvasWrapper.cs | 121 ++++ .../RaycastImpl/CVRPlayerRaycaster.cs | 385 ++++++++++++ .../RaycastImpl/CVRPlayerRaycasterMouse.cs | 13 + .../CVRPlayerRaycasterTransform.cs | 10 + .../RaycastImpl/CVRRaycastResult.cs | 38 ++ .../Interaction/RaycastImpl/RaycastDebug.cs | 312 ++++++++++ .Deprecated/SuperAwesomeMod/Main.cs | 81 +++ .../Properties/AssemblyInfo.cs | 32 + .Deprecated/SuperAwesomeMod/README.md | 54 ++ .../SuperAwesomeMod/SuperAwesomeMod.csproj | 9 +- .Deprecated/SuperAwesomeMod/format.json | 24 + .../SwitchToDesktopOnSteamVRExit/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../SwitchToDesktopOnSteamVRExit/README.md | 0 .../SwitchToDesktopOnSteamVRExit.csproj | 0 .../SwitchToDesktopOnSteamVRExit/format.json | 0 .../TrackedControllerFix/HarmonyPatches.cs | 0 .../TrackedControllerFix/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../TrackedControllerFix/README.md | 0 .../TrackedControllerFix.csproj | 0 .../TrackedControllerFixer.cs | 0 .../TrackedControllerFix/format.json | 0 .../TrackedPointFix/HarmonyPatches.cs | 0 .../TrackedPointFix/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../TrackedPointFix/README.md | 0 .../TrackedPointFix/TrackedPointFix.csproj | 0 .../TrackedPointFix/format.json | 0 .../VisualCloneFix}/Main.cs | 0 .Deprecated/VisualCloneFix/Patches.cs | 157 +++++ .../Properties/AssemblyInfo.cs | 0 .../VisualCloneFix}/README.md | 0 .../VisualCloneFix}/VisualCloneFix.csproj | 0 .../VisualCloneFix}/format.json | 0 .Deprecated/WhereAmIPointing/Main.cs | 106 ++++ .../Properties/AssemblyInfo.cs | 32 + .Deprecated/WhereAmIPointing/README.md | 14 + .../WhereAmIPointing/WhereAmIPointing.csproj | 6 + .Deprecated/WhereAmIPointing/format.json | 23 + .gitignore | 3 + ASTExtension/Integrations/BTKUI/BtkUiAddon.cs | 63 +- .../AvatarClone/AvatarClone.API.cs | 68 --- .../AvatarClone/AvatarClone.Clones.cs | 103 ---- .../AvatarClone/AvatarClone.Exclusion.cs | 141 ----- .../AvatarClone/AvatarClone.Exclusions.cs | 229 +++++++ .../AvatarClone/AvatarClone.Fields.cs | 67 -- .../AvatarClone/AvatarClone.Init.cs | 360 ++++++++--- .../AvatarClone/AvatarClone.MagicaSupport.cs | 34 -- .../AvatarClone/AvatarClone.RenderState.cs | 212 +++++++ .../AvatarClone/AvatarClone.StateSync.cs | 156 +++++ .../AvatarClone/AvatarClone.Update.cs | 181 ------ .../AvatarClone/AvatarClone.Util.cs | 75 ++- AvatarCloneTest/AvatarClone/AvatarClone.cs | 233 +++---- .../FPRExclusion/AvatarCloneExclusion.cs | 15 +- AvatarCloneTest/Main.cs | 31 +- AvatarCloneTest/Patches.cs | 69 +-- AvatarCloneTest/Properties/AssemblyInfo.cs | 2 +- .../Integrations/BTKUI/BtkUiAddon_Utils.cs | 78 --- .../BetterContentLoading.csproj | 15 + .../BetterDownloadManager.cs | 210 +++++++ .../BetterContentLoading/DownloadInfo.cs | 63 ++ .../BetterContentLoading/DownloadProcessor.cs | 152 +++++ .../DownloadQueue/AvatarDownloadQueue.cs | 83 +++ .../DownloadQueue/ContentDownloadQueueBase.cs | 177 ++++++ .../DownloadQueue/PropDownloadQueue.cs | 81 +++ .../DownloadQueue/WorldDownloadQueue.cs | 66 ++ .../BetterContentLoading/DownloadState.cs | 26 + .../Util/ThreadingHelper.cs | 109 ++++ .../DownloadManager/DownloadManager.Core.cs | 237 ++++++++ .../DownloadManager.Helpers.cs | 58 ++ .../DownloadManager.Priority.cs | 38 ++ .../DownloadManager/DownloadTask.Main.cs | 33 + .../DownloadManager/DownloadTask.Priority.cs | 6 + .../ConcurrentPriorityQueue.cs | 43 ++ .../DownloadManager.Bandwidth.cs | 24 + .../DownloadManager2/DownloadManager.Core.cs | 126 ++++ .../DownloadManager.Helpers.cs | 58 ++ .../DownloadManager.Priority.cs | 47 ++ .../DownloadManager.Processing.cs | 144 +++++ .../DownloadManager2/DownloadManager.Queue.cs | 80 +++ .../DownloadManager2/DownloadTask2.cs | 62 ++ .../Main.cs | 17 +- BetterContentLoading/ModSettings.cs | 31 + BetterContentLoading/Patches.cs | 71 +++ .../Properties/AssemblyInfo.cs | 33 + BetterContentLoading/README.md | 14 + BetterContentLoading/format.json | 23 + CVRGizmos/Popcron.Gizmos/Constants.cs | 8 - .../Popcron.Gizmos/Drawers/LineDrawer.cs | 19 - CVRGizmos/Popcron.Gizmos/Element.cs | 12 - .../CVRLuaClientBehaviourExtensions.cs | 6 +- CustomRichPresence/CustomRichPresence.csproj | 11 + CustomRichPresence/Main.cs | 123 ++++ .../Properties/AssemblyInfo.cs | 18 +- CustomRichPresence/README.md | 14 + CustomRichPresence/format.json | 23 + CustomSpawnPoint/SpawnPointManager.cs | 127 ++-- .../Components/InteractionTracker.cs | 213 ------- InteractionTest/ModSettings.cs | 7 - InteractionTest/Patches.cs | 17 - InteractionTest/README.md | 52 -- InteractionTest/format.json | 23 - LegacyContentMitigation/ModSettings.cs | 24 - .../SyncedBehaviour/PickupableBehaviour.cs | 161 ----- .../SyncedBehaviour/PickupableObject.cs | 72 --- LuaTTS/Main.cs | 31 +- LuaTTS/Patches.cs | 10 +- NAK_CVR_Mods.sln | 84 +++ .../BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs | 67 +- OriginShift/Integrations/BTKUI/BtkuiAddon.cs | 43 +- .../Integrations/BTKUI/BtkuiAddon_Utils.cs | 41 +- .../Components/OriginShiftController.cs | 75 ++- .../Receivers/OriginShiftEventReceiver.cs | 62 +- .../OriginShiftParticleSystemReceiver.cs | 41 +- .../Receivers/OriginShiftRigidbodyReceiver.cs | 33 +- .../OriginShiftTrailRendererReceiver.cs | 41 +- .../Receivers/OriginShiftTransformReceiver.cs | 27 +- .../OriginShiftOcclusionCullingDisabler.cs | 39 +- .../OriginShift/Player/OriginShiftMonitor.cs | 51 +- OriginShift/Patches.cs | 1 + PhysicsGunMod/Components/ObjectSyncBridge.cs | 92 --- .../PhysicsGunInteractionBehavior.cs | 572 ------------------ PhysicsGunMod/HarmonyPatches.cs | 20 - PhysicsGunMod/Main.cs | 43 -- PhysicsGunMod/ModSettings.cs | 12 - PhysicsGunMod/PhysicsGunMod.csproj | 37 -- PhysicsGunMod/README.md | 28 - PhysicsGunMod/format.json | 23 - PlayerColorsAPI/Main.cs | 44 ++ .../Patches.cs | 0 PlayerColorsAPI/PlayerColorsAPI.csproj | 6 + PlayerColorsAPI/Properties/AssemblyInfo.cs | 32 + PlayerColorsAPI/README.md | 18 + PlayerColorsAPI/format.json | 23 + Portals/format.json | 1 - References.Items.props | 28 +- SmootherRay/Main.cs | 4 + SmootherRay/Properties/AssemblyInfo.cs | 2 +- SmootherRay/SmootherRayer.cs | 4 + .../Stickers/Networking/ModNetwork.Inbound.cs | 9 +- .../Stickers/StickerSystem.PlayerCallbacks.cs | 4 +- .../StickerSystem.StickerLifecycle.cs | 2 +- ThirdPerson/CameraLogic.cs | 4 +- ThirdPerson/Patches.cs | 5 - ThirdPerson/Properties/AssemblyInfo.cs | 2 +- 539 files changed, 7475 insertions(+), 3120 deletions(-) rename {.DepricatedMods => .Deprecated}/AASBufferFix/AASBufferFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/AASBufferHelper.cs (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/Utils.cs (100%) rename {.DepricatedMods => .Deprecated}/AASBufferFix/format.json (100%) rename {AASDefaultProfileFix => .Deprecated/AASDefaultProfileFix}/AASDefaultProfileFix.csproj (100%) rename {AASDefaultProfileFix => .Deprecated/AASDefaultProfileFix}/Main.cs (100%) rename {AASDefaultProfileFix => .Deprecated/AASDefaultProfileFix}/Properties/AssemblyInfo.cs (100%) rename {AASDefaultProfileFix => .Deprecated/AASDefaultProfileFix}/README.md (100%) rename {AASDefaultProfileFix => .Deprecated/AASDefaultProfileFix}/format.json (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/AlternateIKSystem.csproj (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/BodyControl.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/IKCalibrator.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/IKHandlers/IKHandler.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/IKManager.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/MusclePoses.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/Tracking/SteamVRTrackerManager.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/VRIKHelpers/VRIKLocomotionData.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/WeightManipulators/BodyParts/BodyPart.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/WeightManipulators/DeviceControlManipulator.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/WeightManipulators/Interface/IWeightManipulator.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/WeightManipulators/TrackingControlManipulator.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/IK/WeightManipulators/WeightManipulatorManager.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/Integrations/BTKUIAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/LICENSE.txt (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/ModSettings.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/README.md (100%) rename {.DepricatedMods => .Deprecated}/AlternateIKSystem/format.json (100%) rename {WhereAmIPointing => .Deprecated/AutoSyncTransforms}/Main.cs (100%) rename {WhereAmIPointing => .Deprecated/AutoSyncTransforms}/Properties/AssemblyInfo.cs (100%) rename {WhereAmIPointing => .Deprecated/AutoSyncTransforms}/README.md (100%) rename {WhereAmIPointing => .Deprecated/AutoSyncTransforms}/WhereAmIPointing.csproj (100%) rename {WhereAmIPointing => .Deprecated/AutoSyncTransforms}/format.json (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaleMod.csproj (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/AvatarScaleManager.cs (99%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/Components/BaseScaler.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/Components/LocalScaler.cs (94%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/Components/NetworkScaler.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/Events/AvatarScaleEvents.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/AvatarScaling/ScaledComponents.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/HarmonyPatches.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Input/DebugKeybinds.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Input/ScaleReconizer.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon.cs (71%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs (76%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs (83%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs (71%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs (83%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs (76%) create mode 100644 .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Main.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/ModSettings.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Networking/ModNetwork.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Properties/AssemblyInfo.cs (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/README.md (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/Scripts.cs (83%) rename {.DepricatedMods/InteractionTest => .Deprecated/AvatarScaleMod}/format.json (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/resources/ASM_Icon_AvatarHeightConfig.png (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/resources/ASM_Icon_AvatarHeightCopy.png (100%) rename {AvatarScaleMod => .Deprecated/AvatarScaleMod}/resources/menu.js (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/BadAnimatorFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/BadAnimatorFixManager.cs (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/BadAnimatorFixer.cs (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/BadAnimatorFix/format.json (100%) rename {.DepricatedMods => .Deprecated}/Blackout/AssetHandler.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Blackout.csproj (100%) rename {.DepricatedMods => .Deprecated}/Blackout/BlackoutController.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Integrations/BTKUIAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Integrations/UIExpansionKitAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Resource1.Designer.cs (100%) rename {.DepricatedMods => .Deprecated}/Blackout/Resource1.resx (100%) rename {.DepricatedMods => .Deprecated}/Blackout/format.json (100%) rename {.DepricatedMods => .Deprecated}/Blackout/resources/blackout_controller.asset (100%) rename {BullshitWatcher => .Deprecated/BullshitWatcher}/BullshitWatcher.csproj (100%) rename {BullshitWatcher => .Deprecated/BullshitWatcher}/Main.cs (100%) rename {BullshitWatcher => .Deprecated/BullshitWatcher}/Properties/AssemblyInfo.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/CVRGizmoManager.cs (76%) rename {CVRGizmos => .Deprecated/CVRGizmos}/CVRGizmos.csproj (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRAdvancedAvatarSettingsPointer.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRAdvancedAvatarSettingsTrigger.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRAvatar.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRAvatarPickupMarker.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRDistanceConstrain.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRDistanceLod.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRGizmoBase.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRHapticAreaChest.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRHapticZone.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRPointer.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRSpawnableTrigger.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/CVRToggleStateTrigger.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/Unity_BoxCollider.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/Unity_CapsuleCollider.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/GizmoTypes/Unity_SphereCollider.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Main.cs (100%) create mode 100644 .Deprecated/CVRGizmos/Popcron.Gizmos/Constants.cs rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/Drawer.cs (86%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/Drawers/CubeDrawer.cs (93%) create mode 100644 .Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/Drawers/PolygonDrawer.cs (84%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/Drawers/SquareDrawer.cs (89%) create mode 100644 .Deprecated/CVRGizmos/Popcron.Gizmos/Element.cs rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/Gizmos.cs (52%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/GizmosInstance.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Popcron.Gizmos/LICENSE.md (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/Properties/AssemblyInfo.cs (100%) rename {CVRGizmos => .Deprecated/CVRGizmos}/format.json (100%) rename SmartReticle/SmartReticle.csproj => .Deprecated/CameraExperiments/CameraExperiments.csproj (100%) create mode 100644 .Deprecated/CameraExperiments/Main.cs create mode 100644 .Deprecated/CameraExperiments/Properties/AssemblyInfo.cs create mode 100644 .Deprecated/CameraExperiments/README.md create mode 100644 .Deprecated/CameraExperiments/format.json rename {.DepricatedMods => .Deprecated}/CameraFixes/CameraFixes.csproj (100%) rename {.DepricatedMods => .Deprecated}/CameraFixes/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/CameraFixes/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/CameraFixes/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/CameraFixes/format.json (100%) rename {.DepricatedMods => .Deprecated}/ClearHudNotifications/ClearHudNotifications.csproj (100%) rename {.DepricatedMods => .Deprecated}/ClearHudNotifications/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/ClearHudNotifications/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/ClearHudNotifications/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/ClearHudNotifications/format.json (100%) create mode 100644 .Deprecated/ControlToUnlockMouse/ControlToUnlockMouse.csproj create mode 100644 .Deprecated/ControlToUnlockMouse/Main.cs create mode 100644 .Deprecated/ControlToUnlockMouse/Properties/AssemblyInfo.cs rename {SearchWithSpacesFix => .Deprecated/ControlToUnlockMouse}/README.md (100%) rename {SearchWithSpacesFix => .Deprecated/ControlToUnlockMouse}/format.json (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/ControllerFreeze.csproj (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/README.md (100%) rename {.DepricatedMods => .Deprecated}/ControllerFreeze/format.json (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/DesktopVRIK.csproj (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/IKCalibrator.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/IKHandlers/IKHandler.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/IKManager.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/MusclePoses.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/Integrations/AMTAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/Integrations/BTKUIAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/ModSettings.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/README.md (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRIK/format.json (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/DesktopVRSwitch.csproj (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Integrations/BTKUIAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/ModSettings.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Patches/DestroySteamVRInstancesImmediate.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Patches/ReferenceCameraPatch.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/README.md (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/Utils.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeSwitchDebugger.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeSwitchManager.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVRGestureRecognizerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVRInputManagerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVRPickupObjectTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVRWorldTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVR_InteractableManagerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CVR_MenuManagerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CameraFacingObjectTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CheckVRTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/CohtmlHudTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/HudOperationsTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/IKSystemTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/MetaPortTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/MovementSystemTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/PlayerSetupTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/PortableCameraTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/VRModeTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/VRModeTrackers/ViewManagerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/XRHandler.cs (100%) rename {.DepricatedMods => .Deprecated}/DesktopVRSwitch/format.json (100%) rename {DropPropTweak => .Deprecated/DropPropTweak}/DropPropTweak.csproj (100%) rename {DropPropTweak => .Deprecated/DropPropTweak}/Main.cs (99%) rename {DropPropTweak => .Deprecated/DropPropTweak}/Properties/AssemblyInfo.cs (100%) rename {DropPropTweak => .Deprecated/DropPropTweak}/README.md (100%) rename {DropPropTweak => .Deprecated/DropPropTweak}/format.json (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/EzCurls.csproj (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/InputModules/InputModuleCurlAdjuster.cs (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/ModSettings.cs (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/README.md (100%) rename {.DepricatedMods => .Deprecated}/EzCurls/format.json (100%) rename {.DepricatedMods => .Deprecated}/EzGrab/EzGrab.csproj (100%) rename {.DepricatedMods => .Deprecated}/EzGrab/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/EzGrab/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/EzGrab/format.json (100%) rename {FOVAdjustment => .Deprecated/FOVAdjustment}/FOVAdjustment.csproj (100%) rename {FOVAdjustment => .Deprecated/FOVAdjustment}/Main.cs (94%) rename {FOVAdjustment => .Deprecated/FOVAdjustment}/Properties/AssemblyInfo.cs (100%) rename {FOVAdjustment => .Deprecated/FOVAdjustment}/README.md (100%) rename {FOVAdjustment => .Deprecated/FOVAdjustment}/format.json (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/FuckCohtmlResourceHandler.csproj (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/README.md (100%) rename {.DepricatedMods => .Deprecated}/FuckCohtmlResourceHandler/format.json (100%) rename {.DepricatedMods => .Deprecated}/FuckMLA/FuckMLA.csproj (100%) rename {.DepricatedMods => .Deprecated}/FuckMLA/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMLA/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMLA/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMLA/format.json (100%) rename .DepricatedMods/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj => .Deprecated/FuckMagicaCloth2/FuckMagicaCloth2.csproj (100%) create mode 100644 .Deprecated/FuckMagicaCloth2/Main.cs create mode 100644 .Deprecated/FuckMagicaCloth2/Properties/AssemblyInfo.cs rename {IKSimulatedRootAngleFix => .Deprecated/FuckMagicaCloth2}/README.md (100%) create mode 100644 .Deprecated/FuckMagicaCloth2/format.json rename {.DepricatedMods => .Deprecated}/FuckMetrics/FuckMetrics.csproj (100%) rename {.DepricatedMods => .Deprecated}/FuckMetrics/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMetrics/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMetrics/ManagedLibs/.keep (100%) rename {.DepricatedMods => .Deprecated}/FuckMetrics/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckMetrics/format.json (100%) create mode 100644 .Deprecated/FuckOffUICamera/CohtmlRenderForwarder.cs create mode 100644 .Deprecated/FuckOffUICamera/CommandBufferManager.cs rename .DepricatedMods/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj => .Deprecated/FuckOffUICamera/FuckOffUICamera.csproj (100%) create mode 100644 .Deprecated/FuckOffUICamera/Main.cs rename {PhysicsGunMod => .Deprecated/FuckOffUICamera}/Properties/AssemblyInfo.cs (67%) create mode 100644 .Deprecated/FuckOffUICamera/README.md create mode 100644 .Deprecated/FuckOffUICamera/format.json rename {.DepricatedMods => .Deprecated}/FuckVivox/FuckVivox.csproj (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/VivoxHelpers.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/WindowFocusManager.cs (100%) rename {.DepricatedMods => .Deprecated}/FuckVivox/format.json (100%) rename {GestureLock => .Deprecated/GestureLock}/GestureLock.csproj (100%) rename {GestureLock => .Deprecated/GestureLock}/HarmonyPatches.cs (100%) rename {GestureLock => .Deprecated/GestureLock}/Main.cs (100%) rename {GestureLock => .Deprecated/GestureLock}/Properties/AssemblyInfo.cs (100%) rename {GestureLock => .Deprecated/GestureLock}/README.md (100%) rename {GestureLock => .Deprecated/GestureLock}/format.json (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/HeadBobbingFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/HeadBobbingFix/format.json (100%) rename ReconnectionSystemFix/ReconnectionSystemFix.csproj => .Deprecated/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/HeadLookLockingInputFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/HeadLookLockingInputFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/HeadLookLockingInputFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/HeadLookLockingInputFix/format.json (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/IKAdjuster.cs (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/IKAdjustments.csproj (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/Integrations/BTKUIAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/IKAdjustments/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/IKFixes.csproj (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/Integrations/UIExKitAddon.cs (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/README.md (100%) rename {.DepricatedMods => .Deprecated}/IKFixes/format.json (100%) rename {IKSimulatedRootAngleFix => .Deprecated/IKSimulatedRootAngleFix}/IKSimulatedRootAngleFix.csproj (100%) rename {IKSimulatedRootAngleFix => .Deprecated/IKSimulatedRootAngleFix}/Main.cs (100%) rename {IKSimulatedRootAngleFix => .Deprecated/IKSimulatedRootAngleFix}/Properties/AssemblyInfo.cs (100%) create mode 100644 .Deprecated/IKSimulatedRootAngleFix/README.md rename {IKSimulatedRootAngleFix => .Deprecated/IKSimulatedRootAngleFix}/format.json (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/AutoArmIK.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/ColliderTest/AvatarColliderStruct.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/ColliderTest/AvatarColliders.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/ColliderTest/LineColliderTest.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/GrabbableAvatar.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/GrabbingAvatar.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/InteractionTest.csproj (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/InteractionTest/README.md (100%) rename {AvatarScaleMod => .Deprecated/InteractionTest}/format.json (100%) rename {.DepricatedMods => .Deprecated}/JumpPatch/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/JumpPatch/JumpPatch.csproj (100%) rename {.DepricatedMods => .Deprecated}/JumpPatch/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/JumpPatch/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/JumpPatch/format.json (100%) rename {.DepricatedMods => .Deprecated}/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj (100%) rename {.DepricatedMods => .Deprecated}/LateInitComponentHelperHack/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/LateInitComponentHelperHack/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/LateInitComponentHelperHack/format.json (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Components/CameraCallbackLogger.cs (100%) create mode 100644 .Deprecated/LegacyContentMitigation/Components/FaceMirror.cs rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Components/FakeMultiPassHack.cs (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Integrations/BtkUiAddon.cs (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/LegacyContentMitigation.csproj (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Main.cs (100%) create mode 100644 .Deprecated/LegacyContentMitigation/ModSettings.cs rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Patches.cs (97%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/Properties/AssemblyInfo.cs (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/README.md (100%) rename {LegacyContentMitigation => .Deprecated/LegacyContentMitigation}/format.json (96%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/Helpers/MainMenuHelper.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/Helpers/QuickMenuHelper.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/MSP_Menus.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/MenuScalePatch.csproj (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/README.md (100%) rename {.DepricatedMods => .Deprecated}/MenuScalePatch/format.json (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/ModSettings.cs (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/MoreMenuOptions.csproj (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/README.md (100%) rename {.DepricatedMods => .Deprecated}/MoreMenuOptions/format.json (100%) rename {.DepricatedMods => .Deprecated}/NAK.CustomComponents/Components/NAKPointerTracker.cs (100%) rename {.DepricatedMods => .Deprecated}/NAK.CustomComponents/CustomComponents.csproj (100%) rename {.DepricatedMods => .Deprecated}/NAK.CustomComponents/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/NAK.CustomComponents/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/NAK.CustomComponents/format.json (100%) rename {Nevermind => .Deprecated/Nevermind}/Main.cs (100%) rename {Nevermind => .Deprecated/Nevermind}/Nevermind.csproj (100%) rename {Nevermind => .Deprecated/Nevermind}/Properties/AssemblyInfo.cs (100%) rename {Nevermind => .Deprecated/Nevermind}/README.md (100%) rename {Nevermind => .Deprecated/Nevermind}/format.json (100%) rename {.DepricatedMods => .Deprecated}/NoDepthOnlyFlat/Main.cs (100%) rename SearchWithSpacesFix/SearchWithSpacesFix.csproj => .Deprecated/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj (100%) rename {.DepricatedMods => .Deprecated}/NoDepthOnlyFlat/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/NoDepthOnlyFlat/README.md (100%) rename {.DepricatedMods => .Deprecated}/NoDepthOnlyFlat/format.json (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/InputModules/PickupPushPull_Module.cs (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/PickupPushPull.csproj (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/PickupPushPull/format.json (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/PlaySpaceScaleFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/PlaySpaceScaleFix/format.json (100%) rename {ReconnectionSystemFix => .Deprecated/ReconnectionSystemFix}/Main.cs (100%) rename {ReconnectionSystemFix => .Deprecated/ReconnectionSystemFix}/Patches.cs (100%) rename {ReconnectionSystemFix => .Deprecated/ReconnectionSystemFix}/Properties/AssemblyInfo.cs (100%) rename {ReconnectionSystemFix => .Deprecated/ReconnectionSystemFix}/README.md (100%) create mode 100644 .Deprecated/ReconnectionSystemFix/ReconnectionSystemFix.csproj rename {ReconnectionSystemFix => .Deprecated/ReconnectionSystemFix}/format.json (100%) create mode 100644 .Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Main.cs create mode 100644 .Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Properties/AssemblyInfo.cs create mode 100644 .Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/README.md create mode 100644 .Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/RemoteAvatarDisablingCameraOnFirstFrameFix.csproj create mode 100644 .Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/format.json rename {SearchWithSpacesFix => .Deprecated/SearchWithSpacesFix}/Main.cs (100%) rename {SearchWithSpacesFix => .Deprecated/SearchWithSpacesFix}/Properties/AssemblyInfo.cs (100%) create mode 100644 .Deprecated/SearchWithSpacesFix/README.md create mode 100644 .Deprecated/SearchWithSpacesFix/SearchWithSpacesFix.csproj create mode 100644 .Deprecated/SearchWithSpacesFix/format.json rename {ShadowCloneFallback => .Deprecated/ShadowCloneFallback}/Main.cs (100%) rename {ShadowCloneFallback => .Deprecated/ShadowCloneFallback}/Properties/AssemblyInfo.cs (100%) rename {ShadowCloneFallback => .Deprecated/ShadowCloneFallback}/README.md (100%) rename {ShadowCloneFallback => .Deprecated/ShadowCloneFallback}/ShadowCloneFallback.csproj (100%) rename {ShadowCloneFallback => .Deprecated/ShadowCloneFallback}/format.json (100%) rename {SmartReticle => .Deprecated/SmartReticle}/Main.cs (100%) rename {SmartReticle => .Deprecated/SmartReticle}/Properties/AssemblyInfo.cs (100%) rename {SmartReticle => .Deprecated/SmartReticle}/README.md (100%) create mode 100644 .Deprecated/SmartReticle/SmartReticle.csproj rename {SmartReticle => .Deprecated/SmartReticle}/format.json (100%) rename {StopClosingMyMenuOnWorldLoad => .Deprecated/StopClosingMyMenuOnWorldLoad}/Main.cs (100%) rename {StopClosingMyMenuOnWorldLoad => .Deprecated/StopClosingMyMenuOnWorldLoad}/Properties/AssemblyInfo.cs (100%) rename {StopClosingMyMenuOnWorldLoad => .Deprecated/StopClosingMyMenuOnWorldLoad}/README.md (100%) rename {StopClosingMyMenuOnWorldLoad => .Deprecated/StopClosingMyMenuOnWorldLoad}/StopClosingMyMenuOnWorldLoad.csproj (100%) rename {StopClosingMyMenuOnWorldLoad => .Deprecated/StopClosingMyMenuOnWorldLoad}/format.json (100%) create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/CVRPlayerInteractionManager.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/Components/CVRCanvasWrapper.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycaster.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterMouse.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterTransform.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRRaycastResult.cs create mode 100644 .Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/RaycastDebug.cs create mode 100644 .Deprecated/SuperAwesomeMod/Main.cs create mode 100644 .Deprecated/SuperAwesomeMod/Properties/AssemblyInfo.cs create mode 100644 .Deprecated/SuperAwesomeMod/README.md rename InteractionTest/InteractionTest.csproj => .Deprecated/SuperAwesomeMod/SuperAwesomeMod.csproj (52%) create mode 100644 .Deprecated/SuperAwesomeMod/format.json rename {.DepricatedMods => .Deprecated}/SwitchToDesktopOnSteamVRExit/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/SwitchToDesktopOnSteamVRExit/README.md (100%) rename {.DepricatedMods => .Deprecated}/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj (100%) rename {.DepricatedMods => .Deprecated}/SwitchToDesktopOnSteamVRExit/format.json (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/TrackedControllerFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/TrackedControllerFixer.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedControllerFix/format.json (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/HarmonyPatches.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/Main.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/Properties/AssemblyInfo.cs (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/README.md (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/TrackedPointFix.csproj (100%) rename {.DepricatedMods => .Deprecated}/TrackedPointFix/format.json (100%) rename {VisualCloneFix => .Deprecated/VisualCloneFix}/Main.cs (100%) create mode 100644 .Deprecated/VisualCloneFix/Patches.cs rename {VisualCloneFix => .Deprecated/VisualCloneFix}/Properties/AssemblyInfo.cs (100%) rename {VisualCloneFix => .Deprecated/VisualCloneFix}/README.md (100%) rename {VisualCloneFix => .Deprecated/VisualCloneFix}/VisualCloneFix.csproj (100%) rename {VisualCloneFix => .Deprecated/VisualCloneFix}/format.json (100%) create mode 100644 .Deprecated/WhereAmIPointing/Main.cs create mode 100644 .Deprecated/WhereAmIPointing/Properties/AssemblyInfo.cs create mode 100644 .Deprecated/WhereAmIPointing/README.md create mode 100644 .Deprecated/WhereAmIPointing/WhereAmIPointing.csproj create mode 100644 .Deprecated/WhereAmIPointing/format.json delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.API.cs delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs create mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs delete mode 100644 AvatarCloneTest/AvatarClone/AvatarClone.Update.cs delete mode 100644 AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs create mode 100644 BetterContentLoading/BetterContentLoading.csproj create mode 100644 BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadInfo.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadProcessor.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs create mode 100644 BetterContentLoading/BetterContentLoading/DownloadState.cs create mode 100644 BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs create mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Core.cs create mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs create mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Priority.cs create mode 100644 BetterContentLoading/DownloadManager/DownloadTask.Main.cs create mode 100644 BetterContentLoading/DownloadManager/DownloadTask.Priority.cs create mode 100644 BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Core.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs create mode 100644 BetterContentLoading/DownloadManager2/DownloadTask2.cs rename {InteractionTest => BetterContentLoading}/Main.cs (68%) create mode 100644 BetterContentLoading/ModSettings.cs create mode 100644 BetterContentLoading/Patches.cs create mode 100644 BetterContentLoading/Properties/AssemblyInfo.cs create mode 100644 BetterContentLoading/README.md create mode 100644 BetterContentLoading/format.json delete mode 100644 CVRGizmos/Popcron.Gizmos/Constants.cs delete mode 100644 CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs delete mode 100644 CVRGizmos/Popcron.Gizmos/Element.cs create mode 100644 CustomRichPresence/CustomRichPresence.csproj create mode 100644 CustomRichPresence/Main.cs rename {InteractionTest => CustomRichPresence}/Properties/AssemblyInfo.cs (65%) create mode 100644 CustomRichPresence/README.md create mode 100644 CustomRichPresence/format.json delete mode 100644 InteractionTest/Components/InteractionTracker.cs delete mode 100644 InteractionTest/ModSettings.cs delete mode 100644 InteractionTest/Patches.cs delete mode 100644 InteractionTest/README.md delete mode 100644 InteractionTest/format.json delete mode 100644 LegacyContentMitigation/ModSettings.cs delete mode 100644 LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs delete mode 100644 LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs delete mode 100644 PhysicsGunMod/Components/ObjectSyncBridge.cs delete mode 100644 PhysicsGunMod/Components/PhysicsGunInteractionBehavior.cs delete mode 100644 PhysicsGunMod/HarmonyPatches.cs delete mode 100644 PhysicsGunMod/Main.cs delete mode 100644 PhysicsGunMod/ModSettings.cs delete mode 100644 PhysicsGunMod/PhysicsGunMod.csproj delete mode 100644 PhysicsGunMod/README.md delete mode 100644 PhysicsGunMod/format.json create mode 100644 PlayerColorsAPI/Main.cs rename {VisualCloneFix => PlayerColorsAPI}/Patches.cs (100%) create mode 100644 PlayerColorsAPI/PlayerColorsAPI.csproj create mode 100644 PlayerColorsAPI/Properties/AssemblyInfo.cs create mode 100644 PlayerColorsAPI/README.md create mode 100644 PlayerColorsAPI/format.json delete mode 100644 Portals/format.json diff --git a/.DepricatedMods/AASBufferFix/AASBufferFix.csproj b/.Deprecated/AASBufferFix/AASBufferFix.csproj similarity index 100% rename from .DepricatedMods/AASBufferFix/AASBufferFix.csproj rename to .Deprecated/AASBufferFix/AASBufferFix.csproj diff --git a/.DepricatedMods/AASBufferFix/AASBufferHelper.cs b/.Deprecated/AASBufferFix/AASBufferHelper.cs similarity index 100% rename from .DepricatedMods/AASBufferFix/AASBufferHelper.cs rename to .Deprecated/AASBufferFix/AASBufferHelper.cs diff --git a/.DepricatedMods/AASBufferFix/HarmonyPatches.cs b/.Deprecated/AASBufferFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/AASBufferFix/HarmonyPatches.cs rename to .Deprecated/AASBufferFix/HarmonyPatches.cs diff --git a/.DepricatedMods/AASBufferFix/Main.cs b/.Deprecated/AASBufferFix/Main.cs similarity index 100% rename from .DepricatedMods/AASBufferFix/Main.cs rename to .Deprecated/AASBufferFix/Main.cs diff --git a/.DepricatedMods/AASBufferFix/Properties/AssemblyInfo.cs b/.Deprecated/AASBufferFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/AASBufferFix/Properties/AssemblyInfo.cs rename to .Deprecated/AASBufferFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/AASBufferFix/README.md b/.Deprecated/AASBufferFix/README.md similarity index 100% rename from .DepricatedMods/AASBufferFix/README.md rename to .Deprecated/AASBufferFix/README.md diff --git a/.DepricatedMods/AASBufferFix/Utils.cs b/.Deprecated/AASBufferFix/Utils.cs similarity index 100% rename from .DepricatedMods/AASBufferFix/Utils.cs rename to .Deprecated/AASBufferFix/Utils.cs diff --git a/.DepricatedMods/AASBufferFix/format.json b/.Deprecated/AASBufferFix/format.json similarity index 100% rename from .DepricatedMods/AASBufferFix/format.json rename to .Deprecated/AASBufferFix/format.json diff --git a/AASDefaultProfileFix/AASDefaultProfileFix.csproj b/.Deprecated/AASDefaultProfileFix/AASDefaultProfileFix.csproj similarity index 100% rename from AASDefaultProfileFix/AASDefaultProfileFix.csproj rename to .Deprecated/AASDefaultProfileFix/AASDefaultProfileFix.csproj diff --git a/AASDefaultProfileFix/Main.cs b/.Deprecated/AASDefaultProfileFix/Main.cs similarity index 100% rename from AASDefaultProfileFix/Main.cs rename to .Deprecated/AASDefaultProfileFix/Main.cs diff --git a/AASDefaultProfileFix/Properties/AssemblyInfo.cs b/.Deprecated/AASDefaultProfileFix/Properties/AssemblyInfo.cs similarity index 100% rename from AASDefaultProfileFix/Properties/AssemblyInfo.cs rename to .Deprecated/AASDefaultProfileFix/Properties/AssemblyInfo.cs diff --git a/AASDefaultProfileFix/README.md b/.Deprecated/AASDefaultProfileFix/README.md similarity index 100% rename from AASDefaultProfileFix/README.md rename to .Deprecated/AASDefaultProfileFix/README.md diff --git a/AASDefaultProfileFix/format.json b/.Deprecated/AASDefaultProfileFix/format.json similarity index 100% rename from AASDefaultProfileFix/format.json rename to .Deprecated/AASDefaultProfileFix/format.json diff --git a/.DepricatedMods/AlternateIKSystem/AlternateIKSystem.csproj b/.Deprecated/AlternateIKSystem/AlternateIKSystem.csproj similarity index 100% rename from .DepricatedMods/AlternateIKSystem/AlternateIKSystem.csproj rename to .Deprecated/AlternateIKSystem/AlternateIKSystem.csproj diff --git a/.DepricatedMods/AlternateIKSystem/HarmonyPatches.cs b/.Deprecated/AlternateIKSystem/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/HarmonyPatches.cs rename to .Deprecated/AlternateIKSystem/HarmonyPatches.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/BodyControl.cs b/.Deprecated/AlternateIKSystem/IK/BodyControl.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/BodyControl.cs rename to .Deprecated/AlternateIKSystem/IK/BodyControl.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/IKCalibrator.cs b/.Deprecated/AlternateIKSystem/IK/IKCalibrator.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/IKCalibrator.cs rename to .Deprecated/AlternateIKSystem/IK/IKCalibrator.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandler.cs b/.Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandler.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandler.cs rename to .Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandler.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs b/.Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs rename to .Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs b/.Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs rename to .Deprecated/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/IKManager.cs b/.Deprecated/AlternateIKSystem/IK/IKManager.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/IKManager.cs rename to .Deprecated/AlternateIKSystem/IK/IKManager.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/MusclePoses.cs b/.Deprecated/AlternateIKSystem/IK/MusclePoses.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/MusclePoses.cs rename to .Deprecated/AlternateIKSystem/IK/MusclePoses.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/Tracking/SteamVRTrackerManager.cs b/.Deprecated/AlternateIKSystem/IK/Tracking/SteamVRTrackerManager.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/Tracking/SteamVRTrackerManager.cs rename to .Deprecated/AlternateIKSystem/IK/Tracking/SteamVRTrackerManager.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/VRIKHelpers/VRIKLocomotionData.cs b/.Deprecated/AlternateIKSystem/IK/VRIKHelpers/VRIKLocomotionData.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/VRIKHelpers/VRIKLocomotionData.cs rename to .Deprecated/AlternateIKSystem/IK/VRIKHelpers/VRIKLocomotionData.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs b/.Deprecated/AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs rename to .Deprecated/AlternateIKSystem/IK/VRIKHelpers/VRIKUtils.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/WeightManipulators/BodyParts/BodyPart.cs b/.Deprecated/AlternateIKSystem/IK/WeightManipulators/BodyParts/BodyPart.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/WeightManipulators/BodyParts/BodyPart.cs rename to .Deprecated/AlternateIKSystem/IK/WeightManipulators/BodyParts/BodyPart.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/WeightManipulators/DeviceControlManipulator.cs b/.Deprecated/AlternateIKSystem/IK/WeightManipulators/DeviceControlManipulator.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/WeightManipulators/DeviceControlManipulator.cs rename to .Deprecated/AlternateIKSystem/IK/WeightManipulators/DeviceControlManipulator.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/WeightManipulators/Interface/IWeightManipulator.cs b/.Deprecated/AlternateIKSystem/IK/WeightManipulators/Interface/IWeightManipulator.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/WeightManipulators/Interface/IWeightManipulator.cs rename to .Deprecated/AlternateIKSystem/IK/WeightManipulators/Interface/IWeightManipulator.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/WeightManipulators/TrackingControlManipulator.cs b/.Deprecated/AlternateIKSystem/IK/WeightManipulators/TrackingControlManipulator.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/WeightManipulators/TrackingControlManipulator.cs rename to .Deprecated/AlternateIKSystem/IK/WeightManipulators/TrackingControlManipulator.cs diff --git a/.DepricatedMods/AlternateIKSystem/IK/WeightManipulators/WeightManipulatorManager.cs b/.Deprecated/AlternateIKSystem/IK/WeightManipulators/WeightManipulatorManager.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/IK/WeightManipulators/WeightManipulatorManager.cs rename to .Deprecated/AlternateIKSystem/IK/WeightManipulators/WeightManipulatorManager.cs diff --git a/.DepricatedMods/AlternateIKSystem/Integrations/BTKUIAddon.cs b/.Deprecated/AlternateIKSystem/Integrations/BTKUIAddon.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/Integrations/BTKUIAddon.cs rename to .Deprecated/AlternateIKSystem/Integrations/BTKUIAddon.cs diff --git a/.DepricatedMods/AlternateIKSystem/LICENSE.txt b/.Deprecated/AlternateIKSystem/LICENSE.txt similarity index 100% rename from .DepricatedMods/AlternateIKSystem/LICENSE.txt rename to .Deprecated/AlternateIKSystem/LICENSE.txt diff --git a/.DepricatedMods/AlternateIKSystem/Main.cs b/.Deprecated/AlternateIKSystem/Main.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/Main.cs rename to .Deprecated/AlternateIKSystem/Main.cs diff --git a/.DepricatedMods/AlternateIKSystem/ModSettings.cs b/.Deprecated/AlternateIKSystem/ModSettings.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/ModSettings.cs rename to .Deprecated/AlternateIKSystem/ModSettings.cs diff --git a/.DepricatedMods/AlternateIKSystem/Properties/AssemblyInfo.cs b/.Deprecated/AlternateIKSystem/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/AlternateIKSystem/Properties/AssemblyInfo.cs rename to .Deprecated/AlternateIKSystem/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/AlternateIKSystem/README.md b/.Deprecated/AlternateIKSystem/README.md similarity index 100% rename from .DepricatedMods/AlternateIKSystem/README.md rename to .Deprecated/AlternateIKSystem/README.md diff --git a/.DepricatedMods/AlternateIKSystem/format.json b/.Deprecated/AlternateIKSystem/format.json similarity index 100% rename from .DepricatedMods/AlternateIKSystem/format.json rename to .Deprecated/AlternateIKSystem/format.json diff --git a/WhereAmIPointing/Main.cs b/.Deprecated/AutoSyncTransforms/Main.cs similarity index 100% rename from WhereAmIPointing/Main.cs rename to .Deprecated/AutoSyncTransforms/Main.cs diff --git a/WhereAmIPointing/Properties/AssemblyInfo.cs b/.Deprecated/AutoSyncTransforms/Properties/AssemblyInfo.cs similarity index 100% rename from WhereAmIPointing/Properties/AssemblyInfo.cs rename to .Deprecated/AutoSyncTransforms/Properties/AssemblyInfo.cs diff --git a/WhereAmIPointing/README.md b/.Deprecated/AutoSyncTransforms/README.md similarity index 100% rename from WhereAmIPointing/README.md rename to .Deprecated/AutoSyncTransforms/README.md diff --git a/WhereAmIPointing/WhereAmIPointing.csproj b/.Deprecated/AutoSyncTransforms/WhereAmIPointing.csproj similarity index 100% rename from WhereAmIPointing/WhereAmIPointing.csproj rename to .Deprecated/AutoSyncTransforms/WhereAmIPointing.csproj diff --git a/WhereAmIPointing/format.json b/.Deprecated/AutoSyncTransforms/format.json similarity index 100% rename from WhereAmIPointing/format.json rename to .Deprecated/AutoSyncTransforms/format.json diff --git a/AvatarScaleMod/AvatarScaleMod.csproj b/.Deprecated/AvatarScaleMod/AvatarScaleMod.csproj similarity index 100% rename from AvatarScaleMod/AvatarScaleMod.csproj rename to .Deprecated/AvatarScaleMod/AvatarScaleMod.csproj diff --git a/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs similarity index 99% rename from AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs index 38d6a9a..f3c1da4 100644 --- a/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs +++ b/.Deprecated/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs @@ -211,7 +211,7 @@ public class AvatarScaleManager : MonoBehaviour public float GetHeight() { if (_localAvatarScaler == null) - return PlayerAvatarPoint.defaultAvatarHeight; + return PlayerAvatarPoint.DefaultAvatarHeight; if (!_localAvatarScaler.IsForcingHeight()) return PlayerSetup.Instance.GetAvatarHeight(); @@ -222,7 +222,7 @@ public class AvatarScaleManager : MonoBehaviour public float GetAnimationClipHeight() { if (_localAvatarScaler == null) - return PlayerAvatarPoint.defaultAvatarHeight; + return PlayerAvatarPoint.DefaultAvatarHeight; if (!_localAvatarScaler.IsForcingHeight()) return PlayerSetup.Instance.GetAvatarHeight(); diff --git a/AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs similarity index 100% rename from AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/Components/BaseScaler.cs diff --git a/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs similarity index 94% rename from AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs index e6d79f4..7a076d8 100644 --- a/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs +++ b/.Deprecated/AvatarScaleMod/AvatarScaling/Components/LocalScaler.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.Player; +using ABI_RC.Core; +using ABI_RC.Core.Player; using ABI_RC.Core.UI; using NAK.AvatarScaleMod.AvatarScaling; using UnityEngine; @@ -72,7 +73,7 @@ public class LocalScaler : BaseScaler } // animation scale changed, record it! - Vector3 scaleDifference = PlayerSetup.DivideVectors(localScale - _initialScale, _initialScale); + Vector3 scaleDifference = CVRTools.DivideVectors(localScale - _initialScale, _initialScale); _animatedScaleFactor = scaleDifference.y; _animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight; _animatedScale = localScale; diff --git a/AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs similarity index 100% rename from AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/Components/NetworkScaler.cs diff --git a/AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs similarity index 100% rename from AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/Events/AvatarScaleEvents.cs diff --git a/AvatarScaleMod/AvatarScaling/ScaledComponents.cs b/.Deprecated/AvatarScaleMod/AvatarScaling/ScaledComponents.cs similarity index 100% rename from AvatarScaleMod/AvatarScaling/ScaledComponents.cs rename to .Deprecated/AvatarScaleMod/AvatarScaling/ScaledComponents.cs diff --git a/AvatarScaleMod/HarmonyPatches.cs b/.Deprecated/AvatarScaleMod/HarmonyPatches.cs similarity index 100% rename from AvatarScaleMod/HarmonyPatches.cs rename to .Deprecated/AvatarScaleMod/HarmonyPatches.cs diff --git a/AvatarScaleMod/Input/DebugKeybinds.cs b/.Deprecated/AvatarScaleMod/Input/DebugKeybinds.cs similarity index 100% rename from AvatarScaleMod/Input/DebugKeybinds.cs rename to .Deprecated/AvatarScaleMod/Input/DebugKeybinds.cs diff --git a/AvatarScaleMod/Input/ScaleReconizer.cs b/.Deprecated/AvatarScaleMod/Input/ScaleReconizer.cs similarity index 100% rename from AvatarScaleMod/Input/ScaleReconizer.cs rename to .Deprecated/AvatarScaleMod/Input/ScaleReconizer.cs diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs similarity index 71% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs index 09a5ff4..acf9fab 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs @@ -3,32 +3,32 @@ using BTKUILib; using BTKUILib.UIObjects; using NAK.AvatarScaleMod.AvatarScaling; -namespace NAK.AvatarScaleMod.Integrations -{ - public static partial class BtkUiAddon - { - private static Page _asmRootPage; - private static string _rootPageElementID; +namespace NAK.AvatarScaleMod.Integrations; - public static void Initialize() - { +public static partial class BtkUiAddon +{ + private static Page _asmRootPage; + private static string _rootPageElementID; + + public static void Initialize() + { Prepare_Icons(); Setup_AvatarScaleModTab(); Setup_PlayerSelectPage(); } - #region Initialization + #region Initialization - private static void Prepare_Icons() - { + private static void Prepare_Icons() + { QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig", GetIconStream("ASM_Icon_AvatarHeightConfig.png")); QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy", GetIconStream("ASM_Icon_AvatarHeightCopy.png")); } - private static void Setup_AvatarScaleModTab() - { + private static void Setup_AvatarScaleModTab() + { _asmRootPage = new Page(ModSettings.ModName, ModSettings.ASM_SettingsCategory, true, "ASM_Icon_AvatarHeightConfig") { MenuTitle = ModSettings.ASM_SettingsCategory, @@ -54,18 +54,18 @@ namespace NAK.AvatarScaleMod.Integrations Setup_DebugOptionsCategory(_asmRootPage); } - #endregion + #endregion - #region Player Count Display + #region Player Count Display - private static void OnWorldLeave() - => UpdatePlayerCountDisplay(); + private static void OnWorldLeave() + => UpdatePlayerCountDisplay(); - private static void OnUserJoinLeave(CVRPlayerEntity _) - => UpdatePlayerCountDisplay(); + private static void OnUserJoinLeave(CVRPlayerEntity _) + => UpdatePlayerCountDisplay(); - private static void UpdatePlayerCountDisplay() - { + private static void UpdatePlayerCountDisplay() + { if (_asmRootPage == null) return; @@ -74,14 +74,14 @@ namespace NAK.AvatarScaleMod.Integrations _asmRootPage.MenuSubtitle = $"Everything Avatar Scaling! :: ({modUserCount}/{playerCount} players using ASM)"; } - #endregion + #endregion - #region Double-Click Reset Height + #region Double-Click Reset Height - private static DateTime lastTime = DateTime.Now; + private static DateTime lastTime = DateTime.Now; - private static void OnTabChange(string newTab, string previousTab) - { + private static void OnTabChange(string newTab, string previousTab) + { if (newTab == _rootPageElementID) { UpdatePlayerCountDisplay(); @@ -95,6 +95,5 @@ namespace NAK.AvatarScaleMod.Integrations lastTime = DateTime.Now; } - #endregion - } + #endregion } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs similarity index 76% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs index 875668c..9ddc1c2 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs @@ -1,11 +1,11 @@ using BTKUILib.UIObjects; -namespace NAK.AvatarScaleMod.Integrations +namespace NAK.AvatarScaleMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon + private static void Setup_AvatarScaleModCategory(Page page) { - private static void Setup_AvatarScaleModCategory(Page page) - { Category avScaleModCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_ASM_SettingsCategory); AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleGestureEnabled); @@ -13,5 +13,4 @@ namespace NAK.AvatarScaleMod.Integrations AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistentHeight); AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistThroughRestart); } - } } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs similarity index 83% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs index 2eb6c57..d3820df 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs @@ -1,12 +1,12 @@ using BTKUILib.UIObjects; using BTKUILib.UIObjects.Components; -namespace NAK.AvatarScaleMod.Integrations +namespace NAK.AvatarScaleMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon + private static void Setup_AvatarScaleToolCategory(Page page) { - private static void Setup_AvatarScaleToolCategory(Page page) - { Category avScaleToolCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_AST_SettingsCategory); AddMelonStringInput(ref avScaleToolCategory, ModSettings.EntryASTScaleParameter, "icon"); @@ -19,5 +19,4 @@ namespace NAK.AvatarScaleMod.Integrations ModSettings.EntryASTMaxHeight.ResetToDefault(); }; } - } } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs similarity index 71% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs index b05dd94..088aafd 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs @@ -1,16 +1,15 @@ using BTKUILib.UIObjects; -namespace NAK.AvatarScaleMod.Integrations +namespace NAK.AvatarScaleMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon + private static void Setup_DebugOptionsCategory(Page page) { - private static void Setup_DebugOptionsCategory(Page page) - { Category debugCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_DEBUG_SettingsCategory); AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkInbound); AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkOutbound); AddMelonToggle(ref debugCategory, ModSettings.Debug_ComponentSearchTime); } - } } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs similarity index 83% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs index 216fa48..1437582 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs @@ -4,14 +4,14 @@ using BTKUILib.UIObjects.Components; using NAK.AvatarScaleMod.AvatarScaling; using System.Collections.Generic; // Added for list support -namespace NAK.AvatarScaleMod.Integrations -{ - public static partial class BtkUiAddon - { - private static readonly List USM_QmUiElements = new(); +namespace NAK.AvatarScaleMod.Integrations; - private static void Setup_UniversalScalingSettings(Page page) - { +public static partial class BtkUiAddon +{ + private static readonly List USM_QmUiElements = new(); + + private static void Setup_UniversalScalingSettings(Page page) + { Category uniScalingCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_USM_SettingsCategory); SliderFloat scaleSlider = AddMelonSlider(ref uniScalingCategory, ModSettings.EntryHiddenAvatarHeight, AvatarScaleManager.DefaultMinHeight, AvatarScaleManager.DefaultMaxHeight); @@ -52,24 +52,23 @@ namespace NAK.AvatarScaleMod.Integrations ModSettings.EntryUseUniversalScaling.OnEntryValueChanged.Subscribe((_, newValue) => OnUniversalScalingChanged(newValue)); } - private static void OnUniversalScalingChanged(bool value) - { + private static void OnUniversalScalingChanged(bool value) + { foreach (QMUIElement uiElement in USM_QmUiElements) uiElement.Disabled = !value; } - #region Slider Events + #region Slider Events - private static void OnAvatarHeightSliderChanged(float height) - { + private static void OnAvatarHeightSliderChanged(float height) + { AvatarScaleManager.Instance.SetTargetHeight(height); } - private static void OnAvatarHeightSliderReset() - { + private static void OnAvatarHeightSliderReset() + { AvatarScaleManager.Instance.Setting_UniversalScaling = false; } - #endregion - } + #endregion } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs similarity index 76% rename from AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs rename to .Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs index c83796d..ab54a69 100644 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs @@ -3,15 +3,15 @@ using BTKUILib.UIObjects; using BTKUILib.UIObjects.Components; using NAK.AvatarScaleMod.AvatarScaling; -namespace NAK.AvatarScaleMod.Integrations +namespace NAK.AvatarScaleMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon - { - private static Button _playerHasModElement; - private static string _selectedPlayer; + private static Button _playerHasModElement; + private static string _selectedPlayer; - private static void Setup_PlayerSelectPage() - { + private static void Setup_PlayerSelectPage() + { QuickMenuAPI.OnPlayerSelected += OnPlayerSelected; Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.ASM_SettingsCategory, ModSettings.ModName); @@ -22,27 +22,27 @@ namespace NAK.AvatarScaleMod.Integrations button.OnPress += OnCopyPlayerHeight; } - #region QM Events + #region QM Events - private static void OnPlayerSelected(string _, string id) - { + private static void OnPlayerSelected(string _, string id) + { _selectedPlayer = id; UpdatePlayerHasModIcon(); } - private static void OnCopyPlayerHeight() - { + private static void OnCopyPlayerHeight() + { float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer); if (networkHeight < 0) return; AvatarScaleManager.Instance.SetTargetHeight(networkHeight); } - #endregion + #endregion - #region Private Methods + #region Private Methods - private static void UpdatePlayerHasModIcon() - { + private static void UpdatePlayerHasModIcon() + { if (_playerHasModElement == null) return; @@ -60,6 +60,5 @@ namespace NAK.AvatarScaleMod.Integrations } } - #endregion - } + #endregion } \ No newline at end of file diff --git a/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs new file mode 100644 index 0000000..9c9eb20 --- /dev/null +++ b/.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs @@ -0,0 +1,77 @@ +using System.Reflection; +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using MelonLoader; +using UnityEngine; + +namespace NAK.AvatarScaleMod.Integrations; + +public static partial class BtkUiAddon +{ + #region Melon Preference Helpers + + private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry entry) + { + ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value); + toggle.OnValueUpdated += b => entry.Value = b; + return toggle; + } + + private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry entry, float min, + float max, int decimalPlaces = 2, bool allowReset = true) + { + SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description, + Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); + slider.OnValueUpdated += f => entry.Value = f; + return slider; + } + + private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { + Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); + button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s); + return button; + } + + private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { + Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); + button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f); + return button; + } + + // private static SliderFloat AddMelonSlider(ref Page page, MelonPreferences_Entry entry, float min, float max, int decimalPlaces = 2, bool allowReset = true) + // { + // SliderFloat slider = page.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); + // slider.OnValueUpdated += f => entry.Value = f; + // return slider; + // } + + /// + /// Helper method to create a category that saves its collapsed state to a MelonPreferences entry. + /// + /// + /// + /// + /// + private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry entry, bool showHeader = true) + { + Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value); + category.OnCollapse += b => entry.Value = b; + return category; + } + + #endregion + + #region Icon Utils + + private static Stream GetIconStream(string iconName) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string assemblyName = assembly.GetName().Name; + return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}"); + } + + #endregion +} \ No newline at end of file diff --git a/AvatarScaleMod/Main.cs b/.Deprecated/AvatarScaleMod/Main.cs similarity index 100% rename from AvatarScaleMod/Main.cs rename to .Deprecated/AvatarScaleMod/Main.cs diff --git a/AvatarScaleMod/ModSettings.cs b/.Deprecated/AvatarScaleMod/ModSettings.cs similarity index 100% rename from AvatarScaleMod/ModSettings.cs rename to .Deprecated/AvatarScaleMod/ModSettings.cs diff --git a/AvatarScaleMod/Networking/ModNetwork.cs b/.Deprecated/AvatarScaleMod/Networking/ModNetwork.cs similarity index 100% rename from AvatarScaleMod/Networking/ModNetwork.cs rename to .Deprecated/AvatarScaleMod/Networking/ModNetwork.cs diff --git a/AvatarScaleMod/Properties/AssemblyInfo.cs b/.Deprecated/AvatarScaleMod/Properties/AssemblyInfo.cs similarity index 100% rename from AvatarScaleMod/Properties/AssemblyInfo.cs rename to .Deprecated/AvatarScaleMod/Properties/AssemblyInfo.cs diff --git a/AvatarScaleMod/README.md b/.Deprecated/AvatarScaleMod/README.md similarity index 100% rename from AvatarScaleMod/README.md rename to .Deprecated/AvatarScaleMod/README.md diff --git a/AvatarScaleMod/Scripts.cs b/.Deprecated/AvatarScaleMod/Scripts.cs similarity index 83% rename from AvatarScaleMod/Scripts.cs rename to .Deprecated/AvatarScaleMod/Scripts.cs index 5980b65..16d7319 100644 --- a/AvatarScaleMod/Scripts.cs +++ b/.Deprecated/AvatarScaleMod/Scripts.cs @@ -3,12 +3,12 @@ using System.IO; using System.Reflection; // https://github.com/SDraw/ml_mods_cvr/blob/master/ml_amt/Scripts.cs -namespace NAK.AvatarScaleMod +namespace NAK.AvatarScaleMod; + +static class Scripts { - static class Scripts + public static string GetEmbeddedScript(string p_name) { - public static string GetEmbeddedScript(string p_name) - { string l_result = ""; Assembly l_assembly = Assembly.GetExecutingAssembly(); string l_assemblyName = l_assembly.GetName().Name; @@ -23,5 +23,4 @@ namespace NAK.AvatarScaleMod return l_result; } - } } \ No newline at end of file diff --git a/.DepricatedMods/InteractionTest/format.json b/.Deprecated/AvatarScaleMod/format.json similarity index 100% rename from .DepricatedMods/InteractionTest/format.json rename to .Deprecated/AvatarScaleMod/format.json diff --git a/AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png b/.Deprecated/AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png similarity index 100% rename from AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png rename to .Deprecated/AvatarScaleMod/resources/ASM_Icon_AvatarHeightConfig.png diff --git a/AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png b/.Deprecated/AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png similarity index 100% rename from AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png rename to .Deprecated/AvatarScaleMod/resources/ASM_Icon_AvatarHeightCopy.png diff --git a/AvatarScaleMod/resources/menu.js b/.Deprecated/AvatarScaleMod/resources/menu.js similarity index 100% rename from AvatarScaleMod/resources/menu.js rename to .Deprecated/AvatarScaleMod/resources/menu.js diff --git a/.DepricatedMods/BadAnimatorFix/BadAnimatorFix.csproj b/.Deprecated/BadAnimatorFix/BadAnimatorFix.csproj similarity index 100% rename from .DepricatedMods/BadAnimatorFix/BadAnimatorFix.csproj rename to .Deprecated/BadAnimatorFix/BadAnimatorFix.csproj diff --git a/.DepricatedMods/BadAnimatorFix/BadAnimatorFixManager.cs b/.Deprecated/BadAnimatorFix/BadAnimatorFixManager.cs similarity index 100% rename from .DepricatedMods/BadAnimatorFix/BadAnimatorFixManager.cs rename to .Deprecated/BadAnimatorFix/BadAnimatorFixManager.cs diff --git a/.DepricatedMods/BadAnimatorFix/BadAnimatorFixer.cs b/.Deprecated/BadAnimatorFix/BadAnimatorFixer.cs similarity index 100% rename from .DepricatedMods/BadAnimatorFix/BadAnimatorFixer.cs rename to .Deprecated/BadAnimatorFix/BadAnimatorFixer.cs diff --git a/.DepricatedMods/BadAnimatorFix/HarmonyPatches.cs b/.Deprecated/BadAnimatorFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/BadAnimatorFix/HarmonyPatches.cs rename to .Deprecated/BadAnimatorFix/HarmonyPatches.cs diff --git a/.DepricatedMods/BadAnimatorFix/Main.cs b/.Deprecated/BadAnimatorFix/Main.cs similarity index 100% rename from .DepricatedMods/BadAnimatorFix/Main.cs rename to .Deprecated/BadAnimatorFix/Main.cs diff --git a/.DepricatedMods/BadAnimatorFix/Properties/AssemblyInfo.cs b/.Deprecated/BadAnimatorFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/BadAnimatorFix/Properties/AssemblyInfo.cs rename to .Deprecated/BadAnimatorFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/BadAnimatorFix/format.json b/.Deprecated/BadAnimatorFix/format.json similarity index 100% rename from .DepricatedMods/BadAnimatorFix/format.json rename to .Deprecated/BadAnimatorFix/format.json diff --git a/.DepricatedMods/Blackout/AssetHandler.cs b/.Deprecated/Blackout/AssetHandler.cs similarity index 100% rename from .DepricatedMods/Blackout/AssetHandler.cs rename to .Deprecated/Blackout/AssetHandler.cs diff --git a/.DepricatedMods/Blackout/Blackout.csproj b/.Deprecated/Blackout/Blackout.csproj similarity index 100% rename from .DepricatedMods/Blackout/Blackout.csproj rename to .Deprecated/Blackout/Blackout.csproj diff --git a/.DepricatedMods/Blackout/BlackoutController.cs b/.Deprecated/Blackout/BlackoutController.cs similarity index 100% rename from .DepricatedMods/Blackout/BlackoutController.cs rename to .Deprecated/Blackout/BlackoutController.cs diff --git a/.DepricatedMods/Blackout/HarmonyPatches.cs b/.Deprecated/Blackout/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/Blackout/HarmonyPatches.cs rename to .Deprecated/Blackout/HarmonyPatches.cs diff --git a/.DepricatedMods/Blackout/Integrations/BTKUIAddon.cs b/.Deprecated/Blackout/Integrations/BTKUIAddon.cs similarity index 100% rename from .DepricatedMods/Blackout/Integrations/BTKUIAddon.cs rename to .Deprecated/Blackout/Integrations/BTKUIAddon.cs diff --git a/.DepricatedMods/Blackout/Integrations/UIExpansionKitAddon.cs b/.Deprecated/Blackout/Integrations/UIExpansionKitAddon.cs similarity index 100% rename from .DepricatedMods/Blackout/Integrations/UIExpansionKitAddon.cs rename to .Deprecated/Blackout/Integrations/UIExpansionKitAddon.cs diff --git a/.DepricatedMods/Blackout/Main.cs b/.Deprecated/Blackout/Main.cs similarity index 100% rename from .DepricatedMods/Blackout/Main.cs rename to .Deprecated/Blackout/Main.cs diff --git a/.DepricatedMods/Blackout/Properties/AssemblyInfo.cs b/.Deprecated/Blackout/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/Blackout/Properties/AssemblyInfo.cs rename to .Deprecated/Blackout/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/Blackout/Resource1.Designer.cs b/.Deprecated/Blackout/Resource1.Designer.cs similarity index 100% rename from .DepricatedMods/Blackout/Resource1.Designer.cs rename to .Deprecated/Blackout/Resource1.Designer.cs diff --git a/.DepricatedMods/Blackout/Resource1.resx b/.Deprecated/Blackout/Resource1.resx similarity index 100% rename from .DepricatedMods/Blackout/Resource1.resx rename to .Deprecated/Blackout/Resource1.resx diff --git a/.DepricatedMods/Blackout/format.json b/.Deprecated/Blackout/format.json similarity index 100% rename from .DepricatedMods/Blackout/format.json rename to .Deprecated/Blackout/format.json diff --git a/.DepricatedMods/Blackout/resources/blackout_controller.asset b/.Deprecated/Blackout/resources/blackout_controller.asset similarity index 100% rename from .DepricatedMods/Blackout/resources/blackout_controller.asset rename to .Deprecated/Blackout/resources/blackout_controller.asset diff --git a/BullshitWatcher/BullshitWatcher.csproj b/.Deprecated/BullshitWatcher/BullshitWatcher.csproj similarity index 100% rename from BullshitWatcher/BullshitWatcher.csproj rename to .Deprecated/BullshitWatcher/BullshitWatcher.csproj diff --git a/BullshitWatcher/Main.cs b/.Deprecated/BullshitWatcher/Main.cs similarity index 100% rename from BullshitWatcher/Main.cs rename to .Deprecated/BullshitWatcher/Main.cs diff --git a/BullshitWatcher/Properties/AssemblyInfo.cs b/.Deprecated/BullshitWatcher/Properties/AssemblyInfo.cs similarity index 100% rename from BullshitWatcher/Properties/AssemblyInfo.cs rename to .Deprecated/BullshitWatcher/Properties/AssemblyInfo.cs diff --git a/CVRGizmos/CVRGizmoManager.cs b/.Deprecated/CVRGizmos/CVRGizmoManager.cs similarity index 76% rename from CVRGizmos/CVRGizmoManager.cs rename to .Deprecated/CVRGizmos/CVRGizmoManager.cs index fd03eb0..b1d55b5 100644 --- a/CVRGizmos/CVRGizmoManager.cs +++ b/.Deprecated/CVRGizmos/CVRGizmoManager.cs @@ -2,18 +2,18 @@ using UnityEngine; using Gizmos = Popcron.Gizmos; -namespace NAK.CVRGizmos +namespace NAK.CVRGizmos; + +public class CVRGizmoManager : MonoBehaviour { - public class CVRGizmoManager : MonoBehaviour - { - public static CVRGizmoManager Instance; + public static CVRGizmoManager Instance; - public bool g_enabled = false; - public bool g_localOnly = false; + public bool g_enabled = false; + public bool g_localOnly = false; - public MonoBehaviour[] managed; + public MonoBehaviour[] managed; - public System.Type[] GizmoTypes = { + public System.Type[] GizmoTypes = { typeof(CVRGizmos_Pointer), typeof(CVRGizmos_AdvancedAvatarSettingsTrigger), typeof(CVRGizmos_SpawnableTrigger), @@ -30,8 +30,8 @@ namespace NAK.CVRGizmos typeof(CVRGizmos_CapsuleCollider), }; - private void Start() - { + private void Start() + { CVRGizmoManager.Instance = this; managed = new MonoBehaviour[GizmoTypes.Count()]; for (int i = 0; i < GizmoTypes.Count(); i++) @@ -40,8 +40,8 @@ namespace NAK.CVRGizmos } } - public void EnableGizmos(bool able) - { + public void EnableGizmos(bool able) + { for (int i = 0; i < GizmoTypes.Count(); i++) { managed[i].enabled = able; @@ -50,12 +50,11 @@ namespace NAK.CVRGizmos RefreshGizmos(); } - public void RefreshGizmos() - { + public void RefreshGizmos() + { for (int i = 0; i < GizmoTypes.Count(); i++) { managed[i].Invoke("CacheGizmos", 0f); } } - } } \ No newline at end of file diff --git a/CVRGizmos/CVRGizmos.csproj b/.Deprecated/CVRGizmos/CVRGizmos.csproj similarity index 100% rename from CVRGizmos/CVRGizmos.csproj rename to .Deprecated/CVRGizmos/CVRGizmos.csproj diff --git a/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsPointer.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsPointer.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsPointer.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsPointer.cs diff --git a/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsTrigger.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsTrigger.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsTrigger.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRAdvancedAvatarSettingsTrigger.cs diff --git a/CVRGizmos/GizmoTypes/CVRAvatar.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRAvatar.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRAvatar.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRAvatar.cs diff --git a/CVRGizmos/GizmoTypes/CVRAvatarPickupMarker.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRAvatarPickupMarker.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRAvatarPickupMarker.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRAvatarPickupMarker.cs diff --git a/CVRGizmos/GizmoTypes/CVRDistanceConstrain.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRDistanceConstrain.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRDistanceConstrain.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRDistanceConstrain.cs diff --git a/CVRGizmos/GizmoTypes/CVRDistanceLod.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRDistanceLod.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRDistanceLod.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRDistanceLod.cs diff --git a/CVRGizmos/GizmoTypes/CVRGizmoBase.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRGizmoBase.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRGizmoBase.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRGizmoBase.cs diff --git a/CVRGizmos/GizmoTypes/CVRHapticAreaChest.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRHapticAreaChest.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRHapticAreaChest.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRHapticAreaChest.cs diff --git a/CVRGizmos/GizmoTypes/CVRHapticZone.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRHapticZone.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRHapticZone.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRHapticZone.cs diff --git a/CVRGizmos/GizmoTypes/CVRPointer.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRPointer.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRPointer.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRPointer.cs diff --git a/CVRGizmos/GizmoTypes/CVRSpawnableTrigger.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRSpawnableTrigger.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRSpawnableTrigger.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRSpawnableTrigger.cs diff --git a/CVRGizmos/GizmoTypes/CVRToggleStateTrigger.cs b/.Deprecated/CVRGizmos/GizmoTypes/CVRToggleStateTrigger.cs similarity index 100% rename from CVRGizmos/GizmoTypes/CVRToggleStateTrigger.cs rename to .Deprecated/CVRGizmos/GizmoTypes/CVRToggleStateTrigger.cs diff --git a/CVRGizmos/GizmoTypes/Unity_BoxCollider.cs b/.Deprecated/CVRGizmos/GizmoTypes/Unity_BoxCollider.cs similarity index 100% rename from CVRGizmos/GizmoTypes/Unity_BoxCollider.cs rename to .Deprecated/CVRGizmos/GizmoTypes/Unity_BoxCollider.cs diff --git a/CVRGizmos/GizmoTypes/Unity_CapsuleCollider.cs b/.Deprecated/CVRGizmos/GizmoTypes/Unity_CapsuleCollider.cs similarity index 100% rename from CVRGizmos/GizmoTypes/Unity_CapsuleCollider.cs rename to .Deprecated/CVRGizmos/GizmoTypes/Unity_CapsuleCollider.cs diff --git a/CVRGizmos/GizmoTypes/Unity_SphereCollider.cs b/.Deprecated/CVRGizmos/GizmoTypes/Unity_SphereCollider.cs similarity index 100% rename from CVRGizmos/GizmoTypes/Unity_SphereCollider.cs rename to .Deprecated/CVRGizmos/GizmoTypes/Unity_SphereCollider.cs diff --git a/CVRGizmos/Main.cs b/.Deprecated/CVRGizmos/Main.cs similarity index 100% rename from CVRGizmos/Main.cs rename to .Deprecated/CVRGizmos/Main.cs diff --git a/.Deprecated/CVRGizmos/Popcron.Gizmos/Constants.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Constants.cs new file mode 100644 index 0000000..56f8ef4 --- /dev/null +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Constants.cs @@ -0,0 +1,7 @@ +namespace Popcron; + +public class Constants +{ + public const string UniqueIdentifier = "Popcron.Gizmos"; + public const string EnabledKey = UniqueIdentifier + ".Enabled"; +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Drawer.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawer.cs similarity index 86% rename from CVRGizmos/Popcron.Gizmos/Drawer.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/Drawer.cs index 8e42ae8..729445f 100644 --- a/CVRGizmos/Popcron.Gizmos/Drawer.cs +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawer.cs @@ -1,21 +1,21 @@ using System.Reflection; using UnityEngine; -namespace Popcron +namespace Popcron; + +public abstract class Drawer { - public abstract class Drawer + private static Dictionary typeToDrawer = null; + + public abstract int Draw(ref Vector3[] buffer, params object[] args); + + public Drawer() { - private static Dictionary typeToDrawer = null; - - public abstract int Draw(ref Vector3[] buffer, params object[] args); - - public Drawer() - { } - public static Drawer Get() where T : class - { + public static Drawer Get() where T : class + { //find all drawers if (typeToDrawer == null) { @@ -64,5 +64,4 @@ namespace Popcron return null; } } - } -} +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs similarity index 93% rename from CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs index 478a0b6..78ab2bf 100644 --- a/CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/CubeDrawer.cs @@ -1,16 +1,16 @@ using UnityEngine; -namespace Popcron +namespace Popcron; + +public class CubeDrawer : Drawer { - public class CubeDrawer : Drawer + public CubeDrawer() { - public CubeDrawer() - { } - public override int Draw(ref Vector3[] buffer, params object[] values) - { + public override int Draw(ref Vector3[] buffer, params object[] values) + { Vector3 position = (Vector3)values[0]; Quaternion rotation = (Quaternion)values[1]; Vector3 size = (Vector3)values[2]; @@ -92,5 +92,4 @@ namespace Popcron return 24; } - } -} +} \ No newline at end of file diff --git a/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs new file mode 100644 index 0000000..0f17498 --- /dev/null +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +namespace Popcron; + +public class LineDrawer : Drawer +{ + public LineDrawer() + { + + } + + public override int Draw(ref Vector3[] buffer, params object[] args) + { + buffer[0] = (Vector3)args[0]; + buffer[1] = (Vector3)args[1]; + return 2; + } +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs similarity index 84% rename from CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs index ad3b1b2..ab66d29 100644 --- a/CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/PolygonDrawer.cs @@ -1,16 +1,16 @@ using UnityEngine; -namespace Popcron +namespace Popcron; + +public class PolygonDrawer : Drawer { - public class PolygonDrawer : Drawer + public PolygonDrawer() { - public PolygonDrawer() - { } - public override int Draw(ref Vector3[] buffer, params object[] values) - { + public override int Draw(ref Vector3[] buffer, params object[] values) + { Vector3 position = (Vector3)values[0]; int points = (int)values[1]; float radius = (float)values[2]; @@ -36,5 +36,4 @@ namespace Popcron return points * 2; } - } -} +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs similarity index 89% rename from CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs index c46160a..7b0643d 100644 --- a/CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Drawers/SquareDrawer.cs @@ -1,16 +1,16 @@ using UnityEngine; -namespace Popcron +namespace Popcron; + +public class SquareDrawer : Drawer { - public class SquareDrawer : Drawer + public SquareDrawer() { - public SquareDrawer() - { } - public override int Draw(ref Vector3[] buffer, params object[] values) - { + public override int Draw(ref Vector3[] buffer, params object[] values) + { Vector2 position = default; if (values[0] is Vector2 p2) { @@ -68,5 +68,4 @@ namespace Popcron return 8; } - } -} +} \ No newline at end of file diff --git a/.Deprecated/CVRGizmos/Popcron.Gizmos/Element.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Element.cs new file mode 100644 index 0000000..1ea97d5 --- /dev/null +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Element.cs @@ -0,0 +1,11 @@ +using UnityEngine; + +namespace Popcron; + +internal class Element +{ + public Vector3[] points = { }; + public Color color = Color.white; + public bool dashed = false; + public Matrix4x4 matrix = Matrix4x4.identity; +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Gizmos.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/Gizmos.cs similarity index 52% rename from CVRGizmos/Popcron.Gizmos/Gizmos.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/Gizmos.cs index a897991..cf86940 100644 --- a/CVRGizmos/Popcron.Gizmos/Gizmos.cs +++ b/.Deprecated/CVRGizmos/Popcron.Gizmos/Gizmos.cs @@ -1,29 +1,29 @@ using UnityEngine; -namespace Popcron +namespace Popcron; + +public class Gizmos { - public class Gizmos + private static string _prefsKey = null; + private static int? _bufferSize = null; + private static bool? _enabled = null; + private static float? _dashGap = null; + private static bool? _cull = null; + private static int? _pass = null; + private static Vector3? _offset = null; + + private static Vector3[] buffer = new Vector3[BufferSize]; + + /// + /// By default, it will always render to scene view camera and the main camera. + /// Subscribing to this allows you to whitelist your custom cameras. + /// + public static Func CameraFilter = cam => false; + + private static string PrefsKey { - private static string _prefsKey = null; - private static int? _bufferSize = null; - private static bool? _enabled = null; - private static float? _dashGap = null; - private static bool? _cull = null; - private static int? _pass = null; - private static Vector3? _offset = null; - - private static Vector3[] buffer = new Vector3[BufferSize]; - - /// - /// By default, it will always render to scene view camera and the main camera. - /// Subscribing to this allows you to whitelist your custom cameras. - /// - public static Func CameraFilter = cam => false; - - private static string PrefsKey + get { - get - { if (string.IsNullOrEmpty(_prefsKey)) { _prefsKey = $"{SystemInfo.deviceUniqueIdentifier}.{Application.companyName}.{Application.productName}.{Constants.UniqueIdentifier}"; @@ -31,34 +31,34 @@ namespace Popcron return _prefsKey; } - } + } - /// - /// The matrix to use. - /// - public static Matrix4x4 Matrix - { - get => GizmosInstance.Matrix; - set => GizmosInstance.Matrix = value; - } + /// + /// The matrix to use. + /// + public static Matrix4x4 Matrix + { + get => GizmosInstance.Matrix; + set => GizmosInstance.Matrix = value; + } - /// - /// The matrix to use. - /// - public static Color Color - { - get => GizmosInstance.Color; - set => GizmosInstance.Color = value; - } + /// + /// The matrix to use. + /// + public static Color Color + { + get => GizmosInstance.Color; + set => GizmosInstance.Color = value; + } - /// - /// The size of the total gizmos buffer. - /// Default is 4096. - /// - public static int BufferSize + /// + /// The size of the total gizmos buffer. + /// Default is 4096. + /// + public static int BufferSize + { + get { - get - { if (_bufferSize == null) { _bufferSize = PlayerPrefs.GetInt($"{PrefsKey}.BufferSize", 4096); @@ -66,8 +66,8 @@ namespace Popcron return _bufferSize.Value; } - set - { + set + { value = Mathf.Clamp(value, 0, int.MaxValue); if (_bufferSize != value) { @@ -78,15 +78,15 @@ namespace Popcron buffer = new Vector3[value]; } } - } + } - /// - /// Toggles wether the gizmos could be drawn or not. - /// - public static bool Enabled + /// + /// Toggles wether the gizmos could be drawn or not. + /// + public static bool Enabled + { + get { - get - { if (_enabled == null) { _enabled = PlayerPrefs.GetInt($"{PrefsKey}.Enabled", 1) == 1; @@ -94,24 +94,24 @@ namespace Popcron return _enabled.Value; } - set - { + set + { if (_enabled != value) { _enabled = value; PlayerPrefs.SetInt($"{PrefsKey}.Enabled", value ? 1 : 0); } } - } + } - /// - /// The size of the gap when drawing dashed elements. - /// Default gap size is 0.1 - /// - public static float DashGap + /// + /// The size of the gap when drawing dashed elements. + /// Default gap size is 0.1 + /// + public static float DashGap + { + get { - get - { if (_dashGap == null) { _dashGap = PlayerPrefs.GetFloat($"{PrefsKey}.DashGap", 0.1f); @@ -119,46 +119,46 @@ namespace Popcron return _dashGap.Value; } - set - { + set + { if (_dashGap != value) { _dashGap = value; PlayerPrefs.SetFloat($"{PrefsKey}.DashGap", value); } } - } + } - [Obsolete("This property is obsolete. Use FrustumCulling instead.", false)] - public static bool Cull + [Obsolete("This property is obsolete. Use FrustumCulling instead.", false)] + public static bool Cull + { + get { - get - { return FrustumCulling; } - set - { + set + { FrustumCulling = value; } - } + } - [Obsolete("This property is obsolete. Subscribe to CameraFilter predicate instead and return true for your custom camera.", false)] - public static Camera Camera + [Obsolete("This property is obsolete. Subscribe to CameraFilter predicate instead and return true for your custom camera.", false)] + public static Camera Camera + { + get => null; + set { - get => null; - set - { } - } + } - /// - /// Should the camera not draw elements that are not visible? - /// - public static bool FrustumCulling + /// + /// Should the camera not draw elements that are not visible? + /// + public static bool FrustumCulling + { + get { - get - { if (_cull == null) { _cull = PlayerPrefs.GetInt($"{PrefsKey}.FrustumCulling", 1) == 1; @@ -166,32 +166,32 @@ namespace Popcron return _cull.Value; } - set - { + set + { if (_cull != value) { _cull = value; PlayerPrefs.SetInt($"{PrefsKey}.FrustumCulling", value ? 1 : 0); } } - } + } - /// - /// The material being used to render. - /// - public static Material Material - { - get => GizmosInstance.Material; - set => GizmosInstance.Material = value; - } + /// + /// The material being used to render. + /// + public static Material Material + { + get => GizmosInstance.Material; + set => GizmosInstance.Material = value; + } - /// - /// Rendering pass to activate. - /// - public static int Pass + /// + /// Rendering pass to activate. + /// + public static int Pass + { + get { - get - { if (_pass == null) { _pass = PlayerPrefs.GetInt($"{PrefsKey}.Pass", 0); @@ -199,23 +199,23 @@ namespace Popcron return _pass.Value; } - set - { + set + { if (_pass != value) { _pass = value; PlayerPrefs.SetInt($"{PrefsKey}.Pass", value); } } - } + } - /// - /// Global offset for all points. Default is (0, 0, 0). - /// - public static Vector3 Offset + /// + /// Global offset for all points. Default is (0, 0, 0). + /// + public static Vector3 Offset + { + get { - get - { const string Delim = ","; if (_offset == null) { @@ -235,8 +235,8 @@ namespace Popcron return _offset.Value; } - set - { + set + { const string Delim = ","; if (_offset != value) { @@ -244,13 +244,13 @@ namespace Popcron PlayerPrefs.SetString($"{PrefsKey}.Offset", value.x + Delim + value.y + Delim + value.y); } } - } + } - /// - /// Draws an element onto the screen. - /// - public static void Draw(Color? color, bool dashed, params object[] args) where T : Drawer - { + /// + /// Draws an element onto the screen. + /// + public static void Draw(Color? color, bool dashed, params object[] args) where T : Drawer + { if (!Enabled) { return; @@ -268,11 +268,11 @@ namespace Popcron } } - /// - /// Draws an array of lines. Useful for things like paths. - /// - public static void Lines(Vector3[] lines, Color? color = null, bool dashed = false) - { + /// + /// Draws an array of lines. Useful for things like paths. + /// + public static void Lines(Vector3[] lines, Color? color = null, bool dashed = false) + { if (!Enabled) { return; @@ -281,69 +281,69 @@ namespace Popcron GizmosInstance.Submit(lines, color, dashed); } - /// - /// Draw line in world space. - /// - public static void Line(Vector3 a, Vector3 b, Color? color = null, bool dashed = false) - { + /// + /// Draw line in world space. + /// + public static void Line(Vector3 a, Vector3 b, Color? color = null, bool dashed = false) + { Draw(color, dashed, a, b); } - /// - /// Draw square in world space. - /// - public static void Square(Vector2 position, Vector2 size, Color? color = null, bool dashed = false) - { + /// + /// Draw square in world space. + /// + public static void Square(Vector2 position, Vector2 size, Color? color = null, bool dashed = false) + { Square(position, Quaternion.identity, size, color, dashed); } - /// - /// Draw square in world space with float diameter parameter. - /// - public static void Square(Vector2 position, float diameter, Color? color = null, bool dashed = false) - { + /// + /// Draw square in world space with float diameter parameter. + /// + public static void Square(Vector2 position, float diameter, Color? color = null, bool dashed = false) + { Square(position, Quaternion.identity, Vector2.one * diameter, color, dashed); } - /// - /// Draw square in world space with a rotation parameter. - /// - public static void Square(Vector2 position, Quaternion rotation, Vector2 size, Color? color = null, bool dashed = false) - { + /// + /// Draw square in world space with a rotation parameter. + /// + public static void Square(Vector2 position, Quaternion rotation, Vector2 size, Color? color = null, bool dashed = false) + { Draw(color, dashed, position, rotation, size); } - /// - /// Draws a cube in world space. - /// - public static void Cube(Vector3 position, Quaternion rotation, Vector3 size, Color? color = null, bool dashed = false) - { + /// + /// Draws a cube in world space. + /// + public static void Cube(Vector3 position, Quaternion rotation, Vector3 size, Color? color = null, bool dashed = false) + { Draw(color, dashed, position, rotation, size); } - /// - /// Draws a rectangle in screen space. - /// - public static void Rect(Rect rect, Camera camera, Color? color = null, bool dashed = false) - { + /// + /// Draws a rectangle in screen space. + /// + public static void Rect(Rect rect, Camera camera, Color? color = null, bool dashed = false) + { rect.y = Screen.height - rect.y; Vector2 corner = camera.ScreenToWorldPoint(new Vector2(rect.x, rect.y - rect.height)); Draw(color, dashed, corner + rect.size * 0.5f, Quaternion.identity, rect.size); } - /// - /// Draws a representation of a bounding box. - /// - public static void Bounds(Bounds bounds, Color? color = null, bool dashed = false) - { + /// + /// Draws a representation of a bounding box. + /// + public static void Bounds(Bounds bounds, Color? color = null, bool dashed = false) + { Draw(color, dashed, bounds.center, Quaternion.identity, bounds.size); } - /// - /// Draws a cone similar to the one that spot lights draw. - /// - public static void Cone(Vector3 position, Quaternion rotation, float length, float angle, Color? color = null, bool dashed = false, int pointsCount = 16) - { + /// + /// Draws a cone similar to the one that spot lights draw. + /// + public static void Cone(Vector3 position, Quaternion rotation, float length, float angle, Color? color = null, bool dashed = false, int pointsCount = 16) + { //draw the end of the cone float endAngle = Mathf.Tan(angle * 0.5f * Mathf.Deg2Rad) * length; Vector3 forward = rotation * Vector3.forward; @@ -360,34 +360,33 @@ namespace Popcron } } - /// - /// Draws a sphere at position with specified radius. - /// - public static void Sphere(Vector3 position, float radius, Color? color = null, bool dashed = false, int pointsCount = 16) - { + /// + /// Draws a sphere at position with specified radius. + /// + public static void Sphere(Vector3 position, float radius, Color? color = null, bool dashed = false, int pointsCount = 16) + { float offset = 0f; Draw(color, dashed, position, pointsCount, radius, offset, Quaternion.Euler(0f, 0f, 0f)); Draw(color, dashed, position, pointsCount, radius, offset, Quaternion.Euler(90f, 0f, 0f)); Draw(color, dashed, position, pointsCount, radius, offset, Quaternion.Euler(0f, 90f, 90f)); } - /// - /// Draws a circle in world space and billboards towards the camera. - /// - public static void Circle(Vector3 position, float radius, Camera camera, Color? color = null, bool dashed = false, int pointsCount = 16) - { + /// + /// Draws a circle in world space and billboards towards the camera. + /// + public static void Circle(Vector3 position, float radius, Camera camera, Color? color = null, bool dashed = false, int pointsCount = 16) + { float offset = 0f; Quaternion rotation = Quaternion.LookRotation(position - camera.transform.position); Draw(color, dashed, position, pointsCount, radius, offset, rotation); } - /// - /// Draws a circle in world space with a specified rotation. - /// - public static void Circle(Vector3 position, float radius, Quaternion rotation, Color? color = null, bool dashed = false, int pointsCount = 16) - { + /// + /// Draws a circle in world space with a specified rotation. + /// + public static void Circle(Vector3 position, float radius, Quaternion rotation, Color? color = null, bool dashed = false, int pointsCount = 16) + { float offset = 0f; Draw(color, dashed, position, pointsCount, radius, offset, rotation); } - } -} +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/GizmosInstance.cs b/.Deprecated/CVRGizmos/Popcron.Gizmos/GizmosInstance.cs similarity index 100% rename from CVRGizmos/Popcron.Gizmos/GizmosInstance.cs rename to .Deprecated/CVRGizmos/Popcron.Gizmos/GizmosInstance.cs diff --git a/CVRGizmos/Popcron.Gizmos/LICENSE.md b/.Deprecated/CVRGizmos/Popcron.Gizmos/LICENSE.md similarity index 100% rename from CVRGizmos/Popcron.Gizmos/LICENSE.md rename to .Deprecated/CVRGizmos/Popcron.Gizmos/LICENSE.md diff --git a/CVRGizmos/Properties/AssemblyInfo.cs b/.Deprecated/CVRGizmos/Properties/AssemblyInfo.cs similarity index 100% rename from CVRGizmos/Properties/AssemblyInfo.cs rename to .Deprecated/CVRGizmos/Properties/AssemblyInfo.cs diff --git a/CVRGizmos/format.json b/.Deprecated/CVRGizmos/format.json similarity index 100% rename from CVRGizmos/format.json rename to .Deprecated/CVRGizmos/format.json diff --git a/SmartReticle/SmartReticle.csproj b/.Deprecated/CameraExperiments/CameraExperiments.csproj similarity index 100% rename from SmartReticle/SmartReticle.csproj rename to .Deprecated/CameraExperiments/CameraExperiments.csproj diff --git a/.Deprecated/CameraExperiments/Main.cs b/.Deprecated/CameraExperiments/Main.cs new file mode 100644 index 0000000..51035fd --- /dev/null +++ b/.Deprecated/CameraExperiments/Main.cs @@ -0,0 +1,170 @@ +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using HarmonyLib; +using MagicaCloth2; +using MelonLoader; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace NAK.WhereAmIPointing; + +public class WhereAmIPointingMod : MelonMod +{ + #region Melon Preferences + + // cannot disable because then id need extra logic to reset the alpha :) + // private const string SettingsCategory = nameof(WhereAmIPointingMod); + // + // private static readonly MelonPreferences_Category Category = + // MelonPreferences.CreateCategory(SettingsCategory); + // + // private static readonly MelonPreferences_Entry Entry_Enabled = + // Category.CreateEntry("enabled", true, display_name: "Enabled",description: "Toggle WhereAmIPointingMod entirely."); + + #endregion Melon Preferences + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(TransformManager_Patches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + internal static class TransformManager_Patches + { + // Patch for EnableTransform(DataChunk, bool) + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformManager), nameof(TransformManager.EnableTransform), new[] { typeof(DataChunk), typeof(bool) })] + private static bool OnEnableTransformChunk(TransformManager __instance, DataChunk c, bool sw, ref NativeArray ___flagArray) + { + try + { + // Enhanced validation + if (!__instance.IsValid()) + return false; + + if (___flagArray == null || !___flagArray.IsCreated) + { + Debug.LogWarning("[MagicaCloth2] EnableTransform failed: Flag array is invalid or disposed"); + return false; + } + + if (!c.IsValid || c.startIndex < 0 || c.startIndex + c.dataLength > ___flagArray.Length) + { + Debug.LogWarning($"[MagicaCloth2] EnableTransform failed: Invalid chunk parameters. Start: {c.startIndex}, Length: {c.dataLength}, Array Length: {___flagArray.Length}"); + return false; + } + + // Create and run the job with additional safety + SafeEnableTransformJob job = new() + { + chunk = c, + sw = sw, + flagList = ___flagArray, + maxLength = ___flagArray.Length + }; + + try + { + job.Run(); + } + catch (Exception ex) + { + Debug.LogError($"[MagicaCloth2] Error in EnableTransform job execution: {ex.Message}"); + return false; + } + + return false; // Prevent original method execution + } + catch (Exception ex) + { + Debug.LogError($"[MagicaCloth2] Critical error in EnableTransform patch: {ex.Message}"); + return false; + } + } + + // Patch for EnableTransform(int, bool) + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformManager), nameof(TransformManager.EnableTransform), new[] { typeof(int), typeof(bool) })] + private static bool OnEnableTransformIndex(TransformManager __instance, int index, bool sw, ref NativeArray ___flagArray) + { + try + { + // Enhanced validation + if (!__instance.IsValid()) + return false; + + if (___flagArray == null || !___flagArray.IsCreated) + { + Debug.LogWarning("[MagicaCloth2] EnableTransform failed: Flag array is invalid or disposed"); + return false; + } + + if (index < 0 || index >= ___flagArray.Length) + { + Debug.LogWarning($"[MagicaCloth2] EnableTransform failed: Index {index} out of range [0, {___flagArray.Length})"); + return false; + } + + // Safely modify the flag + var flag = ___flagArray[index]; + if (flag.Value == 0) + return false; + + flag.SetFlag(TransformManager.Flag_Enable, sw); + ___flagArray[index] = flag; + + return false; // Prevent original method execution + } + catch (Exception ex) + { + Debug.LogError($"[MagicaCloth2] Critical error in EnableTransform patch: {ex.Message}"); + return false; + } + } + + [BurstCompile] + private struct SafeEnableTransformJob : IJob + { + public DataChunk chunk; + public bool sw; + public NativeArray flagList; + [ReadOnly] public int maxLength; + + public void Execute() + { + // Additional bounds checking + if (chunk.startIndex < 0 || chunk.startIndex + chunk.dataLength > maxLength) + return; + + for (int i = 0; i < chunk.dataLength; i++) + { + int index = chunk.startIndex + i; + if (index >= maxLength) + break; + + ExBitFlag8 flag = flagList[index]; + if (flag.Value == 0) + continue; + + flag.SetFlag(TransformManager.Flag_Enable, sw); + flagList[index] = flag; + } + } + } + } +} \ No newline at end of file diff --git a/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs b/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..48c359f --- /dev/null +++ b/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.WhereAmIPointing.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.WhereAmIPointing))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.WhereAmIPointing))] + +[assembly: MelonInfo( + typeof(NAK.WhereAmIPointing.WhereAmIPointingMod), + nameof(NAK.WhereAmIPointing), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.WhereAmIPointing.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.1"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/.Deprecated/CameraExperiments/README.md b/.Deprecated/CameraExperiments/README.md new file mode 100644 index 0000000..c78a56a --- /dev/null +++ b/.Deprecated/CameraExperiments/README.md @@ -0,0 +1,14 @@ +# WhereAmIPointing + +Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/CameraExperiments/format.json b/.Deprecated/CameraExperiments/format.json new file mode 100644 index 0000000..654911a --- /dev/null +++ b/.Deprecated/CameraExperiments/format.json @@ -0,0 +1,23 @@ +{ + "_id": 234, + "name": "WhereAmIPointing", + "modversion": "1.0.1", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.", + "searchtags": [ + "controller", + "ray", + "line", + "tomato" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", + "changelog": "- Fixed line renderer alpha not being reset when the menu is closed.", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/.DepricatedMods/CameraFixes/CameraFixes.csproj b/.Deprecated/CameraFixes/CameraFixes.csproj similarity index 100% rename from .DepricatedMods/CameraFixes/CameraFixes.csproj rename to .Deprecated/CameraFixes/CameraFixes.csproj diff --git a/.DepricatedMods/CameraFixes/HarmonyPatches.cs b/.Deprecated/CameraFixes/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/CameraFixes/HarmonyPatches.cs rename to .Deprecated/CameraFixes/HarmonyPatches.cs diff --git a/.DepricatedMods/CameraFixes/Main.cs b/.Deprecated/CameraFixes/Main.cs similarity index 100% rename from .DepricatedMods/CameraFixes/Main.cs rename to .Deprecated/CameraFixes/Main.cs diff --git a/.DepricatedMods/CameraFixes/Properties/AssemblyInfo.cs b/.Deprecated/CameraFixes/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/CameraFixes/Properties/AssemblyInfo.cs rename to .Deprecated/CameraFixes/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/CameraFixes/format.json b/.Deprecated/CameraFixes/format.json similarity index 100% rename from .DepricatedMods/CameraFixes/format.json rename to .Deprecated/CameraFixes/format.json diff --git a/.DepricatedMods/ClearHudNotifications/ClearHudNotifications.csproj b/.Deprecated/ClearHudNotifications/ClearHudNotifications.csproj similarity index 100% rename from .DepricatedMods/ClearHudNotifications/ClearHudNotifications.csproj rename to .Deprecated/ClearHudNotifications/ClearHudNotifications.csproj diff --git a/.DepricatedMods/ClearHudNotifications/HarmonyPatches.cs b/.Deprecated/ClearHudNotifications/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/ClearHudNotifications/HarmonyPatches.cs rename to .Deprecated/ClearHudNotifications/HarmonyPatches.cs diff --git a/.DepricatedMods/ClearHudNotifications/Main.cs b/.Deprecated/ClearHudNotifications/Main.cs similarity index 100% rename from .DepricatedMods/ClearHudNotifications/Main.cs rename to .Deprecated/ClearHudNotifications/Main.cs diff --git a/.DepricatedMods/ClearHudNotifications/Properties/AssemblyInfo.cs b/.Deprecated/ClearHudNotifications/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/ClearHudNotifications/Properties/AssemblyInfo.cs rename to .Deprecated/ClearHudNotifications/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/ClearHudNotifications/format.json b/.Deprecated/ClearHudNotifications/format.json similarity index 100% rename from .DepricatedMods/ClearHudNotifications/format.json rename to .Deprecated/ClearHudNotifications/format.json diff --git a/.Deprecated/ControlToUnlockMouse/ControlToUnlockMouse.csproj b/.Deprecated/ControlToUnlockMouse/ControlToUnlockMouse.csproj new file mode 100644 index 0000000..0f08b56 --- /dev/null +++ b/.Deprecated/ControlToUnlockMouse/ControlToUnlockMouse.csproj @@ -0,0 +1,6 @@ + + + + SpawnableReceiveOwnChanges + + diff --git a/.Deprecated/ControlToUnlockMouse/Main.cs b/.Deprecated/ControlToUnlockMouse/Main.cs new file mode 100644 index 0000000..4ca9845 --- /dev/null +++ b/.Deprecated/ControlToUnlockMouse/Main.cs @@ -0,0 +1,308 @@ +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Core.Player; +using System.Reflection; +using cohtml.Net; +using HarmonyLib; +using UnityEngine; +using MelonLoader; +using Object = UnityEngine.Object; + +namespace NAK.ControlToUnlockMouse; + +public class ControlToUnlockMouseMod : MelonMod +{ + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(ControlToUnlockMouseMod)); + + internal static readonly MelonPreferences_Entry EntryOriginPivotPoint = + Category.CreateEntry("no_rotate_pivot_point", NoRotatePivotPoint.Pickupable, + "NoRotation Pickupable Pivot Point", "The pivot point to use when no rotation object is grabbed."); + + public enum NoRotatePivotPoint + { + Pickupable, + AvatarHead, + AvatarChest, + AvatarClosestShoulder, + } + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Awake), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPlayerSetupAwake), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(CVR_MenuManager).GetMethod(nameof(CVR_MenuManager.Start), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnMenuManagerStart), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleUnityUI), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnControllerRayHandleUnityUIDirectAndIndirect), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleIndirectUnityUI), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnControllerRayHandleUnityUIDirectAndIndirect), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(ControllerRay).GetMethod(nameof(ControllerRay.LateUpdate), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPreControllerRayLateUpdate), + BindingFlags.NonPublic | BindingFlags.Static)), + postfix: new HarmonyMethod(typeof(ControlToUnlockMouseMod).GetMethod(nameof(OnPostControllerRayLateUpdate), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnPlayerSetupAwake(PlayerSetup __instance) + { + // Get original fields + LayerMask layerMask = __instance.desktopRay.generalMask; + + // Destroy the existing desktop ray + Object.Destroy(__instance.desktopRay); + + // Get the desktop camera + Camera desktopCam = __instance.desktopCam; + + // Create a new child object under the desktop camera for the ray + GameObject rayObject = new("DesktopRay") + { + transform = + { + parent = desktopCam.transform, + localPosition = Vector3.zero, + localRotation = Quaternion.identity + } + }; + + // Add ControllerRay component + ControllerRay newRay = rayObject.AddComponent(); + newRay.isDesktopRay = true; + newRay.isInteractionRay = true; + newRay.RayDirection = Vector3.forward; + newRay.generalMask = layerMask; + newRay.hand = CVRHand.Right; // Important to even work + newRay.attachmentDistance = 0f; + newRay.currentAttachmentDistance = 0f; + + // Assign new ray to desktopRay field + __instance.desktopRay = newRay; + + // Add our custom controller script + DesktopRayController rayController = rayObject.AddComponent(); + rayController.controllerRay = newRay; + rayController.desktopCamera = desktopCam; + } + + private static void OnMenuManagerStart(CVR_MenuManager __instance) + { + __instance.desktopControllerRay = PlayerSetup.Instance.desktopRay; + } + + private static bool OnControllerRayHandleUnityUIDirectAndIndirect(ControllerRay __instance) + { + return !__instance.isDesktopRay || Cursor.lockState == CursorLockMode.Locked; + } + + private static void OnPreControllerRayLateUpdate(ControllerRay __instance, ref bool __state) + { + if (!__instance.isDesktopRay) + return; + + ViewManager menu = ViewManager.Instance; + __state = menu._gameMenuOpen; + + if (!__state) menu._gameMenuOpen = Cursor.lockState != CursorLockMode.Locked; + } + + private static void OnPostControllerRayLateUpdate(ControllerRay __instance, ref bool __state) + { + if (!__instance.isDesktopRay) return; + ViewManager.Instance._gameMenuOpen = __state; + } +} + +public class DesktopRayController : MonoBehaviour +{ + internal ControllerRay controllerRay; + internal Camera desktopCamera; + + private void Update() + { + // Toggle desktop mouse mode based on Control key state + if (Input.GetKeyDown(KeyCode.LeftControl)) + { + if (!ViewManager.Instance.IsAnyMenuOpen) RootLogic.CursorLock(false); + } + + if (Input.GetKeyUp(KeyCode.LeftControl)) + { + if (!ViewManager.Instance.IsAnyMenuOpen) RootLogic.CursorLock(true); + } + + Transform rayRoot = controllerRay.transform; + Transform rayDirection = controllerRay.rayDirectionTransform; + Transform attachment = controllerRay.attachmentPoint; + Camera cam = desktopCamera; + + if (Cursor.lockState == CursorLockMode.Locked) + { + // Reset local position when unlocked + rayRoot.localPosition = Vector3.zero; + rayRoot.localRotation = Quaternion.identity; + + // Reset local position and rotation when locked + rayDirection.localPosition = new Vector3(0f, 0f, 0.001f); + rayDirection.localRotation = Quaternion.identity; + } + else + { + bool isAnyMenuOpen = ViewManager.Instance.IsAnyMenuOpen; + Pickupable grabbedObject = controllerRay.grabbedObject; + + // Only do when not holding an origin object + Vector3 screenPos = new(Input.mousePosition.x, Input.mousePosition.y); + + if (isAnyMenuOpen) + { + // Center the ray + rayRoot.localPosition = Vector3.zero; + } + else if (grabbedObject && !grabbedObject.IsObjectRotationAllowed) + { + // Specialized movement of ray around pickupable pivot + Vector3 pivotPoint = grabbedObject.transform.position; + Vector3 pivotPointCenter = grabbedObject.RootTransform.position; + + PlayerSetup playerSetup = PlayerSetup.Instance; + if (playerSetup != null && playerSetup._animator != null && playerSetup._animator.isHuman) + { + Animator animator = playerSetup._animator; + switch (ControlToUnlockMouseMod.EntryOriginPivotPoint.Value) + { + case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarHead: + { + Transform headBone = animator.GetBoneTransform(HumanBodyBones.Head); + if (headBone != null) pivotPoint = headBone.position; + break; + } + case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarChest: + { + if (playerSetup._avatar != null) + { + Transform chestBone = animator.GetBoneTransform(HumanBodyBones.Chest); + if (chestBone != null) pivotPoint = chestBone.position; + } + break; + } + case ControlToUnlockMouseMod.NoRotatePivotPoint.AvatarClosestShoulder: + { + if (playerSetup._avatar != null) + { + Transform leftShoulder = animator.GetBoneTransform(HumanBodyBones.LeftShoulder); + Transform rightShoulder = animator.GetBoneTransform(HumanBodyBones.RightShoulder); + if (leftShoulder != null || rightShoulder != null) + { + if (leftShoulder != null && rightShoulder != null) + { + pivotPoint = Vector3.Distance(leftShoulder.position, pivotPoint) < Vector3.Distance(rightShoulder.position, pivotPoint) + ? leftShoulder.position + : rightShoulder.position; + } + else if (leftShoulder != null) + { + pivotPoint = leftShoulder.position; + } + else + { + pivotPoint = rightShoulder.position; + } + } + } + break; + } + case ControlToUnlockMouseMod.NoRotatePivotPoint.Pickupable: + default: + break; + } + } + + // Get local position of pivotPoint relative to rayRoot + // This is shit but i cant wrap my head around the proper way to compute this lol + Vector3 localPivotPoint = rayRoot.InverseTransformPoint(pivotPoint); + Vector3 localPivotPointCenter = rayRoot.InverseTransformPoint(pivotPointCenter); + localPivotPoint.x = localPivotPointCenter.x; // Maintain local X + localPivotPoint.y = localPivotPointCenter.y; // Maintain local Y + + // Compute target world position based on the mouse and attachment distance. + screenPos.z = 10f; + Vector3 targetWorldPos = cam.ScreenToWorldPoint(screenPos); + + // Desired direction from the pivot point (grabbed object) to the target world position. + Vector3 directionToTarget = targetWorldPos - rayRoot.TransformPoint(localPivotPoint);; + + if (directionToTarget.sqrMagnitude < 1e-6f) + directionToTarget = rayRoot.forward; // Fallback if mouse is centered + + // Calculate the target rotation for rayRoot. + Quaternion targetRotation = Quaternion.LookRotation(directionToTarget, cam.transform.up); + + // Get the current local offset of the grabbed object relative to rayRoot. + Vector3 localPickupOffset = rayRoot.InverseTransformPoint(pivotPoint); + + // Compute the new rayRoot position to keep the grabbed object (child) at pivotPoint. + Vector3 newRayRootPos = pivotPoint - (targetRotation * localPickupOffset); + + // Apply the new rotation and position. + rayRoot.rotation = targetRotation; + rayRoot.position = newRayRootPos; + } + else + { + float distance; + if (grabbedObject) + { + // This position is calculated basically same way as below in BasePickupHandler, + // but not determined by ray hit + distance = attachment.localPosition.z; + } + else + { + // Compute distance forward from ray + Vector3 localOffset = rayRoot.InverseTransformPoint(controllerRay._hit.point); + distance = localOffset.z; + } + + screenPos.z = distance; + + // Compute world position from where mouse is on screen + Vector3 worldPos = cam.ScreenToWorldPoint(screenPos); + + // Normal movement of ray + Vector3 newLocalPos = rayRoot.parent.InverseTransformPoint(worldPos); + newLocalPos.z = rayRoot.localPosition.z; // Maintain local Z + rayRoot.localPosition = newLocalPos; + } + + // Compute mouse ray in world space + Ray mouseRay = cam.ScreenPointToRay(Input.mousePosition); + rayDirection.position = mouseRay.origin; + rayDirection.rotation = Quaternion.LookRotation(mouseRay.direction, cam.transform.up); + } + } +} \ No newline at end of file diff --git a/.Deprecated/ControlToUnlockMouse/Properties/AssemblyInfo.cs b/.Deprecated/ControlToUnlockMouse/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9e54143 --- /dev/null +++ b/.Deprecated/ControlToUnlockMouse/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.ControlToUnlockMouse.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.ControlToUnlockMouse))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.ControlToUnlockMouse))] + +[assembly: MelonInfo( + typeof(NAK.ControlToUnlockMouse.ControlToUnlockMouseMod), + nameof(NAK.ControlToUnlockMouse), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ControlToUnlockMouse" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.ControlToUnlockMouse.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/SearchWithSpacesFix/README.md b/.Deprecated/ControlToUnlockMouse/README.md similarity index 100% rename from SearchWithSpacesFix/README.md rename to .Deprecated/ControlToUnlockMouse/README.md diff --git a/SearchWithSpacesFix/format.json b/.Deprecated/ControlToUnlockMouse/format.json similarity index 100% rename from SearchWithSpacesFix/format.json rename to .Deprecated/ControlToUnlockMouse/format.json diff --git a/.DepricatedMods/ControllerFreeze/ControllerFreeze.csproj b/.Deprecated/ControllerFreeze/ControllerFreeze.csproj similarity index 100% rename from .DepricatedMods/ControllerFreeze/ControllerFreeze.csproj rename to .Deprecated/ControllerFreeze/ControllerFreeze.csproj diff --git a/.DepricatedMods/ControllerFreeze/HarmonyPatches.cs b/.Deprecated/ControllerFreeze/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/ControllerFreeze/HarmonyPatches.cs rename to .Deprecated/ControllerFreeze/HarmonyPatches.cs diff --git a/.DepricatedMods/ControllerFreeze/Main.cs b/.Deprecated/ControllerFreeze/Main.cs similarity index 100% rename from .DepricatedMods/ControllerFreeze/Main.cs rename to .Deprecated/ControllerFreeze/Main.cs diff --git a/.DepricatedMods/ControllerFreeze/Properties/AssemblyInfo.cs b/.Deprecated/ControllerFreeze/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/ControllerFreeze/Properties/AssemblyInfo.cs rename to .Deprecated/ControllerFreeze/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/ControllerFreeze/README.md b/.Deprecated/ControllerFreeze/README.md similarity index 100% rename from .DepricatedMods/ControllerFreeze/README.md rename to .Deprecated/ControllerFreeze/README.md diff --git a/.DepricatedMods/ControllerFreeze/format.json b/.Deprecated/ControllerFreeze/format.json similarity index 100% rename from .DepricatedMods/ControllerFreeze/format.json rename to .Deprecated/ControllerFreeze/format.json diff --git a/.DepricatedMods/DesktopVRIK/DesktopVRIK.csproj b/.Deprecated/DesktopVRIK/DesktopVRIK.csproj similarity index 100% rename from .DepricatedMods/DesktopVRIK/DesktopVRIK.csproj rename to .Deprecated/DesktopVRIK/DesktopVRIK.csproj diff --git a/.DepricatedMods/DesktopVRIK/HarmonyPatches.cs b/.Deprecated/DesktopVRIK/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/HarmonyPatches.cs rename to .Deprecated/DesktopVRIK/HarmonyPatches.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/IKCalibrator.cs b/.Deprecated/DesktopVRIK/IK/IKCalibrator.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/IKCalibrator.cs rename to .Deprecated/DesktopVRIK/IK/IKCalibrator.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/IKHandlers/IKHandler.cs b/.Deprecated/DesktopVRIK/IK/IKHandlers/IKHandler.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/IKHandlers/IKHandler.cs rename to .Deprecated/DesktopVRIK/IK/IKHandlers/IKHandler.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs b/.Deprecated/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs rename to .Deprecated/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/IKManager.cs b/.Deprecated/DesktopVRIK/IK/IKManager.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/IKManager.cs rename to .Deprecated/DesktopVRIK/IK/IKManager.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/MusclePoses.cs b/.Deprecated/DesktopVRIK/IK/MusclePoses.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/MusclePoses.cs rename to .Deprecated/DesktopVRIK/IK/MusclePoses.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs b/.Deprecated/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs rename to .Deprecated/DesktopVRIK/IK/VRIKHelpers/VRIKLocomotionData.cs diff --git a/.DepricatedMods/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs b/.Deprecated/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs rename to .Deprecated/DesktopVRIK/IK/VRIKHelpers/VRIKUtils.cs diff --git a/.DepricatedMods/DesktopVRIK/Integrations/AMTAddon.cs b/.Deprecated/DesktopVRIK/Integrations/AMTAddon.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/Integrations/AMTAddon.cs rename to .Deprecated/DesktopVRIK/Integrations/AMTAddon.cs diff --git a/.DepricatedMods/DesktopVRIK/Integrations/BTKUIAddon.cs b/.Deprecated/DesktopVRIK/Integrations/BTKUIAddon.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/Integrations/BTKUIAddon.cs rename to .Deprecated/DesktopVRIK/Integrations/BTKUIAddon.cs diff --git a/.DepricatedMods/DesktopVRIK/Main.cs b/.Deprecated/DesktopVRIK/Main.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/Main.cs rename to .Deprecated/DesktopVRIK/Main.cs diff --git a/.DepricatedMods/DesktopVRIK/ModSettings.cs b/.Deprecated/DesktopVRIK/ModSettings.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/ModSettings.cs rename to .Deprecated/DesktopVRIK/ModSettings.cs diff --git a/.DepricatedMods/DesktopVRIK/Properties/AssemblyInfo.cs b/.Deprecated/DesktopVRIK/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/DesktopVRIK/Properties/AssemblyInfo.cs rename to .Deprecated/DesktopVRIK/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/DesktopVRIK/README.md b/.Deprecated/DesktopVRIK/README.md similarity index 100% rename from .DepricatedMods/DesktopVRIK/README.md rename to .Deprecated/DesktopVRIK/README.md diff --git a/.DepricatedMods/DesktopVRIK/format.json b/.Deprecated/DesktopVRIK/format.json similarity index 100% rename from .DepricatedMods/DesktopVRIK/format.json rename to .Deprecated/DesktopVRIK/format.json diff --git a/.DepricatedMods/DesktopVRSwitch/DesktopVRSwitch.csproj b/.Deprecated/DesktopVRSwitch/DesktopVRSwitch.csproj similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/DesktopVRSwitch.csproj rename to .Deprecated/DesktopVRSwitch/DesktopVRSwitch.csproj diff --git a/.DepricatedMods/DesktopVRSwitch/HarmonyPatches.cs b/.Deprecated/DesktopVRSwitch/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/HarmonyPatches.cs rename to .Deprecated/DesktopVRSwitch/HarmonyPatches.cs diff --git a/.DepricatedMods/DesktopVRSwitch/Integrations/BTKUIAddon.cs b/.Deprecated/DesktopVRSwitch/Integrations/BTKUIAddon.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Integrations/BTKUIAddon.cs rename to .Deprecated/DesktopVRSwitch/Integrations/BTKUIAddon.cs diff --git a/.DepricatedMods/DesktopVRSwitch/Main.cs b/.Deprecated/DesktopVRSwitch/Main.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Main.cs rename to .Deprecated/DesktopVRSwitch/Main.cs diff --git a/.DepricatedMods/DesktopVRSwitch/ModSettings.cs b/.Deprecated/DesktopVRSwitch/ModSettings.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/ModSettings.cs rename to .Deprecated/DesktopVRSwitch/ModSettings.cs diff --git a/.DepricatedMods/DesktopVRSwitch/Patches/DestroySteamVRInstancesImmediate.cs b/.Deprecated/DesktopVRSwitch/Patches/DestroySteamVRInstancesImmediate.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Patches/DestroySteamVRInstancesImmediate.cs rename to .Deprecated/DesktopVRSwitch/Patches/DestroySteamVRInstancesImmediate.cs diff --git a/.DepricatedMods/DesktopVRSwitch/Patches/ReferenceCameraPatch.cs b/.Deprecated/DesktopVRSwitch/Patches/ReferenceCameraPatch.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Patches/ReferenceCameraPatch.cs rename to .Deprecated/DesktopVRSwitch/Patches/ReferenceCameraPatch.cs diff --git a/.DepricatedMods/DesktopVRSwitch/Properties/AssemblyInfo.cs b/.Deprecated/DesktopVRSwitch/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Properties/AssemblyInfo.cs rename to .Deprecated/DesktopVRSwitch/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/DesktopVRSwitch/README.md b/.Deprecated/DesktopVRSwitch/README.md similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/README.md rename to .Deprecated/DesktopVRSwitch/README.md diff --git a/.DepricatedMods/DesktopVRSwitch/Utils.cs b/.Deprecated/DesktopVRSwitch/Utils.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/Utils.cs rename to .Deprecated/DesktopVRSwitch/Utils.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeSwitchDebugger.cs b/.Deprecated/DesktopVRSwitch/VRModeSwitchDebugger.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeSwitchDebugger.cs rename to .Deprecated/DesktopVRSwitch/VRModeSwitchDebugger.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeSwitchManager.cs b/.Deprecated/DesktopVRSwitch/VRModeSwitchManager.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeSwitchManager.cs rename to .Deprecated/DesktopVRSwitch/VRModeSwitchManager.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRGestureRecognizerTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVRGestureRecognizerTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRGestureRecognizerTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVRGestureRecognizerTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRInputManagerTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVRInputManagerTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRInputManagerTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVRInputManagerTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRPickupObjectTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVRPickupObjectTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRPickupObjectTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVRPickupObjectTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRWorldTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVRWorldTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVRWorldTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVRWorldTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVR_InteractableManagerTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVR_InteractableManagerTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVR_InteractableManagerTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVR_InteractableManagerTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVR_MenuManagerTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CVR_MenuManagerTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CVR_MenuManagerTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CVR_MenuManagerTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CameraFacingObjectTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CameraFacingObjectTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CameraFacingObjectTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CameraFacingObjectTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CheckVRTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CheckVRTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CheckVRTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CheckVRTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/CohtmlHudTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/CohtmlHudTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/CohtmlHudTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/CohtmlHudTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/HudOperationsTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/HudOperationsTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/HudOperationsTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/HudOperationsTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/IKSystemTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/IKSystemTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/IKSystemTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/IKSystemTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/MetaPortTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/MetaPortTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/MetaPortTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/MetaPortTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/MovementSystemTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/MovementSystemTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/MovementSystemTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/MovementSystemTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/PlayerSetupTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/PlayerSetupTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/PlayerSetupTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/PlayerSetupTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/PortableCameraTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/PortableCameraTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/PortableCameraTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/PortableCameraTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/VRModeTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/VRModeTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/VRModeTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/VRModeTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/VRModeTrackers/ViewManagerTracker.cs b/.Deprecated/DesktopVRSwitch/VRModeTrackers/ViewManagerTracker.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/VRModeTrackers/ViewManagerTracker.cs rename to .Deprecated/DesktopVRSwitch/VRModeTrackers/ViewManagerTracker.cs diff --git a/.DepricatedMods/DesktopVRSwitch/XRHandler.cs b/.Deprecated/DesktopVRSwitch/XRHandler.cs similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/XRHandler.cs rename to .Deprecated/DesktopVRSwitch/XRHandler.cs diff --git a/.DepricatedMods/DesktopVRSwitch/format.json b/.Deprecated/DesktopVRSwitch/format.json similarity index 100% rename from .DepricatedMods/DesktopVRSwitch/format.json rename to .Deprecated/DesktopVRSwitch/format.json diff --git a/DropPropTweak/DropPropTweak.csproj b/.Deprecated/DropPropTweak/DropPropTweak.csproj similarity index 100% rename from DropPropTweak/DropPropTweak.csproj rename to .Deprecated/DropPropTweak/DropPropTweak.csproj diff --git a/DropPropTweak/Main.cs b/.Deprecated/DropPropTweak/Main.cs similarity index 99% rename from DropPropTweak/Main.cs rename to .Deprecated/DropPropTweak/Main.cs index 023ce21..b72f652 100644 --- a/DropPropTweak/Main.cs +++ b/.Deprecated/DropPropTweak/Main.cs @@ -44,6 +44,4 @@ public class DropPropTweakMod : MelonMod __instance.CharacterController.gravity = ogGravity; // restore gravity return false; } - - } \ No newline at end of file diff --git a/DropPropTweak/Properties/AssemblyInfo.cs b/.Deprecated/DropPropTweak/Properties/AssemblyInfo.cs similarity index 100% rename from DropPropTweak/Properties/AssemblyInfo.cs rename to .Deprecated/DropPropTweak/Properties/AssemblyInfo.cs diff --git a/DropPropTweak/README.md b/.Deprecated/DropPropTweak/README.md similarity index 100% rename from DropPropTweak/README.md rename to .Deprecated/DropPropTweak/README.md diff --git a/DropPropTweak/format.json b/.Deprecated/DropPropTweak/format.json similarity index 100% rename from DropPropTweak/format.json rename to .Deprecated/DropPropTweak/format.json diff --git a/.DepricatedMods/EzCurls/EzCurls.csproj b/.Deprecated/EzCurls/EzCurls.csproj similarity index 100% rename from .DepricatedMods/EzCurls/EzCurls.csproj rename to .Deprecated/EzCurls/EzCurls.csproj diff --git a/.DepricatedMods/EzCurls/InputModules/InputModuleCurlAdjuster.cs b/.Deprecated/EzCurls/InputModules/InputModuleCurlAdjuster.cs similarity index 100% rename from .DepricatedMods/EzCurls/InputModules/InputModuleCurlAdjuster.cs rename to .Deprecated/EzCurls/InputModules/InputModuleCurlAdjuster.cs diff --git a/.DepricatedMods/EzCurls/Main.cs b/.Deprecated/EzCurls/Main.cs similarity index 100% rename from .DepricatedMods/EzCurls/Main.cs rename to .Deprecated/EzCurls/Main.cs diff --git a/.DepricatedMods/EzCurls/ModSettings.cs b/.Deprecated/EzCurls/ModSettings.cs similarity index 100% rename from .DepricatedMods/EzCurls/ModSettings.cs rename to .Deprecated/EzCurls/ModSettings.cs diff --git a/.DepricatedMods/EzCurls/Properties/AssemblyInfo.cs b/.Deprecated/EzCurls/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/EzCurls/Properties/AssemblyInfo.cs rename to .Deprecated/EzCurls/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/EzCurls/README.md b/.Deprecated/EzCurls/README.md similarity index 100% rename from .DepricatedMods/EzCurls/README.md rename to .Deprecated/EzCurls/README.md diff --git a/.DepricatedMods/EzCurls/format.json b/.Deprecated/EzCurls/format.json similarity index 100% rename from .DepricatedMods/EzCurls/format.json rename to .Deprecated/EzCurls/format.json diff --git a/.DepricatedMods/EzGrab/EzGrab.csproj b/.Deprecated/EzGrab/EzGrab.csproj similarity index 100% rename from .DepricatedMods/EzGrab/EzGrab.csproj rename to .Deprecated/EzGrab/EzGrab.csproj diff --git a/.DepricatedMods/EzGrab/Main.cs b/.Deprecated/EzGrab/Main.cs similarity index 100% rename from .DepricatedMods/EzGrab/Main.cs rename to .Deprecated/EzGrab/Main.cs diff --git a/.DepricatedMods/EzGrab/Properties/AssemblyInfo.cs b/.Deprecated/EzGrab/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/EzGrab/Properties/AssemblyInfo.cs rename to .Deprecated/EzGrab/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/EzGrab/format.json b/.Deprecated/EzGrab/format.json similarity index 100% rename from .DepricatedMods/EzGrab/format.json rename to .Deprecated/EzGrab/format.json diff --git a/FOVAdjustment/FOVAdjustment.csproj b/.Deprecated/FOVAdjustment/FOVAdjustment.csproj similarity index 100% rename from FOVAdjustment/FOVAdjustment.csproj rename to .Deprecated/FOVAdjustment/FOVAdjustment.csproj diff --git a/FOVAdjustment/Main.cs b/.Deprecated/FOVAdjustment/Main.cs similarity index 94% rename from FOVAdjustment/Main.cs rename to .Deprecated/FOVAdjustment/Main.cs index a593ab0..bafb6ae 100644 --- a/FOVAdjustment/Main.cs +++ b/.Deprecated/FOVAdjustment/Main.cs @@ -55,10 +55,10 @@ public class FOVAdjustment : MelonMod private static void UpdateDesktopCameraControllerFov(float value) { - if (CVRWorld.Instance != null && Mathf.Approximately(CVRWorld.Instance.fov, 60f)) - { + // if (CVRWorld.Instance != null && Mathf.Approximately(CVRWorld.Instance.fov, 60f)) + // { CVR_DesktopCameraController.defaultFov = Mathf.Clamp(value, 60f, 120f); CVR_DesktopCameraController.zoomFov = CVR_DesktopCameraController.defaultFov * 0.5f; - } + //} } } \ No newline at end of file diff --git a/FOVAdjustment/Properties/AssemblyInfo.cs b/.Deprecated/FOVAdjustment/Properties/AssemblyInfo.cs similarity index 100% rename from FOVAdjustment/Properties/AssemblyInfo.cs rename to .Deprecated/FOVAdjustment/Properties/AssemblyInfo.cs diff --git a/FOVAdjustment/README.md b/.Deprecated/FOVAdjustment/README.md similarity index 100% rename from FOVAdjustment/README.md rename to .Deprecated/FOVAdjustment/README.md diff --git a/FOVAdjustment/format.json b/.Deprecated/FOVAdjustment/format.json similarity index 100% rename from FOVAdjustment/format.json rename to .Deprecated/FOVAdjustment/format.json diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/FuckCohtmlResourceHandler.csproj b/.Deprecated/FuckCohtmlResourceHandler/FuckCohtmlResourceHandler.csproj similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/FuckCohtmlResourceHandler.csproj rename to .Deprecated/FuckCohtmlResourceHandler/FuckCohtmlResourceHandler.csproj diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/HarmonyPatches.cs b/.Deprecated/FuckCohtmlResourceHandler/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/HarmonyPatches.cs rename to .Deprecated/FuckCohtmlResourceHandler/HarmonyPatches.cs diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/Main.cs b/.Deprecated/FuckCohtmlResourceHandler/Main.cs similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/Main.cs rename to .Deprecated/FuckCohtmlResourceHandler/Main.cs diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/Properties/AssemblyInfo.cs b/.Deprecated/FuckCohtmlResourceHandler/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/Properties/AssemblyInfo.cs rename to .Deprecated/FuckCohtmlResourceHandler/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/README.md b/.Deprecated/FuckCohtmlResourceHandler/README.md similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/README.md rename to .Deprecated/FuckCohtmlResourceHandler/README.md diff --git a/.DepricatedMods/FuckCohtmlResourceHandler/format.json b/.Deprecated/FuckCohtmlResourceHandler/format.json similarity index 100% rename from .DepricatedMods/FuckCohtmlResourceHandler/format.json rename to .Deprecated/FuckCohtmlResourceHandler/format.json diff --git a/.DepricatedMods/FuckMLA/FuckMLA.csproj b/.Deprecated/FuckMLA/FuckMLA.csproj similarity index 100% rename from .DepricatedMods/FuckMLA/FuckMLA.csproj rename to .Deprecated/FuckMLA/FuckMLA.csproj diff --git a/.DepricatedMods/FuckMLA/HarmonyPatches.cs b/.Deprecated/FuckMLA/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/FuckMLA/HarmonyPatches.cs rename to .Deprecated/FuckMLA/HarmonyPatches.cs diff --git a/.DepricatedMods/FuckMLA/Main.cs b/.Deprecated/FuckMLA/Main.cs similarity index 100% rename from .DepricatedMods/FuckMLA/Main.cs rename to .Deprecated/FuckMLA/Main.cs diff --git a/.DepricatedMods/FuckMLA/Properties/AssemblyInfo.cs b/.Deprecated/FuckMLA/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/FuckMLA/Properties/AssemblyInfo.cs rename to .Deprecated/FuckMLA/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/FuckMLA/format.json b/.Deprecated/FuckMLA/format.json similarity index 100% rename from .DepricatedMods/FuckMLA/format.json rename to .Deprecated/FuckMLA/format.json diff --git a/.DepricatedMods/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj b/.Deprecated/FuckMagicaCloth2/FuckMagicaCloth2.csproj similarity index 100% rename from .DepricatedMods/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj rename to .Deprecated/FuckMagicaCloth2/FuckMagicaCloth2.csproj diff --git a/.Deprecated/FuckMagicaCloth2/Main.cs b/.Deprecated/FuckMagicaCloth2/Main.cs new file mode 100644 index 0000000..2b1421d --- /dev/null +++ b/.Deprecated/FuckMagicaCloth2/Main.cs @@ -0,0 +1,49 @@ +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using HarmonyLib; +using MagicaCloth2; +using MelonLoader; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Jobs; + +namespace NAK.FuckOffMagicaCloth2; + +public class FuckOffMagicaCloth2Mod : MelonMod +{ + private static MelonLogger.Instance Logger; + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + ApplyPatches(typeof(MagicaCloth_Patches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + internal static class MagicaCloth_Patches + { + [HarmonyPrefix] + [HarmonyPatch(typeof(MagicaCloth2.MagicaCloth), nameof(MagicaCloth2.MagicaCloth.Awake))] + private static void MagicaCloth_Awake_Prefix(MagicaCloth2.MagicaCloth __instance) + { + __instance.SerializeData.selfCollisionConstraint.selfMode = SelfCollisionConstraint.SelfCollisionMode.None; + __instance.SerializeData.selfCollisionConstraint.syncMode = SelfCollisionConstraint.SelfCollisionMode.None; + } + } +} \ No newline at end of file diff --git a/.Deprecated/FuckMagicaCloth2/Properties/AssemblyInfo.cs b/.Deprecated/FuckMagicaCloth2/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..91ab089 --- /dev/null +++ b/.Deprecated/FuckMagicaCloth2/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using MelonLoader; +using NAK.FuckOffMagicaCloth2.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.FuckOffMagicaCloth2))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.FuckOffMagicaCloth2))] + +[assembly: MelonInfo( + typeof(NAK.FuckOffMagicaCloth2.FuckOffMagicaCloth2Mod), + nameof(NAK.FuckOffMagicaCloth2), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckOffMagicaCloth2" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: HarmonyDontPatchAll] + +namespace NAK.FuckOffMagicaCloth2.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.1"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/IKSimulatedRootAngleFix/README.md b/.Deprecated/FuckMagicaCloth2/README.md similarity index 100% rename from IKSimulatedRootAngleFix/README.md rename to .Deprecated/FuckMagicaCloth2/README.md diff --git a/.Deprecated/FuckMagicaCloth2/format.json b/.Deprecated/FuckMagicaCloth2/format.json new file mode 100644 index 0000000..ddf506b --- /dev/null +++ b/.Deprecated/FuckMagicaCloth2/format.json @@ -0,0 +1,24 @@ +{ + "_id": -1, + "name": "AASDefaultProfileFix", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.", + "searchtags": [ + "aas", + "profile", + "default", + "fix", + "meow" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/.DepricatedMods/FuckMetrics/FuckMetrics.csproj b/.Deprecated/FuckMetrics/FuckMetrics.csproj similarity index 100% rename from .DepricatedMods/FuckMetrics/FuckMetrics.csproj rename to .Deprecated/FuckMetrics/FuckMetrics.csproj diff --git a/.DepricatedMods/FuckMetrics/HarmonyPatches.cs b/.Deprecated/FuckMetrics/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/FuckMetrics/HarmonyPatches.cs rename to .Deprecated/FuckMetrics/HarmonyPatches.cs diff --git a/.DepricatedMods/FuckMetrics/Main.cs b/.Deprecated/FuckMetrics/Main.cs similarity index 100% rename from .DepricatedMods/FuckMetrics/Main.cs rename to .Deprecated/FuckMetrics/Main.cs diff --git a/.DepricatedMods/FuckMetrics/ManagedLibs/.keep b/.Deprecated/FuckMetrics/ManagedLibs/.keep similarity index 100% rename from .DepricatedMods/FuckMetrics/ManagedLibs/.keep rename to .Deprecated/FuckMetrics/ManagedLibs/.keep diff --git a/.DepricatedMods/FuckMetrics/Properties/AssemblyInfo.cs b/.Deprecated/FuckMetrics/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/FuckMetrics/Properties/AssemblyInfo.cs rename to .Deprecated/FuckMetrics/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/FuckMetrics/format.json b/.Deprecated/FuckMetrics/format.json similarity index 100% rename from .DepricatedMods/FuckMetrics/format.json rename to .Deprecated/FuckMetrics/format.json diff --git a/.Deprecated/FuckOffUICamera/CohtmlRenderForwarder.cs b/.Deprecated/FuckOffUICamera/CohtmlRenderForwarder.cs new file mode 100644 index 0000000..5f5dd2a --- /dev/null +++ b/.Deprecated/FuckOffUICamera/CohtmlRenderForwarder.cs @@ -0,0 +1,35 @@ +using ABI_RC.Core.UI; +using UnityEngine; + +namespace NAK.FuckOffUICamera; + +[RequireComponent(typeof(Camera))] +public class CohtmlRenderForwarder : MonoBehaviour +{ + #region Private Variables + + private CohtmlControlledView[] controlledViews; + + #endregion Private Variables + + #region Unity Events + + private void OnPreRender() + { + if (controlledViews == null) return; + foreach (CohtmlControlledView view in controlledViews) + if (view) view.OnPreRender(); + } + + #endregion Unity Events + + #region Public Methods + + public static void Setup(Camera camera, params CohtmlControlledView[] views) + { + CohtmlRenderForwarder forwarder = camera.gameObject.AddComponent(); + forwarder.controlledViews = views; + } + + #endregion Public Methods +} \ No newline at end of file diff --git a/.Deprecated/FuckOffUICamera/CommandBufferManager.cs b/.Deprecated/FuckOffUICamera/CommandBufferManager.cs new file mode 100644 index 0000000..aa76f1f --- /dev/null +++ b/.Deprecated/FuckOffUICamera/CommandBufferManager.cs @@ -0,0 +1,144 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.Rendering; + +namespace NAK.FuckOffUICamera; + +public class CommandBufferManager : MonoBehaviour +{ + #region Private Variables + + private CommandBuffer commandBuffer; + private Camera targetCamera; + private Renderer[] targetRenderers; + private bool[] rendererEnabledStates; + private const string CommandBufferName = "CustomRenderPass"; + private bool _didSetup; + + #endregion Private Variables + + #region Unity Events + + private IEnumerator Start() + { + yield return new WaitForSeconds(2f); // I have no idea why this needs to be delayed + _didSetup = true; + OnEnable(); + } + + private void OnEnable() + { + if (!_didSetup) return; + if (targetCamera == null || targetRenderers == null) + return; + + SetupEnabledStateCollection(); + SetupCommandBuffer(); + } + + private void OnDisable() + { + CleanupCommandBuffer(); + } + + private void LateUpdate() + { + if (targetRenderers == null + || rendererEnabledStates == null) + return; + + bool needsRebuild = false; + + // Check if any renderer enabled states have changed + int targetRenderersLength = targetRenderers.Length; + for (int i = 0; i < targetRenderersLength; i++) + { + if (targetRenderers[i] == null) continue; + + bool currentState = targetRenderers[i].enabled && targetRenderers[i].gameObject.activeInHierarchy; + if (currentState == rendererEnabledStates[i]) + continue; + + rendererEnabledStates[i] = currentState; + needsRebuild = true; + } + + if (needsRebuild) RebuildCommandBuffer(); + } + + #endregion Unity Events + + #region Public Methods + + public static void Setup(Camera camera, params Renderer[] renderers) + { + CommandBufferManager manager = camera.gameObject.AddComponent(); + manager.targetCamera = camera; + manager.targetRenderers = renderers; + } + + #endregion Public Methods + + #region Private Methods + + private void SetupEnabledStateCollection() + { + if (rendererEnabledStates != null) + Array.Resize(ref rendererEnabledStates, targetRenderers.Length); + else + rendererEnabledStates = new bool[targetRenderers.Length]; + } + + private void SetupCommandBuffer() + { + commandBuffer = new CommandBuffer(); + commandBuffer.name = CommandBufferName; + + // Set render target and clear depth + commandBuffer.SetRenderTarget(new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget, + 0, CubemapFace.Unknown, RenderTargetIdentifier.AllDepthSlices)); + + commandBuffer.ClearRenderTarget(true, false, Color.clear); + + for (int i = 0; i < targetRenderers.Length; i++) + { + Renderer renderer = targetRenderers[i]; + if (renderer == null || !rendererEnabledStates[i]) + continue; + + commandBuffer.DrawRenderer(renderer, renderer.sharedMaterial); + renderer.forceRenderingOff = true; + } + + targetCamera.AddCommandBuffer(CameraEvent.AfterImageEffects, commandBuffer); + + Debug.Log($"Command buffer setup for {targetCamera.name} with {targetRenderers.Length} renderers."); + } + + private void RebuildCommandBuffer() + { + CleanupCommandBuffer(); + SetupCommandBuffer(); + } + + private void CleanupCommandBuffer() + { + if (targetCamera == null || commandBuffer == null) + return; + + // Re-enable normal rendering for all renderers + if (targetRenderers != null) + { + foreach (Renderer renderer in targetRenderers) + { + if (renderer != null) + renderer.forceRenderingOff = false; + } + } + + targetCamera.RemoveCommandBuffer(CameraEvent.AfterImageEffects, commandBuffer); + commandBuffer = null; + } + + #endregion Private Methods +} \ No newline at end of file diff --git a/.DepricatedMods/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj b/.Deprecated/FuckOffUICamera/FuckOffUICamera.csproj similarity index 100% rename from .DepricatedMods/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj rename to .Deprecated/FuckOffUICamera/FuckOffUICamera.csproj diff --git a/.Deprecated/FuckOffUICamera/Main.cs b/.Deprecated/FuckOffUICamera/Main.cs new file mode 100644 index 0000000..e4c9092 --- /dev/null +++ b/.Deprecated/FuckOffUICamera/Main.cs @@ -0,0 +1,73 @@ +using System.Reflection; +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using ABI_RC.Core.UI; +using HarmonyLib; +using MelonLoader; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace NAK.FuckOffUICamera; + +public class FuckOffUICameraMod : MelonMod +{ + private static MelonLogger.Instance Logger; + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + } + + public override void OnSceneWasLoaded(int buildIndex, string sceneName) + { + if (buildIndex != 2) return; + if (_isInitialized) return; + SetupShittyMod(); + _isInitialized = true; + } + + private bool _isInitialized; + + private static void SetupShittyMod() + { + // Find all renderers under Cohtml object + GameObject cohtml = GameObject.Find("Cohtml"); + if (cohtml == null) + { + Logger.Error("Cohtml object not found!"); + return; + } + + // Find all CohtmlControlledView objects + var allMenuCohtml = Object.FindObjectsOfType(includeInactive: true); + var allUiInternalRenderers = Object.FindObjectsOfType(includeInactive: true) + .Where(x => x.gameObject.layer == CVRLayers.UIInternal) + .ToArray(); + + //var allMenuRenderers = cohtml.GetComponentsInChildren(true); + + // Add hud renderer to the list of renderers + Renderer hudRenderer = CohtmlHud.Instance.GetComponent(); + // Array.Resize(ref allMenuRenderers, allMenuRenderers.Length + 1); + // allMenuRenderers[^1] = hudRenderer; + + // Fix shader on the hud renderer + Material material = hudRenderer.sharedMaterial; + material.shader = Shader.Find("Alpha Blend Interactive/MenuFX"); + + // Setup command buffer manager for desktop camera + CommandBufferManager.Setup(PlayerSetup.Instance.desktopCam, allUiInternalRenderers); + CohtmlRenderForwarder.Setup(PlayerSetup.Instance.desktopCam, allMenuCohtml); + + // Setup command buffer manager for vr camera + CommandBufferManager.Setup(PlayerSetup.Instance.vrCam, allUiInternalRenderers); + CohtmlRenderForwarder.Setup(PlayerSetup.Instance.vrCam, allMenuCohtml); + + // Disable the ui cameras + PlayerSetup.Instance.desktopUiCam.gameObject.SetActive(false); + PlayerSetup.Instance.vrUiCam.gameObject.SetActive(false); + + Logger.Msg("Disabled UI cameras and setup command buffer manager for Cohtml renderers."); + } +} \ No newline at end of file diff --git a/PhysicsGunMod/Properties/AssemblyInfo.cs b/.Deprecated/FuckOffUICamera/Properties/AssemblyInfo.cs similarity index 67% rename from PhysicsGunMod/Properties/AssemblyInfo.cs rename to .Deprecated/FuckOffUICamera/Properties/AssemblyInfo.cs index df4ffd2..b7a9e9f 100644 --- a/PhysicsGunMod/Properties/AssemblyInfo.cs +++ b/.Deprecated/FuckOffUICamera/Properties/AssemblyInfo.cs @@ -1,30 +1,30 @@ using MelonLoader; -using NAK.PhysicsGunMod.Properties; +using NAK.FuckOffUICamera.Properties; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.PhysicsGunMod))] +[assembly: AssemblyTitle(nameof(NAK.FuckOffUICamera))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.PhysicsGunMod))] +[assembly: AssemblyProduct(nameof(NAK.FuckOffUICamera))] [assembly: MelonInfo( - typeof(NAK.PhysicsGunMod.PhysicsGunMod), - nameof(NAK.PhysicsGunMod), + typeof(NAK.FuckOffUICamera.FuckOffUICameraMod), + nameof(NAK.FuckOffUICamera), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PhysicsGunMod" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckOffUICamera" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 241, 200, 82)] -[assembly: MelonAuthorColor(255, 114, 17, 25)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.PhysicsGunMod.Properties; +namespace NAK.FuckOffUICamera.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/.Deprecated/FuckOffUICamera/README.md b/.Deprecated/FuckOffUICamera/README.md new file mode 100644 index 0000000..1c4c5bc --- /dev/null +++ b/.Deprecated/FuckOffUICamera/README.md @@ -0,0 +1,14 @@ +# SearchWithSpacesFix + +Fixes search terms that use spaces. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/FuckOffUICamera/format.json b/.Deprecated/FuckOffUICamera/format.json new file mode 100644 index 0000000..f8950ca --- /dev/null +++ b/.Deprecated/FuckOffUICamera/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "SearchWithSpacesFix", + "modversion": "1.0.0", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes search terms that include spaces.", + "searchtags": [ + "search", + "spaces", + "fix", + "meow" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/.DepricatedMods/FuckVivox/FuckVivox.csproj b/.Deprecated/FuckVivox/FuckVivox.csproj similarity index 100% rename from .DepricatedMods/FuckVivox/FuckVivox.csproj rename to .Deprecated/FuckVivox/FuckVivox.csproj diff --git a/.DepricatedMods/FuckVivox/HarmonyPatches.cs b/.Deprecated/FuckVivox/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/FuckVivox/HarmonyPatches.cs rename to .Deprecated/FuckVivox/HarmonyPatches.cs diff --git a/.DepricatedMods/FuckVivox/Main.cs b/.Deprecated/FuckVivox/Main.cs similarity index 100% rename from .DepricatedMods/FuckVivox/Main.cs rename to .Deprecated/FuckVivox/Main.cs diff --git a/.DepricatedMods/FuckVivox/Properties/AssemblyInfo.cs b/.Deprecated/FuckVivox/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/FuckVivox/Properties/AssemblyInfo.cs rename to .Deprecated/FuckVivox/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/FuckVivox/VivoxHelpers.cs b/.Deprecated/FuckVivox/VivoxHelpers.cs similarity index 100% rename from .DepricatedMods/FuckVivox/VivoxHelpers.cs rename to .Deprecated/FuckVivox/VivoxHelpers.cs diff --git a/.DepricatedMods/FuckVivox/WindowFocusManager.cs b/.Deprecated/FuckVivox/WindowFocusManager.cs similarity index 100% rename from .DepricatedMods/FuckVivox/WindowFocusManager.cs rename to .Deprecated/FuckVivox/WindowFocusManager.cs diff --git a/.DepricatedMods/FuckVivox/format.json b/.Deprecated/FuckVivox/format.json similarity index 100% rename from .DepricatedMods/FuckVivox/format.json rename to .Deprecated/FuckVivox/format.json diff --git a/GestureLock/GestureLock.csproj b/.Deprecated/GestureLock/GestureLock.csproj similarity index 100% rename from GestureLock/GestureLock.csproj rename to .Deprecated/GestureLock/GestureLock.csproj diff --git a/GestureLock/HarmonyPatches.cs b/.Deprecated/GestureLock/HarmonyPatches.cs similarity index 100% rename from GestureLock/HarmonyPatches.cs rename to .Deprecated/GestureLock/HarmonyPatches.cs diff --git a/GestureLock/Main.cs b/.Deprecated/GestureLock/Main.cs similarity index 100% rename from GestureLock/Main.cs rename to .Deprecated/GestureLock/Main.cs diff --git a/GestureLock/Properties/AssemblyInfo.cs b/.Deprecated/GestureLock/Properties/AssemblyInfo.cs similarity index 100% rename from GestureLock/Properties/AssemblyInfo.cs rename to .Deprecated/GestureLock/Properties/AssemblyInfo.cs diff --git a/GestureLock/README.md b/.Deprecated/GestureLock/README.md similarity index 100% rename from GestureLock/README.md rename to .Deprecated/GestureLock/README.md diff --git a/GestureLock/format.json b/.Deprecated/GestureLock/format.json similarity index 100% rename from GestureLock/format.json rename to .Deprecated/GestureLock/format.json diff --git a/.DepricatedMods/HeadBobbingFix/HarmonyPatches.cs b/.Deprecated/HeadBobbingFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/HeadBobbingFix/HarmonyPatches.cs rename to .Deprecated/HeadBobbingFix/HarmonyPatches.cs diff --git a/.DepricatedMods/HeadBobbingFix/HeadBobbingFix.csproj b/.Deprecated/HeadBobbingFix/HeadBobbingFix.csproj similarity index 100% rename from .DepricatedMods/HeadBobbingFix/HeadBobbingFix.csproj rename to .Deprecated/HeadBobbingFix/HeadBobbingFix.csproj diff --git a/.DepricatedMods/HeadBobbingFix/Main.cs b/.Deprecated/HeadBobbingFix/Main.cs similarity index 100% rename from .DepricatedMods/HeadBobbingFix/Main.cs rename to .Deprecated/HeadBobbingFix/Main.cs diff --git a/.DepricatedMods/HeadBobbingFix/Properties/AssemblyInfo.cs b/.Deprecated/HeadBobbingFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/HeadBobbingFix/Properties/AssemblyInfo.cs rename to .Deprecated/HeadBobbingFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/HeadBobbingFix/README.md b/.Deprecated/HeadBobbingFix/README.md similarity index 100% rename from .DepricatedMods/HeadBobbingFix/README.md rename to .Deprecated/HeadBobbingFix/README.md diff --git a/.DepricatedMods/HeadBobbingFix/format.json b/.Deprecated/HeadBobbingFix/format.json similarity index 100% rename from .DepricatedMods/HeadBobbingFix/format.json rename to .Deprecated/HeadBobbingFix/format.json diff --git a/ReconnectionSystemFix/ReconnectionSystemFix.csproj b/.Deprecated/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj similarity index 100% rename from ReconnectionSystemFix/ReconnectionSystemFix.csproj rename to .Deprecated/HeadLookLockingInputFix/HeadLookLockingInputFix.csproj diff --git a/.DepricatedMods/HeadLookLockingInputFix/Main.cs b/.Deprecated/HeadLookLockingInputFix/Main.cs similarity index 100% rename from .DepricatedMods/HeadLookLockingInputFix/Main.cs rename to .Deprecated/HeadLookLockingInputFix/Main.cs diff --git a/.DepricatedMods/HeadLookLockingInputFix/Properties/AssemblyInfo.cs b/.Deprecated/HeadLookLockingInputFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/HeadLookLockingInputFix/Properties/AssemblyInfo.cs rename to .Deprecated/HeadLookLockingInputFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/HeadLookLockingInputFix/README.md b/.Deprecated/HeadLookLockingInputFix/README.md similarity index 100% rename from .DepricatedMods/HeadLookLockingInputFix/README.md rename to .Deprecated/HeadLookLockingInputFix/README.md diff --git a/.DepricatedMods/HeadLookLockingInputFix/format.json b/.Deprecated/HeadLookLockingInputFix/format.json similarity index 100% rename from .DepricatedMods/HeadLookLockingInputFix/format.json rename to .Deprecated/HeadLookLockingInputFix/format.json diff --git a/.DepricatedMods/IKAdjustments/HarmonyPatches.cs b/.Deprecated/IKAdjustments/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/IKAdjustments/HarmonyPatches.cs rename to .Deprecated/IKAdjustments/HarmonyPatches.cs diff --git a/.DepricatedMods/IKAdjustments/IKAdjuster.cs b/.Deprecated/IKAdjustments/IKAdjuster.cs similarity index 100% rename from .DepricatedMods/IKAdjustments/IKAdjuster.cs rename to .Deprecated/IKAdjustments/IKAdjuster.cs diff --git a/.DepricatedMods/IKAdjustments/IKAdjustments.csproj b/.Deprecated/IKAdjustments/IKAdjustments.csproj similarity index 100% rename from .DepricatedMods/IKAdjustments/IKAdjustments.csproj rename to .Deprecated/IKAdjustments/IKAdjustments.csproj diff --git a/.DepricatedMods/IKAdjustments/Integrations/BTKUIAddon.cs b/.Deprecated/IKAdjustments/Integrations/BTKUIAddon.cs similarity index 100% rename from .DepricatedMods/IKAdjustments/Integrations/BTKUIAddon.cs rename to .Deprecated/IKAdjustments/Integrations/BTKUIAddon.cs diff --git a/.DepricatedMods/IKAdjustments/Main.cs b/.Deprecated/IKAdjustments/Main.cs similarity index 100% rename from .DepricatedMods/IKAdjustments/Main.cs rename to .Deprecated/IKAdjustments/Main.cs diff --git a/.DepricatedMods/IKAdjustments/Properties/AssemblyInfo.cs b/.Deprecated/IKAdjustments/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/IKAdjustments/Properties/AssemblyInfo.cs rename to .Deprecated/IKAdjustments/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/IKFixes/HarmonyPatches.cs b/.Deprecated/IKFixes/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/IKFixes/HarmonyPatches.cs rename to .Deprecated/IKFixes/HarmonyPatches.cs diff --git a/.DepricatedMods/IKFixes/IKFixes.csproj b/.Deprecated/IKFixes/IKFixes.csproj similarity index 100% rename from .DepricatedMods/IKFixes/IKFixes.csproj rename to .Deprecated/IKFixes/IKFixes.csproj diff --git a/.DepricatedMods/IKFixes/Integrations/UIExKitAddon.cs b/.Deprecated/IKFixes/Integrations/UIExKitAddon.cs similarity index 100% rename from .DepricatedMods/IKFixes/Integrations/UIExKitAddon.cs rename to .Deprecated/IKFixes/Integrations/UIExKitAddon.cs diff --git a/.DepricatedMods/IKFixes/Main.cs b/.Deprecated/IKFixes/Main.cs similarity index 100% rename from .DepricatedMods/IKFixes/Main.cs rename to .Deprecated/IKFixes/Main.cs diff --git a/.DepricatedMods/IKFixes/Properties/AssemblyInfo.cs b/.Deprecated/IKFixes/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/IKFixes/Properties/AssemblyInfo.cs rename to .Deprecated/IKFixes/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/IKFixes/README.md b/.Deprecated/IKFixes/README.md similarity index 100% rename from .DepricatedMods/IKFixes/README.md rename to .Deprecated/IKFixes/README.md diff --git a/.DepricatedMods/IKFixes/format.json b/.Deprecated/IKFixes/format.json similarity index 100% rename from .DepricatedMods/IKFixes/format.json rename to .Deprecated/IKFixes/format.json diff --git a/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj b/.Deprecated/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj similarity index 100% rename from IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj rename to .Deprecated/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj diff --git a/IKSimulatedRootAngleFix/Main.cs b/.Deprecated/IKSimulatedRootAngleFix/Main.cs similarity index 100% rename from IKSimulatedRootAngleFix/Main.cs rename to .Deprecated/IKSimulatedRootAngleFix/Main.cs diff --git a/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs b/.Deprecated/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs similarity index 100% rename from IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs rename to .Deprecated/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs diff --git a/.Deprecated/IKSimulatedRootAngleFix/README.md b/.Deprecated/IKSimulatedRootAngleFix/README.md new file mode 100644 index 0000000..8a46af2 --- /dev/null +++ b/.Deprecated/IKSimulatedRootAngleFix/README.md @@ -0,0 +1,14 @@ +# IKSimulatedRootAngleFix + +Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/IKSimulatedRootAngleFix/format.json b/.Deprecated/IKSimulatedRootAngleFix/format.json similarity index 100% rename from IKSimulatedRootAngleFix/format.json rename to .Deprecated/IKSimulatedRootAngleFix/format.json diff --git a/.DepricatedMods/InteractionTest/AutoArmIK.cs b/.Deprecated/InteractionTest/AutoArmIK.cs similarity index 100% rename from .DepricatedMods/InteractionTest/AutoArmIK.cs rename to .Deprecated/InteractionTest/AutoArmIK.cs diff --git a/.DepricatedMods/InteractionTest/ColliderTest/AvatarColliderStruct.cs b/.Deprecated/InteractionTest/ColliderTest/AvatarColliderStruct.cs similarity index 100% rename from .DepricatedMods/InteractionTest/ColliderTest/AvatarColliderStruct.cs rename to .Deprecated/InteractionTest/ColliderTest/AvatarColliderStruct.cs diff --git a/.DepricatedMods/InteractionTest/ColliderTest/AvatarColliders.cs b/.Deprecated/InteractionTest/ColliderTest/AvatarColliders.cs similarity index 100% rename from .DepricatedMods/InteractionTest/ColliderTest/AvatarColliders.cs rename to .Deprecated/InteractionTest/ColliderTest/AvatarColliders.cs diff --git a/.DepricatedMods/InteractionTest/ColliderTest/LineColliderTest.cs b/.Deprecated/InteractionTest/ColliderTest/LineColliderTest.cs similarity index 100% rename from .DepricatedMods/InteractionTest/ColliderTest/LineColliderTest.cs rename to .Deprecated/InteractionTest/ColliderTest/LineColliderTest.cs diff --git a/.DepricatedMods/InteractionTest/GrabbableAvatar.cs b/.Deprecated/InteractionTest/GrabbableAvatar.cs similarity index 100% rename from .DepricatedMods/InteractionTest/GrabbableAvatar.cs rename to .Deprecated/InteractionTest/GrabbableAvatar.cs diff --git a/.DepricatedMods/InteractionTest/GrabbingAvatar.cs b/.Deprecated/InteractionTest/GrabbingAvatar.cs similarity index 100% rename from .DepricatedMods/InteractionTest/GrabbingAvatar.cs rename to .Deprecated/InteractionTest/GrabbingAvatar.cs diff --git a/.DepricatedMods/InteractionTest/HarmonyPatches.cs b/.Deprecated/InteractionTest/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/InteractionTest/HarmonyPatches.cs rename to .Deprecated/InteractionTest/HarmonyPatches.cs diff --git a/.DepricatedMods/InteractionTest/InteractionTest.csproj b/.Deprecated/InteractionTest/InteractionTest.csproj similarity index 100% rename from .DepricatedMods/InteractionTest/InteractionTest.csproj rename to .Deprecated/InteractionTest/InteractionTest.csproj diff --git a/.DepricatedMods/InteractionTest/Main.cs b/.Deprecated/InteractionTest/Main.cs similarity index 100% rename from .DepricatedMods/InteractionTest/Main.cs rename to .Deprecated/InteractionTest/Main.cs diff --git a/.DepricatedMods/InteractionTest/Properties/AssemblyInfo.cs b/.Deprecated/InteractionTest/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/InteractionTest/Properties/AssemblyInfo.cs rename to .Deprecated/InteractionTest/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/InteractionTest/README.md b/.Deprecated/InteractionTest/README.md similarity index 100% rename from .DepricatedMods/InteractionTest/README.md rename to .Deprecated/InteractionTest/README.md diff --git a/AvatarScaleMod/format.json b/.Deprecated/InteractionTest/format.json similarity index 100% rename from AvatarScaleMod/format.json rename to .Deprecated/InteractionTest/format.json diff --git a/.DepricatedMods/JumpPatch/HarmonyPatches.cs b/.Deprecated/JumpPatch/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/JumpPatch/HarmonyPatches.cs rename to .Deprecated/JumpPatch/HarmonyPatches.cs diff --git a/.DepricatedMods/JumpPatch/JumpPatch.csproj b/.Deprecated/JumpPatch/JumpPatch.csproj similarity index 100% rename from .DepricatedMods/JumpPatch/JumpPatch.csproj rename to .Deprecated/JumpPatch/JumpPatch.csproj diff --git a/.DepricatedMods/JumpPatch/Main.cs b/.Deprecated/JumpPatch/Main.cs similarity index 100% rename from .DepricatedMods/JumpPatch/Main.cs rename to .Deprecated/JumpPatch/Main.cs diff --git a/.DepricatedMods/JumpPatch/Properties/AssemblyInfo.cs b/.Deprecated/JumpPatch/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/JumpPatch/Properties/AssemblyInfo.cs rename to .Deprecated/JumpPatch/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/JumpPatch/format.json b/.Deprecated/JumpPatch/format.json similarity index 100% rename from .DepricatedMods/JumpPatch/format.json rename to .Deprecated/JumpPatch/format.json diff --git a/.DepricatedMods/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj b/.Deprecated/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj similarity index 100% rename from .DepricatedMods/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj rename to .Deprecated/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj diff --git a/.DepricatedMods/LateInitComponentHelperHack/Main.cs b/.Deprecated/LateInitComponentHelperHack/Main.cs similarity index 100% rename from .DepricatedMods/LateInitComponentHelperHack/Main.cs rename to .Deprecated/LateInitComponentHelperHack/Main.cs diff --git a/.DepricatedMods/LateInitComponentHelperHack/Properties/AssemblyInfo.cs b/.Deprecated/LateInitComponentHelperHack/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/LateInitComponentHelperHack/Properties/AssemblyInfo.cs rename to .Deprecated/LateInitComponentHelperHack/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/LateInitComponentHelperHack/format.json b/.Deprecated/LateInitComponentHelperHack/format.json similarity index 100% rename from .DepricatedMods/LateInitComponentHelperHack/format.json rename to .Deprecated/LateInitComponentHelperHack/format.json diff --git a/LegacyContentMitigation/Components/CameraCallbackLogger.cs b/.Deprecated/LegacyContentMitigation/Components/CameraCallbackLogger.cs similarity index 100% rename from LegacyContentMitigation/Components/CameraCallbackLogger.cs rename to .Deprecated/LegacyContentMitigation/Components/CameraCallbackLogger.cs diff --git a/.Deprecated/LegacyContentMitigation/Components/FaceMirror.cs b/.Deprecated/LegacyContentMitigation/Components/FaceMirror.cs new file mode 100644 index 0000000..65f3e3b --- /dev/null +++ b/.Deprecated/LegacyContentMitigation/Components/FaceMirror.cs @@ -0,0 +1,100 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using NAK.LegacyContentMitigation; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.XR; + +namespace LegacyContentMitigation.Components; + +public class FaceMirror : MonoBehaviour +{ + private Camera _parentCamera; + private Camera _camera; + public Rect shiftRect; + private CommandBuffer _viewportBuffer; + + private void Start() { + _parentCamera = GetComponent(); + _camera = new GameObject("Face Mirror").AddComponent(); + _camera.transform.parent = transform; + _camera.CopyFrom(_parentCamera); + _camera.ResetReplacementShader(); + _camera.depth = 99; + _camera.clearFlags = CameraClearFlags.Depth; + _camera.transform.position += transform.forward * 0.5f; + _camera.transform.rotation *= Quaternion.Euler(0, 180, 0); + + // View only CVRLayers.PlayerLocal + _camera.cullingMask = 1 << CVRLayers.PlayerLocal; + + // Create and cache the command buffer + _viewportBuffer = new CommandBuffer(); + _viewportBuffer.SetViewport(shiftRect); + + _camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _viewportBuffer); + _camera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, _viewportBuffer); + _camera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _viewportBuffer); + _camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _viewportBuffer); + } + + private void Update() + { + if (ModSettings.EntryUseFaceMirror.Value == false) + { + _camera.enabled = false; + return; + } + _camera.enabled = true; + + // Update camera distance + _camera.transform.localPosition = Vector3.forward * ModSettings.EntryFaceMirrorDistance.Value; + + // Get the display resolution based on VR status + int displayWidth, displayHeight; + if (MetaPort.Instance.isUsingVr) + { + displayWidth = XRSettings.eyeTextureWidth; + displayHeight = XRSettings.eyeTextureHeight; + } + else + { + displayWidth = Screen.width; + displayHeight = Screen.height; + } + + // Calculate pixel sizes first + float pixelSizeX = ModSettings.EntryFaceMirrorSizeX.Value * displayWidth; + float pixelSizeY = ModSettings.EntryFaceMirrorSizeY.Value * displayHeight; + + // Calculate offsets from center + float pixelOffsetX = (ModSettings.EntryFaceMirrorOffsetX.Value * displayWidth) - (pixelSizeX * 0.5f) + (displayWidth * 0.5f); + float pixelOffsetY = (ModSettings.EntryFaceMirrorOffsetY.Value * displayHeight) - (pixelSizeY * 0.5f) + (displayHeight * 0.5f); + + _camera.transform.localScale = Vector3.one * ModSettings.EntryFaceMirrorCameraScale.Value; + + Vector3 playerup = PlayerSetup.Instance.transform.up; + Vector3 cameraForward = _parentCamera.transform.forward; + + // Check if playerup and cameraForward are nearly aligned + if (Mathf.Abs(Vector3.Dot(playerup, cameraForward)) <= Mathf.Epsilon) { + playerup = -_parentCamera.transform.forward; + cameraForward = _parentCamera.transform.up; + } + + _camera.transform.rotation = Quaternion.LookRotation(-cameraForward, playerup); + + // Create viewport rect with pixel values + shiftRect = new Rect( + pixelOffsetX, + pixelOffsetY, + pixelSizeX, + pixelSizeY + ); + + // Update the cached buffer's viewport + _viewportBuffer.Clear(); + _viewportBuffer.SetViewport(shiftRect); + } +} \ No newline at end of file diff --git a/LegacyContentMitigation/Components/FakeMultiPassHack.cs b/.Deprecated/LegacyContentMitigation/Components/FakeMultiPassHack.cs similarity index 100% rename from LegacyContentMitigation/Components/FakeMultiPassHack.cs rename to .Deprecated/LegacyContentMitigation/Components/FakeMultiPassHack.cs diff --git a/LegacyContentMitigation/Integrations/BtkUiAddon.cs b/.Deprecated/LegacyContentMitigation/Integrations/BtkUiAddon.cs similarity index 100% rename from LegacyContentMitigation/Integrations/BtkUiAddon.cs rename to .Deprecated/LegacyContentMitigation/Integrations/BtkUiAddon.cs diff --git a/LegacyContentMitigation/LegacyContentMitigation.csproj b/.Deprecated/LegacyContentMitigation/LegacyContentMitigation.csproj similarity index 100% rename from LegacyContentMitigation/LegacyContentMitigation.csproj rename to .Deprecated/LegacyContentMitigation/LegacyContentMitigation.csproj diff --git a/LegacyContentMitigation/Main.cs b/.Deprecated/LegacyContentMitigation/Main.cs similarity index 100% rename from LegacyContentMitigation/Main.cs rename to .Deprecated/LegacyContentMitigation/Main.cs diff --git a/.Deprecated/LegacyContentMitigation/ModSettings.cs b/.Deprecated/LegacyContentMitigation/ModSettings.cs new file mode 100644 index 0000000..a20c93d --- /dev/null +++ b/.Deprecated/LegacyContentMitigation/ModSettings.cs @@ -0,0 +1,52 @@ +using MelonLoader; + +namespace NAK.LegacyContentMitigation; + +internal static class ModSettings +{ + #region Constants + + internal const string ModName = nameof(LegacyContentMitigation); + internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = + Category.CreateEntry("auto_for_legacy_worlds", true, + "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorDistance = + Category.CreateEntry("face_mirror_distance", 0.5f, + "Face Mirror Distance", description: "Distance from the camera to place the face mirror."); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorOffsetX = + Category.CreateEntry("face_mirror_offset_x", 0f, + "Face Mirror Offset X", description: "Offset the face mirror on the X axis."); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorOffsetY = + Category.CreateEntry("face_mirror_offset_y", 0f, + "Face Mirror Offset Y", description: "Offset the face mirror on the Y axis."); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorSizeX = + Category.CreateEntry("face_mirror_size_x", 0.5f, + "Face Mirror Size X", description: "Size of the face mirror on the X axis."); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorSizeY = + Category.CreateEntry("face_mirror_size_y", 0.5f, + "Face Mirror Size Y", description: "Size of the face mirror on the Y axis."); + + internal static readonly MelonPreferences_Entry EntryFaceMirrorCameraScale = + Category.CreateEntry("face_mirror_camera_scale", 1f, + "Face Mirror Camera Scale", description: "Scale of the face mirror camera."); + + internal static readonly MelonPreferences_Entry EntryUseFaceMirror = + Category.CreateEntry("use_face_mirror", true, + "Use Face Mirror", description: "Should the face mirror be used?"); + + #endregion Melon Preferences +} \ No newline at end of file diff --git a/LegacyContentMitigation/Patches.cs b/.Deprecated/LegacyContentMitigation/Patches.cs similarity index 97% rename from LegacyContentMitigation/Patches.cs rename to .Deprecated/LegacyContentMitigation/Patches.cs index 2b151e8..a389282 100644 --- a/LegacyContentMitigation/Patches.cs +++ b/.Deprecated/LegacyContentMitigation/Patches.cs @@ -9,6 +9,7 @@ using ABI.CCK.Components; using cohtml; using cohtml.Net; using HarmonyLib; +using LegacyContentMitigation.Components; using UnityEngine; using UnityEngine.Rendering.PostProcessing; using UnityEngine.SceneManagement; @@ -21,6 +22,9 @@ internal static class PlayerSetup_Patches [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) { + __instance.vrCam.AddComponentIfMissing(); + __instance.desktopCam.AddComponentIfMissing(); + FakeMultiPassHack.Instance = __instance.vrCam.AddComponentIfMissing(); FakeMultiPassHack.Instance.enabled = ModSettings.EntryAutoForLegacyWorlds.Value; } diff --git a/LegacyContentMitigation/Properties/AssemblyInfo.cs b/.Deprecated/LegacyContentMitigation/Properties/AssemblyInfo.cs similarity index 100% rename from LegacyContentMitigation/Properties/AssemblyInfo.cs rename to .Deprecated/LegacyContentMitigation/Properties/AssemblyInfo.cs diff --git a/LegacyContentMitigation/README.md b/.Deprecated/LegacyContentMitigation/README.md similarity index 100% rename from LegacyContentMitigation/README.md rename to .Deprecated/LegacyContentMitigation/README.md diff --git a/LegacyContentMitigation/format.json b/.Deprecated/LegacyContentMitigation/format.json similarity index 96% rename from LegacyContentMitigation/format.json rename to .Deprecated/LegacyContentMitigation/format.json index 480d0fb..e801124 100644 --- a/LegacyContentMitigation/format.json +++ b/.Deprecated/LegacyContentMitigation/format.json @@ -1,7 +1,7 @@ { - "_id": -1, + "_id": 247, "name": "LegacyContentMitigation", - "modversion": "1.0.1", + "modversion": "1.0.2", "gameversion": "2024r177", "loaderversion": "0.6.1", "modtype": "Mod", diff --git a/.DepricatedMods/MenuScalePatch/HarmonyPatches.cs b/.Deprecated/MenuScalePatch/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/HarmonyPatches.cs rename to .Deprecated/MenuScalePatch/HarmonyPatches.cs diff --git a/.DepricatedMods/MenuScalePatch/Helpers/MainMenuHelper.cs b/.Deprecated/MenuScalePatch/Helpers/MainMenuHelper.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/Helpers/MainMenuHelper.cs rename to .Deprecated/MenuScalePatch/Helpers/MainMenuHelper.cs diff --git a/.DepricatedMods/MenuScalePatch/Helpers/QuickMenuHelper.cs b/.Deprecated/MenuScalePatch/Helpers/QuickMenuHelper.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/Helpers/QuickMenuHelper.cs rename to .Deprecated/MenuScalePatch/Helpers/QuickMenuHelper.cs diff --git a/.DepricatedMods/MenuScalePatch/MSP_Menus.cs b/.Deprecated/MenuScalePatch/MSP_Menus.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/MSP_Menus.cs rename to .Deprecated/MenuScalePatch/MSP_Menus.cs diff --git a/.DepricatedMods/MenuScalePatch/Main.cs b/.Deprecated/MenuScalePatch/Main.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/Main.cs rename to .Deprecated/MenuScalePatch/Main.cs diff --git a/.DepricatedMods/MenuScalePatch/MenuScalePatch.csproj b/.Deprecated/MenuScalePatch/MenuScalePatch.csproj similarity index 100% rename from .DepricatedMods/MenuScalePatch/MenuScalePatch.csproj rename to .Deprecated/MenuScalePatch/MenuScalePatch.csproj diff --git a/.DepricatedMods/MenuScalePatch/Properties/AssemblyInfo.cs b/.Deprecated/MenuScalePatch/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/MenuScalePatch/Properties/AssemblyInfo.cs rename to .Deprecated/MenuScalePatch/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/MenuScalePatch/README.md b/.Deprecated/MenuScalePatch/README.md similarity index 100% rename from .DepricatedMods/MenuScalePatch/README.md rename to .Deprecated/MenuScalePatch/README.md diff --git a/.DepricatedMods/MenuScalePatch/format.json b/.Deprecated/MenuScalePatch/format.json similarity index 100% rename from .DepricatedMods/MenuScalePatch/format.json rename to .Deprecated/MenuScalePatch/format.json diff --git a/.DepricatedMods/MoreMenuOptions/Main.cs b/.Deprecated/MoreMenuOptions/Main.cs similarity index 100% rename from .DepricatedMods/MoreMenuOptions/Main.cs rename to .Deprecated/MoreMenuOptions/Main.cs diff --git a/.DepricatedMods/MoreMenuOptions/ModSettings.cs b/.Deprecated/MoreMenuOptions/ModSettings.cs similarity index 100% rename from .DepricatedMods/MoreMenuOptions/ModSettings.cs rename to .Deprecated/MoreMenuOptions/ModSettings.cs diff --git a/.DepricatedMods/MoreMenuOptions/MoreMenuOptions.csproj b/.Deprecated/MoreMenuOptions/MoreMenuOptions.csproj similarity index 100% rename from .DepricatedMods/MoreMenuOptions/MoreMenuOptions.csproj rename to .Deprecated/MoreMenuOptions/MoreMenuOptions.csproj diff --git a/.DepricatedMods/MoreMenuOptions/Properties/AssemblyInfo.cs b/.Deprecated/MoreMenuOptions/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/MoreMenuOptions/Properties/AssemblyInfo.cs rename to .Deprecated/MoreMenuOptions/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/MoreMenuOptions/README.md b/.Deprecated/MoreMenuOptions/README.md similarity index 100% rename from .DepricatedMods/MoreMenuOptions/README.md rename to .Deprecated/MoreMenuOptions/README.md diff --git a/.DepricatedMods/MoreMenuOptions/format.json b/.Deprecated/MoreMenuOptions/format.json similarity index 100% rename from .DepricatedMods/MoreMenuOptions/format.json rename to .Deprecated/MoreMenuOptions/format.json diff --git a/.DepricatedMods/NAK.CustomComponents/Components/NAKPointerTracker.cs b/.Deprecated/NAK.CustomComponents/Components/NAKPointerTracker.cs similarity index 100% rename from .DepricatedMods/NAK.CustomComponents/Components/NAKPointerTracker.cs rename to .Deprecated/NAK.CustomComponents/Components/NAKPointerTracker.cs diff --git a/.DepricatedMods/NAK.CustomComponents/CustomComponents.csproj b/.Deprecated/NAK.CustomComponents/CustomComponents.csproj similarity index 100% rename from .DepricatedMods/NAK.CustomComponents/CustomComponents.csproj rename to .Deprecated/NAK.CustomComponents/CustomComponents.csproj diff --git a/.DepricatedMods/NAK.CustomComponents/Main.cs b/.Deprecated/NAK.CustomComponents/Main.cs similarity index 100% rename from .DepricatedMods/NAK.CustomComponents/Main.cs rename to .Deprecated/NAK.CustomComponents/Main.cs diff --git a/.DepricatedMods/NAK.CustomComponents/Properties/AssemblyInfo.cs b/.Deprecated/NAK.CustomComponents/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/NAK.CustomComponents/Properties/AssemblyInfo.cs rename to .Deprecated/NAK.CustomComponents/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/NAK.CustomComponents/format.json b/.Deprecated/NAK.CustomComponents/format.json similarity index 100% rename from .DepricatedMods/NAK.CustomComponents/format.json rename to .Deprecated/NAK.CustomComponents/format.json diff --git a/Nevermind/Main.cs b/.Deprecated/Nevermind/Main.cs similarity index 100% rename from Nevermind/Main.cs rename to .Deprecated/Nevermind/Main.cs diff --git a/Nevermind/Nevermind.csproj b/.Deprecated/Nevermind/Nevermind.csproj similarity index 100% rename from Nevermind/Nevermind.csproj rename to .Deprecated/Nevermind/Nevermind.csproj diff --git a/Nevermind/Properties/AssemblyInfo.cs b/.Deprecated/Nevermind/Properties/AssemblyInfo.cs similarity index 100% rename from Nevermind/Properties/AssemblyInfo.cs rename to .Deprecated/Nevermind/Properties/AssemblyInfo.cs diff --git a/Nevermind/README.md b/.Deprecated/Nevermind/README.md similarity index 100% rename from Nevermind/README.md rename to .Deprecated/Nevermind/README.md diff --git a/Nevermind/format.json b/.Deprecated/Nevermind/format.json similarity index 100% rename from Nevermind/format.json rename to .Deprecated/Nevermind/format.json diff --git a/.DepricatedMods/NoDepthOnlyFlat/Main.cs b/.Deprecated/NoDepthOnlyFlat/Main.cs similarity index 100% rename from .DepricatedMods/NoDepthOnlyFlat/Main.cs rename to .Deprecated/NoDepthOnlyFlat/Main.cs diff --git a/SearchWithSpacesFix/SearchWithSpacesFix.csproj b/.Deprecated/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj similarity index 100% rename from SearchWithSpacesFix/SearchWithSpacesFix.csproj rename to .Deprecated/NoDepthOnlyFlat/NoDepthOnlyFlat.csproj diff --git a/.DepricatedMods/NoDepthOnlyFlat/Properties/AssemblyInfo.cs b/.Deprecated/NoDepthOnlyFlat/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/NoDepthOnlyFlat/Properties/AssemblyInfo.cs rename to .Deprecated/NoDepthOnlyFlat/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/NoDepthOnlyFlat/README.md b/.Deprecated/NoDepthOnlyFlat/README.md similarity index 100% rename from .DepricatedMods/NoDepthOnlyFlat/README.md rename to .Deprecated/NoDepthOnlyFlat/README.md diff --git a/.DepricatedMods/NoDepthOnlyFlat/format.json b/.Deprecated/NoDepthOnlyFlat/format.json similarity index 100% rename from .DepricatedMods/NoDepthOnlyFlat/format.json rename to .Deprecated/NoDepthOnlyFlat/format.json diff --git a/.DepricatedMods/PickupPushPull/HarmonyPatches.cs b/.Deprecated/PickupPushPull/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/PickupPushPull/HarmonyPatches.cs rename to .Deprecated/PickupPushPull/HarmonyPatches.cs diff --git a/.DepricatedMods/PickupPushPull/InputModules/PickupPushPull_Module.cs b/.Deprecated/PickupPushPull/InputModules/PickupPushPull_Module.cs similarity index 100% rename from .DepricatedMods/PickupPushPull/InputModules/PickupPushPull_Module.cs rename to .Deprecated/PickupPushPull/InputModules/PickupPushPull_Module.cs diff --git a/.DepricatedMods/PickupPushPull/Main.cs b/.Deprecated/PickupPushPull/Main.cs similarity index 100% rename from .DepricatedMods/PickupPushPull/Main.cs rename to .Deprecated/PickupPushPull/Main.cs diff --git a/.DepricatedMods/PickupPushPull/PickupPushPull.csproj b/.Deprecated/PickupPushPull/PickupPushPull.csproj similarity index 100% rename from .DepricatedMods/PickupPushPull/PickupPushPull.csproj rename to .Deprecated/PickupPushPull/PickupPushPull.csproj diff --git a/.DepricatedMods/PickupPushPull/Properties/AssemblyInfo.cs b/.Deprecated/PickupPushPull/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/PickupPushPull/Properties/AssemblyInfo.cs rename to .Deprecated/PickupPushPull/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/PickupPushPull/format.json b/.Deprecated/PickupPushPull/format.json similarity index 100% rename from .DepricatedMods/PickupPushPull/format.json rename to .Deprecated/PickupPushPull/format.json diff --git a/.DepricatedMods/PlaySpaceScaleFix/HarmonyPatches.cs b/.Deprecated/PlaySpaceScaleFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/HarmonyPatches.cs rename to .Deprecated/PlaySpaceScaleFix/HarmonyPatches.cs diff --git a/.DepricatedMods/PlaySpaceScaleFix/Main.cs b/.Deprecated/PlaySpaceScaleFix/Main.cs similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/Main.cs rename to .Deprecated/PlaySpaceScaleFix/Main.cs diff --git a/.DepricatedMods/PlaySpaceScaleFix/PlaySpaceScaleFix.csproj b/.Deprecated/PlaySpaceScaleFix/PlaySpaceScaleFix.csproj similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/PlaySpaceScaleFix.csproj rename to .Deprecated/PlaySpaceScaleFix/PlaySpaceScaleFix.csproj diff --git a/.DepricatedMods/PlaySpaceScaleFix/Properties/AssemblyInfo.cs b/.Deprecated/PlaySpaceScaleFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/Properties/AssemblyInfo.cs rename to .Deprecated/PlaySpaceScaleFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/PlaySpaceScaleFix/README.md b/.Deprecated/PlaySpaceScaleFix/README.md similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/README.md rename to .Deprecated/PlaySpaceScaleFix/README.md diff --git a/.DepricatedMods/PlaySpaceScaleFix/format.json b/.Deprecated/PlaySpaceScaleFix/format.json similarity index 100% rename from .DepricatedMods/PlaySpaceScaleFix/format.json rename to .Deprecated/PlaySpaceScaleFix/format.json diff --git a/ReconnectionSystemFix/Main.cs b/.Deprecated/ReconnectionSystemFix/Main.cs similarity index 100% rename from ReconnectionSystemFix/Main.cs rename to .Deprecated/ReconnectionSystemFix/Main.cs diff --git a/ReconnectionSystemFix/Patches.cs b/.Deprecated/ReconnectionSystemFix/Patches.cs similarity index 100% rename from ReconnectionSystemFix/Patches.cs rename to .Deprecated/ReconnectionSystemFix/Patches.cs diff --git a/ReconnectionSystemFix/Properties/AssemblyInfo.cs b/.Deprecated/ReconnectionSystemFix/Properties/AssemblyInfo.cs similarity index 100% rename from ReconnectionSystemFix/Properties/AssemblyInfo.cs rename to .Deprecated/ReconnectionSystemFix/Properties/AssemblyInfo.cs diff --git a/ReconnectionSystemFix/README.md b/.Deprecated/ReconnectionSystemFix/README.md similarity index 100% rename from ReconnectionSystemFix/README.md rename to .Deprecated/ReconnectionSystemFix/README.md diff --git a/.Deprecated/ReconnectionSystemFix/ReconnectionSystemFix.csproj b/.Deprecated/ReconnectionSystemFix/ReconnectionSystemFix.csproj new file mode 100644 index 0000000..bec5b03 --- /dev/null +++ b/.Deprecated/ReconnectionSystemFix/ReconnectionSystemFix.csproj @@ -0,0 +1,6 @@ + + + + LoadedObjectHack + + diff --git a/ReconnectionSystemFix/format.json b/.Deprecated/ReconnectionSystemFix/format.json similarity index 100% rename from ReconnectionSystemFix/format.json rename to .Deprecated/ReconnectionSystemFix/format.json diff --git a/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Main.cs b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Main.cs new file mode 100644 index 0000000..7687ea6 --- /dev/null +++ b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Main.cs @@ -0,0 +1,43 @@ +using System.Reflection; +using ABI_RC.Core.Player; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.RemoteAvatarDisablingCameraOnFirstFrameFix; + +public class RemoteAvatarDisablingCameraOnFirstFrameFixMod : MelonMod +{ + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated), + BindingFlags.Public | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(RemoteAvatarDisablingCameraOnFirstFrameFixMod).GetMethod(nameof(OnPuppetMasterAvatarInstantiated), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static void OnPuppetMasterAvatarInstantiated(PuppetMaster __instance) + { + if (__instance._animator == null) return; + + __instance._animator.WriteDefaultValues(); + __instance._animator.keepAnimatorStateOnDisable = false; + __instance._animator.writeDefaultValuesOnDisable = false; + } + + // private static void OnPuppetMasterAvatarInstantiated(PuppetMaster __instance) + // { + // if (__instance._animator == null) return; + // + // // Set culling mode to always animate + // __instance._animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; + // + // // Update the animator to force it to do the first frame + // __instance._animator.Update(0f); + // + // // Set culling mode back to cull update transforms + // __instance._animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms; + // } +} \ No newline at end of file diff --git a/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Properties/AssemblyInfo.cs b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9f70d66 --- /dev/null +++ b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using MelonLoader; +using NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix))] + +[assembly: MelonInfo( + typeof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.RemoteAvatarDisablingCameraOnFirstFrameFixMod), + nameof(NAK.RemoteAvatarDisablingCameraOnFirstFrameFix), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RemoteAvatarDisablingCameraOnFirstFrameFix" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: HarmonyDontPatchAll] + +namespace NAK.RemoteAvatarDisablingCameraOnFirstFrameFix.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/README.md b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/README.md new file mode 100644 index 0000000..8a46af2 --- /dev/null +++ b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/README.md @@ -0,0 +1,14 @@ +# IKSimulatedRootAngleFix + +Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/RemoteAvatarDisablingCameraOnFirstFrameFix.csproj b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/RemoteAvatarDisablingCameraOnFirstFrameFix.csproj new file mode 100644 index 0000000..bec5b03 --- /dev/null +++ b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/RemoteAvatarDisablingCameraOnFirstFrameFix.csproj @@ -0,0 +1,6 @@ + + + + LoadedObjectHack + + diff --git a/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/format.json b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/format.json new file mode 100644 index 0000000..ddf506b --- /dev/null +++ b/.Deprecated/RemoteAvatarDisablingCameraOnFirstFrameFix/format.json @@ -0,0 +1,24 @@ +{ + "_id": -1, + "name": "AASDefaultProfileFix", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.", + "searchtags": [ + "aas", + "profile", + "default", + "fix", + "meow" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/SearchWithSpacesFix/Main.cs b/.Deprecated/SearchWithSpacesFix/Main.cs similarity index 100% rename from SearchWithSpacesFix/Main.cs rename to .Deprecated/SearchWithSpacesFix/Main.cs diff --git a/SearchWithSpacesFix/Properties/AssemblyInfo.cs b/.Deprecated/SearchWithSpacesFix/Properties/AssemblyInfo.cs similarity index 100% rename from SearchWithSpacesFix/Properties/AssemblyInfo.cs rename to .Deprecated/SearchWithSpacesFix/Properties/AssemblyInfo.cs diff --git a/.Deprecated/SearchWithSpacesFix/README.md b/.Deprecated/SearchWithSpacesFix/README.md new file mode 100644 index 0000000..1c4c5bc --- /dev/null +++ b/.Deprecated/SearchWithSpacesFix/README.md @@ -0,0 +1,14 @@ +# SearchWithSpacesFix + +Fixes search terms that use spaces. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/SearchWithSpacesFix/SearchWithSpacesFix.csproj b/.Deprecated/SearchWithSpacesFix/SearchWithSpacesFix.csproj new file mode 100644 index 0000000..e94f9dc --- /dev/null +++ b/.Deprecated/SearchWithSpacesFix/SearchWithSpacesFix.csproj @@ -0,0 +1,2 @@ + + diff --git a/.Deprecated/SearchWithSpacesFix/format.json b/.Deprecated/SearchWithSpacesFix/format.json new file mode 100644 index 0000000..f8950ca --- /dev/null +++ b/.Deprecated/SearchWithSpacesFix/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "SearchWithSpacesFix", + "modversion": "1.0.0", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes search terms that include spaces.", + "searchtags": [ + "search", + "spaces", + "fix", + "meow" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/ShadowCloneFallback/Main.cs b/.Deprecated/ShadowCloneFallback/Main.cs similarity index 100% rename from ShadowCloneFallback/Main.cs rename to .Deprecated/ShadowCloneFallback/Main.cs diff --git a/ShadowCloneFallback/Properties/AssemblyInfo.cs b/.Deprecated/ShadowCloneFallback/Properties/AssemblyInfo.cs similarity index 100% rename from ShadowCloneFallback/Properties/AssemblyInfo.cs rename to .Deprecated/ShadowCloneFallback/Properties/AssemblyInfo.cs diff --git a/ShadowCloneFallback/README.md b/.Deprecated/ShadowCloneFallback/README.md similarity index 100% rename from ShadowCloneFallback/README.md rename to .Deprecated/ShadowCloneFallback/README.md diff --git a/ShadowCloneFallback/ShadowCloneFallback.csproj b/.Deprecated/ShadowCloneFallback/ShadowCloneFallback.csproj similarity index 100% rename from ShadowCloneFallback/ShadowCloneFallback.csproj rename to .Deprecated/ShadowCloneFallback/ShadowCloneFallback.csproj diff --git a/ShadowCloneFallback/format.json b/.Deprecated/ShadowCloneFallback/format.json similarity index 100% rename from ShadowCloneFallback/format.json rename to .Deprecated/ShadowCloneFallback/format.json diff --git a/SmartReticle/Main.cs b/.Deprecated/SmartReticle/Main.cs similarity index 100% rename from SmartReticle/Main.cs rename to .Deprecated/SmartReticle/Main.cs diff --git a/SmartReticle/Properties/AssemblyInfo.cs b/.Deprecated/SmartReticle/Properties/AssemblyInfo.cs similarity index 100% rename from SmartReticle/Properties/AssemblyInfo.cs rename to .Deprecated/SmartReticle/Properties/AssemblyInfo.cs diff --git a/SmartReticle/README.md b/.Deprecated/SmartReticle/README.md similarity index 100% rename from SmartReticle/README.md rename to .Deprecated/SmartReticle/README.md diff --git a/.Deprecated/SmartReticle/SmartReticle.csproj b/.Deprecated/SmartReticle/SmartReticle.csproj new file mode 100644 index 0000000..728edb7 --- /dev/null +++ b/.Deprecated/SmartReticle/SmartReticle.csproj @@ -0,0 +1,6 @@ + + + + net48 + + diff --git a/SmartReticle/format.json b/.Deprecated/SmartReticle/format.json similarity index 100% rename from SmartReticle/format.json rename to .Deprecated/SmartReticle/format.json diff --git a/StopClosingMyMenuOnWorldLoad/Main.cs b/.Deprecated/StopClosingMyMenuOnWorldLoad/Main.cs similarity index 100% rename from StopClosingMyMenuOnWorldLoad/Main.cs rename to .Deprecated/StopClosingMyMenuOnWorldLoad/Main.cs diff --git a/StopClosingMyMenuOnWorldLoad/Properties/AssemblyInfo.cs b/.Deprecated/StopClosingMyMenuOnWorldLoad/Properties/AssemblyInfo.cs similarity index 100% rename from StopClosingMyMenuOnWorldLoad/Properties/AssemblyInfo.cs rename to .Deprecated/StopClosingMyMenuOnWorldLoad/Properties/AssemblyInfo.cs diff --git a/StopClosingMyMenuOnWorldLoad/README.md b/.Deprecated/StopClosingMyMenuOnWorldLoad/README.md similarity index 100% rename from StopClosingMyMenuOnWorldLoad/README.md rename to .Deprecated/StopClosingMyMenuOnWorldLoad/README.md diff --git a/StopClosingMyMenuOnWorldLoad/StopClosingMyMenuOnWorldLoad.csproj b/.Deprecated/StopClosingMyMenuOnWorldLoad/StopClosingMyMenuOnWorldLoad.csproj similarity index 100% rename from StopClosingMyMenuOnWorldLoad/StopClosingMyMenuOnWorldLoad.csproj rename to .Deprecated/StopClosingMyMenuOnWorldLoad/StopClosingMyMenuOnWorldLoad.csproj diff --git a/StopClosingMyMenuOnWorldLoad/format.json b/.Deprecated/StopClosingMyMenuOnWorldLoad/format.json similarity index 100% rename from StopClosingMyMenuOnWorldLoad/format.json rename to .Deprecated/StopClosingMyMenuOnWorldLoad/format.json diff --git a/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs b/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs new file mode 100644 index 0000000..949b638 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs @@ -0,0 +1,42 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Systems.InputManagement; +using UnityEngine; +using UnityEngine.Serialization; + +namespace ABI_RC.Core.Player.Interaction +{ + public class CVRPlayerHand : MonoBehaviour + { + #region Fields + + [SerializeField] + private CVRHand _hand; + + // Pickup rig + [SerializeField] private Transform rayDirection; + [SerializeField] private Transform _attachmentPoint; + [SerializeField] private Transform _pivotPoint; + [SerializeField] private VelocityTracker _velocityTracker; + + // Pickup state + private bool _isHoldingObject; + private Pickupable _heldPickupable; + private Pickupable _proximityPickupable; + + #endregion Fields + + #region Unity Events + + + #endregion Unity Events + + #region Private Methods + + #endregion Private Methods + + #region Public Methods + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerInteractionManager.cs b/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerInteractionManager.cs new file mode 100644 index 0000000..2c0d56e --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerInteractionManager.cs @@ -0,0 +1,214 @@ +using ABI_RC.Core.Player.Interaction.RaycastImpl; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.InputManagement; +using UnityEngine; + +namespace ABI_RC.Core.Player.Interaction +{ + public class CVRPlayerInteractionManager : MonoBehaviour + { + #region Singleton + + public static CVRPlayerInteractionManager Instance { get; private set; } + + #endregion Singleton + + #region Serialized Fields + + [Header("Hand Components")] + [SerializeField] private CVRPlayerHand handVrLeft; + [SerializeField] private CVRPlayerHand handVrRight; + [SerializeField] private CVRPlayerHand handDesktopRight; // Desktop does not have a left hand + + [Header("Raycast Transforms")] + [SerializeField] private Transform raycastTransformVrRight; + [SerializeField] private Transform raycastTransformVrLeft; + [SerializeField] private Transform raycastTransformDesktopRight; + + [Header("Settings")] + [SerializeField] private bool interactionEnabled = true; + [SerializeField] private LayerMask interactionLayerMask = -1; // Default to all layers, will be filtered + + #endregion Serialized Fields + + #region Properties + + private CVRPlayerHand _rightHand; + private CVRPlayerHand _leftHand; + + private CVRPlayerRaycaster _rightRaycaster; + private CVRPlayerRaycaster _leftRaycaster; + + private CVRRaycastResult _rightRaycastResult; + private CVRRaycastResult _leftRaycastResult; + + // Input handler + private CVRPlayerInputHandler _inputHandler; + + // Interaction flags + public bool InteractionEnabled + { + get => interactionEnabled; + set => interactionEnabled = value; + } + + #endregion Properties + + #region Unity Events + + private void Awake() + { + if (Instance != null && Instance != this) + { + Destroy(gameObject); + return; + } + Instance = this; + + // Create the input handler + _inputHandler = gameObject.AddComponent(); + } + + private void Start() + { + // Setup interaction for current device mode + SetupInteractionForDeviceMode(); + + // Listen for VR mode changes + MetaPort.Instance.onVRModeSwitch.AddListener(SetupInteractionForDeviceMode); + } + + private void Update() + { + if (!interactionEnabled) + return; + + // Process right hand + if (_rightRaycaster != null) + { + // Determine raycast flags based on current mode + CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_rightHand); + + // Get raycast results + _rightRaycastResult = _rightRaycaster.GetRaycastResults(flags); + + // Process input based on raycast results + _inputHandler.ProcessInput(CVRHand.Right, _rightRaycastResult); + } + + // Process left hand (if available) + if (_leftRaycaster != null) + { + // Determine raycast flags based on current mode + CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_leftHand); + + // Get raycast results + _leftRaycastResult = _leftRaycaster.GetRaycastResults(flags); + + // Process input based on raycast results + _inputHandler.ProcessInput(CVRHand.Left, _leftRaycastResult); + } + } + + private void OnDestroy() + { + // Clean up event listener + if (MetaPort.Instance != null) + MetaPort.Instance.onVRModeSwitch.RemoveListener(SetupInteractionForDeviceMode); + } + + #endregion Unity Events + + #region Public Methods + + /// + /// Register a custom tool mode + /// + public void RegisterCustomToolMode(System.Action callback) + { + _inputHandler.RegisterCustomTool(callback); + } + + /// + /// Unregister the current custom tool mode + /// + public void UnregisterCustomToolMode() + { + _inputHandler.UnregisterCustomTool(); + } + + /// + /// Set the interaction mode + /// + public void SetInteractionMode(CVRPlayerInputHandler.InteractionMode mode) + { + _inputHandler.SetInteractionMode(mode); + } + + /// + /// Get the raycast result for a specific hand + /// + public CVRRaycastResult GetRaycastResult(CVRHand hand) + { + return hand == CVRHand.Left ? _leftRaycastResult : _rightRaycastResult; + } + + #endregion Public Methods + + #region Private Methods + + private void SetupInteractionForDeviceMode() + { + bool isVr = MetaPort.Instance.isUsingVr; + + if (isVr) + { + // VR mode + _rightHand = handVrRight; + _leftHand = handVrLeft; + + // VR uses the controller transform for raycasting + _rightRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrRight); + _leftRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrLeft); + } + else + { + // Desktop mode + _rightHand = handDesktopRight; + _leftHand = null; + + // Desktop uses the mouse position for raycasting when unlocked + Camera desktopCamera = PlayerSetup.Instance.desktopCam; + _rightRaycaster = new CVRPlayerRaycasterMouse(raycastTransformDesktopRight, desktopCamera); + _leftRaycaster = null; + } + + // Set the layer mask for raycasters + if (_rightRaycaster != null) + _rightRaycaster.SetLayerMask(interactionLayerMask); + + if (_leftRaycaster != null) + _leftRaycaster.SetLayerMask(interactionLayerMask); + } + + private static CVRPlayerRaycaster.RaycastFlags DetermineRaycastFlags(CVRPlayerHand hand) + { + // Default to all flags + CVRPlayerRaycaster.RaycastFlags flags = CVRPlayerRaycaster.RaycastFlags.All; + + // Check if hand is holding a pickup + if (hand != null && hand.IsHoldingObject) + { + // When holding an object, only check for COHTML interaction + flags = CVRPlayerRaycaster.RaycastFlags.CohtmlInteract; + } + + // Could add more conditional flag adjustments here based on the current mode + // For example, in a teleport tool mode, you might only want world hits + + return flags; + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/Components/CVRCanvasWrapper.cs b/.Deprecated/SuperAwesomeMod/Interaction/Components/CVRCanvasWrapper.cs new file mode 100644 index 0000000..2ec46bd --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/Components/CVRCanvasWrapper.cs @@ -0,0 +1,121 @@ +using ABI_RC.Core.Base; +using ABI_RC.Core.Player; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace NAK.SuperAwesomeMod.Components +{ + public class CVRCanvasWrapper : MonoBehaviour + { + public bool IsInteractable = true; + public float MaxInteractDistance = 10f; + + private Canvas _canvas; + private GraphicRaycaster _graphicsRaycaster; + private static readonly List _raycastResults = new(); + private static readonly PointerEventData _pointerEventData = new(EventSystem.current); + + private static Selectable _workingSelectable; + private Camera _camera; + private RectTransform _rectTransform; + + #region Unity Events + + private void Awake() + { + if (!TryGetComponent(out _canvas) + || _canvas.renderMode != RenderMode.WorldSpace) + { + IsInteractable = false; + return; + } + + _rectTransform = _canvas.GetComponent(); + } + + private void Start() + { + _graphicsRaycaster = _canvas.gameObject.AddComponent(); + _camera = PlayerSetup.Instance.activeCam; + _canvas.worldCamera = _camera; + } + + #endregion Unity Events + + #region Public Methods + + public bool GetGraphicsHit(Ray worldRay, out RaycastResult result) + { + result = default; + + if (!IsInteractable || _camera == null) return false; + + // Get the plane of the canvas + Plane canvasPlane = new(transform.forward, transform.position); + + // Find where the ray intersects the canvas plane + if (!canvasPlane.Raycast(worldRay, out float distance)) + return false; + + // Get the world point of intersection + Vector3 worldHitPoint = worldRay.origin + worldRay.direction * distance; + + // Check if hit point is within max interaction distance + if (Vector3.Distance(worldRay.origin, worldHitPoint) > MaxInteractDistance) + return false; + + // Check if hit point is within canvas bounds + Vector3 localHitPoint = transform.InverseTransformPoint(worldHitPoint); + Rect canvasRect = _rectTransform.rect; + if (!canvasRect.Contains(new Vector2(localHitPoint.x, localHitPoint.y))) + return false; + + // Convert world hit point to screen space + Vector2 screenPoint = _camera.WorldToScreenPoint(worldHitPoint); + + // Update pointer event data + _pointerEventData.position = screenPoint; + _pointerEventData.delta = Vector2.zero; + + // Clear previous results and perform raycast + _raycastResults.Clear(); + _graphicsRaycaster.Raycast(_pointerEventData, _raycastResults); + + // Early out if no hits + if (_raycastResults.Count == 0) + { + //Debug.Log($"No hits on canvas {_canvas.name}"); + return false; + } + + // Find first valid interactive UI element + foreach (RaycastResult hit in _raycastResults) + { + if (!hit.isValid) + { + //Debug.Log($"Invalid hit on canvas {_canvas.name}"); + continue; + } + + // Check if the hit object has a Selectable component and is interactable + GameObject hitObject = hit.gameObject; + if (!hitObject.TryGetComponent(out _workingSelectable) + || !_workingSelectable.interactable) + { + //Debug.Log($"Non-interactable hit on canvas {_canvas.name} - {hitObject.name}"); + continue; + } + + //Debug.Log($"Hit on canvas {_canvas.name} with {hitObject.name}"); + + result = hit; + return true; + } + + return false; + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycaster.cs b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycaster.cs new file mode 100644 index 0000000..aa4205a --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycaster.cs @@ -0,0 +1,385 @@ +using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Core.UI; +using ABI.CCK.Components; +using NAK.SuperAwesomeMod.Components; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace ABI_RC.Core.Player.Interaction.RaycastImpl +{ + public abstract class CVRPlayerRaycaster + { + #region Enums + + [Flags] + public enum RaycastFlags + { + None = 0, + TelepathicCandidate = 1 << 0, + ProximityInteract = 1 << 1, + RayInteract = 1 << 2, + CohtmlInteract = 1 << 3, + All = ~0 + } + + #endregion Enums + + #region Constants + + private const float MAX_RAYCAST_DISTANCE = 100f; // Max distance you can raycast + private const float RAYCAST_SPHERE_RADIUS = 0.1f; // Radius of the proximity sphere + private const float TELEPATHIC_SPHERE_RADIUS = 0.3f; // Radius of the telepathic sphere + private const float MAX_TELEPATHIC_DISTANCE = 20f; // Max distance for telepathic grab + private const int MAX_RAYCAST_HITS = 100; // Hit buffer size, high due to triggers, which we use lots in CCK + + // Global setting is Collide, but better to be explicit about what we need + private const QueryTriggerInteraction _triggerInteraction = QueryTriggerInteraction.Collide; + + // Layers that are reserved for other purposes or illegal to interact with + private const int RESERVED_OR_ILLEGAL_LAYERS = (1 << CVRLayers.IgnoreRaycast) + | (1 << CVRLayers.MirrorReflection) + | (1 << CVRLayers.PlayerLocal); + + #endregion Constants + + #region Static Fields + + private static readonly RaycastHit[] _hits = new RaycastHit[MAX_RAYCAST_HITS]; + private static readonly Comparer _hitsComparer = Comparer.Create((hit1, hit2) => + { + bool isUI1 = hit1.collider.gameObject.layer == CVRLayers.UIInternal; + bool isUI2 = hit2.collider.gameObject.layer == CVRLayers.UIInternal; + + // Prioritize UIInternal hits + if (isUI1 && !isUI2) return -1; // UIInternal comes first + if (!isUI1 && isUI2) return 1; // Non-UIInternal comes after + + // If both are UIInternal or both are not, sort by distance + return hit1.distance.CompareTo(hit2.distance); + }); + + private static readonly LayerMask _telepathicLayerMask = 1 << CVRLayers.MirrorReflection; + + // Working variables to avoid repeated allocations + private static Collider _workingCollider; + private static GameObject _workingGameObject; + private static Pickupable _workingPickupable; + private static Interactable _workingInteractable; + private static Selectable _workingSelectable; + private static ICanvasElement _workingCanvasElement; + + #endregion Static Fields + + #region Private Fields + + private LayerMask _layerMask; // Default to no layers so we know if we fucked up + + #endregion Private Fields + + #region Constructor + + protected CVRPlayerRaycaster(Transform rayOrigin) => _rayOrigin = rayOrigin; + protected readonly Transform _rayOrigin; + + #endregion Constructor + + #region Public Methods + + public void SetLayerMask(LayerMask layerMask) + { + layerMask &= ~RESERVED_OR_ILLEGAL_LAYERS; + _layerMask = layerMask; + } + + public CVRRaycastResult GetRaycastResults(RaycastFlags flags = RaycastFlags.All) + { + // Early out if we don't want to do anything + if (flags == RaycastFlags.None) return default; + + Ray ray = GetRayFromImpl(); + CVRRaycastResult result = new(); + + // Always check COHTML first + if ((flags & RaycastFlags.CohtmlInteract) != 0 + && TryProcessCohtmlHit(ray, ref result)) + return result; + + // Check if there are pickups or interactables in immediate proximity + if ((flags & RaycastFlags.ProximityInteract) != 0) + { + ProcessProximityHits(ray, ref result); // TODO: Offset origin to center of palm based on hand type + if (result.isProximityHit) + return result; + } + + // Check for regular raycast hits + if ((flags & RaycastFlags.RayInteract) != 0) + ProcessRaycastHits(ray, ref result); + + // If we hit something, check for telepathic grab candidates at the hit point + if ((flags & RaycastFlags.TelepathicCandidate) != 0 && result.hit.collider) + ProcessTelepathicGrabCandidate(result.hit.point, ref result); + + return result; + } + + #endregion Public Methods + + #region Private Methods + + private static bool TryProcessCohtmlHit(Ray ray, ref CVRRaycastResult result) + { + CohtmlControlledView hitView = CohtmlViewInputHandler.Instance.RayToView(ray, + out float _, out Vector2 hitCoords); + if (hitView == null) return false; + + result.hitCohtml = true; + result.hitCohtmlView = hitView; + result.hitCohtmlCoords = hitCoords; + + // Manually check for pickups & interactables on the hit view (future-proofing for menu grabbing) + if (hitView.TryGetComponent(out _workingInteractable)) result.hitInteractable = _workingInteractable; + if (hitView.TryGetComponent(out _workingPickupable)) result.hitPickupable = _workingPickupable; + + return true; + } + + private void ProcessProximityHits(Ray ray, ref CVRRaycastResult result) + { + int proximityHits = Physics.SphereCastNonAlloc( + ray.origin, + RAYCAST_SPHERE_RADIUS, + Vector3.up, + _hits, + 0.001f, + _layerMask, + _triggerInteraction + ); + + if (proximityHits <= 0) return; + + Array.Sort(_hits, 0, proximityHits, _hitsComparer); + + for (int i = 0; i < proximityHits; i++) + { + RaycastHit hit = _hits[i]; + _workingCollider = hit.collider; + _workingGameObject = _workingCollider.gameObject; + + // Skip things behind the ray origin + if (Vector3.Dot(ray.direction, hit.point - ray.origin) < 0) + continue; + + // Check for interactables & pickupables in proximity + if (!TryProcessInteractables(hit, ref result)) + continue; + + result.isProximityHit = true; + break; + } + } + + private void ProcessRaycastHits(Ray ray, ref CVRRaycastResult result) + { + // Get all hits including triggers, sorted by UI Internal layer & distance + int hitCount = Physics.RaycastNonAlloc(ray, + _hits, + MAX_RAYCAST_DISTANCE, + _layerMask, + _triggerInteraction); + + if (hitCount <= 0) return; + + Array.Sort(_hits, 0, hitCount, _hitsComparer); + + for (int i = 0; i < hitCount; i++) + { + RaycastHit hit = _hits[i]; + _workingCollider = hit.collider; + _workingGameObject = _workingCollider.gameObject; + + // Special case where we only get the closest water hit position. + // As the array is sorted by distance, we only need to check if we didn't hit water yet. + if (!result.hitWater) TryProcessFluidVolume(hit, ref result); + + // Check for hits in order of priority + + if (TryProcessSelectable(hit, ref result)) + break; // Hit a Unity UI Selectable (Button, Slider, etc.) + + if (TryProcessCanvasElement(hit, ref result)) + break; // Hit a Unity UI Canvas Element (ScrollRect, idk what else yet) + + if (TryProcessInteractables(hit, ref result)) + break; // Hit an in-range Interactable or Pickup + + if (TryProcessWorldHit(hit, ref result)) + break; // Hit a non-trigger collider (world, end of ray) + } + } + + private void ProcessTelepathicGrabCandidate(Vector3 hitPoint, ref CVRRaycastResult result) + { + // If we already hit a pickupable, we don't need to check for telepathic grab candidates + if (result.hitPickupable) + { + result.hasTelepathicGrabCandidate = true; + result.telepathicPickupable = result.hitPickupable; + result.telepathicGrabPoint = hitPoint; + return; + } + + // If the hit distance is too far, don't bother checking for telepathic grab candidates + if (Vector3.Distance(hitPoint, _rayOrigin.position) > MAX_TELEPATHIC_DISTANCE) + return; + + // Check for mirror reflection triggers in a sphere around the hit point + int telepathicHits = Physics.SphereCastNonAlloc( + hitPoint, + TELEPATHIC_SPHERE_RADIUS, + Vector3.up, + _hits, + 0.001f, + _telepathicLayerMask, + QueryTriggerInteraction.Collide + ); + + if (telepathicHits <= 0) return; + + // Look for pickupable objects near our hit point + var nearestDistance = float.MaxValue; + for (int i = 0; i < telepathicHits; i++) + { + RaycastHit hit = _hits[i]; + _workingCollider = hit.collider; + // _workingGameObject = _workingCollider.gameObject; + + Transform parentTransform = _workingCollider.transform.parent; + if (!parentTransform + || !parentTransform.TryGetComponent(out _workingPickupable) + || !_workingPickupable.CanPickup) + continue; + + var distance = Vector3.Distance(hitPoint, hit.point); + if (!(distance < nearestDistance)) + continue; + + result.hasTelepathicGrabCandidate = true; + result.telepathicPickupable = _workingPickupable; + result.telepathicGrabPoint = hitPoint; + nearestDistance = distance; + } + } + + private static bool TryProcessSelectable(RaycastHit hit, ref CVRRaycastResult result) + { + if (!_workingGameObject.TryGetComponent(out _workingSelectable)) + return false; + + result.hitUnityUi = true; + result.hitSelectable = _workingSelectable; + result.hit = hit; + return true; + } + + private static bool TryProcessCanvasElement(RaycastHit hit, ref CVRRaycastResult result) + { + if (!_workingGameObject.TryGetComponent(out _workingCanvasElement)) + return false; + + result.hitUnityUi = true; + result.hitCanvasElement = _workingCanvasElement; + result.hit = hit; + return true; + } + + private static void TryProcessFluidVolume(RaycastHit hit, ref CVRRaycastResult result) + { + if (_workingGameObject.layer != CVRLayers.Water) return; + + result.hitWater = true; + result.waterHit = hit; + } + + private static bool TryProcessInteractables(RaycastHit hit, ref CVRRaycastResult result) + { + bool hitValidComponent = false; + + if (_workingGameObject.TryGetComponent(out _workingInteractable) + && _workingInteractable.CanInteract + && IsCVRInteractableWithinRange(_workingInteractable, hit)) + { + result.hitInteractable = _workingInteractable; + hitValidComponent = true; + } + if (_workingGameObject.TryGetComponent(out _workingPickupable) + && _workingPickupable.CanPickup + && IsCVRPickupableWithinRange(_workingPickupable, hit)) + { + result.hitPickupable = _workingPickupable; + hitValidComponent = true; + } + + if (!hitValidComponent) + return false; + + result.hit = hit; + return true; + } + + private static bool TryProcessWorldHit(RaycastHit hit, ref CVRRaycastResult result) + { + if (_workingCollider.isTrigger) + return false; + + result.hitWorld = true; + result.hit = hit; + return true; + } + + #endregion Private Methods + + #region Protected Methods + + protected abstract Ray GetRayFromImpl(); + + #endregion Protected Methods + + #region Utility Because Original Methods Are Broken + + private static bool IsCVRInteractableWithinRange(Interactable interactable, RaycastHit hit) + { + if (interactable is not CVRInteractable cvrInteractable) + return true; + + foreach (CVRInteractableAction action in cvrInteractable.actions) + { + if (action.actionType + is not (CVRInteractableAction.ActionRegister.OnInteractDown + or CVRInteractableAction.ActionRegister.OnInteractUp + or CVRInteractableAction.ActionRegister.OnInputDown + or CVRInteractableAction.ActionRegister.OnInputUp)) + continue; + + float maxDistance = action.floatVal; + if (Mathf.Approximately(maxDistance, 0f) + || hit.distance <= maxDistance) + return true; // Interactable is within range + } + return false; + } + + private static bool IsCVRPickupableWithinRange(Pickupable pickupable, RaycastHit hit) + { + return hit.distance <= pickupable.MaxGrabDistance; + } + + private static bool IsCVRCanvasWrapperWithinRange(CVRCanvasWrapper canvasWrapper, RaycastHit hit) + { + return hit.distance <= canvasWrapper.MaxInteractDistance; + } + + #endregion Utility Because Original Methods Are Broken + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterMouse.cs b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterMouse.cs new file mode 100644 index 0000000..5f5e638 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterMouse.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace ABI_RC.Core.Player.Interaction.RaycastImpl +{ + public class CVRPlayerRaycasterMouse : CVRPlayerRaycaster + { + private readonly Camera _camera; + public CVRPlayerRaycasterMouse(Transform rayOrigin, Camera camera) : base(rayOrigin) { _camera = camera; } + protected override Ray GetRayFromImpl() => Cursor.lockState == CursorLockMode.Locked + ? new Ray(_camera.transform.position, _camera.transform.forward) + : _camera.ScreenPointToRay(Input.mousePosition); + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterTransform.cs b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterTransform.cs new file mode 100644 index 0000000..cc62dab --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRPlayerRaycasterTransform.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace ABI_RC.Core.Player.Interaction.RaycastImpl +{ + public class CVRPlayerRaycasterTransform : CVRPlayerRaycaster + { + public CVRPlayerRaycasterTransform(Transform rayOrigin) : base(rayOrigin) { } + protected override Ray GetRayFromImpl() => new(_rayOrigin.position, _rayOrigin.forward); + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRRaycastResult.cs b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRRaycastResult.cs new file mode 100644 index 0000000..6165354 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/CVRRaycastResult.cs @@ -0,0 +1,38 @@ +using ABI_RC.Core.InteractionSystem.Base; +using ABI_RC.Core.UI; +using NAK.SuperAwesomeMod.Components; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace ABI_RC.Core.Player.Interaction.RaycastImpl +{ + public struct CVRRaycastResult + { + // Hit flags + public bool hitWorld; // Any non-specific collision + public bool hitWater; // Hit a fluid volume + public bool hitCohtml; // Specifically hit a COHTML view (Main/Quick Menu) + public bool isProximityHit; // Hit was from proximity sphere check + public bool hitUnityUi; // Hit a canvas + + // Main raycast hit info + public RaycastHit hit; + public RaycastHit? waterHit; // Only valid if hitWater is true + + // Specific hit components + public Pickupable hitPickupable; + public Interactable hitInteractable; + public Selectable hitSelectable; + public ICanvasElement hitCanvasElement; + + // COHTML specific results + public CohtmlControlledView hitCohtmlView; + public Vector2 hitCohtmlCoords; + + // Telepathic pickup + public bool hasTelepathicGrabCandidate; + public Pickupable telepathicPickupable; + public Vector3 telepathicGrabPoint; + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/RaycastDebug.cs b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/RaycastDebug.cs new file mode 100644 index 0000000..5000f89 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Interaction/RaycastImpl/RaycastDebug.cs @@ -0,0 +1,312 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace ABI_RC.Core.Player.Interaction.RaycastImpl +{ +public class CVRRaycastDebugManager : MonoBehaviour +{ + #region Singleton + + private static CVRRaycastDebugManager _instance; + public static CVRRaycastDebugManager Instance => _instance; + + public static void Initialize(Camera camera) + { + if (_instance != null) return; + + var go = new GameObject("RaycastDebugManager"); + _instance = go.AddComponent(); + DontDestroyOnLoad(go); + + _instance.Setup(camera); + } + + #endregion + + #region Private Fields + + private CVRPlayerRaycasterMouse _raycaster; + private CVRRaycastResult _lastResult; + private System.Diagnostics.Stopwatch _stopwatch; + + // Performance tracking + private const int ROLLING_AVERAGE_SAMPLES = 60; // 1 second at 60fps + private readonly float[] _timeHistory = new float[ROLLING_AVERAGE_SAMPLES]; + private int _currentSampleIndex; + private float _lastRaycastTime; + private float _minRaycastTime = float.MaxValue; + private float _maxRaycastTime; + private float _rollingAverageTime; + private bool _historyFilled; + + private const int DEBUG_PANEL_WIDTH = 300; + private const int DEBUG_PANEL_MARGIN = 10; + private const float MOUSE_CURSOR_SIZE = 24f; + private const float CURSOR_OFFSET = MOUSE_CURSOR_SIZE / 2f; + + private GUIStyle _labelStyle; + private GUIStyle _headerStyle; + private GUIStyle _boxStyle; + + private static readonly Color32 TIMING_COLOR = new(255, 255, 150, 255); // Yellow + private static readonly Color32 COHTML_COLOR = new(150, 255, 150, 255); // Green + private static readonly Color32 UI_COLOR = new(150, 150, 255, 255); // Blue + private static readonly Color32 UNITY_UI_COLOR = new(255, 200, 150, 255); // Orange + private static readonly Color32 INTERACT_COLOR = new(255, 150, 150, 255); // Red + private static readonly Color32 WATER_COLOR = new(150, 255, 255, 255); // Cyan + private static readonly Color32 TELEPATHIC_COLOR = new(255, 150, 255, 255);// Purple + private static readonly Color32 SELECTABLE_COLOR = new(200, 150, 255, 255);// Light Purple + + #endregion + + #region Setup + + private void Setup(Camera camera) + { + _raycaster = new CVRPlayerRaycasterMouse(transform, camera); + _raycaster.SetLayerMask(Physics.DefaultRaycastLayers); + _stopwatch = new System.Diagnostics.Stopwatch(); + } + + #endregion + + #region MonoBehaviour + + private void Update() + { + _stopwatch.Restart(); + _lastResult = _raycaster.GetRaycastResults(); + _stopwatch.Stop(); + + UpdatePerformanceMetrics(); + } + + private void UpdatePerformanceMetrics() + { + // Calculate current frame time + _lastRaycastTime = _stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond; + + // Update min/max + _minRaycastTime = Mathf.Min(_minRaycastTime, _lastRaycastTime); + _maxRaycastTime = Mathf.Max(_maxRaycastTime, _lastRaycastTime); + + // Update rolling average + _timeHistory[_currentSampleIndex] = _lastRaycastTime; + + // Calculate rolling average based on filled samples + float sum = 0f; + int sampleCount = _historyFilled ? ROLLING_AVERAGE_SAMPLES : _currentSampleIndex + 1; + + for (int i = 0; i < sampleCount; i++) + sum += _timeHistory[i]; + + _rollingAverageTime = sum / sampleCount; + + // Update index for next frame + _currentSampleIndex = (_currentSampleIndex + 1) % ROLLING_AVERAGE_SAMPLES; + if (_currentSampleIndex == 0) + _historyFilled = true; + } + + private void OnGUI() + { + InitializeStyles(); + DrawDebugPanel(); + } + + #endregion + + #region Drawing Methods + + private void InitializeStyles() + { + if (_labelStyle != null) return; + + _labelStyle = new GUIStyle + { + normal = { textColor = Color.white }, + fontSize = 12, + padding = new RectOffset(5, 5, 2, 2), + margin = new RectOffset(5, 5, 0, 0) + }; + + _headerStyle = new GUIStyle + { + normal = { textColor = Color.white }, + fontSize = 14, + fontStyle = FontStyle.Bold, + padding = new RectOffset(5, 5, 5, 5), + margin = new RectOffset(5, 5, 5, 5) + }; + + _boxStyle = new GUIStyle(GUI.skin.box) + { + padding = new RectOffset(10, 10, 5, 5), + margin = new RectOffset(5, 5, 5, 5) + }; + } + + private void DrawDebugPanel() + { + var rect = new Rect( + Screen.width - DEBUG_PANEL_WIDTH - DEBUG_PANEL_MARGIN, + DEBUG_PANEL_MARGIN, + DEBUG_PANEL_WIDTH, + Screen.height - (DEBUG_PANEL_MARGIN * 2) + ); + + GUI.Box(rect, ""); + GUILayout.BeginArea(rect); + + GUI.backgroundColor = Color.black; + GUILayout.Label("Raycast Debug Info", _headerStyle); + + DrawPerformanceSection(); + DrawCohtmlSection(); + DrawUnityUISection(); + DrawSelectableSection(); + DrawInteractionSection(); + DrawWaterSection(); + DrawTelepathicSection(); + DrawWorldHitSection(); + + GUILayout.EndArea(); + } + + private void DrawPerformanceSection() + { + GUI.backgroundColor = TIMING_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("Performance", _headerStyle); + DrawLabel("Last Raycast", $"{_lastRaycastTime:F3} ms"); + DrawLabel("Average (1s)", $"{_rollingAverageTime:F3} ms"); + DrawLabel("Min", $"{_minRaycastTime:F3} ms"); + DrawLabel("Max", $"{_maxRaycastTime:F3} ms"); + GUILayout.EndVertical(); + } + + private void DrawCohtmlSection() + { + if (!_lastResult.hitCohtml) return; + + GUI.backgroundColor = COHTML_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("COHTML Hit", _headerStyle); + DrawLabel("View", _lastResult.hitCohtmlView.name); + DrawLabel("Coords", _lastResult.hitCohtmlCoords.ToString()); + GUILayout.EndVertical(); + } + + private void DrawUnityUISection() + { + if (!_lastResult.hitUnityUi || _lastResult.hitCanvasElement == null) return; + + GUI.backgroundColor = UNITY_UI_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("Unity UI Hit", _headerStyle); + + var canvasElement = _lastResult.hitCanvasElement; + var gameObject = canvasElement as MonoBehaviour; + + DrawLabel("Canvas Element", gameObject != null ? gameObject.name : "Unknown"); + DrawLabel("Element Type", canvasElement.GetType().Name); + + if (gameObject != null) + { + DrawLabel("GameObject", gameObject.gameObject.name); + + if (gameObject.transform.parent != null) + DrawLabel("Parent", gameObject.transform.parent.name); + } + + GUILayout.EndVertical(); + } + + private void DrawSelectableSection() + { + if (_lastResult.hitSelectable == null) return; + + GUI.backgroundColor = SELECTABLE_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("UI Selectable", _headerStyle); + DrawLabel("Selectable", _lastResult.hitSelectable.name); + DrawLabel("Selectable Type", _lastResult.hitSelectable.GetType().Name); + DrawLabel("Is Interactable", _lastResult.hitSelectable.interactable.ToString()); + DrawLabel("Navigation Mode", _lastResult.hitSelectable.navigation.mode.ToString()); + + if (_lastResult.hitSelectable is Toggle toggle) + DrawLabel("Toggle State", toggle.isOn.ToString()); + else if (_lastResult.hitSelectable is Slider slider) + DrawLabel("Slider Value", slider.value.ToString("F2")); + else if (_lastResult.hitSelectable is Scrollbar scrollbar) + DrawLabel("Scrollbar Value", scrollbar.value.ToString("F2")); + + GUILayout.EndVertical(); + } + + private void DrawInteractionSection() + { + if (!_lastResult.hitPickupable && !_lastResult.hitInteractable) return; + + GUI.backgroundColor = INTERACT_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("Interaction", _headerStyle); + if (_lastResult.hitPickupable) + DrawLabel("Pickupable", _lastResult.hitPickupable.name); + if (_lastResult.hitInteractable) + DrawLabel("Interactable", _lastResult.hitInteractable.name); + DrawLabel("Is Proximity", _lastResult.isProximityHit.ToString()); + GUILayout.EndVertical(); + } + + private void DrawWaterSection() + { + if (!_lastResult.hitWater || !_lastResult.waterHit.HasValue) return; + + GUI.backgroundColor = WATER_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("Water Surface", _headerStyle); + DrawLabel("Hit Point", _lastResult.waterHit.Value.point.ToString("F2")); + DrawLabel("Surface Normal", _lastResult.waterHit.Value.normal.ToString("F2")); + GUILayout.EndVertical(); + } + + private void DrawTelepathicSection() + { + if (!_lastResult.hasTelepathicGrabCandidate) return; + + GUI.backgroundColor = TELEPATHIC_COLOR; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("Telepathic Grab", _headerStyle); + DrawLabel("Target", _lastResult.telepathicPickupable.name); + DrawLabel("Grab Point", _lastResult.telepathicGrabPoint.ToString("F2")); + GUILayout.EndVertical(); + } + + private void DrawWorldHitSection() + { + if (_lastResult.hitCohtml || + _lastResult.hitPickupable || + _lastResult.hitInteractable || + _lastResult.hitUnityUi || + !_lastResult.hitWorld || + _lastResult.hit.collider == null) return; + + GUI.backgroundColor = Color.grey; + GUILayout.BeginVertical(_boxStyle); + GUILayout.Label("World Hit", _headerStyle); + DrawLabel("Object", _lastResult.hit.collider.name); + DrawLabel("Distance", _lastResult.hit.distance.ToString("F2")); + DrawLabel("Point", _lastResult.hit.point.ToString("F2")); + GUILayout.EndVertical(); + } + + private void DrawLabel(string label, string value) + { + GUILayout.Label($"{label}: {value}", _labelStyle); + } + + #endregion +} +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Main.cs b/.Deprecated/SuperAwesomeMod/Main.cs new file mode 100644 index 0000000..5c410f2 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Main.cs @@ -0,0 +1,81 @@ +using System.Reflection; +using ABI_RC.Core.Base.Jobs; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using ABI_RC.Core.Player.Interaction.RaycastImpl; +using ABI_RC.Core.Util.AssetFiltering; +using ABI.CCK.Components; +using HarmonyLib; +using MelonLoader; +using NAK.SuperAwesomeMod.Components; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace NAK.SuperAwesomeMod; + +public class SuperAwesomeModMod : MelonMod +{ + #region Melon Events + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Start), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnPlayerSetupStart), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(SceneLoaded).GetMethod(nameof(SceneLoaded.FilterWorldComponent), + BindingFlags.NonPublic | BindingFlags.Static), + postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnShitLoaded), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // patch SharedFilter.ProcessCanvas + HarmonyInstance.Patch( + typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessCanvas), + BindingFlags.Public | BindingFlags.Static), + postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnProcessCanvas), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + LoggerInstance.Msg("SuperAwesomeModMod! OnInitializeMelon! :D"); + } + + public override void OnApplicationQuit() + { + LoggerInstance.Msg("SuperAwesomeModMod! OnApplicationQuit! D:"); + } + + #endregion Melon Events + + private static void OnPlayerSetupStart() + { + CVRRaycastDebugManager.Initialize(PlayerSetup.Instance.desktopCam); + } + + private static void OnShitLoaded(Component c, List asyncTasks = null, Scene? scene = null) + { + if (c == null) + return; + + if (c.gameObject == null) + return; + + if (c.gameObject.scene.buildIndex > 0) + return; + + if ((scene != null) + && (c.gameObject.scene != scene)) + return; + + if (c is Canvas canvas) canvas.gameObject.AddComponent(); + } + + private static void OnProcessCanvas(string collectionId, Canvas canvas) + { + canvas.gameObject.AddComponent(); + } +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/Properties/AssemblyInfo.cs b/.Deprecated/SuperAwesomeMod/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f1590e2 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.SuperAwesomeMod.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.SuperAwesomeMod))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.SuperAwesomeMod))] + +[assembly: MelonInfo( + typeof(NAK.SuperAwesomeMod.SuperAwesomeModMod), + nameof(NAK.SuperAwesomeMod), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SuperAwesomeMod" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.SuperAwesomeMod.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/.Deprecated/SuperAwesomeMod/README.md b/.Deprecated/SuperAwesomeMod/README.md new file mode 100644 index 0000000..399e70a --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/README.md @@ -0,0 +1,54 @@ +# ASTExtension + +Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): +- VR Gesture to scale +- Persistent height +- Copy height from others + +Best used with Avatar Scale Tool, but will attempt to work with found scaling setups. +Requires already having Avatar Scaling on the avatar. This is **not** Universal Scaling. + +## Supported Setups + +ASTExtension will attempt to work with the following setups: + +**Parameter Names:** +- AvatarScale +- Scale +- Scaler +- Scale/Scale +- Height +- LoliModifier +- AvatarSize +- Size +- SizeScale +- Scaling + +These parameter names are not case sensitive and have been gathered from polling the community for common parameter names. + +Assuming the parameter is a float, ASTExtension will attempt to use it as the height parameter. Will automatically calibrate to the height range of the found parameter, assuming the scaling animation is in a blend tree / state using motion time & is linear. The scaling animation state **must be active** at time of avatar load. + +The max value ASTExtension will drive the parameter to is 100. As the mod is having to guess the max height, it may not be accurate if the max height is not capped at a multiple of 10. + +Examples: +- `AvatarScale` - 0 to 1 (slider) + - This is the default setup for Avatar Scale Tool and will work perfectly. +- `Scale` - 0 to 100 (input single) + - This will also work perfectly as the max height is a multiple of 10. +- `Height` - 0 to 2 (input single) + - This will not work properly. The max value to drive the parameter to is not a multiple of 10, and as such ASTExtension will believe the parameter range is 0 to 1. +- `BurntToast` - 0 to 10 (input single) + - This will not work properly. The parameter name is not recognized by ASTExtension. + +If your setup is theoretically supported but not working, it is likely the scaling animation is not linear or has loop enabled if using Motion Time, making the first and last frame identical height. In this case, you will need to fix your animation clip curves / blend tree to be linear &|| not loop, or use Avatar Scale Tool to generate a new scaling animation. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/InteractionTest/InteractionTest.csproj b/.Deprecated/SuperAwesomeMod/SuperAwesomeMod.csproj similarity index 52% rename from InteractionTest/InteractionTest.csproj rename to .Deprecated/SuperAwesomeMod/SuperAwesomeMod.csproj index 1b7a95f..fa113da 100644 --- a/InteractionTest/InteractionTest.csproj +++ b/.Deprecated/SuperAwesomeMod/SuperAwesomeMod.csproj @@ -1,13 +1,12 @@ + + ASTExtension + ..\.ManagedLibs\BTKUILib.dll + False - - - - - diff --git a/.Deprecated/SuperAwesomeMod/format.json b/.Deprecated/SuperAwesomeMod/format.json new file mode 100644 index 0000000..5ad14ea --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/format.json @@ -0,0 +1,24 @@ +{ + "_id": 223, + "name": "ASTExtension", + "modversion": "1.0.2", + "gameversion": "2025r178", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.", + "searchtags": [ + "tool", + "scaling", + "height", + "extension", + "avatar" + ], + "requirements": [ + "BTKUILib" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ASTExtension.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/", + "changelog": "- Fixes for 2025r178", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/.DepricatedMods/SwitchToDesktopOnSteamVRExit/Main.cs b/.Deprecated/SwitchToDesktopOnSteamVRExit/Main.cs similarity index 100% rename from .DepricatedMods/SwitchToDesktopOnSteamVRExit/Main.cs rename to .Deprecated/SwitchToDesktopOnSteamVRExit/Main.cs diff --git a/.DepricatedMods/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs b/.Deprecated/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs rename to .Deprecated/SwitchToDesktopOnSteamVRExit/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/SwitchToDesktopOnSteamVRExit/README.md b/.Deprecated/SwitchToDesktopOnSteamVRExit/README.md similarity index 100% rename from .DepricatedMods/SwitchToDesktopOnSteamVRExit/README.md rename to .Deprecated/SwitchToDesktopOnSteamVRExit/README.md diff --git a/.DepricatedMods/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj b/.Deprecated/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj similarity index 100% rename from .DepricatedMods/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj rename to .Deprecated/SwitchToDesktopOnSteamVRExit/SwitchToDesktopOnSteamVRExit.csproj diff --git a/.DepricatedMods/SwitchToDesktopOnSteamVRExit/format.json b/.Deprecated/SwitchToDesktopOnSteamVRExit/format.json similarity index 100% rename from .DepricatedMods/SwitchToDesktopOnSteamVRExit/format.json rename to .Deprecated/SwitchToDesktopOnSteamVRExit/format.json diff --git a/.DepricatedMods/TrackedControllerFix/HarmonyPatches.cs b/.Deprecated/TrackedControllerFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/TrackedControllerFix/HarmonyPatches.cs rename to .Deprecated/TrackedControllerFix/HarmonyPatches.cs diff --git a/.DepricatedMods/TrackedControllerFix/Main.cs b/.Deprecated/TrackedControllerFix/Main.cs similarity index 100% rename from .DepricatedMods/TrackedControllerFix/Main.cs rename to .Deprecated/TrackedControllerFix/Main.cs diff --git a/.DepricatedMods/TrackedControllerFix/Properties/AssemblyInfo.cs b/.Deprecated/TrackedControllerFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/TrackedControllerFix/Properties/AssemblyInfo.cs rename to .Deprecated/TrackedControllerFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/TrackedControllerFix/README.md b/.Deprecated/TrackedControllerFix/README.md similarity index 100% rename from .DepricatedMods/TrackedControllerFix/README.md rename to .Deprecated/TrackedControllerFix/README.md diff --git a/.DepricatedMods/TrackedControllerFix/TrackedControllerFix.csproj b/.Deprecated/TrackedControllerFix/TrackedControllerFix.csproj similarity index 100% rename from .DepricatedMods/TrackedControllerFix/TrackedControllerFix.csproj rename to .Deprecated/TrackedControllerFix/TrackedControllerFix.csproj diff --git a/.DepricatedMods/TrackedControllerFix/TrackedControllerFixer.cs b/.Deprecated/TrackedControllerFix/TrackedControllerFixer.cs similarity index 100% rename from .DepricatedMods/TrackedControllerFix/TrackedControllerFixer.cs rename to .Deprecated/TrackedControllerFix/TrackedControllerFixer.cs diff --git a/.DepricatedMods/TrackedControllerFix/format.json b/.Deprecated/TrackedControllerFix/format.json similarity index 100% rename from .DepricatedMods/TrackedControllerFix/format.json rename to .Deprecated/TrackedControllerFix/format.json diff --git a/.DepricatedMods/TrackedPointFix/HarmonyPatches.cs b/.Deprecated/TrackedPointFix/HarmonyPatches.cs similarity index 100% rename from .DepricatedMods/TrackedPointFix/HarmonyPatches.cs rename to .Deprecated/TrackedPointFix/HarmonyPatches.cs diff --git a/.DepricatedMods/TrackedPointFix/Main.cs b/.Deprecated/TrackedPointFix/Main.cs similarity index 100% rename from .DepricatedMods/TrackedPointFix/Main.cs rename to .Deprecated/TrackedPointFix/Main.cs diff --git a/.DepricatedMods/TrackedPointFix/Properties/AssemblyInfo.cs b/.Deprecated/TrackedPointFix/Properties/AssemblyInfo.cs similarity index 100% rename from .DepricatedMods/TrackedPointFix/Properties/AssemblyInfo.cs rename to .Deprecated/TrackedPointFix/Properties/AssemblyInfo.cs diff --git a/.DepricatedMods/TrackedPointFix/README.md b/.Deprecated/TrackedPointFix/README.md similarity index 100% rename from .DepricatedMods/TrackedPointFix/README.md rename to .Deprecated/TrackedPointFix/README.md diff --git a/.DepricatedMods/TrackedPointFix/TrackedPointFix.csproj b/.Deprecated/TrackedPointFix/TrackedPointFix.csproj similarity index 100% rename from .DepricatedMods/TrackedPointFix/TrackedPointFix.csproj rename to .Deprecated/TrackedPointFix/TrackedPointFix.csproj diff --git a/.DepricatedMods/TrackedPointFix/format.json b/.Deprecated/TrackedPointFix/format.json similarity index 100% rename from .DepricatedMods/TrackedPointFix/format.json rename to .Deprecated/TrackedPointFix/format.json diff --git a/VisualCloneFix/Main.cs b/.Deprecated/VisualCloneFix/Main.cs similarity index 100% rename from VisualCloneFix/Main.cs rename to .Deprecated/VisualCloneFix/Main.cs diff --git a/.Deprecated/VisualCloneFix/Patches.cs b/.Deprecated/VisualCloneFix/Patches.cs new file mode 100644 index 0000000..d1fb84b --- /dev/null +++ b/.Deprecated/VisualCloneFix/Patches.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ABI_RC.Core.Player.LocalClone; +using ABI_RC.Core.Player.TransformHider; +using ABI.CCK.Components; +using HarmonyLib; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace NAK.VisualCloneFix; + +public static class Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))] + private static bool OnSetupAvatar(GameObject avatar) + { + if (!VisualCloneFixMod.EntryUseVisualClone.Value) return true; + LocalCloneHelper.SetupAvatar(avatar); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(LocalCloneHelper), nameof(LocalCloneHelper.CollectTransformToExclusionMap))] + private static bool CollectTransformToExclusionMap( + Component root, Transform headBone, + ref Dictionary __result) + { + // add an fpr exclusion to the head bone + if (!headBone.TryGetComponent(out FPRExclusion headExclusion)) + { + headExclusion = headBone.gameObject.AddComponent(); + headExclusion.isShown = false; // default to hidden + headExclusion.target = headBone; + } + + MeshHiderExclusion headExclusionBehaviour = new(); + headExclusion.behaviour = headExclusionBehaviour; + headExclusionBehaviour.id = 1; // head bone is always 1 + + // get all FPRExclusions + var fprExclusions = root.GetComponentsInChildren(true); + + // get all valid exclusion targets, and destroy invalid exclusions + Dictionary exclusionTargets = new(); + + int nextId = 2; + foreach (FPRExclusion exclusion in fprExclusions) + { + if (exclusion.target == null + || exclusionTargets.ContainsKey(exclusion.target) + || !exclusion.target.gameObject.scene.IsValid()) + continue; // invalid exclusion + + if (exclusion.behaviour == null) // head exclusion is already created + { + MeshHiderExclusion meshHiderExclusion = new(); + exclusion.behaviour = meshHiderExclusion; + meshHiderExclusion.id = nextId++; + } + + // first to add wins + exclusionTargets.TryAdd(exclusion.target, exclusion); + } + + // process each FPRExclusion (recursive) + int exclusionCount = exclusionTargets.Values.Count; + for (var index = 0; index < exclusionCount; index++) + { + FPRExclusion exclusion = exclusionTargets.Values.ElementAt(index); + ProcessExclusion(exclusion, exclusion.target); + exclusion.UpdateExclusions(); // initial state + } + + __result = exclusionTargets; + return false; + + void ProcessExclusion(FPRExclusion exclusion, Transform transform) + { + if (exclusionTargets.ContainsKey(transform) + && exclusionTargets[transform] != exclusion) return; // found other exclusion root + + exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) + foreach (Transform child in transform) + ProcessExclusion(exclusion, child); // process children + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SkinnedLocalClone), nameof(SkinnedLocalClone.FindExclusionVertList))] + private static bool FindExclusionVertList( + SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, + ref int[] __result) + { + // Start the stopwatch + Stopwatch stopwatch = new(); + stopwatch.Start(); + + var boneWeights = renderer.sharedMesh.boneWeights; + var bones = renderer.bones; + int boneCount = bones.Length; + + bool[] boneHasExclusion = new bool[boneCount]; + + // Populate the weights array + for (int i = 0; i < boneCount; i++) + { + Transform bone = bones[i]; + if (bone == null) continue; + if (exclusions.ContainsKey(bone)) + boneHasExclusion[i] = true; + } + + const float minWeightThreshold = 0.2f; + + int[] vertexIndices = new int[renderer.sharedMesh.vertexCount]; + + // Check bone weights and add vertex to exclusion list if needed + for (int i = 0; i < boneWeights.Length; i++) + { + BoneWeight weight = boneWeights[i]; + Transform bone; + + if (boneHasExclusion[weight.boneIndex0] && weight.weight0 > minWeightThreshold) + bone = bones[weight.boneIndex0]; + else if (boneHasExclusion[weight.boneIndex1] && weight.weight1 > minWeightThreshold) + bone = bones[weight.boneIndex1]; + else if (boneHasExclusion[weight.boneIndex2] && weight.weight2 > minWeightThreshold) + bone = bones[weight.boneIndex2]; + else if (boneHasExclusion[weight.boneIndex3] && weight.weight3 > minWeightThreshold) + bone = bones[weight.boneIndex3]; + else continue; + + if (exclusions.TryGetValue(bone, out FPRExclusion exclusion)) + vertexIndices[i] = ((MeshHiderExclusion)(exclusion.behaviour)).id; + } + + // Stop the stopwatch + stopwatch.Stop(); + + // Log the execution time + Debug.Log($"FindExclusionVertList execution time: {stopwatch.ElapsedMilliseconds} ms"); + + __result = vertexIndices; + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(MeshHiderExclusion), nameof(MeshHiderExclusion.UpdateExclusions))] + private static bool OnUpdateExclusions(bool isShown, bool shrinkToZero, ref int ___id) + { + if (isShown) LocalCloneManager.cullingMask &= ~(1 << ___id); + else LocalCloneManager.cullingMask |= 1 << ___id; + return false; + } +} \ No newline at end of file diff --git a/VisualCloneFix/Properties/AssemblyInfo.cs b/.Deprecated/VisualCloneFix/Properties/AssemblyInfo.cs similarity index 100% rename from VisualCloneFix/Properties/AssemblyInfo.cs rename to .Deprecated/VisualCloneFix/Properties/AssemblyInfo.cs diff --git a/VisualCloneFix/README.md b/.Deprecated/VisualCloneFix/README.md similarity index 100% rename from VisualCloneFix/README.md rename to .Deprecated/VisualCloneFix/README.md diff --git a/VisualCloneFix/VisualCloneFix.csproj b/.Deprecated/VisualCloneFix/VisualCloneFix.csproj similarity index 100% rename from VisualCloneFix/VisualCloneFix.csproj rename to .Deprecated/VisualCloneFix/VisualCloneFix.csproj diff --git a/VisualCloneFix/format.json b/.Deprecated/VisualCloneFix/format.json similarity index 100% rename from VisualCloneFix/format.json rename to .Deprecated/VisualCloneFix/format.json diff --git a/.Deprecated/WhereAmIPointing/Main.cs b/.Deprecated/WhereAmIPointing/Main.cs new file mode 100644 index 0000000..24529b4 --- /dev/null +++ b/.Deprecated/WhereAmIPointing/Main.cs @@ -0,0 +1,106 @@ +using ABI_RC.Core.InteractionSystem; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.WhereAmIPointing; + +public class WhereAmIPointingMod : MelonMod +{ + #region Melon Preferences + + // cannot disable because then id need extra logic to reset the alpha :) + // private const string SettingsCategory = nameof(WhereAmIPointingMod); + // + // private static readonly MelonPreferences_Category Category = + // MelonPreferences.CreateCategory(SettingsCategory); + // + // private static readonly MelonPreferences_Entry Entry_Enabled = + // Category.CreateEntry("enabled", true, display_name: "Enabled",description: "Toggle WhereAmIPointingMod entirely."); + + #endregion Melon Preferences + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(ControllerRay_Patches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #region Patches + + private static class ControllerRay_Patches + { + private const float ORIGINAL_ALPHA = 0.502f; + private const float INTERACTION_ALPHA = 0.1f; + private const float RAY_LENGTH = 1000f; // game normally raycasts to PositiveInfinity... -_- + + [HarmonyPostfix] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.LateUpdate))] + private static void Postfix_ControllerRay_LateUpdate(ref ControllerRay __instance) + { + if (__instance.isDesktopRay + || !__instance.enabled + || !__instance.IsTracking() + || !__instance.lineRenderer) + return; + + UpdateLineRendererAlpha(__instance); + + if (__instance.lineRenderer.enabled + || !ShouldOverrideLineRenderer(__instance)) + return; + + UpdateLineRendererPosition(__instance); + } + + private static void UpdateLineRendererAlpha(ControllerRay instance) + { + Material material = instance.lineRenderer.material; + Color color = material.color; + + bool anyMenuOpen = ViewManager.Instance.IsAnyMenuOpen; + float targetAlpha = (!anyMenuOpen || instance.uiActive) ? ORIGINAL_ALPHA : INTERACTION_ALPHA; + if (!(Math.Abs(color.a - targetAlpha) > float.Epsilon)) + return; + + color.a = targetAlpha; + material.color = color; + } + + private static bool ShouldOverrideLineRenderer(ControllerRay instance) + { + if (!ViewManager.Instance.IsAnyMenuOpen) + return false; + + if (CVR_MenuManager.Instance.IsQuickMenuOpen + && instance.hand == CVR_MenuManager.Instance.SelectedQuickMenuHand) + return false; + + return true; + } + + private static void UpdateLineRendererPosition(ControllerRay instance) + { + Vector3 rayOrigin = instance.rayDirectionTransform.position; + Vector3 rayEnd = rayOrigin + instance.rayDirectionTransform.forward * RAY_LENGTH; + + instance.lineRenderer.SetPosition(0, instance.lineRenderer.transform.InverseTransformPoint(rayOrigin)); + instance.lineRenderer.SetPosition(1, instance.lineRenderer.transform.InverseTransformPoint(rayEnd)); + instance.lineRenderer.enabled = true; + } + } + + #endregion Patches +} \ No newline at end of file diff --git a/.Deprecated/WhereAmIPointing/Properties/AssemblyInfo.cs b/.Deprecated/WhereAmIPointing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..48c359f --- /dev/null +++ b/.Deprecated/WhereAmIPointing/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.WhereAmIPointing.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.WhereAmIPointing))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.WhereAmIPointing))] + +[assembly: MelonInfo( + typeof(NAK.WhereAmIPointing.WhereAmIPointingMod), + nameof(NAK.WhereAmIPointing), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.WhereAmIPointing.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.1"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/.Deprecated/WhereAmIPointing/README.md b/.Deprecated/WhereAmIPointing/README.md new file mode 100644 index 0000000..c78a56a --- /dev/null +++ b/.Deprecated/WhereAmIPointing/README.md @@ -0,0 +1,14 @@ +# WhereAmIPointing + +Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/WhereAmIPointing/WhereAmIPointing.csproj b/.Deprecated/WhereAmIPointing/WhereAmIPointing.csproj new file mode 100644 index 0000000..728edb7 --- /dev/null +++ b/.Deprecated/WhereAmIPointing/WhereAmIPointing.csproj @@ -0,0 +1,6 @@ + + + + net48 + + diff --git a/.Deprecated/WhereAmIPointing/format.json b/.Deprecated/WhereAmIPointing/format.json new file mode 100644 index 0000000..654911a --- /dev/null +++ b/.Deprecated/WhereAmIPointing/format.json @@ -0,0 +1,23 @@ +{ + "_id": 234, + "name": "WhereAmIPointing", + "modversion": "1.0.1", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.", + "searchtags": [ + "controller", + "ray", + "line", + "tomato" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", + "changelog": "- Fixed line renderer alpha not being reset when the menu is closed.", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7a3e958..2abd364 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +# NAK +.Experimental/ + # Nstrip & ManagedLibs stuff NStrip.exe .ManagedLibs/*.dll diff --git a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs index cbb7534..e0e294a 100644 --- a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs +++ b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs @@ -1,7 +1,9 @@ using ABI_RC.Core.Player; +using ABI_RC.Core.Util.AnimatorManager; using BTKUILib; using BTKUILib.UIObjects; using BTKUILib.UIObjects.Components; +using UnityEngine; namespace NAK.ASTExtension.Integrations; @@ -29,6 +31,9 @@ public static partial class BtkUiAddon Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ASTExtensionMod.ModName, ASTExtensionMod.ModName); Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height."); button.OnPress += OnCopyPlayerHeight; + + Button button2 = category.AddButton("Copy AAS", string.Empty, "Copy selected players AAS."); + button2.OnPress += OnCopyPlayerAAS; } private static void OnPlayerSelected(string _, string id) @@ -38,18 +43,56 @@ public static partial class BtkUiAddon private static void OnCopyPlayerHeight() { - if (string.IsNullOrEmpty(_selectedPlayer)) - return; - - if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player)) - return; - - if (player._avatar == null) - return; + if (string.IsNullOrEmpty(_selectedPlayer)) + return; + + if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player)) + return; + + if (player._avatar == null) + return; - float height = player.netIkController.GetRemoteHeight(); - ASTExtensionMod.Instance.SetAvatarHeight(height); + float height = player.netIkController.GetRemoteHeight(); + ASTExtensionMod.Instance.SetAvatarHeight(height); + } + + private static void OnCopyPlayerAAS() + { + if (string.IsNullOrEmpty(_selectedPlayer)) + return; + + if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player)) + return; + + AvatarAnimatorManager localAnimator = PlayerSetup.Instance.animatorManager; + AvatarAnimatorManager remoteAnimator = player.animatorManager; + if (!localAnimator.IsInitialized + || !remoteAnimator.IsInitialized) + return; + + // Copy AAS + foreach ((var parameterName, CVRAnimatorManager.ParamDef paramDef) in remoteAnimator.Parameters) + { + switch (paramDef.type) + { + case AnimatorControllerParameterType.Trigger: + case AnimatorControllerParameterType.Bool: + remoteAnimator.GetParameter(parameterName, out bool value); + localAnimator.SetParameter(parameterName, value); + break; + case AnimatorControllerParameterType.Float: + remoteAnimator.GetParameter(parameterName, out float value2); + localAnimator.SetParameter(parameterName, value2); + break; + case AnimatorControllerParameterType.Int: + remoteAnimator.GetParameter(parameterName, out int value3); + localAnimator.SetParameter(parameterName, value3); + break; + } } + + } + #endregion Player Select Page } \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.API.cs b/AvatarCloneTest/AvatarClone/AvatarClone.API.cs deleted file mode 100644 index 89835bd..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.API.cs +++ /dev/null @@ -1,68 +0,0 @@ -using UnityEngine; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - #region Public API Methods - - /// - /// Sets whether the specific renderer requires additional runtime checks when copying to the clone. - /// For example, Magica Cloth modifies the sharedMesh & bones of the renderer at runtime. This is not needed - /// for most renderers, so copying for all renderers would be inefficient. - /// - public void SetRendererNeedsAdditionalChecks(Renderer rend, bool needsChecks) - { - switch (rend) - { - case MeshRenderer meshRenderer: - { - int index = _standardRenderers.IndexOf(meshRenderer); - if (index == -1) return; - - if (needsChecks && !_standardRenderersNeedingChecks.Contains(index)) - { - int insertIndex = _standardRenderersNeedingChecks.Count; - _standardRenderersNeedingChecks.Add(index); - _cachedSharedMeshes.Insert(insertIndex, null); - } - else if (!needsChecks) - { - int removeIndex = _standardRenderersNeedingChecks.IndexOf(index); - if (removeIndex != -1) - { - _standardRenderersNeedingChecks.RemoveAt(removeIndex); - _cachedSharedMeshes.RemoveAt(removeIndex); - } - } - return; - } - case SkinnedMeshRenderer skinnedRenderer: - { - int index = _skinnedRenderers.IndexOf(skinnedRenderer); - if (index == -1) return; - - if (needsChecks && !_skinnedRenderersNeedingChecks.Contains(index)) - { - int insertIndex = _skinnedRenderersNeedingChecks.Count; - _skinnedRenderersNeedingChecks.Add(index); - _cachedSharedMeshes.Insert(_standardRenderersNeedingChecks.Count + insertIndex, null); - _cachedSkinnedBoneCounts.Add(0); - } - else if (!needsChecks) - { - int removeIndex = _skinnedRenderersNeedingChecks.IndexOf(index); - if (removeIndex != -1) - { - _skinnedRenderersNeedingChecks.RemoveAt(removeIndex); - _cachedSharedMeshes.RemoveAt(_standardRenderersNeedingChecks.Count + removeIndex); - _cachedSkinnedBoneCounts.RemoveAt(removeIndex); - } - } - break; - } - } - } - - #endregion Public API Methods -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs deleted file mode 100644 index 8fb54ec..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Clones.cs +++ /dev/null @@ -1,103 +0,0 @@ -using ABI_RC.Core; -using UnityEngine; -using UnityEngine.Rendering; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - #region Clone Creation - - private void CreateClones() - { - int standardCount = _standardRenderers.Count; - _standardClones = new List(standardCount); - _standardCloneFilters = new List(standardCount); - for (int i = 0; i < standardCount; i++) CreateStandardClone(i); - - int skinnedCount = _skinnedRenderers.Count; - _skinnedClones = new List(skinnedCount); - for (int i = 0; i < skinnedCount; i++) CreateSkinnedClone(i); - } - - private void CreateStandardClone(int index) - { - MeshRenderer sourceRenderer = _standardRenderers[index]; - MeshFilter sourceFilter = _standardFilters[index]; - - GameObject go = new(sourceRenderer.name + "_VisualClone") - { - layer = CVRLayers.PlayerClone - }; - - go.transform.SetParent(sourceRenderer.transform, false); - //go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - - MeshRenderer cloneRenderer = go.AddComponent(); - MeshFilter cloneFilter = go.AddComponent(); - - // Initial setup - cloneRenderer.sharedMaterials = sourceRenderer.sharedMaterials; - cloneRenderer.shadowCastingMode = ShadowCastingMode.Off; - cloneRenderer.probeAnchor = sourceRenderer.probeAnchor; - cloneRenderer.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity); - cloneFilter.sharedMesh = sourceFilter.sharedMesh; - - // Optimizations to enforce - cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; - cloneRenderer.allowOcclusionWhenDynamic = false; - - // Optimizations to enforce - sourceRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; - sourceRenderer.allowOcclusionWhenDynamic = false; - - _standardClones.Add(cloneRenderer); - _standardCloneFilters.Add(cloneFilter); - } - - private void CreateSkinnedClone(int index) - { - SkinnedMeshRenderer source = _skinnedRenderers[index]; - - GameObject go = new(source.name + "_VisualClone") - { - layer = CVRLayers.PlayerClone - }; - - go.transform.SetParent(source.transform, false); - //go.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - - SkinnedMeshRenderer clone = go.AddComponent(); - - // Initial setup - clone.sharedMaterials = source.sharedMaterials; - clone.shadowCastingMode = ShadowCastingMode.Off; - clone.probeAnchor = source.probeAnchor; - clone.localBounds = new Bounds(Vector3.zero, Vector3.positiveInfinity); - clone.sharedMesh = source.sharedMesh; - clone.rootBone = source.rootBone; - clone.bones = source.bones; - clone.quality = source.quality; - clone.updateWhenOffscreen = source.updateWhenOffscreen; - - // Optimizations to enforce - clone.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; - clone.allowOcclusionWhenDynamic = false; - clone.updateWhenOffscreen = false; - clone.skinnedMotionVectors = false; - clone.forceMatrixRecalculationPerRender = false; - clone.quality = SkinQuality.Bone4; - - // Optimizations to enforce - source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; - source.allowOcclusionWhenDynamic = false; - source.updateWhenOffscreen = false; - source.skinnedMotionVectors = false; - source.forceMatrixRecalculationPerRender = false; - source.quality = SkinQuality.Bone4; - - _skinnedClones.Add(clone); - } - - #endregion Clone Creation -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs deleted file mode 100644 index d5136e3..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusion.cs +++ /dev/null @@ -1,141 +0,0 @@ -using ABI.CCK.Components; -using UnityEngine; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - private readonly Dictionary> _exclusionDirectRenderers = new(); - private readonly Dictionary> _exclusionControlledBones = new(); - - private void InitializeExclusions() - { - // Add head exclusion for humanoid avatars if not present - var animator = GetComponent(); - if (animator != null && animator.isHuman) - { - var headBone = animator.GetBoneTransform(HumanBodyBones.Head); - if (headBone != null && headBone.GetComponent() == null) - { - var exclusion = headBone.gameObject.AddComponent(); - exclusion.isShown = false; - exclusion.target = headBone; - exclusion.shrinkToZero = true; - } - } - - // Process existing exclusions bottom-up - var exclusions = GetComponentsInChildren(true); - - for (int i = exclusions.Length - 1; i >= 0; i--) - { - var exclusion = exclusions[i]; - if (exclusion.target == null) - exclusion.target = exclusion.transform; - - // Skip invalid exclusions or already processed targets - if (exclusion.target == null || _exclusionDirectRenderers.ContainsKey(exclusion.target)) - { - Destroy(exclusion); - continue; - } - - // Initialize data for this exclusion - _exclusionDirectRenderers[exclusion.target] = new HashSet(); - _exclusionControlledBones[exclusion.target] = new HashSet(); - - // Set up our behaviour - exclusion.behaviour = new AvatarCloneExclusion(this, exclusion.target); - - // Collect affected renderers and bones - CollectExclusionData(exclusion.target); - - // Initial update - exclusion.UpdateExclusions(); - } - } - - private void CollectExclusionData(Transform target) - { - var stack = new Stack(); - stack.Push(target); - - while (stack.Count > 0) - { - var current = stack.Pop(); - - // Skip if this transform belongs to another exclusion - if (current != target && current.GetComponent() != null) - continue; - - _exclusionControlledBones[target].Add(current); - - // Add renderers that will need their clone visibility toggled - foreach (var renderer in current.GetComponents()) - { - // Find corresponding clone renderer - if (renderer is MeshRenderer meshRenderer) - { - int index = _standardRenderers.IndexOf(meshRenderer); - if (index != -1) - _exclusionDirectRenderers[target].Add(_standardClones[index]); - } - else if (renderer is SkinnedMeshRenderer skinnedRenderer) - { - int index = _skinnedRenderers.IndexOf(skinnedRenderer); - if (index != -1) - _exclusionDirectRenderers[target].Add(_skinnedClones[index]); - } - } - - // Add children to stack - foreach (Transform child in current) - { - stack.Push(child); - } - } - } - - public void HandleExclusionUpdate(Transform target, Transform shrinkBone, bool isShown) - { - if (!_exclusionDirectRenderers.TryGetValue(target, out var directCloneRenderers) || - !_exclusionControlledBones.TryGetValue(target, out var controlledBones)) - return; - - // Handle direct clone renderers - foreach (var cloneRenderer in directCloneRenderers) - { - cloneRenderer.enabled = isShown; - } - - // Update bone references in clone renderers - int cloneCount = _skinnedClones.Count; - var cloneRenderers = _skinnedClones; - var sourceRenderers = _skinnedRenderers; - - for (int i = 0; i < cloneCount; i++) - { - var clone = cloneRenderers[i]; - var source = sourceRenderers[i]; - var sourceBones = source.bones; - var cloneBones = clone.bones; - int boneCount = cloneBones.Length; - bool needsUpdate = false; - - for (int j = 0; j < boneCount; j++) - { - // Check if this bone is in our controlled set - if (controlledBones.Contains(sourceBones[j])) - { - cloneBones[j] = isShown ? sourceBones[j] : shrinkBone; - needsUpdate = true; - } - } - - if (needsUpdate) - { - clone.bones = cloneBones; - } - } - } -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs new file mode 100644 index 0000000..cedbcdd --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs @@ -0,0 +1,229 @@ +using ABI.CCK.Components; +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Exclusions + + private FPRExclusion[] _exclusions; + + private void AddExclusionToHeadIfNeeded() + { + if (!TryGetComponent(out Animator animator) + || !animator.isHuman + || !animator.avatar + || !animator.avatar.isValid) + return; + + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + if (!head) + return; + + GameObject headGo = head.gameObject; + if (headGo.TryGetComponent(out FPRExclusion exclusion)) + return; + + exclusion = headGo.AddComponent(); + exclusion.target = head; + exclusion.isShown = false; + } + + private void InitializeExclusions() + { + _exclusions = GetComponentsInChildren(true); + var exclusionRoots = new Dictionary(_exclusions.Length); + + // **1. Precompute Exclusions** + foreach (FPRExclusion exclusion in _exclusions) + { + Transform target = exclusion.target ??= exclusion.transform; + if (exclusionRoots.ContainsKey(target) || !target.gameObject.scene.IsValid()) + continue; + + AvatarCloneExclusion behaviour = new AvatarCloneExclusion(this, target); + exclusion.behaviour = behaviour; + exclusionRoots.Add(target, behaviour); + } + + // Process Exclusion Transforms + Renderer ourRenderer; + + void ProcessTransformHierarchy(Transform current, Transform root, AvatarCloneExclusion behaviour) + { + if (exclusionRoots.ContainsKey(current) && current != root) return; + + behaviour.affectedTransforms.Add(current); + if (current.TryGetComponent(out ourRenderer)) + behaviour.affectedRenderers.Add(ourRenderer); + + for (int i = 0; i < current.childCount; i++) + { + Transform child = current.GetChild(i); + if (!exclusionRoots.ContainsKey(child)) + ProcessTransformHierarchy(child, root, behaviour); + } + } + + foreach (var entry in exclusionRoots) + { + Transform rootTransform = entry.Key; + AvatarCloneExclusion behaviour = entry.Value; + ProcessTransformHierarchy(rootTransform, rootTransform, behaviour); + behaviour.affectedTransformSet = new HashSet(behaviour.affectedTransforms); + } + + // ------------------------------ + // **OPTIMIZED EXCLUSION BONE MAPPING** + // ------------------------------ + + Dictionary.ValueCollection exclusionBehaviours = exclusionRoots.Values; + int skinnedCount = _skinnedClones.Count; + + // **2. Precompute Bone-to-Exclusion Mapping** + int estimatedBoneCount = skinnedCount * 20; // Estimated bones per skinned mesh + var boneToExclusion = new Dictionary>(estimatedBoneCount); + + foreach (AvatarCloneExclusion behaviour in exclusionBehaviours) + { + foreach (Transform bone in behaviour.affectedTransformSet) + { + if (!boneToExclusion.TryGetValue(bone, out var list)) + { + list = new List(2); + boneToExclusion[bone] = list; + } + list.Add(behaviour); + } + } + + // **3. Process Skinned Mesh Renderers** + for (int s = 0; s < skinnedCount; s++) + { + SkinnedMeshRenderer source = _skinnedRenderers[s]; + var bones = source.bones; // Cache bones array + + SkinnedMeshRenderer smr = _skinnedClones[s]; + int boneCount = bones.Length; + + for (int i = 0; i < boneCount; i++) + { + Transform bone = bones[i]; + + // **Skip if the bone isn't mapped to exclusions** + if (!bone // Skip null bones + || !boneToExclusion.TryGetValue(bone, out var behaviours)) + continue; + + // **Avoid redundant dictionary lookups** + for (int j = 0; j < behaviours.Count; j++) + { + AvatarCloneExclusion behaviour = behaviours[j]; + + if (!behaviour.skinnedToBoneIndex.TryGetValue(smr, out var indices)) + { + indices = new List(4); + behaviour.skinnedToBoneIndex[smr] = indices; + } + + indices.Add(i); + } + } + } + + ApplyInitialExclusionState(); + } + + public void ApplyInitialExclusionState() + { + foreach (FPRExclusion exclusion in _exclusions) + { + exclusion._wasShown = exclusion.isShown; + if (!exclusion.isShown) exclusion.UpdateExclusions(); + } + } + + public void HandleExclusionUpdate(AvatarCloneExclusion exclusion, bool isShown) + { +#if ENABLE_PROFILER + s_UpdateExclusions.Begin(); +#endif + + // **1. Update Renderer Visibility** + foreach (Renderer renderer in exclusion.affectedRenderers) + { + if (renderer is SkinnedMeshRenderer skinned) + { + int index = _skinnedRenderers.IndexOf(skinned); + if (index >= 0) _skinnedClones[index].gameObject.SetActive(isShown); + } + else if (renderer is MeshRenderer mesh) + { + int index = _meshRenderers.IndexOf(mesh); + if (index >= 0) + { + if (Setting_CloneMeshRenderers) + { + _meshClones[index].gameObject.SetActive(isShown); + } + else + { + // Other renderer (never cloned) - update shadow casting state + _sourceShouldBeHiddenFromFPR[index] = !isShown; // When hidden, use for shadows + } + } + } + else if (renderer) + { + int index = _otherRenderers.IndexOf(renderer); + if (index >= 0) + { + int shadowIndex = index + (Setting_CloneMeshRenderers ? _meshRenderers.Count : 0); + _sourceShouldBeHiddenFromFPR[shadowIndex] = !isShown; // When hidden, use for shadows + } + } + } + + // **2. Update Bone References in Skinned Mesh Renderers** + UpdateSkinnedMeshBones(exclusion, exclusion._shrinkBone, isShown); + +#if ENABLE_PROFILER + s_UpdateExclusions.End(); +#endif + } + + private void UpdateSkinnedMeshBones(AvatarCloneExclusion exclusion, Transform shrinkBone, bool isShown) + { +#if ENABLE_PROFILER + s_HandleBoneUpdates.Begin(); +#endif + + foreach (var smrEntry in exclusion.skinnedToBoneIndex) + { + SkinnedMeshRenderer smr = smrEntry.Key; + var indices = smrEntry.Value; + bool needsUpdate = false; + + var parentBones = smr.transform.parent.GetComponent().bones; + var cloneBones = smr.bones; + Array.Resize(ref cloneBones, parentBones.Length); + + // Only modify our bones, other exclusions may have modified others + for (int i = 0; i < indices.Count; i++) + { + int index = indices[i]; + if (!isShown) cloneBones[index] = shrinkBone; + else cloneBones[index] = parentBones[index]; + needsUpdate = true; + } + if (needsUpdate) smr.bones = cloneBones; + } + +#if ENABLE_PROFILER + s_HandleBoneUpdates.End(); +#endif + } + + #endregion Exclusions +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs deleted file mode 100644 index 0ba69b0..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Fields.cs +++ /dev/null @@ -1,67 +0,0 @@ -using UnityEngine; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - #region Profile Markers -//#if UNITY_EDITOR - private static readonly UnityEngine.Profiling.CustomSampler s_CopyMaterials = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMaterials"); - private static readonly UnityEngine.Profiling.CustomSampler s_CopyBlendShapes = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyBlendShapes"); - private static readonly UnityEngine.Profiling.CustomSampler s_CopyMeshes = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.CopyMeshes"); - - private static readonly UnityEngine.Profiling.CustomSampler s_MyOnPreRender = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.MyOnPreRender"); - private static readonly UnityEngine.Profiling.CustomSampler s_SetShadowsOnly = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetShadowsOnly"); - private static readonly UnityEngine.Profiling.CustomSampler s_UndoShadowsOnly = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoShadowsOnly"); - private static readonly UnityEngine.Profiling.CustomSampler s_SetUiCulling = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.SetUiCulling"); - private static readonly UnityEngine.Profiling.CustomSampler s_UndoUiCulling = - UnityEngine.Profiling.CustomSampler.Create("AvatarClone2.UndoUiCulling"); - -//#endif - #endregion Profile Markers - - #region Source Renderers - private List _standardRenderers; - private List _standardFilters; - private List _skinnedRenderers; - private List _allSourceRenderers; // For shadow casting only - #endregion Source Renderers - - #region Clone Renderers - private List _standardClones; - private List _standardCloneFilters; - private List _skinnedClones; - #endregion Clone Renderers - - #region Dynamic Check Lists - private List _standardRenderersNeedingChecks; // Stores indices into _standardRenderers - private List _skinnedRenderersNeedingChecks; // Stores indices into _skinnedRenderers - private List _cachedSkinnedBoneCounts; // So we don't copy the bones unless they've changed - private List _cachedSharedMeshes; // So we don't copy the mesh unless it's changed - #endregion Dynamic Check Lists - - #region Material Data - private List _localMaterials; - private List _cullingMaterials; - private List _mainMaterials; - private MaterialPropertyBlock _propertyBlock; - #endregion Material Data - - #region Blend Shape Data - private List> _blendShapeWeights; - #endregion Blend Shape Data - - #region Shadow and UI Culling Settings - private bool _uiCullingActive; - private bool _shadowsOnlyActive; - private bool[] _originallyHadShadows; - private bool[] _originallyWasEnabled; - #endregion Shadow and UI Culling Settings -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs index 41bd84b..f02fe9b 100644 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs @@ -1,5 +1,6 @@ using ABI_RC.Core.Player.ShadowClone; using UnityEngine; +using UnityEngine.Rendering; namespace NAK.AvatarCloneTest; @@ -9,120 +10,295 @@ public partial class AvatarClone private void InitializeCollections() { - _standardRenderers = new List(); - _standardFilters = new List(); +#if ENABLE_PROFILER + s_InitializeData.Begin(); +#endif + + // Initialize source collections _skinnedRenderers = new List(); - _allSourceRenderers = new List(); + _blendShapeWeights = new List>(); - _standardClones = new List(); - _standardCloneFilters = new List(); + _meshRenderers = new List(); + _meshFilters = new List(); + + _otherRenderers = new List(); + + // Initialize clone collections _skinnedClones = new List(); + _skinnedCloneMaterials = new List(); + _skinnedCloneCullingMaterials = new List(); - _standardRenderersNeedingChecks = new List(); - _skinnedRenderersNeedingChecks = new List(); - _cachedSkinnedBoneCounts = new List(); - _cachedSharedMeshes = new List(); + if (Setting_CloneMeshRenderers) + { + _meshClones = new List(); + _meshCloneFilters = new List(); + _meshCloneMaterials = new List(); + _meshCloneCullingMaterials = new List(); + } - _localMaterials = new List(); - _cullingMaterials = new List(); - _mainMaterials = new List(); + // Initialize shared resources + _materialWorkingList = new List(); _propertyBlock = new MaterialPropertyBlock(); - _blendShapeWeights = new List>(); +#if ENABLE_PROFILER + s_InitializeData.End(); +#endif } - private void InitializeRenderers() + private void CollectRenderers() { - var renderers = GetComponentsInChildren(true); + #if ENABLE_PROFILER + s_InitializeData.Begin(); + #endif - // Pre-size lists based on found renderers - // _standardRenderers.Capacity = renderers.Length; - // _standardFilters.Capacity = renderers.Length; - // _skinnedRenderers.Capacity = renderers.Length; - // _allSourceRenderers.Capacity = renderers.Length; - - // Sort renderers into their respective lists - foreach (Renderer render in renderers) + var renderers = GetComponentsInChildren(true); + var currentIndex = 0; + var nonCloned = 0; + + // Single pass: directly categorize renderers + foreach (Renderer renderer in renderers) { - _allSourceRenderers.Add(render); - - switch (render) + switch (renderer) { - case MeshRenderer meshRenderer: - { - MeshFilter filter = meshRenderer.GetComponent(); + case SkinnedMeshRenderer skinned when skinned.sharedMesh != null: + AddSkinnedRenderer(skinned); + currentIndex++; + break; + + case MeshRenderer mesh: + MeshFilter filter = mesh.GetComponent(); if (filter != null && filter.sharedMesh != null) { - _standardRenderers.Add(meshRenderer); - _standardFilters.Add(filter); + if (Setting_CloneMeshRenderers) + { + AddMeshRenderer(mesh, filter); + } + else + { + AddMeshRenderer(mesh, filter); + nonCloned++; + } + currentIndex++; } break; - } - case SkinnedMeshRenderer skinnedRenderer: - { - if (skinnedRenderer.sharedMesh != null) _skinnedRenderers.Add(skinnedRenderer); + + default: + AddOtherRenderer(renderer); + currentIndex++; + nonCloned++; break; - } } } - } - - private void SetupMaterialsAndBlendShapes() - { - // Cache counts - int standardCount = _standardRenderers.Count; - int skinnedCount = _skinnedRenderers.Count; - var standardRenderers = _standardRenderers; - var skinnedRenderers = _skinnedRenderers; - var localMats = _localMaterials; - var cullingMats = _cullingMaterials; - var blendWeights = _blendShapeWeights; - // Setup standard renderer materials - for (int i = 0; i < standardCount; i++) - { - MeshRenderer render = standardRenderers[i]; - int matCount = render.sharedMaterials.Length; - - // Local materials array - var localMatArray = new Material[matCount]; - for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j]; - localMats.Add(localMatArray); - - // Culling materials array - var cullingMatArray = new Material[matCount]; - for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial; - cullingMats.Add(cullingMatArray); - } - - // Setup skinned renderer materials and blend shapes - for (int i = 0; i < skinnedCount; i++) - { - SkinnedMeshRenderer render = skinnedRenderers[i]; - int matCount = render.sharedMaterials.Length; - - // Local materials array - var localMatArray = new Material[matCount]; - for (int j = 0; j < matCount; j++) localMatArray[j] = render.sharedMaterials[j]; - localMats.Add(localMatArray); - - // Culling materials array - var cullingMatArray = new Material[matCount]; - for (int j = 0; j < matCount; j++) cullingMatArray[j] = ShadowCloneUtils.cullingMaterial; - cullingMats.Add(cullingMatArray); - - // Blend shape weights - int blendShapeCount = render.sharedMesh.blendShapeCount; - var weights = new List(blendShapeCount); - for (int j = 0; j < blendShapeCount; j++) weights.Add(0f); - blendWeights.Add(weights); - } - - // Initialize renderer state arrays - int totalRenderers = _allSourceRenderers.Count; - _originallyHadShadows = new bool[totalRenderers]; - _originallyWasEnabled = new bool[totalRenderers]; + _rendererActiveStates = new bool[currentIndex]; + _originalShadowCastingMode = new ShadowCastingMode[currentIndex]; + _sourceShouldBeHiddenFromFPR = new bool[nonCloned]; + + #if ENABLE_PROFILER + s_InitializeData.End(); + #endif } + private void AddSkinnedRenderer(SkinnedMeshRenderer renderer) + { +#if ENABLE_PROFILER + s_AddRenderer.Begin(); +#endif + + _skinnedRenderers.Add(renderer); + + // Clone materials array for clone renderer + var materials = renderer.sharedMaterials; + var cloneMaterials = new Material[materials.Length]; + for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i]; + _skinnedCloneMaterials.Add(cloneMaterials); + + // Cache culling materials + var cullingMaterialArray = new Material[materials.Length]; +#if !UNITY_EDITOR + for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial; +#else + for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial; +#endif + _skinnedCloneCullingMaterials.Add(cullingMaterialArray); + + // Cache blend shape weights + var weights = new List(renderer.sharedMesh.blendShapeCount); + for (int i = 0; i < renderer.sharedMesh.blendShapeCount; i++) weights.Add(0f); + _blendShapeWeights.Add(weights); + +#if ENABLE_PROFILER + s_AddRenderer.End(); +#endif + } + + private void AddMeshRenderer(MeshRenderer renderer, MeshFilter filter) + { +#if ENABLE_PROFILER + s_AddRenderer.Begin(); +#endif + + _meshRenderers.Add(renderer); + _meshFilters.Add(filter); + + if (!Setting_CloneMeshRenderers) return; + + // Clone materials array for clone renderer + var materials = renderer.sharedMaterials; + var cloneMaterials = new Material[materials.Length]; + for (int i = 0; i < materials.Length; i++) cloneMaterials[i] = materials[i]; + _meshCloneMaterials.Add(cloneMaterials); + + // Cache culling materials + var cullingMaterialArray = new Material[materials.Length]; +#if !UNITY_EDITOR + for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = ShadowCloneUtils.cullingMaterial; +#else + for (int i = 0; i < materials.Length; i++) cullingMaterialArray[i] = cullingMaterial; +#endif + _meshCloneCullingMaterials.Add(cullingMaterialArray); + +#if ENABLE_PROFILER + s_AddRenderer.End(); +#endif + } + + private void AddOtherRenderer(Renderer renderer) + { +#if ENABLE_PROFILER + s_AddRenderer.Begin(); +#endif + _otherRenderers.Add(renderer); +#if ENABLE_PROFILER + s_AddRenderer.End(); +#endif + } + + private void CreateClones() + { +#if ENABLE_PROFILER + s_InitializeData.Begin(); +#endif + + // Always create skinned mesh clones + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++) + { + CreateSkinnedClone(i); + } + + // Optionally create mesh clones + if (Setting_CloneMeshRenderers) + { + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++) + { + CreateMeshClone(i); + } + } + +#if ENABLE_PROFILER + s_InitializeData.End(); +#endif + } + + private void CreateSkinnedClone(int index) + { +#if ENABLE_PROFILER + s_CreateClone.Begin(); +#endif + + SkinnedMeshRenderer source = _skinnedRenderers[index]; + + GameObject clone = new(source.name + "_Clone") + { + layer = CLONE_LAYER + }; + + clone.transform.SetParent(source.transform, false); + + SkinnedMeshRenderer cloneRenderer = clone.AddComponent(); + + // Basic setup + cloneRenderer.sharedMaterials = _skinnedCloneMaterials[index]; + cloneRenderer.shadowCastingMode = ShadowCastingMode.Off; + cloneRenderer.probeAnchor = source.probeAnchor; + cloneRenderer.sharedMesh = source.sharedMesh; + cloneRenderer.rootBone = source.rootBone; + cloneRenderer.bones = source.bones; + +#if !UNITY_EDITOR + cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f); +#endif + + // Quality settings + cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + cloneRenderer.allowOcclusionWhenDynamic = false; + cloneRenderer.updateWhenOffscreen = false; + cloneRenderer.skinnedMotionVectors = false; + cloneRenderer.forceMatrixRecalculationPerRender = false; + cloneRenderer.quality = SkinQuality.Bone4; + + source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + source.allowOcclusionWhenDynamic = false; + source.updateWhenOffscreen = false; + source.skinnedMotionVectors = false; + source.forceMatrixRecalculationPerRender = false; + source.quality = SkinQuality.Bone4; + + // Add to clone list + _skinnedClones.Add(cloneRenderer); + +#if ENABLE_PROFILER + s_CreateClone.End(); +#endif + } + + private void CreateMeshClone(int index) + { +#if ENABLE_PROFILER + s_CreateClone.Begin(); +#endif + + MeshRenderer source = _meshRenderers[index]; + MeshFilter sourceFilter = _meshFilters[index]; + + GameObject clone = new(source.name + "_Clone") + { + layer = CLONE_LAYER + }; + + clone.transform.SetParent(source.transform, false); + + MeshRenderer cloneRenderer = clone.AddComponent(); + MeshFilter cloneFilter = clone.AddComponent(); + + // Basic setup + cloneRenderer.sharedMaterials = _meshCloneMaterials[index]; + cloneRenderer.shadowCastingMode = ShadowCastingMode.Off; + cloneRenderer.probeAnchor = source.probeAnchor; + +#if !UNITY_EDITOR + cloneRenderer.localBounds = new Bounds(source.localBounds.center, source.localBounds.size * 2f); +#endif + + cloneFilter.sharedMesh = sourceFilter.sharedMesh; + + // Quality settings + cloneRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + cloneRenderer.allowOcclusionWhenDynamic = false; + + source.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + source.allowOcclusionWhenDynamic = false; + + // Add to clone lists + _meshClones.Add(cloneRenderer); + _meshCloneFilters.Add(cloneFilter); + +#if ENABLE_PROFILER + s_CreateClone.End(); +#endif + } + #endregion Initialization } \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs b/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs deleted file mode 100644 index 7ae5855..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.MagicaSupport.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MagicaCloth; -using MagicaCloth2; -using UnityEngine; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - #region Magica Cloth Support - - private void SetupMagicaClothSupport() - { - var magicaCloths1 = GetComponentsInChildren(true); - foreach (MagicaRenderDeformer magicaCloth in magicaCloths1) - { - // Get the renderer on the same object - Renderer renderer = magicaCloth.gameObject.GetComponent(); - SetRendererNeedsAdditionalChecks(renderer, true); - } - - var magicaCloths2 = GetComponentsInChildren(true); - foreach (MagicaCloth2.MagicaCloth magicaCloth in magicaCloths2) - { - if (magicaCloth.serializeData.clothType != ClothProcess.ClothType.MeshCloth) - continue; // Only matters for cloth physics - - // Set the affected renderers as requiring extra checks - var renderers = magicaCloth.serializeData.sourceRenderers; - foreach (Renderer renderer in renderers) SetRendererNeedsAdditionalChecks(renderer, true); - } - } - - #endregion Magica Cloth Support -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs b/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs new file mode 100644 index 0000000..40c1df7 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs @@ -0,0 +1,212 @@ +using UnityEngine; +using UnityEngine.Rendering; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region Render State Management + + private void MyOnPreCull(Camera cam) + { +#if UNITY_EDITOR + // Scene & Preview cameras are not needed + if (cam.cameraType != CameraType.Game) + return; +#endif + +#if ENABLE_PROFILER + s_PreCullUpdate.Begin(); +#endif + + bool isOurUiCamera = IsUIInternalCamera(cam); + bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam); + bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam); + + bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer; + + // Handle shadow casting when camera renders both layers + if (!_sourcesSetForShadowCasting + && rendersBothPlayerLayers) + { + ConfigureSourceShadowCasting(true); + _sourcesSetForShadowCasting = true; + } + else if (_sourcesSetForShadowCasting && !rendersBothPlayerLayers) + { + ConfigureSourceShadowCasting(false); + _sourcesSetForShadowCasting = false; + } + + // Handle UI culling for clone layer + if (!_clonesSetForUiCulling + && isOurUiCamera && rendersOurCloneLayer) + { + ConfigureCloneUICulling(true); + _clonesSetForUiCulling = true; + } + else if (_clonesSetForUiCulling) + { + ConfigureCloneUICulling(false); + _clonesSetForUiCulling = false; + } + +#if ENABLE_PROFILER + s_PreCullUpdate.End(); +#endif + } + + private void ConfigureSourceShadowCasting(bool setSourcesToShadowCast) + { +#if ENABLE_PROFILER + s_ConfigureShadowCasting.Begin(); +#endif + + int currentIndex = 0; + int shadowArrayIndex = 0; + + // Handle skinned mesh renderers (always have clones) + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) continue; + + SkinnedMeshRenderer source = _skinnedRenderers[i]; + + if (setSourcesToShadowCast) + { + ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode; + if (originalMode == ShadowCastingMode.Off) + source.forceRenderingOff = true; + else + source.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + } + else + { + source.shadowCastingMode = _originalShadowCastingMode[currentIndex]; + source.forceRenderingOff = false; + } + } + + // Handle mesh renderers based on clone setting + if (Setting_CloneMeshRenderers) + { + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) continue; + + MeshRenderer source = _meshRenderers[i]; + + if (setSourcesToShadowCast) + { + ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode; + if (originalMode == ShadowCastingMode.Off) + source.forceRenderingOff = true; + else + source.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + } + else + { + source.shadowCastingMode = _originalShadowCastingMode[currentIndex]; + source.forceRenderingOff = false; + } + } + } + else + { + // When not cloned, mesh renderers use the shadow casting array + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++, shadowArrayIndex++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) continue; + if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue; + + MeshRenderer source = _meshRenderers[i]; + + if (setSourcesToShadowCast) + { + ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode; + if (originalMode == ShadowCastingMode.Off) + source.forceRenderingOff = true; + else + source.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + } + else + { + source.shadowCastingMode = _originalShadowCastingMode[currentIndex]; + source.forceRenderingOff = false; + } + } + } + + // Handle other renderers (never cloned) + int otherCount = _otherRenderers.Count; + for (int i = 0; i < otherCount; i++, shadowArrayIndex++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) continue; + if (!_sourceShouldBeHiddenFromFPR[shadowArrayIndex]) continue; + + Renderer source = _otherRenderers[i]; + + if (setSourcesToShadowCast) + { + ShadowCastingMode originalMode = _originalShadowCastingMode[currentIndex] = source.shadowCastingMode; + if (originalMode == ShadowCastingMode.Off) + source.forceRenderingOff = true; + else + source.shadowCastingMode = ShadowCastingMode.ShadowsOnly; + } + else + { + source.shadowCastingMode = _originalShadowCastingMode[currentIndex]; + source.forceRenderingOff = false; + } + } + +#if ENABLE_PROFILER + s_ConfigureShadowCasting.End(); +#endif + } + + private void ConfigureCloneUICulling(bool enableCulling) + { +#if ENABLE_PROFILER + s_ConfigureUICulling.Begin(); +#endif + + // Set the materials to our culling materials + int currentIndex = 0; + + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) + continue; + + _skinnedClones[i].sharedMaterials = enableCulling ? + _skinnedCloneCullingMaterials[i] : + _skinnedCloneMaterials[i]; + } + + if (Setting_CloneMeshRenderers) + { + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) + continue; + + _meshClones[i].sharedMaterials = enableCulling ? + _meshCloneCullingMaterials[i] : + _meshCloneMaterials[i]; + } + } + +#if ENABLE_PROFILER + s_ConfigureUICulling.End(); +#endif + } + + #endregion Render State Management +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs b/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs new file mode 100644 index 0000000..eb70282 --- /dev/null +++ b/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs @@ -0,0 +1,156 @@ +using UnityEngine; + +namespace NAK.AvatarCloneTest; + +public partial class AvatarClone +{ + #region State Syncing + + private void SyncEnabledState() + { +#if ENABLE_PROFILER + s_CopyEnabledState.Begin(); +#endif + + int currentIndex = 0; + + // Update skinned mesh renderers + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++, currentIndex++) + { + SkinnedMeshRenderer source = _skinnedRenderers[i]; + _skinnedClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source); + } + + // Update mesh renderers + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++, currentIndex++) + { + MeshRenderer source = _meshRenderers[i]; + if (Setting_CloneMeshRenderers) _meshClones[i].enabled = _rendererActiveStates[currentIndex] = IsRendererActive(source); + else _rendererActiveStates[currentIndex] = IsRendererActive(source); + } + + // Update other renderers + int otherCount = _otherRenderers.Count; + for (int i = 0; i < otherCount; i++, currentIndex++) + { + Renderer source = _otherRenderers[i]; + _rendererActiveStates[currentIndex] = IsRendererActive(source); + } + +#if ENABLE_PROFILER + s_CopyEnabledState.End(); +#endif + } + + private void SyncMaterials() + { +#if ENABLE_PROFILER + s_CopyMaterials.Begin(); +#endif + int currentIndex = 0; + + // Sync skinned mesh materials + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) + continue; + + CopyMaterialsAndProperties( + _skinnedRenderers[i], + _skinnedClones[i], + _propertyBlock, + _materialWorkingList, + _skinnedCloneMaterials[i]); + } + + // Sync mesh materials if enabled + if (Setting_CloneMeshRenderers) + { + int meshCount = _meshRenderers.Count; + for (int i = 0; i < meshCount; i++, currentIndex++) + { + if (!_rendererActiveStates[currentIndex]) + continue; + + CopyMaterialsAndProperties( + _meshRenderers[i], + _meshClones[i], + _propertyBlock, + _materialWorkingList, + _meshCloneMaterials[i]); + } + } + +#if ENABLE_PROFILER + s_CopyMaterials.End(); +#endif + } + + private void SyncBlendShapes() + { +#if ENABLE_PROFILER + s_CopyBlendShapes.Begin(); +#endif + + int skinnedCount = _skinnedRenderers.Count; + for (int i = 0; i < skinnedCount; i++) + { + SkinnedMeshRenderer source = _skinnedRenderers[i]; + if (!_rendererActiveStates[i]) + continue; + + CopyBlendShapes( + source, + _skinnedClones[i], + _blendShapeWeights[i]); + } + +#if ENABLE_PROFILER + s_CopyBlendShapes.End(); +#endif + } + + private static void CopyMaterialsAndProperties( + Renderer source, + Renderer clone, + MaterialPropertyBlock propertyBlock, + List workingList, + Material[] cloneMaterials) + { + source.GetSharedMaterials(workingList); + + int matCount = workingList.Count; + bool hasChanged = false; + + for (int i = 0; i < matCount; i++) + { + if (ReferenceEquals(workingList[i], cloneMaterials[i])) continue; + cloneMaterials[i] = workingList[i]; + hasChanged = true; + } + if (hasChanged) clone.sharedMaterials = cloneMaterials; + + source.GetPropertyBlock(propertyBlock); + clone.SetPropertyBlock(propertyBlock); + } + + private static void CopyBlendShapes( + SkinnedMeshRenderer source, + SkinnedMeshRenderer clone, + List weights) + { + int weightCount = weights.Count; + for (int i = 0; i < weightCount; i++) + { + float weight = source.GetBlendShapeWeight(i); + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (weight == weights[i]) continue; // Halves the work + clone.SetBlendShapeWeight(i, weights[i] = weight); + } + } + + #endregion State Syncing +} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs deleted file mode 100644 index 42cef73..0000000 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Update.cs +++ /dev/null @@ -1,181 +0,0 @@ -using UnityEngine; - -namespace NAK.AvatarCloneTest; - -public partial class AvatarClone -{ - #region Update Methods - - private void UpdateStandardRenderers() - { - int count = _standardRenderers.Count; - var sourceRenderers = _standardRenderers; - var cloneRenderers = _standardClones; - var localMats = _localMaterials; - - for (int i = 0; i < count; i++) - { - if (!IsRendererValid(sourceRenderers[i])) continue; - CopyMaterialsAndProperties( - sourceRenderers[i], - cloneRenderers[i], - _propertyBlock, - _mainMaterials, - localMats[i]); - } - } - - private void UpdateSkinnedRenderers() - { - int standardCount = _standardRenderers.Count; - int count = _skinnedRenderers.Count; - var sourceRenderers = _skinnedRenderers; - var cloneRenderers = _skinnedClones; - var localMats = _localMaterials; - var blendWeights = _blendShapeWeights; - - for (int i = 0; i < count; i++) - { - SkinnedMeshRenderer source = sourceRenderers[i]; - if (!IsRendererValid(source)) continue; - - SkinnedMeshRenderer clone = cloneRenderers[i]; - CopyMaterialsAndProperties( - source, - clone, - _propertyBlock, - _mainMaterials, - localMats[i + standardCount]); - - CopyBlendShapes(source, clone, blendWeights[i]); - } - } - - private void UpdateStandardRenderersWithChecks() - { - s_CopyMeshes.Begin(); - - var cloneFilters = _standardCloneFilters; - var sourceFilters = _standardFilters; - var cachedMeshes = _cachedSharedMeshes; - var checkIndices = _standardRenderersNeedingChecks; - int checkCount = checkIndices.Count; - - while (cachedMeshes.Count < checkCount) cachedMeshes.Add(null); - - for (int i = 0; i < checkCount; i++) - { - int rendererIndex = checkIndices[i]; - Mesh newMesh = sourceFilters[rendererIndex].sharedMesh; - if (ReferenceEquals(newMesh, cachedMeshes[i])) continue; - cloneFilters[rendererIndex].sharedMesh = newMesh; // expensive & allocates - cachedMeshes[i] = newMesh; - } - - s_CopyMeshes.End(); - } - - private void UpdateSkinnedRenderersWithChecks() - { - s_CopyMeshes.Begin(); - - var sourceRenderers = _skinnedRenderers; - var cloneRenderers = _skinnedClones; - var cachedMeshes = _cachedSharedMeshes; - var cachedBoneCounts = _cachedSkinnedBoneCounts; - var checkIndices = _skinnedRenderersNeedingChecks; - int checkCount = checkIndices.Count; - int meshOffset = _standardRenderersNeedingChecks.Count; - - // Ensure cache lists are properly sized - while (cachedMeshes.Count < meshOffset + checkCount) cachedMeshes.Add(null); - while (cachedBoneCounts.Count < checkCount) cachedBoneCounts.Add(0); - - for (int i = 0; i < checkCount; i++) - { - int rendererIndex = checkIndices[i]; - SkinnedMeshRenderer source = sourceRenderers[rendererIndex]; - SkinnedMeshRenderer clone = cloneRenderers[rendererIndex]; - - // Check mesh changes - Mesh newMesh = source.sharedMesh; // expensive & allocates - if (!ReferenceEquals(newMesh, cachedMeshes[meshOffset + i])) - { - clone.sharedMesh = newMesh; - cachedMeshes[meshOffset + i] = newMesh; - } - - // Check bone changes - var sourceBones = source.bones; - int newBoneCount = sourceBones.Length; - int oldBoneCount = cachedBoneCounts[i]; - if (newBoneCount == oldBoneCount) - continue; - - var cloneBones = clone.bones; // expensive & allocates - if (newBoneCount > oldBoneCount) - { - // Resize array and copy only the new bones (Magica Cloth appends bones when enabling Mesh Cloth) - Array.Resize(ref cloneBones, newBoneCount); - for (int boneIndex = oldBoneCount; boneIndex < newBoneCount; boneIndex++) - cloneBones[boneIndex] = sourceBones[boneIndex]; - clone.bones = cloneBones; - } - else - { - // If shrinking, just set the whole array - clone.bones = sourceBones; - } - - cachedBoneCounts[i] = newBoneCount; - } - - s_CopyMeshes.End(); - } - - private static void CopyMaterialsAndProperties( - Renderer source, Renderer clone, - MaterialPropertyBlock propertyBlock, - List mainMaterials, - Material[] localMaterials) - { - s_CopyMaterials.Begin(); - - source.GetSharedMaterials(mainMaterials); - - int matCount = mainMaterials.Count; - bool hasChanged = false; - for (var i = 0; i < matCount; i++) - { - if (ReferenceEquals(mainMaterials[i], localMaterials[i])) continue; - localMaterials[i] = mainMaterials[i]; - hasChanged = true; - } - if (hasChanged) clone.sharedMaterials = localMaterials; - - source.GetPropertyBlock(propertyBlock); - clone.SetPropertyBlock(propertyBlock); - - s_CopyMaterials.End(); - } - - private static void CopyBlendShapes( - SkinnedMeshRenderer source, - SkinnedMeshRenderer target, - List blendShapeWeights) - { - s_CopyBlendShapes.Begin(); - - int weightCount = blendShapeWeights.Count; - for (var i = 0; i < weightCount; i++) - { - var weight = source.GetBlendShapeWeight(i); - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (weight == blendShapeWeights[i]) continue; // Halves the work - target.SetBlendShapeWeight(i, blendShapeWeights[i] = weight); - } - - s_CopyBlendShapes.End(); - } - #endregion Update Methods -} \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs b/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs index c6fb033..63f0d45 100644 --- a/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs +++ b/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs @@ -1,20 +1,81 @@ -using ABI_RC.Core; -using ABI_RC.Core.Player; +using ABI_RC.Core.Player; +using MagicaCloth; +using MagicaCloth2; using UnityEngine; namespace NAK.AvatarCloneTest; public partial class AvatarClone { + #region Utilities + + private static bool IsRendererActive(Renderer renderer) + => renderer && renderer.enabled && renderer.gameObject.activeInHierarchy; + private static bool CameraRendersPlayerLocalLayer(Camera cam) - => (cam.cullingMask & (1 << CVRLayers.PlayerLocal)) != 0; + => (cam.cullingMask & (1 << LOCAL_LAYER)) != 0; private static bool CameraRendersPlayerCloneLayer(Camera cam) - => (cam.cullingMask & (1 << CVRLayers.PlayerClone)) != 0; - + => (cam.cullingMask & (1 << CLONE_LAYER)) != 0; + private static bool IsUIInternalCamera(Camera cam) +#if !UNITY_EDITOR => cam == PlayerSetup.Instance.activeUiCam; +#else + => cam.gameObject.layer == 15; +#endif - private static bool IsRendererValid(Renderer renderer) - => renderer && renderer.gameObject.activeInHierarchy; + #endregion Utilities + + #region Magica Cloth Support + + private void SetupMagicaClothSupport() + { + var magicaCloths1 = GetComponentsInChildren(true); + foreach (BaseCloth magicaCloth1 in magicaCloths1) + magicaCloth1.SetCullingMode(PhysicsTeam.TeamCullingMode.Off); + + var magicaCloths2 = base.GetComponentsInChildren(true); + foreach (MagicaCloth2.MagicaCloth magicaCloth2 in magicaCloths2) + magicaCloth2.serializeData.cullingSettings.cameraCullingMode = CullingSettings.CameraCullingMode.AnimatorLinkage; + } + + public void OnMagicaClothMeshSwapped(Renderer render, Mesh newMesh) + { + switch (render) + { + case MeshRenderer mesh: + { + int index = _meshRenderers.IndexOf(mesh); + if (index != -1) _meshCloneFilters[index].sharedMesh = newMesh; + break; + } + case SkinnedMeshRenderer skinned: + { + int index = _skinnedRenderers.IndexOf(skinned); + if (index != -1) + { + // Copy the mesh + _skinnedClones[index].sharedMesh = newMesh; + + // Copy appended bones if count is different + var cloneBones = _skinnedClones[index].bones; // alloc + var sourceBones = skinned.bones; // alloc + + int cloneBoneCount = cloneBones.Length; + int sourceBoneCount = sourceBones.Length; + if (cloneBoneCount != sourceBoneCount) + { + // Copy the new bones only + Array.Resize(ref cloneBones, sourceBoneCount); + for (int i = cloneBoneCount; i < sourceBoneCount; i++) cloneBones[i] = sourceBones[i]; + _skinnedClones[index].bones = cloneBones; + } + } + break; + } + } + } + + #endregion Magica Cloth Support } \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.cs b/AvatarCloneTest/AvatarClone/AvatarClone.cs index 88c8112..ed15ec1 100644 --- a/AvatarCloneTest/AvatarClone/AvatarClone.cs +++ b/AvatarCloneTest/AvatarClone/AvatarClone.cs @@ -1,3 +1,4 @@ +using NAK.AvatarCloneTest; using UnityEngine; using UnityEngine.Rendering; @@ -5,130 +6,142 @@ namespace NAK.AvatarCloneTest; public partial class AvatarClone : MonoBehaviour { + #region Constants + + private const int LOCAL_LAYER = 8; + private const int CLONE_LAYER = 9; + + #endregion Constants + + #region Profiler Markers + +#if ENABLE_PROFILER + private static readonly ProfilerMarker s_CopyEnabledState = new($"{nameof(AvatarClone)}.{nameof(SyncEnabledState)}"); + private static readonly ProfilerMarker s_CopyMaterials = new($"{nameof(AvatarClone)}.{nameof(CopyMaterialsAndProperties)}"); + private static readonly ProfilerMarker s_CopyBlendShapes = new($"{nameof(AvatarClone)}.{nameof(CopyBlendShapes)}"); + private static readonly ProfilerMarker s_InitializeData = new($"{nameof(AvatarClone)}.Initialize"); + private static readonly ProfilerMarker s_UpdateExclusions = new($"{nameof(AvatarClone)}.{nameof(HandleExclusionUpdate)}"); + private static readonly ProfilerMarker s_CollectExclusionData = new($"{nameof(AvatarClone)}.{nameof(CollectExclusionData)}"); + private static readonly ProfilerMarker s_HandleBoneUpdates = new($"{nameof(AvatarClone)}.{nameof(UpdateSkinnedMeshBones)}"); + private static readonly ProfilerMarker s_PreCullUpdate = new($"{nameof(AvatarClone)}.{nameof(MyOnPreCull)}"); + private static readonly ProfilerMarker s_ConfigureShadowCasting = new($"{nameof(AvatarClone)}.{nameof(ConfigureSourceShadowCasting)}"); + private static readonly ProfilerMarker s_ConfigureUICulling = new($"{nameof(AvatarClone)}.{nameof(ConfigureCloneUICulling)}"); + private static readonly ProfilerMarker s_AddRenderer = new($"{nameof(AvatarClone)}.AddRenderer"); + private static readonly ProfilerMarker s_CreateClone = new($"{nameof(AvatarClone)}.CreateClone"); +#endif + + #endregion Profiler Markers + + #region Settings + + public bool Setting_CloneMeshRenderers; + public bool Setting_CopyMaterials = true; + public bool Setting_CopyBlendShapes = true; + + #endregion Settings + + #region Source Collections - Cloned Renderers + + // Skinned mesh renderers (always cloned) + private List _skinnedRenderers; + private List> _blendShapeWeights; + + // Mesh renderers (optionally cloned) + private List _meshRenderers; + private List _meshFilters; + + #endregion Source Collections - Cloned Renderers + + #region Source Collections - Non-Cloned Renderers + + // All other renderers (never cloned) + private List _otherRenderers; + + // True if source renderer should hide. False if source renderer should show. + // Only used for non-cloned renderers (MeshRenderers and other Renderers). + private bool[] _sourceShouldBeHiddenFromFPR; + // Three states: On, ShadowsOnly, Off + private ShadowCastingMode[] _originalShadowCastingMode; + + #endregion Source Collections - Non-Cloned Renderers + + #region Clone Collections + + // Skinned mesh clones + private List _skinnedClones; + private List _skinnedCloneMaterials; + private List _skinnedCloneCullingMaterials; + + // Mesh clones (optional) + private List _meshClones; + private List _meshCloneFilters; + private List _meshCloneMaterials; + private List _meshCloneCullingMaterials; + + #endregion Clone Collections + + #region Shared Resources + + private List _materialWorkingList; // Used for GetSharedMaterials + private MaterialPropertyBlock _propertyBlock; + + #endregion Shared Resources + + #region State + + private bool _sourcesSetForShadowCasting; + private bool _clonesSetForUiCulling; + private bool[] _rendererActiveStates; + + #endregion State + #region Unity Events private void Start() { - InitializeCollections(); - InitializeRenderers(); - SetupMaterialsAndBlendShapes(); - CreateClones(); + Setting_CloneMeshRenderers = AvatarCloneTestMod.EntryCloneMeshRenderers.Value; + InitializeCollections(); + CollectRenderers(); + CreateClones(); + AddExclusionToHeadIfNeeded(); InitializeExclusions(); SetupMagicaClothSupport(); - Camera.onPreCull += MyOnPreRender; - } + // bool animatesClone = transform.Find("[ExplicitlyAnimatesVisualClones]") != null; + // Setting_CopyMaterials = !animatesClone; + // Setting_CopyBlendShapes = !animatesClone; + // Animator animator = GetComponent(); + // if (animator && animatesClone) animator.Rebind(); - private void OnDestroy() - { - Camera.onPreCull -= MyOnPreRender; - } - - private void LateUpdate() - { - // Update all renderers with basic properties (Materials & BlendShapes) - UpdateStandardRenderers(); - UpdateSkinnedRenderers(); - - // Additional pass for renderers needing extra checks (Shared Mesh & Bone Changes) - UpdateStandardRenderersWithChecks(); - UpdateSkinnedRenderersWithChecks(); + // Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff +#if !UNITY_EDITOR + Camera.onPreCull += MyOnPreCull; +#else + Camera.onPreRender += MyOnPreCull; +#endif } - private void MyOnPreRender(Camera cam) + private void LateUpdate() { - s_MyOnPreRender.Begin(); + SyncEnabledState(); - bool isOurUiCamera = IsUIInternalCamera(cam); - bool rendersOurPlayerLayer = CameraRendersPlayerLocalLayer(cam); - bool rendersOurCloneLayer = CameraRendersPlayerCloneLayer(cam); + if (Setting_CopyMaterials && AvatarCloneTestMod.EntryCopyMaterials.Value) + SyncMaterials(); - // Renders both player layers. - // PlayerLocal will now act as a shadow caster, while PlayerClone will act as the actual head-hidden renderer. - bool rendersBothPlayerLayers = rendersOurPlayerLayer && rendersOurCloneLayer; - if (!_shadowsOnlyActive && rendersBothPlayerLayers) - { - s_SetShadowsOnly.Begin(); - - int sourceCount = _allSourceRenderers.Count; - var sourceRenderers = _allSourceRenderers; - for (int i = 0; i < sourceCount; i++) - { - Renderer renderer = sourceRenderers[i]; - if (!IsRendererValid(renderer)) continue; - - bool shouldRender = renderer.shadowCastingMode != ShadowCastingMode.Off; - _originallyWasEnabled[i] = renderer.enabled; - _originallyHadShadows[i] = shouldRender; - renderer.shadowCastingMode = ShadowCastingMode.ShadowsOnly; - if (renderer.forceRenderingOff == shouldRender) renderer.forceRenderingOff = !shouldRender; // TODO: Eval if check is needed - } - _shadowsOnlyActive = true; - - s_SetShadowsOnly.End(); - } - else if (_shadowsOnlyActive && !rendersBothPlayerLayers) - { - s_UndoShadowsOnly.Begin(); - - int sourceCount = _allSourceRenderers.Count; - var sourceRenderers = _allSourceRenderers; - for (int i = 0; i < sourceCount; i++) - { - Renderer renderer = sourceRenderers[i]; - if (!IsRendererValid(renderer)) continue; - - renderer.shadowCastingMode = _originallyHadShadows[i] ? ShadowCastingMode.On : ShadowCastingMode.Off; - if (renderer.forceRenderingOff == _originallyWasEnabled[i]) renderer.forceRenderingOff = !_originallyWasEnabled[i]; // TODO: Eval if check is needed - } - _shadowsOnlyActive = false; - - s_UndoShadowsOnly.End(); - } - - // Handle UI culling material changes - if (isOurUiCamera && !_uiCullingActive && rendersOurCloneLayer) - { - s_SetUiCulling.Begin(); - - int standardCount = _standardRenderers.Count; - var standardClones = _standardClones; - var cullingMaterials = _cullingMaterials; - for (int i = 0; i < standardCount; i++) - standardClones[i].sharedMaterials = cullingMaterials[i]; - - int skinnedCount = _skinnedRenderers.Count; - var skinnedClones = _skinnedClones; - for (int i = 0; i < skinnedCount; i++) - skinnedClones[i].sharedMaterials = cullingMaterials[i + standardCount]; - - _uiCullingActive = true; - - s_SetUiCulling.End(); - } - else if (!isOurUiCamera && _uiCullingActive) - { - s_UndoUiCulling.Begin(); - - int standardCount = _standardRenderers.Count; - var standardClones = _standardClones; - var localMaterials = _localMaterials; - for (int i = 0; i < standardCount; i++) - standardClones[i].sharedMaterials = localMaterials[i]; - - int skinnedCount = _skinnedRenderers.Count; - var skinnedClones = _skinnedClones; - for (int i = 0; i < skinnedCount; i++) - skinnedClones[i].sharedMaterials = localMaterials[i + standardCount]; - - _uiCullingActive = false; - - s_UndoUiCulling.End(); - } - - s_MyOnPreRender.End(); + if (Setting_CopyBlendShapes && AvatarCloneTestMod.EntryCopyBlendShapes.Value) + SyncBlendShapes(); } - + + private void OnDestroy() + { + // Likely a Unity bug with where we can touch shadowCastingMode & forceRenderingOff +#if !UNITY_EDITOR + Camera.onPreCull -= MyOnPreCull; +#else + Camera.onPreRender -= MyOnPreCull; +#endif + } + #endregion Unity Events } \ No newline at end of file diff --git a/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs b/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs index 5681761..4f38900 100644 --- a/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs +++ b/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs @@ -5,9 +5,14 @@ namespace NAK.AvatarCloneTest; public class AvatarCloneExclusion : IExclusionBehaviour { + public readonly Dictionary> skinnedToBoneIndex = new(); + public readonly List affectedTransforms = new(); + public readonly List affectedRenderers = new(); + public HashSet affectedTransformSet = new(); + private readonly AvatarClone _cloneSystem; private readonly Transform _target; - private Transform _shrinkBone; + internal Transform _shrinkBone; public bool isImmuneToGlobalState { get; set; } @@ -19,20 +24,16 @@ public class AvatarCloneExclusion : IExclusionBehaviour public void UpdateExclusions(bool isShown, bool shrinkToZero) { - Debug.Log($"[AvatarClone2] Updating exclusion for {_target.name}: isShown={isShown}, shrinkToZero={shrinkToZero}"); - if (_shrinkBone == null) { // Create shrink bone parented directly to target _shrinkBone = new GameObject($"{_target.name}_Shrink").transform; _shrinkBone.SetParent(_target, false); - Debug.Log($"[AvatarClone2] Created shrink bone for {_target.name}"); } - // Set scale based on shrink mode _shrinkBone.localScale = shrinkToZero ? Vector3.zero : Vector3.positiveInfinity; - // Let the clone system handle the update - _cloneSystem.HandleExclusionUpdate(_target, _shrinkBone, isShown); + // Replace the bone references with the shrink bone for the indicies we modify + _cloneSystem.HandleExclusionUpdate(this, isShown); } } \ No newline at end of file diff --git a/AvatarCloneTest/Main.cs b/AvatarCloneTest/Main.cs index 21a34bf..3bb4f09 100644 --- a/AvatarCloneTest/Main.cs +++ b/AvatarCloneTest/Main.cs @@ -1,4 +1,6 @@ using ABI_RC.Core; +using ABI_RC.Core.EventSystem; +using ABI_RC.Core.Savior; using MelonLoader; using UnityEngine; @@ -15,17 +17,17 @@ public class AvatarCloneTestMod : MelonMod Category.CreateEntry("use_avatar_clone_test", true, "Use Avatar Clone", description: "Uses the Avatar Clone setup for the local avatar."); - // internal static readonly MelonPreferences_Entry EntryCopyBlendShapes = - // Category.CreateEntry("copy_blend_shapes", true, - // "Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone."); - // - // internal static readonly MelonPreferences_Entry EntryCopyMaterials = - // Category.CreateEntry("copy_materials", true, - // "Copy Materials", description: "Copies the materials from the original avatar to the clone."); - // - // internal static readonly MelonPreferences_Entry EntryCopyMeshes = - // Category.CreateEntry("copy_meshes", true, - // "Copy Meshes", description: "Copies the meshes from the original avatar to the clone."); + internal static readonly MelonPreferences_Entry EntryCloneMeshRenderers = + Category.CreateEntry("clone_mesh_renderers", false, + "Clone Mesh Renderers", description: "Clones the mesh renderers from the original avatar to the clone."); + + internal static readonly MelonPreferences_Entry EntryCopyBlendShapes = + Category.CreateEntry("copy_blend_shapes", true, + "Copy Blend Shapes", description: "Copies the blend shapes from the original avatar to the clone."); + + internal static readonly MelonPreferences_Entry EntryCopyMaterials = + Category.CreateEntry("copy_materials", true, + "Copy Materials", description: "Copies the materials from the original avatar to the clone."); #endregion Melon Preferences @@ -49,6 +51,13 @@ public class AvatarCloneTestMod : MelonMod } } } + + // if pressing ctrl + r, reload avatar + if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.R)) + { + var player = MetaPort.Instance.currentAvatarGuid; + AssetManagement.Instance.LoadLocalAvatar(player); + } } #endregion Melon Events diff --git a/AvatarCloneTest/Patches.cs b/AvatarCloneTest/Patches.cs index b4cec17..ecd64c5 100644 --- a/AvatarCloneTest/Patches.cs +++ b/AvatarCloneTest/Patches.cs @@ -18,70 +18,5 @@ public static class Patches avatar.AddComponent(); return false; } - - // [HarmonyPostfix] - // [HarmonyPatch(typeof(FPRExclusion), nameof(FPRExclusion.UpdateExclusions))] - // private static void OnUpdateExclusions(ref FPRExclusion __instance) - // { - // AvatarClone clone = PlayerSetup.Instance._avatar.GetComponent(); - // if (clone == null) return; - // clone.SetBoneChainVisibility(__instance.target, !__instance.isShown, !__instance.shrinkToZero); - // } - - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRMirror), nameof(CVRMirror.Start))] - private static void OnMirrorStart(CVRMirror __instance) - { - if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) - return; - - // Don't reflect the player clone layer - __instance.m_ReflectLayers &= ~(1 << CVRLayers.PlayerClone); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Update))] - private static void OnTransformHiderManagerUpdate(PlayerSetup __instance) - { - if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) - return; - - if (MetaPort.Instance.settings.GetSettingsBool("ExperimentalAvatarOverrenderUI")) - __instance.activeUiCam.cullingMask |= 1 << CVRLayers.PlayerClone; - else - __instance.activeUiCam.cullingMask &= ~(1 << CVRLayers.PlayerClone); - } - - private static bool _wasDebugInPortableCamera; - - [HarmonyPostfix] - [HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.Update))] - private static void OnPortableCameraUpdate(ref PortableCamera __instance) - { - if (!AvatarCloneTestMod.EntryUseAvatarCloneTest.Value) - { - // Show both PlayerLocal and PlayerClone - __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal; - __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone; - return; - } - - if (TransformHiderManager.s_DebugInPortableCamera == _wasDebugInPortableCamera) - return; - - if (TransformHiderManager.s_DebugInPortableCamera) - { - // Hide PlayerLocal, show PlayerClone - __instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerLocal); - __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerClone; - } - else - { - // Show PlayerLocal, hide PlayerClone - __instance.cameraComponent.cullingMask |= 1 << CVRLayers.PlayerLocal; - __instance.cameraComponent.cullingMask &= ~(1 << CVRLayers.PlayerClone); - } - - _wasDebugInPortableCamera = TransformHiderManager.s_DebugInPortableCamera; - } -} \ No newline at end of file +} + \ No newline at end of file diff --git a/AvatarCloneTest/Properties/AssemblyInfo.cs b/AvatarCloneTest/Properties/AssemblyInfo.cs index 3334b28..84f8de2 100644 --- a/AvatarCloneTest/Properties/AssemblyInfo.cs +++ b/AvatarCloneTest/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.AvatarCloneTest.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs b/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs deleted file mode 100644 index 473dc45..0000000 --- a/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon_Utils.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Reflection; -using BTKUILib; -using BTKUILib.UIObjects; -using BTKUILib.UIObjects.Components; -using MelonLoader; -using UnityEngine; - -namespace NAK.AvatarScaleMod.Integrations -{ - public static partial class BtkUiAddon - { - #region Melon Preference Helpers - - private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry entry) - { - ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value); - toggle.OnValueUpdated += b => entry.Value = b; - return toggle; - } - - private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry entry, float min, - float max, int decimalPlaces = 2, bool allowReset = true) - { - SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description, - Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); - slider.OnValueUpdated += f => entry.Value = f; - return slider; - } - - private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { - Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); - button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s); - return button; - } - - private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { - Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); - button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f); - return button; - } - - // private static SliderFloat AddMelonSlider(ref Page page, MelonPreferences_Entry entry, float min, float max, int decimalPlaces = 2, bool allowReset = true) - // { - // SliderFloat slider = page.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); - // slider.OnValueUpdated += f => entry.Value = f; - // return slider; - // } - - /// - /// Helper method to create a category that saves its collapsed state to a MelonPreferences entry. - /// - /// - /// - /// - /// - private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry entry, bool showHeader = true) - { - Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value); - category.OnCollapse += b => entry.Value = b; - return category; - } - - #endregion - - #region Icon Utils - - private static Stream GetIconStream(string iconName) - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string assemblyName = assembly.GetName().Name; - return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}"); - } - - #endregion - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading.csproj b/BetterContentLoading/BetterContentLoading.csproj new file mode 100644 index 0000000..67cef19 --- /dev/null +++ b/BetterContentLoading/BetterContentLoading.csproj @@ -0,0 +1,15 @@ + + + + net48 + NAK.BetterContentLoading + + + + ..\.ManagedLibs\BTKUILib.dll + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs b/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs new file mode 100644 index 0000000..749483f --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs @@ -0,0 +1,210 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using ABI_RC.Systems.GameEventSystem; +using NAK.BetterContentLoading.Queue; +using UnityEngine; + +namespace NAK.BetterContentLoading; + +// Download world -> connect to instance -> receive all Avatar data +// -> initial connection to instance -> receive all props data + +// We receive Prop download data only after we have connected to the instance. Avatar data we seem to receive +// prior to our initial connection event firing. + +public class BetterDownloadManager +{ + #region Singleton + + private static BetterDownloadManager _instance; + public static BetterDownloadManager Instance => _instance ??= new BetterDownloadManager(); + + #endregion Singleton + + #region Constructor + + private BetterDownloadManager() + { + _downloadProcessor = new DownloadProcessor(); + + _worldQueue = new WorldDownloadQueue(this); // Only one world at a time + _avatarQueue = new AvatarDownloadQueue(this); // Up to 3 avatars at once + _propQueue = new PropDownloadQueue(this); // Up to 2 props at once + + // Set to 100MBs by default + MaxDownloadBandwidth = 100 * 1024 * 1024; + + CVRGameEventSystem.Instance.OnConnected.AddListener(_ => + { + if (!Instances.IsReconnecting) OnInitialConnectionToInstance(); + }); + } + + #endregion Constructor + + #region Settings + + /// Log debug messages + public bool IsDebugEnabled { get; set; } = true; + + /// Prioritize friends first in download queue + public bool PrioritizeFriends { get; set; } = true; + + /// Prioritize content closest to player first in download queue + public bool PrioritizeDistance { get; set; } = true; + public float PriorityDownloadDistance { get; set; } = 25f; + + public int MaxDownloadBandwidth + { + get => _downloadProcessor.MaxDownloadBandwidth; + set => _downloadProcessor.MaxDownloadBandwidth = value; + } + + #endregion Settings + + private readonly DownloadProcessor _downloadProcessor; + + private readonly AvatarDownloadQueue _avatarQueue; + private readonly PropDownloadQueue _propQueue; + private readonly WorldDownloadQueue _worldQueue; + + #region Game Events + + private void OnInitialConnectionToInstance() + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg("Initial connection established."); + // await few seconds before chewing through the download queue, to allow for download priorities to be set + // once we have received most of the data from the server + } + + #endregion Game Events + + #region Public Queue Methods + + public void QueueAvatarDownload( + in DownloadInfo info, + string playerId, + CVRLoadingAvatarController loadingController) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing Avatar Download:\n{info.GetLogString()}\n" + + $"PlayerId: {playerId}\n" + + $"LoadingController: {loadingController}"); + } + + _avatarQueue.QueueDownload(in info, playerId); + } + + /// + /// Queues a prop download. + /// + /// The download info. + /// The instance ID for the prop. + /// The user who spawned the prop. + public void QueuePropDownload( + in DownloadInfo info, + string instanceId, + string spawnerId) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing Prop Download:\n{info.GetLogString()}\n" + + $"InstanceId: {instanceId}\n" + + $"SpawnerId: {spawnerId}"); + } + + _propQueue.QueueDownload(in info, instanceId, spawnerId); + } + + /// + /// Queues a world download. + /// + /// Download info. + /// Whether to load into this world once downloaded. + /// Whether the home world is requested. + public void QueueWorldDownload( + in DownloadInfo info, + bool joinOnComplete, + bool isHomeRequested) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing World Download:\n{info.GetLogString()}\n" + + $"JoinOnComplete: {joinOnComplete}\n" + + $"IsHomeRequested: {isHomeRequested}"); + } + + _worldQueue.QueueDownload(in info, joinOnComplete, isHomeRequested); + } + + #endregion Public Queue Methods + + #region Internal Methods + + internal Task ProcessDownload(DownloadInfo info, Action progressCallback = null) + { + return _downloadProcessor.ProcessDownload(info); + } + + #endregion Internal Methods + + #region Private Helper Methods + + internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) + { + CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); + if (player == null) + { + // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); + playerEntity = null; + return false; + } + playerEntity = player; + return true; + } + + internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) + { + CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); + if (prop == null) + { + // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); + propData = null; + return false; + } + propData = prop; + return true; + } + + internal static bool IsPlayerLocal(string playerId) + { + return playerId == MetaPort.Instance.ownerId; + } + + internal static bool IsPlayerFriend(string playerId) + { + return Friends.FriendsWith(playerId); + } + + internal bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) + { + if (player.PuppetMaster == null) return false; + return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; + } + + internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) + { + Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); + return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; + } + + #endregion Private Helper Methods +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadInfo.cs b/BetterContentLoading/BetterContentLoading/DownloadInfo.cs new file mode 100644 index 0000000..038f6bf --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadInfo.cs @@ -0,0 +1,63 @@ +using System.Security.Cryptography; +using System.Text; +using ABI_RC.Core.Networking.API.Responses; + +namespace NAK.BetterContentLoading; + +public readonly struct DownloadInfo +{ + public readonly string AssetId; + public readonly string AssetUrl; + public readonly string FileId; + public readonly long FileSize; + public readonly string FileKey; + public readonly string FileHash; + public readonly int CompatibilityVersion; + public readonly int EncryptionAlgorithm; + public readonly UgcTagsData TagsData; + + public readonly string DownloadId; + + public DownloadInfo( + string assetId, string assetUrl, string fileId, + long fileSize, string fileKey, string fileHash, + int compatibilityVersion, int encryptionAlgorithm, + UgcTagsData tagsData) + { + AssetId = assetId + "meow"; + AssetUrl = assetUrl; + FileId = fileId; + FileSize = fileSize; + FileKey = fileKey; + FileHash = fileHash; + CompatibilityVersion = compatibilityVersion; + EncryptionAlgorithm = encryptionAlgorithm; + TagsData = tagsData; + + using SHA256 sha = SHA256.Create(); + StringBuilder sb = new(); + sb.Append(assetId) + .Append('|').Append(assetUrl) + .Append('|').Append(fileId) + .Append('|').Append(fileSize) + .Append('|').Append(fileKey) + .Append('|').Append(fileHash) + .Append('|').Append(compatibilityVersion) + .Append('|').Append(encryptionAlgorithm); + + var bytes = Encoding.UTF8.GetBytes(sb.ToString()); + var hash = sha.ComputeHash(bytes); + DownloadId = Convert.ToBase64String(hash); + } + + public string GetLogString() => + $"AssetId: {AssetId}\n" + + $"DownloadId: {DownloadId}\n" + + $"AssetUrl: {AssetUrl}\n" + + $"FileId: {FileId}\n" + + $"FileSize: {FileSize}\n" + + $"FileKey: {FileKey}\n" + + $"FileHash: {FileHash}\n" + + $"CompatibilityVersion: {CompatibilityVersion}\n" + + $"EncryptionAlgorithm: {EncryptionAlgorithm}"; +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs b/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs new file mode 100644 index 0000000..e77acdc --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs @@ -0,0 +1,152 @@ +using System.Net.Http; +using ABI_RC.Core; +using ABI_RC.Core.IO.AssetManagement; + +namespace NAK.BetterContentLoading; + +public class DownloadProcessor +{ + private readonly HttpClient _client = new(); + private readonly SemaphoreSlim _bandwidthSemaphore = new(1); + private long _bytesReadLastSecond; + + private int _maxDownloadBandwidth = 10 * 1024 * 1024; // Default 10MB/s + private const int MinBufferSize = 16384; // 16KB min buffer + private const long ThrottleThreshold = 25 * 1024 * 1024; // 25MB threshold for throttling + private const long KnownSizeDifference = 1000; // API reported size is 1000 bytes larger than actual content + + public int MaxDownloadBandwidth + { + get => _maxDownloadBandwidth; + set => _maxDownloadBandwidth = Math.Max(1024 * 1024, value); + } + + private int ActiveDownloads { get; set; } + private int CurrentBandwidthPerDownload => MaxDownloadBandwidth / Math.Max(1, ActiveDownloads); + + public int GetProgress(string downloadId) => _downloadProgress.GetValueOrDefault(downloadId, 0); + private readonly Dictionary _downloadProgress = new(); + + public async Task ProcessDownload(DownloadInfo downloadInfo) + { + try + { + if (await CacheManager.Instance.IsCachedFileUpToDate( + downloadInfo.AssetId, + downloadInfo.FileId, + downloadInfo.FileHash)) + return true; + + var filePath = CacheManager.Instance.GetCachePath(downloadInfo.AssetId, downloadInfo.FileId); + if (!CVRTools.HasEnoughDiskSpace(filePath, downloadInfo.FileSize)) + { + BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {downloadInfo.AssetId}"); + return false; + } + + CacheManager.Instance.EnsureCacheDirectoryExists(downloadInfo.AssetId); + + bool success = false; + Exception lastException = null; + + for (int attempt = 0; attempt <= 1; attempt++) + { + try + { + if (attempt > 0) + await Task.Delay(1000); + + success = await DownloadWithBandwidthLimit(downloadInfo, filePath); + if (success) break; + } + catch (Exception ex) + { + lastException = ex; + } + } + + if (!success && lastException != null) + BetterContentLoadingMod.Logger.Error($"Failed to download {downloadInfo.AssetId}: {lastException}"); + + _downloadProgress.Remove(downloadInfo.DownloadId); + return success; + } + catch (Exception ex) + { + BetterContentLoadingMod.Logger.Error($"Error processing download for {downloadInfo.AssetId}: {ex}"); + _downloadProgress.Remove(downloadInfo.DownloadId); + return false; + } + } + + private async Task DownloadWithBandwidthLimit(DownloadInfo downloadInfo, string filePath) + { + await _bandwidthSemaphore.WaitAsync(); + try { ActiveDownloads++; } + finally { _bandwidthSemaphore.Release(); } + + string tempFilePath = filePath + ".download"; + try + { + using var response = await _client.GetAsync(downloadInfo.AssetUrl, HttpCompletionOption.ResponseHeadersRead); + if (!response.IsSuccessStatusCode) + return false; + + var expectedContentSize = downloadInfo.FileSize - KnownSizeDifference; + using var stream = await response.Content.ReadAsStreamAsync(); + using var fileStream = File.Open(tempFilePath, FileMode.Create); + + var isEligibleForThrottle = downloadInfo.FileSize >= ThrottleThreshold; + var totalBytesRead = 0L; + byte[] buffer = null; + + while (true) + { + var lengthToRead = isEligibleForThrottle ? MinBufferSize : CurrentBandwidthPerDownload; + if (buffer == null || lengthToRead != buffer.Length) + buffer = new byte[lengthToRead]; + + var bytesRead = await stream.ReadAsync(buffer, 0, lengthToRead); + if (bytesRead == 0) break; + + if (isEligibleForThrottle) + Interlocked.Add(ref _bytesReadLastSecond, bytesRead); + + fileStream.Write(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + + var progress = (int)(((float)totalBytesRead / expectedContentSize) * 100f); + _downloadProgress[downloadInfo.DownloadId] = Math.Clamp(progress, 0, 100); + } + + fileStream.Flush(); + + var fileInfo = new FileInfo(tempFilePath); + if (fileInfo.Length != expectedContentSize) + return false; + + if (File.Exists(filePath)) + File.Delete(filePath); + File.Move(tempFilePath, filePath); + + CacheManager.Instance.QueuePrune(); + return true; + } + finally + { + await _bandwidthSemaphore.WaitAsync(); + try { ActiveDownloads--; } + finally { _bandwidthSemaphore.Release(); } + + try + { + if (File.Exists(tempFilePath)) + File.Delete(tempFilePath); + } + catch + { + // Ignore cleanup errors + } + } + } +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs new file mode 100644 index 0000000..60060c5 --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs @@ -0,0 +1,83 @@ +namespace NAK.BetterContentLoading.Queue; + +public class AvatarDownloadQueue : ContentDownloadQueueBase +{ + public AvatarDownloadQueue(BetterDownloadManager manager) : base(manager, 3) { } + + public void QueueDownload(in DownloadInfo info, string playerId, Action onComplete = null) + { + float priority = CalculateAvatarPriority(in info, playerId); + QueueDownload(in info, playerId, priority, onComplete); + } + + protected override async Task ProcessDownload(DownloadInfo info) + { + await base.ProcessDownload(info); + } + + protected override void OnDownloadProgress(string downloadId, float progress) + { + if (DownloadOwners.TryGetValue(downloadId, out var owners)) + { + foreach (var playerId in owners) + { + if (BetterDownloadManager.IsPlayerLocal(playerId)) + { + // Update loading progress on local player + BetterContentLoadingMod.Logger.Msg($"Progress for local player ({playerId}): {progress:P}"); + continue; + } + if (BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) + { + // Update loading progress on avatar controller if needed + BetterContentLoadingMod.Logger.Msg($"Progress for {player.Username} ({playerId}): {progress:P}"); + } + } + } + } + + protected override float RecalculatePriority(string downloadId) + { + if (!DownloadOwners.TryGetValue(downloadId, out var owners)) + return float.MaxValue; + + float lowestPriority = float.MaxValue; + DownloadInfo? downloadInfo = null; + + // Find the queue item to get the DownloadInfo + var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); + if (queueItem.Info.AssetId != null) + downloadInfo = queueItem.Info; + + if (downloadInfo == null) + return lowestPriority; + + // Calculate priority for each owner and use the lowest (highest priority) value + foreach (var playerId in owners) + { + var priority = CalculateAvatarPriority(downloadInfo.Value, playerId); + lowestPriority = Math.Min(lowestPriority, priority); + } + + return lowestPriority; + } + + private float CalculateAvatarPriority(in DownloadInfo info, string playerId) + { + float priority = info.FileSize; + + if (BetterDownloadManager.IsPlayerLocal(playerId)) + return 0f; + + if (!BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) + return priority; + + if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(playerId)) + priority *= 0.5f; + + if (Manager.PrioritizeDistance && Manager.IsPlayerWithinPriorityDistance(player)) + priority *= 0.75f; + + return priority; + } +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs new file mode 100644 index 0000000..66e366b --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs @@ -0,0 +1,177 @@ +namespace NAK.BetterContentLoading.Queue; + +public abstract class ContentDownloadQueueBase +{ + protected readonly struct QueueItem + { + public readonly DownloadInfo Info; + public readonly float Priority; + public readonly Action OnComplete; // Callback with (downloadId, ownerId) + + public QueueItem(DownloadInfo info, float priority, Action onComplete) + { + Info = info; + Priority = priority; + OnComplete = onComplete; + } + } + + protected readonly List Queue = new(); + private readonly HashSet ActiveDownloads = new(); // By DownloadId + protected readonly Dictionary> DownloadOwners = new(); // DownloadId -> Set of OwnerIds + private readonly SemaphoreSlim DownloadSemaphore; + protected readonly BetterDownloadManager Manager; + + protected ContentDownloadQueueBase(BetterDownloadManager manager, int maxParallelDownloads) + { + Manager = manager; + DownloadSemaphore = new SemaphoreSlim(maxParallelDownloads); + } + + protected void QueueDownload(in DownloadInfo info, string ownerId, float priority, Action onComplete) + { + if (Manager.IsDebugEnabled) + BetterContentLoadingMod.Logger.Msg($"Attempting to queue download for {info.AssetId} (DownloadId: {info.DownloadId})"); + + // Add to owners tracking + if (!DownloadOwners.TryGetValue(info.DownloadId, out var owners)) + { + owners = new HashSet(); + DownloadOwners[info.DownloadId] = owners; + } + owners.Add(ownerId); + + // If already downloading, just add the owner and callback + if (ActiveDownloads.Contains(info.DownloadId)) + { + if (Manager.IsDebugEnabled) + BetterContentLoadingMod.Logger.Msg($"Already downloading {info.DownloadId}, added owner {ownerId}"); + return; + } + + DownloadInfo downloadInfo = info; + var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadInfo.DownloadId); + if (existingIndex >= 0) + { + // Update priority if needed based on new owner + var newPriority = RecalculatePriority(info.DownloadId); + Queue[existingIndex] = new QueueItem(info, newPriority, onComplete); + SortQueue(); + return; + } + + Queue.Add(new QueueItem(info, priority, onComplete)); + SortQueue(); + TryStartNextDownload(); + } + + public void RemoveOwner(string downloadId, string ownerId) + { + if (!DownloadOwners.TryGetValue(downloadId, out var owners)) + return; + + owners.Remove(ownerId); + + if (owners.Count == 0) + { + // No more owners, cancel the download + DownloadOwners.Remove(downloadId); + CancelDownload(downloadId); + } + else if (!ActiveDownloads.Contains(downloadId)) + { + // Still has owners and is queued, recalculate priority + var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadId); + if (existingIndex >= 0) + { + var item = Queue[existingIndex]; + var newPriority = RecalculatePriority(downloadId); + Queue[existingIndex] = new QueueItem(item.Info, newPriority, item.OnComplete); + SortQueue(); + } + } + } + + protected virtual async void TryStartNextDownload() + { + try + { + if (Queue.Count == 0) return; + + await DownloadSemaphore.WaitAsync(); + + if (Queue.Count > 0) + { + var item = Queue[0]; + Queue.RemoveAt(0); + + // Double check we still have owners before starting download + if (!DownloadOwners.TryGetValue(item.Info.DownloadId, out var owners) || owners.Count == 0) + { + DownloadSemaphore.Release(); + TryStartNextDownload(); + return; + } + + ActiveDownloads.Add(item.Info.DownloadId); + + try + { + await ProcessDownload(item.Info); + BetterContentLoadingMod.Logger.Msg($"Download completed for {item.Info.DownloadId}"); + + // Notify all owners of completion + if (DownloadOwners.TryGetValue(item.Info.DownloadId, out owners)) + { + foreach (var owner in owners) + { + item.OnComplete?.Invoke(item.Info.DownloadId, owner); + } + } + } + catch (Exception ex) + { + if (Manager.IsDebugEnabled) + BetterContentLoadingMod.Logger.Error($"Download failed for {item.Info.DownloadId}: {ex}"); + } + finally + { + ActiveDownloads.Remove(item.Info.DownloadId); + DownloadSemaphore.Release(); + TryStartNextDownload(); + } + } + else + { + DownloadSemaphore.Release(); + } + } + catch (Exception e) + { + BetterContentLoadingMod.Logger.Error($"Error in TryStartNextDownload: {e}"); + } + } + + protected virtual async Task ProcessDownload(DownloadInfo info) + { + bool success = await Manager.ProcessDownload(info); + + if (!success) + throw new Exception($"Failed to download {info.AssetId}"); + } + + protected abstract void OnDownloadProgress(string downloadId, float progress); + + protected abstract float RecalculatePriority(string downloadId); + + protected virtual void SortQueue() + { + Queue.Sort((a, b) => a.Priority.CompareTo(b.Priority)); + } + + protected virtual void CancelDownload(string downloadId) + { + Queue.RemoveAll(x => x.Info.DownloadId == downloadId); + if (ActiveDownloads.Remove(downloadId)) DownloadSemaphore.Release(); + } +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs new file mode 100644 index 0000000..f100e0b --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs @@ -0,0 +1,81 @@ +using ABI_RC.Core.Util; + +namespace NAK.BetterContentLoading.Queue; + +public class PropDownloadQueue : ContentDownloadQueueBase +{ + private readonly Dictionary _ownerToSpawner = new(); // InstanceId -> SpawnerId + + public PropDownloadQueue(BetterDownloadManager manager) : base(manager, 2) { } + + public void QueueDownload(in DownloadInfo info, string instanceId, string spawnerId, Action onComplete = null) + { + _ownerToSpawner[instanceId] = spawnerId; + float priority = CalculatePropPriority(in info, instanceId, spawnerId); + QueueDownload(in info, instanceId, priority, onComplete); + } + + protected override async Task ProcessDownload(DownloadInfo info) + { + await base.ProcessDownload(info); + } + + protected override void OnDownloadProgress(string downloadId, float progress) + { + if (DownloadOwners.TryGetValue(downloadId, out var owners)) + { + foreach (var instanceId in owners) + { + if (BetterDownloadManager.TryGetPropData(instanceId, out CVRSyncHelper.PropData prop)) + { + BetterContentLoadingMod.Logger.Msg($"Progress for {prop.InstanceId}: {progress:P}"); + } + } + } + } + + protected override float RecalculatePriority(string downloadId) + { + if (!DownloadOwners.TryGetValue(downloadId, out var owners)) + return float.MaxValue; + + float lowestPriority = float.MaxValue; + DownloadInfo? downloadInfo = null; + + // Find the queue item to get the DownloadInfo + var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); + if (queueItem.Info.AssetId != null) + downloadInfo = queueItem.Info; + + if (downloadInfo == null) + return lowestPriority; + + // Calculate priority for each owner and use the lowest (highest priority) value + foreach (var instanceId in owners) + { + if (_ownerToSpawner.TryGetValue(instanceId, out var spawnerId)) + { + var priority = CalculatePropPriority(downloadInfo.Value, instanceId, spawnerId); + lowestPriority = Math.Min(lowestPriority, priority); + } + } + + return lowestPriority; + } + + private float CalculatePropPriority(in DownloadInfo info, string instanceId, string spawnerId) + { + float priority = info.FileSize; + + if (!BetterDownloadManager.TryGetPropData(instanceId, out var prop)) + return priority; + + if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(spawnerId)) + priority *= 0.5f; + + if (Manager.PrioritizeDistance && Manager.IsPropWithinPriorityDistance(prop)) + priority *= 0.75f; + + return priority; + } +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs new file mode 100644 index 0000000..1cea689 --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs @@ -0,0 +1,66 @@ +namespace NAK.BetterContentLoading.Queue; + +public class WorldDownloadQueue : ContentDownloadQueueBase +{ + private readonly Queue<(DownloadInfo Info, bool JoinOnComplete, bool IsHomeRequest)> _backgroundQueue = new(); + private bool _isProcessingPriorityDownload; + + public WorldDownloadQueue(BetterDownloadManager manager) : base(manager, 1) { } + + public void QueueDownload(in DownloadInfo info, bool joinOnComplete, bool isHomeRequest, Action onComplete = null) + { + if (joinOnComplete || isHomeRequest) + { + // Priority download - clear queue and download immediately + Queue.Clear(); + _isProcessingPriorityDownload = true; + QueueDownload(in info, info.DownloadId, 0f, onComplete); + } + else + { + // Background download - add to background queue + _backgroundQueue.Enqueue((info, false, false)); + if (!_isProcessingPriorityDownload) + ProcessBackgroundQueue(); + } + } + + protected override async Task ProcessDownload(DownloadInfo info) + { + await base.ProcessDownload(info); + + if (_isProcessingPriorityDownload) + { + _isProcessingPriorityDownload = false; + ProcessBackgroundQueue(); + } + } + + protected override void OnDownloadProgress(string downloadId, float progress) + { + BetterContentLoadingMod.Logger.Msg($"World download progress: {progress:P}"); + } + + protected override float RecalculatePriority(string downloadId) + { + // For worlds, priority is based on whether it's a priority download and file size + var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); + return queueItem.Priority; + } + + private void ProcessBackgroundQueue() + { + while (_backgroundQueue.Count > 0) + { + (DownloadInfo info, var join, var home) = _backgroundQueue.Dequeue(); + QueueDownload(in info, info.DownloadId, info.FileSize, null); + } + } + + protected override void CancelDownload(string downloadId) + { + base.CancelDownload(downloadId); + _backgroundQueue.Clear(); + _isProcessingPriorityDownload = false; + } +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadState.cs b/BetterContentLoading/BetterContentLoading/DownloadState.cs new file mode 100644 index 0000000..785396b --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/DownloadState.cs @@ -0,0 +1,26 @@ +namespace NAK.BetterContentLoading; + +public readonly struct DownloadState +{ + public readonly string DownloadId; + public readonly long BytesRead; + public readonly long TotalBytes; + public readonly int ProgressPercent; + public readonly float BytesPerSecond; + + public DownloadState(string downloadId, long bytesRead, long totalBytes, float bytesPerSecond) + { + DownloadId = downloadId; + BytesRead = bytesRead; + TotalBytes = totalBytes; + BytesPerSecond = bytesPerSecond; + ProgressPercent = (int)(((float)bytesRead / totalBytes) * 100f); + } +} + +public interface IDownloadMonitor +{ + void OnDownloadProgress(DownloadState state); + void OnDownloadStarted(string downloadId); + void OnDownloadCompleted(string downloadId, bool success); +} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs b/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs new file mode 100644 index 0000000..280aeb2 --- /dev/null +++ b/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs @@ -0,0 +1,109 @@ +using JetBrains.Annotations; +using UnityEngine; + +namespace NAK.BetterContentLoading.Util; + +[PublicAPI] +public static class ThreadingHelper +{ + private static readonly SynchronizationContext _mainThreadContext; + + static ThreadingHelper() + { + _mainThreadContext = SynchronizationContext.Current; + } + + public static bool IsMainThread => SynchronizationContext.Current == _mainThreadContext; + + /// + /// Runs an action on the main thread. Optionally waits for its completion. + /// + public static void RunOnMainThread(Action action, bool waitForCompletion = true, Action onError = null) + { + if (SynchronizationContext.Current == _mainThreadContext) + { + // Already on the main thread + TryExecute(action, onError); + } + else + { + if (waitForCompletion) + { + ManualResetEvent done = new(false); + _mainThreadContext.Post(_ => + { + TryExecute(action, onError); + done.Set(); + }, null); + done.WaitOne(50000); // Block until action is completed + } + else + { + // Fire and forget (don't wait for the action to complete) + _mainThreadContext.Post(_ => TryExecute(action, onError), null); + } + } + } + + /// + /// Runs an action asynchronously on the main thread and returns a Task. + /// + public static Task RunOnMainThreadAsync(Action action, Action onError = null) + { + if (SynchronizationContext.Current == _mainThreadContext) + { + // Already on the main thread + TryExecute(action, onError); + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(); + _mainThreadContext.Post(_ => + { + try + { + TryExecute(action, onError); + tcs.SetResult(true); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }, null); + return tcs.Task; + } + + /// + /// Runs a task on a background thread with cancellation support. + /// + public static async Task RunOffMainThreadAsync(Action action, CancellationToken cancellationToken, Action onError = null) + { + try + { + await Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + TryExecute(action, onError); + }, cancellationToken); + } + catch (OperationCanceledException) + { + Debug.LogWarning("Task was canceled."); + } + } + + /// + /// Helper method for error handling. + /// + private static void TryExecute(Action action, Action onError) + { + try + { + action(); + } + catch (Exception ex) + { + onError?.Invoke(ex); + } + } +} diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager/DownloadManager.Core.cs new file mode 100644 index 0000000..ce484cd --- /dev/null +++ b/BetterContentLoading/DownloadManager/DownloadManager.Core.cs @@ -0,0 +1,237 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Networking.API.Responses; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Savior; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager +{ + #region Singleton + private static DownloadManager _instance; + public static DownloadManager Instance => _instance ??= new DownloadManager(); + #endregion + + private DownloadManager() + { + + } + + #region Settings + public bool IsDebugEnabled { get; set; } = true; + public bool PrioritizeFriends { get; set; } = true; + public bool PrioritizeDistance { get; set; } = true; + public float PriorityDownloadDistance { get; set; } = 25f; + public int MaxConcurrentDownloads { get; set; } = 3; + public int MaxDownloadBandwidth { get; set; } = 100 * 1024 * 1024; // 100MB default + private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling + + public long MaxAvatarDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default + public long MaxPropDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default + + #endregion Settings + + #region State + + // priority -> downloadtask + private readonly SortedList _downloadQueue = new(); + + // downloadId -> downloadtask + private readonly Dictionary _cachedDownloads = new(); + + #endregion State + + #region Public Queue Methods + + public void QueueAvatarDownload( + in DownloadInfo info, + string playerId, + CVRLoadingAvatarController loadingController) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing Avatar Download:\n{info.GetLogString()}\n" + + $"PlayerId: {playerId}\n" + + $"LoadingController: {loadingController}"); + } + + if (!ShouldQueueAvatarDownload(info, playerId)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download not eligible: {info.DownloadId}"); + return; + } + + if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download already queued: {info.DownloadId}"); + cachedDownload.AddInstantiationTarget(playerId); + cachedDownload.BasePriority = CalculatePriority(cachedDownload); + return; + } + + DownloadTask task = new() + { + Info = info, + Type = DownloadTaskType.Avatar + }; + task.AddInstantiationTarget(playerId); + task.BasePriority = CalculatePriority(task); + } + + private bool ShouldQueueAvatarDownload( + in DownloadInfo info, + string playerId) + { + // Check if content is incompatible or banned + if (info.TagsData.Incompatible || info.TagsData.AdminBanned) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is incompatible or banned"); + return false; + } + + // Check if player is blocked + if (MetaPort.Instance.blockedUserIds.Contains(playerId)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Player is blocked: {playerId}"); + return false; + } + + // Check if mature content is disabled + UgcTagsData tags = info.TagsData; + if (!MetaPort.Instance.matureContentAllowed && (tags.Gore || tags.Nudity)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Mature content is disabled"); + return false; + } + + // Check file size + if (info.FileSize > MaxAvatarDownloadSize) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download too large: {info.FileSize} > {MaxAvatarDownloadSize}"); + return false; + } + + // Get visibility status for the avatar + // ForceHidden means player avatar or avatar itself is forced off. + // ForceShown will bypass all checks and return true. + MetaPort.Instance.SelfModerationManager.GetAvatarVisibility(playerId, info.AssetId, + out bool wasForceHidden, out bool wasForceShown); + + if (!wasForceHidden) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is not visible either because player avatar or avatar itself is forced off"); + return false; + } + + if (!wasForceShown) + { + // Check content filter settings if not force shown + CVRSettings settings = MetaPort.Instance.settings; + bool isLocalPlayer = playerId == MetaPort.Instance.ownerId; + bool isFriend = Friends.FriendsWith(playerId); + bool CheckFilterSettings(string settingName) + { + int settingVal = settings.GetSettingInt(settingName); + switch (settingVal) + { + // Only Self + case 0 when !isLocalPlayer: + // Only Friends + case 1 when !isFriend: + return false; + } + return true; + } + + if (!CheckFilterSettings("ContentFilterVisibility")) return false; + if (!CheckFilterSettings("ContentFilterNudity")) return false; + if (!CheckFilterSettings("ContentFilterGore")) return false; + if (!CheckFilterSettings("ContentFilterSuggestive")) return false; + if (!CheckFilterSettings("ContentFilterFlashingColors")) return false; + if (!CheckFilterSettings("ContentFilterFlashingLights")) return false; + if (!CheckFilterSettings("ContentFilterScreenEffects")) return false; + if (!CheckFilterSettings("ContentFilterExtremelyBright")) return false; + if (!CheckFilterSettings("ContentFilterViolence")) return false; + if (!CheckFilterSettings("ContentFilterJumpscare")) return false; + if (!CheckFilterSettings("ContentFilterExcessivelyHuge")) return false; + if (!CheckFilterSettings("ContentFilterExcessivelySmall")) return false; + } + + // All eligibility checks passed + return true; + } + + /// + /// Queues a prop download. + /// + /// The download info. + /// The instance ID for the prop. + /// The user who spawned the prop. + public void QueuePropDownload( + in DownloadInfo info, + string instanceId, + string spawnerId) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing Prop Download:\n{info.GetLogString()}\n" + + $"InstanceId: {instanceId}\n" + + $"SpawnerId: {spawnerId}"); + } + + if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Prop Download already queued: {info.DownloadId}"); + cachedDownload.AddInstantiationTarget(instanceId); + cachedDownload.BasePriority = CalculatePriority(cachedDownload); + return; + } + + DownloadTask task = new() + { + Info = info, + Type = DownloadTaskType.Prop + }; + task.AddInstantiationTarget(instanceId); + task.BasePriority = CalculatePriority(task); + } + + /// + /// Queues a world download. + /// + /// Download info. + /// Whether to load into this world once downloaded. + /// Whether the home world is requested. + public void QueueWorldDownload( + in DownloadInfo info, + bool joinOnComplete, + bool isHomeRequested) + { + if (IsDebugEnabled) + { + BetterContentLoadingMod.Logger.Msg( + $"Queuing World Download:\n{info.GetLogString()}\n" + + $"JoinOnComplete: {joinOnComplete}\n" + + $"IsHomeRequested: {isHomeRequested}"); + } + + if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) + { + if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"World Download already queued: {info.DownloadId}"); + cachedDownload.BasePriority = CalculatePriority(cachedDownload); + return; + } + + DownloadTask task = new() + { + Info = info, + Type = DownloadTaskType.World + }; + task.BasePriority = CalculatePriority(task); + } + + #endregion Public Queue Methods + +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs new file mode 100644 index 0000000..3664ed4 --- /dev/null +++ b/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs @@ -0,0 +1,58 @@ +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using UnityEngine; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager +{ + internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) + { + CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); + if (player == null) + { + // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); + playerEntity = null; + return false; + } + playerEntity = player; + return true; + } + + internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) + { + CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); + if (prop == null) + { + // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); + propData = null; + return false; + } + propData = prop; + return true; + } + + private static bool IsPlayerLocal(string playerId) + { + return playerId == MetaPort.Instance.ownerId; + } + + private static bool IsPlayerFriend(string playerId) + { + return Friends.FriendsWith(playerId); + } + + private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) + { + if (player.PuppetMaster == null) return false; + return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; + } + + internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) + { + Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); + return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs new file mode 100644 index 0000000..ae28cbc --- /dev/null +++ b/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs @@ -0,0 +1,38 @@ +using ABI_RC.Core.Player; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager +{ + private float CalculatePriority(DownloadTask task) + { + return task.Type switch + { + DownloadTaskType.Avatar => CalculateAvatarPriority(task), + // DownloadTaskType.Prop => CalculatePropPriority(task2), + // DownloadTaskType.World => CalculateWorldPriority(task2), + _ => task.Info.FileSize + }; + } + + private float CalculateAvatarPriority(DownloadTask task) + { + float priority = task.Info.FileSize; + + foreach (string target in task.InstantiationTargets) + { + if (IsPlayerLocal(target)) return 0f; + + if (!TryGetPlayerEntity(target, out CVRPlayerEntity player)) + return priority; + + if (PrioritizeFriends && IsPlayerFriend(target)) + priority *= 0.5f; + + if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) + priority *= 0.75f; + } + + return priority; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Main.cs b/BetterContentLoading/DownloadManager/DownloadTask.Main.cs new file mode 100644 index 0000000..5397cf7 --- /dev/null +++ b/BetterContentLoading/DownloadManager/DownloadTask.Main.cs @@ -0,0 +1,33 @@ +namespace NAK.BetterContentLoading; + +public partial class DownloadTask +{ + public DownloadInfo Info { get; set; } + public DownloadTaskStatus Status { get; set; } + public DownloadTaskType Type { get; set; } + + public float BasePriority { get; set; } + public float CurrentPriority => BasePriority * (1 + Progress / 100f); + + public long BytesRead { get; set; } + public int Progress { get; set; } + + /// The avatar/prop instances that wish to utilize this bundle. + public List InstantiationTargets { get; } = new(); + + public void AddInstantiationTarget(string target) + { + if (InstantiationTargets.Contains(target)) + return; + + InstantiationTargets.Add(target); + } + + public void RemoveInstantiationTarget(string target) + { + if (!InstantiationTargets.Contains(target)) + return; + + InstantiationTargets.Remove(target); + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs b/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs new file mode 100644 index 0000000..f697a5e --- /dev/null +++ b/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs @@ -0,0 +1,6 @@ +namespace NAK.BetterContentLoading; + +public partial class DownloadTask +{ + +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs b/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs new file mode 100644 index 0000000..df844d5 --- /dev/null +++ b/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs @@ -0,0 +1,43 @@ +namespace NAK.BetterContentLoading; + +public class ConcurrentPriorityQueue where TPriority : IComparable +{ + private readonly object _lock = new(); + private readonly SortedDictionary> _queues = new(); + + public void Enqueue(TElement item, TPriority priority) + { + lock (_lock) + { + if (!_queues.TryGetValue(priority, out var queue)) + { + queue = new Queue(); + _queues[priority] = queue; + } + queue.Enqueue(item); + } + } + + public bool TryDequeue(out TElement item, out TPriority priority) + { + lock (_lock) + { + if (_queues.Count == 0) + { + item = default; + priority = default; + return false; + } + + var firstQueue = _queues.First(); + priority = firstQueue.Key; + var queue = firstQueue.Value; + item = queue.Dequeue(); + + if (queue.Count == 0) + _queues.Remove(priority); + + return true; + } + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs new file mode 100644 index 0000000..54d5792 --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs @@ -0,0 +1,24 @@ +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + private void StartBandwidthMonitor() + { + Task.Run(async () => + { + while (true) + { + await Task.Delay(1000); + Interlocked.Exchange(ref _bytesReadLastSecond, 0); + Interlocked.Exchange(ref _completedDownloads, 0); + } + }); + } + + private int ComputeUsableBandwidthPerDownload() + { + var activeCount = _activeDownloads.Count; + if (activeCount == 0) return MaxDownloadBandwidth; + return MaxDownloadBandwidth / activeCount; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs new file mode 100644 index 0000000..63fd4c2 --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs @@ -0,0 +1,126 @@ +using System.Collections.Concurrent; +using ABI_RC.Core; +using ABI_RC.Core.IO; +using ABI_RC.Core.IO.AssetManagement; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + #region Singleton + private static DownloadManager2 _instance; + public static DownloadManager2 Instance => _instance ??= new DownloadManager2(); + #endregion + + #region Settings + public bool IsDebugEnabled { get; set; } = true; + public bool PrioritizeFriends { get; set; } = true; + public bool PrioritizeDistance { get; set; } = true; + public float PriorityDownloadDistance { get; set; } = 25f; + public int MaxConcurrentDownloads { get; set; } = 5; + public int MaxDownloadBandwidth { get; set; } // 100MB default + private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling + #endregion + + #region State + private readonly ConcurrentDictionary _activeDownloads; + private readonly ConcurrentPriorityQueue _queuedDownloads; + private readonly ConcurrentDictionary _completedDownloads; + private readonly object _downloadLock = new(); + private long _bytesReadLastSecond; + #endregion + + private DownloadManager2() + { + _activeDownloads = new ConcurrentDictionary(); + _queuedDownloads = new ConcurrentPriorityQueue(); + MaxDownloadBandwidth = 100 * 1024 * 1024; + StartBandwidthMonitor(); + } + + public async Task QueueAvatarDownload(DownloadInfo info, string playerId) + { + if (await ValidateAndCheckCache(info)) + return true; + + DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Avatar); + task2.AddTarget(playerId); + QueueDownload(task2); + return false; + } + + public async Task QueuePropDownload(DownloadInfo info, string instanceId, string spawnerId) + { + if (await ValidateAndCheckCache(info)) + return true; + + DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Prop); + task2.AddTarget(instanceId, spawnerId); + QueueDownload(task2); + return false; + } + + public async Task QueueWorldDownload(DownloadInfo info, bool loadOnComplete) + { + if (await ValidateAndCheckCache(info)) + return true; + + DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.World, loadOnComplete); + QueueDownload(task2); + return false; + } + + private async Task ValidateAndCheckCache(DownloadInfo info) + { + // Check if already cached and up to date + if (await CacheManager.Instance.IsCachedFileUpToDate( + info.AssetId, + info.FileId, + info.FileHash)) + { + return true; + } + + // Validate disk space + var filePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); + if (!CVRTools.HasEnoughDiskSpace(filePath, info.FileSize)) + { + BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {info.AssetId}"); + return false; + } + + // Ensure cache directory exists + CacheManager.Instance.EnsureCacheDirectoryExists(info.AssetId); + return false; + } + + private DownloadTask2 GetOrCreateDownloadTask(DownloadInfo info, DownloadTaskType type, bool loadOnComplete = false) + { + // Check if task already exists in active downloads + if (_activeDownloads.TryGetValue(info.DownloadId, out var activeTask)) + return activeTask; + + // Check if task exists in queued downloads + var queuedTask = _queuedDownloads.TryFind(t => t.Info.DownloadId == info.DownloadId); + if (queuedTask != null) + return queuedTask; + + // Create new task + var cachePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); + return new DownloadTask2(info, cachePath, type, loadOnComplete); + } + + public bool TryFindTask(string downloadId, out DownloadTask2 task2) + { + return _activeDownloads.TryGetValue(downloadId, out task2) || + _completedDownloads.TryGetValue(downloadId, out task2) || + TryFindQueuedTask(downloadId, out task2); + } + + private bool TryFindQueuedTask(string downloadId, out DownloadTask2 task2) + { + task2 = _queuedDownloads.UnorderedItems + .FirstOrDefault(x => x.Element.Info.DownloadId == downloadId).Element; + return task2 != null; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs new file mode 100644 index 0000000..2dd205d --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs @@ -0,0 +1,58 @@ +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using UnityEngine; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) + { + CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); + if (player == null) + { + // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); + playerEntity = null; + return false; + } + playerEntity = player; + return true; + } + + internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) + { + CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); + if (prop == null) + { + // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); + propData = null; + return false; + } + propData = prop; + return true; + } + + private static bool IsPlayerLocal(string playerId) + { + return playerId == MetaPort.Instance.ownerId; + } + + private static bool IsPlayerFriend(string playerId) + { + return Friends.FriendsWith(playerId); + } + + private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) + { + if (player.PuppetMaster == null) return false; + return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; + } + + internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) + { + Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); + return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs new file mode 100644 index 0000000..1132b8e --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs @@ -0,0 +1,47 @@ +using ABI_RC.Core.IO; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + private float CalculatePriority(DownloadTask2 task2) + { + return task2.Type switch + { + DownloadTaskType.Avatar => CalculateAvatarPriority(task2), + DownloadTaskType.Prop => CalculatePropPriority(task2), + DownloadTaskType.World => CalculateWorldPriority(task2), + _ => task2.Info.FileSize + }; + } + + private float CalculateAvatarPriority(DownloadTask2 task2) + { + float priority = task2.Info.FileSize; + + if (IsPlayerLocal(task2.PlayerId)) + return 0f; + + if (!TryGetPlayerEntity(task2.PlayerId, out var player)) + return priority; + + if (PrioritizeFriends && IsPlayerFriend(task2.PlayerId)) + priority *= 0.5f; + + if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) + priority *= 0.75f; + + // Factor in download progress + priority *= (1 + task2.Progress / 100f); + + return priority; + } + + private float CalculatePropPriority(DownloadTask2 task2) + { + float priority = task2.Info.FileSize; + + if (IsPlayerLocal(task2.PlayerId)) + return 0f; + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs new file mode 100644 index 0000000..698ac06 --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs @@ -0,0 +1,144 @@ +using System.Diagnostics; +using System.Net.Http.Headers; +using ABI_RC.Core.IO; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + private async Task ProcessDownload(DownloadTask2 task2) + { + using var client = new HttpClient(); + + // Set up resume headers if we have a resume token + if (!string.IsNullOrEmpty(task2.ResumeToken)) + { + client.DefaultRequestHeaders.Range = new RangeHeaderValue(task2.BytesRead, null); + } + + using var response = await client.GetAsync(task2.Info.AssetUrl, HttpCompletionOption.ResponseHeadersRead); + using var dataStream = await response.Content.ReadAsStreamAsync(); + + // Open file in append mode if resuming, otherwise create new + using var fileStream = new FileStream( + task2.TargetPath, + string.IsNullOrEmpty(task2.ResumeToken) ? FileMode.Create : FileMode.Append, + FileAccess.Write); + + bool isEligibleForThrottle = task2.Info.FileSize > THROTTLE_THRESHOLD; + var stopwatch = new Stopwatch(); + + int bytesRead; + do + { + if (task2.Status == DownloadTaskStatus.Paused) + { + task2.ResumeToken = GenerateResumeToken(task2); + await fileStream.FlushAsync(); + return; + } + + if (task2.Status != DownloadTaskStatus.Downloading) + { + HandleCancellation(task2, dataStream, fileStream); + return; + } + + stopwatch.Restart(); + var lengthToRead = isEligibleForThrottle ? + ComputeUsableBandwidthPerDownload() : + 16384; + + var buffer = new byte[lengthToRead]; + bytesRead = await dataStream.ReadAsync(buffer, 0, lengthToRead); + + if (isEligibleForThrottle) + Interlocked.Add(ref _bytesReadLastSecond, bytesRead); + + await fileStream.WriteAsync(buffer, 0, bytesRead); + UpdateProgress(task2, bytesRead); + + } while (bytesRead > 0); + + CompleteDownload(task2); + } + + private string GenerateResumeToken(DownloadTask2 task2) + { + // Generate a unique token that includes file position and hash + return $"{task2.BytesRead}:{DateTime.UtcNow.Ticks}"; + } + + private async Task FinalizeDownload(DownloadTask2 task2) + { + var tempPath = task2.CachePath + ".tmp"; + + try + { + // Decrypt the file if needed + if (task2.Info.EncryptionAlgorithm != 0) + { + await DecryptFile(tempPath, task2.CachePath, task2.Info); + File.Delete(tempPath); + } + else + { + File.Move(tempPath, task2.CachePath); + } + + CompleteDownload(task2); + } + catch (Exception ex) + { + // _logger.Error($"Failed to finalize download for {task.Info.AssetId}: {ex.Message}"); + task2.Status = DownloadTaskStatus.Failed; + File.Delete(tempPath); + _activeDownloads.TryRemove(task2.Info.DownloadId, out _); + } + } + + private async Task DecryptFile(string sourcePath, string targetPath, DownloadInfo info) + { + // Implementation of file decryption based on EncryptionAlgorithm + // This would use the FileKey from the DownloadInfo + throw new NotImplementedException("File decryption not implemented"); + } + + private void HandleCancellation(DownloadTask2 task2, Stream dataStream, Stream fileStream) + { + if (task2.Status != DownloadTaskStatus.Failed) + task2.Status = DownloadTaskStatus.Cancelled; + + dataStream.Close(); + fileStream.Close(); + + task2.Progress = 0; + task2.BytesRead = 0; + + _activeDownloads.TryRemove(task2.Info.DownloadId, out _); + } + + private void UpdateProgress(DownloadTask2 task2, int bytesRead) + { + task2.BytesRead += bytesRead; + task2.Progress = Math.Clamp( + (int)(((float)task2.BytesRead / task2.Info.FileSize) * 100f), + 0, 100); + } + + private void CompleteDownload(DownloadTask2 task2) + { + task2.Status = DownloadTaskStatus.Complete; + task2.Progress = 100; + _activeDownloads.TryRemove(task2.Info.DownloadId, out _); + _completedDownloads.TryAdd(task2.Info.DownloadId, task2); + + lock (_downloadLock) + { + if (_queuedDownloads.TryDequeue(out var nextTask, out _)) + { + StartDownload(nextTask); + } + } + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs new file mode 100644 index 0000000..e8a580a --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs @@ -0,0 +1,80 @@ +using ABI_RC.Core.IO; + +namespace NAK.BetterContentLoading; + +public partial class DownloadManager2 +{ + public void QueueDownload(DownloadTask2 newTask2) + { + if (_completedDownloads.TryGetValue(newTask2.Info.DownloadId, out var completedTask)) + { + completedTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); + return; + } + + if (TryFindTask(newTask2.Info.DownloadId, out var existingTask)) + { + existingTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); + RecalculatePriority(existingTask); + return; + } + + float priority = CalculatePriority(newTask2); + newTask2.CurrentPriority = priority; + + lock (_downloadLock) + { + if (_activeDownloads.Count < MaxConcurrentDownloads) + { + StartDownload(newTask2); + return; + } + + var lowestPriorityTask = _activeDownloads.Values + .OrderByDescending(t => t.CurrentPriority * (1 + t.Progress / 100f)) + .LastOrDefault(); + + if (lowestPriorityTask != null && priority < lowestPriorityTask.CurrentPriority) + { + PauseDownload(lowestPriorityTask); + StartDownload(newTask2); + } + else + { + _queuedDownloads.Enqueue(newTask2, priority); + } + } + } + + private void StartDownload(DownloadTask2 task2) + { + task2.Status = DownloadTaskStatus.Downloading; + _activeDownloads.TryAdd(task2.Info.DownloadId, task2); + Task.Run(() => ProcessDownload(task2)); + } + + private void PauseDownload(DownloadTask2 task2) + { + task2.Status = DownloadTaskStatus.Queued; + _activeDownloads.TryRemove(task2.Info.DownloadId, out _); + _queuedDownloads.Enqueue(task2, task2.CurrentPriority); + } + + + + + private void RecalculatePriority(DownloadTask2 task2) + { + var newPriority = CalculatePriority(task2); + if (Math.Abs(newPriority - task2.CurrentPriority) < float.Epsilon) + return; + + task2.CurrentPriority = newPriority; + + if (task2.Status == DownloadTaskStatus.Queued || task2.Status == DownloadTaskStatus.Paused) + { + // Re-enqueue with new priority + _queuedDownloads.UpdatePriority(task2, newPriority); + } + } +} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadTask2.cs b/BetterContentLoading/DownloadManager2/DownloadTask2.cs new file mode 100644 index 0000000..0c531f6 --- /dev/null +++ b/BetterContentLoading/DownloadManager2/DownloadTask2.cs @@ -0,0 +1,62 @@ +namespace NAK.BetterContentLoading; + +public class DownloadTask2 +{ + public DownloadInfo Info { get; } + public DownloadTaskStatus Status { get; set; } + public DownloadTaskType Type { get; } + + public float BasePriority { get; set; } + public float CurrentPriority => BasePriority * (1 + Progress / 100f); + + public long BytesRead { get; set; } + public int Progress { get; set; } + + public string CachePath { get; } + public Dictionary Targets { get; } // Key: targetId (playerId/instanceId), Value: spawnerId + public bool LoadOnComplete { get; } // For worlds only + + public DownloadTask2( + DownloadInfo info, + string cachePath, + DownloadTaskType type, + bool loadOnComplete = false) + { + Info = info; + CachePath = cachePath; + Type = type; + LoadOnComplete = loadOnComplete; + Targets = new Dictionary(); + Status = DownloadTaskStatus.Queued; + } + + public void AddTarget(string targetId, string spawnerId = null) + { + if (Type == DownloadTaskType.World && Targets.Count > 0) + throw new InvalidOperationException("World downloads cannot have multiple targets"); + + Targets[targetId] = spawnerId; + } + + public void RemoveTarget(string targetId) + { + Targets.Remove(targetId); + } +} + +public enum DownloadTaskType +{ + Avatar, + Prop, + World +} + +public enum DownloadTaskStatus +{ + Queued, + Downloading, + Paused, + Complete, + Failed, + Cancelled +} \ No newline at end of file diff --git a/InteractionTest/Main.cs b/BetterContentLoading/Main.cs similarity index 68% rename from InteractionTest/Main.cs rename to BetterContentLoading/Main.cs index a88cbfd..5b5d812 100644 --- a/InteractionTest/Main.cs +++ b/BetterContentLoading/Main.cs @@ -1,22 +1,23 @@ using MelonLoader; +using NAK.BetterContentLoading.Patches; -namespace NAK.InteractionTest; +namespace NAK.BetterContentLoading; -public class InteractionTestMod : MelonMod +public class BetterContentLoadingMod : MelonMod { internal static MelonLogger.Instance Logger; - - #region Melon Mod Overrides + + #region Melon Events public override void OnInitializeMelon() { Logger = LoggerInstance; - ApplyPatches(typeof(Patches.ControllerRayPatches)); + ApplyPatches(typeof(CVRDownloadManager_Patches)); } - #endregion Melon Mod Overrides - + #endregion Melon Events + #region Melon Mod Utilities private void ApplyPatches(Type type) @@ -33,4 +34,4 @@ public class InteractionTestMod : MelonMod } #endregion Melon Mod Utilities -} +} \ No newline at end of file diff --git a/BetterContentLoading/ModSettings.cs b/BetterContentLoading/ModSettings.cs new file mode 100644 index 0000000..f07436c --- /dev/null +++ b/BetterContentLoading/ModSettings.cs @@ -0,0 +1,31 @@ +using MelonLoader; + +namespace NAK.RCCVirtualSteeringWheel; + +internal static class ModSettings +{ + #region Constants + + private const string ModName = nameof(RCCVirtualSteeringWheel); + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryOverrideSteeringRange = + Category.CreateEntry("override_steering_range", false, + "Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?"); + + internal static readonly MelonPreferences_Entry EntryCustomSteeringRange = + Category.CreateEntry("custom_steering_range", 60f, + "Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)"); + + internal static readonly MelonPreferences_Entry EntryInvertSteering = + Category.CreateEntry("invert_steering", false, + "Invert Steering", description: "Inverts the steering direction"); + + #endregion Melon Preferences +} \ No newline at end of file diff --git a/BetterContentLoading/Patches.cs b/BetterContentLoading/Patches.cs new file mode 100644 index 0000000..d3c452b --- /dev/null +++ b/BetterContentLoading/Patches.cs @@ -0,0 +1,71 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.Responses; +using HarmonyLib; +using NAK.BetterContentLoading.Util; + +namespace NAK.BetterContentLoading.Patches; + +internal static class CVRDownloadManager_Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(CVRDownloadManager), nameof(CVRDownloadManager.QueueTask))] + private static bool Prefix_CVRDownloadManager_QueueTask( + string assetId, + DownloadTask2.ObjectType type, + string assetUrl, + string fileId, + long fileSize, + string fileKey, + string toAttach, + string fileHash = null, + UgcTagsData tagsData = null, + CVRLoadingAvatarController loadingAvatarController = null, + bool joinOnComplete = false, + bool isHomeRequested = false, + int compatibilityVersion = 0, + int encryptionAlgorithm = 0, + string spawnerId = null) + { + DownloadInfo info; + + switch (type) + { + case DownloadTask2.ObjectType.Avatar: + info = new DownloadInfo( + assetId, assetUrl, fileId, fileSize, fileKey, fileHash, + compatibilityVersion, encryptionAlgorithm, tagsData); + BetterDownloadManager.Instance.QueueAvatarDownload(in info, toAttach, loadingAvatarController); + return true; + case DownloadTask2.ObjectType.Prop: + info = new DownloadInfo( + assetId, assetUrl, fileId, fileSize, fileKey, fileHash, + compatibilityVersion, encryptionAlgorithm, tagsData); + BetterDownloadManager.Instance.QueuePropDownload(in info, toAttach, spawnerId); + return true; + case DownloadTask2.ObjectType.World: + _ = ThreadingHelper.RunOffMainThreadAsync(() => + { + var response = ApiConnection.MakeRequest( + ApiConnection.ApiOperation.WorldMeta, + new { worldID = assetId } + ); + + if (response?.Result.Data == null) + return; + + info = new DownloadInfo( + assetId, response.Result.Data.FileLocation, response.Result.Data.FileId, + response.Result.Data.FileSize, response.Result.Data.FileKey, response.Result.Data.FileHash, + (int)response.Result.Data.CompatibilityVersion, (int)response.Result.Data.EncryptionAlgorithm, + null); + + BetterDownloadManager.Instance.QueueWorldDownload(in info, joinOnComplete, isHomeRequested); + }, CancellationToken.None); + return true; + default: + return true; + } + } +} \ No newline at end of file diff --git a/BetterContentLoading/Properties/AssemblyInfo.cs b/BetterContentLoading/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9ab241d --- /dev/null +++ b/BetterContentLoading/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using MelonLoader; +using NAK.BetterContentLoading; +using NAK.BetterContentLoading.Properties; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.BetterContentLoading))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.BetterContentLoading))] + +[assembly: MelonInfo( + typeof(BetterContentLoadingMod), + nameof(NAK.BetterContentLoading), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BetterContentLoading" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.BetterContentLoading.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/BetterContentLoading/README.md b/BetterContentLoading/README.md new file mode 100644 index 0000000..8d9f529 --- /dev/null +++ b/BetterContentLoading/README.md @@ -0,0 +1,14 @@ +# RCCVirtualSteeringWheel + +Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.~~~~ \ No newline at end of file diff --git a/BetterContentLoading/format.json b/BetterContentLoading/format.json new file mode 100644 index 0000000..f565d36 --- /dev/null +++ b/BetterContentLoading/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "RCCVirtualSteeringWheel", + "modversion": "1.0.1", + "gameversion": "2024r177", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n", + "searchtags": [ + "rcc", + "steering", + "vehicle", + "car" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/RCCVirtualSteeringWheel.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Constants.cs b/CVRGizmos/Popcron.Gizmos/Constants.cs deleted file mode 100644 index e5884c9..0000000 --- a/CVRGizmos/Popcron.Gizmos/Constants.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Popcron -{ - public class Constants - { - public const string UniqueIdentifier = "Popcron.Gizmos"; - public const string EnabledKey = UniqueIdentifier + ".Enabled"; - } -} \ No newline at end of file diff --git a/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs b/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs deleted file mode 100644 index f9193cf..0000000 --- a/CVRGizmos/Popcron.Gizmos/Drawers/LineDrawer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using UnityEngine; - -namespace Popcron -{ - public class LineDrawer : Drawer - { - public LineDrawer() - { - - } - - public override int Draw(ref Vector3[] buffer, params object[] args) - { - buffer[0] = (Vector3)args[0]; - buffer[1] = (Vector3)args[1]; - return 2; - } - } -} diff --git a/CVRGizmos/Popcron.Gizmos/Element.cs b/CVRGizmos/Popcron.Gizmos/Element.cs deleted file mode 100644 index 361fd59..0000000 --- a/CVRGizmos/Popcron.Gizmos/Element.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UnityEngine; - -namespace Popcron -{ - internal class Element - { - public Vector3[] points = { }; - public Color color = Color.white; - public bool dashed = false; - public Matrix4x4 matrix = Matrix4x4.identity; - } -} \ No newline at end of file diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs index 6f77a61..8c07542 100644 --- a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs +++ b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs @@ -2,17 +2,21 @@ using ABI.Scripting.CVRSTL.Client; using System.Diagnostics; using MTJobSystem; +using UnityEngine; namespace NAK.CVRLuaToolsExtension; public static class CVRLuaClientBehaviourExtensions { internal static readonly Dictionary _isRestarting = new(); + private static string PersistentDataPath; #region Public Methods public static void Restart(this CVRLuaClientBehaviour behaviour) { + PersistentDataPath ??= Application.persistentDataPath; // needs to be set on main + if (_isRestarting.TryGetValue(behaviour, out bool isRestarting) && isRestarting) { CVRLuaToolsExtensionMod.Logger.Warning($"Restart is already in progress for {behaviour.ScriptName}."); @@ -105,7 +109,7 @@ public static class CVRLuaClientBehaviourExtensions behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n"); behaviour.script = null; - behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform); + behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform, PersistentDataPath); behaviour.InitTimerIfNeeded(); // only null if crashed prior behaviour.script.AttachDebugger(behaviour.timer); // reattach the debugger diff --git a/CustomRichPresence/CustomRichPresence.csproj b/CustomRichPresence/CustomRichPresence.csproj new file mode 100644 index 0000000..4e44ed3 --- /dev/null +++ b/CustomRichPresence/CustomRichPresence.csproj @@ -0,0 +1,11 @@ + + + + net48 + + + + ..\.ManagedLibs\TheClapper.dll + + + diff --git a/CustomRichPresence/Main.cs b/CustomRichPresence/Main.cs new file mode 100644 index 0000000..a2702d5 --- /dev/null +++ b/CustomRichPresence/Main.cs @@ -0,0 +1,123 @@ +using System.Reflection; +using ABI_RC.Core.Networking; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util.AnimatorManager; +using ABI_RC.Helpers; +using HarmonyLib; +using MagicaCloth; +using MelonLoader; +using Steamworks; +using UnityEngine; + +namespace NAK.CustomRichPresence; + +public class CustomRichPresenceMod : MelonMod +{ + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(CustomRichPresence)); + + private static readonly MelonPreferences_Entry EntryUseCustomPresence = + Category.CreateEntry("use_custom_presence", true, + "Use Custom Presence", description: "Uses the custom rich presence setup."); + + // Discord Rich Presence Customization + private static readonly MelonPreferences_Entry DiscordStatusFormat = + Category.CreateEntry("discord_status_format", "Online using {mode}.", + "Discord Status Format", description: "Format for Discord status message. Available variables: {mode}, {instance_name}, {privacy}"); + + private static readonly MelonPreferences_Entry DiscordDetailsFormat = + Category.CreateEntry("discord_details_format", "{instance_name} [{privacy}]", + "Discord Details Format", description: "Format for Discord details. Available variables: {instance_name}, {privacy}, {world_name}, {mission_name}"); + + // Steam Rich Presence Customization + private static readonly MelonPreferences_Entry SteamStatusFormat = + Category.CreateEntry("steam_status_format", "Exploring ({mode}) {instance_name} [{privacy}].", + "Steam Status Format", description: "Format for Steam status. Available variables: {mode}, {instance_name}, {privacy}"); + + private static readonly MelonPreferences_Entry SteamGameStatusFormat = + Category.CreateEntry("steam_game_status_format", "Connected to a server, Privacy: {privacy}.", + "Steam Game Status Format", description: "Format for Steam game status. Available variables: {privacy}, {instance_name}, {world_name}"); + + private static readonly MelonPreferences_Entry SteamDisplayStatus = + Category.CreateEntry("steam_display_status", "#Status_Online", + "Steam Display Status", description: "Steam display status localization key."); + + #endregion Melon Preferences + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(RichPresence).GetMethod(nameof(RichPresence.PopulateLastMessage), + BindingFlags.NonPublic | BindingFlags.Static), + prefix: new HarmonyMethod(typeof(CustomRichPresenceMod).GetMethod(nameof(OnPopulateLastMessage), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static string FormatPresenceText(string format, string mode) + { + return format + .Replace("{mode}", mode) + .Replace("{instance_name}", RichPresence.LastMsg.InstanceName) + .Replace("{privacy}", RichPresence.LastMsg.InstancePrivacy) + .Replace("{world_name}", RichPresence.LastMsg.InstanceWorldName) + .Replace("{mission_name}", RichPresence.LastMsg.InstanceMissionName) + .Replace("{current_players}", RichPresence.LastMsg.CurrentPlayers.ToString()) + .Replace("{max_players}", RichPresence.LastMsg.MaxPlayers.ToString()); + } + + private static bool OnPopulateLastMessage() + { + if (!EntryUseCustomPresence.Value) + return true; + + string mode = MetaPort.Instance.isUsingVr ? "VR Mode" : "Desktop Mode"; + + PresenceManager.ClearPresence(); + if (RichPresence.DiscordEnabled) + { + string status = FormatPresenceText(DiscordStatusFormat.Value, mode); + string details = FormatPresenceText(DiscordDetailsFormat.Value, mode); + + PresenceManager.UpdatePresence( + status, + details, + RichPresence.LastConnectedToServer, + 0L, + "discordrp-cvrmain", + null, + null, + null, + RichPresence.LastMsg.InstanceMeshId, + RichPresence.LastMsg.CurrentPlayers, + RichPresence.LastMsg.MaxPlayers + ); + } + + if (!CheckVR.Instance.skipSteamApiRegister && SteamManager.Initialized) + { + SteamFriends.ClearRichPresence(); + if (RichPresence.SteamEnabled) + { + string status = FormatPresenceText(SteamStatusFormat.Value, mode); + string gameStatus = FormatPresenceText(SteamGameStatusFormat.Value, mode); + + SteamFriends.SetRichPresence("status", status); + SteamFriends.SetRichPresence("gamestatus", gameStatus); + SteamFriends.SetRichPresence("steam_display", SteamDisplayStatus.Value); + SteamFriends.SetRichPresence("steam_player_group", RichPresence.LastMsg.InstanceMeshId); + SteamFriends.SetRichPresence("steam_player_group_size", RichPresence.LastMsg.CurrentPlayers.ToString()); + SteamFriends.SetRichPresence("gamemode", RichPresence.LastMsg.InstanceMissionName); + SteamFriends.SetRichPresence("worldname", RichPresence.LastMsg.InstanceWorldName); + SteamFriends.SetRichPresence("instancename", RichPresence.LastMsg.InstanceName); + SteamFriends.SetRichPresence("instanceprivacy", RichPresence.LastMsg.InstancePrivacy); + SteamFriends.SetRichPresence("currentplayer", RichPresence.LastMsg.CurrentPlayers.ToString()); + SteamFriends.SetRichPresence("maxplayer", RichPresence.LastMsg.MaxPlayers.ToString()); + } + } + + return false; + } +} \ No newline at end of file diff --git a/InteractionTest/Properties/AssemblyInfo.cs b/CustomRichPresence/Properties/AssemblyInfo.cs similarity index 65% rename from InteractionTest/Properties/AssemblyInfo.cs rename to CustomRichPresence/Properties/AssemblyInfo.cs index ffc8f5e..0e00c7b 100644 --- a/InteractionTest/Properties/AssemblyInfo.cs +++ b/CustomRichPresence/Properties/AssemblyInfo.cs @@ -1,30 +1,30 @@ -using NAK.InteractionTest.Properties; +using NAK.CustomRichPresence.Properties; using MelonLoader; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.InteractionTest))] +[assembly: AssemblyTitle(nameof(NAK.CustomRichPresence))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.InteractionTest))] +[assembly: AssemblyProduct(nameof(NAK.CustomRichPresence))] [assembly: MelonInfo( - typeof(NAK.InteractionTest.InteractionTestMod), - nameof(NAK.InteractionTest), + typeof(NAK.CustomRichPresence.CustomRichPresenceMod), + nameof(NAK.CustomRichPresence), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/InteractionTest" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomRichPresence" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 125, 126, 129)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.InteractionTest.Properties; +namespace NAK.CustomRichPresence.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/CustomRichPresence/README.md b/CustomRichPresence/README.md new file mode 100644 index 0000000..33bd3fd --- /dev/null +++ b/CustomRichPresence/README.md @@ -0,0 +1,14 @@ +# DropPropTweak + +Gives the Drop Prop button more utility by allowing you to drop props in the air. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/CustomRichPresence/format.json b/CustomRichPresence/format.json new file mode 100644 index 0000000..a599614 --- /dev/null +++ b/CustomRichPresence/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "DropPropTweak", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Gives the Drop Prop button more utility by allowing you to drop props in the air.", + "searchtags": [ + "prop", + "spawn", + "indicator", + "loading" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r34/DropPropTweak.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DropPropTweak/", + "changelog": "- Initial Release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/CustomSpawnPoint/SpawnPointManager.cs b/CustomSpawnPoint/SpawnPointManager.cs index 12bcd43..350d07c 100644 --- a/CustomSpawnPoint/SpawnPointManager.cs +++ b/CustomSpawnPoint/SpawnPointManager.cs @@ -7,38 +7,38 @@ using Object = UnityEngine.Object; using ABI_RC.Core; using Newtonsoft.Json; -namespace NAK.CustomSpawnPoint +namespace NAK.CustomSpawnPoint; + +internal static class SpawnPointManager { - internal static class SpawnPointManager - { - #region Fields + #region Fields - private static string currentWorldId = string.Empty; - private static SpawnPointData? currentSpawnPoint; + private static string currentWorldId = string.Empty; + private static SpawnPointData? currentSpawnPoint; - private static string requestedWorldId = string.Empty; - private static SpawnPointData? requestedSpawnPoint; + private static string requestedWorldId = string.Empty; + private static SpawnPointData? requestedSpawnPoint; - private static Dictionary spawnPoints = new(); - private static readonly string jsonFilePath = Path.Combine("UserData", "customspawnpoints.json"); + private static Dictionary spawnPoints = new(); + private static readonly string jsonFilePath = Path.Combine("UserData", "customspawnpoints.json"); - private static GameObject[] customSpawnPointsArray; - private static GameObject[] originalSpawnPointsArray; + private static GameObject[] customSpawnPointsArray; + private static GameObject[] originalSpawnPointsArray; - #endregion Fields + #endregion Fields - #region Initialization + #region Initialization - internal static void Init() - { + internal static void Init() + { LoadSpawnpoints(); CVRGameEventSystem.World.OnLoad.AddListener(OnWorldLoaded); CVRGameEventSystem.World.OnUnload.AddListener(OnWorldUnloaded); MelonLoader.MelonCoroutines.Start(WaitMainMenuUi()); } - private static System.Collections.IEnumerator WaitMainMenuUi() - { + private static System.Collections.IEnumerator WaitMainMenuUi() + { while (ViewManager.Instance == null) yield return null; while (ViewManager.Instance.gameMenuView == null) @@ -65,12 +65,12 @@ namespace NAK.CustomSpawnPoint customSpawnPointsArray = new[] { customSpawnPointObject }; } - #endregion Initialization + #endregion Initialization - #region Game Events + #region Game Events - private static void OnWorldLoaded(string worldId) - { + private static void OnWorldLoaded(string worldId) + { CVRWorld world = CVRWorld.Instance; if (world == null) return; @@ -90,13 +90,13 @@ namespace NAK.CustomSpawnPoint } } - private static void OnWorldUnloaded(string worldId) - { + private static void OnWorldUnloaded(string worldId) + { ClearCurrentWorldState(); } - internal static void OnRequestWorldDetailsPage(string worldId) - { + internal static void OnRequestWorldDetailsPage(string worldId) + { //CustomSpawnPointMod.Logger.Msg("Requesting world details page for world: " + worldId); requestedWorldId = worldId; @@ -106,24 +106,24 @@ namespace NAK.CustomSpawnPoint UpdateMenuButtonState(hasSpawnpoint, worldId == currentWorldId && CVRWorld.Instance != null && CVRWorld.Instance.allowFlying); } - private static void OnClearSpawnpointConfirm(string id, string value, string data) - { + private static void OnClearSpawnpointConfirm(string id, string value, string data) + { if (id != "nak_clear_spawnpoint") return; if (value == "true") ClearSpawnPoint(); } - #endregion Game Events + #endregion Game Events - #region Spawnpoint Management + #region Spawnpoint Management - public static void SetSpawnPoint() - => SetSpawnPointForWorld(currentWorldId); + public static void SetSpawnPoint() + => SetSpawnPointForWorld(currentWorldId); - public static void ClearSpawnPoint() - => ClearSpawnPointForWorld(currentWorldId); + public static void ClearSpawnPoint() + => ClearSpawnPointForWorld(currentWorldId); - private static void SetSpawnPointForWorld(string worldId) - { + private static void SetSpawnPointForWorld(string worldId) + { CustomSpawnPointMod.Logger.Msg("Setting spawn point for world: " + worldId); Vector3 playerPosition = PlayerSetup.Instance.GetPlayerPosition(); @@ -152,8 +152,8 @@ namespace NAK.CustomSpawnPoint UpdateMenuButtonState(true, worldId == currentWorldId); } - private static void ClearSpawnPointForWorld(string worldId) - { + private static void ClearSpawnPointForWorld(string worldId) + { CustomSpawnPointMod.Logger.Msg("Clearing spawn point for world: " + worldId); if (spawnPoints.ContainsKey(worldId)) @@ -172,29 +172,29 @@ namespace NAK.CustomSpawnPoint UpdateMenuButtonState(false, worldId == currentWorldId); } - private static void UpdateCustomSpawnPointTransform(SpawnPointData spawnPoint) - { + private static void UpdateCustomSpawnPointTransform(SpawnPointData spawnPoint) + { customSpawnPointsArray[0].transform.SetPositionAndRotation(spawnPoint.Position, Quaternion.Euler(spawnPoint.Rotation)); } - private static void UpdateMenuButtonState(bool hasSpawnpoint, bool isInWorld) - { + private static void UpdateMenuButtonState(bool hasSpawnpoint, bool isInWorld) + { ViewManager.Instance.gameMenuView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString()); } - private static void ClearCurrentWorldState() - { + private static void ClearCurrentWorldState() + { currentWorldId = string.Empty; currentSpawnPoint = null; originalSpawnPointsArray = null; } - #endregion Spawnpoint Management + #endregion Spawnpoint Management - #region JSON Management + #region JSON Management - private static void LoadSpawnpoints() - { + private static void LoadSpawnpoints() + { if (File.Exists(jsonFilePath)) { string json = File.ReadAllText(jsonFilePath); @@ -206,18 +206,18 @@ namespace NAK.CustomSpawnPoint } } - private static void SaveSpawnpoints() - { + private static void SaveSpawnpoints() + { File.WriteAllText(jsonFilePath, JsonConvert.SerializeObject(spawnPoints, Formatting.Indented, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore } // death )); } - #endregion JSON Management + #endregion JSON Management - #region Spawnpoint JS + #region Spawnpoint JS - private const string spawnpointJs = @" + private const string spawnpointJs = @" let hasSpawnpointForThisWorld = false; let spawnpointButton = null; @@ -249,17 +249,16 @@ engine.on('NAKUpdateSpawnpointStatus', function (hasSpawnpoint, isInWorld) { }); "; - #endregion Spawnpoint JS - } + #endregion Spawnpoint JS +} - #region Serializable +#region Serializable - [Serializable] - public struct SpawnPointData - { - public Vector3 Position; - public Vector3 Rotation; - } +[Serializable] +public struct SpawnPointData +{ + public Vector3 Position; + public Vector3 Rotation; +} - #endregion Serializable -} \ No newline at end of file +#endregion Serializable \ No newline at end of file diff --git a/InteractionTest/Components/InteractionTracker.cs b/InteractionTest/Components/InteractionTracker.cs deleted file mode 100644 index b5ea1b1..0000000 --- a/InteractionTest/Components/InteractionTracker.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Collections; -using ABI_RC.Core; -using ABI_RC.Systems.GameEventSystem; -using ABI_RC.Systems.IK; -using ABI_RC.Systems.Movement; -using ABI.CCK.Components; -using RootMotion.FinalIK; -using UnityEngine; - -namespace NAK.InteractionTest.Components; - -public class InteractionTracker : MonoBehaviour -{ - #region Setup - - public static void Setup(GameObject parentObject, bool isLeft = true) - { - // LeapMotion: RotationTarget - - GameObject trackerObject = new("NAK.InteractionTracker"); - trackerObject.transform.SetParent(parentObject.transform); - trackerObject.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - - GameObject ikObject = new("NAK.InteractionTracker.IK"); - ikObject.transform.SetParent(trackerObject.transform); - ikObject.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - - SphereCollider sphereCol = trackerObject.AddComponent(); - sphereCol.radius = 0f; - sphereCol.isTrigger = true; - - BetterBetterCharacterController.QueueRemovePlayerCollision(sphereCol); - - InteractionTracker tracker = trackerObject.AddComponent(); - tracker.isLeft = isLeft; - tracker.Initialize(); - } - - #endregion Setup - - #region Actions - - public Action OnPenetrationDetected; // called on start of penetration - public Action OnPenetrationLost; // called on end of penetration - public Action OnPenetrationNormalChanged; // called when penetration normal changes after 2 degree threshold - - #endregion Actions - - public bool isLeft; - - public bool IsColliding => _isColliding; - public Vector3 ClosestPoint { get; private set; } - public Vector3 LastPenetrationNormal => _lastPenetrationNormal; - - private bool _isColliding; - private bool _wasPenetrating; - public Vector3 _lastPenetrationNormal = Vector3.forward; - private Collider _selfCollider; - private const float NormalChangeThreshold = 0.2f; - - #region Unity Events - - private void Initialize() - { - _selfCollider = GetComponent(); - CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoaded); - } - - private void OnDestroy() - { - CVRGameEventSystem.Avatar.OnLocalAvatarLoad.RemoveListener(OnLocalAvatarLoaded); - } - - private void OnLocalAvatarLoaded(CVRAvatar _) - { - StartCoroutine(FrameLateInit()); - } - - private IEnumerator FrameLateInit() - { - yield return null; - yield return null; - OnInitSolver(); - IKSystem.vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdate); - IKSystem.vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdate); - } - - private void OnTriggerStay(Collider other) - { - if (other.gameObject.layer == CVRLayers.PlayerLocal) - return; - - if (_selfCollider == null) - return; - - Transform selfTransform = transform; - Transform otherTransform = other.transform; - - bool isPenetrating = Physics.ComputePenetration( - _selfCollider, selfTransform.position, selfTransform.rotation, - other, otherTransform.position, otherTransform.rotation, - out Vector3 direction, out float distance); - - if (isPenetrating) - { - ClosestPoint = selfTransform.position + direction * distance; - Debug.DrawRay(ClosestPoint, direction * 10, Color.red); - - if (!_wasPenetrating) - { - OnPenetrationDetected?.Invoke(); - _wasPenetrating = true; - _lastPenetrationNormal = direction; - } - - float angleChange = Vector3.Angle(_lastPenetrationNormal, direction); - Debug.Log("Angle change: " + angleChange); - if (angleChange > NormalChangeThreshold) - { - _lastPenetrationNormal = direction; - OnPenetrationNormalChanged?.Invoke(); - } - } - else - { - if (_wasPenetrating) - { - OnPenetrationLost?.Invoke(); - _wasPenetrating = false; - } - } - } - - private void OnTriggerEnter(Collider other) - { - if (other.gameObject.layer == CVRLayers.PlayerLocal) - return; - - Debug.Log("Triggered with " + other.gameObject.name); - _isColliding = true; - } - - private void OnTriggerExit(Collider other) - { - if (other.gameObject.layer == CVRLayers.PlayerLocal) - return; - - Debug.Log("Exited trigger with " + other.gameObject.name); - _isColliding = false; - - if (_wasPenetrating) - { - OnPenetrationLost?.Invoke(); - _wasPenetrating = false; - } - } - - #endregion Unity Events - - private Transform _oldTarget; - - private Vector3 _initialPosOffset; - private Quaternion _initialRotOffset; - - private IKSolverVR.Arm _armSolver; - - private void OnInitSolver() - { - _armSolver = isLeft ? IKSystem.vrik.solver.arms[0] : IKSystem.vrik.solver.arms[1]; - - Transform target = _armSolver.target; - if (target == null) - target = transform.parent.Find("RotationTarget"); // LeapMotion: RotationTarget - - if (target == null) return; - - _initialPosOffset = target.localPosition; - _initialRotOffset = target.localRotation; - } - - private void OnPreSolverUpdate() - { - if (!IsColliding) - return; - - Transform selfTransform = transform; - - float dot = Vector3.Dot(_lastPenetrationNormal, selfTransform.forward); - if (dot > -0.45f) - return; - - _oldTarget = _armSolver.target; - _armSolver.target = selfTransform.GetChild(0); - - _armSolver.target.position = ClosestPoint + selfTransform.rotation * _initialPosOffset; - _armSolver.target.rotation = _initialRotOffset * Quaternion.LookRotation(-_lastPenetrationNormal, selfTransform.up); - - _armSolver.positionWeight = 1f; - _armSolver.rotationWeight = 1f; - } - - private void OnPostSolverUpdate() - { - if (!_oldTarget) - return; - - _armSolver.target = _oldTarget; - _oldTarget = null; - - _armSolver.positionWeight = 0f; - _armSolver.rotationWeight = 0f; - } -} \ No newline at end of file diff --git a/InteractionTest/ModSettings.cs b/InteractionTest/ModSettings.cs deleted file mode 100644 index 010f81b..0000000 --- a/InteractionTest/ModSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -using MelonLoader; - -namespace NAK.InteractionTest; - -internal static class ModSettings -{ -} \ No newline at end of file diff --git a/InteractionTest/Patches.cs b/InteractionTest/Patches.cs deleted file mode 100644 index 1fa65fd..0000000 --- a/InteractionTest/Patches.cs +++ /dev/null @@ -1,17 +0,0 @@ - -using ABI_RC.Core; -using ABI_RC.Core.InteractionSystem; -using HarmonyLib; -using NAK.InteractionTest.Components; - -namespace NAK.InteractionTest.Patches; - -internal static class ControllerRayPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.Start))] - private static void Postfix_BetterCharacterController_Start(ref ControllerRay __instance) - { - InteractionTracker.Setup(__instance.gameObject, __instance.hand == CVRHand.Left); - } -} \ No newline at end of file diff --git a/InteractionTest/README.md b/InteractionTest/README.md deleted file mode 100644 index 35bb285..0000000 --- a/InteractionTest/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# OriginShift - -Experimental mod that allows world origin to be shifted to prevent floating point precision issues. - -## Compromises -- Steam Audio data cannot be shifted. -- NavMesh data cannot be shifted. -- Light Probe data cannot be shifted (until [unity 2022](https://docs.unity3d.com/2022.3/Documentation/Manual/LightProbes-Moving.html)). -- Occlusion Culling data cannot be shifted. - - When using "Forced" mode, occlusion culling is disabled. -- Only 10k trail positions can be shifted per Trail Renderer (artificial limit). -- Only 10k particle positions can be shifted per Particle System (artificial limit). - - Potentially can fix by changing Particle System to Custom Simulation Space ? (untested) -- World Constraints are not shifted. - -## Known Issues -- Mod Network is not yet implemented, so Compatibility Mode is required to play with others. -- Portable Camera drone mode is not yet offset by the world origin shift. -- Chunk threshold past 10 units will break Voice Chat with remote players in some cases (without Compatibility Mode). - - This is because the voice server dictates who can hear who based on distance from each other and the world origin shift messes with that. -- Teleports past 50k units will not work. - - BetterBetterCharacterController prevents teleports past 50k units. -- Magica Cloth. - -## Mod Incompatibilities -- PlayerRagdollMod will freak out when you ragdoll between chunk boundaries. - -## Provided Components -- `OriginShiftController` - World script to configure origin shift. -- `OriginShiftEventReceiver` - Event receiver for OriginShift events. -- `OriginShiftTransformReceiver` - Shifts the transform of the GameObject it is attached to. -- `OriginShiftRigidbodyReceiver` - Shifts the rigidbody of the GameObject it is attached to. -- `OriginShiftTrailRendererReceiver` - Shifts the positions of the Trail Renderer of the GameObject it is attached to. -- `OriginShiftParticleSystemReceiver` - Shifts the positions of the Particle System of the GameObject it is attached to. - -The provided receiver components are automatically added to Props, Players, and Object Syncs. - -## Provided Shader Globals -- `_OriginShiftChunkOffset` - The current amount of chunks offset from the origin. -- `_OriginShiftChunkThreshold` - The size of a chunk in world units. -- `_OriginShiftChunkPosition` - The chunk offset multiplied by the chunk threshold. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/InteractionTest/format.json b/InteractionTest/format.json deleted file mode 100644 index 39d8c48..0000000 --- a/InteractionTest/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 211, - "name": "RelativeSync", - "modversion": "1.0.3", - "gameversion": "2024r175", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.", - "searchtags": [ - "relative", - "sync", - "movement", - "chair" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r29/RelativeSync.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Enabled the Experimental settings to fix **local** jitter on Movement Parents by default\n- Adjusted BBCC No Interpolation fix to account for potential native fix (now respects initial value)", - "embedcolor": "#507e64" -} \ No newline at end of file diff --git a/LegacyContentMitigation/ModSettings.cs b/LegacyContentMitigation/ModSettings.cs deleted file mode 100644 index 0fdef02..0000000 --- a/LegacyContentMitigation/ModSettings.cs +++ /dev/null @@ -1,24 +0,0 @@ -using MelonLoader; - -namespace NAK.LegacyContentMitigation; - -internal static class ModSettings -{ - #region Constants - - internal const string ModName = nameof(LegacyContentMitigation); - internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; - - #endregion Constants - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = - Category.CreateEntry("auto_for_legacy_worlds", true, - "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); - - #endregion Melon Preferences -} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs b/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs deleted file mode 100644 index 30fddc1..0000000 --- a/LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs +++ /dev/null @@ -1,161 +0,0 @@ -using UnityEngine; -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Systems.ModNetwork; - -namespace NAK.LuaNetVars; - -public class PickupableBehaviour : MNSyncedBehaviour -{ - private enum PickupMessageType : byte - { - GrabState, - Transform - } - - private bool isHeld; - private string holderId; - private Vector3 lastPosition; - private Quaternion lastRotation; - - public PickupableObject Pickupable { get; private set; } - - public PickupableBehaviour(string networkId, PickupableObject pickupable) : base(networkId, autoAcceptTransfers: false) - { - Pickupable = pickupable; - isHeld = false; - holderId = string.Empty; - lastPosition = pickupable.transform.position; - lastRotation = pickupable.transform.rotation; - } - - public void OnGrabbed(InteractionContext context) - { - RequestOwnership(success => { - if (success) - { - isHeld = true; - holderId = LocalUserId; - SendNetworkedData(WriteGrabState); - } - else - { - // Ownership request failed, drop the object - Pickupable.ControllerRay = null; // Force drop - } - }); - } - - public void OnDropped() - { - if (!HasOwnership) return; - - isHeld = false; - holderId = string.Empty; - SendNetworkedData(WriteGrabState); - } - - public void UpdateTransform(Vector3 position, Quaternion rotation) - { - if (!HasOwnership || !isHeld) return; - - lastPosition = position; - lastRotation = rotation; - SendNetworkedData(WriteTransform); - } - - protected override OwnershipResponse OnOwnershipRequested(string requesterId) - { - // If the object is held by the current owner, reject the transfer - if (isHeld && holderId == LocalUserId) - return OwnershipResponse.Rejected; - - // If theft is disallowed and the object is held by someone, reject the transfer - if (Pickupable.DisallowTheft && !string.IsNullOrEmpty(holderId)) - return OwnershipResponse.Rejected; - - return OwnershipResponse.Accepted; - } - - protected override void WriteState(ModNetworkMessage message) - { - message.Write(isHeld); - message.Write(holderId); - message.Write(lastPosition); - message.Write(lastRotation); - } - - protected override void ReadState(ModNetworkMessage message) - { - message.Read(out isHeld); - message.Read(out holderId); - message.Read(out lastPosition); - message.Read(out lastRotation); - - UpdatePickupableState(); - } - - private void WriteGrabState(ModNetworkMessage message) - { - message.Write((byte)PickupMessageType.GrabState); - message.Write(isHeld); - message.Write(holderId); - } - - private void WriteTransform(ModNetworkMessage message) - { - message.Write((byte)PickupMessageType.Transform); - message.Write(lastPosition); - message.Write(lastRotation); - } - - protected override void ReadCustomData(ModNetworkMessage message) - { - message.Read(out byte messageType); - - switch ((PickupMessageType)messageType) - { - case PickupMessageType.GrabState: - message.Read(out isHeld); - message.Read(out holderId); - break; - - case PickupMessageType.Transform: - message.Read(out Vector3 position); - message.Read(out Quaternion rotation); - lastPosition = position; - lastRotation = rotation; - break; - } - - UpdatePickupableState(); - } - - private void UpdatePickupableState() - { - // Update transform if we're not the holder - if (!isHeld || holderId != LocalUserId) - { - Pickupable.transform.position = lastPosition; - Pickupable.transform.rotation = lastRotation; - } - - // Force drop if we were holding but someone else took ownership - if (isHeld && holderId != LocalUserId) - { - Pickupable.ControllerRay = null; // Force drop - } - } - - protected override void OnOwnershipChanged(string newOwnerId) - { - base.OnOwnershipChanged(newOwnerId); - - // If we lost ownership and were holding, force drop - if (!HasOwnership && holderId == LocalUserId) - { - isHeld = false; - holderId = string.Empty; - Pickupable.ControllerRay = null; // Force drop - } - } -} \ No newline at end of file diff --git a/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs b/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs deleted file mode 100644 index 76b495a..0000000 --- a/LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs +++ /dev/null @@ -1,72 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.InteractionSystem.Base; -using UnityEngine; - -namespace NAK.LuaNetVars; - -public class PickupableObject : Pickupable -{ - [SerializeField] private bool canPickup = true; - [SerializeField] private bool disallowTheft = false; - [SerializeField] private float maxGrabDistance = 2f; - [SerializeField] private float maxPushDistance = 2f; - [SerializeField] private bool isAutoHold = false; - [SerializeField] private bool allowRotation = true; - [SerializeField] private bool allowPushPull = true; - [SerializeField] private bool allowInteraction = true; - - private PickupableBehaviour behaviour; - private bool isInitialized; - - private void Awake() - { - // Generate a unique network ID based on the instance ID - string networkId = $"pickup_{gameObject.name}"; - behaviour = new PickupableBehaviour(networkId, this); - isInitialized = true; - } - - private void OnDestroy() - { - behaviour?.Dispose(); - } - - private void Update() - { - if (behaviour?.HasOwnership == true) - { - transform.SetPositionAndRotation(ControllerRay.pivotPoint.position, ControllerRay.pivotPoint.rotation); - behaviour.UpdateTransform(transform.position, transform.rotation); - } - } - - #region Pickupable Implementation - - public override void OnGrab(InteractionContext context, Vector3 grabPoint) - { - if (!isInitialized) return; - behaviour.OnGrabbed(context); - } - - public override void OnDrop(InteractionContext context) - { - if (!isInitialized) return; - behaviour.OnDropped(); - } - - public override void OnFlingTowardsTarget(Vector3 target) - { - // ignore - } - - public override bool CanPickup => canPickup; - public override bool DisallowTheft => disallowTheft; - public override float MaxGrabDistance => maxGrabDistance; - public override float MaxPushDistance => maxPushDistance; - public override bool IsAutoHold => isAutoHold; - public override bool IsObjectRotationAllowed => allowRotation; - public override bool IsObjectPushPullAllowed => allowPushPull; - public override bool IsObjectInteractionAllowed => allowInteraction; - - #endregion Pickupable Implementation -} \ No newline at end of file diff --git a/LuaTTS/Main.cs b/LuaTTS/Main.cs index 2035bb9..6242c04 100644 --- a/LuaTTS/Main.cs +++ b/LuaTTS/Main.cs @@ -1,4 +1,6 @@ -using MelonLoader; +using ABI_RC.Scripting.ScriptNetwork; +using ABI_RC.Systems.ModNetwork; +using MelonLoader; using NAK.LuaTTS.Patches; namespace NAK.LuaTTS; @@ -8,6 +10,33 @@ public class LuaTTSMod : MelonMod public override void OnInitializeMelon() { ApplyPatches(typeof(LuaScriptFactoryPatches)); + + ModNetworkMessage.AddConverter(ReadScriptID, WriteScriptID); + ModNetworkMessage.AddConverter(ReadScriptInstanceID, WriteScriptInstanceID); + } + + private static ScriptID ReadScriptID(ModNetworkMessage msg) + { + msg.Read(out byte[] value); + ScriptID scriptID = new(value); + return scriptID; + } + + private static void WriteScriptID(ModNetworkMessage msg, ScriptID scriptID) + { + msg.Write(scriptID.value); + } + + private static ScriptInstanceID ReadScriptInstanceID(ModNetworkMessage msg) + { + msg.Read(out byte[] value); + ScriptInstanceID scriptInstanceID = new(value); + return scriptInstanceID; + } + + private static void WriteScriptInstanceID(ModNetworkMessage msg, ScriptInstanceID scriptInstanceID) + { + msg.Write(scriptInstanceID.value); } private void ApplyPatches(Type type) diff --git a/LuaTTS/Patches.cs b/LuaTTS/Patches.cs index 07d4297..c45cec1 100644 --- a/LuaTTS/Patches.cs +++ b/LuaTTS/Patches.cs @@ -11,17 +11,17 @@ internal static class LuaScriptFactoryPatches [HarmonyPostfix] [HarmonyPatch(typeof(LuaScriptFactory.CVRRequireModule), nameof(LuaScriptFactory.CVRRequireModule.Require))] private static void Postfix_CVRRequireModule_require( - string modid, + string moduleFriendlyName, ref LuaScriptFactory.CVRRequireModule __instance, ref object __result, - ref Script ___script, - ref CVRLuaContext ___context) + ref Script ____script, + ref CVRLuaContext ____context) { const string TTSModuleID = "TextToSpeech"; - if (TTSModuleID != modid) + if (TTSModuleID != moduleFriendlyName) return; // not our module - __result = TTSLuaModule.RegisterUserData(___script, ___context); + __result = TTSLuaModule.RegisterUserData(____script, ____context); __instance.RegisteredModules[TTSModuleID] = __result; // add module to cache } } \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index ce1db86..082aeae 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -87,6 +87,34 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComponentMonitor", "Compone EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShareBubbles", "ShareBubbles\ShareBubbles.csproj", "{ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutualMute", "MutualMute\MutualMute.csproj", "{6E315182-CC9F-4F62-8385-5E26EFA3B98A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BullshitWatcher", "BullshitWatcher\BullshitWatcher.csproj", "{09238300-4583-45C6-A997-025CBDC44C24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LegacyContentMitigation", "LegacyContentMitigation\LegacyContentMitigation.csproj", "{21FDAB94-5014-488D-86C7-A366F1902B24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterContentLoading", "BetterContentLoading\BetterContentLoading.csproj", "{FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CameraExperiments", "CameraExperiments\CameraExperiments.csproj", "{71CBD7CC-C787-4796-B05E-4F3BC3C28B48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteAvatarDisablingCameraOnFirstFrameFix", "RemoteAvatarDisablingCameraOnFirstFrameFix\RemoteAvatarDisablingCameraOnFirstFrameFix.csproj", "{42E626F7-9A7E-4F55-B02C-16EB56E2B540}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarCloneTest", "AvatarCloneTest\AvatarCloneTest.csproj", "{8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckOffUICamera", "FuckOffUICamera\FuckOffUICamera.csproj", "{262A8AE0-E610-405F-B4EC-DB714FB54C00}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomRichPresence", "CustomRichPresence\CustomRichPresence.csproj", "{E5F07862-5715-470D-B324-19BDEBB2AA4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckMagicaCloth2", "FuckMagicaCloth2\FuckMagicaCloth2.csproj", "{21B591A0-F6E5-4645-BF2D-4E71F47394A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperAwesomeMod", "SuperAwesomeMod\SuperAwesomeMod.csproj", "{F093BDE5-1824-459E-B86E-B9F79B548E58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlToUnlockMouse", "ControlToUnlockMouse\ControlToUnlockMouse.csproj", "{EDA96974-0BEA-404B-8EED-F19CCA2C95A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", ".DepricatedMods\EzCurls\EzCurls.csproj", "{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -261,6 +289,62 @@ Global {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.Build.0 = Release|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.Build.0 = Release|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.Build.0 = Release|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.Build.0 = Release|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.Build.0 = Release|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.Build.0 = Release|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.Build.0 = Release|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.Build.0 = Release|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.Build.0 = Release|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.Build.0 = Release|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.Build.0 = Release|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.Build.0 = Release|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.Build.0 = Release|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs b/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs index 543ea43..cd32e4f 100644 --- a/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs +++ b/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs @@ -4,19 +4,19 @@ using BTKUILib.UIObjects; using BTKUILib.UIObjects.Components; using NAK.OriginShift; -namespace NAK.OriginShiftMod.Integrations +namespace NAK.OriginShiftMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon + private static Category _ourCategory; + + private static Button _ourMainButton; + private static bool _isForcedMode; + + private static ToggleButton _ourToggle; + + private static void Setup_OriginShiftModCategory(Page page) { - private static Category _ourCategory; - - private static Button _ourMainButton; - private static bool _isForcedMode; - - private static ToggleButton _ourToggle; - - private static void Setup_OriginShiftModCategory(Page page) - { // dear category _ourCategory = page.AddCategory(ModSettings.OSM_SettingsCategory, ModSettings.ModName, true, true, false); @@ -38,21 +38,21 @@ namespace NAK.OriginShiftMod.Integrations debugToggle.OnValueUpdated += OnDebugToggle; } - #region Category Actions + #region Category Actions - private static void UpdateCategoryModUserCount() - { + private static void UpdateCategoryModUserCount() + { int modUsers = 1; // we are always here :3 int playerCount = CVRPlayerManager.Instance.NetworkPlayers.Count + 1; // +1 for us :3 _ourCategory.CategoryName = $"{ModSettings.OSM_SettingsCategory} ({modUsers}/{playerCount})"; } - #endregion Category Actions + #endregion Category Actions - #region Button Actions + #region Button Actions - private static void SetButtonState(OriginShiftManager.OriginShiftState state) - { + private static void SetButtonState(OriginShiftManager.OriginShiftState state) + { switch (state) { default: @@ -74,8 +74,8 @@ namespace NAK.OriginShiftMod.Integrations } } - private static void OnMainButtonClick() - { + private static void OnMainButtonClick() + { // if active, return as world is using Origin Shift if (OriginShiftManager.Instance.CurrentState is OriginShiftManager.OriginShiftState.Active) @@ -97,19 +97,19 @@ namespace NAK.OriginShiftMod.Integrations } } - private static void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) - { + private static void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) + { _isForcedMode = state == OriginShiftManager.OriginShiftState.Forced; SetButtonState(state); SetToggleLocked(_isForcedMode); } - #endregion Button Actions + #endregion Button Actions - #region Toggle Actions + #region Toggle Actions - private static void SetToggleLocked(bool value) - { + private static void SetToggleLocked(bool value) + { if (value) { // lock the toggle @@ -126,20 +126,19 @@ namespace NAK.OriginShiftMod.Integrations } } - private static void OnCompatibilityModeToggle(bool value) - { + private static void OnCompatibilityModeToggle(bool value) + { ModSettings.EntryCompatibilityMode.Value = value; } - #endregion Toggle Actions + #endregion Toggle Actions - #region Debug Toggle Actions + #region Debug Toggle Actions - private static void OnDebugToggle(bool value) - { + private static void OnDebugToggle(bool value) + { OriginShiftManager.Instance.ToggleDebugOverlay(value); } - #endregion Debug Toggle Actions - } + #endregion Debug Toggle Actions } \ No newline at end of file diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon.cs b/OriginShift/Integrations/BTKUI/BtkuiAddon.cs index aa64098..f980330 100644 --- a/OriginShift/Integrations/BTKUI/BtkuiAddon.cs +++ b/OriginShift/Integrations/BTKUI/BtkuiAddon.cs @@ -3,23 +3,23 @@ using BTKUILib; using BTKUILib.UIObjects; using NAK.OriginShift; -namespace NAK.OriginShiftMod.Integrations -{ - public static partial class BtkUiAddon - { - private static Page _miscTabPage; - private static string _miscTabElementID; +namespace NAK.OriginShiftMod.Integrations; - public static void Initialize() - { +public static partial class BtkUiAddon +{ + private static Page _miscTabPage; + private static string _miscTabElementID; + + public static void Initialize() + { Prepare_Icons(); Setup_OriginShiftTab(); } - #region Initialization + #region Initialization - private static void Prepare_Icons() - { + private static void Prepare_Icons() + { QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Active", GetIconStream("OriginShift-Icon-Active.png")); @@ -30,8 +30,8 @@ namespace NAK.OriginShiftMod.Integrations GetIconStream("OriginShift-Icon-Forced.png")); } - private static void Setup_OriginShiftTab() - { + private static void Setup_OriginShiftTab() + { _miscTabPage = QuickMenuAPI.MiscTabPage; _miscTabElementID = _miscTabPage.ElementID; QuickMenuAPI.UserJoin += OnUserJoinLeave; @@ -51,16 +51,15 @@ namespace NAK.OriginShiftMod.Integrations // Setup_DebugOptionsCategory(_miscTabPage); } - #endregion + #endregion - #region Player Count Display + #region Player Count Display - private static void OnWorldLeave() - => UpdateCategoryModUserCount(); + private static void OnWorldLeave() + => UpdateCategoryModUserCount(); - private static void OnUserJoinLeave(CVRPlayerEntity _) - => UpdateCategoryModUserCount(); + private static void OnUserJoinLeave(CVRPlayerEntity _) + => UpdateCategoryModUserCount(); - #endregion - } -} + #endregion +} \ No newline at end of file diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs b/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs index a5cec73..823d41f 100644 --- a/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs +++ b/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs @@ -5,60 +5,59 @@ using BTKUILib.UIObjects.Components; using MelonLoader; using UnityEngine; -namespace NAK.OriginShiftMod.Integrations +namespace NAK.OriginShiftMod.Integrations; + +public static partial class BtkUiAddon { - public static partial class BtkUiAddon - { - #region Melon Preference Helpers + #region Melon Preference Helpers - private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry entry) - { + private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry entry) + { ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value); toggle.OnValueUpdated += b => entry.Value = b; return toggle; } - private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry entry, float min, - float max, int decimalPlaces = 2, bool allowReset = true) - { + private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry entry, float min, + float max, int decimalPlaces = 2, bool allowReset = true) + { SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); slider.OnValueUpdated += f => entry.Value = f; return slider; } - private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { + private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s); return button; } - private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) - { + private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f); return button; } - private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry entry, bool showHeader = true) - { + private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry entry, bool showHeader = true) + { Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value); category.OnCollapse += b => entry.Value = b; return category; } - #endregion Melon Preference Helpers + #endregion Melon Preference Helpers - #region Icon Utils + #region Icon Utils - private static Stream GetIconStream(string iconName) - { + private static Stream GetIconStream(string iconName) + { Assembly assembly = Assembly.GetExecutingAssembly(); string assemblyName = assembly.GetName().Name; return assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}"); } - #endregion Icon Utils - } + #endregion Icon Utils } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/OriginShiftController.cs b/OriginShift/OriginShift/Components/OriginShiftController.cs index 70784f3..568409a 100644 --- a/OriginShift/OriginShift/Components/OriginShiftController.cs +++ b/OriginShift/OriginShift/Components/OriginShiftController.cs @@ -7,45 +7,45 @@ using NAK.OriginShift.Utility; // Creator Exposed component -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftController : MonoBehaviour { - public class OriginShiftController : MonoBehaviour - { - public static OriginShiftController Instance { get; private set; } + public static OriginShiftController Instance { get; private set; } - #region Serialized Fields + #region Serialized Fields - [Header("Config / Shift Params")] + [Header("Config / Shift Params")] - [SerializeField] private bool _shiftVertical = true; - [SerializeField] [Range(10, 2500)] private int _shiftThreshold = 15; + [SerializeField] private bool _shiftVertical = true; + [SerializeField] [Range(10, 2500)] private int _shiftThreshold = 15; - [Header("Config / Scene Objects")] + [Header("Config / Scene Objects")] - [SerializeField] private bool _autoMoveSceneRoots = true; - [SerializeField] private Transform[] _toShiftTransforms = Array.Empty(); + [SerializeField] private bool _autoMoveSceneRoots = true; + [SerializeField] private Transform[] _toShiftTransforms = Array.Empty(); - [Header("Config / Additive Objects")] + [Header("Config / Additive Objects")] - [SerializeField] private bool _shiftRemotePlayers = true; - [SerializeField] private bool _shiftSpawnedObjects = true; + [SerializeField] private bool _shiftRemotePlayers = true; + [SerializeField] private bool _shiftSpawnedObjects = true; - #endregion Serialized Fields + #endregion Serialized Fields - #region Internal Fields + #region Internal Fields - internal bool IsForced { get; set; } + internal bool IsForced { get; set; } - #endregion Internal Fields + #endregion Internal Fields #if !UNITY_EDITOR - public static int ORIGIN_SHIFT_THRESHOLD = 15; + public static int ORIGIN_SHIFT_THRESHOLD = 15; - #region Unity Events + #region Unity Events - private void Awake() - { + private void Awake() + { if (Instance != null && Instance != this) { @@ -56,8 +56,8 @@ namespace NAK.OriginShift.Components Instance = this; } - private void Start() - { + private void Start() + { // set threshold (we can not support dynamic threshold change) ORIGIN_SHIFT_THRESHOLD = IsForced ? 1000 : _shiftThreshold; @@ -73,26 +73,26 @@ namespace NAK.OriginShift.Components AnchorAllStaticRenderers(); } - private void OnDestroy() - { + private void OnDestroy() + { OriginShiftManager.OnOriginShifted -= OnOriginShifted; OriginShiftManager.Instance.ResetManager(); } - #endregion Unity Events + #endregion Unity Events - #region Private Methods + #region Private Methods - private void GetAllSceneRootTransforms() - { + private void GetAllSceneRootTransforms() + { Scene scene = gameObject.scene; var sceneRoots = scene.GetRootGameObjects(); _toShiftTransforms = new Transform[sceneRoots.Length + 1]; // +1 for the static batch anchor for (var i = 0; i < sceneRoots.Length; i++) _toShiftTransforms[i] = sceneRoots[i].transform; } - private void AnchorAllStaticRenderers() - { + private void AnchorAllStaticRenderers() + { // create an anchor object at 0,0,0 Transform anchor = new GameObject("NAK.StaticBatchAnchor").transform; anchor.SetPositionAndRotation(Vector3.zero, Quaternion.identity); @@ -113,12 +113,12 @@ namespace NAK.OriginShift.Components } } - #endregion Private Methods + #endregion Private Methods - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShifted(Vector3 shift) - { + private void OnOriginShifted(Vector3 shift) + { foreach (Transform toShiftTransform in _toShiftTransforms) { if (toShiftTransform == null) continue; // skip nulls @@ -126,8 +126,7 @@ namespace NAK.OriginShift.Components } } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs index fd329bf..11fe6fc 100644 --- a/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs @@ -1,56 +1,65 @@ -using JetBrains.Annotations; +using ABI.CCK.Components; +using JetBrains.Annotations; using UnityEngine; using UnityEngine.Events; #if !UNITY_EDITOR +using ABI_RC.Core.Util; using ABI_RC.Core.Util.AssetFiltering; #endif -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftEventReceiver : MonoBehaviour { - public class OriginShiftEventReceiver : MonoBehaviour - { - #region Serialized Fields + #region Serialized Fields - [SerializeField] private UnityEvent _onOriginShifted = new(); - [SerializeField] private bool _filterChunkBoundary; - [SerializeField] private Vector3 _chunkBoundaryMin = Vector3.zero; - [SerializeField] private Vector3 _chunkBoundaryMax = Vector3.one; + [SerializeField] private UnityEvent _onOriginShifted = new(); + [SerializeField] private bool _filterChunkBoundary; + [SerializeField] private Vector3 _chunkBoundaryMin = Vector3.zero; + [SerializeField] private Vector3 _chunkBoundaryMax = Vector3.one; - #endregion Serialized Fields + #endregion Serialized Fields #if !UNITY_EDITOR - #region Private Fields + #region Private Fields - private bool _isInitialized; + private bool _isInitialized; - #endregion Private Fields + #endregion Private Fields - #region Unity Events + #region Unity Events - private void OnEnable() - { + private void OnEnable() + { if (!_isInitialized) { - SharedFilter.SanitizeUnityEvents("OriginShiftEventReceiver", _onOriginShifted); + //SharedFilter.SanitizeUnityEvents("OriginShiftEventReceiver", _onOriginShifted); + + UnityEventsHelper.SanitizeUnityEvents("OriginShiftEventReceiver", + UnityEventsHelper.EventSource.Unknown, + this, + CVRWorld.Instance, + _onOriginShifted); + _isInitialized = true; } OriginShiftManager.OnOriginShifted += HandleOriginShifted; } - private void OnDisable() - { + private void OnDisable() + { OriginShiftManager.OnOriginShifted -= HandleOriginShifted; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void HandleOriginShifted(Vector3 shift) - { + private void HandleOriginShifted(Vector3 shift) + { if (_filterChunkBoundary && !IsWithinChunkBoundary(shift)) return; @@ -65,15 +74,14 @@ namespace NAK.OriginShift.Components } } - private bool IsWithinChunkBoundary(Vector3 shift) - { + private bool IsWithinChunkBoundary(Vector3 shift) + { return shift.x >= _chunkBoundaryMin.x && shift.x <= _chunkBoundaryMax.x && shift.y >= _chunkBoundaryMin.y && shift.y <= _chunkBoundaryMax.y && shift.z >= _chunkBoundaryMin.z && shift.z <= _chunkBoundaryMax.z; } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs index 819b351..c5f4c52 100644 --- a/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs @@ -1,20 +1,20 @@ using UnityEngine; -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftParticleSystemReceiver : MonoBehaviour { - public class OriginShiftParticleSystemReceiver : MonoBehaviour - { #if !UNITY_EDITOR - // max particles count cause i said so 2 - private static readonly ParticleSystem.Particle[] _tempParticles = new ParticleSystem.Particle[10000]; + // max particles count cause i said so 2 + private static readonly ParticleSystem.Particle[] _tempParticles = new ParticleSystem.Particle[10000]; - private ParticleSystem[] _particleSystems; + private ParticleSystem[] _particleSystems; - #region Unity Events + #region Unity Events - private void Start() - { + private void Start() + { _particleSystems = GetComponentsInChildren(true); if (_particleSystems.Length == 0) { @@ -23,35 +23,34 @@ namespace NAK.OriginShift.Components } } - private void OnEnable() - { + private void OnEnable() + { OriginShiftManager.OnOriginShifted += OnOriginShifted; } - private void OnDisable() - { + private void OnDisable() + { OriginShiftManager.OnOriginShifted -= OnOriginShifted; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShifted(Vector3 offset) - { + private void OnOriginShifted(Vector3 offset) + { foreach (ParticleSystem particleSystem in _particleSystems) ShiftParticleSystem(particleSystem, offset); } - private static void ShiftParticleSystem(ParticleSystem particleSystem, Vector3 offset) - { + private static void ShiftParticleSystem(ParticleSystem particleSystem, Vector3 offset) + { int particleCount = particleSystem.GetParticles(_tempParticles); for (int i = 0; i < particleCount; i++) _tempParticles[i].position += offset; particleSystem.SetParticles(_tempParticles, particleCount); } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs index ae2ca54..7fdc939 100644 --- a/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs @@ -1,17 +1,17 @@ using UnityEngine; -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftRigidbodyReceiver : MonoBehaviour { - public class OriginShiftRigidbodyReceiver : MonoBehaviour - { #if !UNITY_EDITOR - private Rigidbody _rigidbody; + private Rigidbody _rigidbody; - #region Unity Events + #region Unity Events - private void Start() - { + private void Start() + { _rigidbody = GetComponentInChildren(); if (_rigidbody == null) { @@ -20,27 +20,26 @@ namespace NAK.OriginShift.Components } } - private void OnEnable() - { + private void OnEnable() + { OriginShiftManager.OnOriginShifted += OnOriginShifted; } - private void OnDisable() - { + private void OnDisable() + { OriginShiftManager.OnOriginShifted -= OnOriginShifted; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShifted(Vector3 shift) - { + private void OnOriginShifted(Vector3 shift) + { _rigidbody.position += shift; } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs index da1140c..4c3b34d 100644 --- a/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs @@ -1,20 +1,20 @@ using UnityEngine; -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftTrailRendererReceiver : MonoBehaviour { - public class OriginShiftTrailRendererReceiver : MonoBehaviour - { #if !UNITY_EDITOR - // max positions count cause i said so - private static readonly Vector3[] _tempPositions = new Vector3[10000]; + // max positions count cause i said so + private static readonly Vector3[] _tempPositions = new Vector3[10000]; - private TrailRenderer[] _trailRenderers; + private TrailRenderer[] _trailRenderers; - #region Unity Events + #region Unity Events - private void Start() - { + private void Start() + { _trailRenderers = GetComponentsInChildren(true); if (_trailRenderers.Length == 0) { @@ -23,35 +23,34 @@ namespace NAK.OriginShift.Components } } - private void OnEnable() - { + private void OnEnable() + { OriginShiftManager.OnOriginShifted += OnOriginShifted; } - private void OnDisable() - { + private void OnDisable() + { OriginShiftManager.OnOriginShifted -= OnOriginShifted; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShifted(Vector3 offset) - { + private void OnOriginShifted(Vector3 offset) + { foreach (TrailRenderer trailRenderer in _trailRenderers) ShiftTrailRenderer(trailRenderer, offset); } - private static void ShiftTrailRenderer(TrailRenderer trailRenderer, Vector3 offset) - { + private static void ShiftTrailRenderer(TrailRenderer trailRenderer, Vector3 offset) + { trailRenderer.GetPositions(_tempPositions); for (var i = 0; i < _tempPositions.Length; i++) _tempPositions[i] += offset; trailRenderer.SetPositions(_tempPositions); } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs index 1370e34..1e98ce7 100644 --- a/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs @@ -1,34 +1,33 @@ using UnityEngine; -namespace NAK.OriginShift.Components +namespace NAK.OriginShift.Components; + +public class OriginShiftTransformReceiver : MonoBehaviour { - public class OriginShiftTransformReceiver : MonoBehaviour - { #if !UNITY_EDITOR - #region Unity Events + #region Unity Events - private void OnEnable() - { + private void OnEnable() + { OriginShiftManager.OnOriginShifted += OnOriginShifted; } - private void OnDisable() - { + private void OnDisable() + { OriginShiftManager.OnOriginShifted -= OnOriginShifted; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShifted(Vector3 shift) - { + private void OnOriginShifted(Vector3 shift) + { transform.position += shift; } - #endregion Origin Shift Events + #endregion Origin Shift Events #endif - } } \ No newline at end of file diff --git a/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs b/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs index 2e78a6c..3fb90f1 100644 --- a/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs +++ b/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs @@ -1,16 +1,16 @@ using UnityEngine; -namespace NAK.OriginShift.Hacks -{ - public class OriginShiftOcclusionCullingDisabler : MonoBehaviour - { - private Camera _camera; - private bool _originalCullingState; - - #region Unity Events +namespace NAK.OriginShift.Hacks; - private void Start() - { +public class OriginShiftOcclusionCullingDisabler : MonoBehaviour +{ + private Camera _camera; + private bool _originalCullingState; + + #region Unity Events + + private void Start() + { _camera = GetComponent(); if (_camera == null) { @@ -21,25 +21,24 @@ namespace NAK.OriginShift.Hacks _originalCullingState = _camera.useOcclusionCulling; } - private void Awake() // we want to execute even if the component is disabled - { + private void Awake() // we want to execute even if the component is disabled + { OriginShiftManager.OnStateChanged += OnOriginShiftStateChanged; } - private void OnDestroy() - { + private void OnDestroy() + { OriginShiftManager.OnStateChanged -= OnOriginShiftStateChanged; } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) - { + private void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) + { _camera.useOcclusionCulling = state != OriginShiftManager.OriginShiftState.Forced && _originalCullingState; } - #endregion Origin Shift Events - } + #endregion Origin Shift Events } \ No newline at end of file diff --git a/OriginShift/OriginShift/Player/OriginShiftMonitor.cs b/OriginShift/OriginShift/Player/OriginShiftMonitor.cs index a812e08..f2157a0 100644 --- a/OriginShift/OriginShift/Player/OriginShiftMonitor.cs +++ b/OriginShift/OriginShift/Player/OriginShiftMonitor.cs @@ -9,35 +9,35 @@ using UnityEngine; using ABI_RC.Systems.Movement; #endif -namespace NAK.OriginShift +namespace NAK.OriginShift; + +[DefaultExecutionOrder(int.MaxValue)] +public class OriginShiftMonitor : MonoBehaviour { - [DefaultExecutionOrder(int.MaxValue)] - public class OriginShiftMonitor : MonoBehaviour - { #if !UNITY_EDITOR - private PlayerSetup _playerSetup; - private BetterBetterCharacterController _characterController; + private PlayerSetup _playerSetup; + private BetterBetterCharacterController _characterController; #endif - #region Unity Events + #region Unity Events - private void Start() - { + private void Start() + { #if !UNITY_EDITOR - _playerSetup = GetComponent(); - _characterController = GetComponent(); + _playerSetup = GetComponent(); + _characterController = GetComponent(); #endif - OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted; - } + OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted; + } - private void OnDestroy() - { + private void OnDestroy() + { OriginShiftManager.OnPostOriginShifted -= OnPostOriginShifted; StopAllCoroutines(); } - private void Update() - { + private void Update() + { // in CVR use GetPlayerPosition to account for VR offset Vector3 position = PlayerSetup.Instance.GetPlayerPosition(); @@ -56,21 +56,20 @@ namespace NAK.OriginShift OriginShiftManager.Instance.ShiftOrigin(position); } - #endregion Unity Events + #endregion Unity Events - #region Origin Shift Events + #region Origin Shift Events - private void OnPostOriginShifted(Vector3 shift) - { + private void OnPostOriginShifted(Vector3 shift) + { #if UNITY_EDITOR // shift our transform back transform.position += shift; #else - _characterController.OffsetBy(shift); - _playerSetup.OffsetAvatarMovementData(shift); + _characterController.OffsetBy(shift); + _playerSetup.OffsetAvatarMovementData(shift); #endif - } - - #endregion Origin Shift Events } + + #endregion Origin Shift Events } \ No newline at end of file diff --git a/OriginShift/Patches.cs b/OriginShift/Patches.cs index 8e17cb0..cd61791 100644 --- a/OriginShift/Patches.cs +++ b/OriginShift/Patches.cs @@ -10,6 +10,7 @@ using ABI_RC.Systems.GameEventSystem; using ABI_RC.Systems.Movement; using ABI.CCK.Components; using DarkRift; +using ECM2; using HarmonyLib; using NAK.OriginShift.Components; using NAK.OriginShift.Hacks; diff --git a/PhysicsGunMod/Components/ObjectSyncBridge.cs b/PhysicsGunMod/Components/ObjectSyncBridge.cs deleted file mode 100644 index fe3c563..0000000 --- a/PhysicsGunMod/Components/ObjectSyncBridge.cs +++ /dev/null @@ -1,92 +0,0 @@ -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using ABI.CCK.Components; -using UnityEngine; - -namespace NAK.PhysicsGunMod.Components; - -public class ObjectSyncBridge : MonoBehaviour -{ - private PhysicsGunInteractionBehavior _physicsGun; - - private void Start() - { - // find physics gun - if (!TryGetComponent(out _physicsGun)) - { - PhysicsGunMod.Logger.Msg("Failed to find physics gun!"); - Destroy(this); - return; - } - - // listen for events - _physicsGun.OnPreGrabbedObject = o => - { - bool canTakeOwnership = false; - - // - CVRObjectSync objectSync = o.GetComponentInParent(); - // if (objectSync != null - // && (objectSync.SyncType == 0 // check if physics synced or synced by us - // || objectSync.SyncedByMe)) - // canTakeOwnership = true; - // - CVRSpawnable spawnable = o.GetComponentInParent(); - // if (spawnable != null) - // { - // CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId); - // if (propData != null - // && (propData.syncType == 0 // check if physics synced or synced by us - // || propData.syncedBy == MetaPort.Instance.ownerId)) - // canTakeOwnership = true; - // } - // - CVRPickupObject pickup = o.GetComponentInParent(); - // if (pickup != null - // && (pickup.grabbedBy == MetaPort.Instance.ownerId // check if already grabbed by us - // || pickup.grabbedBy == "" || !pickup.disallowTheft)) // check if not grabbed or allows theft - // canTakeOwnership = true; - // - // if (!canTakeOwnership // if we can't take ownership, don't grab, unless there is no syncing at all (local object) - // && (objectSync || spawnable || pickup )) - // return false; - - if (pickup) - { - pickup.GrabbedBy = MetaPort.Instance.ownerId; - pickup._grabStartTime = Time.time; - } - if (spawnable) spawnable.isPhysicsSynced = true; - if (objectSync) objectSync.isPhysicsSynced = true; - - return true; - }; - - _physicsGun.OnObjectReleased = o => - { - // CVRObjectSync objectSync = o.GetComponentInParent(); - // if (objectSync != null && objectSync.SyncType == 0) - // objectSync.isPhysicsSynced = false; - // - // CVRSpawnable spawnable = o.GetComponentInParent(); - // if (spawnable != null) - // { - // CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == spawnable.instanceId); - // if (propData != null && (propData.syncType == 0 || propData.syncedBy == MetaPort.Instance.ownerId)) - // spawnable.isPhysicsSynced = false; - // } - - // CVRPickupObject pickup = o.GetComponentInParent(); - // if (pickup != null && pickup.grabbedBy == MetaPort.Instance.ownerId) - // pickup.grabbedBy = ""; - - return false; - }; - } - - private void OnDestroy() - { - // stop listening for events - _physicsGun.OnObjectGrabbed = null; - } -} \ No newline at end of file diff --git a/PhysicsGunMod/Components/PhysicsGunInteractionBehavior.cs b/PhysicsGunMod/Components/PhysicsGunInteractionBehavior.cs deleted file mode 100644 index bce3321..0000000 --- a/PhysicsGunMod/Components/PhysicsGunInteractionBehavior.cs +++ /dev/null @@ -1,572 +0,0 @@ -using ABI_RC.Core.Player; -using ABI_RC.Systems.InputManagement; -using ABI.CCK.Attributes; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.Scripting.APIUpdating; - -/* - * Physics Gun script from repository - https://github.com/Laumania/Unity3d-PhysicsGun - * Created by: - * Mads Laumann, https://github.com/laumania - * WarmedxMints, https://github.com/WarmedxMints - * - * Original/initial script "Gravity Gun": https://pastebin.com/w1G8m3dH - * Original author: Jake Perry, reddit.com/user/nandos13 -*/ - -namespace NAK.PhysicsGunMod.Components; - -[CCKWhitelistComponent(spawnable: true)] -public class PhysicsGunInteractionBehavior : MonoBehaviour -{ - public static PhysicsGunInteractionBehavior Instance; - - [Header("LayerMask")] [Tooltip("The layer which the gun can grab objects from")] [SerializeField] - private LayerMask _grabLayer; - - [SerializeField] private Camera _camera; - - [Header("Input Setting")] [Space(10)] public KeyCode Rotate = KeyCode.R; - public KeyCode SnapRotation = KeyCode.LeftShift; - public KeyCode SwitchAxis = KeyCode.Tab; - public KeyCode RotateZ = KeyCode.Space; - public KeyCode RotationSpeedIncrease = KeyCode.LeftControl; - public KeyCode ResetRotation = KeyCode.LeftAlt; - - /// The rigidbody we are currently holding - private Rigidbody _grabbedRigidbody; - - /// The offset vector from the object's position to hit point, in local space - private Vector3 _hitOffsetLocal; - - /// The distance we are holding the object at - private float _currentGrabDistance; - - /// The interpolation state when first grabbed - private RigidbodyInterpolation _initialInterpolationSetting; - - /// The difference between player & object rotation, updated when picked up or when rotated by the player - private Quaternion _rotationDifference; - - /// The start point for the Laser. This will typically be on the end of the gun - [SerializeField] private Transform _laserStartPoint; - - /// Tracks player input to rotate current object. Used and reset every fixedupdate call - private Vector3 _rotationInput = Vector3.zero; - - [Header("Rotation Settings")] [Tooltip("Transform of the player, that rotations should be relative to")] - public Transform PlayerTransform; - - [SerializeField] private float _rotationSenstivity = 10f; - - public float SnapRotationDegrees = 45f; - [SerializeField] private float _snappedRotationSens = 25f; - [SerializeField] private float _rotationSpeed = 10f; - - private Quaternion _desiredRotation = Quaternion.identity; - - [SerializeField] [Tooltip("Input values above this will be considered and intentional change in rotation")] - private float _rotationTollerance = 0.8f; - - private bool m_UserRotation; - - public bool UserRotation - { - get => m_UserRotation; - private set - { - if (m_UserRotation == value) - return; - - m_UserRotation = value; - OnRotation?.Invoke(value); - } - } - - private bool m_SnapRotation; - - private bool _snapRotation - { - get => m_SnapRotation; - set - { - if (m_SnapRotation == value) - return; - - m_SnapRotation = value; - OnRotationSnapped?.Invoke(value); - } - } - - private bool m_RotationAxis; - - private bool _rotationAxis - { - get => m_RotationAxis; - set - { - if (m_RotationAxis == value) - return; - - m_RotationAxis = value; - OnAxisChanged?.Invoke(value); - } - } - - private Vector3 _lockedRot; - - private Vector3 _forward; - private Vector3 _up; - private Vector3 _right; - - //ScrollWheel ObjectMovement - private Vector3 _scrollWheelInput = Vector3.zero; - - [Header("Scroll Wheel Object Movement")] [Space(5)] [SerializeField] - private float _scrollWheelSensitivity = 5f; - - [SerializeField] [Tooltip("The min distance the object can be from the player")] - private float _minObjectDistance = 2.5f; - - /// The maximum distance at which a new object can be picked up - [SerializeField] [Tooltip("The maximum distance at which a new object can be picked up")] - private float _maxGrabDistance = 50f; - - private bool _distanceChanged; - - //Vector3.Zero and Vector2.zero create a new Vector3 each time they are called so these simply save that process and a small amount of cpu runtime. - private readonly Vector3 _zeroVector3 = Vector3.zero; - private readonly Vector3 _oneVector3 = Vector3.one; - private readonly Vector3 _zeroVector2 = Vector2.zero; - - private bool _justReleased; - private bool _wasKinematic; - - [Serializable] - public class BoolEvent : UnityEvent {} - - [Serializable] - public class GrabEvent : UnityEvent {} - - [Header("Events")] [Space(10)] - public BoolEvent OnRotation; - public BoolEvent OnRotationSnapped; - public BoolEvent OnAxisChanged; - - public GrabEvent OnObjectGrabbed; - public Func OnObjectReleased; - - // pre event for OnPreGrabbedObject, return false to cancel grab - public Func OnPreGrabbedObject; - // public Action OnGrabbedObject; - - //public properties for the Axis Arrows. These are optional and can be safely removed - // public Vector3 CurrentForward => _forward; - // public Vector3 CurrentUp => _up; - // public Vector3 CurrentRight => _right; - - /// The transfor of the rigidbody we are holding - public Transform CurrentGrabbedTransform { get; private set; } - - //public properties for the Line Renderer - public Vector3 StartPoint { get; private set; } - public Vector3 MidPoint { get; private set; } - public Vector3 EndPoint { get; private set; } - - private void Start() - { - if (Instance != null - && Instance != this) - { - Destroy(this); - return; - } - Instance = this; - gameObject.AddComponent(); - - _camera = PlayerSetup.Instance.GetActiveCamera().GetComponent(); - PlayerTransform = PlayerSetup.Instance.transform; // TODO: this might be fucked in VR - } - - private void OnDestroy() - { - if (Instance == this) - Instance = null; - } - - private void Update() - { - if (!Input.GetMouseButton(0)) - { - // We are not holding the mouse button. Release the object and return before checking for a new one - if (_grabbedRigidbody != null) ReleaseObject(); - - _justReleased = false; - return; - } - - if (_grabbedRigidbody == null && !_justReleased) - { - // We are not holding an object, look for one to pick up - Ray ray = CenterRay(); - RaycastHit hit; - - //Just so These aren't included in a build -#if UNITY_EDITOR - Debug.DrawRay(ray.origin, ray.direction * _maxGrabDistance, Color.blue, 0.01f); -#endif - if (Physics.Raycast(ray, out hit, _maxGrabDistance, _grabLayer)) - // Don't pick up kinematic rigidbodies (they can't move) - if (hit.rigidbody != null /*&& !hit.rigidbody.isKinematic*/) - { - // Check if we are allowed to pick up this object - if (OnPreGrabbedObject != null - && !OnPreGrabbedObject(hit.rigidbody.gameObject)) - return; - - // Track rigidbody's initial information - _grabbedRigidbody = hit.rigidbody; - _wasKinematic = _grabbedRigidbody.isKinematic; - _grabbedRigidbody.isKinematic = false; - _grabbedRigidbody.freezeRotation = true; - _initialInterpolationSetting = _grabbedRigidbody.interpolation; - _rotationDifference = Quaternion.Inverse(PlayerTransform.rotation) * _grabbedRigidbody.rotation; - _hitOffsetLocal = hit.transform.InverseTransformVector(hit.point - hit.transform.position); - _currentGrabDistance = hit.distance; // Vector3.Distance(ray.origin, hit.point); - CurrentGrabbedTransform = _grabbedRigidbody.transform; - // Set rigidbody's interpolation for proper collision detection when being moved by the player - _grabbedRigidbody.interpolation = RigidbodyInterpolation.Interpolate; - - OnObjectGrabbed?.Invoke(_grabbedRigidbody.gameObject); - -#if UNITY_EDITOR - Debug.DrawRay(hit.point, hit.normal * 10f, Color.red, 10f); -#endif - } - } - else if (_grabbedRigidbody != null) - { - UserRotation = Input.GetKey(Rotate); - - if (Input.GetKeyDown(Rotate)) - _desiredRotation = _grabbedRigidbody.rotation; - - if (Input.GetKey(ResetRotation)) - { - _desiredRotation = Quaternion.identity; - } - - // We are already holding an object, listen for rotation input - if (Input.GetKey(Rotate)) - { - var rotateZ = Input.GetKey(RotateZ); - - var increaseSens = Input.GetKey(RotationSpeedIncrease) ? 2.5f : 1f; - - if (Input.GetKeyDown(SwitchAxis)) - { - _rotationAxis = !_rotationAxis; - - OnAxisChanged?.Invoke(_rotationAxis); - } - - //Snap Object nearest _snapRotationDegrees - if (Input.GetKeyDown(SnapRotation)) - { - _snapRotation = true; - - Vector3 newRot = _grabbedRigidbody.transform.rotation.eulerAngles; - - newRot.x = Mathf.Round(newRot.x / SnapRotationDegrees) * SnapRotationDegrees; - newRot.y = Mathf.Round(newRot.y / SnapRotationDegrees) * SnapRotationDegrees; - newRot.z = Mathf.Round(newRot.z / SnapRotationDegrees) * SnapRotationDegrees; - - Quaternion rot = Quaternion.Euler(newRot); - - _desiredRotation = rot; - //_grabbedRigidbody.MoveRotation(rot); - } - else if (Input.GetKeyUp(SnapRotation)) - { - _snapRotation = false; - } - - var x = Input.GetAxisRaw("Mouse X"); - var y = Input.GetAxisRaw("Mouse Y"); - - if (Mathf.Abs(x) > _rotationTollerance) - { - _rotationInput.x = rotateZ ? 0f : x * _rotationSenstivity * increaseSens; - _rotationInput.z = rotateZ ? x * _rotationSenstivity * increaseSens : 0f; - } - - if (Mathf.Abs(y) > _rotationTollerance) _rotationInput.y = y * _rotationSenstivity * increaseSens; - } - else - { - _snapRotation = false; - } - - var direction = Input.GetAxis("Mouse ScrollWheel"); - - //Optional Keyboard inputs - if (Input.GetKeyDown(KeyCode.T)) - direction = -0.1f; - else if (Input.GetKeyDown(KeyCode.G)) - direction = 0.1f; - - if (Mathf.Abs(direction) > 0 && CheckObjectDistance(direction)) - { - _distanceChanged = true; - _scrollWheelInput = PlayerTransform.forward * (_scrollWheelSensitivity * direction); - } - else - { - _scrollWheelInput = _zeroVector3; - } - - if (Input.GetMouseButtonDown(1)) - { - //To prevent warnings in the inpector - _grabbedRigidbody.collisionDetectionMode = !_wasKinematic - ? CollisionDetectionMode.ContinuousSpeculative - : CollisionDetectionMode.Continuous; - _grabbedRigidbody.isKinematic = _wasKinematic = !_wasKinematic; - - _justReleased = true; - ReleaseObject(); - } - } - } - - private void FixedUpdate() - { - if (_grabbedRigidbody) - { - // We are holding an object, time to rotate & move it - Ray ray = CenterRay(); - - UpdateRotationAxis(); - -#if UNITY_EDITOR - Debug.DrawRay(_grabbedTransform.position, _up * 5f , Color.green); - Debug.DrawRay(_grabbedTransform.position, _right * 5f , Color.red); - Debug.DrawRay(_grabbedTransform.position, _forward * 5f , Color.blue); -#endif - // Apply any intentional rotation input made by the player & clear tracked input - Quaternion intentionalRotation = Quaternion.AngleAxis(_rotationInput.z, _forward) * - Quaternion.AngleAxis(_rotationInput.y, _right) * - Quaternion.AngleAxis(-_rotationInput.x, _up) * _desiredRotation; - Quaternion relativeToPlayerRotation = PlayerTransform.rotation * _rotationDifference; - - if (UserRotation && _snapRotation) - { - //Add mouse movement to vector so we can measure the amount of movement - _lockedRot += _rotationInput; - - //If the mouse has moved far enough to rotate the snapped object - if (Mathf.Abs(_lockedRot.x) > _snappedRotationSens || Mathf.Abs(_lockedRot.y) > _snappedRotationSens || - Mathf.Abs(_lockedRot.z) > _snappedRotationSens) - { - for (var i = 0; i < 3; i++) - if (_lockedRot[i] > _snappedRotationSens) - _lockedRot[i] += SnapRotationDegrees; - else if (_lockedRot[i] < -_snappedRotationSens) - _lockedRot[i] += -SnapRotationDegrees; - else - _lockedRot[i] = 0; - - Quaternion q = Quaternion.AngleAxis(-_lockedRot.x, _up) * - Quaternion.AngleAxis(_lockedRot.y, _right) * - Quaternion.AngleAxis(_lockedRot.z, _forward) * _desiredRotation; - - Vector3 newRot = q.eulerAngles; - - newRot.x = Mathf.Round(newRot.x / SnapRotationDegrees) * SnapRotationDegrees; - newRot.y = Mathf.Round(newRot.y / SnapRotationDegrees) * SnapRotationDegrees; - newRot.z = Mathf.Round(newRot.z / SnapRotationDegrees) * SnapRotationDegrees; - - _desiredRotation = Quaternion.Euler(newRot); - - _lockedRot = _zeroVector2; - } - } - else - { - //Rotate the object to remain consistent with any changes in player's rotation - _desiredRotation = UserRotation ? intentionalRotation : relativeToPlayerRotation; - } - - // Remove all torque, reset rotation input & store the rotation difference for next FixedUpdate call - _grabbedRigidbody.angularVelocity = _zeroVector3; - _rotationInput = _zeroVector2; - _rotationDifference = Quaternion.Inverse(PlayerTransform.rotation) * _desiredRotation; - - // Calculate object's center position based on the offset we stored - // NOTE: We need to convert the local-space point back to world coordinates - // Get the destination point for the point on the object we grabbed - Vector3 holdPoint = ray.GetPoint(_currentGrabDistance) + _scrollWheelInput; - Vector3 centerDestination = holdPoint - CurrentGrabbedTransform.TransformVector(_hitOffsetLocal); - -#if UNITY_EDITOR - Debug.DrawLine(ray.origin, holdPoint, Color.blue, Time.fixedDeltaTime); -#endif - // Find vector from current position to destination - Vector3 toDestination = centerDestination - CurrentGrabbedTransform.position; - - // Calculate force - Vector3 force = toDestination / Time.fixedDeltaTime * 0.3f/* / _grabbedRigidbody.mass*/; - - //force += _scrollWheelInput; - // Remove any existing velocity and add force to move to final position - _grabbedRigidbody.velocity = _zeroVector3; - _grabbedRigidbody.AddForce(force, ForceMode.VelocityChange); - - //Rotate object - RotateGrabbedObject(); - - //We need to recalculte the grabbed distance as the object distance from the player has been changed - if (_distanceChanged) - { - _distanceChanged = false; - _currentGrabDistance = Vector3.Distance(ray.origin, holdPoint); - } - - //Update public properties - StartPoint = _laserStartPoint.transform.position; - MidPoint = holdPoint; - EndPoint = CurrentGrabbedTransform.TransformPoint(_hitOffsetLocal); - } - } - - private void RotateGrabbedObject() - { - if (_grabbedRigidbody == null) - return; - - // lerp to desired rotation - _grabbedRigidbody.MoveRotation(Quaternion.Lerp(_grabbedRigidbody.rotation, _desiredRotation, - Time.fixedDeltaTime * _rotationSpeed)); - } - - //Update Rotation axis based on movement - private void UpdateRotationAxis() - { - if (!_snapRotation) - { - _forward = PlayerTransform.forward; - _right = PlayerTransform.right; - _up = PlayerTransform.up; - - return; - } - - if (_rotationAxis) - { - _forward = CurrentGrabbedTransform.forward; - _right = CurrentGrabbedTransform.right; - _up = CurrentGrabbedTransform.up; - - return; - } - - NearestTranformDirection(CurrentGrabbedTransform, PlayerTransform, ref _up, ref _forward, ref _right); - } - - private void NearestTranformDirection(Transform transformToCheck, Transform referenceTransform, ref Vector3 up, - ref Vector3 forward, ref Vector3 right) - { - var directions = new List - { - transformToCheck.forward, - -transformToCheck.forward, - transformToCheck.up, - -transformToCheck.up, - transformToCheck.right, - -transformToCheck.right - }; - - //Find the up Vector - up = GetDirectionVector(directions, referenceTransform.up); - //Remove Vectors from list to prevent duplicates and the opposite vector being found in case where the player is at around a 45 degree angle to the object - directions.Remove(up); - directions.Remove(-up); - //Find the Forward Vector - forward = GetDirectionVector(directions, referenceTransform.forward); - //Remove used directions - directions.Remove(forward); - directions.Remove(-forward); - - right = GetDirectionVector(directions, referenceTransform.right); - } - - private Vector3 GetDirectionVector(List directions, Vector3 direction) - { - var maxDot = -Mathf.Infinity; - Vector3 ret = Vector3.zero; - - for (var i = 0; i < directions.Count; i++) - { - var dot = Vector3.Dot(direction, directions[i]); - - if (dot > maxDot) - { - ret = directions[i]; - maxDot = dot; - } - } - - return ret; - } - - /// Ray from center of the main camera's viewport forward - private Ray CenterRay() - { - return _camera.ViewportPointToRay(_oneVector3 * 0.5f); - } - - //Check distance is within range when moving object with the scroll wheel - private bool CheckObjectDistance(float direction) - { - Vector3 pointA = PlayerTransform.position; - Vector3 pointB = _grabbedRigidbody.position; - - var distance = Vector3.Distance(pointA, pointB); - - if (direction > 0) - return distance <= _maxGrabDistance; - - if (direction < 0) - return distance >= _minObjectDistance; - - return false; - } - - private void ReleaseObject() - { - if (OnObjectReleased != null) - OnObjectReleased(_grabbedRigidbody.gameObject); - - if (_grabbedRigidbody) - { - //Move rotation to desired rotation in case the lerp hasn't finished - //_grabbedRigidbody.MoveRotation(_desiredRotation); - // Reset the rigidbody to how it was before we grabbed it - _grabbedRigidbody.isKinematic = _wasKinematic; - _grabbedRigidbody.interpolation = _initialInterpolationSetting; - _grabbedRigidbody.freezeRotation = false; - _grabbedRigidbody = null; - } - - _scrollWheelInput = _zeroVector3; - CurrentGrabbedTransform = null; - UserRotation = false; - _snapRotation = false; - StartPoint = _zeroVector3; - MidPoint = _zeroVector3; - EndPoint = _zeroVector3; - - OnObjectGrabbed?.Invoke(null); - } -} \ No newline at end of file diff --git a/PhysicsGunMod/HarmonyPatches.cs b/PhysicsGunMod/HarmonyPatches.cs deleted file mode 100644 index 73a618c..0000000 --- a/PhysicsGunMod/HarmonyPatches.cs +++ /dev/null @@ -1,20 +0,0 @@ -using ABI_RC.Systems.InputManagement; -using HarmonyLib; -using NAK.PhysicsGunMod.Components; -using UnityEngine; - -namespace NAK.PhysicsGunMod.HarmonyPatches; - -internal static class CVRInputManagerPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRInputManager), nameof(CVRInputManager.Update))] - private static void Postfix_CVRInputManager_Update(ref CVRInputManager __instance) - { - if (PhysicsGunInteractionBehavior.Instance == null) - return; - - if (PhysicsGunInteractionBehavior.Instance.UserRotation) - __instance.lookVector = Vector2.zero; - } -} diff --git a/PhysicsGunMod/Main.cs b/PhysicsGunMod/Main.cs deleted file mode 100644 index b431ca1..0000000 --- a/PhysicsGunMod/Main.cs +++ /dev/null @@ -1,43 +0,0 @@ -using ABI_RC.Core.Util.AssetFiltering; -using MelonLoader; -using NAK.PhysicsGunMod.Components; -using NAK.PhysicsGunMod.HarmonyPatches; - -namespace NAK.PhysicsGunMod; - -public class PhysicsGunMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - // // add to prop whitelist - // //SharedFilter._spawnableWhitelist.Add(typeof(PhysicsGunInteractionBehavior)); - // - // // add to event whitelist - // SharedFilter._allowedEventComponents.Add(typeof(PhysicsGunInteractionBehavior)); - // SharedFilter._allowedEventFunctions.Add(typeof(PhysicsGunInteractionBehavior), new List - // { - // "set_enabled", - // // TODO: expose more methods like release ? - // }); - - // apply patches - ApplyPatches(typeof(CVRInputManagerPatches)); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } -} \ No newline at end of file diff --git a/PhysicsGunMod/ModSettings.cs b/PhysicsGunMod/ModSettings.cs deleted file mode 100644 index 86e4b48..0000000 --- a/PhysicsGunMod/ModSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MelonLoader; - -namespace NAK.PhysicsGunMod; - -internal static class ModSettings -{ - internal const string ModName = nameof(PhysicsGunMod); - internal const string ASM_SettingsCategory = "Physics Gun Mod"; - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); -} \ No newline at end of file diff --git a/PhysicsGunMod/PhysicsGunMod.csproj b/PhysicsGunMod/PhysicsGunMod.csproj deleted file mode 100644 index 22588f8..0000000 --- a/PhysicsGunMod/PhysicsGunMod.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - - netstandard2.1 - - - - - - - - - - - - - - - - - - - - - - - $(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll - False - - - $(MsBuildThisFileDirectory)\..\.ManagedLibs\ActionMenu.dll - False - - - - \ No newline at end of file diff --git a/PhysicsGunMod/README.md b/PhysicsGunMod/README.md deleted file mode 100644 index eca47c9..0000000 --- a/PhysicsGunMod/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# AvatarScaleMod - -Proof of concept mod to add Avatar Scaling to any avatar. This is local-only, but I may toy with using Mod Network. - -Legit threw this together in three hours. ChilloutVR handles all the hard stuff already with its existing animation-clip-based Avatar Scaling. - -https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/7405cef5-fd68-4103-8c18-b3164029eab1 - -## Notes: -* Constraint scaling partially conflicts with avatars run through my [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool). -* This is local-only, at least unless I bother with Mod Network. -* The entire thing is pretty messy and I am unsure of the performance impact, especially with scaling all lights, audio, & constraints. - -## Relevant Feedback Posts: -https://feedback.abinteractive.net/p/built-in-avatar-scaling-system - -This mod is me creating the system I wanted when I wrote the above feedback post. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/PhysicsGunMod/format.json b/PhysicsGunMod/format.json deleted file mode 100644 index b1de45e..0000000 --- a/PhysicsGunMod/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 126, - "name": "BetterCalibration", - "modversion": "1.0.0", - "gameversion": "2022r173", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Mod to improve the calibration process for FBT.", - "searchtags": [ - "IK", - "FBT", - "VRIK", - "calibration", - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r3/BetterCalibration.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BetterCalibration/", - "changelog": "", - "embedcolor": "9b59b6" -} \ No newline at end of file diff --git a/PlayerColorsAPI/Main.cs b/PlayerColorsAPI/Main.cs new file mode 100644 index 0000000..9e5634d --- /dev/null +++ b/PlayerColorsAPI/Main.cs @@ -0,0 +1,44 @@ +using System; +using MelonLoader; + +namespace NAK.VisualCloneFix; + +public class VisualCloneFixMod : MelonMod +{ + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(VisualCloneFix)); + + internal static readonly MelonPreferences_Entry EntryUseVisualClone = + Category.CreateEntry("use_visual_clone", true, + "Use Visual Clone", description: "Uses the potentially faster Visual Clone setup for the local avatar."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason? + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/VisualCloneFix/Patches.cs b/PlayerColorsAPI/Patches.cs similarity index 100% rename from VisualCloneFix/Patches.cs rename to PlayerColorsAPI/Patches.cs diff --git a/PlayerColorsAPI/PlayerColorsAPI.csproj b/PlayerColorsAPI/PlayerColorsAPI.csproj new file mode 100644 index 0000000..8dc4bcd --- /dev/null +++ b/PlayerColorsAPI/PlayerColorsAPI.csproj @@ -0,0 +1,6 @@ + + + + LocalCloneFix + + diff --git a/PlayerColorsAPI/Properties/AssemblyInfo.cs b/PlayerColorsAPI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..10b75bb --- /dev/null +++ b/PlayerColorsAPI/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.VisualCloneFix.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.VisualCloneFix))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.VisualCloneFix))] + +[assembly: MelonInfo( + typeof(NAK.VisualCloneFix.VisualCloneFixMod), + nameof(NAK.VisualCloneFix), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.VisualCloneFix.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.1"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/PlayerColorsAPI/README.md b/PlayerColorsAPI/README.md new file mode 100644 index 0000000..cc12a9c --- /dev/null +++ b/PlayerColorsAPI/README.md @@ -0,0 +1,18 @@ +# VisualCloneFix + +Fixes the Visual Clone system and allows you to use it again. + +Using the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load. + +**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/PlayerColorsAPI/format.json b/PlayerColorsAPI/format.json new file mode 100644 index 0000000..6634e9f --- /dev/null +++ b/PlayerColorsAPI/format.json @@ -0,0 +1,23 @@ +{ + "_id": 221, + "name": "VisualCloneFix", + "modversion": "1.0.1", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes the Visual Clone system and allows you to use it again.\n\nUsing the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.\n\n**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.", + "searchtags": [ + "visual", + "clone", + "head", + "hiding" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/", + "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/Portals/format.json b/Portals/format.json deleted file mode 100644 index d3c7457..0000000 --- a/Portals/format.json +++ /dev/null @@ -1 +0,0 @@ - "None" diff --git a/References.Items.props b/References.Items.props index b618151..275c721 100644 --- a/References.Items.props +++ b/References.Items.props @@ -196,6 +196,18 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.dll + False + + + $(MsBuildThisFileDirectory)\.ManagedLibs\Pico.Spatializer.Example.dll + False + + + $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.TobSupport.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\PWCommon3DLL.dll False @@ -396,6 +408,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InputSystem.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InputSystem.ForUI.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InternalAPIEngineBridge.002.dll False @@ -584,6 +600,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Hands.Samples.VisualizerSample.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Interaction.Toolkit.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.Management.dll False @@ -616,16 +636,12 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.OculusQuestSupport.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.PICOSupport.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXR.Features.RuntimeDebugger.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.OpenXRPico.dll + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.XR.PICO.dll False diff --git a/SmootherRay/Main.cs b/SmootherRay/Main.cs index b1b1056..eb6c36a 100644 --- a/SmootherRay/Main.cs +++ b/SmootherRay/Main.cs @@ -39,6 +39,10 @@ public class SmootherRayMod : MelonMod Category.CreateEntry("Small Angle Threshold (6f)", 6f, description: "Angle difference to consider a 'small' movement. The less shaky your hands are, the lower you probably want to set this. This is probably the primary value you want to tweak. Use the slider to adjust the threshold angle. Range: 4 to 15."); + public static readonly MelonPreferences_Entry EntrySmoothWhenHoldingPickup = + Category.CreateEntry("Smooth When Holding Pickup", false, + description: "Enable or disable smoothing when holding a pickup."); + #endregion Melon Preferences #region Melon Events diff --git a/SmootherRay/Properties/AssemblyInfo.cs b/SmootherRay/Properties/AssemblyInfo.cs index 57dbe09..d7f1704 100644 --- a/SmootherRay/Properties/AssemblyInfo.cs +++ b/SmootherRay/Properties/AssemblyInfo.cs @@ -28,6 +28,6 @@ namespace NAK.SmootherRay.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.5"; + public const string Version = "1.0.6"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/SmootherRay/SmootherRayer.cs b/SmootherRay/SmootherRayer.cs index 44ef320..6262824 100644 --- a/SmootherRay/SmootherRayer.cs +++ b/SmootherRay/SmootherRayer.cs @@ -227,6 +227,10 @@ public class SmootherRayer : MonoBehaviour } } + if (SmootherRayMod.EntrySmoothWhenHoldingPickup.Value + && ray.grabbedObject) + canSmoothRay = true; + return canSmoothRay; } diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index e769006..57b355c 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -1,4 +1,5 @@ using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Systems.ModNetwork; using NAK.Stickers.Utilities; @@ -47,6 +48,9 @@ public static partial class ModNetwork if (StickerSystem.Instance.IsRestrictedInstance) // ignore messages from users when the world is restricted. This also includes older or modified version of Stickers mod. return false; + if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(sender, out PuppetMaster _)) + return false; // ignore messages from players that don't exist + return true; } @@ -108,7 +112,10 @@ public static partial class ModNetwork msg.Read(out Vector3 up); if (!StickerSystem.Instance.HasTextureHash(msg.Sender, textureHash)) + { SendRequestTexture(stickerSlot, textureHash); + StickerSystem.Instance.ClearStickersForPlayer(msg.Sender, stickerSlot); // Ensure no exploit + } StickerSystem.Instance.OnStickerPlaceReceived(msg.Sender, stickerSlot, position, forward, up); } @@ -158,7 +165,7 @@ public static partial class ModNetwork LoggerInbound($"Received StartTexture message from {sender}: Slot: {stickerSlot}, Hash: {textureHash}, Chunks: {chunkCount}, Resolution: {width}x{height}"); } - + private static void HandleSendTexture(ModNetworkMessage msg) { string sender = msg.Sender; diff --git a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs index e8ab380..f2b5dd3 100644 --- a/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs +++ b/Stickers/Stickers/StickerSystem.PlayerCallbacks.cs @@ -62,7 +62,7 @@ public partial class StickerSystem #endregion Player Callbacks - #region Player Callbacks + #region Sticker Callbacks public void OnStickerPlaceReceived(string playerId, int stickerSlot, Vector3 position, Vector3 forward, Vector3 up) => AttemptPlaceSticker(playerId, position, forward, up, alignWithNormal: true, stickerSlot); @@ -81,5 +81,5 @@ public partial class StickerSystem stickerData.IdentifyTime = Time.time + 3f; } - #endregion Player Callbacks + #endregion Sticker Callbacks } diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index df4ee96..5f350dd 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -110,7 +110,7 @@ public partial class StickerSystem stickerData.Clear(); } - private void ClearStickersForPlayer(string playerId, int stickerSlot) + public void ClearStickersForPlayer(string playerId, int stickerSlot) { if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) return; diff --git a/ThirdPerson/CameraLogic.cs b/ThirdPerson/CameraLogic.cs index 24d10c5..f1b83ef 100644 --- a/ThirdPerson/CameraLogic.cs +++ b/ThirdPerson/CameraLogic.cs @@ -39,7 +39,6 @@ internal static class CameraLogic if (_state) _storedCamMask = _desktopCam.cullingMask; _desktopCam.cullingMask = _state ? 0 : _storedCamMask; - _uiCam.cullingMask = _state ? _uiCam.cullingMask & ~(1 << CVRLayers.PlayerClone) : _uiCam.cullingMask | (1 << CVRLayers.PlayerClone); _thirdPersonCam.gameObject.SetActive(_state); } } @@ -73,6 +72,9 @@ internal static class CameraLogic ThirdPerson.Logger.Msg("Copying active camera settings & components."); CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam, true); + + // Remove PlayerClone + _thirdPersonCam.cullingMask &= ~(1 << CVRLayers.PlayerClone); if (!CheckIsRestricted()) return; diff --git a/ThirdPerson/Patches.cs b/ThirdPerson/Patches.cs index 9267d2e..60dc4dd 100644 --- a/ThirdPerson/Patches.cs +++ b/ThirdPerson/Patches.cs @@ -28,10 +28,6 @@ internal static class Patches typeof(CVRTools).GetMethod(nameof(CVRTools.ConfigureHudAffinity), BindingFlags.Public | BindingFlags.Static), postfix: typeof(Patches).GetMethod(nameof(OnConfigureHudAffinity), BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod() ); - harmony.Patch( - typeof(TransformHiderManager).GetMethod(nameof(TransformHiderManager.CheckPlayerCamWithinRange), BindingFlags.NonPublic | BindingFlags.Static), - prefix: typeof(Patches).GetMethod(nameof(OnCheckPlayerCamWithinRange), BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod() - ); } //Copy camera settings & postprocessing components @@ -39,5 +35,4 @@ internal static class Patches //Adjust camera distance with height as modifier private static void OnScaleAdjusted(float height) => AdjustScale(height); private static void OnConfigureHudAffinity() => CheckVRMode(); - private static bool OnCheckPlayerCamWithinRange() => !State; // don't hide head if in third person } \ No newline at end of file diff --git a/ThirdPerson/Properties/AssemblyInfo.cs b/ThirdPerson/Properties/AssemblyInfo.cs index ab34afe..6c055d1 100644 --- a/ThirdPerson/Properties/AssemblyInfo.cs +++ b/ThirdPerson/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ThirdPerson.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.9"; + public const string Version = "1.1.0"; public const string Author = "Davi & NotAKidoS"; } \ No newline at end of file From 4f8dcb0cd0474d37201693cfafed45f096801a39 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 02:59:18 -0500 Subject: [PATCH 078/188] including mutual mute --- {MutualMute => .Deprecated/MutualMute}/Main.cs | 0 {MutualMute => .Deprecated/MutualMute}/MutualMute.csproj | 0 {MutualMute => .Deprecated/MutualMute}/Properties/AssemblyInfo.cs | 0 {MutualMute => .Deprecated/MutualMute}/README.md | 0 {MutualMute => .Deprecated/MutualMute}/format.json | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {MutualMute => .Deprecated/MutualMute}/Main.cs (100%) rename {MutualMute => .Deprecated/MutualMute}/MutualMute.csproj (100%) rename {MutualMute => .Deprecated/MutualMute}/Properties/AssemblyInfo.cs (100%) rename {MutualMute => .Deprecated/MutualMute}/README.md (100%) rename {MutualMute => .Deprecated/MutualMute}/format.json (100%) diff --git a/MutualMute/Main.cs b/.Deprecated/MutualMute/Main.cs similarity index 100% rename from MutualMute/Main.cs rename to .Deprecated/MutualMute/Main.cs diff --git a/MutualMute/MutualMute.csproj b/.Deprecated/MutualMute/MutualMute.csproj similarity index 100% rename from MutualMute/MutualMute.csproj rename to .Deprecated/MutualMute/MutualMute.csproj diff --git a/MutualMute/Properties/AssemblyInfo.cs b/.Deprecated/MutualMute/Properties/AssemblyInfo.cs similarity index 100% rename from MutualMute/Properties/AssemblyInfo.cs rename to .Deprecated/MutualMute/Properties/AssemblyInfo.cs diff --git a/MutualMute/README.md b/.Deprecated/MutualMute/README.md similarity index 100% rename from MutualMute/README.md rename to .Deprecated/MutualMute/README.md diff --git a/MutualMute/format.json b/.Deprecated/MutualMute/format.json similarity index 100% rename from MutualMute/format.json rename to .Deprecated/MutualMute/format.json From 323eb92f2e0ad7f1ece36537bf9edaf1de37782b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:03:24 -0500 Subject: [PATCH 079/188] further cleanup of repo --- .../AvatarClone/AvatarClone.Exclusions.cs | 0 .../AvatarClone/AvatarClone.Init.cs | 0 .../AvatarClone/AvatarClone.RenderState.cs | 0 .../AvatarClone/AvatarClone.StateSync.cs | 0 .../AvatarClone/AvatarClone.Util.cs | 0 .../AvatarClone/AvatarClone.cs | 0 .../FPRExclusion/AvatarCloneExclusion.cs | 0 .../AvatarCloneTest}/AvatarCloneTest.csproj | 0 .../AvatarCloneTest}/Main.cs | 0 .../AvatarCloneTest}/Patches.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../AvatarCloneTest}/README.md | 0 .../AvatarCloneTest}/format.json | 0 .../ChatBoxExtensions.csproj | 0 .../ChatBoxExtensions}/HarmonyPatches.cs | 0 .../InputModuleChatBoxExtensions.cs | 0 .../ChatBoxExtensions}/Integrations/Base.cs | 0 .../Integrations/ChatBox.cs | 0 .../Integrations/ChilloutVRAAS.cs | 0 .../Integrations/ChilloutVRBase.cs | 0 .../Integrations/ChilloutVRInput.cs | 0 .../Integrations/Commands.cs | 0 .../Integrations/PlayerRagdollMod.cs | 0 .../ChatBoxExtensions}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ChatBoxExtensions}/format.json | 0 .../CVRLuaToolsExtension.csproj | 0 .../LuaToolsExtension/Base/Singleton.cs | 0 .../CVRBaseLuaBehaviourExtensions.cs | 0 .../CVRLuaClientBehaviourExtensions.cs | 0 .../LuaToolsExtension/LuaHotReloadManager.cs | 0 .../NamedPipes/NamedPipeServer.cs | 0 .../LuaToolsExtension/ScriptInfo.cs | 0 .../CVRLuaToolsExtension}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../CVRLuaToolsExtension}/README.md | 0 .../CVRLuaToolsExtension}/format.json | 0 .../LuaNetworkVariables.csproj | 0 .../LuaNetworkVariables}/Main.cs | 0 .../LuaNetworkVariables}/NetLuaModule.cs | 0 .../NetworkVariables/LuaEventContext.cs | 0 .../NetworkVariables/LuaEventTracker.cs | 0 .../LuaNetVarController.Base.cs | 0 .../LuaNetVarController.Networking.cs | 0 .../LuaNetVarController.Registration.cs | 0 .../LuaNetVarController.Serialization.cs | 0 .../LuaNetVarController.Utility.cs | 0 .../LuaNetworkVariables}/Patches.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../LuaNetworkVariables}/README.md | 0 .../SyncedBehaviour/MNSyncedBehaviour.cs | 0 .../SyncedBehaviour/TestSyncedBehaviour.cs | 0 .../SyncedBehaviour/TestSyncedObject.cs | 0 .../LuaNetworkVariables}/format.json | 0 .../LuaTTS}/LuaTTS.csproj | 0 {LuaTTS => .Experimental/LuaTTS}/Main.cs | 0 {LuaTTS => .Experimental/LuaTTS}/Patches.cs | 0 .../LuaTTS}/Properties/AssemblyInfo.cs | 0 {LuaTTS => .Experimental/LuaTTS}/README.md | 0 .../LuaTTS}/TTSLuaModule.cs | 0 {LuaTTS => .Experimental/LuaTTS}/format.json | 0 .../BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs | 0 .../Integrations/BTKUI/BtkuiAddon.cs | 0 .../Integrations/BTKUI/BtkuiAddon_Utils.cs | 0 .../Integrations/Ragdoll/RagdollAddon.cs | 0 .../ThirdPerson/ThirdPersonAddon.cs | 0 .../OriginShift}/Main.cs | 0 .../OriginShift}/ModSettings.cs | 0 .../OriginShift}/Networking/ModNetwork.cs | 0 .../OriginShift}/OriginShift.csproj | 0 .../Components/Chunk/ChunkController.cs | 0 .../Components/Chunk/ChunkCreator.cs | 0 .../Components/Chunk/ChunkListener.cs | 0 .../Components/OriginShiftController.cs | 0 .../Receivers/OriginShiftEventReceiver.cs | 0 .../OriginShiftParticleSystemReceiver.cs | 0 .../Receivers/OriginShiftRigidbodyReceiver.cs | 0 .../OriginShiftTrailRendererReceiver.cs | 0 .../Receivers/OriginShiftTransformReceiver.cs | 0 ...tterBetterCharacterControllerExtensions.cs | 0 .../Extensions/PlayerSetupExtensions.cs | 0 .../OriginShift/Hacks/OcclusionCullingHack.cs | 0 .../OriginShiftOcclusionCullingDisabler.cs | 0 .../OriginShift/OriginShiftManager.cs | 0 .../Dynamics/OriginShiftDbAvatarReceiver.cs | 0 .../OriginShift/Player/OriginShiftMonitor.cs | 0 .../Player/OriginShiftNetIkReceiver.cs | 0 .../Player/OriginShiftObjectSyncReceiver.cs | 0 .../Player/OriginShiftPickupObjectReceiver.cs | 0 .../Player/OriginShiftSpawnableReceiver.cs | 0 .../OriginShift/Utility/DebugTextDisplay.cs | 0 .../Utility/RendererReflectionUtility.cs | 0 .../OriginShift}/Patches.cs | 0 .../OriginShift}/Properties/AssemblyInfo.cs | 0 .../OriginShift}/README.md | 0 .../Resources/OriginShift-Icon-Active.png | Bin .../Resources/OriginShift-Icon-Forced.png | Bin .../Resources/OriginShift-Icon-Inactive.png | Bin .../OriginShift}/format.json | 0 .../ScriptingSpoofer}/Main.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../ScriptingSpoofer}/README.md | 0 .../ScriptingSpoofer}/ScriptingSpoofer.csproj | 0 .../ScriptingSpoofer}/format.json | 0 .gitignore | 2 +- .../BetterContentLoading.csproj | 15 -- .../BetterDownloadManager.cs | 210 ---------------- .../BetterContentLoading/DownloadInfo.cs | 63 ----- .../BetterContentLoading/DownloadProcessor.cs | 152 ----------- .../DownloadQueue/AvatarDownloadQueue.cs | 83 ------ .../DownloadQueue/ContentDownloadQueueBase.cs | 177 ------------- .../DownloadQueue/PropDownloadQueue.cs | 81 ------ .../DownloadQueue/WorldDownloadQueue.cs | 66 ----- .../BetterContentLoading/DownloadState.cs | 26 -- .../Util/ThreadingHelper.cs | 109 -------- .../DownloadManager/DownloadManager.Core.cs | 237 ------------------ .../DownloadManager.Helpers.cs | 58 ----- .../DownloadManager.Priority.cs | 38 --- .../DownloadManager/DownloadTask.Main.cs | 33 --- .../DownloadManager/DownloadTask.Priority.cs | 6 - .../ConcurrentPriorityQueue.cs | 43 ---- .../DownloadManager.Bandwidth.cs | 24 -- .../DownloadManager2/DownloadManager.Core.cs | 126 ---------- .../DownloadManager.Helpers.cs | 58 ----- .../DownloadManager.Priority.cs | 47 ---- .../DownloadManager.Processing.cs | 144 ----------- .../DownloadManager2/DownloadManager.Queue.cs | 80 ------ .../DownloadManager2/DownloadTask2.cs | 62 ----- BetterContentLoading/Main.cs | 37 --- BetterContentLoading/ModSettings.cs | 31 --- BetterContentLoading/Patches.cs | 71 ------ .../Properties/AssemblyInfo.cs | 33 --- BetterContentLoading/README.md | 14 -- BetterContentLoading/format.json | 23 -- PlayerColorsAPI/Main.cs | 44 ---- PlayerColorsAPI/Patches.cs | 159 ------------ PlayerColorsAPI/PlayerColorsAPI.csproj | 6 - PlayerColorsAPI/Properties/AssemblyInfo.cs | 32 --- PlayerColorsAPI/README.md | 18 -- PlayerColorsAPI/format.json | 23 -- 140 files changed, 1 insertion(+), 2430 deletions(-) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.Exclusions.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.Init.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.RenderState.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.StateSync.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.Util.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/AvatarClone.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/AvatarCloneTest.csproj (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/Main.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/Patches.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/Properties/AssemblyInfo.cs (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/README.md (100%) rename {AvatarCloneTest => .Deprecated/AvatarCloneTest}/format.json (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/ChatBoxExtensions.csproj (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/HarmonyPatches.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/InputModules/InputModuleChatBoxExtensions.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/Base.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/ChatBox.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/ChilloutVRAAS.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/ChilloutVRBase.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/ChilloutVRInput.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/Commands.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Integrations/PlayerRagdollMod.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Main.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/Properties/AssemblyInfo.cs (100%) rename {ChatBoxExtensions => .Deprecated/ChatBoxExtensions}/format.json (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/CVRLuaToolsExtension.csproj (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/Base/Singleton.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/LuaHotReloadManager.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/NamedPipes/NamedPipeServer.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/LuaToolsExtension/ScriptInfo.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/Main.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/Properties/AssemblyInfo.cs (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/README.md (100%) rename {CVRLuaToolsExtension => .Experimental/CVRLuaToolsExtension}/format.json (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/LuaNetworkVariables.csproj (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/Main.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetLuaModule.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaEventContext.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaEventTracker.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaNetVarController.Base.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaNetVarController.Networking.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaNetVarController.Registration.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaNetVarController.Serialization.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/NetworkVariables/LuaNetVarController.Utility.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/Patches.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/Properties/AssemblyInfo.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/README.md (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/SyncedBehaviour/MNSyncedBehaviour.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/SyncedBehaviour/TestSyncedBehaviour.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/SyncedBehaviour/TestSyncedObject.cs (100%) rename {LuaNetworkVariables => .Experimental/LuaNetworkVariables}/format.json (100%) rename {LuaTTS => .Experimental/LuaTTS}/LuaTTS.csproj (100%) rename {LuaTTS => .Experimental/LuaTTS}/Main.cs (100%) rename {LuaTTS => .Experimental/LuaTTS}/Patches.cs (100%) rename {LuaTTS => .Experimental/LuaTTS}/Properties/AssemblyInfo.cs (100%) rename {LuaTTS => .Experimental/LuaTTS}/README.md (100%) rename {LuaTTS => .Experimental/LuaTTS}/TTSLuaModule.cs (100%) rename {LuaTTS => .Experimental/LuaTTS}/format.json (100%) rename {OriginShift => .Experimental/OriginShift}/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Integrations/BTKUI/BtkuiAddon.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Integrations/BTKUI/BtkuiAddon_Utils.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Integrations/Ragdoll/RagdollAddon.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Integrations/ThirdPerson/ThirdPersonAddon.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Main.cs (100%) rename {OriginShift => .Experimental/OriginShift}/ModSettings.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Networking/ModNetwork.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift.csproj (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Chunk/ChunkController.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Chunk/ChunkCreator.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Chunk/ChunkListener.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/OriginShiftController.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Extensions/PlayerSetupExtensions.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Hacks/OcclusionCullingHack.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/OriginShiftManager.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/OriginShiftMonitor.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/OriginShiftNetIkReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/OriginShiftObjectSyncReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/OriginShiftPickupObjectReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Player/OriginShiftSpawnableReceiver.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Utility/DebugTextDisplay.cs (100%) rename {OriginShift => .Experimental/OriginShift}/OriginShift/Utility/RendererReflectionUtility.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Patches.cs (100%) rename {OriginShift => .Experimental/OriginShift}/Properties/AssemblyInfo.cs (100%) rename {OriginShift => .Experimental/OriginShift}/README.md (100%) rename {OriginShift => .Experimental/OriginShift}/Resources/OriginShift-Icon-Active.png (100%) rename {OriginShift => .Experimental/OriginShift}/Resources/OriginShift-Icon-Forced.png (100%) rename {OriginShift => .Experimental/OriginShift}/Resources/OriginShift-Icon-Inactive.png (100%) rename {OriginShift => .Experimental/OriginShift}/format.json (100%) rename {ScriptingSpoofer => .Experimental/ScriptingSpoofer}/Main.cs (100%) rename {ScriptingSpoofer => .Experimental/ScriptingSpoofer}/Properties/AssemblyInfo.cs (100%) rename {ScriptingSpoofer => .Experimental/ScriptingSpoofer}/README.md (100%) rename {ScriptingSpoofer => .Experimental/ScriptingSpoofer}/ScriptingSpoofer.csproj (100%) rename {ScriptingSpoofer => .Experimental/ScriptingSpoofer}/format.json (100%) delete mode 100644 BetterContentLoading/BetterContentLoading.csproj delete mode 100644 BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadInfo.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadProcessor.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs delete mode 100644 BetterContentLoading/BetterContentLoading/DownloadState.cs delete mode 100644 BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs delete mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Core.cs delete mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs delete mode 100644 BetterContentLoading/DownloadManager/DownloadManager.Priority.cs delete mode 100644 BetterContentLoading/DownloadManager/DownloadTask.Main.cs delete mode 100644 BetterContentLoading/DownloadManager/DownloadTask.Priority.cs delete mode 100644 BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Core.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs delete mode 100644 BetterContentLoading/DownloadManager2/DownloadTask2.cs delete mode 100644 BetterContentLoading/Main.cs delete mode 100644 BetterContentLoading/ModSettings.cs delete mode 100644 BetterContentLoading/Patches.cs delete mode 100644 BetterContentLoading/Properties/AssemblyInfo.cs delete mode 100644 BetterContentLoading/README.md delete mode 100644 BetterContentLoading/format.json delete mode 100644 PlayerColorsAPI/Main.cs delete mode 100644 PlayerColorsAPI/Patches.cs delete mode 100644 PlayerColorsAPI/PlayerColorsAPI.csproj delete mode 100644 PlayerColorsAPI/Properties/AssemblyInfo.cs delete mode 100644 PlayerColorsAPI/README.md delete mode 100644 PlayerColorsAPI/format.json diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Exclusions.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Init.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Init.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.RenderState.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.StateSync.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.Util.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.Util.cs diff --git a/AvatarCloneTest/AvatarClone/AvatarClone.cs b/.Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/AvatarClone.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/AvatarClone.cs diff --git a/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs b/.Deprecated/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs similarity index 100% rename from AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs rename to .Deprecated/AvatarCloneTest/AvatarClone/FPRExclusion/AvatarCloneExclusion.cs diff --git a/AvatarCloneTest/AvatarCloneTest.csproj b/.Deprecated/AvatarCloneTest/AvatarCloneTest.csproj similarity index 100% rename from AvatarCloneTest/AvatarCloneTest.csproj rename to .Deprecated/AvatarCloneTest/AvatarCloneTest.csproj diff --git a/AvatarCloneTest/Main.cs b/.Deprecated/AvatarCloneTest/Main.cs similarity index 100% rename from AvatarCloneTest/Main.cs rename to .Deprecated/AvatarCloneTest/Main.cs diff --git a/AvatarCloneTest/Patches.cs b/.Deprecated/AvatarCloneTest/Patches.cs similarity index 100% rename from AvatarCloneTest/Patches.cs rename to .Deprecated/AvatarCloneTest/Patches.cs diff --git a/AvatarCloneTest/Properties/AssemblyInfo.cs b/.Deprecated/AvatarCloneTest/Properties/AssemblyInfo.cs similarity index 100% rename from AvatarCloneTest/Properties/AssemblyInfo.cs rename to .Deprecated/AvatarCloneTest/Properties/AssemblyInfo.cs diff --git a/AvatarCloneTest/README.md b/.Deprecated/AvatarCloneTest/README.md similarity index 100% rename from AvatarCloneTest/README.md rename to .Deprecated/AvatarCloneTest/README.md diff --git a/AvatarCloneTest/format.json b/.Deprecated/AvatarCloneTest/format.json similarity index 100% rename from AvatarCloneTest/format.json rename to .Deprecated/AvatarCloneTest/format.json diff --git a/ChatBoxExtensions/ChatBoxExtensions.csproj b/.Deprecated/ChatBoxExtensions/ChatBoxExtensions.csproj similarity index 100% rename from ChatBoxExtensions/ChatBoxExtensions.csproj rename to .Deprecated/ChatBoxExtensions/ChatBoxExtensions.csproj diff --git a/ChatBoxExtensions/HarmonyPatches.cs b/.Deprecated/ChatBoxExtensions/HarmonyPatches.cs similarity index 100% rename from ChatBoxExtensions/HarmonyPatches.cs rename to .Deprecated/ChatBoxExtensions/HarmonyPatches.cs diff --git a/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs b/.Deprecated/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs similarity index 100% rename from ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs rename to .Deprecated/ChatBoxExtensions/InputModules/InputModuleChatBoxExtensions.cs diff --git a/ChatBoxExtensions/Integrations/Base.cs b/.Deprecated/ChatBoxExtensions/Integrations/Base.cs similarity index 100% rename from ChatBoxExtensions/Integrations/Base.cs rename to .Deprecated/ChatBoxExtensions/Integrations/Base.cs diff --git a/ChatBoxExtensions/Integrations/ChatBox.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChatBox.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChatBox.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChatBox.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRAAS.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRAAS.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRBase.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRBase.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRBase.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRBase.cs diff --git a/ChatBoxExtensions/Integrations/ChilloutVRInput.cs b/.Deprecated/ChatBoxExtensions/Integrations/ChilloutVRInput.cs similarity index 100% rename from ChatBoxExtensions/Integrations/ChilloutVRInput.cs rename to .Deprecated/ChatBoxExtensions/Integrations/ChilloutVRInput.cs diff --git a/ChatBoxExtensions/Integrations/Commands.cs b/.Deprecated/ChatBoxExtensions/Integrations/Commands.cs similarity index 100% rename from ChatBoxExtensions/Integrations/Commands.cs rename to .Deprecated/ChatBoxExtensions/Integrations/Commands.cs diff --git a/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs b/.Deprecated/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs similarity index 100% rename from ChatBoxExtensions/Integrations/PlayerRagdollMod.cs rename to .Deprecated/ChatBoxExtensions/Integrations/PlayerRagdollMod.cs diff --git a/ChatBoxExtensions/Main.cs b/.Deprecated/ChatBoxExtensions/Main.cs similarity index 100% rename from ChatBoxExtensions/Main.cs rename to .Deprecated/ChatBoxExtensions/Main.cs diff --git a/ChatBoxExtensions/Properties/AssemblyInfo.cs b/.Deprecated/ChatBoxExtensions/Properties/AssemblyInfo.cs similarity index 100% rename from ChatBoxExtensions/Properties/AssemblyInfo.cs rename to .Deprecated/ChatBoxExtensions/Properties/AssemblyInfo.cs diff --git a/ChatBoxExtensions/format.json b/.Deprecated/ChatBoxExtensions/format.json similarity index 100% rename from ChatBoxExtensions/format.json rename to .Deprecated/ChatBoxExtensions/format.json diff --git a/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj b/.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj similarity index 100% rename from CVRLuaToolsExtension/CVRLuaToolsExtension.csproj rename to .Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs diff --git a/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs similarity index 100% rename from CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs rename to .Experimental/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs diff --git a/CVRLuaToolsExtension/Main.cs b/.Experimental/CVRLuaToolsExtension/Main.cs similarity index 100% rename from CVRLuaToolsExtension/Main.cs rename to .Experimental/CVRLuaToolsExtension/Main.cs diff --git a/CVRLuaToolsExtension/Properties/AssemblyInfo.cs b/.Experimental/CVRLuaToolsExtension/Properties/AssemblyInfo.cs similarity index 100% rename from CVRLuaToolsExtension/Properties/AssemblyInfo.cs rename to .Experimental/CVRLuaToolsExtension/Properties/AssemblyInfo.cs diff --git a/CVRLuaToolsExtension/README.md b/.Experimental/CVRLuaToolsExtension/README.md similarity index 100% rename from CVRLuaToolsExtension/README.md rename to .Experimental/CVRLuaToolsExtension/README.md diff --git a/CVRLuaToolsExtension/format.json b/.Experimental/CVRLuaToolsExtension/format.json similarity index 100% rename from CVRLuaToolsExtension/format.json rename to .Experimental/CVRLuaToolsExtension/format.json diff --git a/LuaNetworkVariables/LuaNetworkVariables.csproj b/.Experimental/LuaNetworkVariables/LuaNetworkVariables.csproj similarity index 100% rename from LuaNetworkVariables/LuaNetworkVariables.csproj rename to .Experimental/LuaNetworkVariables/LuaNetworkVariables.csproj diff --git a/LuaNetworkVariables/Main.cs b/.Experimental/LuaNetworkVariables/Main.cs similarity index 100% rename from LuaNetworkVariables/Main.cs rename to .Experimental/LuaNetworkVariables/Main.cs diff --git a/LuaNetworkVariables/NetLuaModule.cs b/.Experimental/LuaNetworkVariables/NetLuaModule.cs similarity index 100% rename from LuaNetworkVariables/NetLuaModule.cs rename to .Experimental/LuaNetworkVariables/NetLuaModule.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaEventContext.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs diff --git a/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs similarity index 100% rename from LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs rename to .Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs diff --git a/LuaNetworkVariables/Patches.cs b/.Experimental/LuaNetworkVariables/Patches.cs similarity index 100% rename from LuaNetworkVariables/Patches.cs rename to .Experimental/LuaNetworkVariables/Patches.cs diff --git a/LuaNetworkVariables/Properties/AssemblyInfo.cs b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs similarity index 100% rename from LuaNetworkVariables/Properties/AssemblyInfo.cs rename to .Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs diff --git a/LuaNetworkVariables/README.md b/.Experimental/LuaNetworkVariables/README.md similarity index 100% rename from LuaNetworkVariables/README.md rename to .Experimental/LuaNetworkVariables/README.md diff --git a/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs diff --git a/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs similarity index 100% rename from LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs rename to .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs diff --git a/LuaNetworkVariables/format.json b/.Experimental/LuaNetworkVariables/format.json similarity index 100% rename from LuaNetworkVariables/format.json rename to .Experimental/LuaNetworkVariables/format.json diff --git a/LuaTTS/LuaTTS.csproj b/.Experimental/LuaTTS/LuaTTS.csproj similarity index 100% rename from LuaTTS/LuaTTS.csproj rename to .Experimental/LuaTTS/LuaTTS.csproj diff --git a/LuaTTS/Main.cs b/.Experimental/LuaTTS/Main.cs similarity index 100% rename from LuaTTS/Main.cs rename to .Experimental/LuaTTS/Main.cs diff --git a/LuaTTS/Patches.cs b/.Experimental/LuaTTS/Patches.cs similarity index 100% rename from LuaTTS/Patches.cs rename to .Experimental/LuaTTS/Patches.cs diff --git a/LuaTTS/Properties/AssemblyInfo.cs b/.Experimental/LuaTTS/Properties/AssemblyInfo.cs similarity index 100% rename from LuaTTS/Properties/AssemblyInfo.cs rename to .Experimental/LuaTTS/Properties/AssemblyInfo.cs diff --git a/LuaTTS/README.md b/.Experimental/LuaTTS/README.md similarity index 100% rename from LuaTTS/README.md rename to .Experimental/LuaTTS/README.md diff --git a/LuaTTS/TTSLuaModule.cs b/.Experimental/LuaTTS/TTSLuaModule.cs similarity index 100% rename from LuaTTS/TTSLuaModule.cs rename to .Experimental/LuaTTS/TTSLuaModule.cs diff --git a/LuaTTS/format.json b/.Experimental/LuaTTS/format.json similarity index 100% rename from LuaTTS/format.json rename to .Experimental/LuaTTS/format.json diff --git a/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkuiAddon.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon.cs diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs b/.Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs similarity index 100% rename from OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs rename to .Experimental/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs diff --git a/OriginShift/Integrations/Ragdoll/RagdollAddon.cs b/.Experimental/OriginShift/Integrations/Ragdoll/RagdollAddon.cs similarity index 100% rename from OriginShift/Integrations/Ragdoll/RagdollAddon.cs rename to .Experimental/OriginShift/Integrations/Ragdoll/RagdollAddon.cs diff --git a/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs b/.Experimental/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs similarity index 100% rename from OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs rename to .Experimental/OriginShift/Integrations/ThirdPerson/ThirdPersonAddon.cs diff --git a/OriginShift/Main.cs b/.Experimental/OriginShift/Main.cs similarity index 100% rename from OriginShift/Main.cs rename to .Experimental/OriginShift/Main.cs diff --git a/OriginShift/ModSettings.cs b/.Experimental/OriginShift/ModSettings.cs similarity index 100% rename from OriginShift/ModSettings.cs rename to .Experimental/OriginShift/ModSettings.cs diff --git a/OriginShift/Networking/ModNetwork.cs b/.Experimental/OriginShift/Networking/ModNetwork.cs similarity index 100% rename from OriginShift/Networking/ModNetwork.cs rename to .Experimental/OriginShift/Networking/ModNetwork.cs diff --git a/OriginShift/OriginShift.csproj b/.Experimental/OriginShift/OriginShift.csproj similarity index 100% rename from OriginShift/OriginShift.csproj rename to .Experimental/OriginShift/OriginShift.csproj diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkController.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkController.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkController.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkController.cs diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkCreator.cs diff --git a/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs b/.Experimental/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs similarity index 100% rename from OriginShift/OriginShift/Components/Chunk/ChunkListener.cs rename to .Experimental/OriginShift/OriginShift/Components/Chunk/ChunkListener.cs diff --git a/OriginShift/OriginShift/Components/OriginShiftController.cs b/.Experimental/OriginShift/OriginShift/Components/OriginShiftController.cs similarity index 100% rename from OriginShift/OriginShift/Components/OriginShiftController.cs rename to .Experimental/OriginShift/OriginShift/Components/OriginShiftController.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs b/.Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs rename to .Experimental/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs diff --git a/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs b/.Experimental/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs similarity index 100% rename from OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs rename to .Experimental/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs diff --git a/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs b/.Experimental/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs similarity index 100% rename from OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs rename to .Experimental/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs diff --git a/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs b/.Experimental/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs similarity index 100% rename from OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs rename to .Experimental/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs diff --git a/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs b/.Experimental/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs similarity index 100% rename from OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs rename to .Experimental/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs diff --git a/OriginShift/OriginShift/OriginShiftManager.cs b/.Experimental/OriginShift/OriginShift/OriginShiftManager.cs similarity index 100% rename from OriginShift/OriginShift/OriginShiftManager.cs rename to .Experimental/OriginShift/OriginShift/OriginShiftManager.cs diff --git a/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftMonitor.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftMonitor.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftMonitor.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftMonitor.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftObjectSyncReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftPickupObjectReceiver.cs diff --git a/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs b/.Experimental/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs similarity index 100% rename from OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs rename to .Experimental/OriginShift/OriginShift/Player/OriginShiftSpawnableReceiver.cs diff --git a/OriginShift/OriginShift/Utility/DebugTextDisplay.cs b/.Experimental/OriginShift/OriginShift/Utility/DebugTextDisplay.cs similarity index 100% rename from OriginShift/OriginShift/Utility/DebugTextDisplay.cs rename to .Experimental/OriginShift/OriginShift/Utility/DebugTextDisplay.cs diff --git a/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs b/.Experimental/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs similarity index 100% rename from OriginShift/OriginShift/Utility/RendererReflectionUtility.cs rename to .Experimental/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs diff --git a/OriginShift/Patches.cs b/.Experimental/OriginShift/Patches.cs similarity index 100% rename from OriginShift/Patches.cs rename to .Experimental/OriginShift/Patches.cs diff --git a/OriginShift/Properties/AssemblyInfo.cs b/.Experimental/OriginShift/Properties/AssemblyInfo.cs similarity index 100% rename from OriginShift/Properties/AssemblyInfo.cs rename to .Experimental/OriginShift/Properties/AssemblyInfo.cs diff --git a/OriginShift/README.md b/.Experimental/OriginShift/README.md similarity index 100% rename from OriginShift/README.md rename to .Experimental/OriginShift/README.md diff --git a/OriginShift/Resources/OriginShift-Icon-Active.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Active.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Active.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Active.png diff --git a/OriginShift/Resources/OriginShift-Icon-Forced.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Forced.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Forced.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Forced.png diff --git a/OriginShift/Resources/OriginShift-Icon-Inactive.png b/.Experimental/OriginShift/Resources/OriginShift-Icon-Inactive.png similarity index 100% rename from OriginShift/Resources/OriginShift-Icon-Inactive.png rename to .Experimental/OriginShift/Resources/OriginShift-Icon-Inactive.png diff --git a/OriginShift/format.json b/.Experimental/OriginShift/format.json similarity index 100% rename from OriginShift/format.json rename to .Experimental/OriginShift/format.json diff --git a/ScriptingSpoofer/Main.cs b/.Experimental/ScriptingSpoofer/Main.cs similarity index 100% rename from ScriptingSpoofer/Main.cs rename to .Experimental/ScriptingSpoofer/Main.cs diff --git a/ScriptingSpoofer/Properties/AssemblyInfo.cs b/.Experimental/ScriptingSpoofer/Properties/AssemblyInfo.cs similarity index 100% rename from ScriptingSpoofer/Properties/AssemblyInfo.cs rename to .Experimental/ScriptingSpoofer/Properties/AssemblyInfo.cs diff --git a/ScriptingSpoofer/README.md b/.Experimental/ScriptingSpoofer/README.md similarity index 100% rename from ScriptingSpoofer/README.md rename to .Experimental/ScriptingSpoofer/README.md diff --git a/ScriptingSpoofer/ScriptingSpoofer.csproj b/.Experimental/ScriptingSpoofer/ScriptingSpoofer.csproj similarity index 100% rename from ScriptingSpoofer/ScriptingSpoofer.csproj rename to .Experimental/ScriptingSpoofer/ScriptingSpoofer.csproj diff --git a/ScriptingSpoofer/format.json b/.Experimental/ScriptingSpoofer/format.json similarity index 100% rename from ScriptingSpoofer/format.json rename to .Experimental/ScriptingSpoofer/format.json diff --git a/.gitignore b/.gitignore index 2abd364..e3092f6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # NAK -.Experimental/ +.Blackbox/ # Nstrip & ManagedLibs stuff NStrip.exe diff --git a/BetterContentLoading/BetterContentLoading.csproj b/BetterContentLoading/BetterContentLoading.csproj deleted file mode 100644 index 67cef19..0000000 --- a/BetterContentLoading/BetterContentLoading.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net48 - NAK.BetterContentLoading - - - - ..\.ManagedLibs\BTKUILib.dll - - - ..\.ManagedLibs\TheClapper.dll - - - diff --git a/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs b/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs deleted file mode 100644 index 749483f..0000000 --- a/BetterContentLoading/BetterContentLoading/BetterDownloadManager.cs +++ /dev/null @@ -1,210 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.IO.Instancing; -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using ABI_RC.Systems.GameEventSystem; -using NAK.BetterContentLoading.Queue; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -// Download world -> connect to instance -> receive all Avatar data -// -> initial connection to instance -> receive all props data - -// We receive Prop download data only after we have connected to the instance. Avatar data we seem to receive -// prior to our initial connection event firing. - -public class BetterDownloadManager -{ - #region Singleton - - private static BetterDownloadManager _instance; - public static BetterDownloadManager Instance => _instance ??= new BetterDownloadManager(); - - #endregion Singleton - - #region Constructor - - private BetterDownloadManager() - { - _downloadProcessor = new DownloadProcessor(); - - _worldQueue = new WorldDownloadQueue(this); // Only one world at a time - _avatarQueue = new AvatarDownloadQueue(this); // Up to 3 avatars at once - _propQueue = new PropDownloadQueue(this); // Up to 2 props at once - - // Set to 100MBs by default - MaxDownloadBandwidth = 100 * 1024 * 1024; - - CVRGameEventSystem.Instance.OnConnected.AddListener(_ => - { - if (!Instances.IsReconnecting) OnInitialConnectionToInstance(); - }); - } - - #endregion Constructor - - #region Settings - - /// Log debug messages - public bool IsDebugEnabled { get; set; } = true; - - /// Prioritize friends first in download queue - public bool PrioritizeFriends { get; set; } = true; - - /// Prioritize content closest to player first in download queue - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - - public int MaxDownloadBandwidth - { - get => _downloadProcessor.MaxDownloadBandwidth; - set => _downloadProcessor.MaxDownloadBandwidth = value; - } - - #endregion Settings - - private readonly DownloadProcessor _downloadProcessor; - - private readonly AvatarDownloadQueue _avatarQueue; - private readonly PropDownloadQueue _propQueue; - private readonly WorldDownloadQueue _worldQueue; - - #region Game Events - - private void OnInitialConnectionToInstance() - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg("Initial connection established."); - // await few seconds before chewing through the download queue, to allow for download priorities to be set - // once we have received most of the data from the server - } - - #endregion Game Events - - #region Public Queue Methods - - public void QueueAvatarDownload( - in DownloadInfo info, - string playerId, - CVRLoadingAvatarController loadingController) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Avatar Download:\n{info.GetLogString()}\n" + - $"PlayerId: {playerId}\n" + - $"LoadingController: {loadingController}"); - } - - _avatarQueue.QueueDownload(in info, playerId); - } - - /// - /// Queues a prop download. - /// - /// The download info. - /// The instance ID for the prop. - /// The user who spawned the prop. - public void QueuePropDownload( - in DownloadInfo info, - string instanceId, - string spawnerId) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Prop Download:\n{info.GetLogString()}\n" + - $"InstanceId: {instanceId}\n" + - $"SpawnerId: {spawnerId}"); - } - - _propQueue.QueueDownload(in info, instanceId, spawnerId); - } - - /// - /// Queues a world download. - /// - /// Download info. - /// Whether to load into this world once downloaded. - /// Whether the home world is requested. - public void QueueWorldDownload( - in DownloadInfo info, - bool joinOnComplete, - bool isHomeRequested) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing World Download:\n{info.GetLogString()}\n" + - $"JoinOnComplete: {joinOnComplete}\n" + - $"IsHomeRequested: {isHomeRequested}"); - } - - _worldQueue.QueueDownload(in info, joinOnComplete, isHomeRequested); - } - - #endregion Public Queue Methods - - #region Internal Methods - - internal Task ProcessDownload(DownloadInfo info, Action progressCallback = null) - { - return _downloadProcessor.ProcessDownload(info); - } - - #endregion Internal Methods - - #region Private Helper Methods - - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - internal static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - internal static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - internal bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } - - #endregion Private Helper Methods -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadInfo.cs b/BetterContentLoading/BetterContentLoading/DownloadInfo.cs deleted file mode 100644 index 038f6bf..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadInfo.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using ABI_RC.Core.Networking.API.Responses; - -namespace NAK.BetterContentLoading; - -public readonly struct DownloadInfo -{ - public readonly string AssetId; - public readonly string AssetUrl; - public readonly string FileId; - public readonly long FileSize; - public readonly string FileKey; - public readonly string FileHash; - public readonly int CompatibilityVersion; - public readonly int EncryptionAlgorithm; - public readonly UgcTagsData TagsData; - - public readonly string DownloadId; - - public DownloadInfo( - string assetId, string assetUrl, string fileId, - long fileSize, string fileKey, string fileHash, - int compatibilityVersion, int encryptionAlgorithm, - UgcTagsData tagsData) - { - AssetId = assetId + "meow"; - AssetUrl = assetUrl; - FileId = fileId; - FileSize = fileSize; - FileKey = fileKey; - FileHash = fileHash; - CompatibilityVersion = compatibilityVersion; - EncryptionAlgorithm = encryptionAlgorithm; - TagsData = tagsData; - - using SHA256 sha = SHA256.Create(); - StringBuilder sb = new(); - sb.Append(assetId) - .Append('|').Append(assetUrl) - .Append('|').Append(fileId) - .Append('|').Append(fileSize) - .Append('|').Append(fileKey) - .Append('|').Append(fileHash) - .Append('|').Append(compatibilityVersion) - .Append('|').Append(encryptionAlgorithm); - - var bytes = Encoding.UTF8.GetBytes(sb.ToString()); - var hash = sha.ComputeHash(bytes); - DownloadId = Convert.ToBase64String(hash); - } - - public string GetLogString() => - $"AssetId: {AssetId}\n" + - $"DownloadId: {DownloadId}\n" + - $"AssetUrl: {AssetUrl}\n" + - $"FileId: {FileId}\n" + - $"FileSize: {FileSize}\n" + - $"FileKey: {FileKey}\n" + - $"FileHash: {FileHash}\n" + - $"CompatibilityVersion: {CompatibilityVersion}\n" + - $"EncryptionAlgorithm: {EncryptionAlgorithm}"; -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs b/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs deleted file mode 100644 index e77acdc..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadProcessor.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Net.Http; -using ABI_RC.Core; -using ABI_RC.Core.IO.AssetManagement; - -namespace NAK.BetterContentLoading; - -public class DownloadProcessor -{ - private readonly HttpClient _client = new(); - private readonly SemaphoreSlim _bandwidthSemaphore = new(1); - private long _bytesReadLastSecond; - - private int _maxDownloadBandwidth = 10 * 1024 * 1024; // Default 10MB/s - private const int MinBufferSize = 16384; // 16KB min buffer - private const long ThrottleThreshold = 25 * 1024 * 1024; // 25MB threshold for throttling - private const long KnownSizeDifference = 1000; // API reported size is 1000 bytes larger than actual content - - public int MaxDownloadBandwidth - { - get => _maxDownloadBandwidth; - set => _maxDownloadBandwidth = Math.Max(1024 * 1024, value); - } - - private int ActiveDownloads { get; set; } - private int CurrentBandwidthPerDownload => MaxDownloadBandwidth / Math.Max(1, ActiveDownloads); - - public int GetProgress(string downloadId) => _downloadProgress.GetValueOrDefault(downloadId, 0); - private readonly Dictionary _downloadProgress = new(); - - public async Task ProcessDownload(DownloadInfo downloadInfo) - { - try - { - if (await CacheManager.Instance.IsCachedFileUpToDate( - downloadInfo.AssetId, - downloadInfo.FileId, - downloadInfo.FileHash)) - return true; - - var filePath = CacheManager.Instance.GetCachePath(downloadInfo.AssetId, downloadInfo.FileId); - if (!CVRTools.HasEnoughDiskSpace(filePath, downloadInfo.FileSize)) - { - BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {downloadInfo.AssetId}"); - return false; - } - - CacheManager.Instance.EnsureCacheDirectoryExists(downloadInfo.AssetId); - - bool success = false; - Exception lastException = null; - - for (int attempt = 0; attempt <= 1; attempt++) - { - try - { - if (attempt > 0) - await Task.Delay(1000); - - success = await DownloadWithBandwidthLimit(downloadInfo, filePath); - if (success) break; - } - catch (Exception ex) - { - lastException = ex; - } - } - - if (!success && lastException != null) - BetterContentLoadingMod.Logger.Error($"Failed to download {downloadInfo.AssetId}: {lastException}"); - - _downloadProgress.Remove(downloadInfo.DownloadId); - return success; - } - catch (Exception ex) - { - BetterContentLoadingMod.Logger.Error($"Error processing download for {downloadInfo.AssetId}: {ex}"); - _downloadProgress.Remove(downloadInfo.DownloadId); - return false; - } - } - - private async Task DownloadWithBandwidthLimit(DownloadInfo downloadInfo, string filePath) - { - await _bandwidthSemaphore.WaitAsync(); - try { ActiveDownloads++; } - finally { _bandwidthSemaphore.Release(); } - - string tempFilePath = filePath + ".download"; - try - { - using var response = await _client.GetAsync(downloadInfo.AssetUrl, HttpCompletionOption.ResponseHeadersRead); - if (!response.IsSuccessStatusCode) - return false; - - var expectedContentSize = downloadInfo.FileSize - KnownSizeDifference; - using var stream = await response.Content.ReadAsStreamAsync(); - using var fileStream = File.Open(tempFilePath, FileMode.Create); - - var isEligibleForThrottle = downloadInfo.FileSize >= ThrottleThreshold; - var totalBytesRead = 0L; - byte[] buffer = null; - - while (true) - { - var lengthToRead = isEligibleForThrottle ? MinBufferSize : CurrentBandwidthPerDownload; - if (buffer == null || lengthToRead != buffer.Length) - buffer = new byte[lengthToRead]; - - var bytesRead = await stream.ReadAsync(buffer, 0, lengthToRead); - if (bytesRead == 0) break; - - if (isEligibleForThrottle) - Interlocked.Add(ref _bytesReadLastSecond, bytesRead); - - fileStream.Write(buffer, 0, bytesRead); - totalBytesRead += bytesRead; - - var progress = (int)(((float)totalBytesRead / expectedContentSize) * 100f); - _downloadProgress[downloadInfo.DownloadId] = Math.Clamp(progress, 0, 100); - } - - fileStream.Flush(); - - var fileInfo = new FileInfo(tempFilePath); - if (fileInfo.Length != expectedContentSize) - return false; - - if (File.Exists(filePath)) - File.Delete(filePath); - File.Move(tempFilePath, filePath); - - CacheManager.Instance.QueuePrune(); - return true; - } - finally - { - await _bandwidthSemaphore.WaitAsync(); - try { ActiveDownloads--; } - finally { _bandwidthSemaphore.Release(); } - - try - { - if (File.Exists(tempFilePath)) - File.Delete(tempFilePath); - } - catch - { - // Ignore cleanup errors - } - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs deleted file mode 100644 index 60060c5..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/AvatarDownloadQueue.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public class AvatarDownloadQueue : ContentDownloadQueueBase -{ - public AvatarDownloadQueue(BetterDownloadManager manager) : base(manager, 3) { } - - public void QueueDownload(in DownloadInfo info, string playerId, Action onComplete = null) - { - float priority = CalculateAvatarPriority(in info, playerId); - QueueDownload(in info, playerId, priority, onComplete); - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - if (DownloadOwners.TryGetValue(downloadId, out var owners)) - { - foreach (var playerId in owners) - { - if (BetterDownloadManager.IsPlayerLocal(playerId)) - { - // Update loading progress on local player - BetterContentLoadingMod.Logger.Msg($"Progress for local player ({playerId}): {progress:P}"); - continue; - } - if (BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) - { - // Update loading progress on avatar controller if needed - BetterContentLoadingMod.Logger.Msg($"Progress for {player.Username} ({playerId}): {progress:P}"); - } - } - } - } - - protected override float RecalculatePriority(string downloadId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return float.MaxValue; - - float lowestPriority = float.MaxValue; - DownloadInfo? downloadInfo = null; - - // Find the queue item to get the DownloadInfo - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - if (queueItem.Info.AssetId != null) - downloadInfo = queueItem.Info; - - if (downloadInfo == null) - return lowestPriority; - - // Calculate priority for each owner and use the lowest (highest priority) value - foreach (var playerId in owners) - { - var priority = CalculateAvatarPriority(downloadInfo.Value, playerId); - lowestPriority = Math.Min(lowestPriority, priority); - } - - return lowestPriority; - } - - private float CalculateAvatarPriority(in DownloadInfo info, string playerId) - { - float priority = info.FileSize; - - if (BetterDownloadManager.IsPlayerLocal(playerId)) - return 0f; - - if (!BetterDownloadManager.TryGetPlayerEntity(playerId, out var player)) - return priority; - - if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(playerId)) - priority *= 0.5f; - - if (Manager.PrioritizeDistance && Manager.IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs deleted file mode 100644 index 66e366b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/ContentDownloadQueueBase.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public abstract class ContentDownloadQueueBase -{ - protected readonly struct QueueItem - { - public readonly DownloadInfo Info; - public readonly float Priority; - public readonly Action OnComplete; // Callback with (downloadId, ownerId) - - public QueueItem(DownloadInfo info, float priority, Action onComplete) - { - Info = info; - Priority = priority; - OnComplete = onComplete; - } - } - - protected readonly List Queue = new(); - private readonly HashSet ActiveDownloads = new(); // By DownloadId - protected readonly Dictionary> DownloadOwners = new(); // DownloadId -> Set of OwnerIds - private readonly SemaphoreSlim DownloadSemaphore; - protected readonly BetterDownloadManager Manager; - - protected ContentDownloadQueueBase(BetterDownloadManager manager, int maxParallelDownloads) - { - Manager = manager; - DownloadSemaphore = new SemaphoreSlim(maxParallelDownloads); - } - - protected void QueueDownload(in DownloadInfo info, string ownerId, float priority, Action onComplete) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Msg($"Attempting to queue download for {info.AssetId} (DownloadId: {info.DownloadId})"); - - // Add to owners tracking - if (!DownloadOwners.TryGetValue(info.DownloadId, out var owners)) - { - owners = new HashSet(); - DownloadOwners[info.DownloadId] = owners; - } - owners.Add(ownerId); - - // If already downloading, just add the owner and callback - if (ActiveDownloads.Contains(info.DownloadId)) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Msg($"Already downloading {info.DownloadId}, added owner {ownerId}"); - return; - } - - DownloadInfo downloadInfo = info; - var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadInfo.DownloadId); - if (existingIndex >= 0) - { - // Update priority if needed based on new owner - var newPriority = RecalculatePriority(info.DownloadId); - Queue[existingIndex] = new QueueItem(info, newPriority, onComplete); - SortQueue(); - return; - } - - Queue.Add(new QueueItem(info, priority, onComplete)); - SortQueue(); - TryStartNextDownload(); - } - - public void RemoveOwner(string downloadId, string ownerId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return; - - owners.Remove(ownerId); - - if (owners.Count == 0) - { - // No more owners, cancel the download - DownloadOwners.Remove(downloadId); - CancelDownload(downloadId); - } - else if (!ActiveDownloads.Contains(downloadId)) - { - // Still has owners and is queued, recalculate priority - var existingIndex = Queue.FindIndex(x => x.Info.DownloadId == downloadId); - if (existingIndex >= 0) - { - var item = Queue[existingIndex]; - var newPriority = RecalculatePriority(downloadId); - Queue[existingIndex] = new QueueItem(item.Info, newPriority, item.OnComplete); - SortQueue(); - } - } - } - - protected virtual async void TryStartNextDownload() - { - try - { - if (Queue.Count == 0) return; - - await DownloadSemaphore.WaitAsync(); - - if (Queue.Count > 0) - { - var item = Queue[0]; - Queue.RemoveAt(0); - - // Double check we still have owners before starting download - if (!DownloadOwners.TryGetValue(item.Info.DownloadId, out var owners) || owners.Count == 0) - { - DownloadSemaphore.Release(); - TryStartNextDownload(); - return; - } - - ActiveDownloads.Add(item.Info.DownloadId); - - try - { - await ProcessDownload(item.Info); - BetterContentLoadingMod.Logger.Msg($"Download completed for {item.Info.DownloadId}"); - - // Notify all owners of completion - if (DownloadOwners.TryGetValue(item.Info.DownloadId, out owners)) - { - foreach (var owner in owners) - { - item.OnComplete?.Invoke(item.Info.DownloadId, owner); - } - } - } - catch (Exception ex) - { - if (Manager.IsDebugEnabled) - BetterContentLoadingMod.Logger.Error($"Download failed for {item.Info.DownloadId}: {ex}"); - } - finally - { - ActiveDownloads.Remove(item.Info.DownloadId); - DownloadSemaphore.Release(); - TryStartNextDownload(); - } - } - else - { - DownloadSemaphore.Release(); - } - } - catch (Exception e) - { - BetterContentLoadingMod.Logger.Error($"Error in TryStartNextDownload: {e}"); - } - } - - protected virtual async Task ProcessDownload(DownloadInfo info) - { - bool success = await Manager.ProcessDownload(info); - - if (!success) - throw new Exception($"Failed to download {info.AssetId}"); - } - - protected abstract void OnDownloadProgress(string downloadId, float progress); - - protected abstract float RecalculatePriority(string downloadId); - - protected virtual void SortQueue() - { - Queue.Sort((a, b) => a.Priority.CompareTo(b.Priority)); - } - - protected virtual void CancelDownload(string downloadId) - { - Queue.RemoveAll(x => x.Info.DownloadId == downloadId); - if (ActiveDownloads.Remove(downloadId)) DownloadSemaphore.Release(); - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs deleted file mode 100644 index f100e0b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/PropDownloadQueue.cs +++ /dev/null @@ -1,81 +0,0 @@ -using ABI_RC.Core.Util; - -namespace NAK.BetterContentLoading.Queue; - -public class PropDownloadQueue : ContentDownloadQueueBase -{ - private readonly Dictionary _ownerToSpawner = new(); // InstanceId -> SpawnerId - - public PropDownloadQueue(BetterDownloadManager manager) : base(manager, 2) { } - - public void QueueDownload(in DownloadInfo info, string instanceId, string spawnerId, Action onComplete = null) - { - _ownerToSpawner[instanceId] = spawnerId; - float priority = CalculatePropPriority(in info, instanceId, spawnerId); - QueueDownload(in info, instanceId, priority, onComplete); - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - if (DownloadOwners.TryGetValue(downloadId, out var owners)) - { - foreach (var instanceId in owners) - { - if (BetterDownloadManager.TryGetPropData(instanceId, out CVRSyncHelper.PropData prop)) - { - BetterContentLoadingMod.Logger.Msg($"Progress for {prop.InstanceId}: {progress:P}"); - } - } - } - } - - protected override float RecalculatePriority(string downloadId) - { - if (!DownloadOwners.TryGetValue(downloadId, out var owners)) - return float.MaxValue; - - float lowestPriority = float.MaxValue; - DownloadInfo? downloadInfo = null; - - // Find the queue item to get the DownloadInfo - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - if (queueItem.Info.AssetId != null) - downloadInfo = queueItem.Info; - - if (downloadInfo == null) - return lowestPriority; - - // Calculate priority for each owner and use the lowest (highest priority) value - foreach (var instanceId in owners) - { - if (_ownerToSpawner.TryGetValue(instanceId, out var spawnerId)) - { - var priority = CalculatePropPriority(downloadInfo.Value, instanceId, spawnerId); - lowestPriority = Math.Min(lowestPriority, priority); - } - } - - return lowestPriority; - } - - private float CalculatePropPriority(in DownloadInfo info, string instanceId, string spawnerId) - { - float priority = info.FileSize; - - if (!BetterDownloadManager.TryGetPropData(instanceId, out var prop)) - return priority; - - if (Manager.PrioritizeFriends && BetterDownloadManager.IsPlayerFriend(spawnerId)) - priority *= 0.5f; - - if (Manager.PrioritizeDistance && Manager.IsPropWithinPriorityDistance(prop)) - priority *= 0.75f; - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs b/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs deleted file mode 100644 index 1cea689..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadQueue/WorldDownloadQueue.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace NAK.BetterContentLoading.Queue; - -public class WorldDownloadQueue : ContentDownloadQueueBase -{ - private readonly Queue<(DownloadInfo Info, bool JoinOnComplete, bool IsHomeRequest)> _backgroundQueue = new(); - private bool _isProcessingPriorityDownload; - - public WorldDownloadQueue(BetterDownloadManager manager) : base(manager, 1) { } - - public void QueueDownload(in DownloadInfo info, bool joinOnComplete, bool isHomeRequest, Action onComplete = null) - { - if (joinOnComplete || isHomeRequest) - { - // Priority download - clear queue and download immediately - Queue.Clear(); - _isProcessingPriorityDownload = true; - QueueDownload(in info, info.DownloadId, 0f, onComplete); - } - else - { - // Background download - add to background queue - _backgroundQueue.Enqueue((info, false, false)); - if (!_isProcessingPriorityDownload) - ProcessBackgroundQueue(); - } - } - - protected override async Task ProcessDownload(DownloadInfo info) - { - await base.ProcessDownload(info); - - if (_isProcessingPriorityDownload) - { - _isProcessingPriorityDownload = false; - ProcessBackgroundQueue(); - } - } - - protected override void OnDownloadProgress(string downloadId, float progress) - { - BetterContentLoadingMod.Logger.Msg($"World download progress: {progress:P}"); - } - - protected override float RecalculatePriority(string downloadId) - { - // For worlds, priority is based on whether it's a priority download and file size - var queueItem = Queue.Find(x => x.Info.DownloadId == downloadId); - return queueItem.Priority; - } - - private void ProcessBackgroundQueue() - { - while (_backgroundQueue.Count > 0) - { - (DownloadInfo info, var join, var home) = _backgroundQueue.Dequeue(); - QueueDownload(in info, info.DownloadId, info.FileSize, null); - } - } - - protected override void CancelDownload(string downloadId) - { - base.CancelDownload(downloadId); - _backgroundQueue.Clear(); - _isProcessingPriorityDownload = false; - } -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/DownloadState.cs b/BetterContentLoading/BetterContentLoading/DownloadState.cs deleted file mode 100644 index 785396b..0000000 --- a/BetterContentLoading/BetterContentLoading/DownloadState.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NAK.BetterContentLoading; - -public readonly struct DownloadState -{ - public readonly string DownloadId; - public readonly long BytesRead; - public readonly long TotalBytes; - public readonly int ProgressPercent; - public readonly float BytesPerSecond; - - public DownloadState(string downloadId, long bytesRead, long totalBytes, float bytesPerSecond) - { - DownloadId = downloadId; - BytesRead = bytesRead; - TotalBytes = totalBytes; - BytesPerSecond = bytesPerSecond; - ProgressPercent = (int)(((float)bytesRead / totalBytes) * 100f); - } -} - -public interface IDownloadMonitor -{ - void OnDownloadProgress(DownloadState state); - void OnDownloadStarted(string downloadId); - void OnDownloadCompleted(string downloadId, bool success); -} \ No newline at end of file diff --git a/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs b/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs deleted file mode 100644 index 280aeb2..0000000 --- a/BetterContentLoading/BetterContentLoading/Util/ThreadingHelper.cs +++ /dev/null @@ -1,109 +0,0 @@ -using JetBrains.Annotations; -using UnityEngine; - -namespace NAK.BetterContentLoading.Util; - -[PublicAPI] -public static class ThreadingHelper -{ - private static readonly SynchronizationContext _mainThreadContext; - - static ThreadingHelper() - { - _mainThreadContext = SynchronizationContext.Current; - } - - public static bool IsMainThread => SynchronizationContext.Current == _mainThreadContext; - - /// - /// Runs an action on the main thread. Optionally waits for its completion. - /// - public static void RunOnMainThread(Action action, bool waitForCompletion = true, Action onError = null) - { - if (SynchronizationContext.Current == _mainThreadContext) - { - // Already on the main thread - TryExecute(action, onError); - } - else - { - if (waitForCompletion) - { - ManualResetEvent done = new(false); - _mainThreadContext.Post(_ => - { - TryExecute(action, onError); - done.Set(); - }, null); - done.WaitOne(50000); // Block until action is completed - } - else - { - // Fire and forget (don't wait for the action to complete) - _mainThreadContext.Post(_ => TryExecute(action, onError), null); - } - } - } - - /// - /// Runs an action asynchronously on the main thread and returns a Task. - /// - public static Task RunOnMainThreadAsync(Action action, Action onError = null) - { - if (SynchronizationContext.Current == _mainThreadContext) - { - // Already on the main thread - TryExecute(action, onError); - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(); - _mainThreadContext.Post(_ => - { - try - { - TryExecute(action, onError); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }, null); - return tcs.Task; - } - - /// - /// Runs a task on a background thread with cancellation support. - /// - public static async Task RunOffMainThreadAsync(Action action, CancellationToken cancellationToken, Action onError = null) - { - try - { - await Task.Run(() => - { - cancellationToken.ThrowIfCancellationRequested(); - TryExecute(action, onError); - }, cancellationToken); - } - catch (OperationCanceledException) - { - Debug.LogWarning("Task was canceled."); - } - } - - /// - /// Helper method for error handling. - /// - private static void TryExecute(Action action, Action onError) - { - try - { - action(); - } - catch (Exception ex) - { - onError?.Invoke(ex); - } - } -} diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager/DownloadManager.Core.cs deleted file mode 100644 index ce484cd..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Core.cs +++ /dev/null @@ -1,237 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.API.Responses; -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Savior; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - #region Singleton - private static DownloadManager _instance; - public static DownloadManager Instance => _instance ??= new DownloadManager(); - #endregion - - private DownloadManager() - { - - } - - #region Settings - public bool IsDebugEnabled { get; set; } = true; - public bool PrioritizeFriends { get; set; } = true; - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - public int MaxConcurrentDownloads { get; set; } = 3; - public int MaxDownloadBandwidth { get; set; } = 100 * 1024 * 1024; // 100MB default - private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling - - public long MaxAvatarDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default - public long MaxPropDownloadSize { get; set; } = 25 * 1024 * 1024; // 25MB default - - #endregion Settings - - #region State - - // priority -> downloadtask - private readonly SortedList _downloadQueue = new(); - - // downloadId -> downloadtask - private readonly Dictionary _cachedDownloads = new(); - - #endregion State - - #region Public Queue Methods - - public void QueueAvatarDownload( - in DownloadInfo info, - string playerId, - CVRLoadingAvatarController loadingController) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Avatar Download:\n{info.GetLogString()}\n" + - $"PlayerId: {playerId}\n" + - $"LoadingController: {loadingController}"); - } - - if (!ShouldQueueAvatarDownload(info, playerId)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download not eligible: {info.DownloadId}"); - return; - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download already queued: {info.DownloadId}"); - cachedDownload.AddInstantiationTarget(playerId); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.Avatar - }; - task.AddInstantiationTarget(playerId); - task.BasePriority = CalculatePriority(task); - } - - private bool ShouldQueueAvatarDownload( - in DownloadInfo info, - string playerId) - { - // Check if content is incompatible or banned - if (info.TagsData.Incompatible || info.TagsData.AdminBanned) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is incompatible or banned"); - return false; - } - - // Check if player is blocked - if (MetaPort.Instance.blockedUserIds.Contains(playerId)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Player is blocked: {playerId}"); - return false; - } - - // Check if mature content is disabled - UgcTagsData tags = info.TagsData; - if (!MetaPort.Instance.matureContentAllowed && (tags.Gore || tags.Nudity)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Mature content is disabled"); - return false; - } - - // Check file size - if (info.FileSize > MaxAvatarDownloadSize) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar Download too large: {info.FileSize} > {MaxAvatarDownloadSize}"); - return false; - } - - // Get visibility status for the avatar - // ForceHidden means player avatar or avatar itself is forced off. - // ForceShown will bypass all checks and return true. - MetaPort.Instance.SelfModerationManager.GetAvatarVisibility(playerId, info.AssetId, - out bool wasForceHidden, out bool wasForceShown); - - if (!wasForceHidden) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Avatar is not visible either because player avatar or avatar itself is forced off"); - return false; - } - - if (!wasForceShown) - { - // Check content filter settings if not force shown - CVRSettings settings = MetaPort.Instance.settings; - bool isLocalPlayer = playerId == MetaPort.Instance.ownerId; - bool isFriend = Friends.FriendsWith(playerId); - bool CheckFilterSettings(string settingName) - { - int settingVal = settings.GetSettingInt(settingName); - switch (settingVal) - { - // Only Self - case 0 when !isLocalPlayer: - // Only Friends - case 1 when !isFriend: - return false; - } - return true; - } - - if (!CheckFilterSettings("ContentFilterVisibility")) return false; - if (!CheckFilterSettings("ContentFilterNudity")) return false; - if (!CheckFilterSettings("ContentFilterGore")) return false; - if (!CheckFilterSettings("ContentFilterSuggestive")) return false; - if (!CheckFilterSettings("ContentFilterFlashingColors")) return false; - if (!CheckFilterSettings("ContentFilterFlashingLights")) return false; - if (!CheckFilterSettings("ContentFilterScreenEffects")) return false; - if (!CheckFilterSettings("ContentFilterExtremelyBright")) return false; - if (!CheckFilterSettings("ContentFilterViolence")) return false; - if (!CheckFilterSettings("ContentFilterJumpscare")) return false; - if (!CheckFilterSettings("ContentFilterExcessivelyHuge")) return false; - if (!CheckFilterSettings("ContentFilterExcessivelySmall")) return false; - } - - // All eligibility checks passed - return true; - } - - /// - /// Queues a prop download. - /// - /// The download info. - /// The instance ID for the prop. - /// The user who spawned the prop. - public void QueuePropDownload( - in DownloadInfo info, - string instanceId, - string spawnerId) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing Prop Download:\n{info.GetLogString()}\n" + - $"InstanceId: {instanceId}\n" + - $"SpawnerId: {spawnerId}"); - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"Prop Download already queued: {info.DownloadId}"); - cachedDownload.AddInstantiationTarget(instanceId); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.Prop - }; - task.AddInstantiationTarget(instanceId); - task.BasePriority = CalculatePriority(task); - } - - /// - /// Queues a world download. - /// - /// Download info. - /// Whether to load into this world once downloaded. - /// Whether the home world is requested. - public void QueueWorldDownload( - in DownloadInfo info, - bool joinOnComplete, - bool isHomeRequested) - { - if (IsDebugEnabled) - { - BetterContentLoadingMod.Logger.Msg( - $"Queuing World Download:\n{info.GetLogString()}\n" + - $"JoinOnComplete: {joinOnComplete}\n" + - $"IsHomeRequested: {isHomeRequested}"); - } - - if (_cachedDownloads.TryGetValue(info.DownloadId, out DownloadTask cachedDownload)) - { - if (IsDebugEnabled) BetterContentLoadingMod.Logger.Msg($"World Download already queued: {info.DownloadId}"); - cachedDownload.BasePriority = CalculatePriority(cachedDownload); - return; - } - - DownloadTask task = new() - { - Info = info, - Type = DownloadTaskType.World - }; - task.BasePriority = CalculatePriority(task); - } - - #endregion Public Queue Methods - -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs deleted file mode 100644 index 3664ed4..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Helpers.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - private static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - private static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs deleted file mode 100644 index ae28cbc..0000000 --- a/BetterContentLoading/DownloadManager/DownloadManager.Priority.cs +++ /dev/null @@ -1,38 +0,0 @@ -using ABI_RC.Core.Player; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager -{ - private float CalculatePriority(DownloadTask task) - { - return task.Type switch - { - DownloadTaskType.Avatar => CalculateAvatarPriority(task), - // DownloadTaskType.Prop => CalculatePropPriority(task2), - // DownloadTaskType.World => CalculateWorldPriority(task2), - _ => task.Info.FileSize - }; - } - - private float CalculateAvatarPriority(DownloadTask task) - { - float priority = task.Info.FileSize; - - foreach (string target in task.InstantiationTargets) - { - if (IsPlayerLocal(target)) return 0f; - - if (!TryGetPlayerEntity(target, out CVRPlayerEntity player)) - return priority; - - if (PrioritizeFriends && IsPlayerFriend(target)) - priority *= 0.5f; - - if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - } - - return priority; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Main.cs b/BetterContentLoading/DownloadManager/DownloadTask.Main.cs deleted file mode 100644 index 5397cf7..0000000 --- a/BetterContentLoading/DownloadManager/DownloadTask.Main.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadTask -{ - public DownloadInfo Info { get; set; } - public DownloadTaskStatus Status { get; set; } - public DownloadTaskType Type { get; set; } - - public float BasePriority { get; set; } - public float CurrentPriority => BasePriority * (1 + Progress / 100f); - - public long BytesRead { get; set; } - public int Progress { get; set; } - - /// The avatar/prop instances that wish to utilize this bundle. - public List InstantiationTargets { get; } = new(); - - public void AddInstantiationTarget(string target) - { - if (InstantiationTargets.Contains(target)) - return; - - InstantiationTargets.Add(target); - } - - public void RemoveInstantiationTarget(string target) - { - if (!InstantiationTargets.Contains(target)) - return; - - InstantiationTargets.Remove(target); - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs b/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs deleted file mode 100644 index f697a5e..0000000 --- a/BetterContentLoading/DownloadManager/DownloadTask.Priority.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadTask -{ - -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs b/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs deleted file mode 100644 index df844d5..0000000 --- a/BetterContentLoading/DownloadManager2/ConcurrentPriorityQueue.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NAK.BetterContentLoading; - -public class ConcurrentPriorityQueue where TPriority : IComparable -{ - private readonly object _lock = new(); - private readonly SortedDictionary> _queues = new(); - - public void Enqueue(TElement item, TPriority priority) - { - lock (_lock) - { - if (!_queues.TryGetValue(priority, out var queue)) - { - queue = new Queue(); - _queues[priority] = queue; - } - queue.Enqueue(item); - } - } - - public bool TryDequeue(out TElement item, out TPriority priority) - { - lock (_lock) - { - if (_queues.Count == 0) - { - item = default; - priority = default; - return false; - } - - var firstQueue = _queues.First(); - priority = firstQueue.Key; - var queue = firstQueue.Value; - item = queue.Dequeue(); - - if (queue.Count == 0) - _queues.Remove(priority); - - return true; - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs deleted file mode 100644 index 54d5792..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Bandwidth.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private void StartBandwidthMonitor() - { - Task.Run(async () => - { - while (true) - { - await Task.Delay(1000); - Interlocked.Exchange(ref _bytesReadLastSecond, 0); - Interlocked.Exchange(ref _completedDownloads, 0); - } - }); - } - - private int ComputeUsableBandwidthPerDownload() - { - var activeCount = _activeDownloads.Count; - if (activeCount == 0) return MaxDownloadBandwidth; - return MaxDownloadBandwidth / activeCount; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs deleted file mode 100644 index 63fd4c2..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Core.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Concurrent; -using ABI_RC.Core; -using ABI_RC.Core.IO; -using ABI_RC.Core.IO.AssetManagement; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - #region Singleton - private static DownloadManager2 _instance; - public static DownloadManager2 Instance => _instance ??= new DownloadManager2(); - #endregion - - #region Settings - public bool IsDebugEnabled { get; set; } = true; - public bool PrioritizeFriends { get; set; } = true; - public bool PrioritizeDistance { get; set; } = true; - public float PriorityDownloadDistance { get; set; } = 25f; - public int MaxConcurrentDownloads { get; set; } = 5; - public int MaxDownloadBandwidth { get; set; } // 100MB default - private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling - #endregion - - #region State - private readonly ConcurrentDictionary _activeDownloads; - private readonly ConcurrentPriorityQueue _queuedDownloads; - private readonly ConcurrentDictionary _completedDownloads; - private readonly object _downloadLock = new(); - private long _bytesReadLastSecond; - #endregion - - private DownloadManager2() - { - _activeDownloads = new ConcurrentDictionary(); - _queuedDownloads = new ConcurrentPriorityQueue(); - MaxDownloadBandwidth = 100 * 1024 * 1024; - StartBandwidthMonitor(); - } - - public async Task QueueAvatarDownload(DownloadInfo info, string playerId) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Avatar); - task2.AddTarget(playerId); - QueueDownload(task2); - return false; - } - - public async Task QueuePropDownload(DownloadInfo info, string instanceId, string spawnerId) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Prop); - task2.AddTarget(instanceId, spawnerId); - QueueDownload(task2); - return false; - } - - public async Task QueueWorldDownload(DownloadInfo info, bool loadOnComplete) - { - if (await ValidateAndCheckCache(info)) - return true; - - DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.World, loadOnComplete); - QueueDownload(task2); - return false; - } - - private async Task ValidateAndCheckCache(DownloadInfo info) - { - // Check if already cached and up to date - if (await CacheManager.Instance.IsCachedFileUpToDate( - info.AssetId, - info.FileId, - info.FileHash)) - { - return true; - } - - // Validate disk space - var filePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); - if (!CVRTools.HasEnoughDiskSpace(filePath, info.FileSize)) - { - BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {info.AssetId}"); - return false; - } - - // Ensure cache directory exists - CacheManager.Instance.EnsureCacheDirectoryExists(info.AssetId); - return false; - } - - private DownloadTask2 GetOrCreateDownloadTask(DownloadInfo info, DownloadTaskType type, bool loadOnComplete = false) - { - // Check if task already exists in active downloads - if (_activeDownloads.TryGetValue(info.DownloadId, out var activeTask)) - return activeTask; - - // Check if task exists in queued downloads - var queuedTask = _queuedDownloads.TryFind(t => t.Info.DownloadId == info.DownloadId); - if (queuedTask != null) - return queuedTask; - - // Create new task - var cachePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId); - return new DownloadTask2(info, cachePath, type, loadOnComplete); - } - - public bool TryFindTask(string downloadId, out DownloadTask2 task2) - { - return _activeDownloads.TryGetValue(downloadId, out task2) || - _completedDownloads.TryGetValue(downloadId, out task2) || - TryFindQueuedTask(downloadId, out task2); - } - - private bool TryFindQueuedTask(string downloadId, out DownloadTask2 task2) - { - task2 = _queuedDownloads.UnorderedItems - .FirstOrDefault(x => x.Element.Info.DownloadId == downloadId).Element; - return task2 != null; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs deleted file mode 100644 index 2dd205d..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Helpers.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ABI_RC.Core.Networking.IO.Social; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using UnityEngine; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity) - { - CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId); - if (player == null) - { - // BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}"); - playerEntity = null; - return false; - } - playerEntity = player; - return true; - } - - internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData) - { - CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId); - if (prop == null) - { - // BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}"); - propData = null; - return false; - } - propData = prop; - return true; - } - - private static bool IsPlayerLocal(string playerId) - { - return playerId == MetaPort.Instance.ownerId; - } - - private static bool IsPlayerFriend(string playerId) - { - return Friends.FriendsWith(playerId); - } - - private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player) - { - if (player.PuppetMaster == null) return false; - return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance; - } - - internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop) - { - Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ); - return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs deleted file mode 100644 index 1132b8e..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Priority.cs +++ /dev/null @@ -1,47 +0,0 @@ -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private float CalculatePriority(DownloadTask2 task2) - { - return task2.Type switch - { - DownloadTaskType.Avatar => CalculateAvatarPriority(task2), - DownloadTaskType.Prop => CalculatePropPriority(task2), - DownloadTaskType.World => CalculateWorldPriority(task2), - _ => task2.Info.FileSize - }; - } - - private float CalculateAvatarPriority(DownloadTask2 task2) - { - float priority = task2.Info.FileSize; - - if (IsPlayerLocal(task2.PlayerId)) - return 0f; - - if (!TryGetPlayerEntity(task2.PlayerId, out var player)) - return priority; - - if (PrioritizeFriends && IsPlayerFriend(task2.PlayerId)) - priority *= 0.5f; - - if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player)) - priority *= 0.75f; - - // Factor in download progress - priority *= (1 + task2.Progress / 100f); - - return priority; - } - - private float CalculatePropPriority(DownloadTask2 task2) - { - float priority = task2.Info.FileSize; - - if (IsPlayerLocal(task2.PlayerId)) - return 0f; - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs deleted file mode 100644 index 698ac06..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Processing.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Diagnostics; -using System.Net.Http.Headers; -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - private async Task ProcessDownload(DownloadTask2 task2) - { - using var client = new HttpClient(); - - // Set up resume headers if we have a resume token - if (!string.IsNullOrEmpty(task2.ResumeToken)) - { - client.DefaultRequestHeaders.Range = new RangeHeaderValue(task2.BytesRead, null); - } - - using var response = await client.GetAsync(task2.Info.AssetUrl, HttpCompletionOption.ResponseHeadersRead); - using var dataStream = await response.Content.ReadAsStreamAsync(); - - // Open file in append mode if resuming, otherwise create new - using var fileStream = new FileStream( - task2.TargetPath, - string.IsNullOrEmpty(task2.ResumeToken) ? FileMode.Create : FileMode.Append, - FileAccess.Write); - - bool isEligibleForThrottle = task2.Info.FileSize > THROTTLE_THRESHOLD; - var stopwatch = new Stopwatch(); - - int bytesRead; - do - { - if (task2.Status == DownloadTaskStatus.Paused) - { - task2.ResumeToken = GenerateResumeToken(task2); - await fileStream.FlushAsync(); - return; - } - - if (task2.Status != DownloadTaskStatus.Downloading) - { - HandleCancellation(task2, dataStream, fileStream); - return; - } - - stopwatch.Restart(); - var lengthToRead = isEligibleForThrottle ? - ComputeUsableBandwidthPerDownload() : - 16384; - - var buffer = new byte[lengthToRead]; - bytesRead = await dataStream.ReadAsync(buffer, 0, lengthToRead); - - if (isEligibleForThrottle) - Interlocked.Add(ref _bytesReadLastSecond, bytesRead); - - await fileStream.WriteAsync(buffer, 0, bytesRead); - UpdateProgress(task2, bytesRead); - - } while (bytesRead > 0); - - CompleteDownload(task2); - } - - private string GenerateResumeToken(DownloadTask2 task2) - { - // Generate a unique token that includes file position and hash - return $"{task2.BytesRead}:{DateTime.UtcNow.Ticks}"; - } - - private async Task FinalizeDownload(DownloadTask2 task2) - { - var tempPath = task2.CachePath + ".tmp"; - - try - { - // Decrypt the file if needed - if (task2.Info.EncryptionAlgorithm != 0) - { - await DecryptFile(tempPath, task2.CachePath, task2.Info); - File.Delete(tempPath); - } - else - { - File.Move(tempPath, task2.CachePath); - } - - CompleteDownload(task2); - } - catch (Exception ex) - { - // _logger.Error($"Failed to finalize download for {task.Info.AssetId}: {ex.Message}"); - task2.Status = DownloadTaskStatus.Failed; - File.Delete(tempPath); - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - } - } - - private async Task DecryptFile(string sourcePath, string targetPath, DownloadInfo info) - { - // Implementation of file decryption based on EncryptionAlgorithm - // This would use the FileKey from the DownloadInfo - throw new NotImplementedException("File decryption not implemented"); - } - - private void HandleCancellation(DownloadTask2 task2, Stream dataStream, Stream fileStream) - { - if (task2.Status != DownloadTaskStatus.Failed) - task2.Status = DownloadTaskStatus.Cancelled; - - dataStream.Close(); - fileStream.Close(); - - task2.Progress = 0; - task2.BytesRead = 0; - - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - } - - private void UpdateProgress(DownloadTask2 task2, int bytesRead) - { - task2.BytesRead += bytesRead; - task2.Progress = Math.Clamp( - (int)(((float)task2.BytesRead / task2.Info.FileSize) * 100f), - 0, 100); - } - - private void CompleteDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Complete; - task2.Progress = 100; - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - _completedDownloads.TryAdd(task2.Info.DownloadId, task2); - - lock (_downloadLock) - { - if (_queuedDownloads.TryDequeue(out var nextTask, out _)) - { - StartDownload(nextTask); - } - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs b/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs deleted file mode 100644 index e8a580a..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadManager.Queue.cs +++ /dev/null @@ -1,80 +0,0 @@ -using ABI_RC.Core.IO; - -namespace NAK.BetterContentLoading; - -public partial class DownloadManager2 -{ - public void QueueDownload(DownloadTask2 newTask2) - { - if (_completedDownloads.TryGetValue(newTask2.Info.DownloadId, out var completedTask)) - { - completedTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); - return; - } - - if (TryFindTask(newTask2.Info.DownloadId, out var existingTask)) - { - existingTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault()); - RecalculatePriority(existingTask); - return; - } - - float priority = CalculatePriority(newTask2); - newTask2.CurrentPriority = priority; - - lock (_downloadLock) - { - if (_activeDownloads.Count < MaxConcurrentDownloads) - { - StartDownload(newTask2); - return; - } - - var lowestPriorityTask = _activeDownloads.Values - .OrderByDescending(t => t.CurrentPriority * (1 + t.Progress / 100f)) - .LastOrDefault(); - - if (lowestPriorityTask != null && priority < lowestPriorityTask.CurrentPriority) - { - PauseDownload(lowestPriorityTask); - StartDownload(newTask2); - } - else - { - _queuedDownloads.Enqueue(newTask2, priority); - } - } - } - - private void StartDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Downloading; - _activeDownloads.TryAdd(task2.Info.DownloadId, task2); - Task.Run(() => ProcessDownload(task2)); - } - - private void PauseDownload(DownloadTask2 task2) - { - task2.Status = DownloadTaskStatus.Queued; - _activeDownloads.TryRemove(task2.Info.DownloadId, out _); - _queuedDownloads.Enqueue(task2, task2.CurrentPriority); - } - - - - - private void RecalculatePriority(DownloadTask2 task2) - { - var newPriority = CalculatePriority(task2); - if (Math.Abs(newPriority - task2.CurrentPriority) < float.Epsilon) - return; - - task2.CurrentPriority = newPriority; - - if (task2.Status == DownloadTaskStatus.Queued || task2.Status == DownloadTaskStatus.Paused) - { - // Re-enqueue with new priority - _queuedDownloads.UpdatePriority(task2, newPriority); - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/DownloadManager2/DownloadTask2.cs b/BetterContentLoading/DownloadManager2/DownloadTask2.cs deleted file mode 100644 index 0c531f6..0000000 --- a/BetterContentLoading/DownloadManager2/DownloadTask2.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NAK.BetterContentLoading; - -public class DownloadTask2 -{ - public DownloadInfo Info { get; } - public DownloadTaskStatus Status { get; set; } - public DownloadTaskType Type { get; } - - public float BasePriority { get; set; } - public float CurrentPriority => BasePriority * (1 + Progress / 100f); - - public long BytesRead { get; set; } - public int Progress { get; set; } - - public string CachePath { get; } - public Dictionary Targets { get; } // Key: targetId (playerId/instanceId), Value: spawnerId - public bool LoadOnComplete { get; } // For worlds only - - public DownloadTask2( - DownloadInfo info, - string cachePath, - DownloadTaskType type, - bool loadOnComplete = false) - { - Info = info; - CachePath = cachePath; - Type = type; - LoadOnComplete = loadOnComplete; - Targets = new Dictionary(); - Status = DownloadTaskStatus.Queued; - } - - public void AddTarget(string targetId, string spawnerId = null) - { - if (Type == DownloadTaskType.World && Targets.Count > 0) - throw new InvalidOperationException("World downloads cannot have multiple targets"); - - Targets[targetId] = spawnerId; - } - - public void RemoveTarget(string targetId) - { - Targets.Remove(targetId); - } -} - -public enum DownloadTaskType -{ - Avatar, - Prop, - World -} - -public enum DownloadTaskStatus -{ - Queued, - Downloading, - Paused, - Complete, - Failed, - Cancelled -} \ No newline at end of file diff --git a/BetterContentLoading/Main.cs b/BetterContentLoading/Main.cs deleted file mode 100644 index 5b5d812..0000000 --- a/BetterContentLoading/Main.cs +++ /dev/null @@ -1,37 +0,0 @@ -using MelonLoader; -using NAK.BetterContentLoading.Patches; - -namespace NAK.BetterContentLoading; - -public class BetterContentLoadingMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - #region Melon Events - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ApplyPatches(typeof(CVRDownloadManager_Patches)); - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/BetterContentLoading/ModSettings.cs b/BetterContentLoading/ModSettings.cs deleted file mode 100644 index f07436c..0000000 --- a/BetterContentLoading/ModSettings.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MelonLoader; - -namespace NAK.RCCVirtualSteeringWheel; - -internal static class ModSettings -{ - #region Constants - - private const string ModName = nameof(RCCVirtualSteeringWheel); - - #endregion Constants - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - internal static readonly MelonPreferences_Entry EntryOverrideSteeringRange = - Category.CreateEntry("override_steering_range", false, - "Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?"); - - internal static readonly MelonPreferences_Entry EntryCustomSteeringRange = - Category.CreateEntry("custom_steering_range", 60f, - "Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)"); - - internal static readonly MelonPreferences_Entry EntryInvertSteering = - Category.CreateEntry("invert_steering", false, - "Invert Steering", description: "Inverts the steering direction"); - - #endregion Melon Preferences -} \ No newline at end of file diff --git a/BetterContentLoading/Patches.cs b/BetterContentLoading/Patches.cs deleted file mode 100644 index d3c452b..0000000 --- a/BetterContentLoading/Patches.cs +++ /dev/null @@ -1,71 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.IO; -using ABI_RC.Core.Networking.API; -using ABI_RC.Core.Networking.API.Responses; -using HarmonyLib; -using NAK.BetterContentLoading.Util; - -namespace NAK.BetterContentLoading.Patches; - -internal static class CVRDownloadManager_Patches -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(CVRDownloadManager), nameof(CVRDownloadManager.QueueTask))] - private static bool Prefix_CVRDownloadManager_QueueTask( - string assetId, - DownloadTask2.ObjectType type, - string assetUrl, - string fileId, - long fileSize, - string fileKey, - string toAttach, - string fileHash = null, - UgcTagsData tagsData = null, - CVRLoadingAvatarController loadingAvatarController = null, - bool joinOnComplete = false, - bool isHomeRequested = false, - int compatibilityVersion = 0, - int encryptionAlgorithm = 0, - string spawnerId = null) - { - DownloadInfo info; - - switch (type) - { - case DownloadTask2.ObjectType.Avatar: - info = new DownloadInfo( - assetId, assetUrl, fileId, fileSize, fileKey, fileHash, - compatibilityVersion, encryptionAlgorithm, tagsData); - BetterDownloadManager.Instance.QueueAvatarDownload(in info, toAttach, loadingAvatarController); - return true; - case DownloadTask2.ObjectType.Prop: - info = new DownloadInfo( - assetId, assetUrl, fileId, fileSize, fileKey, fileHash, - compatibilityVersion, encryptionAlgorithm, tagsData); - BetterDownloadManager.Instance.QueuePropDownload(in info, toAttach, spawnerId); - return true; - case DownloadTask2.ObjectType.World: - _ = ThreadingHelper.RunOffMainThreadAsync(() => - { - var response = ApiConnection.MakeRequest( - ApiConnection.ApiOperation.WorldMeta, - new { worldID = assetId } - ); - - if (response?.Result.Data == null) - return; - - info = new DownloadInfo( - assetId, response.Result.Data.FileLocation, response.Result.Data.FileId, - response.Result.Data.FileSize, response.Result.Data.FileKey, response.Result.Data.FileHash, - (int)response.Result.Data.CompatibilityVersion, (int)response.Result.Data.EncryptionAlgorithm, - null); - - BetterDownloadManager.Instance.QueueWorldDownload(in info, joinOnComplete, isHomeRequested); - }, CancellationToken.None); - return true; - default: - return true; - } - } -} \ No newline at end of file diff --git a/BetterContentLoading/Properties/AssemblyInfo.cs b/BetterContentLoading/Properties/AssemblyInfo.cs deleted file mode 100644 index 9ab241d..0000000 --- a/BetterContentLoading/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using MelonLoader; -using NAK.BetterContentLoading; -using NAK.BetterContentLoading.Properties; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.BetterContentLoading))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.BetterContentLoading))] - -[assembly: MelonInfo( - typeof(BetterContentLoadingMod), - nameof(NAK.BetterContentLoading), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BetterContentLoading" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.BetterContentLoading.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.0"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/BetterContentLoading/README.md b/BetterContentLoading/README.md deleted file mode 100644 index 8d9f529..0000000 --- a/BetterContentLoading/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# RCCVirtualSteeringWheel - -Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.~~~~ \ No newline at end of file diff --git a/BetterContentLoading/format.json b/BetterContentLoading/format.json deleted file mode 100644 index f565d36..0000000 --- a/BetterContentLoading/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "RCCVirtualSteeringWheel", - "modversion": "1.0.1", - "gameversion": "2024r177", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n", - "searchtags": [ - "rcc", - "steering", - "vehicle", - "car" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r44/RCCVirtualSteeringWheel.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Initial release", - "embedcolor": "#f61963" -} \ No newline at end of file diff --git a/PlayerColorsAPI/Main.cs b/PlayerColorsAPI/Main.cs deleted file mode 100644 index 9e5634d..0000000 --- a/PlayerColorsAPI/Main.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using MelonLoader; - -namespace NAK.VisualCloneFix; - -public class VisualCloneFixMod : MelonMod -{ - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(VisualCloneFix)); - - internal static readonly MelonPreferences_Entry EntryUseVisualClone = - Category.CreateEntry("use_visual_clone", true, - "Use Visual Clone", description: "Uses the potentially faster Visual Clone setup for the local avatar."); - - #endregion Melon Preferences - - #region Melon Events - - public override void OnInitializeMelon() - { - ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason? - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/PlayerColorsAPI/Patches.cs b/PlayerColorsAPI/Patches.cs deleted file mode 100644 index 2fc9a14..0000000 --- a/PlayerColorsAPI/Patches.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using ABI_RC.Core.Player.LocalClone; -using ABI_RC.Core.Player.TransformHider; -using ABI.CCK.Components; -using HarmonyLib; -using UnityEngine; -using Debug = UnityEngine.Debug; - -namespace NAK.VisualCloneFix; - -public static class Patches -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))] - private static bool OnSetupAvatar(GameObject avatar) - { - if (!VisualCloneFixMod.EntryUseVisualClone.Value) - return true; - - LocalCloneHelper.SetupAvatar(avatar); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(LocalCloneHelper), nameof(LocalCloneHelper.CollectTransformToExclusionMap))] - private static bool CollectTransformToExclusionMap( - Component root, Transform headBone, - ref Dictionary __result) - { - // add an fpr exclusion to the head bone - if (!headBone.TryGetComponent(out FPRExclusion headExclusion)) - { - headExclusion = headBone.gameObject.AddComponent(); - headExclusion.isShown = false; // default to hidden - headExclusion.target = headBone; - } - - MeshHiderExclusion headExclusionBehaviour = new(); - headExclusion.behaviour = headExclusionBehaviour; - headExclusionBehaviour.id = 1; // head bone is always 1 - - // get all FPRExclusions - var fprExclusions = root.GetComponentsInChildren(true); - - // get all valid exclusion targets, and destroy invalid exclusions - Dictionary exclusionTargets = new(); - - int nextId = 2; - foreach (FPRExclusion exclusion in fprExclusions) - { - if (exclusion.target == null - || exclusionTargets.ContainsKey(exclusion.target) - || !exclusion.target.gameObject.scene.IsValid()) - continue; // invalid exclusion - - if (exclusion.behaviour == null) // head exclusion is already created - { - MeshHiderExclusion meshHiderExclusion = new(); - exclusion.behaviour = meshHiderExclusion; - meshHiderExclusion.id = nextId++; - } - - // first to add wins - exclusionTargets.TryAdd(exclusion.target, exclusion); - } - - // process each FPRExclusion (recursive) - int exclusionCount = exclusionTargets.Values.Count; - for (var index = 0; index < exclusionCount; index++) - { - FPRExclusion exclusion = exclusionTargets.Values.ElementAt(index); - ProcessExclusion(exclusion, exclusion.target); - exclusion.UpdateExclusions(); // initial state - } - - __result = exclusionTargets; - return false; - - void ProcessExclusion(FPRExclusion exclusion, Transform transform) - { - if (exclusionTargets.ContainsKey(transform) - && exclusionTargets[transform] != exclusion) return; // found other exclusion root - - exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) - foreach (Transform child in transform) - ProcessExclusion(exclusion, child); // process children - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SkinnedLocalClone), nameof(SkinnedLocalClone.FindExclusionVertList))] - private static bool FindExclusionVertList( - SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, - ref int[] __result) - { - // Start the stopwatch - Stopwatch stopwatch = new(); - stopwatch.Start(); - - var boneWeights = renderer.sharedMesh.boneWeights; - var bones = renderer.bones; - int boneCount = bones.Length; - - bool[] boneHasExclusion = new bool[boneCount]; - - // Populate the weights array - for (int i = 0; i < boneCount; i++) - { - Transform bone = bones[i]; - if (bone == null) continue; - if (exclusions.ContainsKey(bone)) - boneHasExclusion[i] = true; - } - - const float minWeightThreshold = 0.2f; - - int[] vertexIndices = new int[renderer.sharedMesh.vertexCount]; - - // Check bone weights and add vertex to exclusion list if needed - for (int i = 0; i < boneWeights.Length; i++) - { - BoneWeight weight = boneWeights[i]; - Transform bone; - - if (boneHasExclusion[weight.boneIndex0] && weight.weight0 > minWeightThreshold) - bone = bones[weight.boneIndex0]; - else if (boneHasExclusion[weight.boneIndex1] && weight.weight1 > minWeightThreshold) - bone = bones[weight.boneIndex1]; - else if (boneHasExclusion[weight.boneIndex2] && weight.weight2 > minWeightThreshold) - bone = bones[weight.boneIndex2]; - else if (boneHasExclusion[weight.boneIndex3] && weight.weight3 > minWeightThreshold) - bone = bones[weight.boneIndex3]; - else continue; - - if (exclusions.TryGetValue(bone, out FPRExclusion exclusion)) - vertexIndices[i] = ((MeshHiderExclusion)(exclusion.behaviour)).id; - } - - // Stop the stopwatch - stopwatch.Stop(); - - // Log the execution time - Debug.Log($"FindExclusionVertList execution time: {stopwatch.ElapsedMilliseconds} ms"); - - __result = vertexIndices; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MeshHiderExclusion), nameof(MeshHiderExclusion.UpdateExclusions))] - private static bool OnUpdateExclusions(bool isShown, bool shrinkToZero, ref int ___id) - { - if (isShown) LocalCloneManager.cullingMask &= ~(1 << ___id); - else LocalCloneManager.cullingMask |= 1 << ___id; - return false; - } -} \ No newline at end of file diff --git a/PlayerColorsAPI/PlayerColorsAPI.csproj b/PlayerColorsAPI/PlayerColorsAPI.csproj deleted file mode 100644 index 8dc4bcd..0000000 --- a/PlayerColorsAPI/PlayerColorsAPI.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - LocalCloneFix - - diff --git a/PlayerColorsAPI/Properties/AssemblyInfo.cs b/PlayerColorsAPI/Properties/AssemblyInfo.cs deleted file mode 100644 index 10b75bb..0000000 --- a/PlayerColorsAPI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MelonLoader; -using NAK.VisualCloneFix.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.VisualCloneFix))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.VisualCloneFix))] - -[assembly: MelonInfo( - typeof(NAK.VisualCloneFix.VisualCloneFixMod), - nameof(NAK.VisualCloneFix), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.VisualCloneFix.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.1"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/PlayerColorsAPI/README.md b/PlayerColorsAPI/README.md deleted file mode 100644 index cc12a9c..0000000 --- a/PlayerColorsAPI/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# VisualCloneFix - -Fixes the Visual Clone system and allows you to use it again. - -Using the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load. - -**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/PlayerColorsAPI/format.json b/PlayerColorsAPI/format.json deleted file mode 100644 index 6634e9f..0000000 --- a/PlayerColorsAPI/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 221, - "name": "VisualCloneFix", - "modversion": "1.0.1", - "gameversion": "2024r175", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Fixes the Visual Clone system and allows you to use it again.\n\nUsing the Visual Clone should be faster than the default Head Hiding & Shadow Clones, but will add a longer hitch on initial avatar load.\n\n**NOTE:** The Visual Clone is still an experimental feature that was temporarily removed in [ChilloutVR 2024r175 Hotfix 1](https://abinteractive.net/blog/chilloutvr_2024r175_hotfix_1), so there may be bugs or issues with it.", - "searchtags": [ - "visual", - "clone", - "head", - "hiding" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/VisualCloneFix.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/VisualCloneFix/", - "changelog": "- Fixed FPRExclusions IsShown state being inverted when toggled.\n- Fixed head FPRExclusion generation not checking for existing exclusion.\n- Sped up FindExclusionVertList by 100x by not being an idiot. This heavily reduces avatar hitch with Visual Clone active.", - "embedcolor": "#f61963" -} \ No newline at end of file From 7ad549b0bb1a408b90a0f20ab764a553edbcaa9b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:08:25 -0500 Subject: [PATCH 080/188] dead --- .../CameraExperiments.csproj | 6 - .Deprecated/CameraExperiments/Main.cs | 170 ------------------ .../Properties/AssemblyInfo.cs | 32 ---- .Deprecated/CameraExperiments/README.md | 14 -- .Deprecated/CameraExperiments/format.json | 23 --- 5 files changed, 245 deletions(-) delete mode 100644 .Deprecated/CameraExperiments/CameraExperiments.csproj delete mode 100644 .Deprecated/CameraExperiments/Main.cs delete mode 100644 .Deprecated/CameraExperiments/Properties/AssemblyInfo.cs delete mode 100644 .Deprecated/CameraExperiments/README.md delete mode 100644 .Deprecated/CameraExperiments/format.json diff --git a/.Deprecated/CameraExperiments/CameraExperiments.csproj b/.Deprecated/CameraExperiments/CameraExperiments.csproj deleted file mode 100644 index 728edb7..0000000 --- a/.Deprecated/CameraExperiments/CameraExperiments.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - net48 - - diff --git a/.Deprecated/CameraExperiments/Main.cs b/.Deprecated/CameraExperiments/Main.cs deleted file mode 100644 index 51035fd..0000000 --- a/.Deprecated/CameraExperiments/Main.cs +++ /dev/null @@ -1,170 +0,0 @@ -using ABI_RC.Core; -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Player; -using HarmonyLib; -using MagicaCloth2; -using MelonLoader; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using UnityEngine; - -namespace NAK.WhereAmIPointing; - -public class WhereAmIPointingMod : MelonMod -{ - #region Melon Preferences - - // cannot disable because then id need extra logic to reset the alpha :) - // private const string SettingsCategory = nameof(WhereAmIPointingMod); - // - // private static readonly MelonPreferences_Category Category = - // MelonPreferences.CreateCategory(SettingsCategory); - // - // private static readonly MelonPreferences_Entry Entry_Enabled = - // Category.CreateEntry("enabled", true, display_name: "Enabled",description: "Toggle WhereAmIPointingMod entirely."); - - #endregion Melon Preferences - - public override void OnInitializeMelon() - { - ApplyPatches(typeof(TransformManager_Patches)); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - internal static class TransformManager_Patches - { - // Patch for EnableTransform(DataChunk, bool) - [HarmonyPrefix] - [HarmonyPatch(typeof(TransformManager), nameof(TransformManager.EnableTransform), new[] { typeof(DataChunk), typeof(bool) })] - private static bool OnEnableTransformChunk(TransformManager __instance, DataChunk c, bool sw, ref NativeArray ___flagArray) - { - try - { - // Enhanced validation - if (!__instance.IsValid()) - return false; - - if (___flagArray == null || !___flagArray.IsCreated) - { - Debug.LogWarning("[MagicaCloth2] EnableTransform failed: Flag array is invalid or disposed"); - return false; - } - - if (!c.IsValid || c.startIndex < 0 || c.startIndex + c.dataLength > ___flagArray.Length) - { - Debug.LogWarning($"[MagicaCloth2] EnableTransform failed: Invalid chunk parameters. Start: {c.startIndex}, Length: {c.dataLength}, Array Length: {___flagArray.Length}"); - return false; - } - - // Create and run the job with additional safety - SafeEnableTransformJob job = new() - { - chunk = c, - sw = sw, - flagList = ___flagArray, - maxLength = ___flagArray.Length - }; - - try - { - job.Run(); - } - catch (Exception ex) - { - Debug.LogError($"[MagicaCloth2] Error in EnableTransform job execution: {ex.Message}"); - return false; - } - - return false; // Prevent original method execution - } - catch (Exception ex) - { - Debug.LogError($"[MagicaCloth2] Critical error in EnableTransform patch: {ex.Message}"); - return false; - } - } - - // Patch for EnableTransform(int, bool) - [HarmonyPrefix] - [HarmonyPatch(typeof(TransformManager), nameof(TransformManager.EnableTransform), new[] { typeof(int), typeof(bool) })] - private static bool OnEnableTransformIndex(TransformManager __instance, int index, bool sw, ref NativeArray ___flagArray) - { - try - { - // Enhanced validation - if (!__instance.IsValid()) - return false; - - if (___flagArray == null || !___flagArray.IsCreated) - { - Debug.LogWarning("[MagicaCloth2] EnableTransform failed: Flag array is invalid or disposed"); - return false; - } - - if (index < 0 || index >= ___flagArray.Length) - { - Debug.LogWarning($"[MagicaCloth2] EnableTransform failed: Index {index} out of range [0, {___flagArray.Length})"); - return false; - } - - // Safely modify the flag - var flag = ___flagArray[index]; - if (flag.Value == 0) - return false; - - flag.SetFlag(TransformManager.Flag_Enable, sw); - ___flagArray[index] = flag; - - return false; // Prevent original method execution - } - catch (Exception ex) - { - Debug.LogError($"[MagicaCloth2] Critical error in EnableTransform patch: {ex.Message}"); - return false; - } - } - - [BurstCompile] - private struct SafeEnableTransformJob : IJob - { - public DataChunk chunk; - public bool sw; - public NativeArray flagList; - [ReadOnly] public int maxLength; - - public void Execute() - { - // Additional bounds checking - if (chunk.startIndex < 0 || chunk.startIndex + chunk.dataLength > maxLength) - return; - - for (int i = 0; i < chunk.dataLength; i++) - { - int index = chunk.startIndex + i; - if (index >= maxLength) - break; - - ExBitFlag8 flag = flagList[index]; - if (flag.Value == 0) - continue; - - flag.SetFlag(TransformManager.Flag_Enable, sw); - flagList[index] = flag; - } - } - } - } -} \ No newline at end of file diff --git a/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs b/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs deleted file mode 100644 index 48c359f..0000000 --- a/.Deprecated/CameraExperiments/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NAK.WhereAmIPointing.Properties; -using MelonLoader; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.WhereAmIPointing))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.WhereAmIPointing))] - -[assembly: MelonInfo( - typeof(NAK.WhereAmIPointing.WhereAmIPointingMod), - nameof(NAK.WhereAmIPointing), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.WhereAmIPointing.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.1"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/.Deprecated/CameraExperiments/README.md b/.Deprecated/CameraExperiments/README.md deleted file mode 100644 index c78a56a..0000000 --- a/.Deprecated/CameraExperiments/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# WhereAmIPointing - -Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/.Deprecated/CameraExperiments/format.json b/.Deprecated/CameraExperiments/format.json deleted file mode 100644 index 654911a..0000000 --- a/.Deprecated/CameraExperiments/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 234, - "name": "WhereAmIPointing", - "modversion": "1.0.1", - "gameversion": "2024r175", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.", - "searchtags": [ - "controller", - "ray", - "line", - "tomato" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/", - "changelog": "- Fixed line renderer alpha not being reset when the menu is closed.", - "embedcolor": "#f61963" -} \ No newline at end of file From 8cffe8a5be5fd9529bda723edbef8abd8c4b2c25 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:16:00 -0500 Subject: [PATCH 081/188] [NAK_CVR_Mods] Update solution --- NAK_CVR_Mods.sln | 654 +++++++++++++++++++---------------------- References.Items.props | 20 ++ 2 files changed, 319 insertions(+), 355 deletions(-) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 082aeae..5cc9493 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -1,355 +1,299 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32630.192 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CVRGizmos", "CVRGizmos\CVRGizmos.csproj", "{CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FuckToes", "FuckToes\FuckToes.csproj", "{79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GestureLock", "GestureLock\GestureLock.csproj", "{45A65AEB-4BFC-4E47-B181-BBB43BD81283}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathCamDisabler", "PathCamDisabler\PathCamDisabler.csproj", "{98169FD2-5CEB-46D1-A320-D7E06F82C9E0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustment\FOVAdjustment.csproj", "{EE552804-30B1-49CF-BBDE-3B312895AFF7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatBoxExtensions", "ChatBoxExtensions\ChatBoxExtensions.csproj", "{0E1DD746-33A1-4179-AE70-8FB83AC40ABC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysicsGunMod", "PhysicsGunMod\PhysicsGunMod.csproj", "{F94DDB73-9041-4F5C-AD43-6960701E8417}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nevermind", "Nevermind\Nevermind.csproj", "{AC4857DD-F6D9-436D-A3EE-D148A518E642}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadowCloneFallback", "ShadowCloneFallback\ShadowCloneFallback.csproj", "{69AF3C10-1BB1-4746-B697-B5A81D78C8D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StopClosingMyMenuOnWorldLoad", "StopClosingMyMenuOnWorldLoad\StopClosingMyMenuOnWorldLoad.csproj", "{9FA83514-13F8-412C-9790-C2B750E0E7E7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelativeSync", "RelativeSync\RelativeSync.csproj", "{B48C8F19-9451-4EE2-999F-82C0033CDE2C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptingSpoofer", "ScriptingSpoofer\ScriptingSpoofer.csproj", "{6B4396C7-B451-4FFD-87B6-3ED8377AC308}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaTTS", "LuaTTS\LuaTTS.csproj", "{24A069F4-4D69-4ABD-AA16-77765469245B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyPrune", "LazyPrune\LazyPrune.csproj", "{8FA6D481-5801-4E4C-822E-DE561155D22B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReconnectionSystemFix", "ReconnectionSystemFix\ReconnectionSystemFix.csproj", "{05C427DD-1261-4AAD-B316-A551FC126F2C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AASDefaultProfileFix", "AASDefaultProfileFix\AASDefaultProfileFix.csproj", "{C6794B18-E785-4F91-A517-3A2A8006E008}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginShift", "OriginShift\OriginShift.csproj", "{F381F604-9C16-4870-AD49-4BD7CA3F36DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrollFlight", "ScrollFlight\ScrollFlight.csproj", "{1B5D7DCB-01A4-4988-8B25-211948AEED76}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portals", "Portals\Portals.csproj", "{BE9629C2-8461-481C-B267-1B8A1805DCD7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropLoadingHexagon", "PropLoadingHexagon\PropLoadingHexagon.csproj", "{642A2BC7-C027-4F8F-969C-EF0F867936FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IKSimulatedRootAngleFix", "IKSimulatedRootAngleFix\IKSimulatedRootAngleFix.csproj", "{D11214B0-94FE-4008-8D1B-3DC8614466B3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropPropTweak", "DropPropTweak\DropPropTweak.csproj", "{2CC1F7C6-A953-4008-8C10-C7592EB401E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualCloneFix", "VisualCloneFix\VisualCloneFix.csproj", "{39915C4C-B555-4CB9-890F-26DE1388BC2E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractionTest", "InteractionTest\InteractionTest.csproj", "{7C675E64-0A2D-4B34-B6D1-5D6AA369A520}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", "KeepVelocityOnExitFlight\KeepVelocityOnExitFlight.csproj", "{0BB3D187-BBBA-4C58-B246-102342BE5E8C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartReticle", "SmartReticle\SmartReticle.csproj", "{3C992D0C-9729-438E-800C-496B7EFFFB25}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhereAmIPointing", "WhereAmIPointing\WhereAmIPointing.csproj", "{E285BCC9-D953-4066-8FA2-97EA28EB348E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarScaleMod", "AvatarScaleMod\AvatarScaleMod.csproj", "{A38E687F-8B6B-499E-ABC9-BD95C53DD391}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\SmootherRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", "LuaNetworkVariables\LuaNetworkVariables.csproj", "{A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchWithSpacesFix", "SearchWithSpacesFix\SearchWithSpacesFix.csproj", "{0640B2BF-1EF5-4FFE-A144-0368748FC48B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComponentMonitor", "ComponentMonitor\ComponentMonitor.csproj", "{FC91FFFE-1E0A-4F59-8802-BFF99152AD07}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShareBubbles", "ShareBubbles\ShareBubbles.csproj", "{ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutualMute", "MutualMute\MutualMute.csproj", "{6E315182-CC9F-4F62-8385-5E26EFA3B98A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BullshitWatcher", "BullshitWatcher\BullshitWatcher.csproj", "{09238300-4583-45C6-A997-025CBDC44C24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LegacyContentMitigation", "LegacyContentMitigation\LegacyContentMitigation.csproj", "{21FDAB94-5014-488D-86C7-A366F1902B24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterContentLoading", "BetterContentLoading\BetterContentLoading.csproj", "{FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CameraExperiments", "CameraExperiments\CameraExperiments.csproj", "{71CBD7CC-C787-4796-B05E-4F3BC3C28B48}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteAvatarDisablingCameraOnFirstFrameFix", "RemoteAvatarDisablingCameraOnFirstFrameFix\RemoteAvatarDisablingCameraOnFirstFrameFix.csproj", "{42E626F7-9A7E-4F55-B02C-16EB56E2B540}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarCloneTest", "AvatarCloneTest\AvatarCloneTest.csproj", "{8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckOffUICamera", "FuckOffUICamera\FuckOffUICamera.csproj", "{262A8AE0-E610-405F-B4EC-DB714FB54C00}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomRichPresence", "CustomRichPresence\CustomRichPresence.csproj", "{E5F07862-5715-470D-B324-19BDEBB2AA4D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckMagicaCloth2", "FuckMagicaCloth2\FuckMagicaCloth2.csproj", "{21B591A0-F6E5-4645-BF2D-4E71F47394A7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperAwesomeMod", "SuperAwesomeMod\SuperAwesomeMod.csproj", "{F093BDE5-1824-459E-B86E-B9F79B548E58}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlToUnlockMouse", "ControlToUnlockMouse\ControlToUnlockMouse.csproj", "{EDA96974-0BEA-404B-8EED-F19CCA2C95A8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", ".DepricatedMods\EzCurls\EzCurls.csproj", "{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.Build.0 = Release|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.Build.0 = Release|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.Build.0 = Release|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.Build.0 = Release|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.Build.0 = Release|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.Build.0 = Release|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU - {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.Build.0 = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.Build.0 = Release|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.Build.0 = Release|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.Build.0 = Release|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.Build.0 = Release|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.Build.0 = Release|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.Build.0 = Release|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.Build.0 = Release|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.Build.0 = Release|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.Build.0 = Release|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.Build.0 = Release|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.Build.0 = Release|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.Build.0 = Release|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.Build.0 = Release|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.Build.0 = Release|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.Build.0 = Release|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.Build.0 = Release|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.Build.0 = Release|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.Build.0 = Release|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.Build.0 = Release|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.Build.0 = Release|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.Build.0 = Release|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.Build.0 = Release|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.Build.0 = Release|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU - {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.Build.0 = Release|Any CPU - {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.Build.0 = Release|Any CPU - {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.Build.0 = Release|Any CPU - {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.Build.0 = Release|Any CPU - {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.Build.0 = Release|Any CPU - {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.Build.0 = Release|Any CPU - {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.Build.0 = Release|Any CPU - {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.Build.0 = Release|Any CPU - {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.Build.0 = Release|Any CPU - {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.Build.0 = Release|Any CPU - {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.Build.0 = Release|Any CPU - {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.Build.0 = Release|Any CPU - {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.Build.0 = Release|Any CPU - {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.Build.0 = Release|Any CPU - {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.Build.0 = Release|Any CPU - {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.Build.0 = Release|Any CPU - {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CD7DECEC-F4A0-4EEF-978B-72748414D52A} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +EndProject +EndProject +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathCamDisabler", "PathCamDisabler\PathCamDisabler.csproj", "{98169FD2-5CEB-46D1-A320-D7E06F82C9E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" +EndProject +EndProject +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelativeSync", "RelativeSync\RelativeSync.csproj", "{B48C8F19-9451-4EE2-999F-82C0033CDE2C}" +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyPrune", "LazyPrune\LazyPrune.csproj", "{8FA6D481-5801-4E4C-822E-DE561155D22B}" +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrollFlight", "ScrollFlight\ScrollFlight.csproj", "{1B5D7DCB-01A4-4988-8B25-211948AEED76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropLoadingHexagon", "PropLoadingHexagon\PropLoadingHexagon.csproj", "{642A2BC7-C027-4F8F-969C-EF0F867936FD}" +EndProject +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", "KeepVelocityOnExitFlight\KeepVelocityOnExitFlight.csproj", "{0BB3D187-BBBA-4C58-B246-102342BE5E8C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmootherRay", "SmootherRay\SmootherRay.csproj", "{99F9D60D-9A2D-4DBE-AA52-13D8A0838696}" +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShareBubbles", "ShareBubbles\ShareBubbles.csproj", "{ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}" +EndProject +EndProject +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}" +EndProject +EndProject +EndProject +EndProject +EndProject +EndProject +EndProject +EndProject +EndProject +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF9BC79E-4FB6-429A-8C19-DF31F040BD4A}.Release|Any CPU.Build.0 = Release|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79B2A7C4-348D-4A8E-94D1-BA22FDD5FEED}.Release|Any CPU.Build.0 = Release|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45A65AEB-4BFC-4E47-B181-BBB43BD81283}.Release|Any CPU.Build.0 = Release|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98169FD2-5CEB-46D1-A320-D7E06F82C9E0}.Release|Any CPU.Build.0 = Release|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}.Release|Any CPU.Build.0 = Release|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Debug|Any CPU.Build.0 = Debug|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.ActiveCfg = Release|Any CPU + {675CEC0E-3E8A-4970-98EA-9B79277A7252}.Release|Any CPU.Build.0 = Release|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.Build.0 = Release|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.Build.0 = Release|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F94DDB73-9041-4F5C-AD43-6960701E8417}.Release|Any CPU.Build.0 = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC4857DD-F6D9-436D-A3EE-D148A518E642}.Release|Any CPU.Build.0 = Release|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69AF3C10-1BB1-4746-B697-B5A81D78C8D9}.Release|Any CPU.Build.0 = Release|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FA83514-13F8-412C-9790-C2B750E0E7E7}.Release|Any CPU.Build.0 = Release|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B48C8F19-9451-4EE2-999F-82C0033CDE2C}.Release|Any CPU.Build.0 = Release|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B4396C7-B451-4FFD-87B6-3ED8377AC308}.Release|Any CPU.Build.0 = Release|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.Build.0 = Release|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.Build.0 = Release|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C427DD-1261-4AAD-B316-A551FC126F2C}.Release|Any CPU.Build.0 = Release|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.Build.0 = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.Build.0 = Release|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.Build.0 = Release|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.Build.0 = Release|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.Build.0 = Release|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D11214B0-94FE-4008-8D1B-3DC8614466B3}.Release|Any CPU.Build.0 = Release|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CC1F7C6-A953-4008-8C10-C7592EB401E8}.Release|Any CPU.Build.0 = Release|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39915C4C-B555-4CB9-890F-26DE1388BC2E}.Release|Any CPU.Build.0 = Release|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C675E64-0A2D-4B34-B6D1-5D6AA369A520}.Release|Any CPU.Build.0 = Release|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BB3D187-BBBA-4C58-B246-102342BE5E8C}.Release|Any CPU.Build.0 = Release|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6580AA87-6A95-438E-A5D3-70E583CCD77B}.Release|Any CPU.Build.0 = Release|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D178E422-283B-4FB3-89A6-AA4FB9F87E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C992D0C-9729-438E-800C-496B7EFFFB25}.Release|Any CPU.Build.0 = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E285BCC9-D953-4066-8FA2-97EA28EB348E}.Release|Any CPU.Build.0 = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A38E687F-8B6B-499E-ABC9-BD95C53DD391}.Release|Any CPU.Build.0 = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99F9D60D-9A2D-4DBE-AA52-13D8A0838696}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D97D1A-3099-49C5-85AD-D8C79CC5FDE3}.Release|Any CPU.Build.0 = Release|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0640B2BF-1EF5-4FFE-A144-0368748FC48B}.Release|Any CPU.Build.0 = Release|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC91FFFE-1E0A-4F59-8802-BFF99152AD07}.Release|Any CPU.Build.0 = Release|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADD6205B-67A4-4BA8-8BED-DF7D0E857A6A}.Release|Any CPU.Build.0 = Release|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E315182-CC9F-4F62-8385-5E26EFA3B98A}.Release|Any CPU.Build.0 = Release|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09238300-4583-45C6-A997-025CBDC44C24}.Release|Any CPU.Build.0 = Release|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21FDAB94-5014-488D-86C7-A366F1902B24}.Release|Any CPU.Build.0 = Release|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A378F81-3805-41E8-9565-A8A89A8C00D6}.Release|Any CPU.Build.0 = Release|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFCF6FA8-4F38-415E-AC2D-B576FFD5FED5}.Release|Any CPU.Build.0 = Release|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71CBD7CC-C787-4796-B05E-4F3BC3C28B48}.Release|Any CPU.Build.0 = Release|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42E626F7-9A7E-4F55-B02C-16EB56E2B540}.Release|Any CPU.Build.0 = Release|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BF2CBBF-6DAB-4D7A-87E0-AE643D6019AB}.Release|Any CPU.Build.0 = Release|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {262A8AE0-E610-405F-B4EC-DB714FB54C00}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F07862-5715-470D-B324-19BDEBB2AA4D}.Release|Any CPU.Build.0 = Release|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21B591A0-F6E5-4645-BF2D-4E71F47394A7}.Release|Any CPU.Build.0 = Release|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F093BDE5-1824-459E-B86E-B9F79B548E58}.Release|Any CPU.Build.0 = Release|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDA96974-0BEA-404B-8EED-F19CCA2C95A8}.Release|Any CPU.Build.0 = Release|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CD7DECEC-F4A0-4EEF-978B-72748414D52A} + EndGlobalSection +EndGlobal diff --git a/References.Items.props b/References.Items.props index 275c721..69164f3 100644 --- a/References.Items.props +++ b/References.Items.props @@ -52,6 +52,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Bhaptics.Tact.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\BouncyCastle.Crypto.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Boxophobic.TheVehetationEngine.Runtime.dll False @@ -96,6 +100,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\DarkRift.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\DTLS.Net.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\ECM2.dll False @@ -304,6 +312,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\System.Runtime.Serialization.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\System.Security.Cryptography.Cng.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\System.Security.dll False @@ -364,6 +376,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\UniTask.TextMeshPro.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.2D.Common.Runtime.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.AI.Navigation.dll False @@ -412,6 +428,10 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InputSystem.ForUI.dll False + + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InternalAPIEngineBridge.001.dll + False + $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.InternalAPIEngineBridge.002.dll False From 915253972dff2a72c9ef73dc1271331df2e7636a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:31:01 -0500 Subject: [PATCH 082/188] [MuteSFX] Deprecate because functionality is doable native --- .../MuteSFX}/AudioModuleManager.cs | 0 {MuteSFX => .Deprecated/MuteSFX}/Main.cs | 0 {MuteSFX => .Deprecated/MuteSFX}/MuteSFX.csproj | 4 ++-- .../MuteSFX}/Properties/AssemblyInfo.cs | 2 +- {MuteSFX => .Deprecated/MuteSFX}/README.md | 0 {MuteSFX => .Deprecated/MuteSFX}/SFX/sfx_mute.wav | Bin {MuteSFX => .Deprecated/MuteSFX}/SFX/sfx_unmute.wav | Bin {MuteSFX => .Deprecated/MuteSFX}/format.json | 2 +- 8 files changed, 4 insertions(+), 4 deletions(-) rename {MuteSFX => .Deprecated/MuteSFX}/AudioModuleManager.cs (100%) rename {MuteSFX => .Deprecated/MuteSFX}/Main.cs (100%) rename {MuteSFX => .Deprecated/MuteSFX}/MuteSFX.csproj (67%) rename {MuteSFX => .Deprecated/MuteSFX}/Properties/AssemblyInfo.cs (96%) rename {MuteSFX => .Deprecated/MuteSFX}/README.md (100%) rename {MuteSFX => .Deprecated/MuteSFX}/SFX/sfx_mute.wav (100%) rename {MuteSFX => .Deprecated/MuteSFX}/SFX/sfx_unmute.wav (100%) rename {MuteSFX => .Deprecated/MuteSFX}/format.json (96%) diff --git a/MuteSFX/AudioModuleManager.cs b/.Deprecated/MuteSFX/AudioModuleManager.cs similarity index 100% rename from MuteSFX/AudioModuleManager.cs rename to .Deprecated/MuteSFX/AudioModuleManager.cs diff --git a/MuteSFX/Main.cs b/.Deprecated/MuteSFX/Main.cs similarity index 100% rename from MuteSFX/Main.cs rename to .Deprecated/MuteSFX/Main.cs diff --git a/MuteSFX/MuteSFX.csproj b/.Deprecated/MuteSFX/MuteSFX.csproj similarity index 67% rename from MuteSFX/MuteSFX.csproj rename to .Deprecated/MuteSFX/MuteSFX.csproj index 21b218c..5eba218 100644 --- a/MuteSFX/MuteSFX.csproj +++ b/.Deprecated/MuteSFX/MuteSFX.csproj @@ -5,7 +5,7 @@ - - + + diff --git a/MuteSFX/Properties/AssemblyInfo.cs b/.Deprecated/MuteSFX/Properties/AssemblyInfo.cs similarity index 96% rename from MuteSFX/Properties/AssemblyInfo.cs rename to .Deprecated/MuteSFX/Properties/AssemblyInfo.cs index 75cb9f3..452c973 100644 --- a/MuteSFX/Properties/AssemblyInfo.cs +++ b/.Deprecated/MuteSFX/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.MuteSFX.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/MuteSFX/README.md b/.Deprecated/MuteSFX/README.md similarity index 100% rename from MuteSFX/README.md rename to .Deprecated/MuteSFX/README.md diff --git a/MuteSFX/SFX/sfx_mute.wav b/.Deprecated/MuteSFX/SFX/sfx_mute.wav similarity index 100% rename from MuteSFX/SFX/sfx_mute.wav rename to .Deprecated/MuteSFX/SFX/sfx_mute.wav diff --git a/MuteSFX/SFX/sfx_unmute.wav b/.Deprecated/MuteSFX/SFX/sfx_unmute.wav similarity index 100% rename from MuteSFX/SFX/sfx_unmute.wav rename to .Deprecated/MuteSFX/SFX/sfx_unmute.wav diff --git a/MuteSFX/format.json b/.Deprecated/MuteSFX/format.json similarity index 96% rename from MuteSFX/format.json rename to .Deprecated/MuteSFX/format.json index 3601dd3..877e3e0 100644 --- a/MuteSFX/format.json +++ b/.Deprecated/MuteSFX/format.json @@ -1,7 +1,7 @@ { "_id": 172, "name": "MuteSFX", - "modversion": "1.0.2", + "modversion": "1.0.3", "gameversion": "2023r171", "loaderversion": "0.6.1", "modtype": "Mod", From 61f56b2f846defae784310483ee4fddd6da43527 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:58:34 -0500 Subject: [PATCH 083/188] [ASTExtension] bump version --- ASTExtension/Properties/AssemblyInfo.cs | 2 +- ASTExtension/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ASTExtension/Properties/AssemblyInfo.cs b/ASTExtension/Properties/AssemblyInfo.cs index 5c617e2..0200119 100644 --- a/ASTExtension/Properties/AssemblyInfo.cs +++ b/ASTExtension/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ASTExtension.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/ASTExtension/format.json b/ASTExtension/format.json index 5ad14ea..934110a 100644 --- a/ASTExtension/format.json +++ b/ASTExtension/format.json @@ -1,8 +1,8 @@ { "_id": 223, "name": "ASTExtension", - "modversion": "1.0.2", - "gameversion": "2025r178", + "modversion": "1.0.3", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "BTKUILib" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ASTExtension.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/", - "changelog": "- Fixes for 2025r178", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 736dc71eeca981a51994c266515b4f3ece2622b9 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:58:46 -0500 Subject: [PATCH 084/188] [AvatarQueueSystemTweaks] bump version --- AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs | 2 +- AvatarQueueSystemTweaks/format.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs b/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs index 1273c2c..821459b 100644 --- a/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs +++ b/AvatarQueueSystemTweaks/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.AvatarQueueSystemTweaks.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/AvatarQueueSystemTweaks/format.json b/AvatarQueueSystemTweaks/format.json index bb6a269..84dadfc 100644 --- a/AvatarQueueSystemTweaks/format.json +++ b/AvatarQueueSystemTweaks/format.json @@ -1,8 +1,8 @@ { - "_id": -1, + "_id": 226, "name": "AvatarQueueSystemTweaks", - "modversion": "1.0.0", - "gameversion": "2024r175", + "modversion": "1.0.1", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r36/AvatarQueueSystemTweaks.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks/", - "changelog": "- Initial Release", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From c0ba230fa59d94f530d8f974faedf4f9f256b12f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:58:57 -0500 Subject: [PATCH 085/188] [CustomSpawnPoint] bump version --- CustomSpawnPoint/Properties/AssemblyInfo.cs | 2 +- CustomSpawnPoint/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CustomSpawnPoint/Properties/AssemblyInfo.cs b/CustomSpawnPoint/Properties/AssemblyInfo.cs index 480612c..3676fa0 100644 --- a/CustomSpawnPoint/Properties/AssemblyInfo.cs +++ b/CustomSpawnPoint/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.CustomSpawnPoint.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.2"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/CustomSpawnPoint/format.json b/CustomSpawnPoint/format.json index 57d83fd..98f22da 100644 --- a/CustomSpawnPoint/format.json +++ b/CustomSpawnPoint/format.json @@ -1,8 +1,8 @@ { "_id": 228, "name": "CustomSpawnPoint", - "modversion": "1.0.1", - "gameversion": "2024r175", + "modversion": "1.0.2", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/CustomSpawnPoint.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint/", - "changelog": "- Removed unneeded logging when opening a World Details page.", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 4b5c19676d18b2479317973e7561fae4e6c171df Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:59:10 -0500 Subject: [PATCH 086/188] [KeepVelocityOnExitFlight] bump version --- KeepVelocityOnExitFlight/Properties/AssemblyInfo.cs | 2 +- KeepVelocityOnExitFlight/format.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/KeepVelocityOnExitFlight/Properties/AssemblyInfo.cs b/KeepVelocityOnExitFlight/Properties/AssemblyInfo.cs index 9ad16f8..e51a838 100644 --- a/KeepVelocityOnExitFlight/Properties/AssemblyInfo.cs +++ b/KeepVelocityOnExitFlight/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.KeepVelocityOnExitFlight.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/KeepVelocityOnExitFlight/format.json b/KeepVelocityOnExitFlight/format.json index ae56509..94d3b8a 100644 --- a/KeepVelocityOnExitFlight/format.json +++ b/KeepVelocityOnExitFlight/format.json @@ -1,8 +1,8 @@ { - "_id": -1, + "_id": 222, "name": "KeepVelocityOnExitFlight", - "modversion": "1.0.0", - "gameversion": "2024r175", + "modversion": "1.0.1", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r34/KeepVelocityOnExitFlight.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight/", - "changelog": "- Initial release", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 200e174b3f315fc85471775980fe564db59f0fad Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:59:19 -0500 Subject: [PATCH 087/188] [LazyPrune] bump version --- LazyPrune/Properties/AssemblyInfo.cs | 4 +++- LazyPrune/format.json | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/LazyPrune/Properties/AssemblyInfo.cs b/LazyPrune/Properties/AssemblyInfo.cs index 48fb1ee..169f34e 100644 --- a/LazyPrune/Properties/AssemblyInfo.cs +++ b/LazyPrune/Properties/AssemblyInfo.cs @@ -20,11 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.LazyPrune.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/LazyPrune/format.json b/LazyPrune/format.json index d954f95..5125073 100644 --- a/LazyPrune/format.json +++ b/LazyPrune/format.json @@ -1,8 +1,8 @@ { "_id": 214, "name": "LazyPrune", - "modversion": "1.0.2", - "gameversion": "2024r175", + "modversion": "1.0.3", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r32/LazyPrune.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LazyPrune/", - "changelog": "- Fixed killtime check, would needlessly check known non-eligible objects for pruning.\n- Moved away from using GameEventSystem as it proved unreliable for tracking remote Avatar destruction, now patching Object Loader directly as loadedObject is not assigned when object wrappers are enabled.\n- Fixed scheduled prune job being nuked as it was created before initial scene load.\n- Patched two race conditions in the game that would cause the object loader to lock up.", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#1c75f1" } \ No newline at end of file From ef3ecde55311d4f96f7e57c371d9c4e5a0b99cd8 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:59:29 -0500 Subject: [PATCH 088/188] [PathCamDisabler] bump version --- PathCamDisabler/Properties/AssemblyInfo.cs | 6 +++--- PathCamDisabler/format.json | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PathCamDisabler/Properties/AssemblyInfo.cs b/PathCamDisabler/Properties/AssemblyInfo.cs index 75cb1f5..8e01437 100644 --- a/PathCamDisabler/Properties/AssemblyInfo.cs +++ b/PathCamDisabler/Properties/AssemblyInfo.cs @@ -20,13 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 155, 89, 182)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.PathCamDisabler.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PathCamDisabler/format.json b/PathCamDisabler/format.json index 0be5676..6a8d8df 100644 --- a/PathCamDisabler/format.json +++ b/PathCamDisabler/format.json @@ -1,8 +1,8 @@ { "_id": 110, "name": "PathCamDisabler", - "modversion": "1.0.2", - "gameversion": "2023r171", + "modversion": "1.0.3", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r16/PathCamDisabler.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PathCamDisabler.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PathCamDisabler/", - "changelog": "- Fixes for 2023r171.", - "embedcolor": "#9b59b6" + "changelog": "- Recompiled for 2025r179", + "embedcolor": "#f61963" } \ No newline at end of file From d0504fef91f4c49c20c9205977827dfed9ca0914 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 03:59:40 -0500 Subject: [PATCH 089/188] [PortableCameraAdditions] bump version --- PortableCameraAdditions/Properties/AssemblyInfo.cs | 6 +++--- PortableCameraAdditions/format.json | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/PortableCameraAdditions/Properties/AssemblyInfo.cs b/PortableCameraAdditions/Properties/AssemblyInfo.cs index 44b24ae..841c286 100644 --- a/PortableCameraAdditions/Properties/AssemblyInfo.cs +++ b/PortableCameraAdditions/Properties/AssemblyInfo.cs @@ -20,13 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 255, 217, 106)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.PortableCameraAdditions.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.5"; + public const string Version = "1.0.6"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PortableCameraAdditions/format.json b/PortableCameraAdditions/format.json index b9b8f25..957110c 100644 --- a/PortableCameraAdditions/format.json +++ b/PortableCameraAdditions/format.json @@ -1,8 +1,8 @@ { "_id": 123, "name": "PortableCameraAdditions", - "modversion": "1.0.5", - "gameversion": "2023r173", + "modversion": "1.0.6", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -18,8 +18,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r22/PortableCameraAdditions.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PortableCameraAdditions.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PortableCameraAdditions/", - "changelog": "- Fixes for 2023r173.", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#ffd96a" } \ No newline at end of file From e24eae5a22cb0ca381ffc01a783cd5b97383d6bc Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:08 -0500 Subject: [PATCH 090/188] [PropLoadingHexagon] bump version, fix for 2025r179 --- PropLoadingHexagon/Main.cs | 6 +++--- PropLoadingHexagon/Properties/AssemblyInfo.cs | 3 +-- PropLoadingHexagon/format.json | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/PropLoadingHexagon/Main.cs b/PropLoadingHexagon/Main.cs index aecb7df..eac818f 100644 --- a/PropLoadingHexagon/Main.cs +++ b/PropLoadingHexagon/Main.cs @@ -50,9 +50,9 @@ public class PropLoadingHexagonMod : MelonMod ); HarmonyInstance.Patch( // delete mode on prop placeholder - typeof(ControllerRay).GetMethod(nameof(ControllerRay.DeleteSpawnable), + typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleSpawnableClicked), BindingFlags.NonPublic | BindingFlags.Instance), - prefix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnDeleteSpawnableCheck), + prefix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnHandleSpawnableClicked), BindingFlags.NonPublic | BindingFlags.Static)) ); @@ -185,7 +185,7 @@ public class PropLoadingHexagonMod : MelonMod Loading_Hex_List.Add(loadingHex); } - private static void OnDeleteSpawnableCheck(ref ControllerRay __instance) + private static void OnHandleSpawnableClicked(ref ControllerRay __instance) { if (!__instance._interactDown) return; // not interacted, no need to check diff --git a/PropLoadingHexagon/Properties/AssemblyInfo.cs b/PropLoadingHexagon/Properties/AssemblyInfo.cs index a423d48..35e50f1 100644 --- a/PropLoadingHexagon/Properties/AssemblyInfo.cs +++ b/PropLoadingHexagon/Properties/AssemblyInfo.cs @@ -22,12 +22,11 @@ using System.Reflection; [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink [assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: MelonOptionalDependencies("TheClapper")] [assembly: HarmonyDontPatchAll] namespace NAK.PropLoadingHexagon.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "Exterrata & NotAKidoS"; } \ No newline at end of file diff --git a/PropLoadingHexagon/format.json b/PropLoadingHexagon/format.json index 541cd73..4d12613 100644 --- a/PropLoadingHexagon/format.json +++ b/PropLoadingHexagon/format.json @@ -1,8 +1,8 @@ { "_id": 220, "name": "PropLoadingHexagon", - "modversion": "1.0.0", - "gameversion": "2024r175", + "modversion": "1.0.1", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "Exterrata & NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r34/PropLoadingHexagon.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropLoadingHexagon.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PropLoadingHexagon/", - "changelog": "- Initial release", + "changelog": "- Fixes for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From bddc21ec08ca3823217c2fa82fc380faaf2c1778 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:19 -0500 Subject: [PATCH 091/188] [PropUndoButton] bump version --- PropUndoButton/Properties/AssemblyInfo.cs | 4 +++- PropUndoButton/format.json | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/PropUndoButton/Properties/AssemblyInfo.cs b/PropUndoButton/Properties/AssemblyInfo.cs index 424a9d7..ca45627 100644 --- a/PropUndoButton/Properties/AssemblyInfo.cs +++ b/PropUndoButton/Properties/AssemblyInfo.cs @@ -20,11 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] namespace NAK.PropUndoButton.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PropUndoButton/format.json b/PropUndoButton/format.json index 9f6f07a..32227a9 100644 --- a/PropUndoButton/format.json +++ b/PropUndoButton/format.json @@ -1,8 +1,8 @@ { "_id": 147, "name": "PropUndoButton", - "modversion": "1.0.2", - "gameversion": "2024r175", + "modversion": "1.0.3", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r25/PropUndoButton.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropUndoButton.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PropUndoButton/", - "changelog": "- Recompiled for 2024r175", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#00FFFF" } \ No newline at end of file From 9d2c3ed244b1cf3ecf24bdb8288625a1d4bf17a9 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:35 -0500 Subject: [PATCH 092/188] [RCCVirtualSteeringWheel] bump version --- RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs | 2 +- RCCVirtualSteeringWheel/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs index 616aa81..38d7ecc 100644 --- a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs +++ b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs @@ -28,6 +28,6 @@ using NAK.RCCVirtualSteeringWheel.Properties; namespace NAK.RCCVirtualSteeringWheel.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json index 438f31f..4a5ad1a 100644 --- a/RCCVirtualSteeringWheel/format.json +++ b/RCCVirtualSteeringWheel/format.json @@ -1,8 +1,8 @@ { "_id": 248, "name": "RCCVirtualSteeringWheel", - "modversion": "1.0.3", - "gameversion": "2025r178", + "modversion": "1.0.4", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/RCCVirtualSteeringWheel.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Fixes for 2025r178", + "changelog": "- Recompiled for 2025r179\n- Fixed steering wheel pickup colliders not being set to isTrigger", "embedcolor": "#f61963" } \ No newline at end of file From 75de6d33a0923d9c8d5f6a311c6374b6e161dec3 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:42 -0500 Subject: [PATCH 093/188] [RelativeSync] bump version --- RelativeSync/Properties/AssemblyInfo.cs | 2 +- RelativeSync/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RelativeSync/Properties/AssemblyInfo.cs b/RelativeSync/Properties/AssemblyInfo.cs index f014b61..ef60248 100644 --- a/RelativeSync/Properties/AssemblyInfo.cs +++ b/RelativeSync/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.RelativeSync.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.5"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/RelativeSync/format.json b/RelativeSync/format.json index 7b5c456..2df6da2 100644 --- a/RelativeSync/format.json +++ b/RelativeSync/format.json @@ -1,8 +1,8 @@ { "_id": 211, "name": "RelativeSync", - "modversion": "1.0.4", - "gameversion": "2024r175", + "modversion": "1.0.5", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r34/RelativeSync.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Fixed log spam when receiving relative sync data from a blocked user (thanks Mod Network for still forwarding that data -_-)\n- Adjusted execution order to apply relative sync before Totally Wholesomes LineController\n- Adjusted Relative Sync to still apply to avatar distance-hidden users\n- Adjusted Relative Sync to not apply if the CVRSeat or Movement Parent is disabled on receiving client", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 73d76010bc2f7d5d8bb0056c9d3368a2cbc89502 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:48 -0500 Subject: [PATCH 094/188] [ScrollFlight] bump version --- ScrollFlight/Main.cs | 6 +++--- ScrollFlight/format.json | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ScrollFlight/Main.cs b/ScrollFlight/Main.cs index 98fdabd..f2180b8 100644 --- a/ScrollFlight/Main.cs +++ b/ScrollFlight/Main.cs @@ -42,8 +42,8 @@ public class ScrollFlightMod : MelonMod { CVRWorld.GameRulesUpdated += OnApplyMovementSettings; // thank you kafe for using actions } - - bool wasFlying = false; + + private bool wasFlying; // stole from LucMod :3 public override void OnUpdate() @@ -66,7 +66,7 @@ public class ScrollFlightMod : MelonMod wasFlying = isFlying; if (!isFlying - || Input.GetKey(KeyCode.Mouse2) // scroll zoom + || Input.GetKey(KeyCode.Mouse2) // scroll zoom (TODO: Use CVRInputManager.zoom, but requires fixing zoom toggle mode on client) || Input.GetKey(KeyCode.LeftControl) // third person / better interact desktop || Cursor.lockState != CursorLockMode.Locked) // unity explorer / in menu return; diff --git a/ScrollFlight/format.json b/ScrollFlight/format.json index 3981ee1..ddcf1de 100644 --- a/ScrollFlight/format.json +++ b/ScrollFlight/format.json @@ -1,8 +1,8 @@ { - "_id": -1, + "_id": 219, "name": "ScrollFlight", - "modversion": "1.0.0", - "gameversion": "2024r175", + "modversion": "1.0.3", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r34/ScrollFlight.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ScrollFlight.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight/", - "changelog": "- Initial release", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 940777d9e5224b424ed712b2f8825fc044468df5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:00:56 -0500 Subject: [PATCH 095/188] [ScrollFlight] bump version --- ScrollFlight/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ScrollFlight/Properties/AssemblyInfo.cs b/ScrollFlight/Properties/AssemblyInfo.cs index 3aeb6cb..64e0133 100644 --- a/ScrollFlight/Properties/AssemblyInfo.cs +++ b/ScrollFlight/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ScrollFlight.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file From 697ad77f5f554b798fb5b930ee761713d3b434c5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:01:15 -0500 Subject: [PATCH 096/188] [ShareBubbles] bump version, fixes for 2025r179 --- ShareBubbles/Patches.cs | 2 +- ShareBubbles/Properties/AssemblyInfo.cs | 2 +- ShareBubbles/format.json | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ShareBubbles/Patches.cs b/ShareBubbles/Patches.cs index 162c75b..5f95231 100644 --- a/ShareBubbles/Patches.cs +++ b/ShareBubbles/Patches.cs @@ -35,7 +35,7 @@ internal static class PlayerSetup_Patches internal static class ControllerRay_Patches { [HarmonyPostfix] - [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.DeleteSpawnable))] + [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.HandleSpawnableClicked))] public static void Postfix_ControllerRay_DeleteSpawnable(ref ControllerRay __instance) { if (!__instance._interactDown) diff --git a/ShareBubbles/Properties/AssemblyInfo.cs b/ShareBubbles/Properties/AssemblyInfo.cs index 07120d8..ce8f183 100644 --- a/ShareBubbles/Properties/AssemblyInfo.cs +++ b/ShareBubbles/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties; namespace NAK.ShareBubbles.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.5"; public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler"; } \ No newline at end of file diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index be28ae9..ce31103 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -1,8 +1,8 @@ { "_id": 244, "name": "ShareBubbles", - "modversion": "1.0.4", - "gameversion": "2025r178", + "modversion": "1.0.5", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r45/ShareBubbles.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", - "changelog": "- Fixes for 2025r178", + "changelog": "- Fixes for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From cdcb70a4b13742b92c17f2ea691550bf77238ed4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:01:22 -0500 Subject: [PATCH 097/188] [SmootherRay] bump version, fixes for 2025r179 --- SmootherRay/Main.cs | 8 ++++---- SmootherRay/Properties/AssemblyInfo.cs | 3 +-- SmootherRay/format.json | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SmootherRay/Main.cs b/SmootherRay/Main.cs index eb6c36a..1d6d3f4 100644 --- a/SmootherRay/Main.cs +++ b/SmootherRay/Main.cs @@ -51,7 +51,7 @@ public class SmootherRayMod : MelonMod { Logger = LoggerInstance; ApplyPatches(typeof(PlayerSetup_Patches)); - ApplyPatches(typeof(ControllerRay_Patches)); + ApplyPatches(typeof(ControllerSmoothing_Patches)); } private void ApplyPatches(Type type) @@ -82,12 +82,12 @@ public class SmootherRayMod : MelonMod } } - internal static class ControllerRay_Patches + internal static class ControllerSmoothing_Patches { // SmootherRay [HarmonyPrefix] - [HarmonyPatch(typeof(ControllerRay), nameof(ControllerRay.SmoothRay))] - private static bool Prefix_ControllerRay_SmoothRay(ref ControllerRay __instance) + [HarmonyPatch(typeof(ControllerSmoothing), nameof(ControllerSmoothing.OnAppliedPoses))] + private static bool Prefix_ControllerSmoothing_OnAppliedPoses(ref ControllerSmoothing __instance) => !EntryEnabled.Value; // SmootherRay method enforces identity local pos when disabled, so we skip it } diff --git a/SmootherRay/Properties/AssemblyInfo.cs b/SmootherRay/Properties/AssemblyInfo.cs index d7f1704..c47196d 100644 --- a/SmootherRay/Properties/AssemblyInfo.cs +++ b/SmootherRay/Properties/AssemblyInfo.cs @@ -25,9 +25,8 @@ using System.Reflection; [assembly: HarmonyDontPatchAll] namespace NAK.SmootherRay.Properties; - internal static class AssemblyInfoParams { - public const string Version = "1.0.6"; + public const string Version = "1.0.7"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/SmootherRay/format.json b/SmootherRay/format.json index a627013..18f38c3 100644 --- a/SmootherRay/format.json +++ b/SmootherRay/format.json @@ -1,8 +1,8 @@ { "_id": 162, "name": "SmootherRay", - "modversion": "1.0.5", - "gameversion": "2024r176", + "modversion": "1.0.6", + "gameversion": "2025r177", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/SmootherRay.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SmootherRay/", - "changelog": "- Fixed for 2024r176.\n- Rebranded to SmootherRayer due to native implementation now existing and sucking.", + "changelog": "- Fixes for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From bf89ee24f597af1eaf3505c1ae67c5ccac183983 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:01:28 -0500 Subject: [PATCH 098/188] [Stickers] bump version, fixes for 2025r179 --- Stickers/Properties/AssemblyInfo.cs | 2 +- Stickers/format.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index 5107aed..db9f15b 100644 --- a/Stickers/Properties/AssemblyInfo.cs +++ b/Stickers/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.Stickers.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.8"; + public const string Version = "1.0.9"; public const string Author = "NotAKidoS, SketchFoxsky"; } \ No newline at end of file diff --git a/Stickers/format.json b/Stickers/format.json index 5c28daa..fd49c43 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -1,8 +1,8 @@ { "_id": 232, "name": "Stickers", - "modversion": "1.0.8", - "gameversion": "2024r177", + "modversion": "1.0.9", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS, SketchFoxsky", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r41/Stickers.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Added world restriction via `[DisableStickers]` GameObject (thx Sketch).\n- Added sticker placement preview.\n- Fixed stickers being hit by VR switch shader replacement.\n- Fixed Desktop Sticker placement bind firing when a text field was focused.\n- **This version is not backwards compatible with previous versions over network.**", + "changelog": "- Fixes for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file From 16fb070e8b2b5d7e82530a16fb29855a67e94ce1 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:01:37 -0500 Subject: [PATCH 099/188] [ThirdPerson] bump version --- ThirdPerson/Patches.cs | 1 - ThirdPerson/Properties/AssemblyInfo.cs | 6 +++--- ThirdPerson/format.json | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ThirdPerson/Patches.cs b/ThirdPerson/Patches.cs index 60dc4dd..131b45b 100644 --- a/ThirdPerson/Patches.cs +++ b/ThirdPerson/Patches.cs @@ -4,7 +4,6 @@ using MelonLoader; using System.Reflection; using static NAK.ThirdPerson.CameraLogic; using ABI_RC.Core; -using ABI_RC.Core.Player.TransformHider; namespace NAK.ThirdPerson; diff --git a/ThirdPerson/Properties/AssemblyInfo.cs b/ThirdPerson/Properties/AssemblyInfo.cs index 6c055d1..c2c7701 100644 --- a/ThirdPerson/Properties/AssemblyInfo.cs +++ b/ThirdPerson/Properties/AssemblyInfo.cs @@ -20,13 +20,13 @@ using System.Reflection; [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 97)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 97)] // do not change color, originally chosen by Davi +[assembly: MelonAuthorColor(255, 158, 21, 32)] // do not change color, originally chosen by Davi [assembly: HarmonyDontPatchAll] namespace NAK.ThirdPerson.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.1.0"; + public const string Version = "1.1.1"; public const string Author = "Davi & NotAKidoS"; } \ No newline at end of file diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index 52c0017..4cdca3e 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -2,8 +2,8 @@ { "_id": 16, "name": "ThirdPerson", - "modversion": "1.0.9", - "gameversion": "2024r175", + "modversion": "1.1.1", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "Davi & NotAKidoS", @@ -14,9 +14,9 @@ "third person" ], "requirements": [], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r26/ThirdPerson.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Fixed an issue where VR Switching without using ThirdPerson prior would incorrectly set the Desktop camera culling mask to 0\n- Fixed an NRE when checking CVRWorld zoom rule when no CVRWorld instance was found\n- Prevented head hiding from persisting into third person while Avatar Overrender Ui is enabled", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#F61961" } ] \ No newline at end of file From c35d03b1ec166d5034e3e99f2d4c60c76603055f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:01:50 -0500 Subject: [PATCH 100/188] [NAK_CVR_Mods] update sln --- NAK_CVR_Mods.sln | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 5cc9493..976736f 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -7,11 +7,12 @@ EndProject EndProject EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathCamDisabler", "PathCamDisabler\PathCamDisabler.csproj", "{98169FD2-5CEB-46D1-A320-D7E06F82C9E0}" -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableCameraAdditions", "PortableCameraAdditions\PortableCameraAdditions.csproj", "{C4DAFE9D-C79B-4417-9B7D-B7327999DA4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton\PropUndoButton.csproj", "{FBFDB717-F81E-4C06-ACF9-A0F3FFDCDE00}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPerson", "ThirdPerson\ThirdPerson.csproj", "{675CEC0E-3E8A-4970-98EA-9B79277A7252}" EndProject EndProject EndProject @@ -32,8 +33,11 @@ EndProject EndProject EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepVelocityOnExitFlight", "KeepVelocityOnExitFlight\KeepVelocityOnExitFlight.csproj", "{0BB3D187-BBBA-4C58-B246-102342BE5E8C}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASTExtension", "ASTExtension\ASTExtension.csproj", "{6580AA87-6A95-438E-A5D3-70E583CCD77B}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "AvatarQueueSystemTweaks\AvatarQueueSystemTweaks.csproj", "{D178E422-283B-4FB3-89A6-AA4FB9F87E2F}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}" @@ -97,10 +101,6 @@ Global {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE552804-30B1-49CF-BBDE-3B312895AFF7}.Release|Any CPU.Build.0 = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E1DD746-33A1-4179-AE70-8FB83AC40ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU From f99a22499c68ef1b8d7cfd078930ab569c9f2c6e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:02:58 -0500 Subject: [PATCH 101/188] [Stickers] Fixed placing stickers when Cohtml text input fields were focused --- Stickers/Main.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Stickers/Main.cs b/Stickers/Main.cs index 80ebd6b..022559b 100644 --- a/Stickers/Main.cs +++ b/Stickers/Main.cs @@ -1,4 +1,5 @@ using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Systems.InputManagement; @@ -51,7 +52,8 @@ public class StickerMod : MelonMod if (!Input.GetKeyDown((KeyCode)ModSettings.Entry_PlaceBinding.Value)) return; - if (CVRInputManager.Instance.textInputFocused) + if (CVRInputManager.Instance.textInputFocused + || ViewManager.Instance.textInputFocused) // BRUH return; // prevent placing stickers while typing StickerSystem.Instance.PlaceStickerFromControllerRay(PlayerSetup.Instance.activeCam.transform); From a9cebb85e95943cd8dc601c403603340bef7f308 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:04:14 -0500 Subject: [PATCH 102/188] [Stickers] Fixed scrolling cycling selected sticker slot despite not being in placement mode --- Stickers/Main.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Stickers/Main.cs b/Stickers/Main.cs index 022559b..1234f10 100644 --- a/Stickers/Main.cs +++ b/Stickers/Main.cs @@ -39,12 +39,15 @@ public class StickerMod : MelonMod if (StickerSystem.Instance == null) return; - if (Input.mouseScrollDelta.y != 0f - && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus - && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom - StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; + if (StickerSystem.Instance.IsInStickerMode) + { + if (Input.mouseScrollDelta.y != 0f + && Cursor.lockState == CursorLockMode.Locked // prevent scrolling while in menus + && !CVRInputManager.Instance.zoom) // prevent scrolling while using scroll zoom + StickerSystem.Instance.SelectedStickerSlot += (int)Input.mouseScrollDelta.y; - StickerSystem.Instance.UpdateStickerPreview(); // flashy flash + StickerSystem.Instance.UpdateStickerPreview(); // flashy flash + } if (!ModSettings.Entry_UsePlaceBinding.Value) return; From aad4276f889b13aaaa0398976e4360ef8f534c92 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:05:24 -0500 Subject: [PATCH 103/188] [Stickers] Update format.json --- Stickers/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stickers/format.json b/Stickers/format.json index fd49c43..2d2cab5 100644 --- a/Stickers/format.json +++ b/Stickers/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Stickers/", - "changelog": "- Fixes for 2025r179", + "changelog": "- Fixes for 2025r179\n- Fixed placing stickers when Cohtml text input fields were focused\n- Fixed scrolling cycling selected sticker slot despite not being in placement mode", "embedcolor": "#f61963" } \ No newline at end of file From 138c9a9856c49fa984443a56ca214b2c916dafeb Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:06:43 -0500 Subject: [PATCH 104/188] [RCCVirtualSteeringWheel] Fixed generated steering wheel pickup collider not being marked isTrigger --- .../RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs index 0154bd5..088752d 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs @@ -53,6 +53,7 @@ public class SteeringWheelRoot : MonoBehaviour BoxCollider collider = pickup.AddComponent(); collider.size = steeringWheelBounds.size; collider.center = steeringWheelBounds.center; + collider.isTrigger = true; wheelPickup = pickup.AddComponent(); wheelPickup.root = wheel; From 018112d6b9eac08197809e66b64fcdc2f8cd85a8 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:11:40 -0500 Subject: [PATCH 105/188] [RCCVirtualSteeringWheel] Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent --- RCCVirtualSteeringWheel/Patches.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/RCCVirtualSteeringWheel/Patches.cs b/RCCVirtualSteeringWheel/Patches.cs index b9745d9..1ef3064 100644 --- a/RCCVirtualSteeringWheel/Patches.cs +++ b/RCCVirtualSteeringWheel/Patches.cs @@ -1,4 +1,5 @@ -using ABI_RC.Systems.InputManagement; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.Movement; using HarmonyLib; using NAK.RCCVirtualSteeringWheel.Util; @@ -39,12 +40,20 @@ internal static class CVRInputManager_Patches [HarmonyPatch(typeof(CVRInputManager), nameof(CVRInputManager.UpdateInput))] private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance) { - // Steering input is clamped in RCC component - if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat() - && SteeringWheelRoot.TryGetWheelInput( - BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue)) - { + BetterBetterCharacterController characterController = BetterBetterCharacterController.Instance; + if (!characterController._isSitting) + return; // Must be sitting + + CVRSeat cvrSeat = characterController._lastCvrSeat; + if (!cvrSeat + || !cvrSeat.lockControls) + return; // Must be a driver seat + + RCC_CarControllerV3 carController = characterController._lastCvrSeat._carController; + if (!carController) + return; // Specific to RCC + + if (SteeringWheelRoot.TryGetWheelInput(carController, out float steeringValue)) __instance.steering = steeringValue; - } } } \ No newline at end of file From e540628db15559a4e73e192136d91939900c30ae Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:12:04 -0500 Subject: [PATCH 106/188] [RCCVirtualSteeringWheel] Updated format.json --- RCCVirtualSteeringWheel/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RCCVirtualSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json index 4a5ad1a..c1c44c8 100644 --- a/RCCVirtualSteeringWheel/format.json +++ b/RCCVirtualSteeringWheel/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Recompiled for 2025r179\n- Fixed steering wheel pickup colliders not being set to isTrigger", + "changelog": "- Recompiled for 2025r179\n- Fixed generated steering wheel pickup collider not being marked isTrigger\n- Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent", "embedcolor": "#f61963" } \ No newline at end of file From 72b690365b5d2e0cc214b8231778dac6d0e6e3c5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:15:11 -0500 Subject: [PATCH 107/188] [ShareBubbles] Fixed Public/Private text on bubble not being correct, Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you --- .../API/PedestalInfoBatchProcessor.cs | 17 +++++++++-------- .../PedestalInfoResponse_ButCorrect.cs | 10 ---------- .../Implementation/AvatarBubbleImpl.cs | 6 ++---- .../Implementation/SpawnableBubbleImpl.cs | 6 ++---- 4 files changed, 13 insertions(+), 26 deletions(-) delete mode 100644 ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs diff --git a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs index 00b9f41..967734b 100644 --- a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs +++ b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs @@ -1,4 +1,5 @@ using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.Responses; using NAK.ShareBubbles.API.Responses; namespace NAK.ShareBubbles.API; @@ -19,11 +20,11 @@ public enum PedestalType /// public static class PedestalInfoBatchProcessor { - private static readonly Dictionary>> _pendingRequests + private static readonly Dictionary>> _pendingRequests = new() { - { PedestalType.Avatar, new Dictionary>() }, - { PedestalType.Prop, new Dictionary>() } + { PedestalType.Avatar, new Dictionary>() }, + { PedestalType.Prop, new Dictionary>() } }; private static readonly Dictionary _isBatchProcessing @@ -36,9 +37,9 @@ public static class PedestalInfoBatchProcessor private static readonly object _lock = new(); private const float BATCH_DELAY = 2f; - public static Task QueuePedestalInfoRequest(PedestalType type, string contentId) + public static Task QueuePedestalInfoRequest(PedestalType type, string contentId) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); lock (_lock) { @@ -62,12 +63,12 @@ public static class PedestalInfoBatchProcessor await Task.Delay(TimeSpan.FromSeconds(BATCH_DELAY)); List contentIds; - Dictionary> requestBatch; + Dictionary> requestBatch; lock (_lock) { contentIds = _pendingRequests[type].Keys.ToList(); - requestBatch = new Dictionary>(_pendingRequests[type]); + requestBatch = new Dictionary>(_pendingRequests[type]); _pendingRequests[type].Clear(); _isBatchProcessing[type] = false; //ShareBubblesMod.Logger.Msg($"Processing {type} pedestal info batch with {contentIds.Count} items"); @@ -82,7 +83,7 @@ public static class PedestalInfoBatchProcessor _ => throw new ArgumentException($"Unsupported pedestal type: {type}") }; - var response = await ApiConnection.MakeRequest>(operation, contentIds); + var response = await ApiConnection.MakeRequest>(operation, contentIds); if (response?.Data != null) { diff --git a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs b/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs deleted file mode 100644 index 4741a34..0000000 --- a/ShareBubbles/ShareBubbles/API/Responses/PedestalInfoResponse_ButCorrect.cs +++ /dev/null @@ -1,10 +0,0 @@ -using ABI_RC.Core.Networking.API.Responses; - -namespace NAK.ShareBubbles.API.Responses; - -[Serializable] -public class PedestalInfoResponse_ButCorrect : UgcResponse -{ - public UserDetails User { get; set; } - public bool Published { get; set; } // Client mislabelled this as Permitted, but it's actually Published -} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs index 60dc79d..b79f0c8 100644 --- a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs @@ -39,10 +39,8 @@ namespace NAK.ShareBubbles.Impl Name = infoResponse.Name, ImageUrl = infoResponse.ImageUrl, AuthorId = infoResponse.User.Id, - IsPublic = infoResponse.Published, - - // Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE) - IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId, + IsPublic = infoResponse.IsPublished, + IsPermitted = infoResponse.Permitted, }; downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); diff --git a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs index fc29336..813222c 100644 --- a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs @@ -39,10 +39,8 @@ namespace NAK.ShareBubbles.Impl Name = infoResponse.Name, ImageUrl = infoResponse.ImageUrl, AuthorId = infoResponse.User.Id, - IsPublic = infoResponse.Published, - - // Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE) - IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId, + IsPublic = infoResponse.IsPublished, + IsPermitted = infoResponse.Permitted, }; downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl); From 0fdbcdec34275cb044508e2cc597b53887aeb4bf Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:16:01 -0500 Subject: [PATCH 108/188] [ShareBubbles] Updated format.json --- ShareBubbles/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index ce31103..d2028d6 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -19,6 +19,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", - "changelog": "- Fixes for 2025r179", + "changelog": "- Fixes for 2025r179\n- Fixed Public/Private text on bubble not being correct\n- Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you", "embedcolor": "#f61963" } \ No newline at end of file From 84dcf3536277b5a31ebf08c409222e8d72ac6fc9 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:19:17 -0500 Subject: [PATCH 109/188] [ScrollFlight] Update format.json --- ScrollFlight/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ScrollFlight/format.json b/ScrollFlight/format.json index ddcf1de..3cc60bd 100644 --- a/ScrollFlight/format.json +++ b/ScrollFlight/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ScrollFlight.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Recompiled for 2025r179\n- Added an option to reset flight speed to default on exit flight", "embedcolor": "#f61963" } \ No newline at end of file From 9133c6b16154f0b0ad051d5bec9d174618b582e2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:34:30 -0500 Subject: [PATCH 110/188] [ShareBubbles] Remove todo notes --- ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs b/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs index 932fa6c..87699e4 100644 --- a/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs +++ b/ShareBubbles/ShareBubbles/DataTypes/BubblePedestalInfo.cs @@ -5,6 +5,6 @@ public class BubblePedestalInfo public string Name; public string ImageUrl; public string AuthorId; - public bool IsPermitted; // TODO: awaiting luc to fix not being true for private shared (only true for public) - public bool IsPublic; // TODO: awaiting luc to add + public bool IsPermitted; + public bool IsPublic; } \ No newline at end of file From 063669e8a60f6cca4352efaa479abd1a5224c34b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 04:40:20 -0500 Subject: [PATCH 111/188] [SmootherRay] Update format.json --- SmootherRay/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SmootherRay/format.json b/SmootherRay/format.json index 18f38c3..5e49dd0 100644 --- a/SmootherRay/format.json +++ b/SmootherRay/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Smoothes your controller while the raycast lines are visible.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n- An option is provided to only smooth when aiming at menus.\n- Smoothing characteristics are completely configurable, but the defaults are basically perfect.\n- Pairs well with the [WhereAmIPointing](https://discord.com/channels/1001388809184870441/1002058238545641542/1282798820073406556) mod.\n\n**Only supports OpenVR, not OpenXR.**\n\n-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.", + "description": "Smoothes your controller while the raycast lines are visible.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n- An option is provided to only smooth when aiming at menus.\n- Smoothing characteristics are completely configurable, but the defaults are basically perfect.\n\n**Only supports OpenVR, not OpenXR.**\n\n-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.", "searchtags": [ "vr", "ray", From ea5a9eef970fe60d2b341492bd8880424c0cc88a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 05:08:25 -0500 Subject: [PATCH 112/188] [RelativeSync] Fixed execution order of RelativeSyncController & PuppetMaster.ProcessAvatarVisibility, so moving at high speeds with passengers does not disrupt voice or avatar distance hider --- RelativeSync/Main.cs | 8 +-- RelativeSync/ModSettings.cs | 7 +-- RelativeSync/Patches.cs | 54 ++++++++++--------- .../Components/RelativeSyncController.cs | 17 +++++- .../RelativeSync/RelativeSyncManager.cs | 1 + 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/RelativeSync/Main.cs b/RelativeSync/Main.cs index 83ec331..0a50472 100644 --- a/RelativeSync/Main.cs +++ b/RelativeSync/Main.cs @@ -17,10 +17,7 @@ public class RelativeSyncMod : MelonMod // Experimental sync hack ApplyPatches(typeof(CVRSpawnablePatches)); - - // Experimental no interpolation on Better Better Character Controller - ApplyPatches(typeof(BetterBetterCharacterControllerPatches)); - + // Send relative sync update after network root data update ApplyPatches(typeof(NetworkRootDataUpdatePatches)); @@ -31,6 +28,9 @@ public class RelativeSyncMod : MelonMod // Add components if missing (for relative sync markers) ApplyPatches(typeof(CVRSeatPatches)); ApplyPatches(typeof(CVRMovementParentPatches)); + + // So we run after the client moves the remote player + ApplyPatches(typeof(NetIKController_Patches)); } private void ApplyPatches(Type type) diff --git a/RelativeSync/ModSettings.cs b/RelativeSync/ModSettings.cs index 91a60a7..f8567ad 100644 --- a/RelativeSync/ModSettings.cs +++ b/RelativeSync/ModSettings.cs @@ -23,11 +23,7 @@ internal static class ModSettings private static readonly MelonPreferences_Entry ExpSyncedObjectHack = Category.CreateEntry("ExpSyncedObjectHack", true, "Exp Spawnable Sync Fix", description: "Forces CVRSpawnable to update position in FixedUpdate. May help reduce local jitter on synced movement parents."); - - private static readonly MelonPreferences_Entry ExpNoInterpolationOnBBCC = - Category.CreateEntry("ExpNoInterpolationOnBBCC", true, - "Exp Disable Interpolation on BBCC", description: "Disable interpolation on Better Better Character Controller. May help reduce local jitter on synced movement parents."); - + #endregion Melon Preferences internal static void Initialize() @@ -43,6 +39,5 @@ internal static class ModSettings ModNetwork.Debug_NetworkInbound = DebugLogInbound.Value; ModNetwork.Debug_NetworkOutbound = DebugLogOutbound.Value; Patches.CVRSpawnablePatches.UseHack = ExpSyncedObjectHack.Value; - Patches.BetterBetterCharacterControllerPatches.NoInterpolation = ExpNoInterpolationOnBBCC.Value; } } \ No newline at end of file diff --git a/RelativeSync/Patches.cs b/RelativeSync/Patches.cs index 71d4ec8..43937dd 100644 --- a/RelativeSync/Patches.cs +++ b/RelativeSync/Patches.cs @@ -2,12 +2,10 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.Networking.Jobs; using ABI_RC.Core.Player; -using ABI_RC.Systems.Movement; using ABI.CCK.Components; using HarmonyLib; using NAK.RelativeSync.Components; using NAK.RelativeSync.Networking; -using UnityEngine; namespace NAK.RelativeSync.Patches; @@ -19,7 +17,6 @@ internal static class PlayerSetupPatches { __instance.AddComponentIfMissing(); } - } internal static class PuppetMasterPatches @@ -30,6 +27,20 @@ internal static class PuppetMasterPatches { __instance.AddComponentIfMissing(); } + + private static bool ShouldProcessAvatarVisibility { get; set; } + + [HarmonyPrefix] + [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.ProcessAvatarVisibility))] + private static bool Prefix_PuppetMaster_ProcessAvatarVisibility() + => ShouldProcessAvatarVisibility; + + public static void ForceProcessAvatarVisibility(PuppetMaster puppetMaster) + { + ShouldProcessAvatarVisibility = true; + puppetMaster.ProcessAvatarVisibility(); + ShouldProcessAvatarVisibility = false; + } } internal static class CVRSeatPatches @@ -85,29 +96,24 @@ internal static class CVRSpawnablePatches } } -internal static class BetterBetterCharacterControllerPatches +internal static class NetIKController_Patches { - private static bool _noInterpolation; - internal static bool NoInterpolation - { - get => _noInterpolation; - set - { - _noInterpolation = value; - if (_rigidbody == null) return; - _rigidbody.interpolation = value ? RigidbodyInterpolation.None : _initialInterpolation; - } - } - - private static Rigidbody _rigidbody; - private static RigidbodyInterpolation _initialInterpolation; - [HarmonyPostfix] - [HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.Start))] - private static void Postfix_BetterBetterCharacterController_Update(ref BetterBetterCharacterController __instance) + [HarmonyPatch(typeof(NetIKController), nameof(NetIKController.LateUpdate))] + private static void Postfix_NetIKController_LateUpdate(ref NetIKController __instance) { - _rigidbody = __instance.GetComponent(); - _initialInterpolation = _rigidbody.interpolation; - NoInterpolation = _noInterpolation; // get initial value as patch runs later than settings init + if (!RelativeSyncManager.NetIkControllersToRelativeSyncControllers.TryGetValue(__instance, + out RelativeSyncController syncController)) + { + // Process visibility only after applying network IK + PuppetMasterPatches.ForceProcessAvatarVisibility(__instance._puppetMaster); + return; + } + + // Apply relative sync after the network IK has been applied + syncController.OnPostNetIkControllerLateUpdate(); + + // Process visibility after we have moved the remote player + PuppetMasterPatches.ForceProcessAvatarVisibility(__instance._puppetMaster); } } \ No newline at end of file diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs b/RelativeSync/RelativeSync/Components/RelativeSyncController.cs index 2e11301..66af2c7 100644 --- a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs +++ b/RelativeSync/RelativeSync/Components/RelativeSyncController.cs @@ -26,14 +26,29 @@ public class RelativeSyncController : MonoBehaviour _userId = puppetMaster._playerDescriptor.ownerId; RelativeSyncManager.RelativeSyncControllers.Add(_userId, this); + RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Add(puppetMaster.netIkController, this); } private void OnDestroy() { RelativeSyncManager.RelativeSyncControllers.Remove(_userId); + + if (puppetMaster == null + || puppetMaster.netIkController == null) + { + // remove by value ? + foreach (var kvp in RelativeSyncManager.NetIkControllersToRelativeSyncControllers) + { + if (kvp.Value != this) continue; + RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Remove(kvp.Key); + break; + } + return; + } + RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Remove(puppetMaster.netIkController); } - private void LateUpdate() + internal void OnPostNetIkControllerLateUpdate() { // if (puppetMaster._isHidden) // return; diff --git a/RelativeSync/RelativeSync/RelativeSyncManager.cs b/RelativeSync/RelativeSync/RelativeSyncManager.cs index 9a532f4..052ca3c 100644 --- a/RelativeSync/RelativeSync/RelativeSyncManager.cs +++ b/RelativeSync/RelativeSync/RelativeSyncManager.cs @@ -11,6 +11,7 @@ public static class RelativeSyncManager public static readonly Dictionary RelativeSyncTransforms = new(); public static readonly Dictionary RelativeSyncControllers = new(); + public static readonly Dictionary NetIkControllersToRelativeSyncControllers = new(); public static void ApplyRelativeSync(string userId, int target, Vector3 position, Vector3 rotation) { From 6249696efaa00151708201b07259ea5cd5284e4d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 05:08:57 -0500 Subject: [PATCH 113/188] [RelativeSync] Updated format.json --- RelativeSync/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RelativeSync/format.json b/RelativeSync/format.json index 2df6da2..b917a7d 100644 --- a/RelativeSync/format.json +++ b/RelativeSync/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Recompiled for 2025r179\n- Fixed execution order of RelativeSyncController & PuppetMaster.ProcessAvatarVisibility, so moving at high speeds with passengers does not disrupt voice or avatar distance hider", "embedcolor": "#f61963" } \ No newline at end of file From fea10ac221b9ada48e145242c721bacc6d5562df Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 05:24:48 -0500 Subject: [PATCH 114/188] [RelativeSync] actually fixed distance hider --- RelativeSync/Patches.cs | 31 ++++++------------- .../Components/RelativeSyncController.cs | 8 ++++- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/RelativeSync/Patches.cs b/RelativeSync/Patches.cs index 43937dd..10acf15 100644 --- a/RelativeSync/Patches.cs +++ b/RelativeSync/Patches.cs @@ -6,6 +6,7 @@ using ABI.CCK.Components; using HarmonyLib; using NAK.RelativeSync.Components; using NAK.RelativeSync.Networking; +using UnityEngine; namespace NAK.RelativeSync.Patches; @@ -27,20 +28,6 @@ internal static class PuppetMasterPatches { __instance.AddComponentIfMissing(); } - - private static bool ShouldProcessAvatarVisibility { get; set; } - - [HarmonyPrefix] - [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.ProcessAvatarVisibility))] - private static bool Prefix_PuppetMaster_ProcessAvatarVisibility() - => ShouldProcessAvatarVisibility; - - public static void ForceProcessAvatarVisibility(PuppetMaster puppetMaster) - { - ShouldProcessAvatarVisibility = true; - puppetMaster.ProcessAvatarVisibility(); - ShouldProcessAvatarVisibility = false; - } } internal static class CVRSeatPatches @@ -104,16 +91,18 @@ internal static class NetIKController_Patches { if (!RelativeSyncManager.NetIkControllersToRelativeSyncControllers.TryGetValue(__instance, out RelativeSyncController syncController)) - { - // Process visibility only after applying network IK - PuppetMasterPatches.ForceProcessAvatarVisibility(__instance._puppetMaster); return; - } // Apply relative sync after the network IK has been applied syncController.OnPostNetIkControllerLateUpdate(); - - // Process visibility after we have moved the remote player - PuppetMasterPatches.ForceProcessAvatarVisibility(__instance._puppetMaster); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NetIKController), nameof(NetIKController.GetLocalPlayerPosition))] + private static bool Prefix_NetIKController_GetLocalPlayerPosition(ref NetIKController __instance, ref Vector3 __result) + { + // why is the original method so bad + __result = PlayerSetup.Instance.activeCam.transform.position; + return false; } } \ No newline at end of file diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs b/RelativeSync/RelativeSync/Components/RelativeSyncController.cs index 66af2c7..a94cde0 100644 --- a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs +++ b/RelativeSync/RelativeSync/Components/RelativeSyncController.cs @@ -1,4 +1,5 @@ using ABI_RC.Core.Player; +using ABI_RC.Systems.Movement; using UnityEngine; namespace NAK.RelativeSync.Components; @@ -62,7 +63,7 @@ public class RelativeSyncController : MonoBehaviour Animator animator = puppetMaster._animator; if (animator == null) return; - + Transform avatarTransform = animator.transform; Transform hipTrans = (animator.avatar != null && animator.isHuman) ? animator.GetBoneTransform(HumanBodyBones.Hips) : null; @@ -97,6 +98,11 @@ public class RelativeSyncController : MonoBehaviour hipTrans.position = transform.position + transform.rotation * relativeHipPos; hipTrans.rotation = transform.rotation * relativeHipRot; } + + // Reprocess the root distance so we don't fuck avatar distance hider + NetIKController netIkController = puppetMaster.netIkController; + netIkController._rootDistance = Vector3.Distance((netIkController._collider.transform.position + netIkController._collider.center), + netIkController.GetLocalPlayerPosition()) - (netIkController._collider.radius + BetterBetterCharacterController.Instance.Radius); } private void ApplyRelativeRotation(Transform avatarTransform, Transform hipTransform, float lerp) From 364de89b30037b6fc6587cb80a16a8142338eae2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 3 Apr 2025 05:25:37 -0500 Subject: [PATCH 115/188] [RelativeSync] Updated format.json --- RelativeSync/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RelativeSync/format.json b/RelativeSync/format.json index b917a7d..a5346fa 100644 --- a/RelativeSync/format.json +++ b/RelativeSync/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Recompiled for 2025r179\n- Fixed execution order of RelativeSyncController & PuppetMaster.ProcessAvatarVisibility, so moving at high speeds with passengers does not disrupt voice or avatar distance hider", + "changelog": "- Recompiled for 2025r179\n- Fixed execution order of RelativeSyncController so moving at high speeds with passengers does not disrupt voice or avatar distance hider", "embedcolor": "#f61963" } \ No newline at end of file From 7030ed865087be4a0fa41c02eac777b71edb6da5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:49:27 -0500 Subject: [PATCH 116/188] [NAK_CVR_Mods] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 76c86ad..5fa34df 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## NotAKids ChilloutVR Mods + + ## Released Mods | Mod Name | README | Download | Description | @@ -37,6 +39,8 @@ | VisualCloneFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/VisualCloneFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/VisualCloneFix.dll) | Fixes the Visual Clone system. | | WhereAmIPointing | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/WhereAmIPointing) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/WhereAmIPointing.dll) | Makes your controller rays always visible when the menus are open. | + + # How To Install The majority of modifications found in this repository are reviewed and made available through the [ChilloutVR Modding Group](https://discord.gg/dndGPM3bxu). From 0564de3ebfade2fed62d134f6ad94b5465db74ab Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:52:32 -0500 Subject: [PATCH 117/188] [NAK_CVR_Mods] Experiment --- .github/scripts/update-modlist.js | 55 ++++++++++++++++++++++++++++ .github/workflows/update-modlist.yml | 34 +++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 .github/scripts/update-modlist.js create mode 100644 .github/workflows/update-modlist.yml diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js new file mode 100644 index 0000000..2e5c55d --- /dev/null +++ b/.github/scripts/update-modlist.js @@ -0,0 +1,55 @@ +const fs = require('fs'); +const path = require('path'); + +const ROOT = '.'; +const EXPERIMENTAL = '.Experimental'; +const README_PATH = 'README.md'; +const MARKER_START = ''; +const MARKER_END = ''; + +function getModFolders(baseDir) { + const entries = fs.readdirSync(baseDir, { withFileTypes: true }); + return entries + .filter(entry => entry.isDirectory()) + .map(entry => path.join(baseDir, entry.name)) + .filter(dir => fs.existsSync(path.join(dir, 'README.md'))); +} + +function formatTable(mods, baseDir) { + if (mods.length === 0) return ''; + + let rows = mods.map(modPath => { + const modName = path.basename(modPath); + const readmeLink = path.join(modPath, 'README.md'); + const zipLink = path.join(modPath, `${modName}.zip`); + return `| [${modName}](${readmeLink}) | [Download](${zipLink}) |`; + }); + + return [ + `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, + '', + '| Name | Download |', + '|------|----------|', + ...rows, + '' + ].join('\n'); +} + +function updateReadme(modListSection) { + const readme = fs.readFileSync(README_PATH, 'utf8'); + const before = readme.split(MARKER_START)[0]; + const after = readme.split(MARKER_END)[1]; + + const newReadme = `${before}${MARKER_START}\n\n${modListSection}\n${MARKER_END}${after}`; + fs.writeFileSync(README_PATH, newReadme); +} + +const mainMods = getModFolders(ROOT).filter(dir => !dir.startsWith(EXPERIMENTAL)); +const experimentalMods = getModFolders(EXPERIMENTAL); + +const tableContent = [ + formatTable(mainMods, ROOT), + formatTable(experimentalMods, EXPERIMENTAL) +].join('\n'); + +updateReadme(tableContent); \ No newline at end of file diff --git a/.github/workflows/update-modlist.yml b/.github/workflows/update-modlist.yml new file mode 100644 index 0000000..73d8919 --- /dev/null +++ b/.github/workflows/update-modlist.yml @@ -0,0 +1,34 @@ +name: Update Mod List in README + +on: + push: + paths: + - '**/README.md' + - '.github/workflows/update-modlist.yml' + workflow_dispatch: + +jobs: + update-readme: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: npm install gray-matter + + - name: Generate mod list + run: node .github/scripts/update-modlist.js + + - name: Commit changes + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "[NAK_CVR_Mods] Update mod list in README" || echo "No changes to commit" + git push From 68d7c22b7c54430ec9e54573cee97e11da3eb66f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:02:53 -0500 Subject: [PATCH 118/188] [NAK_CVR_Mods] Experiment 2 --- .github/workflows/update-modlist.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/update-modlist.yml b/.github/workflows/update-modlist.yml index 73d8919..2b117fb 100644 --- a/.github/workflows/update-modlist.yml +++ b/.github/workflows/update-modlist.yml @@ -1,34 +1,35 @@ -name: Update Mod List in README +name: Update Mod List on: push: paths: - - '**/README.md' + - 'scripts/update_modlist.py' - '.github/workflows/update-modlist.yml' + - 'README.md' + - '**/README.md' workflow_dispatch: jobs: - update-readme: + update-modlist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo + uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v3 + - name: Set up Python + uses: actions/setup-python@v5 with: - node-version: '20' + python-version: '3.x' - - name: Install dependencies - run: npm install gray-matter + - name: Run mod list updater + run: python scripts/update_modlist.py - - name: Generate mod list - run: node .github/scripts/update-modlist.js - - - name: Commit changes + - name: Commit and push changes run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }} git add README.md git commit -m "[NAK_CVR_Mods] Update mod list in README" || echo "No changes to commit" git push From dafff6a9a2d31d7c54e36d3e89a6ccd5d661913f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:05:01 -0500 Subject: [PATCH 119/188] [NAK_CVR_Mods] trust --- .github/workflows/update-modlist.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-modlist.yml b/.github/workflows/update-modlist.yml index 2b117fb..3376080 100644 --- a/.github/workflows/update-modlist.yml +++ b/.github/workflows/update-modlist.yml @@ -3,7 +3,7 @@ name: Update Mod List on: push: paths: - - 'scripts/update_modlist.py' + - 'update-modlist.js' - '.github/workflows/update-modlist.yml' - 'README.md' - '**/README.md' @@ -17,13 +17,13 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Set up Node.js + uses: actions/setup-node@v3 with: - python-version: '3.x' + node-version: '20' - name: Run mod list updater - run: python scripts/update_modlist.py + run: node update-modlist.js - name: Commit and push changes run: | From 02be4ec4453ad2516497462e7fd1003160c4cb60 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:07:44 -0500 Subject: [PATCH 120/188] [NAK_CVR_Mods] trust 2 --- .github/workflows/update-modlist.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-modlist.yml b/.github/workflows/update-modlist.yml index 3376080..69c4426 100644 --- a/.github/workflows/update-modlist.yml +++ b/.github/workflows/update-modlist.yml @@ -3,7 +3,7 @@ name: Update Mod List on: push: paths: - - 'update-modlist.js' + - '.github/scripts/update-modlist.js' - '.github/workflows/update-modlist.yml' - 'README.md' - '**/README.md' @@ -23,7 +23,7 @@ jobs: node-version: '20' - name: Run mod list updater - run: node update-modlist.js + run: node .github/scripts/update-modlist.js - name: Commit and push changes run: | @@ -32,4 +32,4 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }} git add README.md git commit -m "[NAK_CVR_Mods] Update mod list in README" || echo "No changes to commit" - git push + git push \ No newline at end of file From 21aa6463590381c17beeb87bcd769c6920326d5a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Apr 2025 21:23:54 +0000 Subject: [PATCH 121/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 62 ++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 5fa34df..c84f23e 100644 --- a/README.md +++ b/README.md @@ -2,42 +2,34 @@ -## Released Mods +### Released Mods -| Mod Name | README | Download | Description | -|------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| AASDefaultProfileFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AASDefaultProfileFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AASDefaultProfileFix.dll) | Fixes the Default AAS profile not being applied. | -| ASTExtension | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ASTExtension) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ASTExtension.dll) | Avatar scaling gesture & persistance on existing avatars. | -| AvatarQueueSystemTweaks | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AvatarQueueSystemTweaks.dll) | Tweaks to improve the avatar queuing system. | -| ChatBoxExtensions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ChatBoxExtensions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ChatBoxExtensions.dll) | Adds some chat commands to the ChatBox mod. | -| CustomSpawnPoint | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CustomSpawnPoint) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CustomSpawnPoint.dll) | Allows setting custom spawn points in worlds. | -| CVRGizmos | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CVRGizmos) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CVRGizmos.dll) | Adds runtime gizmos to common CCK components. | -| DropPropTweak | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/DropPropTweak) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/DropPropTweak.dll) | Allows you to drop props in the air. | -| FOVAdjustment | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FOVAdjustment) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/FOVAdjustment.dll) | Makes CVR_DesktopCameraController default FOV configurable. | -| HeadLookLockingInputFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/HeadLookLockingInputFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/HeadLookLockingInputFix.dll) | Fixes head look locking input issues. | -| IKAdjustments | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKAdjustments) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKAdjustments.dll) | Allows grabbing IK points for manual adjustment. | -| IKSimulatedRootAngleFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKSimulatedRootAngleFix.dll) | Fixes Desktop & HalfBody root angle issues. | -| KeepVelocityOnExitFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/KeepVelocityOnExitFlight.dll) | Keeps the player's velocity when exiting flight mode. | -| LazyPrune | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LazyPrune.dll) | Prevents loaded objects from immediately unloading. | -| LuaTTS | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LuaTTS) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LuaTTS.dll) | Adds Text-to-Speech (TTS) functionality through Lua. | -| MoreMenuOptions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MoreMenuOptions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MoreMenuOptions.dll) | Exposes some menu placement configuration options. | -| MuteSFX | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MuteSFX) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MuteSFX.dll) | Adds an audio cue for muting and unmuting. | -| OriginShift | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/OriginShift) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/OriginShift.dll) | Shifts the world origin to avoid precision issues. | -| PathCamDisabler | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PathCamDisabler) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PathCamDisabler.dll) | Adds option to disable the Path Camera Controller keybinds. | -| PortableCameraAdditions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PortableCameraAdditions.dll) | Adds a few basic settings to the Portable Camera. | -| PropLoadingHexagon | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropLoadingHexagon.dll) | Adds a hexagon indicator to downloading props. | -| PropUndoButton | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropUndoButton) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropUndoButton.dll) | CTRL+Z to undo latest spawned prop. | -| ReconnectionSystemFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ReconnectionSystemFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ReconnectionSystemFix.dll) | Prevents recreating and reloading all remote players. | -| RelativeSync | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/RelativeSync.dll) | Relative sync for Movement Parent & Chairs. | -| ScrollFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ScrollFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ScrollFlight.dll) | Scroll-wheel to adjust flight speed in Desktop. | -| ShadowCloneFallback | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ShadowCloneFallback) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ShadowCloneFallback.dll) | Exposes a toggle for the Fallback Shadow Clone. | -| SmartReticle | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SmartReticle) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SmartReticle.dll) | Makes the reticle only appear when hovering interactables. | -| Stickers | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/Stickers) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/Stickers.dll) | Allows you to place small images on any surface. | -| StopClosingMyMenuOnWorldLoad | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/StopClosingMyMenuOnWorldLoad)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/StopClosingMyMenuOnWorldLoad.dll) | Prevents your menu from being closed when a world is loaded. | -| SwitchToDesktopOnSteamVRExit | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SwitchToDesktopOnSteamVRExit)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SwitchToDesktopOnSteamVRExit.dll) | Initiates a VR Switch to Desktop when SteamVR is exited. | -| ThirdPerson | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ThirdPerson) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ThirdPerson.dll) | Allows you to go into third person view. | -| VisualCloneFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/VisualCloneFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/VisualCloneFix.dll) | Fixes the Visual Clone system. | -| WhereAmIPointing | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/WhereAmIPointing) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/WhereAmIPointing.dll) | Makes your controller rays always visible when the menus are open. | +| Name | Download | +|------|----------| +| [ASTExtension](ASTExtension/README.md) | [Download](ASTExtension/ASTExtension.zip) | +| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | +| [CustomRichPresence](CustomRichPresence/README.md) | [Download](CustomRichPresence/CustomRichPresence.zip) | +| [CustomSpawnPoint](CustomSpawnPoint/README.md) | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | +| [FuckToes](FuckToes/README.md) | [Download](FuckToes/FuckToes.zip) | +| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | +| [LazyPrune](LazyPrune/README.md) | [Download](LazyPrune/LazyPrune.zip) | +| [PropLoadingHexagon](PropLoadingHexagon/README.md) | [Download](PropLoadingHexagon/PropLoadingHexagon.zip) | +| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | [Download](RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.zip) | +| [RelativeSync](RelativeSync/README.md) | [Download](RelativeSync/RelativeSync.zip) | +| [ShareBubbles](ShareBubbles/README.md) | [Download](ShareBubbles/ShareBubbles.zip) | +| [SmootherRay](SmootherRay/README.md) | [Download](SmootherRay/SmootherRay.zip) | +| [Stickers](Stickers/README.md) | [Download](Stickers/Stickers.zip) | +| [ThirdPerson](ThirdPerson/README.md) | [Download](ThirdPerson/ThirdPerson.zip) | + +### Experimental Mods + +| Name | Download | +|------|----------| +| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | +| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | +| [LuaTTS](.Experimental/LuaTTS/README.md) | [Download](.Experimental/LuaTTS/LuaTTS.zip) | +| [OriginShift](.Experimental/OriginShift/README.md) | [Download](.Experimental/OriginShift/OriginShift.zip) | +| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | [Download](.Experimental/ScriptingSpoofer/ScriptingSpoofer.zip) | From d5480e32b49ea6a86491fde7952345be19c9340f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:29:13 -0500 Subject: [PATCH 122/188] [NAK_CVR_Mods] test --- .github/scripts/update-modlist.js | 36 ++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js index 2e5c55d..7045224 100644 --- a/.github/scripts/update-modlist.js +++ b/.github/scripts/update-modlist.js @@ -15,6 +15,33 @@ function getModFolders(baseDir) { .filter(dir => fs.existsSync(path.join(dir, 'README.md'))); } +function extractDescription(readmePath) { + try { + const content = fs.readFileSync(readmePath, 'utf8'); + const lines = content.split('\n'); + + // Find the first header (# something) + let headerIndex = -1; + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim().startsWith('# ')) { + headerIndex = i; + break; + } + } + + // If we found a header, try to get the third line after it + if (headerIndex !== -1 && headerIndex + 2 < lines.length) { + const description = lines[headerIndex + 2].trim(); + return description || 'No description available'; + } + + return 'No description available'; + } catch (error) { + console.error(`Error reading ${readmePath}:`, error); + return 'No description available'; + } +} + function formatTable(mods, baseDir) { if (mods.length === 0) return ''; @@ -22,14 +49,17 @@ function formatTable(mods, baseDir) { const modName = path.basename(modPath); const readmeLink = path.join(modPath, 'README.md'); const zipLink = path.join(modPath, `${modName}.zip`); - return `| [${modName}](${readmeLink}) | [Download](${zipLink}) |`; + const readmePath = path.join(modPath, 'README.md'); + const description = extractDescription(readmePath); + + return `| [${modName}](${readmeLink}) | ${description} | [Download](${zipLink}) |`; }); return [ `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, '', - '| Name | Download |', - '|------|----------|', + '| Name | Description | Download |', + '|------|-------------|----------|', ...rows, '' ].join('\n'); From e67d5719711963dcee86a5f3e9f3a8e622c460cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Apr 2025 21:29:34 +0000 Subject: [PATCH 123/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c84f23e..dde9417 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,32 @@ ### Released Mods -| Name | Download | -|------|----------| -| [ASTExtension](ASTExtension/README.md) | [Download](ASTExtension/ASTExtension.zip) | -| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | -| [CustomRichPresence](CustomRichPresence/README.md) | [Download](CustomRichPresence/CustomRichPresence.zip) | -| [CustomSpawnPoint](CustomSpawnPoint/README.md) | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | -| [FuckToes](FuckToes/README.md) | [Download](FuckToes/FuckToes.zip) | -| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | -| [LazyPrune](LazyPrune/README.md) | [Download](LazyPrune/LazyPrune.zip) | -| [PropLoadingHexagon](PropLoadingHexagon/README.md) | [Download](PropLoadingHexagon/PropLoadingHexagon.zip) | -| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | [Download](RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.zip) | -| [RelativeSync](RelativeSync/README.md) | [Download](RelativeSync/RelativeSync.zip) | -| [ShareBubbles](ShareBubbles/README.md) | [Download](ShareBubbles/ShareBubbles.zip) | -| [SmootherRay](SmootherRay/README.md) | [Download](SmootherRay/SmootherRay.zip) | -| [Stickers](Stickers/README.md) | [Download](Stickers/Stickers.zip) | -| [ThirdPerson](ThirdPerson/README.md) | [Download](ThirdPerson/ThirdPerson.zip) | +| Name | Description | Download | +|------|-------------|----------| +| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](ASTExtension/ASTExtension.zip) | +| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | +| [CustomRichPresence](CustomRichPresence/README.md) | Gives the Drop Prop button more utility by allowing you to drop props in the air. | [Download](CustomRichPresence/CustomRichPresence.zip) | +| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | +| [FuckToes](FuckToes/README.md) | No description available | [Download](FuckToes/FuckToes.zip) | +| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | +| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](LazyPrune/LazyPrune.zip) | +| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](PropLoadingHexagon/PropLoadingHexagon.zip) | +| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.zip) | +| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | [Download](RelativeSync/RelativeSync.zip) | +| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](ShareBubbles/ShareBubbles.zip) | +| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](SmootherRay/SmootherRay.zip) | +| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](Stickers/Stickers.zip) | +| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](ThirdPerson/ThirdPerson.zip) | ### Experimental Mods -| Name | Download | -|------|----------| -| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | -| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | -| [LuaTTS](.Experimental/LuaTTS/README.md) | [Download](.Experimental/LuaTTS/LuaTTS.zip) | -| [OriginShift](.Experimental/OriginShift/README.md) | [Download](.Experimental/OriginShift/OriginShift.zip) | -| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | [Download](.Experimental/ScriptingSpoofer/ScriptingSpoofer.zip) | +| Name | Description | Download | +|------|-------------|----------| +| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | +| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | +| [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | [Download](.Experimental/LuaTTS/LuaTTS.zip) | +| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | [Download](.Experimental/OriginShift/OriginShift.zip) | +| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | [Download](.Experimental/ScriptingSpoofer/ScriptingSpoofer.zip) | From ef6ad34a4eb3c6cccc3868fd9c1be320560fb28d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:32:15 -0500 Subject: [PATCH 124/188] [NAK_CVR_Mods] Fixed finding description for FuckToes --- .github/scripts/update-modlist.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js index 7045224..b404561 100644 --- a/.github/scripts/update-modlist.js +++ b/.github/scripts/update-modlist.js @@ -29,10 +29,14 @@ function extractDescription(readmePath) { } } - // If we found a header, try to get the third line after it - if (headerIndex !== -1 && headerIndex + 2 < lines.length) { - const description = lines[headerIndex + 2].trim(); - return description || 'No description available'; + // If we found a header, look for the first non-empty line after it + if (headerIndex !== -1) { + for (let i = headerIndex + 1; i < lines.length; i++) { + const line = lines[i].trim(); + if (line && !line.startsWith('#')) { + return line; + } + } } return 'No description available'; From c13e48638c96ff9da9c321374b754065ca25a2f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Apr 2025 21:32:36 +0000 Subject: [PATCH 125/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dde9417..0a11162 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | | [CustomRichPresence](CustomRichPresence/README.md) | Gives the Drop Prop button more utility by allowing you to drop props in the air. | [Download](CustomRichPresence/CustomRichPresence.zip) | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | -| [FuckToes](FuckToes/README.md) | No description available | [Download](FuckToes/FuckToes.zip) | +| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | [Download](FuckToes/FuckToes.zip) | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](LazyPrune/LazyPrune.zip) | | [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](PropLoadingHexagon/PropLoadingHexagon.zip) | From ba26a1faae0c5f3f76ccef66a6bbe0d57faceac2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:39:16 -0500 Subject: [PATCH 126/188] [CustomRichPresence] Move to .Experimental folder --- .../CustomRichPresence}/CustomRichPresence.csproj | 0 {CustomRichPresence => .Experimental/CustomRichPresence}/Main.cs | 0 .../CustomRichPresence}/Properties/AssemblyInfo.cs | 0 .../CustomRichPresence}/README.md | 0 .../CustomRichPresence}/format.json | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {CustomRichPresence => .Experimental/CustomRichPresence}/CustomRichPresence.csproj (100%) rename {CustomRichPresence => .Experimental/CustomRichPresence}/Main.cs (100%) rename {CustomRichPresence => .Experimental/CustomRichPresence}/Properties/AssemblyInfo.cs (100%) rename {CustomRichPresence => .Experimental/CustomRichPresence}/README.md (100%) rename {CustomRichPresence => .Experimental/CustomRichPresence}/format.json (100%) diff --git a/CustomRichPresence/CustomRichPresence.csproj b/.Experimental/CustomRichPresence/CustomRichPresence.csproj similarity index 100% rename from CustomRichPresence/CustomRichPresence.csproj rename to .Experimental/CustomRichPresence/CustomRichPresence.csproj diff --git a/CustomRichPresence/Main.cs b/.Experimental/CustomRichPresence/Main.cs similarity index 100% rename from CustomRichPresence/Main.cs rename to .Experimental/CustomRichPresence/Main.cs diff --git a/CustomRichPresence/Properties/AssemblyInfo.cs b/.Experimental/CustomRichPresence/Properties/AssemblyInfo.cs similarity index 100% rename from CustomRichPresence/Properties/AssemblyInfo.cs rename to .Experimental/CustomRichPresence/Properties/AssemblyInfo.cs diff --git a/CustomRichPresence/README.md b/.Experimental/CustomRichPresence/README.md similarity index 100% rename from CustomRichPresence/README.md rename to .Experimental/CustomRichPresence/README.md diff --git a/CustomRichPresence/format.json b/.Experimental/CustomRichPresence/format.json similarity index 100% rename from CustomRichPresence/format.json rename to .Experimental/CustomRichPresence/format.json From 392390cde7a097f851f94f984bf0a130b7c8d342 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:39:32 -0500 Subject: [PATCH 127/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] Initial push --- NAK_CVR_Mods.sln | 6 + .../Main.cs | 479 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 ++ .../README.md | 19 + ...eMyPropNowWeAreHavingSoftTacosLater.csproj | 6 + .../format.json | 23 + 6 files changed, 565 insertions(+) create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/README.md create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/format.json diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 976736f..26deacd 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -54,6 +54,8 @@ EndProject EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreMyPropNowWeAreHavingSoftTacosLater", "YouAreMyPropNowWeAreHavingSoftTacosLater\YouAreMyPropNowWeAreHavingSoftTacosLater.csproj", "{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}" +EndProject EndProject EndProject EndProject @@ -289,6 +291,10 @@ Global {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU + {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs new file mode 100644 index 0000000..645f7c0 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -0,0 +1,479 @@ +using System.Collections; +using System.Reflection; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking; +using ABI_RC.Core.Networking.API.Responses; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using ABI_RC.Systems.GameEventSystem; +using ABI_RC.Systems.Movement; +using ABI.CCK.Components; +using DarkRift; +using HarmonyLib; +using MelonLoader; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; +using Random = UnityEngine.Random; + +namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater; + +public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod +{ + #region Melon Events + + public override void OnInitializeMelon() + { + #region CVRPickupObject Patches + + HarmonyInstance.Patch( + typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.OnGrab), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRPickupObjectOnGrab), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.OnDrop), BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRPickupObjectOnDrop), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRPickupObject Patches + + #region CVRAttachment Patches + + HarmonyInstance.Patch( // Cannot compile when using nameof + typeof(CVRAttachment).GetMethod("\u003CAttachInternal\u003Eg__DoAttachmentSetup\u007C43_0", + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentAttachInternal), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(CVRAttachment).GetMethod(nameof(CVRAttachment.DeAttach)), + prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentDeAttach), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRAttachment Patches + + #region CVRSyncHelper Patches + + HarmonyInstance.Patch( // Replaces method, original needlessly ToArray??? + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.ClearProps), + BindingFlags.Public | BindingFlags.Static), + prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRSyncHelperClearProps), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRSyncHelper Patches + + #region CVRDownloadManager Patches + + HarmonyInstance.Patch( + typeof(CVRDownloadManager).GetMethod(nameof(CVRDownloadManager.QueueTask), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRDownloadManagerQueueTask), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRDownloadManager Patches + + #region BetterBetterCharacterController Patches + + HarmonyInstance.Patch( + typeof(BetterBetterCharacterController).GetMethod(nameof(BetterBetterCharacterController.SetSitting), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnBetterBetterCharacterControllerSetSitting), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion BetterBetterCharacterController Patches + + #region CVRWorld Game Events + + CVRGameEventSystem.World.OnLoad.AddListener(OnWorldLoad); + CVRGameEventSystem.World.OnUnload.AddListener(OnWorldUnload); + + #endregion CVRWorld Game Events + + #region Instances Game Events + + CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected); + + #endregion Instances Game Events + } + + #endregion Melon Events + + #region Harmony Patches + + private static readonly List _heldPropData = new(); + private static GameObject _persistantPropsContainer; + private static GameObject GetOrCreatePropsContainer() + { + if (_persistantPropsContainer != null) return _persistantPropsContainer; + _persistantPropsContainer = new("[NAK] PersistantProps"); + _persistantPropsContainer.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + _persistantPropsContainer.transform.localScale = Vector3.one; + Object.DontDestroyOnLoad(_persistantPropsContainer); + return _persistantPropsContainer; + } + + private static readonly Dictionary _keyToPropData = new(); + private static readonly Stack _spawnablePositionStack = new(); + private static bool _ignoreNextSeatExit; + private static float _heightOffset; + + private static void OnCVRPickupObjectOnGrab(CVRPickupObject __instance) + { + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + } + + private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance) + { + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + } + + private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance) + { + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + } + + private static void OnCVRAttachmentDeAttach(CVRAttachment __instance) + { + if (!__instance._isAttached) return; // Can invoke DeAttach without being attached + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + } + + // ReSharper disable UnusedParameter.Local + private static bool OnCVRDownloadManagerQueueTask(string assetId, DownloadTask.ObjectType type, string assetUrl, string fileId, long fileSize, string fileKey, string toAttach, + string fileHash = null, UgcTagsData tagsData = null, CVRLoadingAvatarController loadingAvatarController = null, + bool joinOnComplete = false, bool isHomeRequested = false, int compatibilityVersion = 0, int encryptionAlgorithm = 0, + string spawnerId = null) + { + if (type != DownloadTask.ObjectType.Prop) return true; // Only care about props + + // toAttach is our instanceId, lets find the propData + if (!GetPropDataById(toAttach, out CVRSyncHelper.PropData newPropData)) return true; + + // Check if this is a prop we requested to spawn + Vector3 identity = GetIdentityKeyFromPropData(newPropData); + if (!_keyToPropData.Remove(identity, out CVRSyncHelper.PropData originalPropData)) return true; + + // Remove original prop data from held + if (_heldPropData.Contains(originalPropData)) _heldPropData.Remove(originalPropData); + + // If original prop data is null spawn a new prop i guess :( + if (originalPropData.Spawnable == null) return true; + + // Add the new prop data to our held props in place of the old one + if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData); + + // Apply new prop data to the spawnable + newPropData.Spawnable = originalPropData.Spawnable; + newPropData.Wrapper = originalPropData.Wrapper; + newPropData.Wrapper.name = $"p+{newPropData.ObjectId}~{newPropData.InstanceId}"; + + // Copy sync values + Array.Copy(newPropData.CustomFloats, originalPropData.CustomFloats, newPropData.CustomFloatsAmount); + + CVRSyncHelper.ApplyPropValuesSpawn(newPropData); + + // Place the prop in the additive content scene + PlacePropInAdditiveContentScene(newPropData.Spawnable); + + // Clear old data so Recycle() doesn't delete our prop + originalPropData.Spawnable = null; + originalPropData.Wrapper = null; + originalPropData.Recycle(); + + return false; + } + + private static bool OnCVRSyncHelperClearProps() // Prevent deleting of our held props on scene load + { + for (var index = CVRSyncHelper.Props.Count - 1; index >= 0; index--) + { + CVRSyncHelper.PropData prop = CVRSyncHelper.Props[index]; + if (prop.Spawnable != null && _heldPropData.Contains(prop)) + continue; // Do not recycle props that are valid & held + + DeleteOrRecycleProp(prop); + } + + CVRSyncHelper.MySpawnedPropInstanceIds.Clear(); + return false; + } + + private static bool OnBetterBetterCharacterControllerSetSitting(bool isSitting, CVRSeat cvrSeat = null, bool callExitSeat = true) + { + if (!_ignoreNextSeatExit) return true; + _ignoreNextSeatExit = false; + if (BetterBetterCharacterController.Instance._lastCvrSeat == null) return true; // run original + return false; // dont run if there is a chair & we skipped it + } + + #endregion Harmony Patches + + #region Game Events + + private object _worldLoadTimer; + + private void OnWorldLoad(string _) + { + CVRWorld worldInstance = CVRWorld.Instance; + if (worldInstance != null && !worldInstance.allowSpawnables) + { + foreach (CVRSyncHelper.PropData prop in _heldPropData) DeleteOrRecycleProp(prop); // Delete all props we kept + return; + } + + for (var index = _heldPropData.Count - 1; index >= 0; index--) + { + CVRSyncHelper.PropData prop = _heldPropData[index]; + if (prop.Spawnable == null) + { + DeleteOrRecycleProp(prop); + return; + } + + // apply positions + int stackCount = _spawnablePositionStack.Count; + for (int i = stackCount - 1; i >= 0; i--) _spawnablePositionStack.Pop().ReapplyOffsets(); + } + + // Start a timer, and anything that did not load within 3 seconds will be destroyed + if (_worldLoadTimer != null) + { + MelonCoroutines.Stop(_worldLoadTimer); + _worldLoadTimer = null; + } + _worldLoadTimer = MelonCoroutines.Start(DestroyPersistantPropContainerInFive()); + _ignoreNextSeatExit = true; // just in case we are in a car / vehicle + } + + private IEnumerator DestroyPersistantPropContainerInFive() + { + yield return new WaitForSeconds(3f); + _worldLoadTimer = null; + Object.Destroy(_persistantPropsContainer); + _persistantPropsContainer = null; + _keyToPropData.Clear(); // no more chances + } + + private static void OnWorldUnload(string _) + { + // Prevent deleting of our held props on scene destruction + foreach (CVRSyncHelper.PropData prop in _heldPropData) + { + if (prop.Spawnable == null) continue; + PlacePropInPersistantPropsContainer(prop.Spawnable); + _spawnablePositionStack.Push(new SpawnablePositionContainer(prop.Spawnable)); + } + + // Likely in a vehicle + _heightOffset = BetterBetterCharacterController.Instance._lastCvrSeat != null + ? GetHeightOffsetFromPlayer() + : 0f; + } + + private static void OnInstanceConnected(string _) + { + // Request the server to respawn our props by GUID, and add a secret key to the propData to identify it + + foreach (CVRSyncHelper.PropData prop in _heldPropData) + { + if (prop.Spawnable == null) continue; + + // Generate a new identity key for the prop (this is used to identify the prop when we respawn it) + Vector3 identityKey = new(Random.Range(0, 1000), Random.Range(0, 1000), Random.Range(0, 1000)); + _keyToPropData.Add(identityKey, prop); + + SpawnPropFromGuid(prop.ObjectId, + new Vector3(prop.PositionX, prop.PositionY, prop.PositionZ), + new Vector3(prop.RotationX, prop.RotationY, prop.RotationZ), + identityKey); + } + } + + #endregion Game Events + + #region Util + + private static bool GetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData) + { + if (spawnable == null) + { + propData = null; + return false; + } + foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props) + { + if (data.InstanceId != spawnable.instanceId) continue; + propData = data; + return true; + } + propData = null; + return false; + } + + private static bool GetPropDataById(string instanceId, out CVRSyncHelper.PropData propData) + { + foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props) + { + if (data.InstanceId != instanceId) continue; + propData = data; + return true; + } + propData = null; + return false; + } + + private static void PlacePropInAdditiveContentScene(CVRSpawnable spawnable) + { + spawnable.transform.parent.SetParent(null); // Unparent from the prop container + SceneManager.MoveGameObjectToScene(spawnable.transform.parent.gameObject, + SceneManager.GetSceneByName(CVRObjectLoader.AdditiveContentSceneName)); + } + + private static void PlacePropInPersistantPropsContainer(CVRSpawnable spawnable) + { + spawnable.transform.parent.SetParent(GetOrCreatePropsContainer().transform); + } + + private static void DeleteOrRecycleProp(CVRSyncHelper.PropData prop) + { + if (prop.Spawnable == null) prop.Recycle(); + else prop.Spawnable.Delete(); + if (_heldPropData.Contains(prop)) _heldPropData.Remove(prop); + } + + private static void SpawnPropFromGuid(string propGuid, Vector3 position, Vector3 rotation, Vector3 identityKey) + { + using DarkRiftWriter darkRiftWriter = DarkRiftWriter.Create(); + darkRiftWriter.Write(propGuid); + darkRiftWriter.Write(position.x); + darkRiftWriter.Write(position.y); + darkRiftWriter.Write(position.z); + darkRiftWriter.Write(rotation.x); + darkRiftWriter.Write(rotation.y); + darkRiftWriter.Write(rotation.z); + darkRiftWriter.Write(identityKey.x); // for scale, but unused by CVR + darkRiftWriter.Write(identityKey.y); // we will use this to identify our prop + darkRiftWriter.Write(identityKey.z); // and recycle existing instance if it exists + darkRiftWriter.Write(0f); // if not zero, prop spawn will be rejected by gs + using Message message = Message.Create(10050, darkRiftWriter); + NetworkManager.Instance.GameNetwork.SendMessage(message, SendMode.Reliable); + } + + private static Vector3 GetIdentityKeyFromPropData(CVRSyncHelper.PropData propData) + => new(propData.ScaleX, propData.ScaleY, propData.ScaleZ); + + private const int WORLD_RAYCAST_LAYER_MASK = + (1 << 0) | // Default + (1 << 16) | (1 << 17) | (1 << 18) | (1 << 19) | + (1 << 20) | (1 << 21) | (1 << 22) | (1 << 23) | + (1 << 24) | (1 << 25) | (1 << 26) | (1 << 27) | + (1 << 28) | (1 << 29) | (1 << 30) | (1 << 31); + + private static float GetHeightOffsetFromPlayer() + { + Vector3 playerPos = PlayerSetup.Instance.GetPlayerPosition(); + Ray ray = new(playerPos, Vector3.down); + + // ReSharper disable once Unity.PreferNonAllocApi + RaycastHit[] hits = Physics.RaycastAll(ray, 1000f, WORLD_RAYCAST_LAYER_MASK, QueryTriggerInteraction.Ignore); + Scene baseScene = SceneManager.GetActiveScene(); + + float closestDist = float.MaxValue; + Vector3 closestPoint = Vector3.zero; + bool foundValidHit = false; + + foreach (RaycastHit hit in hits) + { + if (hit.collider.gameObject.scene != baseScene) continue; // Ignore objects not in the world scene + if (!(hit.distance < closestDist)) continue; + closestDist = hit.distance; + closestPoint = hit.point; + foundValidHit = true; + } + + if (!foundValidHit) return 0f; // TODO: idk if i should do this + float offset = playerPos.y - closestPoint.y; + return Mathf.Clamp(offset, 0f, 20f); + } + + #endregion Util + + #region Helper Classes + + private readonly struct SpawnablePositionContainer + { + private readonly CVRSpawnable _spawnable; + private readonly Vector3[] _posOffsets; + private readonly Quaternion[] _rotOffsets; + + public SpawnablePositionContainer(CVRSpawnable spawnable) + { + _spawnable = spawnable; + + int syncedTransforms = 1 + _spawnable.subSyncs.Count; // root + subSyncs + + _posOffsets = new Vector3[syncedTransforms]; + _rotOffsets = new Quaternion[syncedTransforms]; + + Transform playerTransform = PlayerSetup.Instance.transform; + + // Save root offset relative to player + Transform _spawnableTransform = _spawnable.transform; + _posOffsets[0] = playerTransform.InverseTransformPoint(_spawnableTransform.position); + _rotOffsets[0] = Quaternion.Inverse(playerTransform.rotation) * _spawnableTransform.rotation; + + // Save subSync offsets relative to player + for (int i = 0; i < _spawnable.subSyncs.Count; i++) + { + Transform subSyncTransform = _spawnable.subSyncs[i].transform; + if (subSyncTransform == null) continue; + _posOffsets[i + 1] = playerTransform.InverseTransformPoint(subSyncTransform.position); + _rotOffsets[i + 1] = Quaternion.Inverse(playerTransform.rotation) * subSyncTransform.rotation; + } + } + + public void ReapplyOffsets() + { + Transform playerTransform = PlayerSetup.Instance.transform; + + // Reapply to root + Vector3 rootWorldPos = playerTransform.TransformPoint(_posOffsets[0]); + rootWorldPos.y += _heightOffset; + _spawnable.transform.position = rootWorldPos; + _spawnable.transform.rotation = playerTransform.rotation * _rotOffsets[0]; + + // Reapply to subSyncs + for (int i = 0; i < _spawnable.subSyncs.Count; i++) + { + Transform subSyncTransform = _spawnable.subSyncs[i].transform; + if (subSyncTransform == null) continue; + + Vector3 subWorldPos = playerTransform.TransformPoint(_posOffsets[i + 1]); + subWorldPos.y += _heightOffset; + subSyncTransform.position = subWorldPos; + subSyncTransform.rotation = playerTransform.rotation * _rotOffsets[i + 1]; + } + + // hack + _spawnable.needsUpdate = true; + _spawnable.UpdateSubSyncValues(); + _spawnable.sendUpdate(); + } + } + + #endregion Helper Classes +} \ No newline at end of file diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bfc0a13 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))] + +[assembly: MelonInfo( + typeof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.YouAreMyPropNowWeAreHavingSoftTacosLaterMod), + nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md new file mode 100644 index 0000000..9ddff99 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md @@ -0,0 +1,19 @@ +# YouAreMyPropNowWeAreHavingSoftTacosLater + +Lets you bring held & attached props through world loads. + +https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO + +## Examples +https://fixupx.com/NotAKidoS/status/1910545346922422675 + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj b/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json new file mode 100644 index 0000000..78c5bbc --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "YouAreMyPropNowWeAreHavingSoftTacosLater", + "modversion": "1.0.0", + "gameversion": "2025r179", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO", + "searchtags": [ + "prop", + "spawn", + "friend", + "load" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/", + "changelog": "- Initial Release", + "embedcolor": "#00FFFF" +} \ No newline at end of file From 0cde9ecb21676ed05b5b74dff1d82aea8e658f30 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Apr 2025 12:39:51 +0000 Subject: [PATCH 128/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a11162..84b53d5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ |------|-------------|----------| | [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](ASTExtension/ASTExtension.zip) | | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | -| [CustomRichPresence](CustomRichPresence/README.md) | Gives the Drop Prop button more utility by allowing you to drop props in the air. | [Download](CustomRichPresence/CustomRichPresence.zip) | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | | [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | [Download](FuckToes/FuckToes.zip) | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | @@ -20,12 +19,14 @@ | [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](SmootherRay/SmootherRay.zip) | | [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](Stickers/Stickers.zip) | | [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](ThirdPerson/ThirdPerson.zip) | +| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | [Download](YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.zip) | ### Experimental Mods | Name | Description | Download | |------|-------------|----------| | [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | +| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Gives the Drop Prop button more utility by allowing you to drop props in the air. | [Download](.Experimental/CustomRichPresence/CustomRichPresence.zip) | | [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | | [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | [Download](.Experimental/LuaTTS/LuaTTS.zip) | | [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | [Download](.Experimental/OriginShift/OriginShift.zip) | From b246f71e6eac1cbe754a939b87ded0b732302c0e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:40:53 -0500 Subject: [PATCH 129/188] [CustomRichPresence] fix readme --- .Experimental/CustomRichPresence/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.Experimental/CustomRichPresence/README.md b/.Experimental/CustomRichPresence/README.md index 33bd3fd..1f37b48 100644 --- a/.Experimental/CustomRichPresence/README.md +++ b/.Experimental/CustomRichPresence/README.md @@ -1,6 +1,6 @@ -# DropPropTweak +# Custom Rich Presence -Gives the Drop Prop button more utility by allowing you to drop props in the air. +Lets you customize the Steam & Discord rich presence messages & values. --- From a2aa3b9871ccaab0657967d5e6ada19d3a53d6c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Apr 2025 12:41:14 +0000 Subject: [PATCH 130/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84b53d5..b8a81e8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ | Name | Description | Download | |------|-------------|----------| | [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | -| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Gives the Drop Prop button more utility by allowing you to drop props in the air. | [Download](.Experimental/CustomRichPresence/CustomRichPresence.zip) | +| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | [Download](.Experimental/CustomRichPresence/CustomRichPresence.zip) | | [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | | [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | [Download](.Experimental/LuaTTS/LuaTTS.zip) | | [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | [Download](.Experimental/OriginShift/OriginShift.zip) | From 8f8f2ad1fb780126d98d07e34a35850c6e2d62ef Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:45:31 -0500 Subject: [PATCH 131/188] [NAK_CVR_Mods] fixed generated download link in readme --- .github/scripts/update-modlist.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js index b404561..37cf1c5 100644 --- a/.github/scripts/update-modlist.js +++ b/.github/scripts/update-modlist.js @@ -52,11 +52,11 @@ function formatTable(mods, baseDir) { let rows = mods.map(modPath => { const modName = path.basename(modPath); const readmeLink = path.join(modPath, 'README.md'); - const zipLink = path.join(modPath, `${modName}.zip`); + const dllLink = path.join(modPath, `${modName}.dll`); const readmePath = path.join(modPath, 'README.md'); const description = extractDescription(readmePath); - return `| [${modName}](${readmeLink}) | ${description} | [Download](${zipLink}) |`; + return `| [${modName}](${readmeLink}) | ${description} | [Download](${dllLink}) |`; }); return [ From a2e29149e29397d96956a26fe8d32cdb87db77cb Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:46:51 -0500 Subject: [PATCH 132/188] [NAK_CVR_Mods] test --- .github/scripts/update-modlist.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js index 37cf1c5..383e275 100644 --- a/.github/scripts/update-modlist.js +++ b/.github/scripts/update-modlist.js @@ -1,6 +1,5 @@ const fs = require('fs'); const path = require('path'); - const ROOT = '.'; const EXPERIMENTAL = '.Experimental'; const README_PATH = 'README.md'; @@ -19,7 +18,7 @@ function extractDescription(readmePath) { try { const content = fs.readFileSync(readmePath, 'utf8'); const lines = content.split('\n'); - + // Find the first header (# something) let headerIndex = -1; for (let i = 0; i < lines.length; i++) { @@ -28,7 +27,7 @@ function extractDescription(readmePath) { break; } } - + // If we found a header, look for the first non-empty line after it if (headerIndex !== -1) { for (let i = headerIndex + 1; i < lines.length; i++) { @@ -38,7 +37,7 @@ function extractDescription(readmePath) { } } } - + return 'No description available'; } catch (error) { console.error(`Error reading ${readmePath}:`, error); @@ -46,19 +45,29 @@ function extractDescription(readmePath) { } } +function checkIfDllExists(modPath, modName) { + const dllPath = path.join(modPath, `${modName}.dll`); + return fs.existsSync(dllPath); +} + function formatTable(mods, baseDir) { if (mods.length === 0) return ''; - + let rows = mods.map(modPath => { const modName = path.basename(modPath); const readmeLink = path.join(modPath, 'README.md'); - const dllLink = path.join(modPath, `${modName}.dll`); const readmePath = path.join(modPath, 'README.md'); const description = extractDescription(readmePath); - return `| [${modName}](${readmeLink}) | ${description} | [Download](${dllLink}) |`; + // Check if DLL exists and format download cell accordingly + const hasDll = checkIfDllExists(modPath, modName); + const downloadCell = hasDll + ? `[Download](${path.join(modPath, `${modName}.dll`)})` + : 'No Download'; + + return `| [${modName}](${readmeLink}) | ${description} | ${downloadCell} |`; }); - + return [ `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, '', @@ -73,7 +82,6 @@ function updateReadme(modListSection) { const readme = fs.readFileSync(README_PATH, 'utf8'); const before = readme.split(MARKER_START)[0]; const after = readme.split(MARKER_END)[1]; - const newReadme = `${before}${MARKER_START}\n\n${modListSection}\n${MARKER_END}${after}`; fs.writeFileSync(README_PATH, newReadme); } From d5eb9ae1a08553803510dd8efa591b7c61beb074 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Apr 2025 12:47:11 +0000 Subject: [PATCH 133/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b8a81e8..017ca92 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,31 @@ | Name | Description | Download | |------|-------------|----------| -| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](ASTExtension/ASTExtension.zip) | -| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](AvatarQueueSystemTweaks/AvatarQueueSystemTweaks.zip) | -| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](CustomSpawnPoint/CustomSpawnPoint.zip) | -| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | [Download](FuckToes/FuckToes.zip) | -| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](KeepVelocityOnExitFlight/KeepVelocityOnExitFlight.zip) | -| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](LazyPrune/LazyPrune.zip) | -| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](PropLoadingHexagon/PropLoadingHexagon.zip) | -| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.zip) | -| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | [Download](RelativeSync/RelativeSync.zip) | -| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](ShareBubbles/ShareBubbles.zip) | -| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](SmootherRay/SmootherRay.zip) | -| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](Stickers/Stickers.zip) | -| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](ThirdPerson/ThirdPerson.zip) | -| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | [Download](YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.zip) | +| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | No Download | +| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | No Download | +| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | No Download | +| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | No Download | +| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | No Download | +| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | No Download | +| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download | +| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | No Download | +| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | No Download | +| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | No Download | +| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download | +| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download | +| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | No Download | +| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | No Download | ### Experimental Mods | Name | Description | Download | |------|-------------|----------| -| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | [Download](.Experimental/CVRLuaToolsExtension/CVRLuaToolsExtension.zip) | -| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | [Download](.Experimental/CustomRichPresence/CustomRichPresence.zip) | -| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | [Download](.Experimental/LuaNetworkVariables/LuaNetworkVariables.zip) | -| [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | [Download](.Experimental/LuaTTS/LuaTTS.zip) | -| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | [Download](.Experimental/OriginShift/OriginShift.zip) | -| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | [Download](.Experimental/ScriptingSpoofer/ScriptingSpoofer.zip) | +| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | No Download | +| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | No Download | +| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | No Download | +| [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | No Download | +| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | No Download | +| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | No Download | From 1fbfcd80cdb6b443079df37b825022d3edf61a5b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:52:15 -0500 Subject: [PATCH 134/188] [NAK_CVR_Mods] 2 --- .github/scripts/update-modlist.js | 160 +++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 36 deletions(-) diff --git a/.github/scripts/update-modlist.js b/.github/scripts/update-modlist.js index 383e275..8ed69bf 100644 --- a/.github/scripts/update-modlist.js +++ b/.github/scripts/update-modlist.js @@ -1,10 +1,56 @@ const fs = require('fs'); const path = require('path'); +const https = require('https'); + +// Configuration const ROOT = '.'; const EXPERIMENTAL = '.Experimental'; const README_PATH = 'README.md'; const MARKER_START = ''; const MARKER_END = ''; +const REPO_OWNER = process.env.REPO_OWNER || 'NotAKidoS'; +const REPO_NAME = process.env.REPO_NAME || 'NAK_CVR_Mods'; + +// Function to get latest release info from GitHub API +async function getLatestRelease() { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.github.com', + path: `/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`, + method: 'GET', + headers: { + 'User-Agent': 'Node.js GitHub Release Checker', + 'Accept': 'application/vnd.github.v3+json' + } + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode === 200) { + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(new Error(`Failed to parse GitHub API response: ${e.message}`)); + } + } else { + reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`)); + } + }); + }); + + req.on('error', (e) => { + reject(new Error(`GitHub API request error: ${e.message}`)); + }); + + req.end(); + }); +} function getModFolders(baseDir) { const entries = fs.readdirSync(baseDir, { withFileTypes: true }); @@ -45,37 +91,69 @@ function extractDescription(readmePath) { } } -function checkIfDllExists(modPath, modName) { - const dllPath = path.join(modPath, `${modName}.dll`); - return fs.existsSync(dllPath); -} - -function formatTable(mods, baseDir) { +async function formatTable(mods, baseDir) { if (mods.length === 0) return ''; - let rows = mods.map(modPath => { - const modName = path.basename(modPath); - const readmeLink = path.join(modPath, 'README.md'); - const readmePath = path.join(modPath, 'README.md'); - const description = extractDescription(readmePath); + try { + // Get the latest release info from GitHub + const latestRelease = await getLatestRelease(); + const releaseAssets = latestRelease.assets || []; - // Check if DLL exists and format download cell accordingly - const hasDll = checkIfDllExists(modPath, modName); - const downloadCell = hasDll - ? `[Download](${path.join(modPath, `${modName}.dll`)})` - : 'No Download'; - - return `| [${modName}](${readmeLink}) | ${description} | ${downloadCell} |`; - }); - - return [ - `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, - '', - '| Name | Description | Download |', - '|------|-------------|----------|', - ...rows, - '' - ].join('\n'); + // Create a map of available files in the release + const availableFiles = {}; + releaseAssets.forEach(asset => { + availableFiles[asset.name] = asset.browser_download_url; + }); + + let rows = mods.map(modPath => { + const modName = path.basename(modPath); + const readmeLink = path.join(modPath, 'README.md'); + const readmePath = path.join(modPath, 'README.md'); + const description = extractDescription(readmePath); + + // Check if the DLL exists in the latest release + const dllFilename = `${modName}.dll`; + let downloadSection; + + if (availableFiles[dllFilename]) { + downloadSection = `[Download](${availableFiles[dllFilename]})`; + } else { + downloadSection = 'No Download'; + } + + return `| [${modName}](${readmeLink}) | ${description} | ${downloadSection} |`; + }); + + return [ + `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, + '', + '| Name | Description | Download |', + '|------|-------------|----------|', + ...rows, + '' + ].join('\n'); + } catch (error) { + console.error('Error fetching release information:', error); + + // Fallback to showing "No Download" for all mods if we can't fetch release info + let rows = mods.map(modPath => { + const modName = path.basename(modPath); + const readmeLink = path.join(modPath, 'README.md'); + const readmePath = path.join(modPath, 'README.md'); + const description = extractDescription(readmePath); + + return `| [${modName}](${readmeLink}) | ${description} | No Download |`; + }); + + return [ + `### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`, + '', + '| Name | Description | Download |', + '|------|-------------|----------|', + ...rows, + '' + ].join('\n'); + } } function updateReadme(modListSection) { @@ -86,12 +164,22 @@ function updateReadme(modListSection) { fs.writeFileSync(README_PATH, newReadme); } -const mainMods = getModFolders(ROOT).filter(dir => !dir.startsWith(EXPERIMENTAL)); -const experimentalMods = getModFolders(EXPERIMENTAL); +async function main() { + try { + const mainMods = getModFolders(ROOT).filter(dir => !dir.startsWith(EXPERIMENTAL)); + const experimentalMods = getModFolders(EXPERIMENTAL); + + const mainModsTable = await formatTable(mainMods, ROOT); + const experimentalModsTable = await formatTable(experimentalMods, EXPERIMENTAL); + + const tableContent = [mainModsTable, experimentalModsTable].join('\n'); + updateReadme(tableContent); + + console.log('README.md updated successfully!'); + } catch (error) { + console.error('Error updating README:', error); + process.exit(1); + } +} -const tableContent = [ - formatTable(mainMods, ROOT), - formatTable(experimentalMods, EXPERIMENTAL) -].join('\n'); - -updateReadme(tableContent); \ No newline at end of file +main(); \ No newline at end of file From 2af27cc81d1278b956e2d043cf499b35f002f6b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Apr 2025 12:52:38 +0000 Subject: [PATCH 135/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 017ca92..cb01fcb 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,19 @@ | Name | Description | Download | |------|-------------|----------| -| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | No Download | -| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | No Download | -| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | No Download | +| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll) | +| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | +| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | | [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | No Download | -| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | No Download | -| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | No Download | -| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download | -| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | No Download | -| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | No Download | -| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | No Download | -| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download | -| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download | -| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | No Download | +| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | +| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | +| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropLoadingHexagon.dll) | +| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll) | +| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll) | +| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll) | +| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll) | +| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll) | +| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll) | | [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | No Download | ### Experimental Mods From a8e0553e53532fe83913f12c7a1a80edbd083dff Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 02:41:43 -0500 Subject: [PATCH 136/188] [DoubleTapJumpToExitSeat] Initial push --- .../DoubleTapJumpToExitSeat.csproj | 6 + DoubleTapJumpToExitSeat/Main.cs | 103 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 ++++++ DoubleTapJumpToExitSeat/README.md | 14 +++ DoubleTapJumpToExitSeat/format.json | 23 ++++ 5 files changed, 178 insertions(+) create mode 100644 DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj create mode 100644 DoubleTapJumpToExitSeat/Main.cs create mode 100644 DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs create mode 100644 DoubleTapJumpToExitSeat/README.md create mode 100644 DoubleTapJumpToExitSeat/format.json diff --git a/DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj b/DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/DoubleTapJumpToExitSeat/Main.cs b/DoubleTapJumpToExitSeat/Main.cs new file mode 100644 index 0000000..91c268e --- /dev/null +++ b/DoubleTapJumpToExitSeat/Main.cs @@ -0,0 +1,103 @@ +using System.Reflection; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.InputManagement; +using ABI_RC.Systems.Movement; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.DoubleTapJumpToExitSeat; + +public class DoubleTapJumpToExitSeatMod : MelonMod +{ + #region Melon Preferences + + public static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(DoubleTapJumpToExitSeatMod)); + + public static readonly MelonPreferences_Entry EntryOnlyInVR = + Category.CreateEntry("only_in_vr", false, display_name: "Only In VR", description: "Should this behaviour only be active in VR?"); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + #region CVRSeat Patches + + HarmonyInstance.Patch( + typeof(CVRSeat).GetMethod(nameof(CVRSeat.Update), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPreCVRSeatUpdate), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRSeat Patches + + #region ViewManager Patches + + HarmonyInstance.Patch( + typeof(ViewManager).GetMethod(nameof(ViewManager.Update), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPreViewManagerUpdate), + BindingFlags.NonPublic | BindingFlags.Static)), + postfix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPostViewManagerUpdate), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion ViewManager Patches + } + + #endregion Melon Events + + #region Harmony Patches + + private static float lastJumpTime = -1f; + private static bool wasJumping; + + private static bool OnPreCVRSeatUpdate(CVRSeat __instance) + { + if (!__instance.occupied) return false; + + // Crazy? + bool jumped = CVRInputManager.Instance.jump; + bool justJumped = jumped && !wasJumping; + wasJumping = jumped; + if (justJumped && (!EntryOnlyInVR.Value || MetaPort.Instance.isUsingVr)) + { + float t = Time.time; + if (t - lastJumpTime <= BetterBetterCharacterController.DoubleJumpFlightTimeOut) + { + lastJumpTime = -1f; + __instance.ExitSeat(); + return false; + } + lastJumpTime = t; + } + + // Double update this frame (this ensures Extrapolate / Every Frame Updated objects are seated correctly) + if (__instance.vrSitPosition.position != __instance._lastPosition || __instance.vrSitPosition.rotation != __instance._lastRotation) + __instance.MovePlayerToSeat(__instance.vrSitPositionReady ? __instance.vrSitPosition : __instance.transform); + + // Steal sync + if (__instance.lockControls) + { + if (__instance._spawnable != null) __instance._spawnable.ForceUpdate(4); + if (__instance._objectSync != null) __instance._objectSync.ForceUpdate(4); + } + + return false; // don't call original method + } + + // ReSharper disable once RedundantAssignment + private static void OnPreViewManagerUpdate(ref bool __state) + => (__state, BetterBetterCharacterController.Instance._isSitting) + = (BetterBetterCharacterController.Instance._isSitting, false); + + private static void OnPostViewManagerUpdate(ref bool __state) + => BetterBetterCharacterController.Instance._isSitting = __state; + + #endregion Harmony Patches +} \ No newline at end of file diff --git a/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs b/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fd30036 --- /dev/null +++ b/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.DoubleTapJumpToExitSeat.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.DoubleTapJumpToExitSeat))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.DoubleTapJumpToExitSeat))] + +[assembly: MelonInfo( + typeof(NAK.DoubleTapJumpToExitSeat.DoubleTapJumpToExitSeatMod), + nameof(NAK.DoubleTapJumpToExitSeat), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.DoubleTapJumpToExitSeat.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/DoubleTapJumpToExitSeat/README.md b/DoubleTapJumpToExitSeat/README.md new file mode 100644 index 0000000..df6f722 --- /dev/null +++ b/DoubleTapJumpToExitSeat/README.md @@ -0,0 +1,14 @@ +# DoubleTapJumpToExitSeat + +Literally the mod name. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/DoubleTapJumpToExitSeat/format.json b/DoubleTapJumpToExitSeat/format.json new file mode 100644 index 0000000..b7fa28e --- /dev/null +++ b/DoubleTapJumpToExitSeat/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "DoubleTapJumpToExitSeat", + "modversion": "1.0.0", + "gameversion": "2025r179", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Literally the mod name.", + "searchtags": [ + "double", + "jump", + "chair", + "seat" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat/", + "changelog": "- Initial Release", + "embedcolor": "#00FFFF" +} \ No newline at end of file From 8764339ac8b462dce73d2617df3260930455120e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Apr 2025 07:41:58 +0000 Subject: [PATCH 137/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cb01fcb..56402aa 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ | [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll) | | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | +| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Literally the mod name. | No Download | | [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | No Download | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | From 89b8f19288aced61416e7b8ad3cf5ce9baa14c8d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 03:33:10 -0500 Subject: [PATCH 138/188] [YouAreMyPropNowWeAreHavingSoftTacosLaterMod] added settingd --- .../Main.cs | 71 +++++++++++++++++-- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs index 645f7c0..b4f22de 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -21,6 +21,25 @@ namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater; public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod { + #region Melon Preferences + + public static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod)); + + public static readonly MelonPreferences_Entry EntryTrackPickups = + Category.CreateEntry("track_pickups", true, display_name: "Track Pickups", description: "Should pickups be tracked?"); + + public static readonly MelonPreferences_Entry EntryTrackAttachments = + Category.CreateEntry("track_attachments", true, display_name: "Track Attachments", description: "Should attachments be tracked?"); + + public static readonly MelonPreferences_Entry EntryTrackSeats = + Category.CreateEntry("track_seats", true, display_name: "Track Seats", description: "Should seats be tracked?"); + + public static readonly MelonPreferences_Entry EntryOnlySpawnedByMe = + Category.CreateEntry("only_spawned_by_me", true, display_name: "Only Spawned By Me", description: "Should only props spawned by me be tracked?"); + + #endregion Melon Preferences + #region Melon Events public override void OnInitializeMelon() @@ -58,6 +77,24 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod ); #endregion CVRAttachment Patches + + #region CVRSeat Patches + + HarmonyInstance.Patch( + typeof(CVRSeat).GetMethod(nameof(CVRSeat.SitDown), + BindingFlags.Public | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRSeatSitDown), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( + typeof(CVRSeat).GetMethod(nameof(CVRSeat.ExitSeat), + BindingFlags.Public | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRSeatExitSeat), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRSeat Patches #region CVRSyncHelper Patches @@ -129,26 +166,41 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod private static void OnCVRPickupObjectOnGrab(CVRPickupObject __instance) { - if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!EntryTrackPickups.Value) return; + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); } private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance) { - if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); } private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance) { - if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!EntryTrackAttachments.Value) return; + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); } private static void OnCVRAttachmentDeAttach(CVRAttachment __instance) { if (!__instance._isAttached) return; // Can invoke DeAttach without being attached - if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + } + + private static void OnCVRSeatSitDown(CVRSeat __instance) + { + if (!EntryTrackSeats.Value) return; + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + } + + private static void OnCVRSeatExitSeat(CVRSeat __instance) + { + if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); } @@ -161,7 +213,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod if (type != DownloadTask.ObjectType.Prop) return true; // Only care about props // toAttach is our instanceId, lets find the propData - if (!GetPropDataById(toAttach, out CVRSyncHelper.PropData newPropData)) return true; + if (!TryGetPropDataById(toAttach, out CVRSyncHelper.PropData newPropData)) return true; // Check if this is a prop we requested to spawn Vector3 identity = GetIdentityKeyFromPropData(newPropData); @@ -307,13 +359,18 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod #region Util - private static bool GetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData) + private static bool TryGetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData) { if (spawnable == null) { propData = null; return false; } + if (EntryOnlySpawnedByMe.Value && !spawnable.IsMine()) + { + propData = null; + return false; + } foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props) { if (data.InstanceId != spawnable.instanceId) continue; @@ -324,7 +381,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod return false; } - private static bool GetPropDataById(string instanceId, out CVRSyncHelper.PropData propData) + private static bool TryGetPropDataById(string instanceId, out CVRSyncHelper.PropData propData) { foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props) { From 1f99312c22024bc15f405a674649171d82b4203d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 04:05:48 -0500 Subject: [PATCH 139/188] [WindowFocusCheckFix] iniyial push --- WindowFocusCheckFix/Main.cs | 39 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 +++++++++++++++ WindowFocusCheckFix/README.md | 14 +++++++ .../WindowFocusCheckFix.csproj | 6 +++ WindowFocusCheckFix/format.json | 23 +++++++++++ 5 files changed, 114 insertions(+) create mode 100644 WindowFocusCheckFix/Main.cs create mode 100644 WindowFocusCheckFix/Properties/AssemblyInfo.cs create mode 100644 WindowFocusCheckFix/README.md create mode 100644 WindowFocusCheckFix/WindowFocusCheckFix.csproj create mode 100644 WindowFocusCheckFix/format.json diff --git a/WindowFocusCheckFix/Main.cs b/WindowFocusCheckFix/Main.cs new file mode 100644 index 0000000..24d5de5 --- /dev/null +++ b/WindowFocusCheckFix/Main.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using ABI_RC.Core; +using HarmonyLib; +using MelonLoader; +using UnityEngine; + +namespace NAK.WindowFocusCheckFix; + +public class WindowFocusCheckFixMod : MelonMod +{ + #region Melon Events + + public override void OnInitializeMelon() + { + #region WindowFocusManager Patches + + HarmonyInstance.Patch( + typeof(WindowFocusManager).GetMethod(nameof(WindowFocusManager.IsWindowFocused), + BindingFlags.NonPublic | BindingFlags.Static), + prefix: new HarmonyMethod(typeof(WindowFocusCheckFixMod).GetMethod(nameof(OnPreWindowFocusManagerIsWindowFocused), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion WindowFocusManager Patches + } + + #endregion Melon Events + + #region Harmony Patches + + // ReSharper disable once RedundantAssignment + private static bool OnPreWindowFocusManagerIsWindowFocused(ref bool __result) + { + __result = Application.isFocused; // use Unity method instead + return false; + } + + #endregion Harmony Patches +} \ No newline at end of file diff --git a/WindowFocusCheckFix/Properties/AssemblyInfo.cs b/WindowFocusCheckFix/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1f95200 --- /dev/null +++ b/WindowFocusCheckFix/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.WindowFocusCheckFix.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.WindowFocusCheckFix))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.WindowFocusCheckFix))] + +[assembly: MelonInfo( + typeof(NAK.WindowFocusCheckFix.WindowFocusCheckFixMod), + nameof(NAK.WindowFocusCheckFix), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WindowFocusCheckFix" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.WindowFocusCheckFix.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/WindowFocusCheckFix/README.md b/WindowFocusCheckFix/README.md new file mode 100644 index 0000000..5c2dbe0 --- /dev/null +++ b/WindowFocusCheckFix/README.md @@ -0,0 +1,14 @@ +# WindowFocusCheckFix + +Fixes window focus check on specific Linux distribution. ????!??!?! + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/WindowFocusCheckFix/WindowFocusCheckFix.csproj b/WindowFocusCheckFix/WindowFocusCheckFix.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/WindowFocusCheckFix/WindowFocusCheckFix.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/WindowFocusCheckFix/format.json b/WindowFocusCheckFix/format.json new file mode 100644 index 0000000..9fe9458 --- /dev/null +++ b/WindowFocusCheckFix/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "WindowFocusCheckFix", + "modversion": "1.0.0", + "gameversion": "2025r179", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Fixes window focus check on specific Linux distribution. ????!??!?!\n", + "searchtags": [ + "prop", + "spawn", + "friend", + "load" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/WindowFocusCheckFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WindowFocusCheckFix/", + "changelog": "- Initial Release", + "embedcolor": "#00FFFF" +} \ No newline at end of file From 446a89f35d0643dcb66e486d7ddbb3fe26f75fab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Apr 2025 09:06:01 +0000 Subject: [PATCH 140/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56402aa..77e26db 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ | [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll) | | [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll) | | [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll) | +| [WindowFocusCheckFix](WindowFocusCheckFix/README.md) | Fixes window focus check on specific Linux distribution. ????!??!?! | No Download | | [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | No Download | ### Experimental Mods From 1f8435edb9fe4f5245b5f13d0437e5b0aa26c38f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 04:29:25 -0500 Subject: [PATCH 141/188] nvm --- WindowFocusCheckFix/Main.cs | 39 ------------------- .../Properties/AssemblyInfo.cs | 32 --------------- WindowFocusCheckFix/README.md | 14 ------- .../WindowFocusCheckFix.csproj | 6 --- WindowFocusCheckFix/format.json | 23 ----------- 5 files changed, 114 deletions(-) delete mode 100644 WindowFocusCheckFix/Main.cs delete mode 100644 WindowFocusCheckFix/Properties/AssemblyInfo.cs delete mode 100644 WindowFocusCheckFix/README.md delete mode 100644 WindowFocusCheckFix/WindowFocusCheckFix.csproj delete mode 100644 WindowFocusCheckFix/format.json diff --git a/WindowFocusCheckFix/Main.cs b/WindowFocusCheckFix/Main.cs deleted file mode 100644 index 24d5de5..0000000 --- a/WindowFocusCheckFix/Main.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Reflection; -using ABI_RC.Core; -using HarmonyLib; -using MelonLoader; -using UnityEngine; - -namespace NAK.WindowFocusCheckFix; - -public class WindowFocusCheckFixMod : MelonMod -{ - #region Melon Events - - public override void OnInitializeMelon() - { - #region WindowFocusManager Patches - - HarmonyInstance.Patch( - typeof(WindowFocusManager).GetMethod(nameof(WindowFocusManager.IsWindowFocused), - BindingFlags.NonPublic | BindingFlags.Static), - prefix: new HarmonyMethod(typeof(WindowFocusCheckFixMod).GetMethod(nameof(OnPreWindowFocusManagerIsWindowFocused), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - #endregion WindowFocusManager Patches - } - - #endregion Melon Events - - #region Harmony Patches - - // ReSharper disable once RedundantAssignment - private static bool OnPreWindowFocusManagerIsWindowFocused(ref bool __result) - { - __result = Application.isFocused; // use Unity method instead - return false; - } - - #endregion Harmony Patches -} \ No newline at end of file diff --git a/WindowFocusCheckFix/Properties/AssemblyInfo.cs b/WindowFocusCheckFix/Properties/AssemblyInfo.cs deleted file mode 100644 index 1f95200..0000000 --- a/WindowFocusCheckFix/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MelonLoader; -using NAK.WindowFocusCheckFix.Properties; -using System.Reflection; - -[assembly: AssemblyVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.WindowFocusCheckFix))] -[assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.WindowFocusCheckFix))] - -[assembly: MelonInfo( - typeof(NAK.WindowFocusCheckFix.WindowFocusCheckFixMod), - nameof(NAK.WindowFocusCheckFix), - AssemblyInfoParams.Version, - AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WindowFocusCheckFix" -)] - -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] -[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] -[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 246, 25, 99)] // red-pink -[assembly: MelonAuthorColor(255, 158, 21, 32)] // red -[assembly: HarmonyDontPatchAll] - -namespace NAK.WindowFocusCheckFix.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.0.0"; - public const string Author = "NotAKidoS"; -} \ No newline at end of file diff --git a/WindowFocusCheckFix/README.md b/WindowFocusCheckFix/README.md deleted file mode 100644 index 5c2dbe0..0000000 --- a/WindowFocusCheckFix/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# WindowFocusCheckFix - -Fixes window focus check on specific Linux distribution. ????!??!?! - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/WindowFocusCheckFix/WindowFocusCheckFix.csproj b/WindowFocusCheckFix/WindowFocusCheckFix.csproj deleted file mode 100644 index 5a8badc..0000000 --- a/WindowFocusCheckFix/WindowFocusCheckFix.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - YouAreMineNow - - diff --git a/WindowFocusCheckFix/format.json b/WindowFocusCheckFix/format.json deleted file mode 100644 index 9fe9458..0000000 --- a/WindowFocusCheckFix/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "WindowFocusCheckFix", - "modversion": "1.0.0", - "gameversion": "2025r179", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Fixes window focus check on specific Linux distribution. ????!??!?!\n", - "searchtags": [ - "prop", - "spawn", - "friend", - "load" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/WindowFocusCheckFix.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WindowFocusCheckFix/", - "changelog": "- Initial Release", - "embedcolor": "#00FFFF" -} \ No newline at end of file From 8ad74b5ef66bfe74c60fce715f7e046ac98dc00b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Apr 2025 09:29:46 +0000 Subject: [PATCH 142/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 77e26db..56402aa 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ | [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll) | | [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll) | | [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll) | -| [WindowFocusCheckFix](WindowFocusCheckFix/README.md) | Fixes window focus check on specific Linux distribution. ????!??!?! | No Download | | [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | No Download | ### Experimental Mods From 21b791083b0624b01837ec9cb55b1f8f41e7f67d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:20:22 -0500 Subject: [PATCH 143/188] [DoubleTapJumpToExitSeat] bump for release --- DoubleTapJumpToExitSeat/Main.cs | 27 ++++++++------------------- DoubleTapJumpToExitSeat/README.md | 2 +- DoubleTapJumpToExitSeat/format.json | 2 +- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/DoubleTapJumpToExitSeat/Main.cs b/DoubleTapJumpToExitSeat/Main.cs index 91c268e..c3b5a6f 100644 --- a/DoubleTapJumpToExitSeat/Main.cs +++ b/DoubleTapJumpToExitSeat/Main.cs @@ -1,6 +1,5 @@ using System.Reflection; using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Savior; using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.Movement; using HarmonyLib; @@ -11,16 +10,6 @@ namespace NAK.DoubleTapJumpToExitSeat; public class DoubleTapJumpToExitSeatMod : MelonMod { - #region Melon Preferences - - public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(DoubleTapJumpToExitSeatMod)); - - public static readonly MelonPreferences_Entry EntryOnlyInVR = - Category.CreateEntry("only_in_vr", false, display_name: "Only In VR", description: "Should this behaviour only be active in VR?"); - - #endregion Melon Preferences - #region Melon Events public override void OnInitializeMelon() @@ -54,8 +43,8 @@ public class DoubleTapJumpToExitSeatMod : MelonMod #region Harmony Patches - private static float lastJumpTime = -1f; - private static bool wasJumping; + private static float _lastJumpTime = -1f; + private static bool _wasJumping; private static bool OnPreCVRSeatUpdate(CVRSeat __instance) { @@ -63,18 +52,18 @@ public class DoubleTapJumpToExitSeatMod : MelonMod // Crazy? bool jumped = CVRInputManager.Instance.jump; - bool justJumped = jumped && !wasJumping; - wasJumping = jumped; - if (justJumped && (!EntryOnlyInVR.Value || MetaPort.Instance.isUsingVr)) + bool justJumped = jumped && !_wasJumping; + _wasJumping = jumped; + if (justJumped) { float t = Time.time; - if (t - lastJumpTime <= BetterBetterCharacterController.DoubleJumpFlightTimeOut) + if (t - _lastJumpTime <= BetterBetterCharacterController.DoubleJumpFlightTimeOut) { - lastJumpTime = -1f; + _lastJumpTime = -1f; __instance.ExitSeat(); return false; } - lastJumpTime = t; + _lastJumpTime = t; } // Double update this frame (this ensures Extrapolate / Every Frame Updated objects are seated correctly) diff --git a/DoubleTapJumpToExitSeat/README.md b/DoubleTapJumpToExitSeat/README.md index df6f722..c154674 100644 --- a/DoubleTapJumpToExitSeat/README.md +++ b/DoubleTapJumpToExitSeat/README.md @@ -1,6 +1,6 @@ # DoubleTapJumpToExitSeat -Literally the mod name. +Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. --- diff --git a/DoubleTapJumpToExitSeat/format.json b/DoubleTapJumpToExitSeat/format.json index b7fa28e..f09cc1f 100644 --- a/DoubleTapJumpToExitSeat/format.json +++ b/DoubleTapJumpToExitSeat/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Literally the mod name.", + "description": "Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu.", "searchtags": [ "double", "jump", From 377b365cdcb0fe7ce90129b44a201b490099b200 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:20:37 -0500 Subject: [PATCH 144/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] fix accessors --- YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs index b4f22de..7ed15f1 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -23,19 +23,19 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod { #region Melon Preferences - public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod)); + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(YouAreMyPropNowWeAreHavingSoftTacosLater)); - public static readonly MelonPreferences_Entry EntryTrackPickups = + private static readonly MelonPreferences_Entry EntryTrackPickups = Category.CreateEntry("track_pickups", true, display_name: "Track Pickups", description: "Should pickups be tracked?"); - public static readonly MelonPreferences_Entry EntryTrackAttachments = + private static readonly MelonPreferences_Entry EntryTrackAttachments = Category.CreateEntry("track_attachments", true, display_name: "Track Attachments", description: "Should attachments be tracked?"); - public static readonly MelonPreferences_Entry EntryTrackSeats = + private static readonly MelonPreferences_Entry EntryTrackSeats = Category.CreateEntry("track_seats", true, display_name: "Track Seats", description: "Should seats be tracked?"); - public static readonly MelonPreferences_Entry EntryOnlySpawnedByMe = + private static readonly MelonPreferences_Entry EntryOnlySpawnedByMe = Category.CreateEntry("only_spawned_by_me", true, display_name: "Only Spawned By Me", description: "Should only props spawned by me be tracked?"); #endregion Melon Preferences From d5d4e3eddd5895e881f559f93a9752822bb02c80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Apr 2025 21:20:39 +0000 Subject: [PATCH 145/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56402aa..5bd6054 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ | [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll) | | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | -| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Literally the mod name. | No Download | +| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll) | | [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | No Download | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | From 9606b10c9d2b731f53f20531718e33fcfea23c42 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:20:56 -0500 Subject: [PATCH 146/188] [NAK_CVR_Mods] update solution --- NAK_CVR_Mods.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 26deacd..a85eb63 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -56,6 +56,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreMyPropNowWeAreHavingSoftTacosLater", "YouAreMyPropNowWeAreHavingSoftTacosLater\YouAreMyPropNowWeAreHavingSoftTacosLater.csproj", "{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoubleTapJumpToExitSeat", "DoubleTapJumpToExitSeat\DoubleTapJumpToExitSeat.csproj", "{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}" +EndProject EndProject EndProject EndProject @@ -295,6 +297,10 @@ Global {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.Build.0 = Release|Any CPU + {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c4ab9cce47649a30930a8ed5ff03d4da01d4ccf5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:23:00 -0500 Subject: [PATCH 147/188] [DoubleTapJumpToExitSeat] fix format.json --- DoubleTapJumpToExitSeat/format.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DoubleTapJumpToExitSeat/format.json b/DoubleTapJumpToExitSeat/format.json index f09cc1f..ab8e14e 100644 --- a/DoubleTapJumpToExitSeat/format.json +++ b/DoubleTapJumpToExitSeat/format.json @@ -18,6 +18,6 @@ ], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat/", - "changelog": "- Initial Release", - "embedcolor": "#00FFFF" + "changelog": "- Initial release", + "embedcolor": "#f61963" } \ No newline at end of file From 6e37bcbabb74bba955e415400a67a26c8f392f71 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:38:19 -0500 Subject: [PATCH 148/188] [FuckToes] i guess bring this back --- FuckToes/Main.cs | 20 +++++++++----------- FuckToes/Properties/AssemblyInfo.cs | 2 +- FuckToes/README.md | 6 ++---- FuckToes/format.json | 8 ++++---- NAK_CVR_Mods.sln | 6 ++++++ 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/FuckToes/Main.cs b/FuckToes/Main.cs index 7e6a936..7fc6892 100644 --- a/FuckToes/Main.cs +++ b/FuckToes/Main.cs @@ -3,23 +3,22 @@ using ABI_RC.Systems.IK; using MelonLoader; using RootMotion.FinalIK; using System.Reflection; +using ABI_RC.Core; namespace NAK.FuckToes; public class FuckToesMod : MelonMod { - private static MelonLogger.Instance Logger; - #region Melon Preferences private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(FuckToesMod)); + MelonPreferences.CreateCategory(nameof(FuckToes)); private static readonly MelonPreferences_Entry EntryEnabledVR = - Category.CreateEntry("Enabled in HalfBody", true, description: "Nuke VRIK toes when in Halfbody."); + Category.CreateEntry("use_in_halfbody", true, display_name:"No Toes in Halfbody", description: "Nuke VRIK toes when in Halfbody."); private static readonly MelonPreferences_Entry EntryEnabledFBT = - Category.CreateEntry("Enabled in FBT", true, description: "Nuke VRIK toes when in FBT."); + Category.CreateEntry("use_in_fbt", true, display_name:"No Toes in Fullbody", description: "Nuke VRIK toes when in FBT."); #endregion Melon Preferences @@ -27,10 +26,10 @@ public class FuckToesMod : MelonMod public override void OnInitializeMelon() { - Logger = LoggerInstance; HarmonyInstance.Patch( typeof(VRIK).GetMethod(nameof(VRIK.AutoDetectReferences)), - prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToesMod).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToesMod).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix), + BindingFlags.NonPublic | BindingFlags.Static)) ); } @@ -43,13 +42,12 @@ public class FuckToesMod : MelonMod try { // Must be PlayerLocal layer and in VR - if (__instance.gameObject.layer != 8 + if (__instance.gameObject.layer != CVRLayers.PlayerLocal || !MetaPort.Instance.isUsingVr) return; switch (IKSystem.Instance.BodySystem.FullBodyActive) { - case false when !EntryEnabledVR.Value: // Not in FBT, and not enabled, perish case true when !EntryEnabledFBT.Value: // In FBT, and not enabled in fbt, perish return; @@ -61,8 +59,8 @@ public class FuckToesMod : MelonMod } catch (Exception e) { - Logger.Error($"Error during the patched method {nameof(OnVRIKAutoDetectReferences_Prefix)}"); - Logger.Error(e); + MelonLogger.Error($"Error during the patched method {nameof(OnVRIKAutoDetectReferences_Prefix)}"); + MelonLogger.Error(e); } } diff --git a/FuckToes/Properties/AssemblyInfo.cs b/FuckToes/Properties/AssemblyInfo.cs index c26d688..c308a51 100644 --- a/FuckToes/Properties/AssemblyInfo.cs +++ b/FuckToes/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.FuckToes.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/FuckToes/README.md b/FuckToes/README.md index e9b9e4d..6caf1f4 100644 --- a/FuckToes/README.md +++ b/FuckToes/README.md @@ -1,7 +1,6 @@ # FuckToes -Prevents VRIK from autodetecting toes in HalfbodyIK. -Optionally can be applied in FBT, but toes in FBT are nice so you are a monster if so. +Prevents VRIK from autodetecting toes in Halfbody or Fullbody. ![fuckthetoes](https://user-images.githubusercontent.com/37721153/216518012-ae3b1dde-17ea-419a-a875-48d57e13f3dd.png) @@ -14,5 +13,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. - +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. \ No newline at end of file diff --git a/FuckToes/format.json b/FuckToes/format.json index c02103b..3bd10a4 100644 --- a/FuckToes/format.json +++ b/FuckToes/format.json @@ -1,8 +1,8 @@ { "_id": 129, "name": "FuckToes", - "modversion": "1.0.3", - "gameversion": "2023r171", + "modversion": "1.0.4", + "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r14/FuckToes.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/FuckToes.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckToes/", - "changelog": "- Fixes for 2023r171.", + "changelog": "- Recompiled for 2025r179", "embedcolor": "#f61963" } \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index a85eb63..eb7c1a3 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -58,6 +58,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreMyPropNowWeAreHavingS EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoubleTapJumpToExitSeat", "DoubleTapJumpToExitSeat\DoubleTapJumpToExitSeat.csproj", "{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckToes", "FuckToes\FuckToes.csproj", "{751E4140-2F4D-4550-A4A9-65ABA9F7893A}" +EndProject EndProject EndProject EndProject @@ -301,6 +303,10 @@ Global {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.Build.0 = Release|Any CPU + {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 8343d6c5bb22e7e10e97d6629855f9f637b91370 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Apr 2025 21:38:33 +0000 Subject: [PATCH 149/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bd6054..e3217d1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | | [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll) | -| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in HalfbodyIK. | No Download | +| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | No Download | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | | [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropLoadingHexagon.dll) | From f6afea3c4452965480d248bb6d360c3f1f14c32f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:41:05 -0500 Subject: [PATCH 150/188] [FuckToes] fix format.json --- FuckToes/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FuckToes/format.json b/FuckToes/format.json index 3bd10a4..c7c42ee 100644 --- a/FuckToes/format.json +++ b/FuckToes/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", - "description": "Prevents VRIK from using toe bones in HalfBody or FBT.\n\nVRIK calculates weird center of mass when toes are mapped, so it is sometimes desired to unmap toes to prevent an avatars feet from resting far back.\n\nPlease see the README for relevant imagery detailing the problem.", + "description": "Prevents VRIK from using toe bones in HalfBody or FBT.\n\nVRIK calculates weird center of mass when toes are mapped, so it is sometimes desired to unmap toes to prevent an avatars feet from resting far back.\n\nPlease see the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckToes/README.md) for relevant imagery detailing the problem.", "searchtags": [ "toes", "vrik", From 47b69dfbc77524554d8d1c2d8b0c5aaae05cd000 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 17:01:24 -0500 Subject: [PATCH 151/188] [RelativeSync] add support for YouAreMyPropNowWeAreHavingSoftTacosLaterMod --- .../Components/RelativeSyncMarker.cs | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs b/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs index 0669d81..9627146 100644 --- a/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs +++ b/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs @@ -12,18 +12,35 @@ public class RelativeSyncMarker : MonoBehaviour public bool IsComponentActive => _component.isActiveAndEnabled; - + public bool ApplyRelativePosition = true; public bool ApplyRelativeRotation = true; public bool OnlyApplyRelativeHeading; - + private MonoBehaviour _component; - + private void Start() { + RegisterWithManager(); + ConfigureForPotentialMovementParent(); + } + + private void OnDestroy() + { + RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash); + } + + public void OnHavingSoftTacosNow() + => RegisterWithManager(); + + private void RegisterWithManager() + { + // Remove old hash in case this is a re-registration + RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash); + string path = GetGameObjectPath(transform); int hash = path.GetHashCode(); - + // check if it already exists (this **should** only matter in worlds) if (RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash)) { @@ -34,18 +51,11 @@ public class RelativeSyncMarker : MonoBehaviour return; } } - + pathHash = hash; RelativeSyncManager.RelativeSyncTransforms.Add(hash, this); - - ConfigureForPotentialMovementParent(); } - private void OnDestroy() - { - RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash); - } - private void ConfigureForPotentialMovementParent() { if (!gameObject.TryGetComponent(out CVRMovementParent movementParent)) @@ -54,20 +64,20 @@ public class RelativeSyncMarker : MonoBehaviour return; } _component = movementParent; - + // TODO: a refactor may be needed to handle the orientation mode being animated - + // respect orientation mode & gravity zone ApplyRelativeRotation = movementParent.orientationMode == CVRMovementParent.OrientationMode.RotateWithParent; OnlyApplyRelativeHeading = movementParent.GetComponent() == null; } - + private static string GetGameObjectPath(Transform transform) { // props already have a unique instance identifier at root // worlds uhhhh, dont duplicate the same thing over and over thx // avatars on remote/local client have diff path, we need to account for it -_- - + string path = transform.name; while (transform.parent != null) { @@ -79,22 +89,22 @@ public class RelativeSyncMarker : MonoBehaviour path = MetaPort.Instance.ownerId + "/" + path; break; } // remote player object root is already player guid - + path = transform.name + "/" + path; } - + return path; } - - private bool FindAvailableHash(ref int hash) + + private static bool FindAvailableHash(ref int hash) { for (int i = 0; i < 16; i++) { hash += 1; if (!RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash)) return true; } - + // failed to find a hash in 16 tries, dont care return false; } -} \ No newline at end of file +} From ece15e0dfc56ff4c982712d86aa016ce9d202250 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sat, 12 Apr 2025 17:01:33 -0500 Subject: [PATCH 152/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] fix for relative sync --- YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs index 7ed15f1..02e39c0 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -231,6 +231,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod // Apply new prop data to the spawnable newPropData.Spawnable = originalPropData.Spawnable; newPropData.Wrapper = originalPropData.Wrapper; + newPropData.Wrapper.BroadcastMessage("OnHavingSoftTacosNow", SendMessageOptions.DontRequireReceiver); // support with RelativeSync newPropData.Wrapper.name = $"p+{newPropData.ObjectId}~{newPropData.InstanceId}"; // Copy sync values From e85c1e2f2506446265d10f5a2199d9eb88e13176 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:11:23 -0500 Subject: [PATCH 153/188] [LuaNetworkVariables] Remove exp stuff, fix context properties --- .Experimental/LuaNetworkVariables/Main.cs | 28 +- .../LuaNetworkVariables/NetLuaModule.cs | 22 +- .../NetworkVariables/LuaEventContext.cs | 20 +- .../LuaNetVarController.Base.cs | 32 +- .../LuaNetVarController.Networking.cs | 31 +- .Experimental/LuaNetworkVariables/Patches.cs | 41 +-- .../Properties/AssemblyInfo.cs | 4 +- .../SyncedBehaviour/MNSyncedBehaviour.cs | 326 ------------------ .../SyncedBehaviour/TestSyncedBehaviour.cs | 63 ---- .../SyncedBehaviour/TestSyncedObject.cs | 42 --- 10 files changed, 95 insertions(+), 514 deletions(-) delete mode 100644 .Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs delete mode 100644 .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs delete mode 100644 .Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs diff --git a/.Experimental/LuaNetworkVariables/Main.cs b/.Experimental/LuaNetworkVariables/Main.cs index d8c5def..2a8b2fb 100644 --- a/.Experimental/LuaNetworkVariables/Main.cs +++ b/.Experimental/LuaNetworkVariables/Main.cs @@ -18,24 +18,22 @@ public class LuaNetVarsMod : MelonMod public override void OnInitializeMelon() { Logger = LoggerInstance; - ApplyPatches(typeof(Patches.LuaScriptFactory_Patches)); - ApplyPatches(typeof(Patches.CVRSyncHelper_Patches)); } - public override void OnUpdate() - { - // if (Input.GetKeyDown(KeyCode.F1)) - // { - // PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0"); - // } - // - // if (Input.GetKeyDown(KeyCode.F2)) - // { - // GameObject go = new("TestSyncedObject"); - // go.AddComponent(); - // } - } + // public override void OnUpdate() + // { + // // if (Input.GetKeyDown(KeyCode.F1)) + // // { + // // PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0"); + // // } + // // + // // if (Input.GetKeyDown(KeyCode.F2)) + // // { + // // GameObject go = new("TestSyncedObject"); + // // go.AddComponent(); + // // } + // } #endregion Melon Events diff --git a/.Experimental/LuaNetworkVariables/NetLuaModule.cs b/.Experimental/LuaNetworkVariables/NetLuaModule.cs index 319fefa..b13bc9b 100644 --- a/.Experimental/LuaNetworkVariables/NetLuaModule.cs +++ b/.Experimental/LuaNetworkVariables/NetLuaModule.cs @@ -1,12 +1,11 @@ using ABI_RC.Core.Base; using ABI.Scripting.CVRSTL.Common; using JetBrains.Annotations; -using NAK.LuaNetVars; using MoonSharp.Interpreter; namespace NAK.LuaNetVars.Modules; -[PublicAPI] // Indicates that this class is used and should not be considered unused +[PublicAPI] public class LuaNetModule : BaseScriptedStaticWrapper { public const string MODULE_ID = "NetworkModule"; @@ -101,6 +100,25 @@ public class LuaNetModule : BaseScriptedStaticWrapper _controller.SendLuaEvent(eventName, args); } + + /// + /// Sends a Lua event to other clients. + /// + /// The name of the event to send. + /// Optional arguments to send with the event. + public void SendLuaEventToUser(string eventName, string userId, params DynValue[] args) + { + CheckIfCanAccessMethod(nameof(SendLuaEventToUser), false, + CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY); + + if (_controller == null) + { + LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + return; + } + + _controller.SendLuaEventToUser(eventName, userId, args); + } /// /// Checks if the current client is the owner of the synchronized object. diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs index 01fbdc4..6a3fd57 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs @@ -1,6 +1,6 @@ -using MoonSharp.Interpreter; +using ABI_RC.Core.Networking; +using MoonSharp.Interpreter; using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; namespace NAK.LuaNetVars; @@ -12,9 +12,9 @@ public struct LuaEventContext private double TimeSinceLastInvoke { get; set; } private bool IsLocal { get; set; } - public static LuaEventContext Create(string senderId, DateTime lastInvokeTime) + public static LuaEventContext Create(bool isLocal, string senderId, DateTime lastInvokeTime) { - var playerName = CVRPlayerManager.Instance.TryGetPlayerName(senderId); + var playerName = isLocal ? AuthManager.Username : CVRPlayerManager.Instance.TryGetPlayerName(senderId); return new LuaEventContext { @@ -22,7 +22,7 @@ public struct LuaEventContext SenderName = playerName ?? "Unknown", LastInvokeTime = lastInvokeTime, TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds, - IsLocal = senderId == MetaPort.Instance.ownerId + IsLocal = isLocal }; } @@ -30,11 +30,11 @@ public struct LuaEventContext { Table table = new(script) { - ["senderId"] = SenderId, - ["senderName"] = SenderName, - ["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"), - ["timeSinceLastInvoke"] = TimeSinceLastInvoke, - ["isLocal"] = IsLocal + ["SenderId"] = SenderId, + ["SenderName"] = SenderName, + ["LastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"), + ["TimeSinceLastInvoke"] = TimeSinceLastInvoke, + ["IsLocal"] = IsLocal }; return table; } diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs index 589c405..811fa9b 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs @@ -5,6 +5,7 @@ using ABI.CCK.Components; using ABI.Scripting.CVRSTL.Common; using MoonSharp.Interpreter; using UnityEngine; +using Coroutine = UnityEngine.Coroutine; namespace NAK.LuaNetVars; @@ -26,21 +27,23 @@ public partial class LuaNetVarController : MonoBehaviour private bool _requestInitialSync; private CVRSpawnable _spawnable; private CVRObjectSync _objectSync; - + + private bool _isInitialized; + private Coroutine _syncCoroutine; + #region Unity Events private void Awake() - { - if (!Initialize()) - return; - - // TODO: a manager script should be in charge of this - // TODO: disabling object will kill coroutine - StartCoroutine(SendVariableUpdatesCoroutine()); - } - + => _isInitialized = Initialize(); + private void OnDestroy() => Cleanup(); + + private void OnEnable() + => StartStopVariableUpdatesCoroutine(true); + + private void OnDisable() + => StartStopVariableUpdatesCoroutine(false); #endregion Unity Events @@ -102,9 +105,16 @@ public partial class LuaNetVarController : MonoBehaviour _hashes.Remove(_uniquePathHash); } + private void StartStopVariableUpdatesCoroutine(bool start) + { + if (_syncCoroutine != null) StopCoroutine(_syncCoroutine); + _syncCoroutine = null; + if (start) _syncCoroutine = StartCoroutine(SendVariableUpdatesCoroutine()); + } + private System.Collections.IEnumerator SendVariableUpdatesCoroutine() { - while (true) + while (isActiveAndEnabled) { yield return new WaitForSeconds(0.1f); if (IsSyncOwner()) SendVariableUpdates(); diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs index 47f6415..bed3bb8 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs @@ -1,7 +1,6 @@ using ABI_RC.Core.Savior; using ABI_RC.Systems.ModNetwork; using MoonSharp.Interpreter; -using Unity.Services.Authentication.Internal; namespace NAK.LuaNetVars { @@ -66,7 +65,7 @@ namespace NAK.LuaNetVars msg.Read(out int argsCount); DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); - LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); + LuaEventContext context = LuaEventContext.Create(false, senderId, lastInvokeTime); // Update tracking _eventTracker.UpdateInvokeTime(eventName, senderId); @@ -187,7 +186,7 @@ namespace NAK.LuaNetVars { string senderId = MetaPort.Instance.ownerId; DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); - LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); + LuaEventContext context = LuaEventContext.Create(true, senderId, lastInvokeTime); // Update tracking _eventTracker.UpdateInvokeTime(eventName, senderId); @@ -209,6 +208,32 @@ namespace NAK.LuaNetVars modMsg.Send(); } + internal void SendLuaEventToUser(string eventName, string userId, DynValue[] args) + { + string senderId = MetaPort.Instance.ownerId; + DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); + LuaEventContext context = LuaEventContext.Create(true, senderId, lastInvokeTime); + + // Update tracking + _eventTracker.UpdateInvokeTime(eventName, senderId); + + var argsWithContext = new DynValue[args.Length + 1]; + argsWithContext[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script)); + Array.Copy(args, 0, argsWithContext, 1, args.Length); + + InvokeLuaEvent(eventName, argsWithContext); + + using ModNetworkMessage modMsg = new(ModNetworkID, userId); + modMsg.Write((byte)MessageType.LuaEvent); + modMsg.Write(eventName); + modMsg.Write(args.Length); + + foreach (DynValue arg in args) + SerializeDynValue(modMsg, arg); + + modMsg.Send(); + } + #endregion } } \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/Patches.cs b/.Experimental/LuaNetworkVariables/Patches.cs index ce861fe..672e1df 100644 --- a/.Experimental/LuaNetworkVariables/Patches.cs +++ b/.Experimental/LuaNetworkVariables/Patches.cs @@ -1,13 +1,8 @@ -using ABI_RC.Core.Base; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util; -using ABI.CCK.Components; -using ABI.Scripting.CVRSTL.Client; +using ABI.Scripting.CVRSTL.Client; using ABI.Scripting.CVRSTL.Common; using HarmonyLib; using MoonSharp.Interpreter; using NAK.LuaNetVars.Modules; -using UnityEngine; namespace NAK.LuaNetVars.Patches; @@ -28,38 +23,4 @@ internal static class LuaScriptFactory_Patches __result = LuaNetModule.RegisterUserData(____script, ____context); __instance.RegisteredModules[LuaNetModule.MODULE_ID] = __result; // add module to cache } -} - -internal static class CVRSyncHelper_Patches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.UpdatePropValues))] - private static void Postfix_CVRSyncHelper_UpdatePropValues( - Vector3 position, Vector3 rotation, Vector3 scale, - float[] syncValues, string guid, string instanceId, - Span subSyncValues, int numSyncValues, int syncType = 0) - { - CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(prop => prop.InstanceId == instanceId); - if (propData == null) return; - - // Update locally stored prop data with new values - // as GS does not reply with our own data... - - propData.PositionX = position.x; - propData.PositionY = position.y; - propData.PositionZ = position.z; - propData.RotationX = rotation.x; - propData.RotationY = rotation.y; - propData.RotationZ = rotation.z; - propData.ScaleX = scale.x; - propData.ScaleY = scale.y; - propData.ScaleZ = scale.z; - propData.CustomFloatsAmount = numSyncValues; - for (int i = 0; i < numSyncValues; i++) - propData.CustomFloats[i] = syncValues[i]; - - //propData.SpawnedBy - propData.syncedBy = MetaPort.Instance.ownerId; - propData.syncType = syncType; - } } \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs index 4cc49cf..84217ef 100644 --- a/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs +++ b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; nameof(NAK.LuaNetVars), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LuaNetworkVariables" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.LuaNetVars.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs deleted file mode 100644 index 3576155..0000000 --- a/.Experimental/LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs +++ /dev/null @@ -1,326 +0,0 @@ -using UnityEngine; -using System; -using System.Collections.Generic; -using System.Threading; -using ABI_RC.Core.Savior; -using ABI_RC.Systems.ModNetwork; - -namespace NAK.LuaNetVars -{ - public abstract class MNSyncedBehaviour : IDisposable - { - // Add static property for clarity - protected static string LocalUserId => MetaPort.Instance.ownerId; - - protected enum MessageType : byte - { - OwnershipRequest, - OwnershipResponse, - OwnershipTransfer, - StateRequest, - StateUpdate, - CustomData - } - - protected enum OwnershipResponse : byte - { - Accepted, - Rejected - } - - protected readonly string networkId; - protected string currentOwnerId; - private readonly bool autoAcceptTransfers; - private readonly Dictionary> pendingRequests; - private bool isInitialized; - private bool disposedValue; - private bool isSoftOwner = false; - private Timer stateRequestTimer; - private const int StateRequestTimeout = 3000; // 3 seconds - - public string CurrentOwnerId => currentOwnerId; - public bool HasOwnership => currentOwnerId == LocalUserId; - - protected MNSyncedBehaviour(string networkId, string currentOwnerId = "", bool autoAcceptTransfers = false) - { - this.networkId = networkId; - this.currentOwnerId = currentOwnerId; - this.autoAcceptTransfers = autoAcceptTransfers; - this.pendingRequests = new Dictionary>(); - - ModNetworkManager.Subscribe(networkId, OnMessageReceived); - - if (!HasOwnership) - RequestInitialState(); - else - isInitialized = true; - } - - private void RequestInitialState() - { - using ModNetworkMessage msg = new(networkId); - msg.Write((byte)MessageType.StateRequest); - msg.Send(); - - stateRequestTimer = new Timer(StateRequestTimeoutCallback, null, StateRequestTimeout, Timeout.Infinite); - } - - private void StateRequestTimeoutCallback(object state) - { - // If isInitialized is still false, we assume soft ownership - if (!isInitialized) - { - currentOwnerId = LocalUserId; - isSoftOwner = true; - isInitialized = true; - OnOwnershipChanged(currentOwnerId); - } - - stateRequestTimer.Dispose(); - stateRequestTimer = null; - } - - public virtual void RequestOwnership(Action callback = null) - { - if (HasOwnership) - { - callback?.Invoke(true); - return; - } - - using (ModNetworkMessage msg = new(networkId)) - { - msg.Write((byte)MessageType.OwnershipRequest); - msg.Send(); - } - - if (callback != null) - { - pendingRequests[LocalUserId] = callback; - } - } - - protected void SendNetworkedData(Action writeData) - { - if (!HasOwnership) - { - Debug.LogWarning($"[MNSyncedBehaviour] Cannot send data without ownership. NetworkId: {networkId}"); - return; - } - - using (ModNetworkMessage msg = new(networkId)) - { - msg.Write((byte)MessageType.CustomData); - writeData(msg); - msg.Send(); - } - } - - protected virtual void OnMessageReceived(ModNetworkMessage message) - { - message.Read(out byte type); - MessageType messageType = (MessageType)type; - - if (!Enum.IsDefined(typeof(MessageType), messageType)) - return; - - switch (messageType) - { - case MessageType.OwnershipRequest: - if (!HasOwnership) break; - HandleOwnershipRequest(message); - break; - - case MessageType.OwnershipResponse: - if (message.Sender != currentOwnerId) break; - HandleOwnershipResponse(message); - break; - - case MessageType.OwnershipTransfer: - if (message.Sender != currentOwnerId) break; - currentOwnerId = message.Sender; - OnOwnershipChanged(currentOwnerId); - break; - - case MessageType.StateRequest: - if (!HasOwnership) break; // this is the only safeguard against ownership hijacking... idk how to prevent it - // TODO: only respond to a StateUpdate if expecting one - HandleStateRequest(message); - break; - - case MessageType.StateUpdate: - // Accept state updates from current owner or if we have soft ownership - if (message.Sender != currentOwnerId && !isSoftOwner) break; - HandleStateUpdate(message); - break; - - case MessageType.CustomData: - if (message.Sender != currentOwnerId) - { - // If we have soft ownership and receive data from real owner, accept it - if (isSoftOwner && message.Sender != LocalUserId) - { - currentOwnerId = message.Sender; - isSoftOwner = false; - OnOwnershipChanged(currentOwnerId); - } - else - { - // Ignore data from non-owner - break; - } - } - HandleCustomData(message); - break; - } - } - - protected virtual void HandleOwnershipRequest(ModNetworkMessage message) - { - if (!HasOwnership) - return; - - string requesterId = message.Sender; - var response = autoAcceptTransfers ? OwnershipResponse.Accepted : - OnOwnershipRequested(requesterId); - - using (ModNetworkMessage responseMsg = new(networkId)) - { - responseMsg.Write((byte)MessageType.OwnershipResponse); - responseMsg.Write((byte)response); - responseMsg.Send(); - } - - if (response == OwnershipResponse.Accepted) - { - TransferOwnership(requesterId); - } - } - - protected virtual void HandleOwnershipResponse(ModNetworkMessage message) - { - message.Read(out byte responseByte); - OwnershipResponse response = (OwnershipResponse)responseByte; - - if (pendingRequests.TryGetValue(LocalUserId, out var callback)) - { - bool accepted = response == OwnershipResponse.Accepted; - callback(accepted); - pendingRequests.Remove(LocalUserId); - - // Update ownership locally only if accepted - if (accepted) - { - currentOwnerId = LocalUserId; - OnOwnershipChanged(currentOwnerId); - } - } - } - - protected virtual void HandleStateRequest(ModNetworkMessage message) - { - if (!HasOwnership) - return; - - using ModNetworkMessage response = new(networkId, message.Sender); - response.Write((byte)MessageType.StateUpdate); - WriteState(response); - response.Send(); - } - - protected virtual void HandleStateUpdate(ModNetworkMessage message) - { - currentOwnerId = message.Sender; - isSoftOwner = false; - ReadState(message); - isInitialized = true; - - // Dispose of the state request timer if it's still running - if (stateRequestTimer != null) - { - stateRequestTimer.Dispose(); - stateRequestTimer = null; - } - } - - protected virtual void HandleCustomData(ModNetworkMessage message) - { - if (!isInitialized) - { - Debug.LogWarning($"[MNSyncedBehaviour] Received custom data before initialization. NetworkId: {networkId}"); - return; - } - - if (message.Sender != currentOwnerId) - { - // If we have soft ownership and receive data from real owner, accept it - if (isSoftOwner && message.Sender != LocalUserId) - { - currentOwnerId = message.Sender; - isSoftOwner = false; - OnOwnershipChanged(currentOwnerId); - } - else - { - // Ignore data from non-owner - return; - } - } - - ReadCustomData(message); - } - - protected virtual void TransferOwnership(string newOwnerId) - { - using (ModNetworkMessage msg = new(networkId)) - { - msg.Write((byte)MessageType.OwnershipTransfer); - msg.Write(newOwnerId); // Include the new owner ID in transfer message - msg.Send(); - } - - currentOwnerId = newOwnerId; - OnOwnershipChanged(newOwnerId); - } - - protected virtual OwnershipResponse OnOwnershipRequested(string requesterId) - { - return OwnershipResponse.Rejected; - } - - protected virtual void OnOwnershipChanged(string newOwnerId) - { - // Override to handle ownership changes - } - - protected virtual void WriteState(ModNetworkMessage message) { } - protected virtual void ReadState(ModNetworkMessage message) { } - protected virtual void ReadCustomData(ModNetworkMessage message) { } - - protected virtual void Dispose(bool disposing) - { - if (disposedValue) - return; - - if (disposing) - { - ModNetworkManager.Unsubscribe(networkId); - pendingRequests.Clear(); - - if (stateRequestTimer != null) - { - stateRequestTimer.Dispose(); - stateRequestTimer = null; - } - } - - disposedValue = true; - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs deleted file mode 100644 index 4941aaa..0000000 --- a/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs +++ /dev/null @@ -1,63 +0,0 @@ -using ABI_RC.Systems.ModNetwork; -using UnityEngine; - -namespace NAK.LuaNetVars; - - -// Test implementation -public class TestSyncedBehaviour : MNSyncedBehaviour -{ - private readonly System.Random random = new(); - private int testValue; - private int incrementValue; - - public TestSyncedBehaviour(string networkId) : base(networkId, autoAcceptTransfers: true) - { - Debug.Log($"[TestSyncedBehaviour] Initialized. NetworkId: {networkId}"); - } - - public void SendTestMessage() - { - if (!HasOwnership) return; - - SendNetworkedData(msg => { - testValue = random.Next(1000); - incrementValue++; - msg.Write(testValue); - msg.Write(incrementValue); - }); - } - - protected override void WriteState(ModNetworkMessage message) - { - message.Write(testValue); - message.Write(incrementValue); - } - - protected override void ReadState(ModNetworkMessage message) - { - message.Read(out testValue); - message.Read(out incrementValue); - Debug.Log($"[TestSyncedBehaviour] State synchronized. TestValue: {testValue}, IncrementValue: {incrementValue}"); - } - - protected override void ReadCustomData(ModNetworkMessage message) - { - message.Read(out int receivedValue); - message.Read(out int receivedIncrement); - testValue = receivedValue; - incrementValue = receivedIncrement; - Debug.Log($"[TestSyncedBehaviour] Received custom data: TestValue: {testValue}, IncrementValue: {incrementValue}"); - } - - protected override void OnOwnershipChanged(string newOwnerId) - { - Debug.Log($"[TestSyncedBehaviour] Ownership changed to: {newOwnerId}"); - } - - protected override OwnershipResponse OnOwnershipRequested(string requesterId) - { - Debug.Log($"[TestSyncedBehaviour] Ownership requested by: {requesterId}"); - return OwnershipResponse.Accepted; - } -} \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs b/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs deleted file mode 100644 index 76d6281..0000000 --- a/.Experimental/LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs +++ /dev/null @@ -1,42 +0,0 @@ -using ABI_RC.Core.Savior; -using UnityEngine; - -namespace NAK.LuaNetVars; - -public class TestSyncedObject : MonoBehaviour -{ - private const string TEST_NETWORK_ID = "test.synced.object.1"; - private TestSyncedBehaviour syncBehaviour; - private float messageTimer = 0f; - private const float MESSAGE_INTERVAL = 2f; - - private void Start() - { - syncBehaviour = new TestSyncedBehaviour(TEST_NETWORK_ID); - Debug.Log($"TestSyncedObject started. Local Player ID: {MetaPort.Instance.ownerId}"); - } - - private void Update() - { - // Request ownership on Space key - if (Input.GetKeyDown(KeyCode.Space)) - { - Debug.Log("Requesting ownership..."); - syncBehaviour.RequestOwnership((success) => - { - Debug.Log($"Ownership request {(success ? "accepted" : "rejected")}"); - }); - } - - // If we have ownership, send custom data periodically - if (syncBehaviour.HasOwnership) - { - messageTimer += Time.deltaTime; - if (messageTimer >= MESSAGE_INTERVAL) - { - messageTimer = 0f; - syncBehaviour.SendTestMessage(); - } - } - } -} \ No newline at end of file From 0f6006db832d7877ef8dd3ada24b1f4664c53d89 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:14:17 -0500 Subject: [PATCH 154/188] [CVRLuaToolsExtension] kinda fix --- .../Extensions/CVRLuaClientBehaviourExtensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs index 8c07542..5dc7b86 100644 --- a/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs +++ b/.Experimental/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs @@ -1,6 +1,7 @@ using ABI.CCK.Components; using ABI.Scripting.CVRSTL.Client; using System.Diagnostics; +using ABI_RC.Scripting.Persistence; using MTJobSystem; using UnityEngine; @@ -108,8 +109,11 @@ public static class CVRLuaClientBehaviourExtensions behaviour._startupMessageQueue.Clear(); // will be repopulated behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n"); + // remove the script from the persistence manager, as the storage needs to be reinitialized + PersistenceManager.HandleUnsubscribe(behaviour.Storage, behaviour.script, behaviour.Context.ParentContent.ContentType, behaviour.Context.AssetID); + behaviour.script = null; - behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform, PersistentDataPath); + behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform); behaviour.InitTimerIfNeeded(); // only null if crashed prior behaviour.script.AttachDebugger(behaviour.timer); // reattach the debugger From 3521453010d663b473ebaa420a51dd1209964510 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:14:35 -0500 Subject: [PATCH 155/188] [ASTExtension] Fix no supported parameter found spam --- ASTExtension/Main.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ASTExtension/Main.cs b/ASTExtension/Main.cs index f7b7115..461dc16 100644 --- a/ASTExtension/Main.cs +++ b/ASTExtension/Main.cs @@ -121,7 +121,8 @@ public class ASTExtensionMod : MelonMod private void OnLocalAvatarLoad() { - if (!FindSupportedParameter(out string parameterName)) + _currentAvatarSupported = FindSupportedParameter(out string parameterName); + if (!_currentAvatarSupported) return; if (!AttemptCalibrateParameter(parameterName, out float minHeight, out float maxHeight, out float modifier)) From da6520beb0ce858788fe0ef03211fc9b8f74e1fc Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:14:49 -0500 Subject: [PATCH 156/188] [ConfigureCalibrationPose] initial builds --- .../ConfigureCalibrationPose.csproj | 6 + ConfigureCalibrationPose/Main.cs | 543 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 ++ ConfigureCalibrationPose/README.md | 14 + ConfigureCalibrationPose/format.json | 23 + 5 files changed, 618 insertions(+) create mode 100644 ConfigureCalibrationPose/ConfigureCalibrationPose.csproj create mode 100644 ConfigureCalibrationPose/Main.cs create mode 100644 ConfigureCalibrationPose/Properties/AssemblyInfo.cs create mode 100644 ConfigureCalibrationPose/README.md create mode 100644 ConfigureCalibrationPose/format.json diff --git a/ConfigureCalibrationPose/ConfigureCalibrationPose.csproj b/ConfigureCalibrationPose/ConfigureCalibrationPose.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/ConfigureCalibrationPose/ConfigureCalibrationPose.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/ConfigureCalibrationPose/Main.cs b/ConfigureCalibrationPose/Main.cs new file mode 100644 index 0000000..179f7e4 --- /dev/null +++ b/ConfigureCalibrationPose/Main.cs @@ -0,0 +1,543 @@ +using System.Reflection; +using ABI_RC.Core.Player; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using ABI_RC.Systems.InputManagement; +using ABI_RC.Systems.Movement; +using HarmonyLib; +using MelonLoader; +using RootMotion.FinalIK; +using UnityEngine; + +namespace NAK.ConfigureCalibrationPose; + +public class ConfigureCalibrationPoseMod : MelonMod +{ + #region Enums + + private enum CalibrationPose + { + TPose, + APose, + IKPose, + BikePose, + RacushSit, + CCKSitting, + CCKCrouch, + CCKProne, + } + + #endregion Enums + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(ConfigureCalibrationPose)); + + private static readonly MelonPreferences_Entry EntryCalibrationPose = + Category.CreateEntry("calibration_pose", CalibrationPose.APose, display_name: "Calibration Pose", + description: "What pose to use for FBT calibration."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + #region BodySystem Patches + + HarmonyInstance.Patch( + typeof(BodySystem).GetMethod(nameof(BodySystem.MuscleUpdate), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(ConfigureCalibrationPoseMod).GetMethod(nameof(OnPreBodySystemMuscleUpdate), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion BodySystem Patches + } + + #endregion Melon Events + + #region Harmony Patches + + private static bool OnPreBodySystemMuscleUpdate(ref float[] muscles) + { + PlayerSetup playerSetup = PlayerSetup.Instance; + IKSystem ikSystem = IKSystem.Instance; + ref HumanPose humanPose = ref ikSystem._humanPose; + + if (BodySystem.isCalibrating) + { + switch (EntryCalibrationPose.Value) + { + default: + case CalibrationPose.TPose: + for (int i = 0; i < MusclePoses.TPoseMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.TPoseMuscles[i], ref muscles); + break; + case CalibrationPose.APose: + for (int i = 0; i < MusclePoses.APoseMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.APoseMuscles[i], ref muscles); + break; + case CalibrationPose.IKPose: + for (int i = 0; i < MusclePoses.IKPoseMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.IKPoseMuscles[i], ref muscles); + break; + case CalibrationPose.BikePose: + for (int i = 0; i < MusclePoses.TPoseMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, 0f, ref muscles); + break; + case CalibrationPose.CCKSitting: + for (int i = 0; i < CCKSittingMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKSittingMuscles[i], ref muscles); + break; + case CalibrationPose.CCKCrouch: + for (int i = 0; i < CCKCrouchMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKCrouchMuscles[i], ref muscles); + break; + case CalibrationPose.CCKProne: + for (int i = 0; i < CCKProneMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKProneMuscles[i], ref muscles); + break; + case CalibrationPose.RacushSit: + for (int i = 0; i < RacushSitMuscles.Length; i++) + ikSystem.ApplyMuscleValue((MuscleIndex) i, RacushSitMuscles[i], ref muscles); + break; + } + + humanPose.bodyPosition = Vector3.up; + humanPose.bodyRotation = Quaternion.identity; + } + else if (BodySystem.isCalibratedAsFullBody && BodySystem.TrackingPositionWeight > 0f) + { + BetterBetterCharacterController characterController = playerSetup.CharacterController; + + bool isRunning = characterController.IsMoving(); + bool isGrounded = characterController.IsGrounded(); + bool isFlying = characterController.IsFlying(); + bool isSwimming = characterController.IsSwimming(); + + if ((BodySystem.PlayRunningAnimationInFullBody + && (isRunning || !isGrounded && !isFlying && !isSwimming))) + { + ikSystem.applyOriginalHipPosition = true; + ikSystem.applyOriginalHipRotation = true; + + IKSolverVR solver = IKSystem.vrik.solver; + BodySystem.SetPelvisWeight(solver.spine, 0f); + BodySystem.SetLegWeight(solver.leftLeg, 0f); + BodySystem.SetLegWeight(solver.rightLeg, 0f); + } + else + { + ikSystem.applyOriginalHipPosition = true; + ikSystem.applyOriginalHipRotation = false; + humanPose.bodyRotation = Quaternion.identity; + } + } + + return false; // dont run original + } + + #endregion Harmony Patches + + #region Custom Pose Arrays + + private static readonly float[] CCKSittingMuscles = + [ + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -0.8000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -0.8000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -1.0000f, + 0.0000f, + -0.3000f, + 0.3000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -1.0000f, + 0.0000f, + -0.3000f, + 0.3000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f + ]; + + private static readonly float[] CCKCrouchMuscles = + [ + -1.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.5000f, + 0.0000f, + 0.0000f, + 0.5000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -0.6279f, + 0.0000f, + 0.0000f, + -0.8095f, + 0.0000f, + -1.0091f, + 0.0000f, + 0.0000f, + -0.4126f, + 0.0013f, + -0.0860f, + -0.9331f, + -0.0869f, + -1.3586f, + 0.1791f, + 0.0000f, + 0.0000f, + 0.0000f, + -0.1998f, + -0.2300f, + 0.1189f, + 0.3479f, + 0.1364f, + -0.3737f, + 0.0069f, + 0.0000f, + 0.0000f, + -0.1994f, + -0.2301f, + 0.0267f, + 0.7532f, + 0.1922f, + 0.0009f, + -0.0005f, + -1.4747f, + -0.0443f, + -0.3347f, + -0.3062f, + -0.7596f, + -1.2067f, + -0.7329f, + -0.7329f, + -0.5984f, + -2.7162f, + -0.7439f, + -0.7439f, + -0.5812f, + 1.8528f, + -0.7520f, + -0.7520f, + -0.7242f, + 0.5912f, + -0.7632f, + -0.7632f, + -1.4747f, + -0.0443f, + -0.3347f, + -0.3062f, + -0.7596f, + -1.2067f, + -0.7329f, + -0.7329f, + -0.5984f, + -2.7162f, + -0.7439f, + -0.7439f, + -0.5812f, + 1.8528f, + -0.7520f, + 0.8104f, + -0.7242f, + 0.5912f, + -0.7632f, + 0.8105f + ]; + + private static readonly float[] CCKProneMuscles = + [ + 0.6604f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.7083f, + 0.0000f, + 0.0000f, + 0.7083f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.2444f, + -0.0554f, + -0.8192f, + 0.9301f, + 0.5034f, + 1.0274f, + -0.1198f, + 0.5849f, + 0.2360f, + -0.0837f, + -1.1803f, + 0.9676f, + 0.7390f, + 0.9944f, + -0.1717f, + 0.5849f, + 0.0000f, + 0.0000f, + 0.2823f, + -0.6297f, + 0.3200f, + -0.3376f, + 0.0714f, + 0.9260f, + -1.5768f, + 0.0000f, + 0.0000f, + 0.1561f, + -0.6712f, + 0.2997f, + -0.3392f, + 0.0247f, + 0.7672f, + -1.5269f, + -1.1422f, + 0.0392f, + 0.6457f, + 0.0000f, + 0.6185f, + -0.5393f, + 0.8104f, + 0.8104f, + 0.6223f, + -0.8225f, + 0.8104f, + 0.8104f, + 0.6218f, + -0.3961f, + 0.8104f, + 0.8104f, + 0.6160f, + -0.3721f, + 0.8105f, + 0.8105f, + -1.1422f, + 0.0392f, + 0.6457f, + 0.0000f, + 0.6185f, + -0.5393f, + 0.8104f, + 0.8104f, + 0.6223f, + -0.8226f, + 0.8104f, + 0.8104f, + 0.6218f, + -0.3961f, + 0.8104f, + 0.8104f, + 0.6160f, + -0.3721f, + 0.8105f, + 0.8105f + ]; + + private static readonly float[] RacushSitMuscles = + [ + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + -0.7500f, + -0.0002f, + 0.1599f, + -0.1500f, + 0.1000f, + 0.1300f, + -0.0001f, + 0.0000f, + -0.7500f, + -0.0002f, + 0.1599f, + -0.1500f, + 0.1000f, + 0.1300f, + -0.0001f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.3927f, + 0.3114f, + 0.0805f, + 0.9650f, + -0.0536f, + 0.0024f, + 0.0005f, + 0.0000f, + 0.0000f, + 0.3928f, + 0.3114f, + 0.0805f, + 0.9650f, + -0.0536f, + 0.0024f, + 0.0005f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f, + 0.0000f + ]; + + #endregion Custom Pose Arrays +} \ No newline at end of file diff --git a/ConfigureCalibrationPose/Properties/AssemblyInfo.cs b/ConfigureCalibrationPose/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1fdc09d --- /dev/null +++ b/ConfigureCalibrationPose/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.ConfigureCalibrationPose.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.ConfigureCalibrationPose))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.ConfigureCalibrationPose))] + +[assembly: MelonInfo( + typeof(NAK.ConfigureCalibrationPose.ConfigureCalibrationPoseMod), + nameof(NAK.ConfigureCalibrationPose), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ConfigureCalibrationPose" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.ConfigureCalibrationPose.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/ConfigureCalibrationPose/README.md b/ConfigureCalibrationPose/README.md new file mode 100644 index 0000000..f17ba48 --- /dev/null +++ b/ConfigureCalibrationPose/README.md @@ -0,0 +1,14 @@ +# ConfigureCalibrationPose + +Select FBT calibration pose. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/ConfigureCalibrationPose/format.json b/ConfigureCalibrationPose/format.json new file mode 100644 index 0000000..6324d2b --- /dev/null +++ b/ConfigureCalibrationPose/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "ConfigureCalibrationPose", + "modversion": "1.0.0", + "gameversion": "2025r179", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO", + "searchtags": [ + "prop", + "spawn", + "friend", + "load" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ConfigureCalibrationPose.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ConfigureCalibrationPose/", + "changelog": "- Initial Release", + "embedcolor": "#00FFFF" +} \ No newline at end of file From abd7b9d67424348ac2b015077cc06324f9902196 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 25 Apr 2025 02:15:00 +0000 Subject: [PATCH 157/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3217d1..39be991 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ |------|-------------|----------| | [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll) | | [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | +| [ConfigureCalibrationPose](ConfigureCalibrationPose/README.md) | Select FBT calibration pose. | No Download | | [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | | [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll) | -| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | No Download | +| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/FuckToes.dll) | | [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | | [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropLoadingHexagon.dll) | From bdf6b0e51a8a9e73082610c0190b9042cc9f634f Mon Sep 17 00:00:00 2001 From: SketchFoxsky <109103755+SketchFoxsky@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:29:32 -0400 Subject: [PATCH 158/188] updated seated calibration --- ConfigureCalibrationPose/Main.cs | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ConfigureCalibrationPose/Main.cs b/ConfigureCalibrationPose/Main.cs index 179f7e4..df677fd 100644 --- a/ConfigureCalibrationPose/Main.cs +++ b/ConfigureCalibrationPose/Main.cs @@ -101,10 +101,10 @@ public class ConfigureCalibrationPoseMod : MelonMod break; case CalibrationPose.RacushSit: for (int i = 0; i < RacushSitMuscles.Length; i++) - ikSystem.ApplyMuscleValue((MuscleIndex) i, RacushSitMuscles[i], ref muscles); + ikSystem.ApplyMuscleValue((MuscleIndex)i, RacushSitMuscles[i], ref muscles); break; } - + humanPose.bodyPosition = Vector3.up; humanPose.bodyRotation = Quaternion.identity; } @@ -439,8 +439,8 @@ public class ConfigureCalibrationPoseMod : MelonMod 0.8105f, 0.8105f ]; - - private static readonly float[] RacushSitMuscles = + + public static readonly float[] RacushSitMuscles = [ 0.0000f, 0.0000f, @@ -463,40 +463,40 @@ public class ConfigureCalibrationPoseMod : MelonMod 0.0000f, 0.0000f, 0.0000f, - -0.7500f, - -0.0002f, - 0.1599f, - -0.1500f, + -0.7600f, 0.1000f, + 0.0600f, + -0.1800f, + -0.0991f, 0.1300f, - -0.0001f, + 0.0001f, 0.0000f, - -0.7500f, - -0.0002f, - 0.1599f, - -0.1500f, + -0.7600f, 0.1000f, + 0.0600f, + -0.1800f, + -0.0991f, 0.1300f, - -0.0001f, + 0.0001f, 0.0000f, 0.0000f, 0.0000f, 0.3927f, - 0.3114f, - 0.0805f, + 0.3115f, + 0.0931f, 0.9650f, - -0.0536f, - 0.0024f, - 0.0005f, + -0.0662f, + 0.0026f, + 0.0006f, 0.0000f, 0.0000f, - 0.3928f, - 0.3114f, - 0.0805f, + 0.3927f, + 0.3115f, + 0.0931f, 0.9650f, - -0.0536f, - 0.0024f, - 0.0005f, + -0.0662f, + 0.0026f, + 0.0006f, 0.0000f, 0.0000f, 0.0000f, From d54ec06190e0f235fc0ee16770387e90dab3f52a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:33:43 -0500 Subject: [PATCH 159/188] [ShareBubbles] no send shit if no people --- NAK_CVR_Mods.sln | 24 +++++++++++++++++++ .../ShareBubbles/API/ShareApiHelper.cs | 1 + .../Networking/ModNetwork.Helpers.cs | 5 +++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index eb7c1a3..1541fc7 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -60,6 +60,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoubleTapJumpToExitSeat", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckToes", "FuckToes\FuckToes.csproj", "{751E4140-2F4D-4550-A4A9-65ABA9F7893A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", ".Experimental\LuaNetworkVariables\LuaNetworkVariables.csproj", "{6E7857D9-07AC-419F-B111-0DB0348D1C92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchySquishy", ".Blackbox\TouchySquishy\TouchySquishy.csproj", "{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", ".Experimental\CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{3D221A25-007F-4764-98CD-CEEF2EB92165}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigureCalibrationPose", "ConfigureCalibrationPose\ConfigureCalibrationPose.csproj", "{31667A36-D069-4708-9DCA-E3446009941B}" +EndProject EndProject EndProject EndProject @@ -307,6 +315,22 @@ Global {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Debug|Any CPU.Build.0 = Debug|Any CPU {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.ActiveCfg = Release|Any CPU {751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.Build.0 = Release|Any CPU + {6E7857D9-07AC-419F-B111-0DB0348D1C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E7857D9-07AC-419F-B111-0DB0348D1C92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E7857D9-07AC-419F-B111-0DB0348D1C92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E7857D9-07AC-419F-B111-0DB0348D1C92}.Release|Any CPU.Build.0 = Release|Any CPU + {FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Release|Any CPU.Build.0 = Release|Any CPU + {3D221A25-007F-4764-98CD-CEEF2EB92165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D221A25-007F-4764-98CD-CEEF2EB92165}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D221A25-007F-4764-98CD-CEEF2EB92165}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D221A25-007F-4764-98CD-CEEF2EB92165}.Release|Any CPU.Build.0 = Release|Any CPU + {31667A36-D069-4708-9DCA-E3446009941B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31667A36-D069-4708-9DCA-E3446009941B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31667A36-D069-4708-9DCA-E3446009941B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31667A36-D069-4708-9DCA-E3446009941B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs b/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs index 3200deb..c338930 100644 --- a/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs +++ b/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http; using System.Text; using System.Web; using ABI_RC.Core.Networking; diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs index 3ee6e81..e65a178 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Helpers.cs @@ -1,4 +1,5 @@ using ABI_RC.Core.Networking; +using ABI_RC.Core.Player; using DarkRift; using UnityEngine; @@ -9,7 +10,9 @@ public static partial class ModNetwork #region Private Methods private static bool CanSendModNetworkMessage() - => _isSubscribedToModNetwork && IsConnectedToGameNetwork(); + => _isSubscribedToModNetwork + && IsConnectedToGameNetwork() + && CVRPlayerManager.Instance.NetworkPlayers.Count > 0; // No need to send if there are no players private static bool IsConnectedToGameNetwork() { From 0a01534aa4d5226e0f079e02cf59b4c3fb05e07b Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 8 May 2025 17:15:52 -0500 Subject: [PATCH 160/188] [LuaNetworkVariables] rename classes --- .Experimental/LuaNetworkVariables/Main.cs | 4 +- .../LuaNetworkVariables/NetLuaModule.cs | 16 ++-- .../NetworkVariables/LuaEventContext.cs | 2 +- .../NetworkVariables/LuaEventTracker.cs | 2 +- .../LuaNetVarController.Base.cs | 8 +- .../LuaNetVarController.Networking.cs | 16 ++-- .../LuaNetVarController.Registration.cs | 20 ++--- .../LuaNetVarController.Serialization.cs | 6 +- .../LuaNetVarController.Utility.cs | 6 +- .Experimental/LuaNetworkVariables/Patches.cs | 4 +- .../Properties/AssemblyInfo.cs | 14 ++-- .Experimental/LuaNetworkVariables/README.md | 75 ++++++++++++++++++- 12 files changed, 122 insertions(+), 51 deletions(-) diff --git a/.Experimental/LuaNetworkVariables/Main.cs b/.Experimental/LuaNetworkVariables/Main.cs index 2a8b2fb..ce59fa0 100644 --- a/.Experimental/LuaNetworkVariables/Main.cs +++ b/.Experimental/LuaNetworkVariables/Main.cs @@ -2,9 +2,9 @@ using MelonLoader; using UnityEngine; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; -public class LuaNetVarsMod : MelonMod +public class LuaNetworkVariablesMod : MelonMod { internal static MelonLogger.Instance Logger; diff --git a/.Experimental/LuaNetworkVariables/NetLuaModule.cs b/.Experimental/LuaNetworkVariables/NetLuaModule.cs index b13bc9b..23855ed 100644 --- a/.Experimental/LuaNetworkVariables/NetLuaModule.cs +++ b/.Experimental/LuaNetworkVariables/NetLuaModule.cs @@ -3,7 +3,7 @@ using ABI.Scripting.CVRSTL.Common; using JetBrains.Annotations; using MoonSharp.Interpreter; -namespace NAK.LuaNetVars.Modules; +namespace NAK.LuaNetworkVariables.Modules; [PublicAPI] public class LuaNetModule : BaseScriptedStaticWrapper @@ -37,7 +37,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return; } @@ -56,7 +56,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return; } @@ -75,7 +75,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return; } @@ -94,7 +94,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return; } @@ -113,7 +113,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return; } @@ -130,7 +130,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return false; } @@ -144,7 +144,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper if (_controller == null) { - LuaNetVarsMod.Logger.Error("LuaNetVarController is null."); + LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null."); return string.Empty; } diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs index 6a3fd57..59b385e 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventContext.cs @@ -2,7 +2,7 @@ using MoonSharp.Interpreter; using ABI_RC.Core.Player; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; public struct LuaEventContext { diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs index ed60cee..98f6322 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs @@ -1,4 +1,4 @@ -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; internal class LuaEventTracker { diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs index 811fa9b..51e60e8 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs @@ -7,7 +7,7 @@ using MoonSharp.Interpreter; using UnityEngine; using Coroutine = UnityEngine.Coroutine; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; public partial class LuaNetVarController : MonoBehaviour { @@ -58,13 +58,13 @@ public partial class LuaNetVarController : MonoBehaviour if (ModNetworkID.Length > ModNetworkManager.MaxMessageIdLength) { - LuaNetVarsMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!"); + LuaNetworkVariablesMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!"); return false; } _hashes.Add(_uniquePathHash); ModNetworkManager.Subscribe(ModNetworkID, OnMessageReceived); - LuaNetVarsMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}"); + LuaNetworkVariablesMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}"); switch (_luaClientBehaviour.Context.objContext) { @@ -101,7 +101,7 @@ public partial class LuaNetVarController : MonoBehaviour return; ModNetworkManager.Unsubscribe(ModNetworkID); - LuaNetVarsMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}"); + LuaNetworkVariablesMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}"); _hashes.Remove(_uniquePathHash); } diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs index bed3bb8..a674987 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Networking.cs @@ -2,7 +2,7 @@ using ABI_RC.Systems.ModNetwork; using MoonSharp.Interpreter; -namespace NAK.LuaNetVars +namespace NAK.LuaNetworkVariables { public partial class LuaNetVarController { @@ -46,7 +46,7 @@ namespace NAK.LuaNetVars msg.Read(out string varName); DynValue newValue = DeserializeDynValue(msg); - LuaNetVarsMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}"); + LuaNetworkVariablesMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}"); if (_registeredNetworkVars.TryGetValue(varName, out DynValue var)) { @@ -54,7 +54,7 @@ namespace NAK.LuaNetVars } else { - LuaNetVarsMod.Logger.Warning($"Received update for unregistered variable {varName}"); + LuaNetworkVariablesMod.Logger.Warning($"Received update for unregistered variable {varName}"); } } @@ -79,7 +79,7 @@ namespace NAK.LuaNetVars args[i + 1] = DeserializeDynValue(msg); } - LuaNetVarsMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args"); + LuaNetworkVariablesMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args"); InvokeLuaEvent(eventName, args); } @@ -98,7 +98,7 @@ namespace NAK.LuaNetVars } else { - LuaNetVarsMod.Logger.Warning($"Received sync for unregistered variable {varName}"); + LuaNetworkVariablesMod.Logger.Warning($"Received sync for unregistered variable {varName}"); } } } @@ -121,7 +121,7 @@ namespace NAK.LuaNetVars } else { - LuaNetVarsMod.Logger.Warning($"No registered callback for event {eventName}"); + LuaNetworkVariablesMod.Logger.Warning($"No registered callback for event {eventName}"); } } @@ -159,7 +159,7 @@ namespace NAK.LuaNetVars } modMsg.Send(); - LuaNetVarsMod.Logger.Msg($"Sent variable sync to {userId}"); + LuaNetworkVariablesMod.Logger.Msg($"Sent variable sync to {userId}"); } private void RequestVariableSync() @@ -167,7 +167,7 @@ namespace NAK.LuaNetVars using ModNetworkMessage modMsg = new(ModNetworkID); modMsg.Write((byte)MessageType.RequestSync); modMsg.Send(); - LuaNetVarsMod.Logger.Msg("Requested variable sync"); + LuaNetworkVariablesMod.Logger.Msg("Requested variable sync"); } // private DynValue SendLuaEventCallback(ScriptExecutionContext context, CallbackArguments args) diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs index e68170f..fde4fdf 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Registration.cs @@ -1,6 +1,6 @@ using MoonSharp.Interpreter; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; public partial class LuaNetVarController { @@ -8,7 +8,7 @@ public partial class LuaNetVarController { if (_registeredNetworkVars.ContainsKey(varName)) { - LuaNetVarsMod.Logger.Warning($"Network variable {varName} already registered!"); + LuaNetworkVariablesMod.Logger.Warning($"Network variable {varName} already registered!"); return; } @@ -18,7 +18,7 @@ public partial class LuaNetVarController RegisterGetterFunction(varName); RegisterSetterFunction(varName); - LuaNetVarsMod.Logger.Msg($"Registered network variable {varName}"); + LuaNetworkVariablesMod.Logger.Msg($"Registered network variable {varName}"); } private void RegisterGetterFunction(string varName) @@ -38,7 +38,7 @@ public partial class LuaNetVarController var newValue = args[0]; if (!IsSupportedDynValue(newValue)) { - LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}"); + LuaNetworkVariablesMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}"); return DynValue.Nil; } @@ -68,10 +68,10 @@ public partial class LuaNetVarController if (!ValidateCallback(callback) || !ValidateNetworkVar(varName)) return; if (_registeredNotifyCallbacks.ContainsKey(varName)) - LuaNetVarsMod.Logger.Warning($"Overwriting notify callback for {varName}"); + LuaNetworkVariablesMod.Logger.Warning($"Overwriting notify callback for {varName}"); _registeredNotifyCallbacks[varName] = callback; - LuaNetVarsMod.Logger.Msg($"Registered notify callback for {varName}"); + LuaNetworkVariablesMod.Logger.Msg($"Registered notify callback for {varName}"); } internal void RegisterEventCallback(string eventName, DynValue callback) @@ -79,23 +79,23 @@ public partial class LuaNetVarController if (!ValidateCallback(callback)) return; if (_registeredEventCallbacks.ContainsKey(eventName)) - LuaNetVarsMod.Logger.Warning($"Overwriting event callback for {eventName}"); + LuaNetworkVariablesMod.Logger.Warning($"Overwriting event callback for {eventName}"); _registeredEventCallbacks[eventName] = callback; - LuaNetVarsMod.Logger.Msg($"Registered event callback for {eventName}"); + LuaNetworkVariablesMod.Logger.Msg($"Registered event callback for {eventName}"); } private bool ValidateCallback(DynValue callback) { if (callback?.Function != null) return true; - LuaNetVarsMod.Logger.Error("Passed DynValue must be a function"); + LuaNetworkVariablesMod.Logger.Error("Passed DynValue must be a function"); return false; } private bool ValidateNetworkVar(string varName) { if (_registeredNetworkVars.ContainsKey(varName)) return true; - LuaNetVarsMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}."); + LuaNetworkVariablesMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}."); return false; } } \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs index 57221a7..ba20565 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Serialization.cs @@ -1,7 +1,7 @@ using ABI_RC.Systems.ModNetwork; using MoonSharp.Interpreter; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; public partial class LuaNetVarController { @@ -24,7 +24,7 @@ public partial class LuaNetVarController case DataType.Nil: return DynValue.Nil; default: - LuaNetVarsMod.Logger.Error($"Unsupported data type received: {dataType}"); + LuaNetworkVariablesMod.Logger.Error($"Unsupported data type received: {dataType}"); return DynValue.Nil; } } @@ -49,7 +49,7 @@ public partial class LuaNetVarController msg.Write((byte)DataType.Nil); break; default: - LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {value.Type}"); + LuaNetworkVariablesMod.Logger.Error($"Unsupported DynValue type: {value.Type}"); msg.Write((byte)DataType.Nil); break; } diff --git a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs index 7712781..d52d504 100644 --- a/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs +++ b/.Experimental/LuaNetworkVariables/NetworkVariables/LuaNetVarController.Utility.cs @@ -1,7 +1,7 @@ using ABI_RC.Core.Savior; using UnityEngine; -namespace NAK.LuaNetVars; +namespace NAK.LuaNetworkVariables; public partial class LuaNetVarController { @@ -13,10 +13,10 @@ public partial class LuaNetVarController // Check if it already exists (this **should** only matter in worlds) if (_hashes.Contains(hash)) { - LuaNetVarsMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}"); + LuaNetworkVariablesMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}"); if (!FindAvailableHash(ref hash)) // Super lazy fix idfc { - LuaNetVarsMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}"); + LuaNetworkVariablesMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}"); return false; } } diff --git a/.Experimental/LuaNetworkVariables/Patches.cs b/.Experimental/LuaNetworkVariables/Patches.cs index 672e1df..a442013 100644 --- a/.Experimental/LuaNetworkVariables/Patches.cs +++ b/.Experimental/LuaNetworkVariables/Patches.cs @@ -2,9 +2,9 @@ using ABI.Scripting.CVRSTL.Common; using HarmonyLib; using MoonSharp.Interpreter; -using NAK.LuaNetVars.Modules; +using NAK.LuaNetworkVariables.Modules; -namespace NAK.LuaNetVars.Patches; +namespace NAK.LuaNetworkVariables.Patches; internal static class LuaScriptFactory_Patches { diff --git a/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs index 84217ef..66b2858 100644 --- a/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs +++ b/.Experimental/LuaNetworkVariables/Properties/AssemblyInfo.cs @@ -1,17 +1,17 @@ -using NAK.LuaNetVars.Properties; +using NAK.LuaNetworkVariables.Properties; using MelonLoader; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.LuaNetVars))] +[assembly: AssemblyTitle(nameof(NAK.LuaNetworkVariables))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.LuaNetVars))] +[assembly: AssemblyProduct(nameof(NAK.LuaNetworkVariables))] [assembly: MelonInfo( - typeof(NAK.LuaNetVars.LuaNetVarsMod), - nameof(NAK.LuaNetVars), + typeof(NAK.LuaNetworkVariables.LuaNetworkVariablesMod), + nameof(NAK.LuaNetworkVariables), AssemblyInfoParams.Version, AssemblyInfoParams.Author, downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LuaNetworkVariables" @@ -24,9 +24,9 @@ using System.Reflection; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.LuaNetVars.Properties; +namespace NAK.LuaNetworkVariables.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.2"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/.Experimental/LuaNetworkVariables/README.md b/.Experimental/LuaNetworkVariables/README.md index c78a56a..c6ee80c 100644 --- a/.Experimental/LuaNetworkVariables/README.md +++ b/.Experimental/LuaNetworkVariables/README.md @@ -1,6 +1,77 @@ -# WhereAmIPointing +# LuaNetworkVariables -Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. +Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. + +Example Usage: +```lua +-- Requires UnityEngine and NetworkModule +UnityEngine = require("UnityEngine") +NetworkModule = require("NetworkModule") + +-- Unity Events -- + +function Start() + + if NetworkModule == nil then + print("NetworkModule did not load.") + return + end + + -- Registers "AvatarHeight" as a network variable + -- This creates Get and Set functions (GetAvatarHeight() and SetAvatarHeight()) + NetworkModule:RegisterNetworkVar("AvatarHeight") + + -- Registers a callback for when "AvatarHeight" is changed. + NetworkModule:RegisterNotifyCallback("AvatarHeight", function(varName, oldValue, newValue) + print(varName .. " changed from " .. tostring(oldValue) .. " to " .. tostring(newValue)) + end) + + -- Registers "ButtonClickedEvent" as a networked event. This provides context alongside the arguments passed. + NetworkModule:RegisterEventCallback("ButtonClickedEvent", function(context, message) + print("ButtonClickedEvent triggered by " .. tostring(context.senderName) .. " with message: " .. tostring(message)) + print("Context details:") + print(" senderId: " .. tostring(context.senderId)) + print(" senderName: " .. tostring(context.senderName)) + print(" lastInvokeTime: " .. tostring(context.lastInvokeTime)) + print(" timeSinceLastInvoke: " .. tostring(context.timeSinceLastInvoke)) + print(" isLocal: " .. tostring(context.isLocal)) + end) + + -- Secondry example + NetworkModule:RegisterEventCallback("CoolEvent", OnCoolEventOccured) +end + +function Update() + if not NetworkModule:IsSyncOwner() then + return + end + + SetAvatarHeight(PlayerAPI.LocalPlayer:GetViewPointPosition().y) +end + +-- Global Functions -- + +function SendClickEvent() + NetworkModule:SendLuaEvent("ButtonClickedEvent", "The button was clicked!") + print("Sent ButtonClickedEvent") +end + +function SendCoolEvent() + NetworkModule:SendLuaEvent("CoolEvent", 1, 2) +end + +-- Listener Functions -- + +function OnCoolEventOccured(context, value, value2) + print("CoolEvent triggered by " .. tostring(context.senderName)) + print("Received values: " .. tostring(value) .. ", " .. tostring(value2)) + print("Context details:") + print(" SenderId: " .. tostring(context.SenderId)) + print(" LastInvokeTime: " .. tostring(context.LastInvokeTime)) + print(" TimeSinceLastInvoke: " .. tostring(context.TimeSinceLastInvoke)) + print(" IsLocal: " .. tostring(context.IsLocal)) +end +``` --- From b75dce1d0222a990af4de65e0f67a5e438c495d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 8 May 2025 22:16:07 +0000 Subject: [PATCH 161/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39be991..93d8d1e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ |------|-------------|----------| | [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | No Download | | [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | No Download | -| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction. | No Download | +| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LuaNetworkVariables.dll) | | [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | No Download | | [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | No Download | | [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | No Download | From cc7762293dc43831518a1645b07330090fb44c9d Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 8 May 2025 17:36:28 -0500 Subject: [PATCH 162/188] [LuaNetworkVariables] fixed readmoe --- .Experimental/LuaNetworkVariables/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.Experimental/LuaNetworkVariables/README.md b/.Experimental/LuaNetworkVariables/README.md index c6ee80c..5ad6ee2 100644 --- a/.Experimental/LuaNetworkVariables/README.md +++ b/.Experimental/LuaNetworkVariables/README.md @@ -28,13 +28,13 @@ function Start() -- Registers "ButtonClickedEvent" as a networked event. This provides context alongside the arguments passed. NetworkModule:RegisterEventCallback("ButtonClickedEvent", function(context, message) - print("ButtonClickedEvent triggered by " .. tostring(context.senderName) .. " with message: " .. tostring(message)) + print("ButtonClickedEvent triggered by " .. tostring(context.SenderName) .. " with message: " .. tostring(message)) print("Context details:") - print(" senderId: " .. tostring(context.senderId)) - print(" senderName: " .. tostring(context.senderName)) - print(" lastInvokeTime: " .. tostring(context.lastInvokeTime)) - print(" timeSinceLastInvoke: " .. tostring(context.timeSinceLastInvoke)) - print(" isLocal: " .. tostring(context.isLocal)) + print(" SenderId: " .. tostring(context.SenderId)) + print(" SenderName: " .. tostring(context.SenderName)) + print(" LastInvokeTime: " .. tostring(context.LastInvokeTime)) + print(" TimeSinceLastInvoke: " .. tostring(context.TimeSinceLastInvoke)) + print(" IsLocal: " .. tostring(context.IsLocal)) end) -- Secondry example @@ -63,7 +63,7 @@ end -- Listener Functions -- function OnCoolEventOccured(context, value, value2) - print("CoolEvent triggered by " .. tostring(context.senderName)) + print("CoolEvent triggered by " .. tostring(context.SenderName)) print("Received values: " .. tostring(value) .. ", " .. tostring(value2)) print("Context details:") print(" SenderId: " .. tostring(context.SenderId)) From e96a0e164df6acd7fcf29ae2b658fa35cf17cf16 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:02:03 -0500 Subject: [PATCH 163/188] [ThirdPerson] Fixed for Stable and Nightly, did some cleanup - Adjusted the local player scaled event patch to work both on Stable and Nightly --- ThirdPerson/CameraLogic.cs | 16 +++++++--------- ThirdPerson/Patches.cs | 2 +- ThirdPerson/Properties/AssemblyInfo.cs | 2 +- ThirdPerson/format.json | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/ThirdPerson/CameraLogic.cs b/ThirdPerson/CameraLogic.cs index f1b83ef..a0ef565 100644 --- a/ThirdPerson/CameraLogic.cs +++ b/ThirdPerson/CameraLogic.cs @@ -13,7 +13,6 @@ internal static class CameraLogic private static float _dist; private static float _scale = 1f; private static Camera _thirdPersonCam; - private static Camera _uiCam; private static Camera _desktopCam; private static int _storedCamMask; private static CameraFovClone _cameraFovClone; @@ -51,11 +50,10 @@ internal static class CameraLogic _cameraFovClone = _thirdPersonCam.gameObject.AddComponent(); - _desktopCam = PlayerSetup.Instance.desktopCamera.GetComponent(); + _desktopCam = PlayerSetup.Instance.desktopCam; _cameraFovClone.targetCamera = _desktopCam; _thirdPersonCam.transform.SetParent(_desktopCam.transform); - _uiCam = _desktopCam.transform.Find("_UICamera").GetComponent(); RelocateCam(CameraLocation.Default); @@ -66,15 +64,15 @@ internal static class CameraLogic internal static void CopyPlayerCamValues() { - Camera activePlayerCam = PlayerSetup.Instance.GetActiveCamera().GetComponent(); - if (_thirdPersonCam == null || activePlayerCam == null) + Camera activePlayerCam = PlayerSetup.Instance.activeCam; + if (!_thirdPersonCam || !activePlayerCam) return; ThirdPerson.Logger.Msg("Copying active camera settings & components."); - CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam, true); + CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam); // Remove PlayerClone - _thirdPersonCam.cullingMask &= ~(1 << CVRLayers.PlayerClone); + // _thirdPersonCam.cullingMask &= ~(1 << CVRLayers.PlayerClone); if (!CheckIsRestricted()) return; @@ -116,9 +114,9 @@ internal static class CameraLogic private static void ResetDist() => _dist = 0; internal static void ScrollDist(float sign) { _dist += sign * 0.25f; RelocateCam(CurrentLocation); } - internal static void AdjustScale(float height) { _scale = height; RelocateCam(CurrentLocation); } + internal static void AdjustScale(float avatarScaleRelation) { _scale = avatarScaleRelation; RelocateCam(CurrentLocation); } internal static void CheckVRMode() { if (MetaPort.Instance.isUsingVr) State = false; } private static bool CheckIsRestricted() - => CVRWorld.Instance != null && !CVRWorld.Instance.enableZoom; + => CVRWorld.Instance && !CVRWorld.Instance.enableZoom; } \ No newline at end of file diff --git a/ThirdPerson/Patches.cs b/ThirdPerson/Patches.cs index 131b45b..02a60db 100644 --- a/ThirdPerson/Patches.cs +++ b/ThirdPerson/Patches.cs @@ -32,6 +32,6 @@ internal static class Patches //Copy camera settings & postprocessing components private static void OnPostWorldStart() => CopyPlayerCamValues(); //Adjust camera distance with height as modifier - private static void OnScaleAdjusted(float height) => AdjustScale(height); + private static void OnScaleAdjusted(ref float ____avatarScaleRelation) => AdjustScale(____avatarScaleRelation); private static void OnConfigureHudAffinity() => CheckVRMode(); } \ No newline at end of file diff --git a/ThirdPerson/Properties/AssemblyInfo.cs b/ThirdPerson/Properties/AssemblyInfo.cs index c2c7701..e6254b3 100644 --- a/ThirdPerson/Properties/AssemblyInfo.cs +++ b/ThirdPerson/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ThirdPerson.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.1.1"; + public const string Version = "1.1.2"; public const string Author = "Davi & NotAKidoS"; } \ No newline at end of file diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index 4cdca3e..c2536e7 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -2,7 +2,7 @@ { "_id": 16, "name": "ThirdPerson", - "modversion": "1.1.1", + "modversion": "1.1.2", "gameversion": "2025r179", "loaderversion": "0.6.1", "modtype": "Mod", @@ -16,7 +16,7 @@ "requirements": [], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Adjusted the local player scaled event patch to work both on Stable and Nightly.", "embedcolor": "#F61961" } ] \ No newline at end of file From 0cdef04a53d10ed003324714614fe7e766344e0e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:02:05 -0500 Subject: [PATCH 164/188] Create DepthCameraFix.cs --- .Deprecated/SuperAwesomeMod/DepthCameraFix.cs | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .Deprecated/SuperAwesomeMod/DepthCameraFix.cs diff --git a/.Deprecated/SuperAwesomeMod/DepthCameraFix.cs b/.Deprecated/SuperAwesomeMod/DepthCameraFix.cs new file mode 100644 index 0000000..933cfa6 --- /dev/null +++ b/.Deprecated/SuperAwesomeMod/DepthCameraFix.cs @@ -0,0 +1,106 @@ +namespace NAK.SuperAwesomeMod; + +using UnityEngine; +using UnityEngine.Rendering; + +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Rendering; + +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Rendering; + +[RequireComponent(typeof(Camera))] +public class DepthTextureFix : MonoBehaviour +{ + private Camera cam; + private CommandBuffer beforeCommandBuffer; + private CommandBuffer afterCommandBuffer; + + void Start() + { + cam = GetComponent(); + + // Ensure camera generates depth texture + cam.depthTextureMode |= DepthTextureMode.Depth; + + // Create command buffers + beforeCommandBuffer = new CommandBuffer(); + beforeCommandBuffer.name = "DepthTextureFix_Before"; + + afterCommandBuffer = new CommandBuffer(); + afterCommandBuffer.name = "DepthTextureFix_After"; + + // Add command buffers at the right events + cam.AddCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer); + cam.AddCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer); + } + + void OnPreRender() + { + // Set up command buffers each frame to handle dynamic changes + SetupCommandBuffers(); + } + + void SetupCommandBuffers() + { + // Get current camera viewport in pixels + Rect pixelRect = cam.pixelRect; + + // Before depth texture: override viewport to full screen + beforeCommandBuffer.Clear(); + beforeCommandBuffer.SetViewport(new Rect(0, 0, Screen.width, Screen.height)); + + // After depth texture: restore original viewport + afterCommandBuffer.Clear(); + afterCommandBuffer.SetViewport(pixelRect); + } + + void OnDestroy() + { + // Clean up + if (cam != null) + { + if (beforeCommandBuffer != null) + { + cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer); + beforeCommandBuffer.Dispose(); + } + + if (afterCommandBuffer != null) + { + cam.RemoveCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer); + afterCommandBuffer.Dispose(); + } + } + } + + void OnDisable() + { + if (cam != null) + { + if (beforeCommandBuffer != null) + cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer); + if (afterCommandBuffer != null) + cam.RemoveCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer); + } + } + + void OnEnable() + { + if (cam != null && beforeCommandBuffer != null && afterCommandBuffer != null) + { + cam.AddCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer); + cam.AddCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer); + } + } +} \ No newline at end of file From c455d20f9c22ec85b156ec750618e912af2f4cd7 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:13:55 -0500 Subject: [PATCH 165/188] [ThirdPerson] update format.json --- ThirdPerson/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index c2536e7..52d5bc8 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -16,7 +16,7 @@ "requirements": [], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Adjusted the local player scaled event patch to work both on Stable and Nightly.", + "changelog": "- Adjusted the local player scaled event patch to work both on Stable and Nightly (r179 & r180)", "embedcolor": "#F61961" } ] \ No newline at end of file From faf9d48fb6a166280c14085b95b1f476befde761 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:30:02 -0500 Subject: [PATCH 166/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] initial release --- .../Main.cs | 104 ++++++++++++------ .../Properties/AssemblyInfo.cs | 2 +- .../format.json | 4 +- 3 files changed, 74 insertions(+), 36 deletions(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs index 02e39c0..ee47977 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -1,11 +1,13 @@ using System.Collections; using System.Reflection; +using ABI_RC.Core.EventSystem; using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; using ABI_RC.Core.Networking; using ABI_RC.Core.Networking.API.Responses; using ABI_RC.Core.Player; using ABI_RC.Core.Util; +using ABI_RC.Core.Util.Encryption; using ABI_RC.Systems.GameEventSystem; using ABI_RC.Systems.Movement; using ABI.CCK.Components; @@ -64,7 +66,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod #region CVRAttachment Patches HarmonyInstance.Patch( // Cannot compile when using nameof - typeof(CVRAttachment).GetMethod("\u003CAttachInternal\u003Eg__DoAttachmentSetup\u007C43_0", + typeof(CVRAttachment).GetMethod(nameof(CVRAttachment.DoAttachmentSetup), BindingFlags.NonPublic | BindingFlags.Instance), postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentAttachInternal), BindingFlags.NonPublic | BindingFlags.Static)) @@ -96,6 +98,17 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod #endregion CVRSeat Patches + #region CVRSpawnable Patches + + HarmonyInstance.Patch( + typeof(CVRSpawnable).GetMethod(nameof(CVRSpawnable.OnDestroy), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnSpawnableOnDestroy), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + #endregion CVRSpawnable Patches + #region CVRSyncHelper Patches HarmonyInstance.Patch( // Replaces method, original needlessly ToArray??? @@ -147,12 +160,28 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod #region Harmony Patches - private static readonly List _heldPropData = new(); + [Flags] private enum HeldPropState { None = 0, Pickup = 1, Attachment = 2, Seat = 3 } + + private static readonly Dictionary _heldPropStates = new(); + + private static void AddHeldPropState(CVRSyncHelper.PropData propData, HeldPropState state) + { + if (!_heldPropStates.TryAdd(propData, state)) _heldPropStates[propData] |= state; + } + + private static void RemoveHeldPropState(CVRSyncHelper.PropData propData, HeldPropState state) + { + if (!_heldPropStates.TryGetValue(propData, out HeldPropState currentState)) return; + currentState &= ~state; + if (currentState == HeldPropState.None) _heldPropStates.Remove(propData); + else _heldPropStates[propData] = currentState; + } + private static GameObject _persistantPropsContainer; private static GameObject GetOrCreatePropsContainer() { - if (_persistantPropsContainer != null) return _persistantPropsContainer; - _persistantPropsContainer = new("[NAK] PersistantProps"); + if (_persistantPropsContainer) return _persistantPropsContainer; + _persistantPropsContainer = new("YouAreMyPropNowWeAreHavingSoftTacosLater"); _persistantPropsContainer.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); _persistantPropsContainer.transform.localScale = Vector3.one; Object.DontDestroyOnLoad(_persistantPropsContainer); @@ -168,47 +197,50 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod { if (!EntryTrackPickups.Value) return; if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + AddHeldPropState(propData, HeldPropState.Pickup); } private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance) { if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + RemoveHeldPropState(propData, HeldPropState.Pickup); } private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance) { if (!EntryTrackAttachments.Value) return; if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + AddHeldPropState(propData, HeldPropState.Attachment); } private static void OnCVRAttachmentDeAttach(CVRAttachment __instance) { if (!__instance._isAttached) return; // Can invoke DeAttach without being attached if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + RemoveHeldPropState(propData, HeldPropState.Attachment); } private static void OnCVRSeatSitDown(CVRSeat __instance) { if (!EntryTrackSeats.Value) return; if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + AddHeldPropState(propData, HeldPropState.Seat); } private static void OnCVRSeatExitSeat(CVRSeat __instance) { if (!TryGetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; - if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + RemoveHeldPropState(propData, HeldPropState.Seat); + } + + private static void OnSpawnableOnDestroy(CVRSpawnable __instance) + { + if (!TryGetPropData(__instance, out CVRSyncHelper.PropData propData)) return; + _heldPropStates.Remove(propData); } // ReSharper disable UnusedParameter.Local - private static bool OnCVRDownloadManagerQueueTask(string assetId, DownloadTask.ObjectType type, string assetUrl, string fileId, long fileSize, string fileKey, string toAttach, - string fileHash = null, UgcTagsData tagsData = null, CVRLoadingAvatarController loadingAvatarController = null, - bool joinOnComplete = false, bool isHomeRequested = false, int compatibilityVersion = 0, int encryptionAlgorithm = 0, - string spawnerId = null) + private static bool OnCVRDownloadManagerQueueTask(AssetManagement.UgcMetadata metadata, DownloadTask.ObjectType type, string assetUrl, string fileId, string toAttach, CVRLoadingAvatarController loadingAvatarController = null, bool joinOnComplete = false, bool isHomeRequested = false, CompatibilityVersions compatibilityVersion = CompatibilityVersions.NotSpi, CVREncryptionRouter.EncryptionAlgorithm encryptionAlgorithm = CVREncryptionRouter.EncryptionAlgorithm.Gen1, string spawnerId = null) { if (type != DownloadTask.ObjectType.Prop) return true; // Only care about props @@ -219,14 +251,20 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod Vector3 identity = GetIdentityKeyFromPropData(newPropData); if (!_keyToPropData.Remove(identity, out CVRSyncHelper.PropData originalPropData)) return true; - // Remove original prop data from held - if (_heldPropData.Contains(originalPropData)) _heldPropData.Remove(originalPropData); + // Remove original prop data from held, cache states + HeldPropState heldState = HeldPropState.None; + if (_heldPropStates.ContainsKey(originalPropData)) + { + heldState = _heldPropStates[originalPropData]; + _heldPropStates.Remove(originalPropData); + } // If original prop data is null spawn a new prop i guess :( - if (originalPropData.Spawnable == null) return true; + if (!originalPropData.Spawnable) return true; // Add the new prop data to our held props in place of the old one - if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData); + // if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData); + _heldPropStates.TryAdd(newPropData, heldState); // Apply new prop data to the spawnable newPropData.Spawnable = originalPropData.Spawnable; @@ -255,7 +293,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod for (var index = CVRSyncHelper.Props.Count - 1; index >= 0; index--) { CVRSyncHelper.PropData prop = CVRSyncHelper.Props[index]; - if (prop.Spawnable != null && _heldPropData.Contains(prop)) + if (prop.Spawnable && _heldPropStates.ContainsKey(prop)) continue; // Do not recycle props that are valid & held DeleteOrRecycleProp(prop); @@ -269,7 +307,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod { if (!_ignoreNextSeatExit) return true; _ignoreNextSeatExit = false; - if (BetterBetterCharacterController.Instance._lastCvrSeat == null) return true; // run original + if (!BetterBetterCharacterController.Instance._lastCvrSeat) return true; // run original return false; // dont run if there is a chair & we skipped it } @@ -282,16 +320,16 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod private void OnWorldLoad(string _) { CVRWorld worldInstance = CVRWorld.Instance; - if (worldInstance != null && !worldInstance.allowSpawnables) + if (worldInstance && !worldInstance.allowSpawnables) { - foreach (CVRSyncHelper.PropData prop in _heldPropData) DeleteOrRecycleProp(prop); // Delete all props we kept + foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys) DeleteOrRecycleProp(prop); // Delete all props we kept return; } - for (var index = _heldPropData.Count - 1; index >= 0; index--) + for (var index = _heldPropStates.Count - 1; index >= 0; index--) { - CVRSyncHelper.PropData prop = _heldPropData[index]; - if (prop.Spawnable == null) + CVRSyncHelper.PropData prop = _heldPropStates.ElementAt(index).Key; + if (!prop.Spawnable) { DeleteOrRecycleProp(prop); return; @@ -324,9 +362,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod private static void OnWorldUnload(string _) { // Prevent deleting of our held props on scene destruction - foreach (CVRSyncHelper.PropData prop in _heldPropData) + foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys) { - if (prop.Spawnable == null) continue; + if (!prop.Spawnable) continue; PlacePropInPersistantPropsContainer(prop.Spawnable); _spawnablePositionStack.Push(new SpawnablePositionContainer(prop.Spawnable)); } @@ -341,9 +379,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod { // Request the server to respawn our props by GUID, and add a secret key to the propData to identify it - foreach (CVRSyncHelper.PropData prop in _heldPropData) + foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys) { - if (prop.Spawnable == null) continue; + if (!prop.Spawnable) continue; // Generate a new identity key for the prop (this is used to identify the prop when we respawn it) Vector3 identityKey = new(Random.Range(0, 1000), Random.Range(0, 1000), Random.Range(0, 1000)); @@ -362,7 +400,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod private static bool TryGetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData) { - if (spawnable == null) + if (!spawnable) { propData = null; return false; @@ -408,9 +446,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod private static void DeleteOrRecycleProp(CVRSyncHelper.PropData prop) { - if (prop.Spawnable == null) prop.Recycle(); + if (!prop.Spawnable) prop.Recycle(); else prop.Spawnable.Delete(); - if (_heldPropData.Contains(prop)) _heldPropData.Remove(prop); + _heldPropStates.Remove(prop); } private static void SpawnPropFromGuid(string propGuid, Vector3 position, Vector3 rotation, Vector3 identityKey) @@ -518,7 +556,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod for (int i = 0; i < _spawnable.subSyncs.Count; i++) { Transform subSyncTransform = _spawnable.subSyncs[i].transform; - if (subSyncTransform == null) continue; + if (!subSyncTransform) continue; Vector3 subWorldPos = playerTransform.TransformPoint(_posOffsets[i + 1]); subWorldPos.y += _heightOffset; diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs index bfc0a13..fa1864b 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json index 78c5bbc..b88a58b 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -2,8 +2,8 @@ "_id": -1, "name": "YouAreMyPropNowWeAreHavingSoftTacosLater", "modversion": "1.0.0", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO", From ee4df06d2ecd30964f1e98cf8a33d4630d9d0580 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:30:16 -0500 Subject: [PATCH 167/188] [ThirdPerson] Fixes for r180 --- ThirdPerson/CameraLogic.cs | 5 +---- ThirdPerson/Properties/AssemblyInfo.cs | 4 ++-- ThirdPerson/ThirdPerson.cs | 5 +++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ThirdPerson/CameraLogic.cs b/ThirdPerson/CameraLogic.cs index a0ef565..8f26e68 100644 --- a/ThirdPerson/CameraLogic.cs +++ b/ThirdPerson/CameraLogic.cs @@ -1,7 +1,6 @@ using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Core.Util.Object_Behaviour; -using System.Collections; using ABI_RC.Core; using ABI.CCK.Components; using UnityEngine; @@ -42,10 +41,8 @@ internal static class CameraLogic } } - internal static IEnumerator SetupCamera() + internal static void SetupCamera() { - yield return new WaitUntil(() => PlayerSetup.Instance); - _thirdPersonCam = new GameObject("ThirdPersonCameraObj", typeof(Camera)).GetComponent(); _cameraFovClone = _thirdPersonCam.gameObject.AddComponent(); diff --git a/ThirdPerson/Properties/AssemblyInfo.cs b/ThirdPerson/Properties/AssemblyInfo.cs index e6254b3..85d8bfa 100644 --- a/ThirdPerson/Properties/AssemblyInfo.cs +++ b/ThirdPerson/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 97)] // do not change color, originally chosen by Davi @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ThirdPerson.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.1.2"; + public const string Version = "1.1.3"; public const string Author = "Davi & NotAKidoS"; } \ No newline at end of file diff --git a/ThirdPerson/ThirdPerson.cs b/ThirdPerson/ThirdPerson.cs index 21691c4..5147e39 100644 --- a/ThirdPerson/ThirdPerson.cs +++ b/ThirdPerson/ThirdPerson.cs @@ -1,4 +1,5 @@ -using MelonLoader; +using ABI_RC.Systems.GameEventSystem; +using MelonLoader; using UnityEngine; using static NAK.ThirdPerson.CameraLogic; @@ -13,7 +14,7 @@ public class ThirdPerson : MelonMod Logger = LoggerInstance; Patches.Apply(HarmonyInstance); - MelonCoroutines.Start(SetupCamera()); + CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(SetupCamera); } public override void OnUpdate() From 548fcf72bcc6e7d824ba79bd6ae39531712e2535 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:31:07 -0500 Subject: [PATCH 168/188] [ThirdPerson] Updated format.json --- ThirdPerson/format.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index 52d5bc8..780db31 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -2,9 +2,9 @@ { "_id": 16, "name": "ThirdPerson", - "modversion": "1.1.2", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.1.3", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "Davi & NotAKidoS", "description": "Allows you to go into third person view by pressing Ctrl + T to toggle and Ctrl + Y to cycle modes.", @@ -16,7 +16,7 @@ "requirements": [], "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Adjusted the local player scaled event patch to work both on Stable and Nightly (r179 & r180)", + "changelog": "- Fixes for r180", "embedcolor": "#F61961" } ] \ No newline at end of file From 13e206cd5882585db4560c538b941717e1bec183 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:33:42 -0500 Subject: [PATCH 169/188] [ThirdPerson] Updated format.json --- ThirdPerson/format.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ThirdPerson/format.json b/ThirdPerson/format.json index 780db31..ff4c491 100644 --- a/ThirdPerson/format.json +++ b/ThirdPerson/format.json @@ -14,9 +14,9 @@ "third person" ], "requirements": [], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson", - "changelog": "- Fixes for r180", + "changelog": "- Fixes for 2025r180", "embedcolor": "#F61961" } ] \ No newline at end of file From a0a859aa868cdf56bd81534404e9d85656e0a808 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:33:53 -0500 Subject: [PATCH 170/188] [ShareBubbles] Fixes for 2025r180 --- ShareBubbles/Main.cs | 2 +- ShareBubbles/Patches.cs | 1631 +---------------- ShareBubbles/Properties/AssemblyInfo.cs | 4 +- .../API/Exceptions/ShareApiExceptions.cs | 66 - .../API/PedestalInfoBatchProcessor.cs | 3 +- .../API/Responses/ActiveSharesResponse.cs | 22 - .../ShareBubbles/API/ShareApiHelper.cs | 237 --- .../Implementation/AvatarBubbleImpl.cs | 4 +- .../Implementation/SpawnableBubbleImpl.cs | 4 +- .../Implementation/TempShareManager.cs | 4 +- .../Networking/ModNetwork.Inbound.cs | 4 +- .../Networking/ModNetwork.Outbound.cs | 4 +- .../ShareBubbles/UI/BubbleInteract.cs | 18 +- ShareBubbles/format.json | 10 +- 14 files changed, 60 insertions(+), 1953 deletions(-) delete mode 100644 ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs delete mode 100644 ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs delete mode 100644 ShareBubbles/ShareBubbles/API/ShareApiHelper.cs diff --git a/ShareBubbles/Main.cs b/ShareBubbles/Main.cs index 72f3c3d..802a6be 100644 --- a/ShareBubbles/Main.cs +++ b/ShareBubbles/Main.cs @@ -28,7 +28,7 @@ public class ShareBubblesMod : MelonMod LoadAssetBundle(); } - + public override void OnApplicationQuit() { ModNetwork.Unsubscribe(); diff --git a/ShareBubbles/Patches.cs b/ShareBubbles/Patches.cs index 5f95231..eaf2440 100644 --- a/ShareBubbles/Patches.cs +++ b/ShareBubbles/Patches.cs @@ -1,14 +1,6 @@ using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.API.Responses; -using ABI_RC.Core.Networking.API.UserWebsocket; using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; using HarmonyLib; -using MTJobSystem; -using NAK.ShareBubbles.API; -using NAK.ShareBubbles.API.Exceptions; -using NAK.ShareBubbles.API.Responses; -using Newtonsoft.Json; namespace NAK.ShareBubbles.Patches; @@ -68,1419 +60,31 @@ internal static class ControllerRay_Patches internal static class ViewManager_Patches { - -private const string DETAILS_TOOLBAR_PATCHES = """ - -const ContentShareMod = { - debugMode: false, - currentContentData: null, - themeColors: null, - - /* Theme Handling */ - - getThemeColors: function() { - if (this.themeColors) return this.themeColors; - - // Default fallback colors - const defaultColors = { - background: '#373021', - border: '#59885d' - }; - - // Try to get colors from favorite category element - const favoriteCategoryElement = document.querySelector('.favorite-category-selection'); - if (!favoriteCategoryElement) return defaultColors; - - const computedStyle = window.getComputedStyle(favoriteCategoryElement); - this.themeColors = { - background: computedStyle.backgroundColor || defaultColors.background, - border: computedStyle.borderColor || defaultColors.border - }; - - return this.themeColors; - }, - - applyThemeToDialog: function(dialog) { - const colors = this.getThemeColors(); - dialog.style.backgroundColor = colors.background; - dialog.style.borderColor = colors.border; - - // Update any close or page buttons to match theme - const buttons = dialog.querySelectorAll('.close-btn, .page-btn'); - buttons.forEach(button => { - button.style.borderColor = colors.border; - }); - - return colors; - }, - - /* Core Initialization */ - - init: function() { - const styles = [ - this.getSharedStyles(), - this.ShareBubble.initStyles(), - this.ShareSelect.initStyles(), - this.DirectShare.initStyles(), - this.Unshare.initStyles() - ].join('\n'); - - const styleElement = document.createElement('style'); - styleElement.type = 'text/css'; - styleElement.innerHTML = styles; - document.head.appendChild(styleElement); - - this.shareBubbleDialog = this.ShareBubble.createDialog(); - this.shareSelectDialog = this.ShareSelect.createDialog(); - this.directShareDialog = this.DirectShare.createDialog(); - this.unshareDialog = this.Unshare.createDialog(); - - this.initializeToolbars(); - this.bindEvents(); - }, - - getSharedStyles: function() { - return ` - .content-sharing-base-dialog { - position: fixed; - background-color: #373021; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 800px; - min-width: 500px; - border: 3px solid #59885d; - padding: 20px; - z-index: 100000; - opacity: 0; - transition: opacity 0.2s linear; - } - - .content-sharing-base-dialog.in { - opacity: 1; - } - - .content-sharing-base-dialog.out { - opacity: 0; - } - - .content-sharing-base-dialog.hidden { - display: none; - } - - .content-sharing-base-dialog h2, - .content-sharing-base-dialog h3 { - margin-top: 0; - margin-bottom: 0.5em; - text-align: left; - } - - .content-sharing-base-dialog .description { - margin-bottom: 1em; - text-align: left; - font-size: 0.9em; - color: #aaa; - } - - .content-sharing-base-dialog .close-btn { - position: absolute; - top: 1%; - right: 1%; - border-radius: 0.25em; - border: 3px solid #59885d; - padding: 0.5em; - width: 8em; - text-align: center; - } - - .page-btn { - border-radius: 0.25em; - border: 3px solid #59885d; - padding: 0.5em; - width: 8em; - text-align: center; - } - - .page-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - .inp-hidden { - display: none; - } - `; - }, - - /* Feature Modules */ - - ShareBubble: { - initStyles: function() { - return ` - .share-bubble-dialog { - max-width: 800px; - transform: translate(-50%, -60%); - } - - .share-bubble-dialog .content-btn { - position: relative; - margin-bottom: 0.5em; - } - - .share-bubble-dialog .btn-group { - display: flex; - margin-bottom: 1em; - } - - .share-bubble-dialog .option-select { - flex: 1 1 0; - min-width: 0; - background-color: inherit; - text-align: center; - padding: 0.5em; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid inherit; - } - - .share-bubble-dialog .option-select + .option-select { - margin-left: -1px; - } - - .share-bubble-dialog .option-select.active, - .share-bubble-dialog .option-select:hover { - background-color: rgba(27, 80, 55, 1); - } - - .share-bubble-dialog .action-buttons { - margin-top: 1em; - display: flex; - justify-content: space-between; - } - - .share-bubble-dialog .action-btn { - flex: 1; - text-align: center; - padding: 0.5em; - border-radius: 0.25em; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - margin-right: 0.5em; - } - - .share-bubble-dialog .action-btn:last-child { - margin-right: 0; - } - `; - }, - - createDialog: function() { - const dialog = document.createElement('div'); - dialog.id = 'content-share-bubble-dialog'; - dialog.className = 'content-sharing-base-dialog share-bubble-dialog hidden'; - dialog.innerHTML = ` -

Share Bubble

-
Close
- -

Visibility

-

Choose who can see your Sharing Bubble.

-
-
Everyone
-
Friends Only
-
- -

Lifetime

-

How long the Sharing Bubble lasts. You can delete it at any time.

-
-
2 Minutes
-
For Session
-
- -
-

Access Control

-
- - -

- Users cannot access your Private Content through this share. -

-
-
-
Keep
-
Instance Only
-
None
-
-
- - - - - - - -
-
Drop
-
Select
-
- `; - document.body.appendChild(dialog); - return dialog; - }, - - show: function(contentDetails) { - const dialog = ContentShareMod.shareBubbleDialog; - const colors = ContentShareMod.applyThemeToDialog(dialog); - - // Additional ShareBubble-specific theming - const optionSelects = dialog.querySelectorAll('.option-select'); - optionSelects.forEach(element => { - element.style.borderColor = colors.border; - }); - - const grantAccessSection = dialog.querySelector('.grant-access-section'); - const noAccessControlMessage = dialog.querySelector('.no-access-control-message'); - const showGrantAccess = contentDetails.IsMine && !contentDetails.IsPublic; - - if (grantAccessSection && noAccessControlMessage) { - grantAccessSection.style.display = showGrantAccess ? '' : 'none'; - noAccessControlMessage.style.display = showGrantAccess ? 'none' : ''; - } - - dialog.classList.remove('hidden', 'out'); - setTimeout(() => dialog.classList.add('in'), 50); - - ContentShareMod.currentContentData = contentDetails; - }, - - hide: function() { - const dialog = ContentShareMod.shareBubbleDialog; - dialog.classList.remove('in'); - dialog.classList.add('out'); - setTimeout(() => { - dialog.classList.add('hidden'); - dialog.classList.remove('out'); - }, 200); - }, - - changeVisibility: function(element) { - document.getElementById('share-visibility').value = element.dataset.visibilityValue; - const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.visibility-btn'); - buttons.forEach(btn => btn.classList.remove('active')); - element.classList.add('active'); - }, - - changeDuration: function(element) { - document.getElementById('share-duration').value = element.dataset.durationValue; - const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.duration-btn'); - buttons.forEach(btn => btn.classList.remove('active')); - element.classList.add('active'); - }, - - changeAccess: function(element) { - document.getElementById('share-access').value = element.dataset.accessValue; - const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-btn'); - buttons.forEach(btn => btn.classList.remove('active')); - element.classList.add('active'); - - const descriptions = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-desc'); - descriptions.forEach(desc => { - desc.style.display = desc.dataset.accessType === element.dataset.accessValue ? '' : 'none'; - }); - }, - - submit: function(action) { - const contentDetails = ContentShareMod.currentContentData; - let bubbleImpl, bubbleContent, contentImage, contentName; - - if (contentDetails.AvatarId) { - bubbleImpl = 'Avatar'; - bubbleContent = contentDetails.AvatarId; - contentImage = contentDetails.AvatarImageCoui; - contentName = contentDetails.AvatarName; - } else if (contentDetails.SpawnableId) { - bubbleImpl = 'Spawnable'; - bubbleContent = contentDetails.SpawnableId; - contentImage = contentDetails.SpawnableImageCoui; - contentName = contentDetails.SpawnableName; - } else if (contentDetails.WorldId) { - bubbleImpl = 'World'; - bubbleContent = contentDetails.WorldId; - contentImage = contentDetails.WorldImageCoui; - contentName = contentDetails.WorldName; - } else if (contentDetails.UserId) { - bubbleImpl = 'User'; - bubbleContent = contentDetails.UserId; - contentImage = contentDetails.UserImageCoui; - contentName = contentDetails.UserName; - } else { - console.error('No valid content ID found'); - return; - } - - const visibility = document.getElementById('share-visibility').value; - const duration = document.getElementById('share-duration').value; - const access = document.getElementById('share-access').value; - - const shareRule = visibility === 'FriendsOnly' ? 'FriendsOnly' : 'Everyone'; - const shareLifetime = duration === 'Session' ? 'Session' : 'TwoMinutes'; - const shareAccess = access === 'Permanent' ? 'Permanent' : - access === 'Session' ? 'Session' : 'NoAccess'; - - if (ContentShareMod.debugMode) { - console.log('Sharing content:', { - action, bubbleImpl, bubbleContent, - shareRule, shareLifetime, shareAccess - }); - } - - engine.call('NAKCallShareContent', action, bubbleImpl, bubbleContent, - shareRule, shareLifetime, shareAccess, contentImage, contentName); - - this.hide(); - } - }, - - Unshare: { - currentPage: 1, - totalPages: 1, - sharesPerPage: 5, - sharesList: null, - - initStyles: function() { - return ` - .unshare-dialog { - width: 800px; - height: 1000px; - transform: translate(-50%, -60%); - display: flex; - flex-direction: column; - } - - .unshare-dialog .shares-container { - flex: 1; - overflow-y: auto; - margin: 20px 0; - min-height: 0; - } - - .unshare-dialog #shares-loading, - .unshare-dialog #shares-error, - .unshare-dialog #shares-empty { - text-align: center; - padding: 2em; - font-size: 1.1em; - } - - .unshare-dialog #shares-error { - color: #ff6b6b; - } - - .unshare-dialog .share-item { - display: flex; - align-items: center; - padding: 15px; - border: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 10px; - background: rgba(0, 0, 0, 0.2); - border-radius: 4px; - min-height: 120px; - } - - .unshare-dialog .share-item img { - width: 96px; - height: 96px; - border-radius: 4px; - margin-right: 15px; - cursor: pointer; - } - - .unshare-dialog .share-item .user-name { - flex: 1; - font-size: 1.3em; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 15px; - cursor: pointer; - } - - .unshare-dialog .action-btn { - width: 180px; - height: 60px; - font-size: 1.2em; - color: white; - border-radius: 4px; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 10px; - padding: 0 20px; - text-align: center; - } - - .unshare-dialog .revoke-btn { - background-color: #ff6b6b; - } - - .unshare-dialog .undo-btn { - background-color: #4a9eff; - } - - .unshare-dialog .action-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - background-color: #666; - } - - .unshare-dialog .pagination { - border-top: 1px solid rgba(255, 255, 255, 0.1); - padding-top: 20px; - } - - .unshare-dialog .page-info { - font-size: 1.1em; - opacity: 0.8; - margin-bottom: 15px; - text-align: center; - } - - .unshare-dialog .page-buttons { - display: flex; - justify-content: center; - gap: 20px; - } - - .unshare-dialog .share-item.revoked { - opacity: 0.7; - } - `; - }, - - createDialog: function() { - const dialog = document.createElement('div'); - dialog.id = 'content-unshare-dialog'; - dialog.className = 'content-sharing-base-dialog unshare-dialog hidden'; - dialog.innerHTML = ` -

Manage Shares

-
Close
- -
-
Loading shares...
- - - -
- - - `; - document.body.appendChild(dialog); - return dialog; - }, - - show: function(contentDetails) { - const dialog = ContentShareMod.unshareDialog; - ContentShareMod.applyThemeToDialog(dialog); - - dialog.classList.remove('hidden', 'out'); - setTimeout(() => dialog.classList.add('in'), 50); - - ContentShareMod.currentContentData = contentDetails; - this.currentPage = 1; - this.totalPages = 1; - this.sharesList = null; - this.requestShares(); - }, - - hide: function() { - const dialog = ContentShareMod.unshareDialog; - dialog.classList.remove('in'); - dialog.classList.add('out'); - setTimeout(() => { - dialog.classList.add('hidden'); - dialog.classList.remove('out'); - }, 200); - }, - - requestShares: function() { - const dialog = ContentShareMod.unshareDialog; - const sharesContainer = dialog.querySelector('.shares-container'); - - sharesContainer.querySelector('#shares-loading').style.display = ''; - sharesContainer.querySelector('#shares-error').style.display = 'none'; - sharesContainer.querySelector('#shares-empty').style.display = 'none'; - sharesContainer.querySelector('#shares-list').style.display = 'none'; - - const contentDetails = ContentShareMod.currentContentData; - const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; - const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; - - engine.call('NAKGetContentShares', contentType, contentId); - }, - - handleSharesResponse: function(success, shares) { - const dialog = ContentShareMod.unshareDialog; - const sharesContainer = dialog.querySelector('.shares-container'); - const loadingElement = sharesContainer.querySelector('#shares-loading'); - const errorElement = sharesContainer.querySelector('#shares-error'); - const emptyElement = sharesContainer.querySelector('#shares-empty'); - const sharesListElement = sharesContainer.querySelector('#shares-list'); - - loadingElement.style.display = 'none'; - - if (!success) { - errorElement.style.display = ''; - return; - } - - try { - const response = JSON.parse(shares); - this.sharesList = response.Data.value; - - if (!this.sharesList || this.sharesList.length === 0) { - emptyElement.style.display = ''; - const pagination = dialog.querySelector('.pagination'); - const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); - prevButton.disabled = true; - nextButton.disabled = true; - pagination.querySelector('.page-info').textContent = '1/1'; - return; - } - - this.totalPages = Math.ceil(this.sharesList.length / this.sharesPerPage); - this.updatePageContent(); - } catch (error) { - console.error('Error parsing shares:', error); - errorElement.style.display = ''; - } - }, - - updatePageContent: function() { - const dialog = ContentShareMod.unshareDialog; - const sharesListElement = dialog.querySelector('#shares-list'); - - const startIndex = (this.currentPage - 1) * this.sharesPerPage; - const endIndex = startIndex + this.sharesPerPage; - const currentShares = this.sharesList.slice(startIndex, endIndex); - - sharesListElement.innerHTML = currentShares.map(share => ` - - `).join(''); - - sharesListElement.style.display = ''; - - const pagination = dialog.querySelector('.pagination'); - pagination.querySelector('.page-info').textContent = `${this.currentPage}/${this.totalPages}`; - const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); - prevButton.disabled = this.currentPage === 1; - nextButton.disabled = this.currentPage === this.totalPages; - }, - - previousPage: function() { - if (this.currentPage > 1) { - this.currentPage--; - this.updatePageContent(); - } - }, - - nextPage: function() { - if (this.currentPage < this.totalPages) { - this.currentPage++; - this.updatePageContent(); - } - }, - - viewUserProfile: function(userId) { - this.hide(); - getUserDetails(userId); - }, - - revokeShare: function(userId, buttonElement) { - const contentDetails = ContentShareMod.currentContentData; - const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; - const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; - - buttonElement.disabled = true; - buttonElement.textContent = 'Revoking...'; - - engine.call('NAKRevokeContentShare', contentType, contentId, userId); - }, - - handleRevokeResponse: function(success, userId, error) { - const dialog = ContentShareMod.unshareDialog; - const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`); - if (!shareItem) return; - - const actionButton = shareItem.querySelector('button'); - - if (success) { - shareItem.classList.add('revoked'); - actionButton.className = 'action-btn undo-btn button'; - actionButton.textContent = 'Undo'; - actionButton.onclick = () => { - actionButton.disabled = true; - actionButton.textContent = 'Restoring...'; - - const contentDetails = ContentShareMod.currentContentData; - const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; - const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; - - engine.call('NAKCallShareContentDirect', contentType, contentId, userId); - }; - uiPushShow("Share revoked successfully", 3); - } else { - actionButton.textContent = 'Failed'; - actionButton.classList.add('failed'); - uiPushShow(error || "Failed to revoke share", 3); - - // Reset button after a moment - setTimeout(() => { - actionButton.disabled = false; - actionButton.textContent = 'Revoke'; - actionButton.classList.remove('failed'); - }, 1000); - } - }, - - handleShareResponse: function(success, userId, error) { - const dialog = ContentShareMod.unshareDialog; - const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`); - if (!shareItem) return; - - const actionButton = shareItem.querySelector('button'); - - if (success) { - this.requestShares(); - uiPushShow("Share restored successfully", 3); - } else { - actionButton.textContent = 'Failed'; - actionButton.classList.add('failed'); - uiPushShow(error || "Failed to restore share", 3); - - // Reset button after a moment - setTimeout(() => { - actionButton.disabled = false; - actionButton.textContent = 'Undo'; - actionButton.classList.remove('failed'); - }, 1000); - } - } - }, - - DirectShare: { - currentPage: 1, - totalPages: 1, - usersPerPage: 5, - usersList: null, - isInstanceUsers: true, - - initStyles: function() { - return ` - .direct-share-dialog { - width: 800px; - height: 1000px; - transform: translate(-50%, -60%); - display: flex; - flex-direction: column; - } - - .direct-share-dialog .search-container { - margin: 20px 0; - padding: 10px; - background: rgba(0, 0, 0, 0.2); - border-radius: 4px; - } - - .direct-share-dialog .search-input { - width: 100%; - padding: 10px; - border: none; - background: transparent; - color: inherit; - font-size: 1.1em; - } - - .direct-share-dialog .source-indicator { - padding: 10px; - text-align: center; - opacity: 0.8; - background: rgba(0, 0, 0, 0.1); - border-radius: 4px; - margin-bottom: 20px; - } - - .direct-share-dialog .users-container { - flex: 1; - overflow-y: auto; - margin-bottom: 20px; - min-height: 0; - } - - .direct-share-dialog .user-item { - display: flex; - align-items: center; - padding: 15px; - border: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 10px; - background: rgba(0, 0, 0, 0.2); - border-radius: 4px; - min-height: 96px; - } - - .direct-share-dialog .user-item img { - width: 96px; - height: 96px; - margin-right: 15px; - cursor: pointer; - border-radius: 4px; - } - - .direct-share-dialog .user-item .user-name { - flex: 1; - font-size: 1.3em; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 15px; - cursor: pointer; - } - - .direct-share-dialog .user-item .action-btn { - width: 140px; - height: 50px; - font-size: 1.2em; - color: white; - background-color: rgba(27, 80, 55, 1); - border-radius: 4px; - display: inline-flex; - align-items: center; - justify-content: center; - } - - .direct-share-dialog .user-item .action-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - background-color: #4a9eff; - } - - .direct-share-dialog .pagination { - border-top: 1px solid rgba(255, 255, 255, 0.1); - padding-top: 20px; - } - - .direct-share-dialog .page-info { - font-size: 1.1em; - opacity: 0.8; - margin-bottom: 15px; - text-align: center; - } - - .direct-share-dialog .page-buttons { - display: flex; - justify-content: center; - gap: 20px; - } - - .direct-share-dialog .user-item .action-btn { - width: 180px; - height: 60px; - font-size: 1.2em; - color: white; - border-radius: 4px; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 10px; - padding: 0 20px; - text-align: center; - background-color: rgba(27, 80, 55, 1); - } - - .direct-share-dialog .user-item .action-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - .direct-share-dialog .user-item .action-btn.shared { - background-color: #4a9eff; - } - - .direct-share-dialog .user-item .action-btn.failed { - background-color: #ff6b6b; - } - `; - }, - - createDialog: function() { - const dialog = document.createElement('div'); - dialog.id = 'content-direct-share-dialog'; - dialog.className = 'content-sharing-base-dialog direct-share-dialog hidden'; - dialog.innerHTML = ` -

Direct Share

-
Close
- -
-
Loading users...
- - - -
- - - `; - document.body.appendChild(dialog); - return dialog; - }, - - show: function(contentDetails) { - const dialog = ContentShareMod.directShareDialog; - ContentShareMod.applyThemeToDialog(dialog); - - dialog.classList.remove('hidden', 'out'); - setTimeout(() => dialog.classList.add('in'), 50); - - ContentShareMod.currentContentData = contentDetails; - this.currentPage = 1; - this.totalPages = 1; - this.usersList = null; - this.requestUsers(true); - }, - - hide: function() { - const dialog = ContentShareMod.directShareDialog; - dialog.classList.remove('in'); - dialog.classList.add('out'); - setTimeout(() => { - dialog.classList.add('hidden'); - dialog.classList.remove('out'); - }, 200); - }, - - handleUsersResponse: function(success, users, isInstanceUsers) { - const dialog = ContentShareMod.directShareDialog; - const usersContainer = dialog.querySelector('.users-container'); - // const sourceIndicator = dialog.querySelector('.source-indicator'); - const loadingElement = usersContainer.querySelector('#users-loading'); - const errorElement = usersContainer.querySelector('#users-error'); - const emptyElement = usersContainer.querySelector('#users-empty'); - const usersListElement = usersContainer.querySelector('#users-list'); - - loadingElement.style.display = 'none'; - // sourceIndicator.textContent = isInstanceUsers ? - // 'Showing users in current instance' : - // 'Showing search results'; - - // TODO: Add source indicator to html: - //
- // Showing users in current instance - //
- - if (!success) { - errorElement.style.display = ''; - return; - } - - try { - const response = JSON.parse(users); - this.usersList = response.entries; - this.isInstanceUsers = isInstanceUsers; - - if (!this.usersList || this.usersList.length === 0) { - emptyElement.style.display = ''; - this.updatePagination(); - return; - } - - this.totalPages = Math.ceil(this.usersList.length / this.usersPerPage); - this.updatePageContent(); - } catch (error) { - console.error('Error parsing users:', error); - errorElement.style.display = ''; - } - }, - - handleSearch: function(event) { - if (event.key === 'Enter') { - const searchValue = event.target.value.trim(); - // Pass true for instance users when empty search, false for search results - this.requestUsers(searchValue === '', searchValue); - } - }, - - requestUsers: function(isInstanceUsers, searchQuery = '') { - const dialog = ContentShareMod.directShareDialog; - const usersContainer = dialog.querySelector('.users-container'); - - usersContainer.querySelector('#users-loading').style.display = ''; - usersContainer.querySelector('#users-error').style.display = 'none'; - usersContainer.querySelector('#users-empty').style.display = 'none'; - usersContainer.querySelector('#users-list').style.display = 'none'; - - engine.call('NAKGetUsersForSharing', searchQuery); - }, - - updatePageContent: function() { - const dialog = ContentShareMod.directShareDialog; - const usersListElement = dialog.querySelector('#users-list'); - - const startIndex = (this.currentPage - 1) * this.usersPerPage; - const endIndex = startIndex + this.usersPerPage; - const currentUsers = this.usersList.slice(startIndex, endIndex); - - usersListElement.innerHTML = currentUsers.map(user => ` -
- ${user.name}'s avatar - ${user.name} - -
- `).join(''); - - usersListElement.style.display = ''; - this.updatePagination(); - }, - - updatePagination: function() { - const dialog = ContentShareMod.directShareDialog; - const pagination = dialog.querySelector('.pagination'); - const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn'); - - pagination.querySelector('.page-info').textContent = `Page ${this.currentPage}/${this.totalPages}`; - prevButton.disabled = this.currentPage === 1; - nextButton.disabled = this.currentPage === this.totalPages; - }, - - previousPage: function() { - if (this.currentPage > 1) { - this.currentPage--; - this.updatePageContent(); - } - }, - - nextPage: function() { - if (this.currentPage < this.totalPages) { - this.currentPage++; - this.updatePageContent(); - } - }, - - viewUserProfile: function(userId) { - this.hide(); - getUserDetails(userId); - }, - - shareWithUser: function(userId, buttonElement) { - const contentDetails = ContentShareMod.currentContentData; - const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable'; - const contentId = contentDetails.AvatarId || contentDetails.SpawnableId; - const contentName = contentDetails.AvatarName || contentDetails.SpawnableName; - const contentImage = contentDetails.AvatarImageURL || contentDetails.SpawnableImageURL; - - buttonElement.disabled = true; - buttonElement.textContent = 'Sharing...'; - - engine.call('NAKCallShareContentDirect', contentType, contentId, userId, contentName, contentImage); - }, - - handleShareResponse: function(success, userId, error) { - const dialog = ContentShareMod.directShareDialog; - const userItem = dialog.querySelector(`[data-user-id="${userId}"]`); - if (!userItem) return; - - const actionButton = userItem.querySelector('button'); - - if (success) { - actionButton.textContent = 'Shared'; - actionButton.disabled = true; - actionButton.classList.add('shared'); - uiPushShow("Content shared successfully", 3, "shareresponse"); - } else { - actionButton.disabled = false; - actionButton.textContent = 'Failed'; - actionButton.classList.add('failed'); - uiPushShow(error || "Failed to share content", 3, "shareresponse"); - - // Reset button after a moment - setTimeout(() => { - actionButton.disabled = false; - actionButton.textContent = 'Share'; - actionButton.classList.remove('failed', 'shared'); - }, 1000); - } - } - }, - - ShareSelect: { - initStyles: function() { - return ` - .share-select-dialog { - width: 650px; - height: 480px; - transform: translate(-50%, -80%); - } - - .share-select-dialog .share-options { - display: flex; - flex-direction: column; - gap: 15px; - margin-top: 20px; - } - - .share-select-dialog .share-option { - padding: 20px; - text-align: left; - cursor: pointer; - background: rgba(0, 0, 0, 0.2); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 4px; - transition: background-color 0.2s ease; - } - - .share-select-dialog .share-option:hover { - background-color: rgba(27, 80, 55, 1); - border-color: rgba(255, 255, 255, 0.2); - } - - .share-select-dialog h3 { - margin: 0 0 8px 0; - font-size: 1.2em; - } - - .share-select-dialog p { - margin: 0; - opacity: 0.8; - font-size: 0.95em; - line-height: 1.4; - } - `; - }, - - createDialog: function() { - const dialog = document.createElement('div'); - dialog.id = 'content-share-select-dialog'; - dialog.className = 'content-sharing-base-dialog share-select-dialog hidden'; - dialog.innerHTML = ` -

Share Content

-
Close
- - - `; - document.body.appendChild(dialog); - return dialog; - }, - - show: function(contentDetails) { - const dialog = ContentShareMod.shareSelectDialog; - ContentShareMod.applyThemeToDialog(dialog); - - dialog.classList.remove('hidden', 'out'); - setTimeout(() => dialog.classList.add('in'), 50); - - ContentShareMod.currentContentData = contentDetails; - }, - - hide: function() { - const dialog = ContentShareMod.shareSelectDialog; - dialog.classList.remove('in'); - dialog.classList.add('out'); - setTimeout(() => { - dialog.classList.add('hidden'); - dialog.classList.remove('out'); - }, 200); - }, - - openShareBubble: function() { - this.hide(); - ContentShareMod.ShareBubble.show(ContentShareMod.currentContentData); - }, - - openDirectShare: function() { - this.hide(); - ContentShareMod.DirectShare.show(ContentShareMod.currentContentData); - } - }, - - // Toolbar initialization and event bindings - initializeToolbars: function() { - const findEmptyButtons = (toolbar) => { - return Array.from(toolbar.querySelectorAll('.toolbar-btn')).filter( - btn => btn.textContent.trim() === "" - ); - }; - - const setupToolbar = (selector) => { - const toolbar = document.querySelector(selector); - if (!toolbar) return; - - const emptyButtons = findEmptyButtons(toolbar); - if (emptyButtons.length >= 2) { - emptyButtons[0].classList.add('content-share-btn'); - emptyButtons[0].textContent = 'Share'; - - emptyButtons[1].classList.add('content-unshare-btn'); - emptyButtons[1].textContent = 'Unshare'; - } - }; - - setupToolbar('#avatar-detail .avatar-toolbar'); - setupToolbar('#prop-detail .avatar-toolbar'); - }, - - bindEvents: function() { - // Avatar events - engine.on("LoadAvatarDetails", (avatarDetails) => { - const shareBtn = document.querySelector('#avatar-detail .content-share-btn'); - const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn'); - const canShareDirectly = avatarDetails.IsMine; - const canUnshare = avatarDetails.IsMine || avatarDetails.IsSharedWithMe; - - if (shareBtn) { - shareBtn.classList.remove('disabled'); - - if (canShareDirectly) { - shareBtn.onclick = () => ContentShareMod.ShareSelect.show(avatarDetails); - } else { - shareBtn.onclick = () => ContentShareMod.ShareBubble.show(avatarDetails); - } - } - - if (unshareBtn) { - if (canUnshare) { - unshareBtn.classList.remove('disabled'); - unshareBtn.onclick = () => { - if (avatarDetails.IsMine) { - ContentShareMod.Unshare.show(avatarDetails); - } else { - uiConfirmShow("Unshare Avatar", - "Are you sure you want to unshare this avatar?", - "unshare_avatar_confirmation", - avatarDetails.AvatarId); - } - }; - } else { - unshareBtn.classList.add('disabled'); - unshareBtn.onclick = null; - } - } - - ContentShareMod.currentContentData = avatarDetails; - }); - - // Prop events - engine.on("LoadPropDetails", (propDetails) => { - const shareBtn = document.querySelector('#prop-detail .content-share-btn'); - const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn'); - const canShareDirectly = propDetails.IsMine; - const canUnshare = propDetails.IsMine || propDetails.IsSharedWithMe; - - if (shareBtn) { - shareBtn.classList.remove('disabled'); - - if (canShareDirectly) { - shareBtn.onclick = () => ContentShareMod.ShareSelect.show(propDetails); - } else { - shareBtn.onclick = () => ContentShareMod.ShareBubble.show(propDetails); - } - } - - if (unshareBtn) { - if (canUnshare) { - unshareBtn.classList.remove('disabled'); - unshareBtn.onclick = () => { - if (propDetails.IsMine) { - ContentShareMod.Unshare.show(propDetails); - } else { - uiConfirmShow("Unshare Prop", - "Are you sure you want to unshare this prop?", - "unshare_prop_confirmation", - propDetails.SpawnableId); - } - }; - } else { - unshareBtn.classList.add('disabled'); - unshareBtn.onclick = null; - } - } - - ContentShareMod.currentContentData = propDetails; - }); - - // Share response handlers - engine.on("OnHandleSharesResponse", (success, shares) => { - if (ContentShareMod.debugMode) { - console.log('Shares response:', success, shares); - } - ContentShareMod.Unshare.handleSharesResponse(success, shares); - }); - - engine.on("OnHandleRevokeResponse", (success, userId, error) => { - ContentShareMod.Unshare.handleRevokeResponse(success, userId, error); - }); - - engine.on("OnHandleShareResponse", function(success, userId, error) { - // Pass event to Unshare and DirectShare modules depending on which dialog is open - if (ContentShareMod.unshareDialog && !ContentShareMod.unshareDialog.classList.contains('hidden')) { - ContentShareMod.Unshare.handleShareResponse(success, userId, error); - } else if (ContentShareMod.directShareDialog && !ContentShareMod.directShareDialog.classList.contains('hidden')) { - ContentShareMod.DirectShare.handleShareResponse(success, userId, error); - } - }); - - // Share release handlers - engine.on("OnReleasedAvatarShare", (contentId) => { - if (!ContentShareMod.currentContentData || - ContentShareMod.currentContentData.AvatarId !== contentId) return; - - const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn'); - ContentShareMod.currentContentData.IsSharedWithMe = false; - - if (unshareBtn) { - unshareBtn.classList.add('disabled'); - unshareBtn.onclick = null; - } - - const contentIsAccessible = ContentShareMod.currentContentData.IsMine || - ContentShareMod.currentContentData.IsPublic; - - if (!contentIsAccessible) { - const detail = document.querySelector('#avatar-detail'); - if (detail) { - ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => { - const button = detail.querySelector('.' + className); - if (button) { - button.classList.add('disabled'); - button.removeAttribute('onclick'); - } - }); - } - } - }); - - engine.on("OnReleasedPropShare", (contentId) => { - if (!ContentShareMod.currentContentData || - ContentShareMod.currentContentData.SpawnableId !== contentId) return; - - const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn'); - ContentShareMod.currentContentData.IsSharedWithMe = false; - - if (unshareBtn) { - unshareBtn.classList.add('disabled'); - unshareBtn.onclick = null; - } - - const contentIsAccessible = ContentShareMod.currentContentData.IsMine || - ContentShareMod.currentContentData.IsPublic; - - if (!contentIsAccessible) { - const detail = document.querySelector('#prop-detail'); - if (detail) { - ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => { - const button = detail.querySelector('.' + className); - if (button) { - button.classList.add('disabled'); - button.removeAttribute('onclick'); - } - }); - } - } - }); - - engine.on("OnHandleUsersResponse", (success, users, isInstanceUsers) => { - if (ContentShareMod.debugMode) { - console.log('Users response:', success, isInstanceUsers, users); - } - ContentShareMod.DirectShare.handleUsersResponse(success, users, isInstanceUsers); - }); - } -}; - -ContentShareMod.init(); - -"""; - - private const string UiConfirmId_ReleaseAvatarShareWarning = "unshare_avatar_confirmation"; - private const string UiConfirmId_ReleasePropShareWarning = "unshare_prop_confirmation"; - [HarmonyPostfix] - [HarmonyPatch(typeof(ViewManager), nameof(ViewManager.Start))] - public static void Postfix_ViewManager_Start(ViewManager __instance) + [HarmonyPatch(typeof(ViewManager), nameof(ViewManager.RegisterShareEvents))] + public static void Postfix_ViewManager_RegisterShareEvents(ViewManager __instance) { - // Inject the details toolbar patches when the game menu view is loaded - __instance.gameMenuView.Listener.FinishLoad += (_) => { - __instance.gameMenuView.View._view.ExecuteScript(DETAILS_TOOLBAR_PATCHES); - __instance.gameMenuView.View.BindCall("NAKCallShareContent", OnShareContent); - __instance.gameMenuView.View.BindCall("NAKGetContentShares", OnGetContentShares); - __instance.gameMenuView.View.BindCall("NAKRevokeContentShare", OnRevokeContentShare); - __instance.gameMenuView.View.BindCall("NAKCallShareContentDirect", OnShareContentDirect); - __instance.gameMenuView.View.BindCall("NAKGetUsersForSharing", OnGetUsersForSharing); + __instance.cohtmlView.View.BindCall("NAKCallShareContent", OnShareContent); + __instance.cohtmlView.Listener.FinishLoad += (_) => { + __instance.cohtmlView.View._view.ExecuteScript( +""" +(function waitForContentShare(){ + if (typeof ContentShare !== 'undefined') { + ContentShare.HasShareBubbles = true; + console.log('ShareBubbles patch applied'); + } else { + setTimeout(waitForContentShare, 50); + } +})(); +"""); }; - - // Add the event listener for the unshare confirmation dialog - __instance.OnUiConfirm.AddListener(OnReleaseContentShareConfirmation); - - return; + return; void OnShareContent( - string action, - string bubbleImpl, - string bubbleContent, - string shareRule, + string action, + string bubbleImpl, + string bubbleContent, + string shareRule, string shareLifetime, string shareAccess, string contentImage, @@ -1492,21 +96,21 @@ ContentShareMod.init(); // ShareRule: Public, FriendsOnly // ShareLifetime: TwoMinutes, Session // ShareAccess: PermanentAccess, SessionAccess, NoAccess - + ShareRule rule = shareRule switch { "Everyone" => ShareRule.Everyone, "FriendsOnly" => ShareRule.FriendsOnly, _ => ShareRule.Everyone }; - + ShareLifetime lifetime = shareLifetime switch { "Session" => ShareLifetime.Session, "TwoMinutes" => ShareLifetime.TwoMinutes, _ => ShareLifetime.TwoMinutes }; - + ShareAccess access = shareAccess switch { "Permanent" => ShareAccess.Permanent, @@ -1536,196 +140,9 @@ ContentShareMod.init(); ShareBubbleManager.Instance.SelectBubbleForPlace(contentImage, contentName, bubbleData); break; } - + // Close menu ViewManager.Instance.UiStateToggle(false); } - - void OnReleaseContentShareConfirmation(string id, string value, string contentId) - { - // Check if the confirmation event is for unsharing content - if (id != UiConfirmId_ReleaseAvatarShareWarning - && id != UiConfirmId_ReleasePropShareWarning) - return; - - //ShareBubblesMod.Logger.Msg($"Unshare confirmation received: {id}, {value}"); - - // Check if the user confirmed the unshare action - if (value != "true") - { - //ShareBubblesMod.Logger.Msg("Unshare action cancelled by user"); - return; - } - - //ShareBubblesMod.Logger.Msg("Releasing share..."); - - // Determine the content type based on the confirmation ID - ShareApiHelper.ShareContentType contentType = id == UiConfirmId_ReleaseAvatarShareWarning - ? ShareApiHelper.ShareContentType.Avatar - : ShareApiHelper.ShareContentType.Spawnable; - - Task.Run(async () => { - try - { - await ShareApiHelper.ReleaseShareAsync(contentType, contentId); - MTJobManager.RunOnMainThread("release_share_response", () => - { - // Cannot display a success message as opening details page pushes itself to top - // after talking to api, so success message would need to be timed to show after - // if (contentType == ApiShareHelper.ShareContentType.Avatar) - // ViewManager.Instance.RequestAvatarDetailsPage(contentId); - // else - // ViewManager.Instance.GetPropDetails(contentId); - - ViewManager.Instance.gameMenuView.View._view.TriggerEvent( - contentType == ShareApiHelper.ShareContentType.Avatar - ? "OnReleasedAvatarShare" : "OnReleasedPropShare", - contentId); - - ViewManager.Instance.TriggerPushNotification("Content unshared successfully", 3f); - }); - } - catch (ShareApiException ex) - { - ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}"); - MTJobManager.RunOnMainThread("release_share_error", () => { - ViewManager.Instance.TriggerAlert("Release Share Error", ex.UserFriendlyMessage, -1, true); - }); - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Unexpected error releasing share: {ex.Message}"); - MTJobManager.RunOnMainThread("release_share_error", () => { - ViewManager.Instance.TriggerAlert("Release Share Error", "An unexpected error occurred", -1, true); - }); - } - }); - } - - async void OnGetContentShares(string contentType, string contentId) - { - try - { - var response = await ShareApiHelper.GetSharesAsync>( - contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable, - contentId - ); - - // TODO: somethign better than this cause this is ass and i need to replace the image urls with ImageCache coui ones - // FUICJK< - string json = JsonConvert.SerializeObject(response.Data); - - // log the json to console - //ShareBubblesMod.Logger.Msg($"Shares response: {json}"); - - __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", true, json); - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Failed to get content shares: {ex.Message}"); - __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", false); - } - } - - async void OnRevokeContentShare(string contentType, string contentId, string userId) - { - try - { - await ShareApiHelper.ReleaseShareAsync( - contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable, - contentId, - userId - ); - - __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", true, userId); - } - catch (ShareApiException ex) - { - ShareBubblesMod.Logger.Error($"Share API error revoking share: {ex.Message}"); - __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, ex.UserFriendlyMessage); - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Unexpected error revoking share: {ex.Message}"); - __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, "An unexpected error occurred"); - } - } - - async void OnShareContentDirect(string contentType, string contentId, string userId, string contentName = "", string contentImage = "") - { - try - { - ShareApiHelper.ShareContentType shareContentType = contentType == "Avatar" - ? ShareApiHelper.ShareContentType.Avatar - : ShareApiHelper.ShareContentType.Spawnable; - - await ShareApiHelper.ShareContentAsync( - shareContentType, - contentId, - userId - ); - - // Alert the user that the share occurred - //ModNetwork.SendDirectShareNotification(userId, shareContentType, contentId); - - __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", true, userId); - } - catch (ShareApiException ex) - { - ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}"); - __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, ex.UserFriendlyMessage); - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Unexpected error sharing content: {ex.Message}"); - __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, "An unexpected error occurred"); - } - } - void OnGetUsersForSharing(string searchTerm = "") - { - try - { - if (!string.IsNullOrEmpty(searchTerm)) - { - // TODO: Search users implementation will go here - // For now just return an empty list - var response = new { entries = new List() }; - string json = JsonConvert.SerializeObject(response); - __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", true, json, false); - } - else - { - // Get instance users - CVRPlayerManager playerManager = CVRPlayerManager.Instance; - if (playerManager == null) - { - __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", false, false, true); - return; - } - - var response = new - { - entries = playerManager.NetworkPlayers - .Where(p => p != null && !string.IsNullOrEmpty(p.Uuid) - && !MetaPort.Instance.blockedUserIds.Contains(p.Uuid)) // You SHOULDNT HAVE TO DO THIS, but GS dumb - .Select(p => new - { - id = p.Uuid, - name = p.Username, - image = p.ApiProfileImageUrl - }) - .ToList() - }; - - string json = JsonConvert.SerializeObject(response); - __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", true, json, true); - } - } - catch (Exception ex) - { - ShareBubblesMod.Logger.Error($"Failed to get users: {ex.Message}"); - __instance.gameMenuView.View.TriggerEvent("OnHandleUsersResponse", false, false, string.IsNullOrEmpty(searchTerm)); - } - } } } \ No newline at end of file diff --git a/ShareBubbles/Properties/AssemblyInfo.cs b/ShareBubbles/Properties/AssemblyInfo.cs index ce8f183..13d5a60 100644 --- a/ShareBubbles/Properties/AssemblyInfo.cs +++ b/ShareBubbles/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using NAK.ShareBubbles.Properties; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties; namespace NAK.ShareBubbles.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.5"; + public const string Version = "1.1.6"; public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler"; } \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs b/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs deleted file mode 100644 index 799a6a2..0000000 --- a/ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Net; - -namespace NAK.ShareBubbles.API.Exceptions; - -public class ShareApiException : Exception -{ - public HttpStatusCode StatusCode { get; } // TODO: network back status code to claiming client, to show why the request failed - public string UserFriendlyMessage { get; } - - public ShareApiException(HttpStatusCode statusCode, string message, string userFriendlyMessage) - : base(message) - { - StatusCode = statusCode; - UserFriendlyMessage = userFriendlyMessage; - } -} - -public class ContentNotSharedException : ShareApiException -{ - public ContentNotSharedException(string contentId) - : base(HttpStatusCode.BadRequest, - $"Content {contentId} is not currently shared", - "This content is not currently shared with anyone") - { - } -} - -public class ContentNotFoundException : ShareApiException -{ - public ContentNotFoundException(string contentId) - : base(HttpStatusCode.NotFound, - $"Content {contentId} not found", - "The specified content could not be found") - { - } -} - -public class UserOnlyAllowsSharesFromFriendsException : ShareApiException -{ - public UserOnlyAllowsSharesFromFriendsException(string userId) - : base(HttpStatusCode.Forbidden, - $"User {userId} only accepts shares from friends", - "This user only accepts shares from friends") - { - } -} - -public class UserNotFoundException : ShareApiException -{ - public UserNotFoundException(string userId) - : base(HttpStatusCode.NotFound, - $"User {userId} not found", - "The specified user could not be found") - { - } -} - -public class ContentAlreadySharedException : ShareApiException -{ - public ContentAlreadySharedException(string contentId, string userId) - : base(HttpStatusCode.Conflict, - $"Content {contentId} is already shared with user {userId}", - "This content is already shared with this user") - { - } -} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs index 967734b..43287cc 100644 --- a/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs +++ b/ShareBubbles/ShareBubbles/API/PedestalInfoBatchProcessor.cs @@ -1,6 +1,5 @@ using ABI_RC.Core.Networking.API; using ABI_RC.Core.Networking.API.Responses; -using NAK.ShareBubbles.API.Responses; namespace NAK.ShareBubbles.API; @@ -34,6 +33,8 @@ public static class PedestalInfoBatchProcessor { PedestalType.Prop, false } }; + // This breaks compile accepting this change. + // ReSharper disable once ChangeFieldTypeToSystemThreadingLock private static readonly object _lock = new(); private const float BATCH_DELAY = 2f; diff --git a/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs b/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs deleted file mode 100644 index 70dc731..0000000 --- a/ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace NAK.ShareBubbles.API.Responses; - -public class ActiveSharesResponse -{ - [JsonProperty("value")] - public List Value { get; set; } -} - -// Idk why not just reuse UserDetails -public class ShareUser -{ - [JsonProperty("image")] - public string Image { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } -} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs b/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs deleted file mode 100644 index c338930..0000000 --- a/ShareBubbles/ShareBubbles/API/ShareApiHelper.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web; -using ABI_RC.Core.Networking; -using ABI_RC.Core.Networking.API; -using ABI_RC.Core.Networking.API.Responses; -using ABI_RC.Core.Savior; -using NAK.ShareBubbles.API.Exceptions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace NAK.ShareBubbles.API; - -/// -/// API for content sharing management. -/// -public static class ShareApiHelper -{ - #region Enums - - public enum ShareContentType - { - Avatar, - Spawnable - } - - private enum ShareApiOperation - { - ShareAvatar, - ReleaseAvatar, - - ShareSpawnable, - ReleaseSpawnable, - - GetAvatarShares, - GetSpawnableShares - } - - #endregion Enums - - #region Public API - - /// - /// Shares content with a specified user. - /// - /// Type of content to share - /// ID of the content - /// Target user ID - /// Response containing share information - /// Thrown when API request fails - /// Thrown when target user is not found - /// Thrown when content is not found - /// Thrown when user only accepts shares from friends - /// Thrown when content is already shared with user - public static Task> ShareContentAsync(ShareContentType type, string contentId, string userId) - { - ShareApiOperation operation = type == ShareContentType.Avatar - ? ShareApiOperation.ShareAvatar - : ShareApiOperation.ShareSpawnable; - - ShareRequest data = new() - { - ContentId = contentId, - UserId = userId - }; - - return MakeApiRequestAsync(operation, data); - } - - /// - /// Releases shared content from a specified user. - /// - /// Type of content to release - /// ID of the content - /// Optional user ID. If null, releases share from self - /// Response indicating success - /// Thrown when API request fails - /// Thrown when content is not shared - /// Thrown when content is not found - /// Thrown when specified user is not found - public static Task> ReleaseShareAsync(ShareContentType type, string contentId, string userId = null) - { - ShareApiOperation operation = type == ShareContentType.Avatar - ? ShareApiOperation.ReleaseAvatar - : ShareApiOperation.ReleaseSpawnable; - - // If no user ID is provided, release share from self - userId ??= MetaPort.Instance.ownerId; - - ShareRequest data = new() - { - ContentId = contentId, - UserId = userId - }; - - return MakeApiRequestAsync(operation, data); - } - - /// - /// Gets all shares for specified content. - /// - /// Type of content - /// ID of the content - /// Response containing share information - /// Thrown when API request fails - /// Thrown when content is not found - public static Task> GetSharesAsync(ShareContentType type, string contentId) - { - ShareApiOperation operation = type == ShareContentType.Avatar - ? ShareApiOperation.GetAvatarShares - : ShareApiOperation.GetSpawnableShares; - - ShareRequest data = new() { ContentId = contentId }; - return MakeApiRequestAsync(operation, data); - } - - #endregion Public API - - #region Private Implementation - - [Serializable] - private record ShareRequest - { - public string ContentId { get; set; } - public string UserId { get; set; } - } - - private static async Task> MakeApiRequestAsync(ShareApiOperation operation, ShareRequest data) - { - ValidateAuthenticationState(); - - (string endpoint, HttpMethod method) = GetApiEndpointAndMethod(operation, data); - using HttpRequestMessage request = CreateHttpRequest(endpoint, method, data); - - try - { - using HttpResponseMessage response = await ApiConnection._client.SendAsync(request); - string content = await response.Content.ReadAsStringAsync(); - - return HandleApiResponse(response, content, data.ContentId, data.UserId); - } - catch (HttpRequestException ex) - { - throw new ShareApiException( - HttpStatusCode.ServiceUnavailable, - $"Failed to communicate with the server: {ex.Message}", - "Unable to connect to the server. Please check your internet connection."); - } - catch (JsonException ex) - { - throw new ShareApiException( - HttpStatusCode.UnprocessableEntity, - $"Failed to process response data: {ex.Message}", - "Server returned invalid data. Please try again later."); - } - } - - private static void ValidateAuthenticationState() - { - if (!AuthManager.IsAuthenticated) - { - throw new ShareApiException( - HttpStatusCode.Unauthorized, - "User is not authenticated", - "Please log in to perform this action"); - } - } - - private static HttpRequestMessage CreateHttpRequest(string endpoint, HttpMethod method, ShareRequest data) - { - HttpRequestMessage request = new(method, endpoint); - - if (method == HttpMethod.Post) - { - JObject json = JObject.FromObject(data); - request.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json"); - } - - return request; - } - - private static BaseResponse HandleApiResponse(HttpResponseMessage response, string content, string contentId, string userId) - { - if (response.IsSuccessStatusCode) - return CreateSuccessResponse(content); - - // Let specific exceptions propagate up to the caller - throw response.StatusCode switch - { - HttpStatusCode.BadRequest => new ContentNotSharedException(contentId), - HttpStatusCode.NotFound when userId != null => new UserNotFoundException(userId), - HttpStatusCode.NotFound => new ContentNotFoundException(contentId), - HttpStatusCode.Forbidden => new UserOnlyAllowsSharesFromFriendsException(userId), - HttpStatusCode.Conflict => new ContentAlreadySharedException(contentId, userId), - _ => new ShareApiException( - response.StatusCode, - $"API request failed with status {response.StatusCode}: {content}", - "An unexpected error occurred. Please try again later.") - }; - } - - private static BaseResponse CreateSuccessResponse(string content) - { - var response = new BaseResponse("") - { - IsSuccessStatusCode = true, - HttpStatusCode = HttpStatusCode.OK - }; - - if (!string.IsNullOrEmpty(content)) - { - response.Data = JsonConvert.DeserializeObject(content); - } - - return response; - } - - private static (string endpoint, HttpMethod method) GetApiEndpointAndMethod(ShareApiOperation operation, ShareRequest data) - { - string baseUrl = $"{ApiConnection.APIAddress}/{ApiConnection.APIVersion}"; - string encodedContentId = HttpUtility.UrlEncode(data.ContentId); - - return operation switch - { - ShareApiOperation.GetAvatarShares => ($"{baseUrl}/avatars/{encodedContentId}/shares", HttpMethod.Get), - ShareApiOperation.ShareAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post), - ShareApiOperation.ReleaseAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete), - ShareApiOperation.GetSpawnableShares => ($"{baseUrl}/spawnables/{encodedContentId}/shares", HttpMethod.Get), - ShareApiOperation.ShareSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post), - ShareApiOperation.ReleaseSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete), - _ => throw new ArgumentException($"Unknown operation: {operation}") - }; - } - - #endregion Private Implementation -} \ No newline at end of file diff --git a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs index b79f0c8..1c9e3de 100644 --- a/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/AvatarBubbleImpl.cs @@ -1,10 +1,10 @@ using ABI_RC.Core.EventSystem; using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.Exceptions; using ABI_RC.Core.Networking.API.UserWebsocket; -using ABI_RC.Core.Savior; using NAK.ShareBubbles.API; -using NAK.ShareBubbles.API.Exceptions; using ShareBubbles.ShareBubbles.Implementation; using UnityEngine; using Object = UnityEngine.Object; diff --git a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs index 813222c..9de2b05 100644 --- a/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs +++ b/ShareBubbles/ShareBubbles/Implementation/SpawnableBubbleImpl.cs @@ -1,10 +1,10 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.Exceptions; using ABI_RC.Core.Networking.API.UserWebsocket; using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; using NAK.ShareBubbles.API; -using NAK.ShareBubbles.API.Exceptions; using ShareBubbles.ShareBubbles.Implementation; using UnityEngine; using Object = UnityEngine.Object; diff --git a/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs b/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs index d35810a..f0425e5 100644 --- a/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs +++ b/ShareBubbles/ShareBubbles/Implementation/TempShareManager.cs @@ -1,8 +1,8 @@ -using ABI_RC.Core.Networking.API.UserWebsocket; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.API.UserWebsocket; using ABI_RC.Core.Networking.IO.Instancing; using ABI_RC.Core.Player; using ABI_RC.Systems.GameEventSystem; -using NAK.ShareBubbles.API; using Newtonsoft.Json; using UnityEngine; diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs index a9caec4..cdf302c 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Inbound.cs @@ -1,7 +1,7 @@ -using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Networking.API; +using ABI_RC.Core.Networking.IO.Social; using ABI_RC.Core.Savior; using ABI_RC.Systems.ModNetwork; -using NAK.ShareBubbles.API; using UnityEngine; namespace NAK.ShareBubbles.Networking; diff --git a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs index a09e0e1..16b065c 100644 --- a/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs +++ b/ShareBubbles/ShareBubbles/Networking/ModNetwork.Outbound.cs @@ -1,5 +1,5 @@ -using ABI_RC.Systems.ModNetwork; -using NAK.ShareBubbles.API; +using ABI_RC.Core.Networking.API; +using ABI_RC.Systems.ModNetwork; using UnityEngine; namespace NAK.ShareBubbles.Networking; diff --git a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs index ad2b4f4..ea5f369 100644 --- a/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs +++ b/ShareBubbles/ShareBubbles/UI/BubbleInteract.cs @@ -1,6 +1,8 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.InteractionSystem.Base; using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.InputManagement; using UnityEngine; namespace NAK.ShareBubbles.UI; @@ -9,11 +11,23 @@ namespace NAK.ShareBubbles.UI; // Must be added manually by ShareBubble creation... public class BubbleInteract : Interactable { - public override bool IsInteractableWithinRange(Vector3 sourcePos) + public override bool IsInteractable { - return Vector3.Distance(transform.position, sourcePos) < 1.5f; + get + { + if (ViewManager.Instance.IsAnyMenuOpen) + return true; + + if (!MetaPort.Instance.isUsingVr + && CVRInputManager.Instance.unlockMouse) + return true; + + return false; + } } + public override bool IsInteractableWithinRange(Vector3 sourcePos) => true; + public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay) { // Not used diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index d2028d6..05b88e6 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -1,9 +1,9 @@ { "_id": 244, "name": "ShareBubbles", - "modversion": "1.0.5", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.1.6", + "gameversion": "2025r80", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", "description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n- Adds basic in-game Share Management:\n - Directly share content to users within the current instance.\n - Revoke granted or received content shares.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ABI Account settings!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/", - "changelog": "- Fixes for 2025r179\n- Fixed Public/Private text on bubble not being correct\n- Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From e378a717d3e3ed8472c6b454bba8282306fe3045 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:34:09 -0500 Subject: [PATCH 171/188] [RCCVirtualSteeringWheel] Fixes for 2025r180 --- RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs | 4 ++-- .../Components/SteeringWheelPickup.cs | 4 ++-- RCCVirtualSteeringWheel/format.json | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs index 38d7ecc..03e9324 100644 --- a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs +++ b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs @@ -18,7 +18,7 @@ using NAK.RCCVirtualSteeringWheel.Properties; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -28,6 +28,6 @@ using NAK.RCCVirtualSteeringWheel.Properties; namespace NAK.RCCVirtualSteeringWheel.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.6"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs index 7981d63..91495dd 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs @@ -26,8 +26,8 @@ public class SteeringWheelPickup : Pickupable public override void OnUseDown(InteractionContext context) { } public override void OnUseUp(InteractionContext context) { } - public override void OnFlingTowardsTarget(Vector3 target) { } - + public override void FlingTowardsTarget(ControllerRay controllerRay) { } + public override void OnGrab(InteractionContext context, Vector3 grabPoint) { if (ControllerRay?.pivotPoint != null) root.StartTrackingTransform(ControllerRay.transform); diff --git a/RCCVirtualSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json index c1c44c8..78825b0 100644 --- a/RCCVirtualSteeringWheel/format.json +++ b/RCCVirtualSteeringWheel/format.json @@ -1,9 +1,9 @@ { "_id": 248, "name": "RCCVirtualSteeringWheel", - "modversion": "1.0.4", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.6", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/", - "changelog": "- Recompiled for 2025r179\n- Fixed generated steering wheel pickup collider not being marked isTrigger\n- Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From aaeb187b9e16549f325a7871052c10e51119236f Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:34:19 -0500 Subject: [PATCH 172/188] [PropUndoButton] Fixes for 2025r180 --- PropUndoButton/Main.cs | 12 ++++++------ PropUndoButton/Properties/AssemblyInfo.cs | 4 ++-- PropUndoButton/format.json | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/PropUndoButton/Main.cs b/PropUndoButton/Main.cs index f0c0c67..c3c6337 100644 --- a/PropUndoButton/Main.cs +++ b/PropUndoButton/Main.cs @@ -16,17 +16,17 @@ namespace NAK.PropUndoButton; public class PropUndoButton : MelonMod { - public static readonly MelonPreferences_Category Category = + private static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(PropUndoButton)); - public static readonly MelonPreferences_Entry EntryEnabled = + private static readonly MelonPreferences_Entry EntryEnabled = Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button."); - public static readonly MelonPreferences_Entry EntryUseSFX = + private static readonly MelonPreferences_Entry EntryUseSFX = Category.CreateEntry("Use SFX", true, description: "Toggle audio queues for prop spawn, undo, redo, and warning."); - internal static List deletedProps = new(); + private static readonly List deletedProps = []; // audio clip names, InterfaceAudio adds "PropUndo_" prefix private const string sfx_spawn = "PropUndo_sfx_spawn"; @@ -92,11 +92,11 @@ public class PropUndoButton : MelonMod if (!File.Exists(clipPath)) { // read the clip data from embedded resources - byte[] clipData = null; + byte[] clipData; var resourceName = "PropUndoButton.SFX." + clipName; using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { - clipData = new byte[stream.Length]; + clipData = new byte[stream!.Length]; stream.Read(clipData, 0, clipData.Length); } diff --git a/PropUndoButton/Properties/AssemblyInfo.cs b/PropUndoButton/Properties/AssemblyInfo.cs index ca45627..3849345 100644 --- a/PropUndoButton/Properties/AssemblyInfo.cs +++ b/PropUndoButton/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/UndoPropButton" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.PropUndoButton.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PropUndoButton/format.json b/PropUndoButton/format.json index 32227a9..721593d 100644 --- a/PropUndoButton/format.json +++ b/PropUndoButton/format.json @@ -1,9 +1,9 @@ { "_id": 147, "name": "PropUndoButton", - "modversion": "1.0.3", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.4", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "**CTRL+Z** to undo latest spawned prop. **CTRL+SHIFT+Z** to redo deleted prop.\nIncludes optional SFX for prop spawn, undo, redo, warn, and deny, which can be disabled in settings.\n\nYou can replace the sfx in 'ChilloutVR\\ChilloutVR_Data\\StreamingAssets\\Cohtml\\UIResources\\GameUI\\mods\\PropUndo\\audio'.", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropUndoButton.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/PropUndoButton.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PropUndoButton/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Fixes for 2025r180", "embedcolor": "#00FFFF" } \ No newline at end of file From 07daceea44f770b177b894073f97f513458e6a44 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:35:04 -0500 Subject: [PATCH 173/188] [ScrollFlight] Fixes for 2025r180 --- ScrollFlight/Main.cs | 3 --- ScrollFlight/Properties/AssemblyInfo.cs | 4 ++-- ScrollFlight/format.json | 10 +++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/ScrollFlight/Main.cs b/ScrollFlight/Main.cs index f2180b8..4cce7f0 100644 --- a/ScrollFlight/Main.cs +++ b/ScrollFlight/Main.cs @@ -1,10 +1,7 @@ using System.Globalization; -using System.Reflection; using ABI_RC.Core.UI; -using ABI_RC.Systems.IK.VRIKHandlers; using ABI_RC.Systems.Movement; using ABI.CCK.Components; -using HarmonyLib; using MelonLoader; using UnityEngine; diff --git a/ScrollFlight/Properties/AssemblyInfo.cs b/ScrollFlight/Properties/AssemblyInfo.cs index 64e0133..22630c9 100644 --- a/ScrollFlight/Properties/AssemblyInfo.cs +++ b/ScrollFlight/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ScrollFlight.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/ScrollFlight/format.json b/ScrollFlight/format.json index 3cc60bd..486ad34 100644 --- a/ScrollFlight/format.json +++ b/ScrollFlight/format.json @@ -1,9 +1,9 @@ { "_id": 219, "name": "ScrollFlight", - "modversion": "1.0.3", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.4", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Scroll-wheel to adjust flight speed in Desktop. Stole idea from Luc.", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ScrollFlight.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ScrollFlight.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight/", - "changelog": "- Recompiled for 2025r179\n- Added an option to reset flight speed to default on exit flight", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From 218344a12179ac39d0a1902945fa8e952146abcf Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:41:03 -0500 Subject: [PATCH 174/188] [ShareBubbles] Updated format.json --- ShareBubbles/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index 05b88e6..2dcf79f 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -2,7 +2,7 @@ "_id": 244, "name": "ShareBubbles", "modversion": "1.1.6", - "gameversion": "2025r80", + "gameversion": "2025r180", "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", From 1a591749c60e053bf9e666181ea48dd8ae3e369a Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:42:54 -0500 Subject: [PATCH 175/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] Update format.json --- YouAreMyPropNowWeAreHavingSoftTacosLater/format.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json index b88a58b..c59e4aa 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/", - "changelog": "- Initial Release", - "embedcolor": "#00FFFF" + "changelog": "- Initial release", + "embedcolor": "#f61963" } \ No newline at end of file From 7deddd88eaf021336c54f3a2040e33d9be06add4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:47:06 -0500 Subject: [PATCH 176/188] [CustomSpawnPoint] Fixes for 2025r180 --- CustomSpawnPoint/Properties/AssemblyInfo.cs | 4 ++-- CustomSpawnPoint/SpawnPointManager.cs | 14 +++++++------- CustomSpawnPoint/format.json | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CustomSpawnPoint/Properties/AssemblyInfo.cs b/CustomSpawnPoint/Properties/AssemblyInfo.cs index 3676fa0..d1887b3 100644 --- a/CustomSpawnPoint/Properties/AssemblyInfo.cs +++ b/CustomSpawnPoint/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.CustomSpawnPoint.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.3"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/CustomSpawnPoint/SpawnPointManager.cs b/CustomSpawnPoint/SpawnPointManager.cs index 350d07c..78c81dd 100644 --- a/CustomSpawnPoint/SpawnPointManager.cs +++ b/CustomSpawnPoint/SpawnPointManager.cs @@ -41,20 +41,20 @@ internal static class SpawnPointManager { while (ViewManager.Instance == null) yield return null; - while (ViewManager.Instance.gameMenuView == null) + while (ViewManager.Instance.cohtmlView == null) yield return null; - while (ViewManager.Instance.gameMenuView.Listener == null) + while (ViewManager.Instance.cohtmlView.Listener == null) yield return null; ViewManager.Instance.OnUiConfirm.AddListener(OnClearSpawnpointConfirm); - ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) => + ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) => { - ViewManager.Instance.gameMenuView.View._view.ExecuteScript(spawnpointJs); + ViewManager.Instance.cohtmlView.View._view.ExecuteScript(spawnpointJs); }; - ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () => + ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () => { // listen for setting the spawn point on our custom button - ViewManager.Instance.gameMenuView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint); + ViewManager.Instance.cohtmlView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint); }; // create our custom spawn point object @@ -179,7 +179,7 @@ internal static class SpawnPointManager private static void UpdateMenuButtonState(bool hasSpawnpoint, bool isInWorld) { - ViewManager.Instance.gameMenuView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString()); + ViewManager.Instance.cohtmlView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString()); } private static void ClearCurrentWorldState() diff --git a/CustomSpawnPoint/format.json b/CustomSpawnPoint/format.json index 98f22da..52fe5e3 100644 --- a/CustomSpawnPoint/format.json +++ b/CustomSpawnPoint/format.json @@ -1,9 +1,9 @@ { "_id": 228, "name": "CustomSpawnPoint", - "modversion": "1.0.2", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.3", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Replaces the unused Images button in the World Details page with a button to set a custom spawn point.", @@ -17,8 +17,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/CustomSpawnPoint.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From 969bd00df34796dad9c8dfdac8a22e2df5571ee3 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:57:04 -0500 Subject: [PATCH 177/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] Updated format.json --- YouAreMyPropNowWeAreHavingSoftTacosLater/format.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json index c59e4aa..c25dfa5 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -1,12 +1,12 @@ { - "_id": -1, + "_id": 262, "name": "YouAreMyPropNowWeAreHavingSoftTacosLater", "modversion": "1.0.0", "gameversion": "2025r180", "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", - "description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO", + "description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 10m above the spawnpoint of the next world.", "searchtags": [ "prop", "spawn", From 6d28f734da330f6de9d2c9c8e7c097277fe409b8 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 20 Aug 2025 00:04:03 -0500 Subject: [PATCH 178/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] Updated format.json --- YouAreMyPropNowWeAreHavingSoftTacosLater/format.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json index c25dfa5..8e68782 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", - "description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 10m above the spawnpoint of the next world.", + "description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 20m above the spawnpoint of the next world.", "searchtags": [ "prop", "spawn", From 2f668b289ccee2a23c9f6f6ccd1610c80560124e Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:09:14 -0500 Subject: [PATCH 179/188] [YouAreMyPropNowWeAreHavingSoftTacosLater] Updated README.md --- YouAreMyPropNowWeAreHavingSoftTacosLater/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md index 9ddff99..b092e40 100644 --- a/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md @@ -1,9 +1,12 @@ # YouAreMyPropNowWeAreHavingSoftTacosLater -Lets you bring held & attached props through world loads. +Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO +There is special logic in place for bringing air vehicles through world loads. +If above the ground you will be placed up to 20m above the spawnpoint of the next world. + ## Examples https://fixupx.com/NotAKidoS/status/1910545346922422675 From 6ad23e6fc52fc929d68c6049a8ac8ab65e1bdc4e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 20 Aug 2025 17:09:37 +0000 Subject: [PATCH 180/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93d8d1e..76534fa 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ | [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll) | | [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll) | | [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll) | -| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held & attached props through world loads. | No Download | +| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | No Download | ### Experimental Mods From 6dc7f8a26752544ff3826fe8b54919550a9c0cab Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:31:04 -0500 Subject: [PATCH 181/188] [ShareBubbles] Updated format.json --- ShareBubbles/README.md | 3 ++- ShareBubbles/format.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ShareBubbles/README.md b/ShareBubbles/README.md index f56f04f..650f790 100644 --- a/ShareBubbles/README.md +++ b/ShareBubbles/README.md @@ -28,7 +28,8 @@ While viewing content details of an item shared *to you* by another player, you Access Control is only available for **Private Content** and serves as a way to share content in-game. -**Note:** Session Access requires the game to be running to revoke access once you or the claimant leaves the instance. If the game is closed unexpectedly, the claimant will keep the content until you next launch the game and connect to an online instance. +**Note:** Session Access requires the game to be running to revoke access once you or the claimant leaves the instance. +If the game is closed unexpectedly, the claimant will keep the content until you next launch the game and connect to an online instance. ## Credits - Noachi - the bubble diff --git a/ShareBubbles/format.json b/ShareBubbles/format.json index 2dcf79f..4c07d5d 100644 --- a/ShareBubbles/format.json +++ b/ShareBubbles/format.json @@ -6,7 +6,7 @@ "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc", - "description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n- Adds basic in-game Share Management:\n - Directly share content to users within the current instance.\n - Revoke granted or received content shares.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ABI Account settings!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).", + "description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ChilloutVR Account settings on the Hub!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).", "searchtags": [ "share", "bubbles", From 4123a1f25d9047137bc764adcaf4bc923835c2cf Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:23:37 -0500 Subject: [PATCH 182/188] [ASTExtension] Fixes for 2025r180 --- ASTExtension/Extensions/PlayerSetupExtensions.cs | 4 ++-- ASTExtension/Integrations/BTKUI/BtkUiAddon.cs | 6 +++--- ASTExtension/Main.cs | 15 +++++++++------ ASTExtension/Properties/AssemblyInfo.cs | 4 ++-- ASTExtension/format.json | 10 +++++----- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/ASTExtension/Extensions/PlayerSetupExtensions.cs b/ASTExtension/Extensions/PlayerSetupExtensions.cs index 7fa9e5b..661a94a 100644 --- a/ASTExtension/Extensions/PlayerSetupExtensions.cs +++ b/ASTExtension/Extensions/PlayerSetupExtensions.cs @@ -9,13 +9,13 @@ public static class PlayerSetupExtensions // immediate measurement of the player's avatar height public static float GetCurrentAvatarHeight(this PlayerSetup playerSetup) { - if (playerSetup._avatar == null) + if (!playerSetup.IsAvatarLoaded) { ASTExtensionMod.Logger.Error("GetCurrentAvatarHeight: Avatar is null"); return 0f; } - Vector3 localScale = playerSetup._avatar.transform.localScale; + Vector3 localScale = playerSetup.AvatarTransform.localScale; Vector3 initialScale = playerSetup.initialScale; float initialHeight = playerSetup._initialAvatarHeight; Vector3 scaleDifference = CVRTools.DivideVectors(localScale - initialScale, initialScale); diff --git a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs index e0e294a..e075e88 100644 --- a/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs +++ b/ASTExtension/Integrations/BTKUI/BtkUiAddon.cs @@ -49,7 +49,7 @@ public static partial class BtkUiAddon if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player)) return; - if (player._avatar == null) + if (!player.IsAvatarLoaded) return; float height = player.netIkController.GetRemoteHeight(); @@ -64,8 +64,8 @@ public static partial class BtkUiAddon if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player)) return; - AvatarAnimatorManager localAnimator = PlayerSetup.Instance.animatorManager; - AvatarAnimatorManager remoteAnimator = player.animatorManager; + AvatarAnimatorManager localAnimator = PlayerSetup.Instance.AnimatorManager; + AvatarAnimatorManager remoteAnimator = player.AnimatorManager; if (!localAnimator.IsInitialized || !remoteAnimator.IsInitialized) return; diff --git a/ASTExtension/Main.cs b/ASTExtension/Main.cs index 461dc16..4ddf871 100644 --- a/ASTExtension/Main.cs +++ b/ASTExtension/Main.cs @@ -81,7 +81,7 @@ public class ASTExtensionMod : MelonMod ); HarmonyInstance.Patch( - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar), + typeof(PlayerBase).GetMethod(nameof(PlayerBase.ClearAvatar), BindingFlags.Public | BindingFlags.Instance), prefix: new HarmonyMethod(typeof(ASTExtensionMod).GetMethod(nameof(OnClearAvatar), BindingFlags.NonPublic | BindingFlags.Static)) @@ -112,8 +112,11 @@ public class ASTExtensionMod : MelonMod Instance.OnLocalAvatarLoad(); } - private static void OnClearAvatar(ref CVRAvatar ____avatarDescriptor) - => Instance.OnLocalAvatarClear(____avatarDescriptor); + private static void OnClearAvatar(ref PlayerBase __instance) + { + if (!__instance.IsLocalPlayer) return; + Instance.OnLocalAvatarClear(__instance.AvatarDescriptor); + } #endregion Harmony Patches @@ -227,7 +230,7 @@ public class ASTExtensionMod : MelonMod { parameterName = null; - AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager; + AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager; if (!animatorManager.IsInitialized) { Logger.Error("AnimatorManager is not initialized!"); @@ -254,7 +257,7 @@ public class ASTExtensionMod : MelonMod maxHeight = 0f; modifier = 1f; - AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager; + AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager; if (!animatorManager.IsInitialized) { Logger.Error("AnimatorManager is not initialized!"); @@ -319,7 +322,7 @@ public class ASTExtensionMod : MelonMod if (!_currentAvatarSupported) return; - AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager; + AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager; if (!animatorManager.IsInitialized) { Logger.Error("AnimatorManager is not initialized!"); diff --git a/ASTExtension/Properties/AssemblyInfo.cs b/ASTExtension/Properties/AssemblyInfo.cs index 0200119..2e4d7b4 100644 --- a/ASTExtension/Properties/AssemblyInfo.cs +++ b/ASTExtension/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.ASTExtension.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/ASTExtension/format.json b/ASTExtension/format.json index 934110a..abcdbfc 100644 --- a/ASTExtension/format.json +++ b/ASTExtension/format.json @@ -1,9 +1,9 @@ { "_id": 223, "name": "ASTExtension", - "modversion": "1.0.3", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.4", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.", @@ -17,8 +17,8 @@ "requirements": [ "BTKUILib" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ASTExtension.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From c9acb000882dc5673246425e155a4ce59a9c4ed5 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:23:45 -0500 Subject: [PATCH 183/188] [DoubleTapJumpToExitSeat] Fixes for 2025r180 --- DoubleTapJumpToExitSeat/Main.cs | 4 ++-- DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs | 4 ++-- DoubleTapJumpToExitSeat/format.json | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/DoubleTapJumpToExitSeat/Main.cs b/DoubleTapJumpToExitSeat/Main.cs index c3b5a6f..18dd9fb 100644 --- a/DoubleTapJumpToExitSeat/Main.cs +++ b/DoubleTapJumpToExitSeat/Main.cs @@ -73,8 +73,8 @@ public class DoubleTapJumpToExitSeatMod : MelonMod // Steal sync if (__instance.lockControls) { - if (__instance._spawnable != null) __instance._spawnable.ForceUpdate(4); - if (__instance._objectSync != null) __instance._objectSync.ForceUpdate(4); + if (__instance._spawnable) __instance._spawnable.ForceUpdate(4); + if (__instance._objectSync) __instance._objectSync.ForceUpdate(4); } return false; // don't call original method diff --git a/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs b/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs index fd30036..2e85366 100644 --- a/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs +++ b/DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.DoubleTapJumpToExitSeat.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/DoubleTapJumpToExitSeat/format.json b/DoubleTapJumpToExitSeat/format.json index ab8e14e..e14365c 100644 --- a/DoubleTapJumpToExitSeat/format.json +++ b/DoubleTapJumpToExitSeat/format.json @@ -1,9 +1,9 @@ { - "_id": -1, + "_id": 255, "name": "DoubleTapJumpToExitSeat", - "modversion": "1.0.0", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.1", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu.", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/DoubleTapJumpToExitSeat.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat/", - "changelog": "- Initial release", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From 30c069388c0e76414f2e152df9413d7c5be29311 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:23:56 -0500 Subject: [PATCH 184/188] [PathCamDisabler] Fixes for 2025r180 --- PathCamDisabler/Properties/AssemblyInfo.cs | 4 ++-- PathCamDisabler/format.json | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/PathCamDisabler/Properties/AssemblyInfo.cs b/PathCamDisabler/Properties/AssemblyInfo.cs index 8e01437..5dedc26 100644 --- a/PathCamDisabler/Properties/AssemblyInfo.cs +++ b/PathCamDisabler/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ using System.Reflection; downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PathCamDisabler" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.PathCamDisabler.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.3"; + public const string Version = "1.0.4"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PathCamDisabler/format.json b/PathCamDisabler/format.json index 6a8d8df..44362ba 100644 --- a/PathCamDisabler/format.json +++ b/PathCamDisabler/format.json @@ -1,9 +1,9 @@ { "_id": 110, "name": "PathCamDisabler", - "modversion": "1.0.3", - "gameversion": "2025r179", - "loaderversion": "0.6.1", + "modversion": "1.0.4", + "gameversion": "2025r180", + "loaderversion": "0.7.2", "modtype": "Mod", "author": "NotAKidoS", "description": "Adds option to disable the Path Camera Controller to free up your numkeys.\nAdditional option to disable flight binding.", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PathCamDisabler.dll", + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/PathCamDisabler.dll", "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PathCamDisabler/", - "changelog": "- Recompiled for 2025r179", + "changelog": "- Fixes for 2025r180", "embedcolor": "#f61963" } \ No newline at end of file From c368daab4fa132d9b33ff51b8d4a004eb4c55562 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:34:56 -0500 Subject: [PATCH 185/188] killed RelativeSync, now its just RelativeSyncJitterFix --- RelativeSync/Main.cs | 48 ---- RelativeSync/ModSettings.cs | 43 ---- RelativeSync/Networking/ModNetwork.cs | 151 ------------- RelativeSync/Patches.cs | 108 --------- RelativeSync/README.md | 30 --- .../Components/RelativeSyncController.cs | 206 ------------------ .../Components/RelativeSyncMarker.cs | 110 ---------- .../Components/RelativeSyncMonitor.cs | 79 ------- .../RelativeSync/RelativeSyncManager.cs | 71 ------ RelativeSync/format.json | 23 -- RelativeSyncJitterFix/Main.cs | 25 +++ RelativeSyncJitterFix/Patches.cs | 22 ++ .../Properties/AssemblyInfo.cs | 18 +- RelativeSyncJitterFix/README.md | 21 ++ .../RelativeSyncJitterFix.csproj | 0 RelativeSyncJitterFix/format.json | 23 ++ 16 files changed, 100 insertions(+), 878 deletions(-) delete mode 100644 RelativeSync/Main.cs delete mode 100644 RelativeSync/ModSettings.cs delete mode 100644 RelativeSync/Networking/ModNetwork.cs delete mode 100644 RelativeSync/Patches.cs delete mode 100644 RelativeSync/README.md delete mode 100644 RelativeSync/RelativeSync/Components/RelativeSyncController.cs delete mode 100644 RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs delete mode 100644 RelativeSync/RelativeSync/Components/RelativeSyncMonitor.cs delete mode 100644 RelativeSync/RelativeSync/RelativeSyncManager.cs delete mode 100644 RelativeSync/format.json create mode 100644 RelativeSyncJitterFix/Main.cs create mode 100644 RelativeSyncJitterFix/Patches.cs rename {RelativeSync => RelativeSyncJitterFix}/Properties/AssemblyInfo.cs (65%) create mode 100644 RelativeSyncJitterFix/README.md rename RelativeSync/RelativeSync.csproj => RelativeSyncJitterFix/RelativeSyncJitterFix.csproj (100%) create mode 100644 RelativeSyncJitterFix/format.json diff --git a/RelativeSync/Main.cs b/RelativeSync/Main.cs deleted file mode 100644 index 0a50472..0000000 --- a/RelativeSync/Main.cs +++ /dev/null @@ -1,48 +0,0 @@ -using MelonLoader; -using NAK.RelativeSync.Networking; -using NAK.RelativeSync.Patches; - -namespace NAK.RelativeSync; - -public class RelativeSyncMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ModNetwork.Subscribe(); - ModSettings.Initialize(); - - // Experimental sync hack - ApplyPatches(typeof(CVRSpawnablePatches)); - - // Send relative sync update after network root data update - ApplyPatches(typeof(NetworkRootDataUpdatePatches)); - - // Add components if missing (for relative sync monitor and controller) - ApplyPatches(typeof(PlayerSetupPatches)); - ApplyPatches(typeof(PuppetMasterPatches)); - - // Add components if missing (for relative sync markers) - ApplyPatches(typeof(CVRSeatPatches)); - ApplyPatches(typeof(CVRMovementParentPatches)); - - // So we run after the client moves the remote player - ApplyPatches(typeof(NetIKController_Patches)); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } -} \ No newline at end of file diff --git a/RelativeSync/ModSettings.cs b/RelativeSync/ModSettings.cs deleted file mode 100644 index f8567ad..0000000 --- a/RelativeSync/ModSettings.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MelonLoader; -using NAK.RelativeSync.Networking; - -namespace NAK.RelativeSync; - -internal static class ModSettings -{ - internal const string ModName = nameof(RelativeSync); - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - private static readonly MelonPreferences_Entry DebugLogInbound = - Category.CreateEntry("DebugLogInbound", false, - "Debug Log Inbound", description: "Log inbound network messages."); - - private static readonly MelonPreferences_Entry DebugLogOutbound = - Category.CreateEntry("DebugLogOutbound", false, - "Debug Log Outbound", description: "Log outbound network messages."); - - private static readonly MelonPreferences_Entry ExpSyncedObjectHack = - Category.CreateEntry("ExpSyncedObjectHack", true, - "Exp Spawnable Sync Fix", description: "Forces CVRSpawnable to update position in FixedUpdate. May help reduce local jitter on synced movement parents."); - - #endregion Melon Preferences - - internal static void Initialize() - { - foreach (MelonPreferences_Entry setting in Category.Entries) - setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); - - OnSettingsChanged(); - } - - private static void OnSettingsChanged(object oldValue = null, object newValue = null) - { - ModNetwork.Debug_NetworkInbound = DebugLogInbound.Value; - ModNetwork.Debug_NetworkOutbound = DebugLogOutbound.Value; - Patches.CVRSpawnablePatches.UseHack = ExpSyncedObjectHack.Value; - } -} \ No newline at end of file diff --git a/RelativeSync/Networking/ModNetwork.cs b/RelativeSync/Networking/ModNetwork.cs deleted file mode 100644 index 713f27c..0000000 --- a/RelativeSync/Networking/ModNetwork.cs +++ /dev/null @@ -1,151 +0,0 @@ -using ABI_RC.Core.Networking; -using ABI_RC.Systems.ModNetwork; -using DarkRift; -using UnityEngine; - -namespace NAK.RelativeSync.Networking; - -public static class ModNetwork -{ - public static bool Debug_NetworkInbound = false; - public static bool Debug_NetworkOutbound = false; - - private static bool _isSubscribedToModNetwork; - - private struct MovementParentSyncData - { - public bool HasSyncedThisData; - public int MarkerHash; - public Vector3 RootPosition; - public Vector3 RootRotation; - // public Vector3 HipPosition; - // public Vector3 HipRotation; - } - - private static MovementParentSyncData _latestMovementParentSyncData; - - #region Constants - - private const string ModId = "MelonMod.NAK.RelativeSync"; - - #endregion - - #region Enums - - private enum MessageType : byte - { - MovementParentOrChair = 0 - //RelativePickup = 1, - //RelativeAttachment = 2, - } - - #endregion - - #region Mod Network Internals - - internal static void Subscribe() - { - ModNetworkManager.Subscribe(ModId, OnMessageReceived); - - _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); - if (!_isSubscribedToModNetwork) - Debug.LogError("Failed to subscribe to Mod Network!"); - } - - // Called right after NetworkRootDataUpdate.Submit() - internal static void SendRelativeSyncUpdate() - { - if (!_isSubscribedToModNetwork) - return; - - if (_latestMovementParentSyncData.HasSyncedThisData) - return; - - SendMessage(MessageType.MovementParentOrChair, _latestMovementParentSyncData.MarkerHash, - _latestMovementParentSyncData.RootPosition, _latestMovementParentSyncData.RootRotation); - - _latestMovementParentSyncData.HasSyncedThisData = true; - } - - public static void SetLatestRelativeSync( - int markerHash, - Vector3 position, Vector3 rotation) - { - // check if the data has changed - if (_latestMovementParentSyncData.MarkerHash == markerHash - && _latestMovementParentSyncData.RootPosition == position - && _latestMovementParentSyncData.RootRotation == rotation) - return; // no need to update (shocking) - - _latestMovementParentSyncData.HasSyncedThisData = false; // reset - _latestMovementParentSyncData.MarkerHash = markerHash; - _latestMovementParentSyncData.RootPosition = position; - _latestMovementParentSyncData.RootRotation = rotation; - } - - private static void SendMessage(MessageType messageType, int markerHash, Vector3 position, Vector3 rotation) - { - if (!IsConnectedToGameNetwork()) - return; - - using ModNetworkMessage modMsg = new(ModId); - modMsg.Write((byte)messageType); - modMsg.Write(markerHash); - modMsg.Write(position); - modMsg.Write(rotation); - modMsg.Send(); - - if (Debug_NetworkOutbound) - Debug.Log( - $"[Outbound] MessageType: {messageType}, MarkerHash: {markerHash}, Position: {position}, " + - $"Rotation: {rotation}"); - } - - private static void OnMessageReceived(ModNetworkMessage msg) - { - msg.Read(out byte msgTypeRaw); - - if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) - return; - - switch ((MessageType)msgTypeRaw) - { - case MessageType.MovementParentOrChair: - msg.Read(out int markerHash); - msg.Read(out Vector3 receivedPosition); - msg.Read(out Vector3 receivedRotation); - // msg.Read(out Vector3 receivedHipPosition); - // msg.Read(out Vector3 receivedHipRotation); - - OnNetworkPositionUpdateReceived(msg.Sender, markerHash, receivedPosition, receivedRotation); - - if (Debug_NetworkInbound) - Debug.Log($"[Inbound] Sender: {msg.Sender}, MarkerHash: {markerHash}, " + - $"Position: {receivedPosition}, Rotation: {receivedRotation}"); - break; - default: - Debug.LogError($"Invalid message type received from: {msg.Sender}"); - break; - } - } - - #endregion - - #region Private Methods - - private static bool IsConnectedToGameNetwork() - { - return NetworkManager.Instance != null - && NetworkManager.Instance.GameNetwork != null - && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; - } - - private static void OnNetworkPositionUpdateReceived( - string sender, int markerHash, - Vector3 position, Vector3 rotation) - { - RelativeSyncManager.ApplyRelativeSync(sender, markerHash, position, rotation); - } - - #endregion -} \ No newline at end of file diff --git a/RelativeSync/Patches.cs b/RelativeSync/Patches.cs deleted file mode 100644 index 10acf15..0000000 --- a/RelativeSync/Patches.cs +++ /dev/null @@ -1,108 +0,0 @@ -using ABI_RC.Core.Base; -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Networking.Jobs; -using ABI_RC.Core.Player; -using ABI.CCK.Components; -using HarmonyLib; -using NAK.RelativeSync.Components; -using NAK.RelativeSync.Networking; -using UnityEngine; - -namespace NAK.RelativeSync.Patches; - -internal static class PlayerSetupPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] - private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) - { - __instance.AddComponentIfMissing(); - } -} - -internal static class PuppetMasterPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.Start))] - private static void Postfix_PuppetMaster_Start(ref PuppetMaster __instance) - { - __instance.AddComponentIfMissing(); - } -} - -internal static class CVRSeatPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRSeat), nameof(CVRSeat.Awake))] - private static void Postfix_CVRSeat_Awake(ref CVRSeat __instance) - { - __instance.AddComponentIfMissing(); - } -} - -internal static class CVRMovementParentPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRMovementParent), nameof(CVRMovementParent.Start))] - private static void Postfix_CVRMovementParent_Start(ref CVRMovementParent __instance) - { - __instance.AddComponentIfMissing(); - } -} - -internal static class NetworkRootDataUpdatePatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(NetworkRootDataUpdate), nameof(NetworkRootDataUpdate.Submit))] - private static void Postfix_NetworkRootDataUpdater_Submit() - { - ModNetwork.SendRelativeSyncUpdate(); // Send the relative sync update after the network root data update - } -} - -internal static class CVRSpawnablePatches -{ - internal static bool UseHack; - - private static bool _canUpdate; - - [HarmonyPrefix] - [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Update))] - private static bool Prefix_CVRSpawnable_Update() - => !UseHack || _canUpdate; - - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.FixedUpdate))] - private static void Postfix_CVRSpawnable_FixedUpdate(ref CVRSpawnable __instance) - { - if (!UseHack) return; - - _canUpdate = true; - __instance.Update(); - _canUpdate = false; - } -} - -internal static class NetIKController_Patches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(NetIKController), nameof(NetIKController.LateUpdate))] - private static void Postfix_NetIKController_LateUpdate(ref NetIKController __instance) - { - if (!RelativeSyncManager.NetIkControllersToRelativeSyncControllers.TryGetValue(__instance, - out RelativeSyncController syncController)) - return; - - // Apply relative sync after the network IK has been applied - syncController.OnPostNetIkControllerLateUpdate(); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(NetIKController), nameof(NetIKController.GetLocalPlayerPosition))] - private static bool Prefix_NetIKController_GetLocalPlayerPosition(ref NetIKController __instance, ref Vector3 __result) - { - // why is the original method so bad - __result = PlayerSetup.Instance.activeCam.transform.position; - return false; - } -} \ No newline at end of file diff --git a/RelativeSync/README.md b/RelativeSync/README.md deleted file mode 100644 index 4ab0455..0000000 --- a/RelativeSync/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# RelativeSync - -Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. - -https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/ae6c6e4b-7529-42e2-bd2c-afa050849906 - -## Mod Settings -- **Debug Network Inbound**: Log network messages received from other players. -- **Debug Network Outbound**: Log network messages sent to other players. -- **Exp Spawnable Sync Hack**: Forces CVRSpawnable to update position in FixedUpdate. This can help with local jitter while on a remote synced movement parent. -- **Exp Disable Interpolation on BBCC**: Disables interpolation on BetterBetterCharacterController. This can help with local jitter while on any movement parent. - -## Known Issues -- Movement Parents on remote users will still locally jitter. - - PuppetMaster/NetIkController applies received position updates in LateUpdate, while character controller updates in FixedUpdate. -- Movement Parents using CVRObjectSync synced by remote users will still locally jitter. - - CVRObjectSync applies received position updates in LateUpdate, while character controller updates in FixedUpdate. -- Slight interpolation issue with humanoid avatar hips while standing on a Movement Parent. - - Requires further investigation. I believe it to be because hips are not synced properly, requiring me to relative sync the hips as well. - ---- - -Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. -https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games - -> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. - -> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. - -> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs b/RelativeSync/RelativeSync/Components/RelativeSyncController.cs deleted file mode 100644 index a94cde0..0000000 --- a/RelativeSync/RelativeSync/Components/RelativeSyncController.cs +++ /dev/null @@ -1,206 +0,0 @@ -using ABI_RC.Core.Player; -using ABI_RC.Systems.Movement; -using UnityEngine; - -namespace NAK.RelativeSync.Components; - -[DefaultExecutionOrder(9000)] // make sure this runs after NetIKController, but before Totally Wholesome LineController (9999) -public class RelativeSyncController : MonoBehaviour -{ - private const float MaxMagnitude = 750000000000f; - - private float _updateInterval = 0.05f; - private float _lastUpdate; - - private string _userId; - private PuppetMaster puppetMaster { get; set; } - private RelativeSyncMarker _relativeSyncMarker; - - private RelativeSyncData _relativeSyncData; - private RelativeSyncData _lastSyncData; - - #region Unity Events - - private void Start() - { - puppetMaster = GetComponent(); - - _userId = puppetMaster._playerDescriptor.ownerId; - RelativeSyncManager.RelativeSyncControllers.Add(_userId, this); - RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Add(puppetMaster.netIkController, this); - } - - private void OnDestroy() - { - RelativeSyncManager.RelativeSyncControllers.Remove(_userId); - - if (puppetMaster == null - || puppetMaster.netIkController == null) - { - // remove by value ? - foreach (var kvp in RelativeSyncManager.NetIkControllersToRelativeSyncControllers) - { - if (kvp.Value != this) continue; - RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Remove(kvp.Key); - break; - } - return; - } - RelativeSyncManager.NetIkControllersToRelativeSyncControllers.Remove(puppetMaster.netIkController); - } - - internal void OnPostNetIkControllerLateUpdate() - { - // if (puppetMaster._isHidden) - // return; - - if (_relativeSyncMarker == null) - return; - - if (!_relativeSyncMarker.IsComponentActive) - return; - - Animator animator = puppetMaster._animator; - if (animator == null) - return; - - Transform avatarTransform = animator.transform; - Transform hipTrans = (animator.avatar != null && animator.isHuman) - ? animator.GetBoneTransform(HumanBodyBones.Hips) : null; - - Vector3 relativeHipPos = default; - Quaternion relativeHipRot = default; - if (hipTrans != null) - { - Vector3 worldRootPos = avatarTransform.position; - Quaternion worldRootRot = avatarTransform.rotation; - - Vector3 hipPos = hipTrans.position; - Quaternion hipRot = hipTrans.rotation; - - relativeHipPos = Quaternion.Inverse(worldRootRot) * (hipPos - worldRootPos); - relativeHipRot = Quaternion.Inverse(worldRootRot) * hipRot; - } - - // TODO: handle the case where hip is not synced but is found on remote client - - float lerp = Mathf.Min((Time.time - _lastUpdate) / _updateInterval, 1f); - - ApplyRelativeRotation(avatarTransform, hipTrans, lerp); - ApplyRelativePosition(hipTrans, lerp); - - // idk if needed (both player root & avatar root are set to same world position) -_-_-_- - avatarTransform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); - - // fix hip syncing because it is not relative to root, it is synced in world space -_- - if (hipTrans != null) - { - hipTrans.position = transform.position + transform.rotation * relativeHipPos; - hipTrans.rotation = transform.rotation * relativeHipRot; - } - - // Reprocess the root distance so we don't fuck avatar distance hider - NetIKController netIkController = puppetMaster.netIkController; - netIkController._rootDistance = Vector3.Distance((netIkController._collider.transform.position + netIkController._collider.center), - netIkController.GetLocalPlayerPosition()) - (netIkController._collider.radius + BetterBetterCharacterController.Instance.Radius); - } - - private void ApplyRelativeRotation(Transform avatarTransform, Transform hipTransform, float lerp) - { - if (!_relativeSyncMarker.ApplyRelativeRotation || - !(_relativeSyncData.LocalRootRotation.sqrMagnitude < MaxMagnitude)) - return; // not applying relative rotation or data is invalid - - Quaternion markerRotation = _relativeSyncMarker.transform.rotation; - Quaternion lastWorldRotation = markerRotation * Quaternion.Euler(_lastSyncData.LocalRootRotation); - Quaternion worldRotation = markerRotation * Quaternion.Euler(_relativeSyncData.LocalRootRotation); - - if (_relativeSyncMarker.OnlyApplyRelativeHeading) - { - Vector3 currentWorldUp = avatarTransform.up; - - Vector3 currentForward = lastWorldRotation * Vector3.forward; - Vector3 targetForward = worldRotation * Vector3.forward; - - currentForward = Vector3.ProjectOnPlane(currentForward, currentWorldUp).normalized; - targetForward = Vector3.ProjectOnPlane(targetForward, currentWorldUp).normalized; - - lastWorldRotation = Quaternion.LookRotation(currentForward, currentWorldUp); - worldRotation = Quaternion.LookRotation(targetForward, currentWorldUp); - } - - transform.rotation = Quaternion.Slerp(lastWorldRotation, worldRotation, lerp); - } - - private void ApplyRelativePosition(Transform hipTransform, float lerp) - { - if (!_relativeSyncMarker.ApplyRelativePosition || - !(_relativeSyncData.LocalRootPosition.sqrMagnitude < MaxMagnitude)) - return; // not applying relative position or data is invalid - - Transform targetTransform = _relativeSyncMarker.transform; - - Vector3 lastWorldPosition = targetTransform.TransformPoint(_lastSyncData.LocalRootPosition); - Vector3 worldPosition = targetTransform.TransformPoint(_relativeSyncData.LocalRootPosition); - transform.position = Vector3.Lerp(lastWorldPosition, worldPosition, lerp); - - // if (hipTransform == null) - // return; - // - // Vector3 lastWorldHipPosition = targetTransform.TransformPoint(_lastSyncData.LocalHipPosition); - // Vector3 worldHipPosition = targetTransform.TransformPoint(_relativeSyncData.LocalHipPosition); - // hipTransform.position = Vector3.Lerp(lastWorldHipPosition, worldHipPosition, lerp); - } - - #endregion Unity Events - - #region Public Methods - - public void SetRelativeSyncMarker(RelativeSyncMarker target) - { - if (_relativeSyncMarker == target) - return; - - _relativeSyncMarker = target; - - // calculate relative position and rotation so lerp can smooth it out (hack) - if (_relativeSyncMarker == null) - return; - - Animator avatarAnimator = puppetMaster._animator; - if (avatarAnimator == null) - return; // i dont care to bother - - RelativeSyncManager.GetRelativeAvatarPositionsFromMarker( - avatarAnimator, _relativeSyncMarker.transform, - out Vector3 relativePosition, out Vector3 relativeRotation); - - // set last sync data to current position and rotation so we don't lerp from the last marker - _lastSyncData.LocalRootPosition = relativePosition; - _lastSyncData.LocalRootRotation = relativeRotation; - _lastUpdate = Time.time; // reset update time - } - - public void SetRelativePositions(Vector3 position, Vector3 rotation) - { - // calculate update interval - float prevUpdate = _lastUpdate; - _lastUpdate = Time.time; - _updateInterval = _lastUpdate - prevUpdate; - - // cycle last sync data - _lastSyncData = _relativeSyncData; - - // set new sync data - _relativeSyncData.LocalRootPosition = position; - _relativeSyncData.LocalRootRotation = rotation; - } - - #endregion Public Methods - - private struct RelativeSyncData - { - public Vector3 LocalRootPosition; - public Vector3 LocalRootRotation; - } -} \ No newline at end of file diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs b/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs deleted file mode 100644 index 9627146..0000000 --- a/RelativeSync/RelativeSync/Components/RelativeSyncMarker.cs +++ /dev/null @@ -1,110 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.Player; -using ABI_RC.Core.Savior; -using ABI.CCK.Components; -using UnityEngine; - -namespace NAK.RelativeSync.Components; - -public class RelativeSyncMarker : MonoBehaviour -{ - public int pathHash { get; private set; } - - public bool IsComponentActive - => _component.isActiveAndEnabled; - - public bool ApplyRelativePosition = true; - public bool ApplyRelativeRotation = true; - public bool OnlyApplyRelativeHeading; - - private MonoBehaviour _component; - - private void Start() - { - RegisterWithManager(); - ConfigureForPotentialMovementParent(); - } - - private void OnDestroy() - { - RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash); - } - - public void OnHavingSoftTacosNow() - => RegisterWithManager(); - - private void RegisterWithManager() - { - // Remove old hash in case this is a re-registration - RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash); - - string path = GetGameObjectPath(transform); - int hash = path.GetHashCode(); - - // check if it already exists (this **should** only matter in worlds) - if (RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash)) - { - RelativeSyncMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}"); - if (!FindAvailableHash(ref hash)) // super lazy fix idfc - { - RelativeSyncMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}"); - return; - } - } - - pathHash = hash; - RelativeSyncManager.RelativeSyncTransforms.Add(hash, this); - } - - private void ConfigureForPotentialMovementParent() - { - if (!gameObject.TryGetComponent(out CVRMovementParent movementParent)) - { - _component = GetComponent(); // users cant animate enabled state so i dont think matters - return; - } - _component = movementParent; - - // TODO: a refactor may be needed to handle the orientation mode being animated - - // respect orientation mode & gravity zone - ApplyRelativeRotation = movementParent.orientationMode == CVRMovementParent.OrientationMode.RotateWithParent; - OnlyApplyRelativeHeading = movementParent.GetComponent() == null; - } - - private static string GetGameObjectPath(Transform transform) - { - // props already have a unique instance identifier at root - // worlds uhhhh, dont duplicate the same thing over and over thx - // avatars on remote/local client have diff path, we need to account for it -_- - - string path = transform.name; - while (transform.parent != null) - { - transform = transform.parent; - - // only true at root of local player object - if (transform.CompareTag("Player")) - { - path = MetaPort.Instance.ownerId + "/" + path; - break; - } // remote player object root is already player guid - - path = transform.name + "/" + path; - } - - return path; - } - - private static bool FindAvailableHash(ref int hash) - { - for (int i = 0; i < 16; i++) - { - hash += 1; - if (!RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash)) return true; - } - - // failed to find a hash in 16 tries, dont care - return false; - } -} diff --git a/RelativeSync/RelativeSync/Components/RelativeSyncMonitor.cs b/RelativeSync/RelativeSync/Components/RelativeSyncMonitor.cs deleted file mode 100644 index 134234c..0000000 --- a/RelativeSync/RelativeSync/Components/RelativeSyncMonitor.cs +++ /dev/null @@ -1,79 +0,0 @@ -using ABI_RC.Core.Player; -using ABI_RC.Systems.Movement; -using NAK.RelativeSync.Networking; -using UnityEngine; - -namespace NAK.RelativeSync.Components; - -[DefaultExecutionOrder(int.MaxValue)] -public class RelativeSyncMonitor : MonoBehaviour -{ - private BetterBetterCharacterController _characterController { get; set; } - - private RelativeSyncMarker _relativeSyncMarker; - private RelativeSyncMarker _lastRelativeSyncMarker; - - private void Start() - { - _characterController = GetComponent(); - } - - private void LateUpdate() - { - if (_characterController == null) - return; - - CheckForRelativeSyncMarker(); - - if (_relativeSyncMarker == null) - { - if (_lastRelativeSyncMarker == null) - return; - - // send empty position and rotation to stop syncing - SendEmptyPositionAndRotation(); - _lastRelativeSyncMarker = null; - return; - } - - _lastRelativeSyncMarker = _relativeSyncMarker; - - Animator avatarAnimator = PlayerSetup.Instance._animator; - if (avatarAnimator == null) - return; // i dont care to bother - - RelativeSyncManager.GetRelativeAvatarPositionsFromMarker( - avatarAnimator, _relativeSyncMarker.transform, - out Vector3 relativePosition, out Vector3 relativeRotation); - - ModNetwork.SetLatestRelativeSync( - _relativeSyncMarker.pathHash, - relativePosition, relativeRotation); - } - - private void CheckForRelativeSyncMarker() - { - if (_characterController._isSitting && _characterController._lastCvrSeat) - { - RelativeSyncMarker newMarker = _characterController._lastCvrSeat.GetComponent(); - _relativeSyncMarker = newMarker; - return; - } - - if (_characterController._previousMovementParent != null) - { - RelativeSyncMarker newMarker = _characterController._previousMovementParent.GetComponent(); - _relativeSyncMarker = newMarker; - return; - } - - // none found - _relativeSyncMarker = null; - } - - private void SendEmptyPositionAndRotation() - { - ModNetwork.SetLatestRelativeSync(RelativeSyncManager.NoTarget, - Vector3.zero, Vector3.zero); - } -} \ No newline at end of file diff --git a/RelativeSync/RelativeSync/RelativeSyncManager.cs b/RelativeSync/RelativeSync/RelativeSyncManager.cs deleted file mode 100644 index 052ca3c..0000000 --- a/RelativeSync/RelativeSync/RelativeSyncManager.cs +++ /dev/null @@ -1,71 +0,0 @@ -using ABI_RC.Core.Base; -using ABI_RC.Core.Player; -using NAK.RelativeSync.Components; -using UnityEngine; - -namespace NAK.RelativeSync; - -public static class RelativeSyncManager -{ - public const int NoTarget = -1; - - public static readonly Dictionary RelativeSyncTransforms = new(); - public static readonly Dictionary RelativeSyncControllers = new(); - public static readonly Dictionary NetIkControllersToRelativeSyncControllers = new(); - - public static void ApplyRelativeSync(string userId, int target, Vector3 position, Vector3 rotation) - { - if (!RelativeSyncControllers.TryGetValue(userId, out RelativeSyncController controller)) - { - if (CVRPlayerManager.Instance.GetPlayerPuppetMaster(userId, out PuppetMaster pm)) - { - controller = pm.AddComponentIfMissing(); - RelativeSyncMod.Logger.Msg($"Found PuppetMaster for user {userId}. This user is now eligible for relative sync."); - } - else - { - RelativeSyncControllers.Add(userId, null); // add null controller to prevent future lookups - RelativeSyncMod.Logger.Warning($"Failed to find PuppetMaster for user {userId}. This is likely because the user is blocked or has blocked you. This user will not be eligible for relative sync until next game restart."); - } - } - - if (controller == null) - return; - - // find target transform - RelativeSyncMarker syncMarker = null; - if (target != NoTarget) RelativeSyncTransforms.TryGetValue(target, out syncMarker); - - controller.SetRelativeSyncMarker(syncMarker); - controller.SetRelativePositions(position, rotation); - } - - public static void GetRelativeAvatarPositionsFromMarker( - Animator avatarAnimator, Transform markerTransform, - out Vector3 relativePosition, out Vector3 relativeRotation) - // out Vector3 relativeHipPosition, out Vector3 relativeHipRotation) - { - Transform avatarTransform = avatarAnimator.transform; - - // because our syncing is retarded, we need to sync relative from the avatar root... - Vector3 avatarRootPosition = avatarTransform.position; // PlayerSetup.Instance.GetPlayerPosition() - Quaternion avatarRootRotation = avatarTransform.rotation; // PlayerSetup.Instance.GetPlayerRotation() - - relativePosition = markerTransform.InverseTransformPoint(avatarRootPosition); - relativeRotation = (Quaternion.Inverse(markerTransform.rotation) * avatarRootRotation).eulerAngles; - - // Transform hipTrans = (avatarAnimator.avatar != null && avatarAnimator.isHuman) - // ? avatarAnimator.GetBoneTransform(HumanBodyBones.Hips) : null; - // - // if (hipTrans == null) - // { - // relativeHipPosition = Vector3.zero; - // relativeHipRotation = Vector3.zero; - // } - // else - // { - // relativeHipPosition = markerTransform.InverseTransformPoint(hipTrans.position); - // relativeHipRotation = (Quaternion.Inverse(markerTransform.rotation) * hipTrans.rotation).eulerAngles; - // } - } -} \ No newline at end of file diff --git a/RelativeSync/format.json b/RelativeSync/format.json deleted file mode 100644 index a5346fa..0000000 --- a/RelativeSync/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": 211, - "name": "RelativeSync", - "modversion": "1.0.5", - "gameversion": "2025r179", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "NotAKidoS", - "description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.", - "searchtags": [ - "relative", - "sync", - "movement", - "chair" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll", - "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/", - "changelog": "- Recompiled for 2025r179\n- Fixed execution order of RelativeSyncController so moving at high speeds with passengers does not disrupt voice or avatar distance hider", - "embedcolor": "#f61963" -} \ No newline at end of file diff --git a/RelativeSyncJitterFix/Main.cs b/RelativeSyncJitterFix/Main.cs new file mode 100644 index 0000000..19863d1 --- /dev/null +++ b/RelativeSyncJitterFix/Main.cs @@ -0,0 +1,25 @@ +using MelonLoader; + +namespace NAK.RelativeSyncJitterFix; + +public class RelativeSyncJitterFixMod : MelonMod +{ + public override void OnInitializeMelon() + { + // Experimental sync hack + ApplyPatches(typeof(Patches.CVRSpawnablePatches)); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } +} \ No newline at end of file diff --git a/RelativeSyncJitterFix/Patches.cs b/RelativeSyncJitterFix/Patches.cs new file mode 100644 index 0000000..71a67f7 --- /dev/null +++ b/RelativeSyncJitterFix/Patches.cs @@ -0,0 +1,22 @@ +using ABI.CCK.Components; +using HarmonyLib; + +namespace NAK.RelativeSyncJitterFix.Patches; + +internal static class CVRSpawnablePatches +{ + private static bool _canUpdate; + + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.FixedUpdate))] + private static void Postfix_CVRSpawnable_FixedUpdate(ref CVRSpawnable __instance) + { + _canUpdate = true; + __instance.Update(); + _canUpdate = false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Update))] + private static bool Prefix_CVRSpawnable_Update() => _canUpdate; +} \ No newline at end of file diff --git a/RelativeSync/Properties/AssemblyInfo.cs b/RelativeSyncJitterFix/Properties/AssemblyInfo.cs similarity index 65% rename from RelativeSync/Properties/AssemblyInfo.cs rename to RelativeSyncJitterFix/Properties/AssemblyInfo.cs index ef60248..ff4fcd8 100644 --- a/RelativeSync/Properties/AssemblyInfo.cs +++ b/RelativeSyncJitterFix/Properties/AssemblyInfo.cs @@ -1,32 +1,32 @@ -using NAK.RelativeSync.Properties; +using NAK.RelativeSyncJitterFix.Properties; using MelonLoader; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.RelativeSync))] +[assembly: AssemblyTitle(nameof(NAK.RelativeSyncJitterFix))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.RelativeSync))] +[assembly: AssemblyProduct(nameof(NAK.RelativeSyncJitterFix))] [assembly: MelonInfo( - typeof(NAK.RelativeSync.RelativeSyncMod), - nameof(NAK.RelativeSync), + typeof(NAK.RelativeSyncJitterFix.RelativeSyncJitterFixMod), + nameof(NAK.RelativeSyncJitterFix), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSyncJitterFix" )] -[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonColor(255, 246, 25, 99)] // red-pink [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.RelativeSync.Properties; +namespace NAK.RelativeSyncJitterFix.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.5"; + public const string Version = "1.0.0"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/RelativeSyncJitterFix/README.md b/RelativeSyncJitterFix/README.md new file mode 100644 index 0000000..7978ed2 --- /dev/null +++ b/RelativeSyncJitterFix/README.md @@ -0,0 +1,21 @@ +# RelativeSyncJitterFix + +Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. +Changes when props apply their incoming sync data to be before the character controller simulation. + +## Known Issues +- Movement Parents on remote users will still locally jitter. + - PuppetMaster/NetIkController applies received position updates in LateUpdate, while character controller updates in FixedUpdate. +- Movement Parents using CVRObjectSync synced by remote users will still locally jitter. + - CVRObjectSync applies received position updates in LateUpdate, while character controller updates in FixedUpdate. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ChilloutVR. +https://docs.chilloutvr.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by ChilloutVR. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by ChilloutVR. diff --git a/RelativeSync/RelativeSync.csproj b/RelativeSyncJitterFix/RelativeSyncJitterFix.csproj similarity index 100% rename from RelativeSync/RelativeSync.csproj rename to RelativeSyncJitterFix/RelativeSyncJitterFix.csproj diff --git a/RelativeSyncJitterFix/format.json b/RelativeSyncJitterFix/format.json new file mode 100644 index 0000000..b128e2a --- /dev/null +++ b/RelativeSyncJitterFix/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "RelativeSyncJitterFix", + "modversion": "1.0.0", + "gameversion": "2025r180", + "loaderversion": "0.7.2", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync.\nChanges when props apply their incoming sync data to be before the character controller simulation.", + "searchtags": [ + "relative", + "sync", + "movement", + "chair" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RelativeSyncJitterFix.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSyncJitterFix/", + "changelog": "- Removed RelativeSync except for a single harmony patch", + "embedcolor": "#f61963" +} \ No newline at end of file From 1496c25184aa87d04fb0ee7402e6c19d28a9fbc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 27 Aug 2025 06:35:11 +0000 Subject: [PATCH 186/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 76534fa..2d2371e 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,22 @@ | Name | Description | Download | |------|-------------|----------| -| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll) | -| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll) | +| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ASTExtension.dll) | +| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | No Download | | [ConfigureCalibrationPose](ConfigureCalibrationPose/README.md) | Select FBT calibration pose. | No Download | -| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll) | -| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/DoubleTapJumpToExitSeat.dll) | -| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/FuckToes.dll) | -| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll) | -| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll) | -| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropLoadingHexagon.dll) | -| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll) | -| [RelativeSync](RelativeSync/README.md) | Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll) | -| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll) | -| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/SmootherRay.dll) | -| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/Stickers.dll) | -| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll) | -| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | No Download | +| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/CustomSpawnPoint.dll) | +| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/DoubleTapJumpToExitSeat.dll) | +| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | No Download | +| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | No Download | +| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | No Download | +| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download | +| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll) | +| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | No Download | +| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll) | +| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download | +| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download | +| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll) | +| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll) | ### Experimental Mods @@ -29,7 +29,7 @@ |------|-------------|----------| | [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | No Download | | [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | No Download | -| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LuaNetworkVariables.dll) | +| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | No Download | | [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | No Download | | [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | No Download | | [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | No Download | From 6bef1d0c9694a3704b5747a619a23eaa7defbf03 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Wed, 27 Aug 2025 05:21:26 -0500 Subject: [PATCH 187/188] [Tinyboard] Initial release --- Tinyboard/Main.cs | 346 +++++++++++++++++++++++++++ Tinyboard/Properties/AssemblyInfo.cs | 32 +++ Tinyboard/README.md | 19 ++ Tinyboard/Tinyboard.csproj | 6 + Tinyboard/format.json | 23 ++ 5 files changed, 426 insertions(+) create mode 100644 Tinyboard/Main.cs create mode 100644 Tinyboard/Properties/AssemblyInfo.cs create mode 100644 Tinyboard/README.md create mode 100644 Tinyboard/Tinyboard.csproj create mode 100644 Tinyboard/format.json diff --git a/Tinyboard/Main.cs b/Tinyboard/Main.cs new file mode 100644 index 0000000..540d150 --- /dev/null +++ b/Tinyboard/Main.cs @@ -0,0 +1,346 @@ +using System.Reflection; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Savior; +using ABI_RC.Core.UI; +using ABI_RC.Core.UI.UIRework.Managers; +using ABI_RC.Systems.VRModeSwitch; +using ABI_RC.VideoPlayer.Scripts; +using HarmonyLib; +using MelonLoader; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace NAK.Tinyboard; + +public class TinyboardMod : MelonMod +{ + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(Tinyboard)); + + private static readonly MelonPreferences_Entry EntrySmartAlignToMenu = + Category.CreateEntry( + identifier: "smart_align_to_menu", + true, + display_name: "Smart Align To Menu", + description: "Should the keyboard align to the menu it was opened from? (Main Menu, World-Anchored Quick Menu)"); + + private static readonly MelonPreferences_Entry EntryEnforceTitle = + Category.CreateEntry( + identifier: "enforce_title", + true, + display_name: "Enforce Title", + description: "Should the keyboard enforce a title when opened from an input field or main menu?"); + + private static readonly MelonPreferences_Entry EntryResizeKeyboard = + Category.CreateEntry( + identifier: "resize_keyboard", + true, + display_name: "Resize Keyboard", + description: "Should the keyboard be resized to match XSOverlays width?"); + + private static readonly MelonPreferences_Entry EntryUseModifiers = + Category.CreateEntry( + identifier: "use_scale_distance_modifiers", + true, + display_name: "Use Scale/Distance/Offset Modifiers", + description: "Should the scale/distance/offset modifiers be used?"); + + private static readonly MelonPreferences_Entry EntryDesktopScaleModifier = + Category.CreateEntry( + identifier: "desktop_scale_modifier", + 0.75f, + display_name: "Desktop Scale Modifier", + description: "Scale modifier for desktop mode."); + + private static readonly MelonPreferences_Entry EntryDesktopDistance = + Category.CreateEntry( + identifier: "desktop_distance_modifier", + 0f, + display_name: "Desktop Distance Modifier", + description: "Distance modifier for desktop mode."); + + private static readonly MelonPreferences_Entry EntryDesktopVerticalAdjustment = + Category.CreateEntry( + identifier: "desktop_vertical_adjustment", + 0.1f, + display_name: "Desktop Vertical Adjustment", + description: "Vertical adjustment for desktop mode."); + + private static readonly MelonPreferences_Entry EntryVRScaleModifier = + Category.CreateEntry( + identifier: "vr_scale_modifier", + 0.85f, + display_name: "VR Scale Modifier", + description: "Scale modifier for VR mode."); + + private static readonly MelonPreferences_Entry EntryVRDistance = + Category.CreateEntry( + identifier: "vr_distance_modifier", + 0.2f, + display_name: "VR Distance Modifier", + description: "Distance modifier for VR mode."); + + private static readonly MelonPreferences_Entry EntryVRVerticalAdjustment = + Category.CreateEntry( + identifier: "vr_vertical_adjustment", + 0f, + display_name: "VR Vertical Adjustment", + description: "Vertical adjustment for VR mode."); + + #endregion Melon Preferences + + private static Transform _tinyBoardOffset; + private static void ApplyTinyBoardOffsetsForVRMode() + { + if (!EntryUseModifiers.Value) + { + _tinyBoardOffset.localScale = Vector3.one; + _tinyBoardOffset.localPosition = Vector3.zero; + return; + } + float distanceModifier; + float scaleModifier; + float verticalAdjustment; + if (MetaPort.Instance.isUsingVr) + { + scaleModifier = EntryVRScaleModifier.Value; + distanceModifier = EntryVRDistance.Value; + verticalAdjustment = EntryVRVerticalAdjustment.Value; + } + else + { + scaleModifier = EntryDesktopScaleModifier.Value; + distanceModifier = EntryDesktopDistance.Value; + verticalAdjustment = EntryDesktopVerticalAdjustment.Value; + } + _tinyBoardOffset.localScale = Vector3.one * scaleModifier; + _tinyBoardOffset.localPosition = new Vector3(0f, verticalAdjustment, distanceModifier); + } + + private static void ApplyTinyBoardWidthResize() + { + KeyboardManager km = KeyboardManager.Instance; + CohtmlControlledView cohtmlView = km.cohtmlView; + Transform keyboardTransform = cohtmlView.transform; + + int targetWidthPixels = EntryResizeKeyboard.Value ? 1330 : 1520; + float targetScaleX = EntryResizeKeyboard.Value ? 1.4f : 1.6f; + + cohtmlView.Width = targetWidthPixels; + Vector3 currentScale = keyboardTransform.localScale; + currentScale.x = targetScaleX; + keyboardTransform.localScale = currentScale; + } + + public override void OnInitializeMelon() + { + // add our shim transform to scale the menu down by 0.75 + HarmonyInstance.Patch( + typeof(CVRKeyboardPositionHelper).GetMethod(nameof(CVRKeyboardPositionHelper.Awake), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnCVRKeyboardPositionHelperAwake), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // reposition the keyboard when it is opened to match the menu position if it is opened from a menu + HarmonyInstance.Patch( + typeof(MenuPositionHelperBase).GetMethod(nameof(MenuPositionHelperBase.OnMenuOpen), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnMenuPositionHelperBaseOnMenuOpen), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // enforces a title for the keyboard in cases it did not already have one + HarmonyInstance.Patch( + typeof(KeyboardManager).GetMethod(nameof(KeyboardManager.ShowKeyboard), + BindingFlags.Public | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnKeyboardManagerShowKeyboard), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // resize keyboard to match XSOverlays width + HarmonyInstance.Patch( + typeof(KeyboardManager).GetMethod(nameof(KeyboardManager.Start), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnKeyboardManagerStart), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // update offsets when switching VR modes + VRModeSwitchEvents.OnPostVRModeSwitch.AddListener((_) => ApplyTinyBoardOffsetsForVRMode()); + + // listen for setting changes + EntryUseModifiers.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode()); + EntryDesktopScaleModifier.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode()); + EntryVRScaleModifier.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode()); + EntryDesktopDistance.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode()); + EntryVRDistance.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode()); + EntryResizeKeyboard.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardWidthResize()); + } + + private static void OnCVRKeyboardPositionHelperAwake(CVRKeyboardPositionHelper __instance) + { + _tinyBoardOffset = new GameObject("NAKTinyBoard").transform; + + Transform offsetTransform = __instance.transform.GetChild(0); + _tinyBoardOffset.SetParent(offsetTransform, false); + + ApplyTinyBoardOffsetsForVRMode(); + + Transform menuTransform = __instance.menuTransform; + menuTransform.SetParent(_tinyBoardOffset, false); + } + + private static void OnMenuPositionHelperBaseOnMenuOpen(MenuPositionHelperBase __instance) + { + if (!EntrySmartAlignToMenu.Value) return; + if (__instance is not CVRKeyboardPositionHelper { IsMenuOpen: true }) return; + + // Check if the open source was an open menu + KeyboardManager.OpenSource? openSource = KeyboardManager.Instance._keyboardOpenSource; + + MenuPositionHelperBase menuPositionHelper; + switch (openSource) + { + case KeyboardManager.OpenSource.MainMenu: + menuPositionHelper = CVRMainMenuPositionHelper.Instance; + break; + case KeyboardManager.OpenSource.QuickMenu: + menuPositionHelper = CVRQuickMenuPositionHelper.Instance; + if (!menuPositionHelper.IsUsingWorldAnchoredMenu) return; // hand anchored quick menu, don't touch + break; + default: return; + } + + // get modifiers + float rootScaleModifier = __instance.transform.lossyScale.x; + float keyboardDistanceModifier = __instance.MenuDistanceModifier; + float menuDistanceModifier = menuPositionHelper.MenuDistanceModifier; + + // get difference between modifiers + float distanceModifier = keyboardDistanceModifier - menuDistanceModifier; + + // place keyboard at menu position + difference in modifiers + Transform menuOffsetTransform = menuPositionHelper._offsetTransform; + Quaternion keyboardRotation = menuOffsetTransform.rotation; + Vector3 keyboardPosition = menuOffsetTransform.position + + menuOffsetTransform.forward * (rootScaleModifier * distanceModifier); + + // place keyboard as if it was opened with player camera in same place as menu was + __instance._offsetTransform.SetPositionAndRotation(keyboardPosition, keyboardRotation); + } + + private static void OnKeyboardManagerStart() => ApplyTinyBoardWidthResize(); + + /* + public void ShowKeyboard( + string currentText, + Action callback, + string placeholder = null, + string successText = "Success", + int maxCharacterCount = 0, + bool hidden = false, + bool multiLine = false, + string title = null, + OpenSource openSource = OpenSource.Other) + */ + + // using mix of index and args params because otherwise explodes with invalid IL ? + private static void OnKeyboardManagerShowKeyboard(ref string __7, ref string __2, object[] __args) + { + if (!EntryEnforceTitle.Value) return; + + // ReSharper disable thrice InlineTemporaryVariable + ref string title = ref __7; + ref string placeholder = ref __2; + if (!string.IsNullOrWhiteSpace(title)) return; + + Action callback = __args[1] as Action; + KeyboardManager.OpenSource? openSource = __args[8] as KeyboardManager.OpenSource?; + + if (callback?.Target != null) + { + var target = callback.Target; + switch (openSource) + { + case KeyboardManager.OpenSource.CVRInputFieldKeyboardHandler: + TrySetPlaceholderFromKeyboardHandler(target, ref title, ref placeholder); + break; + case KeyboardManager.OpenSource.MainMenu: + title = TryExtractTitleFromMainMenu(target); + break; + } + } + + if (!string.IsNullOrWhiteSpace(placeholder)) + { + // fallback to placeholder if no title found + if (string.IsNullOrWhiteSpace(title)) title = placeholder; + + // clear placeholder if it is longer than 10 characters + if (placeholder.Length > 10) placeholder = string.Empty; + } + } + + private static void TrySetPlaceholderFromKeyboardHandler(object target, ref string title, ref string placeholder) + { + Type type = target.GetType(); + + TMP_InputField tmpInput = type.GetField("input", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as TMP_InputField; + if (tmpInput != null) + { + if (tmpInput.GetComponentInParent()) title = "VideoPlayer URL or Search"; + if (tmpInput.placeholder is TMP_Text ph) + { + placeholder = ph.text; + return; + } + placeholder = PrettyString(tmpInput.gameObject.name); + return; + } + + InputField legacyInput = type.GetField("inputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as InputField; + if (legacyInput != null) + { + if (legacyInput.placeholder is Text ph) + { + placeholder = ph.text; + return; + } + placeholder = PrettyString(legacyInput.gameObject.name); + return; + } + } + + private static string TryExtractTitleFromMainMenu(object target) + { + Type type = target.GetType(); + string targetId = type.GetField("targetId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as string; + return string.IsNullOrWhiteSpace(targetId) ? null : PrettyString(targetId); + } + + private static string PrettyString(string str) + { + int len = str.Length; + Span buffer = stackalloc char[len * 2]; + int pos = 0; + bool newWord = true; + for (int i = 0; i < len; i++) + { + char c = str[i]; + if (c is '_' or '-') + { + buffer[pos++] = ' '; + newWord = true; + continue; + } + if (char.IsUpper(c) && i > 0 && !newWord) buffer[pos++] = ' '; + buffer[pos++] = newWord ? char.ToUpperInvariant(c) : c; + newWord = false; + } + return new string(buffer[..pos]); + } +} \ No newline at end of file diff --git a/Tinyboard/Properties/AssemblyInfo.cs b/Tinyboard/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b3048a5 --- /dev/null +++ b/Tinyboard/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.Tinyboard.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.Tinyboard))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.Tinyboard))] + +[assembly: MelonInfo( + typeof(NAK.Tinyboard.TinyboardMod), + nameof(NAK.Tinyboard), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Tinyboard" +)] + +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.Tinyboard.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/Tinyboard/README.md b/Tinyboard/README.md new file mode 100644 index 0000000..7ff0922 --- /dev/null +++ b/Tinyboard/README.md @@ -0,0 +1,19 @@ +# Tinyboard + +Makes the keyboard small and smart. + +Few small tweaks to the keyboard: +- Shrinks the keyboard to a size that isn't fit for grandma. +- Adjusts keyboard placement logic to align with the menu that it spawns from. +- Enforces a title on the keyboard input if one is not found. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/Tinyboard/Tinyboard.csproj b/Tinyboard/Tinyboard.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/Tinyboard/Tinyboard.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/Tinyboard/format.json b/Tinyboard/format.json new file mode 100644 index 0000000..da65a56 --- /dev/null +++ b/Tinyboard/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "Tinyboard", + "modversion": "1.0.0", + "gameversion": "2025r180", + "loaderversion": "0.7.2", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Few small tweaks to the keyboard:\n- Shrinks the keyboard to a size that isn't fit for grandma.\n- Adjusts keyboard placement logic to align with the menu that it spawns from.\n- Enforces a title on the keyboard input if one is not found.", + "searchtags": [ + "keyboard", + "menu", + "ui", + "input" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/Tinyboard.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Tinyboard/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file From 226b3695378b56fc63a51cbfc8fa1f153309afa6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 27 Aug 2025 10:22:00 +0000 Subject: [PATCH 188/188] [NAK_CVR_Mods] Update mod list in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d2371e..ed9da33 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,12 @@ | [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | No Download | | [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download | | [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll) | -| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | No Download | +| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RelativeSyncJitterFix.dll) | | [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll) | | [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download | | [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download | | [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll) | +| [Tinyboard](Tinyboard/README.md) | Makes the keyboard small and smart. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/Tinyboard.dll) | | [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll) | ### Experimental Mods

l>v!hN_KAq-@;=jmUTEW|BH8-Ef$%egIo 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�5xq49zbQY+d0RA4|;Xe|2h1QvKZy>x~&6lv=x z9C+xe->lR1zqvtYx%i%Jx|yVa!^`IwlGf z-sVE6*}CIfva$JjB|=QltZIcrCQ8cSTk`cKS;D<(yL`Va)KK#j_&T#ls`6un!8*~X zJOxls_V**mfJE|r5$%&J8DZ41LNPx(N2Kn*78*sQ1GiaKnZI_*OJr{jN~u`liuRNgH34u<<~|E&1L1YhZ+-9@5i-}GaP%E8&3oru_lX}k1$QBnOwaK4 zFv)1k^yWbiD@}XSeU00UFMu{?{>FZUbMzMWX8FL+Mw#3ue(CbrF=HJuKZT1nK5&3- zi7`MAHfQ2gh9s4}J&fhgIV3V5#G4T~i{UOcDx@?JH_AsSaZcG-K-Y~7%3KZ2Q{x_J zVo4YzeqLV z*2eElNro4Z=?@WHQB847=b0VPTk@gIiZIA=@6pPjNrun*>1Gy>{(`tDHAa{yo3Ur~ z^Fvk*y;g>{&KYa9UCaGB5txyr38no1NS~U@<<6;mr+(9@5Eg7q2gN4H1dOBch231vqxk|fAZ;LmMX{(5UwShY(cnvAw@@yU^jmPJC%#8A z%A#Nm39Qn5iN+dBS#1E}qfN5+&A1z}klWpE^|ESTk8!?ygz^f@0!_?>F*Zx(2Yssugd(4KFOs%9Ams|Y|rZZ!2 z&4nR`mz{8Ik{%Izm=`Kjk67Hb9w|7R#X&+_3J26^k_+-a;MuORJc$mD>pf@gERh#rMMr zA5B=;prni8@5`OO>EsM8teGOAIG^*P%&)*TBd-h&c6hZPHzAOXHlpVuKz7e-dhV0I zWH4WFZx|0WOoreuH1JNla3zjN7?5H+Q{x}s=#YGsKoO&XLed@?GrR`+XliXWZlc_8 z6>ShdB_?-7bMk@SK*#ewN8F|9S_K@UQ6qOf^ay3GqDiD{ z_%fp?udHk7SD9SYiF3L^O06?R&RQ(0C@n~tDQ%BF*3t!CH?sP9^zo_Q zZedx4J6uTew{0%BM-qN&3$pavo*sOV*WwO!B_xNMQO31hT!9x%LvbL^w26DSqa|c| zcrmn2Q;!kTD3zuJ*NU3P#@bGOJ*fo8gVJz>lCD^LzD0%xo>Q-2@)mFtgR|?=Ts?Ty z00C%P^;nvvjFcoV?Fw;w>z}M#ChI&ZVICnNdz|}2!=Aq~p1(q7HG(af?Bhq&zx3*A zvT)W%Prc9qPVF{F4@$Dzr_=pM5|r{5EP`?|R$ z3*i$Sf^TC)C2X*a0U2K>(2d5Z%kIA5tZmv-Idk*^fZwa{(1j`JX`!L-%y;Nmq>{S3 z9#SHMnks&`%(Lr=vxGM#_-xQn?vI;29aluw76QujcbrYxBo=uz7Nrs4VMsev_%-6+r#iAK<<-j6@WqqH`19qfW;L7~U*NmDa zqSekbavNbdD8{r!d@#}3NDThEB1+MgnDn13R*)&x1y!Rc-fLHj79PhwH~~{_HGfqv zeAO+2ELl8`{(NLoMqGdUI^|-`5-!D_3yCR2`|wltZ3LgJL5Qomyd2~eCouogWp+!y zQ0S)tDpOdkI^uBpgH5ICahH!es4} zv^ny+zAEe~Ss_Bq-$##=${La}t^{m>DpIG*$ea5E|i$yok-ur|T(sN-! zK92CQb)arH*COGLq_w}s%h9TIoGe{B>pm3aOFe3!@%;Qh-hyO&Sy#i>?^18neEc9K z^~uIvVcXa?pmvHO+_sboL7O9quMf6JWB?)&>q-em*AX>>U=%lh8R_QtQ^g%&>{z^m zRKY7wofz`8A|5=)RX@2;E+KlqbX%DJ$<+6bQiO?K_~bjD^#OQHhN)*R>O!B6#7`UicjLzCE$jt^~8W@gxV?CVRz%@YsZ zsv!kEDKxSbwQBpO7I#yDObks_*C<{21>}+qBr0&zJ|^IyFpR0Tg}iT8S$M-cRo}h* zI9ZGvam!bIb8F%zjCDfa`V~+{v0t&OYuwm4rD~_vmr2GsTdaTCPlU_BkNuo1x`T9= z>Bg3F01-}~R>N*|QR(0Jm0pLPK?|^Y$&ElnxaT$!$61cN8~jjKgs1B|n2M>Dm@XKl zRPCMNKqDBCLAwLTEi ziO&Y_6j*fNc(#vb6KLE)dS`JPd4|9M$YSM&~z4)=Wjkiou9aSM>+{pK`n?#IBs!GQd2^?sAkEQkgDo+ zyBn!`a!oZO#&L4A^tLa^jjt_SY43iI zG3a+H)LdA-8(W$3UM$NTtcDX~gGWBH@{!CGKW~LF_>xJC`DQM{le&-m7_4m$l1hbX zMQ{$o&q_{HF|Va0rb`fzq+DfwHl-L`LXDG|{dKriXMqNX4gD^fiYsoZ)=hUeKs~fO zjx91V?kUj*X&uS=bRu=CjobDqDMc~$jpal836vQ=NDskGtp$#IG!+wgu#$_Gl9R)t z@F!Ttrj?|cW2u#pOLVC*O}r@7HNwln+3~o?IG&&V8vl-~|j7t;>w0_Ae(i+n}AK59&-|?AyTQ29l_&mfqRl*)jkrChiZ4 z2w1=jbp9}Q9mvry=8#vw?WO!uQ!6lQ6&TlC*a&HK|C5YDQZ!yK_MuE{tXVq;Hq}*Rak%e_6m`@cPuT9YOKX zzzdS*Y=uEc@ikl?nuIVC<1|n7bO6Xcvqj{?(#1Sm;D~G$1rB23JPGzB0mO^8fvnRG z2Vxj(?j9)&ZBTwCBa|S|2q~t zj#FZ!tc^hO#>gSh863V&JI8-#p1(N`fzNqjmd9IT2F4bm%b4v~(wz#)Rlh+Ck`mB+D_4z^(jyub*8q)>sGLCd> z%CE)e)~*FC9YpA*fDBNK7vA?Jbz4E5OtGBez&80+b$NfJ6iG?X!=kg68sc!Nt8Yj` ztny!|=lf@_lQrz;j1cw6JQEitnf4hywB#a^uS05q*4z`KZ7^}0J5NcQ8V}2OC6V3M zFs #k=5%RaNxwSZ!*Lvsp}gnqC8=1-uZ6jW9LZ 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