From 5046c2259df2ecbe556077fccb6c9331323589f4 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Sun, 28 Dec 2025 20:52:46 -0600 Subject: [PATCH] Normalize JSON encoding --- .gitattributes | 1 + ShowPlayerInSelfMirror/format.json | 2 +- .../BTKUI/UIAddon.Category.StickersMod.cs | 242 ++++---- Stickers/Integrations/BTKUI/UIAddon.Main.cs | 230 ++++---- Stickers/Properties/AssemblyInfo.cs | 62 +-- .../Stickers/Networking/ModNetwork.Inbound.cs | 518 +++++++++--------- Stickers/Stickers/StickerSystem.Main.cs | 278 +++++----- .../StickerSystem.StickerLifecycle.cs | 390 ++++++------- 8 files changed, 862 insertions(+), 861 deletions(-) diff --git a/.gitattributes b/.gitattributes index dfe0770..a0b7e44 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +*.json text eol=lf \ No newline at end of file diff --git a/ShowPlayerInSelfMirror/format.json b/ShowPlayerInSelfMirror/format.json index ab4bb75..b7cead8 100644 --- a/ShowPlayerInSelfMirror/format.json +++ b/ShowPlayerInSelfMirror/format.json @@ -1,4 +1,4 @@ -{ +{ "_id": -1, "name": "ShowPlayerInSelfMirror", "modversion": "1.0.0", diff --git a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs index b1c2097..a4564ce 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Category.StickersMod.cs @@ -1,122 +1,122 @@ -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 Button _placeStickersButton; - - 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); - - _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.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() - { - 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 OnStickerRestrictionUpdated(bool isRestricted = false) //TODO: add Icon changing, Bono needs to expose the value first. - { - if (_rootPage == null || _placeStickersButton == null) - return; - - 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"; - 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 +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 Button _placeStickersButton; + + 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); + + _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.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() + { + 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 OnStickerRestrictionUpdated(bool isRestricted = false) //TODO: add Icon changing, Bono needs to expose the value first. + { + if (_rootPage == null || _placeStickersButton == null) + return; + + 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"; + 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 } \ No newline at end of file diff --git a/Stickers/Integrations/BTKUI/UIAddon.Main.cs b/Stickers/Integrations/BTKUI/UIAddon.Main.cs index fe2a4d7..67955fe 100644 --- a/Stickers/Integrations/BTKUI/UIAddon.Main.cs +++ b/Stickers/Integrations/BTKUI/UIAddon.Main.cs @@ -1,116 +1,116 @@ -using BTKUILib; -using BTKUILib.UIObjects; -using NAK.Stickers.Networking; -using NAK.Stickers.Utilities; -using System.Reflection; - -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() - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string assemblyName = assembly.GetName().Name; - - // All icons used - https://www.flaticon.com/authors/gohsantosadrive - 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.. - { - MenuTitle = ModSettings.SM_SettingsCategory + $" (Network Version v{ModNetwork.NetworkVersion})", - 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: - 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; + +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() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string assemblyName = assembly.GetName().Name; + + // All icons used - https://www.flaticon.com/authors/gohsantosadrive + 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.. + { + MenuTitle = ModSettings.SM_SettingsCategory + $" (Network Version v{ModNetwork.NetworkVersion})", + 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: + 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 diff --git a/Stickers/Properties/AssemblyInfo.cs b/Stickers/Properties/AssemblyInfo.cs index e7e3e30..c4cb6f1 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("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.Stickers.Properties; -internal static class AssemblyInfoParams -{ - public const string Version = "1.1.1"; - 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("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.Stickers.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.1.1"; + public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs index 2e65fcb..5b4224f 100644 --- a/Stickers/Stickers/Networking/ModNetwork.Inbound.cs +++ b/Stickers/Stickers/Networking/ModNetwork.Inbound.cs @@ -1,260 +1,260 @@ -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; -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.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; - } - - 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); - msg.Read(out int size); - msg.Read(out float opacity); - - 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, (StickerSize)size, opacity); - } - - 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.Player; +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.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; + } + + 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); + msg.Read(out int size); + msg.Read(out float opacity); + + 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, (StickerSize)size, opacity); + } + + 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 diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index b377ccb..556352f 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -1,140 +1,140 @@ -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 ABI.CCK.Components; -using NAK.Stickers.Integrations; - -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() - { - // TODO: this can be spammed by world author toggling CVRWorld.enabled state - CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); - 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); - BetterScheduleSystem.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 OnWorldLoad() - { - CVRDataStore worldDS = CVRWorld.Instance.DataStore; - // IsRestrictedInstance = worldDS && worldDS.GetValue("StickersMod-ForceDisable"); - if (IsRestrictedInstance) StickerMod.Logger.Msg("Stickers are restricted by the world author."); - BTKUIAddon.OnStickerRestrictionUpdated(IsRestrictedInstance); - } - - private void OnWorldUnload() - { - IsRestrictedInstance = false; - CleanupAllButSelf(); - } - - #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; - // } - // } - - public bool IsRestrictedInstance { get; internal set; } - - 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 && !IsRestrictedInstance; // ensure cannot enter when restricted - 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 NAK.Stickers.Networking; +using NAK.Stickers.Utilities; +using ABI.CCK.Components; +using NAK.Stickers.Integrations; + +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() + { + // TODO: this can be spammed by world author toggling CVRWorld.enabled state + CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); + 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); + BetterScheduleSystem.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 OnWorldLoad() + { + CVRDataStore worldDS = CVRWorld.Instance.DataStore; + // IsRestrictedInstance = worldDS && worldDS.GetValue("StickersMod-ForceDisable"); + if (IsRestrictedInstance) StickerMod.Logger.Msg("Stickers are restricted by the world author."); + BTKUIAddon.OnStickerRestrictionUpdated(IsRestrictedInstance); + } + + private void OnWorldUnload() + { + IsRestrictedInstance = false; + CleanupAllButSelf(); + } + + #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; + // } + // } + + public bool IsRestrictedInstance { get; internal set; } + + 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 && !IsRestrictedInstance; // ensure cannot enter when restricted + 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 bab30b5..7d02f3a 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -1,195 +1,195 @@ -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, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value, position, forward, up, alignWithNormal, SelectedStickerSlot)) - return false; // failed - - // placed, now network - ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value); - return true; - } - - private bool AttemptPlaceSticker(string playerId, StickerSize size, float opacity, 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; - - // 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, size); - return true; - } - - stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot, size, opacity); - 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(); - } - - public 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, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value, position, forward, up, true, SelectedStickerSlot, true); - } - - public void UpdateStickerPreview() - { - if (!IsInStickerMode) return; - - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - localStickerData.ClearPreview(); // clear prior frames sticker preview - 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, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value, position, forward, up, alignWithNormal, SelectedStickerSlot)) + return false; // failed + + // placed, now network + ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value); + return true; + } + + private bool AttemptPlaceSticker(string playerId, StickerSize size, float opacity, 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; + + // 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, size); + return true; + } + + stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot, size, opacity); + 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(); + } + + public 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, ModSettings.Entry_StickerSize.Value, ModSettings.Entry_StickerOpacity.Value, position, forward, up, true, SelectedStickerSlot, true); + } + + public void UpdateStickerPreview() + { + if (!IsInStickerMode) return; + + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); // clear prior frames sticker preview + localStickerData.UpdatePreview(SelectedStickerSlot); + } + + public void ClearStickerPreview() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); + } + + #endregion Sticker Lifecycle +}