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 01/13] [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 02/13] [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 03/13] [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 04/13] [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 05/13] [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 06/13] [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 07/13] [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 08/13] [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 09/13] [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 10/13] [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 11/13] [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 12/13] [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 13/13] [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",