From 2375678a59b21b244d4c7b8c342c6a90c0992e42 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:22:10 -0500 Subject: [PATCH] [OriginShift] Initial Fuckup --- NAK_CVR_Mods.sln | 6 + .../BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs | 135 +++++++++ OriginShift/Integrations/BTKUI/BtkuiAddon.cs | 64 ++++ .../Integrations/BTKUI/BtkuiAddon_Utils.cs | 70 +++++ OriginShift/Main.cs | 103 +++++++ OriginShift/ModSettings.cs | 41 +++ OriginShift/Networking/ModNetwork.cs | 151 ++++++++++ OriginShift/OriginShift.csproj | 19 ++ .../Components/OriginShiftController.cs | 133 +++++++++ .../Receivers/OriginShiftEventReceiver.cs | 78 +++++ .../OriginShiftParticleSystemReceiver.cs | 57 ++++ .../Receivers/OriginShiftRigidbodyReceiver.cs | 46 +++ .../OriginShiftTrailRendererReceiver.cs | 57 ++++ .../Receivers/OriginShiftTransformReceiver.cs | 34 +++ ...tterBetterCharacterControllerExtensions.cs | 57 ++++ .../Extensions/PlayerSetupExtensions.cs | 19 ++ .../OriginShift/Hacks/OcclusionCullingHack.cs | 50 ++++ .../OriginShiftOcclusionCullingDisabler.cs | 45 +++ OriginShift/OriginShift/OriginShiftManager.cs | 254 ++++++++++++++++ .../Dynamics/OriginShiftDbAvatarReceiver.cs | 68 +++++ .../OriginShift/Player/OriginShiftMonitor.cs | 93 ++++++ .../Player/OriginShiftNetIkReceiver.cs | 47 +++ .../OriginShift/Utility/DebugTextDisplay.cs | 99 +++++++ .../Utility/RendererReflectionUtility.cs | 33 +++ OriginShift/Patches.cs | 276 ++++++++++++++++++ OriginShift/Properties/AssemblyInfo.cs | 34 +++ OriginShift/README.md | 14 + .../Resources/OriginShift-Icon-Active.png | Bin 0 -> 13012 bytes .../Resources/OriginShift-Icon-Forced.png | Bin 0 -> 13032 bytes .../Resources/OriginShift-Icon-Inactive.png | Bin 0 -> 11885 bytes OriginShift/format.json | 23 ++ References.Items.props | 28 -- copy_and_nstrip_dll.ps1 | 2 +- 33 files changed, 2107 insertions(+), 29 deletions(-) create mode 100644 OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs create mode 100644 OriginShift/Integrations/BTKUI/BtkuiAddon.cs create mode 100644 OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs create mode 100644 OriginShift/Main.cs create mode 100644 OriginShift/ModSettings.cs create mode 100644 OriginShift/Networking/ModNetwork.cs create mode 100644 OriginShift/OriginShift.csproj create mode 100644 OriginShift/OriginShift/Components/OriginShiftController.cs create mode 100644 OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs create mode 100644 OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs create mode 100644 OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs create mode 100644 OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs create mode 100644 OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs create mode 100644 OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs create mode 100644 OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs create mode 100644 OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs create mode 100644 OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs create mode 100644 OriginShift/OriginShift/OriginShiftManager.cs create mode 100644 OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs create mode 100644 OriginShift/OriginShift/Player/OriginShiftMonitor.cs create mode 100644 OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs create mode 100644 OriginShift/OriginShift/Utility/DebugTextDisplay.cs create mode 100644 OriginShift/OriginShift/Utility/RendererReflectionUtility.cs create mode 100644 OriginShift/Patches.cs create mode 100644 OriginShift/Properties/AssemblyInfo.cs create mode 100644 OriginShift/README.md create mode 100644 OriginShift/Resources/OriginShift-Icon-Active.png create mode 100644 OriginShift/Resources/OriginShift-Icon-Forced.png create mode 100644 OriginShift/Resources/OriginShift-Icon-Inactive.png create mode 100644 OriginShift/format.json diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 2bf430e..c05398b 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReconnectionSystemFix", "Re EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AASDefaultProfileFix", "AASDefaultProfileFix\AASDefaultProfileFix.csproj", "{C6794B18-E785-4F91-A517-3A2A8006E008}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginShift", "OriginShift\OriginShift.csproj", "{F381F604-9C16-4870-AD49-4BD7CA3F36DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -177,6 +179,10 @@ Global {C6794B18-E785-4F91-A517-3A2A8006E008}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.ActiveCfg = Release|Any CPU {C6794B18-E785-4F91-A517-3A2A8006E008}.Release|Any CPU.Build.0 = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F381F604-9C16-4870-AD49-4BD7CA3F36DC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs b/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs new file mode 100644 index 0000000..02eda40 --- /dev/null +++ b/OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs @@ -0,0 +1,135 @@ +using ABI_RC.Core.Player; +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using NAK.OriginShift; + +namespace NAK.OriginShiftMod.Integrations +{ + public static partial class BtkUiAddon + { + private static Category _ourCategory; + + private static Button _ourMainButton; + private static bool _isForcedMode; + + private static ToggleButton _ourToggle; + + private static void Setup_OriginShiftModCategory(Page page) + { + // dear category + _ourCategory = AddMelonCategory(ref page, ModSettings.OSM_SettingsCategory); + + // the button + _ourMainButton = _ourCategory.AddButton(string.Empty, string.Empty, string.Empty, ButtonStyle.TextOnly); + _ourMainButton.OnPress += OnMainButtonClick; + SetButtonState(OriginShiftManager.OriginShiftState.Inactive); // default state + + // compatibility mode + _ourToggle = _ourCategory.AddToggle(ModSettings.EntryCompatibilityMode.DisplayName, + ModSettings.EntryCompatibilityMode.Description, ModSettings.EntryCompatibilityMode.Value); + _ourToggle.OnValueUpdated += OnCompatibilityModeToggle; + + // listen for state changes + OriginShiftManager.OnStateChanged += OnOriginShiftStateChanged; + } + + #region Category Actions + + private static void UpdateCategoryModUserCount() + { + int modUsers = 1; // we are always here :3 + int playerCount = CVRPlayerManager.Instance.NetworkPlayers.Count + 1; // +1 for us :3 + _ourCategory.CategoryName = $"{ModSettings.OSM_SettingsCategory} ({modUsers}/{playerCount})"; + } + + #endregion Category Actions + + #region Button Actions + + private static void SetButtonState(OriginShiftManager.OriginShiftState state) + { + switch (state) + { + default: + case OriginShiftManager.OriginShiftState.Inactive: + _ourMainButton.ButtonText = "Inactive"; + _ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftConfig"; + _ourMainButton.ButtonTooltip = "World does not use Origin Shift."; + _ourMainButton.ButtonIcon = "OriginShift-Icon-Inactive"; + break; + case OriginShiftManager.OriginShiftState.Active: + _ourMainButton.ButtonText = "Active"; + _ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftConfig"; + _ourMainButton.ButtonTooltip = "World uses Origin Shift."; + _ourMainButton.ButtonIcon = "OriginShift-Icon-Active"; + break; + case OriginShiftManager.OriginShiftState.Forced: + _ourMainButton.ButtonText = "Forced"; + _ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftCopy"; + _ourMainButton.ButtonTooltip = "World is forced to use Origin Shift."; + _ourMainButton.ButtonIcon = "OriginShift-Icon-Forced"; + break; + } + } + + private static void OnMainButtonClick() + { + // if active, return as world is using Origin Shift + if (OriginShiftManager.Instance.CurrentState + is OriginShiftManager.OriginShiftState.Active) + return; + + if (_isForcedMode) + { + OriginShiftManager.Instance.ResetManager(); + } + else + { + QuickMenuAPI.ShowConfirm("Force Origin Shift", + "Are you sure you want to force Origin Shift for this world? " + + "This is a highly experimental feature that may break the world in unexpected ways!", + () => + { + OriginShiftManager.Instance.ForceManager(); + }); + } + } + + private static void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) + { + _isForcedMode = state == OriginShiftManager.OriginShiftState.Forced; + SetButtonState(state); + SetToggleLocked(_isForcedMode); + } + + #endregion Button Actions + + #region Toggle Actions + + private static void SetToggleLocked(bool value) + { + if (value) + { + // lock the toggle + _ourToggle.ToggleTooltip = "This setting is locked while Origin Shift is forced in Public instances."; + _ourToggle.ToggleValue = true; + _ourToggle.Disabled = true; + } + else + { + // unlock the toggle + _ourToggle.ToggleValue = ModSettings.EntryCompatibilityMode.Value; + _ourToggle.ToggleTooltip = ModSettings.EntryCompatibilityMode.Description; + _ourToggle.Disabled = false; + } + } + + private static void OnCompatibilityModeToggle(bool value) + { + ModSettings.EntryCompatibilityMode.Value = value; + } + + #endregion Toggle Actions + } +} \ No newline at end of file diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon.cs b/OriginShift/Integrations/BTKUI/BtkuiAddon.cs new file mode 100644 index 0000000..97ff328 --- /dev/null +++ b/OriginShift/Integrations/BTKUI/BtkuiAddon.cs @@ -0,0 +1,64 @@ +using ABI_RC.Core.Player; +using BTKUILib; +using BTKUILib.UIObjects; +using NAK.OriginShift; + +namespace NAK.OriginShiftMod.Integrations +{ + public static partial class BtkUiAddon + { + private static Page _miscTabPage; + private static string _miscTabElementID; + + public static void Initialize() + { + Prepare_Icons(); + Setup_OriginShiftTab(); + } + + #region Initialization + + private static void Prepare_Icons() + { + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Active", + GetIconStream("OriginShift-Icon-Active.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Inactive", + GetIconStream("OriginShift-Icon-Inactive.png")); + QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Forced", + GetIconStream("OriginShift-Icon-Forced.png")); + } + + private static void Setup_OriginShiftTab() + { + _miscTabPage = QuickMenuAPI.MiscTabPage; + _miscTabElementID = _miscTabPage.ElementID; + QuickMenuAPI.UserJoin += OnUserJoinLeave; + QuickMenuAPI.UserLeave += OnUserJoinLeave; + QuickMenuAPI.OnWorldLeave += OnWorldLeave; + + // // Origin Shift Mod + Setup_OriginShiftModCategory(_miscTabPage); + // + // // Origin Shift Tool + // Setup_OriginShiftToolCategory(_miscTabPage); + // + // // Universal Shifting Settings + // Setup_UniversalShiftingSettings(_miscTabPage); + // + // // Debug Options + // Setup_DebugOptionsCategory(_miscTabPage); + } + + #endregion + + #region Player Count Display + + private static void OnWorldLeave() + => UpdateCategoryModUserCount(); + + private static void OnUserJoinLeave(CVRPlayerEntity _) + => UpdateCategoryModUserCount(); + + #endregion + } +} diff --git a/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs b/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs new file mode 100644 index 0000000..226af66 --- /dev/null +++ b/OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs @@ -0,0 +1,70 @@ +using System.Reflection; +using BTKUILib; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; +using MelonLoader; +using UnityEngine; + +namespace NAK.OriginShiftMod.Integrations +{ + public static partial class BtkUiAddon + { + #region Melon Preference Helpers + + private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry entry) + { + ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value); + toggle.OnValueUpdated += b => entry.Value = b; + return toggle; + } + + private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry entry, float min, + float max, int decimalPlaces = 2, bool allowReset = true) + { + SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description, + Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset); + slider.OnValueUpdated += f => entry.Value = f; + return slider; + } + + private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { + Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); + button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s); + return button; + } + + private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly) + { + Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle); + button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f); + return button; + } + + private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry entry, bool showHeader = true) + { + Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value); + category.OnCollapse += b => entry.Value = b; + return category; + } + + private static Category AddMelonCategory(ref Page page, string displayName, bool showHeader = true) + { + Category category = page.AddCategory(displayName, showHeader); + return category; + } + + #endregion Melon Preference Helpers + + #region Icon Utils + + private static Stream GetIconStream(string iconName) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string assemblyName = assembly.GetName().Name; + return assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}"); + } + + #endregion Icon Utils + } +} \ No newline at end of file diff --git a/OriginShift/Main.cs b/OriginShift/Main.cs new file mode 100644 index 0000000..0724c39 --- /dev/null +++ b/OriginShift/Main.cs @@ -0,0 +1,103 @@ +#if !UNITY_EDITOR +using System.Globalization; +using ABI_RC.Core.UI; +using ABI_RC.Core.Util.AssetFiltering; +using ABI_RC.Systems.Movement; +using MelonLoader; +using NAK.OriginShift.Components; +using NAK.OriginShiftMod.Integrations; +using UnityEngine; + +namespace NAK.OriginShift; + +// Links I looked at: +// Controller/Event Listener Setup: https://manuel-rauber.com/2022/04/06/floating-origin-in-unity/amp/ +// Move Scene Roots: https://gist.github.com/brihernandez/9ebbaf35070181fa1ee56f9e702cc7a5 +// Looked cool but didn't really find anything to use: https://docs.coherence.io/coherence-sdk-for-unity/world-origin-shifting +// One Day when we move to 2022: https://docs.unity3d.com/6000.0/Documentation/Manual/LightProbes-Moving.html + +public class OriginShiftMod : MelonMod +{ + internal static MelonLogger.Instance Logger; + + #region Melon Mod Overrides + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ModSettings.Initialize(); + + ApplyPatches(typeof(Patches.BetterBetterCharacterControllerPatches)); // origin shift monitor + ApplyPatches(typeof(Patches.CVRSpawnablePatches)); // components & remote spawnable pos + + // Compatibility Mode + ApplyPatches(typeof(Patches.PlayerSetupPatches)); // net ik, camera occlusion culling + ApplyPatches(typeof(Patches.Comms_ClientPatches)); // voice pos + ApplyPatches(typeof(Patches.CVRSyncHelperPatches)); // spawnable pos + ApplyPatches(typeof(Patches.PuppetMasterPatches)); // remote player pos + ApplyPatches(typeof(Patches.CVRObjectSyncPatches)); // remote object pos + + ApplyPatches(typeof(Patches.DbJobsAvatarManagerPatches)); // dynamic bones + ApplyPatches(typeof(Patches.CVRPortalManagerPatches)); // portals + + ApplyPatches(typeof(Patches.PortableCameraPatches)); // camera occlusion culling + ApplyPatches(typeof(Patches.PathingCameraPatches)); // camera occlusion culling + + // add our components to the world whitelist + WorldFilter._Base.Add(typeof(OriginShiftController)); // base component + WorldFilter._Base.Add(typeof(OriginShiftEventReceiver)); // generic event listener + + WorldFilter._Base.Add(typeof(OriginShiftParticleSystemReceiver)); // particle system + WorldFilter._Base.Add(typeof(OriginShiftRigidbodyReceiver)); // rigidbody + WorldFilter._Base.Add(typeof(OriginShiftTrailRendererReceiver)); // trail renderer + WorldFilter._Base.Add(typeof(OriginShiftTransformReceiver)); // transform + + InitializeIntegration("BTKUILib", BtkUiAddon.Initialize); + } + + public override void OnUpdate() + { + if (BetterBetterCharacterController.Instance == null + || !BetterBetterCharacterController.Instance.IsFlying() + || Input.GetKey(KeyCode.Mouse2) + || Cursor.lockState != CursorLockMode.Locked) + return; + + BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier = Math.Max(0f, + BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier + Input.mouseScrollDelta.y); + if (Input.mouseScrollDelta.y != 0f) + CohtmlHud.Instance.ViewDropTextImmediate("(Local) ScrollFlight", + BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier.ToString(CultureInfo + .InvariantCulture), "Speed multiplier"); + } + + #endregion Melon Mod Overrides + + #region Melon Mod Utilities + + private static void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + Logger.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} +#endif \ No newline at end of file diff --git a/OriginShift/ModSettings.cs b/OriginShift/ModSettings.cs new file mode 100644 index 0000000..e637755 --- /dev/null +++ b/OriginShift/ModSettings.cs @@ -0,0 +1,41 @@ +using MelonLoader; + +namespace NAK.OriginShift; + +internal static class ModSettings +{ + #region Constants + + internal const string ModName = nameof(OriginShift); + internal const string OSM_SettingsCategory = "Origin Shift Mod"; + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryCompatibilityMode = + Category.CreateEntry("EntryCompatibilityMode", true, + "Compatibility Mode", description: "Origin Shifts locally, but modifies outbound network messages to be compatible with non-Origin Shifted clients."); + + #endregion Melon Preferences + + #region Settings Managment + + internal static void Initialize() + { + foreach (MelonPreferences_Entry setting in Category.Entries) + setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); + + OnSettingsChanged(); + } + + private static void OnSettingsChanged(object oldValue = null, object newValue = null) + { + OriginShiftManager.CompatibilityMode = EntryCompatibilityMode.Value; + } + + #endregion Settings Managment +} \ No newline at end of file diff --git a/OriginShift/Networking/ModNetwork.cs b/OriginShift/Networking/ModNetwork.cs new file mode 100644 index 0000000..c9e6b52 --- /dev/null +++ b/OriginShift/Networking/ModNetwork.cs @@ -0,0 +1,151 @@ +// using ABI_RC.Core.Networking; +// using ABI_RC.Systems.ModNetwork; +// using DarkRift; +// using UnityEngine; +// +// namespace NAK.OriginShift.Networking; +// +// public static class ModNetwork +// { +// public static bool Debug_NetworkInbound = false; +// public static bool Debug_NetworkOutbound = false; +// +// private static bool _isSubscribedToModNetwork; +// +// private struct MovementParentSyncData +// { +// public bool HasSyncedThisData; +// public int MarkerHash; +// public Vector3 RootPosition; +// public Vector3 RootRotation; +// // public Vector3 HipPosition; +// // public Vector3 HipRotation; +// } +// +// private static MovementParentSyncData _latestMovementParentSyncData; +// +// #region Constants +// +// private const string ModId = "MelonMod.NAK.RelativeSync"; +// +// #endregion +// +// #region Enums +// +// private enum MessageType : byte +// { +// MovementParentOrChair = 0 +// //RelativePickup = 1, +// //RelativeAttachment = 2, +// } +// +// #endregion +// +// #region Mod Network Internals +// +// internal static void Subscribe() +// { +// ModNetworkManager.Subscribe(ModId, OnMessageReceived); +// +// _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); +// if (!_isSubscribedToModNetwork) +// Debug.LogError("Failed to subscribe to Mod Network!"); +// } +// +// // Called right after NetworkRootDataUpdate.Submit() +// internal static void SendRelativeSyncUpdate() +// { +// if (!_isSubscribedToModNetwork) +// return; +// +// if (_latestMovementParentSyncData.HasSyncedThisData) +// return; +// +// SendMessage(MessageType.MovementParentOrChair, _latestMovementParentSyncData.MarkerHash, +// _latestMovementParentSyncData.RootPosition, _latestMovementParentSyncData.RootRotation); +// +// _latestMovementParentSyncData.HasSyncedThisData = true; +// } +// +// public static void SetLatestRelativeSync( +// int markerHash, +// Vector3 position, Vector3 rotation) +// { +// // check if the data has changed +// if (_latestMovementParentSyncData.MarkerHash == markerHash +// && _latestMovementParentSyncData.RootPosition == position +// && _latestMovementParentSyncData.RootRotation == rotation) +// return; // no need to update (shocking) +// +// _latestMovementParentSyncData.HasSyncedThisData = false; // reset +// _latestMovementParentSyncData.MarkerHash = markerHash; +// _latestMovementParentSyncData.RootPosition = position; +// _latestMovementParentSyncData.RootRotation = rotation; +// } +// +// private static void SendMessage(MessageType messageType, int markerHash, Vector3 position, Vector3 rotation) +// { +// if (!IsConnectedToGameNetwork()) +// return; +// +// using ModNetworkMessage modMsg = new(ModId); +// modMsg.Write((byte)messageType); +// modMsg.Write(markerHash); +// modMsg.Write(position); +// modMsg.Write(rotation); +// modMsg.Send(); +// +// if (Debug_NetworkOutbound) +// Debug.Log( +// $"[Outbound] MessageType: {messageType}, MarkerHash: {markerHash}, Position: {position}, " + +// $"Rotation: {rotation}"); +// } +// +// private static void OnMessageReceived(ModNetworkMessage msg) +// { +// msg.Read(out byte msgTypeRaw); +// +// if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) +// return; +// +// switch ((MessageType)msgTypeRaw) +// { +// case MessageType.MovementParentOrChair: +// msg.Read(out int markerHash); +// msg.Read(out Vector3 receivedPosition); +// msg.Read(out Vector3 receivedRotation); +// // msg.Read(out Vector3 receivedHipPosition); +// // msg.Read(out Vector3 receivedHipRotation); +// +// OnNetworkPositionUpdateReceived(msg.Sender, markerHash, receivedPosition, receivedRotation); +// +// if (Debug_NetworkInbound) +// Debug.Log($"[Inbound] Sender: {msg.Sender}, MarkerHash: {markerHash}, " + +// $"Position: {receivedPosition}, Rotation: {receivedRotation}"); +// break; +// default: +// Debug.LogError($"Invalid message type received from: {msg.Sender}"); +// break; +// } +// } +// +// #endregion +// +// #region Private Methods +// +// private static bool IsConnectedToGameNetwork() +// { +// return NetworkManager.Instance != null +// && NetworkManager.Instance.GameNetwork != null +// && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; +// } +// +// private static void OnNetworkPositionUpdateReceived( +// string sender, int markerHash, +// Vector3 position, Vector3 rotation) +// { +// RelativeSyncManager.ApplyRelativeSync(sender, markerHash, position, rotation); +// } +// +// #endregion +// } \ No newline at end of file diff --git a/OriginShift/OriginShift.csproj b/OriginShift/OriginShift.csproj new file mode 100644 index 0000000..36bb74e --- /dev/null +++ b/OriginShift/OriginShift.csproj @@ -0,0 +1,19 @@ + + + + Sprays + + + + ..\.ManagedLibs\BTKUILib.dll + + + + + + + + + + + diff --git a/OriginShift/OriginShift/Components/OriginShiftController.cs b/OriginShift/OriginShift/Components/OriginShiftController.cs new file mode 100644 index 0000000..6704768 --- /dev/null +++ b/OriginShift/OriginShift/Components/OriginShiftController.cs @@ -0,0 +1,133 @@ +using UnityEngine; + +#if !UNITY_EDITOR +using UnityEngine.SceneManagement; +using NAK.OriginShift.Utility; +#endif + +// Creator Exposed component + +namespace NAK.OriginShift.Components +{ + public class OriginShiftController : MonoBehaviour + { + public static OriginShiftController Instance { get; private set; } + + #region Serialized Fields + + [Header("Config / Shift Params")] + + [SerializeField] private bool _shiftVertical = true; + [SerializeField] [Range(10f, 2500f)] private float _shiftThreshold = 15f; + + [Header("Config / Scene Objects")] + + [SerializeField] private bool _autoMoveSceneRoots = true; + [SerializeField] private Transform[] _toShiftTransforms = Array.Empty(); + + [Header("Config / Additive Objects")] + + [SerializeField] private bool _shiftRemotePlayers = true; + [SerializeField] private bool _shiftSpawnedObjects = true; + + #endregion Serialized Fields + + #region Internal Fields + + internal bool IsForced { get; set; } + + #endregion Internal Fields + +#if !UNITY_EDITOR + + public static float ORIGIN_SHIFT_THRESHOLD = 15f; + + #region Unity Events + + private void Awake() + { + if (Instance != null + && Instance != this) + { + Destroy(this); + OriginShiftMod.Logger.Error("Only one OriginShiftController can exist in a scene."); + return; + } + Instance = this; + } + + private void Start() + { + // set threshold (we can not support dynamic threshold change) + ORIGIN_SHIFT_THRESHOLD = _shiftThreshold; + + OriginShiftManager.OnOriginShifted += OnOriginShifted; + OriginShiftManager.Instance.SetupManager(IsForced); + + // if auto, we will just move everything :) + if (_autoMoveSceneRoots) + GetAllSceneRootTransforms(); + + // if we have scene roots, we will anchor all static renderers + if (_toShiftTransforms.Length != 0) + AnchorAllStaticRenderers(); + } + + private void OnDestroy() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + OriginShiftManager.Instance.ResetManager(); + } + + #endregion Unity Events + + #region Private Methods + + private void GetAllSceneRootTransforms() + { + Scene scene = gameObject.scene; + var sceneRoots = scene.GetRootGameObjects(); + _toShiftTransforms = new Transform[sceneRoots.Length + 1]; // +1 for the static batch anchor + for (var i = 0; i < sceneRoots.Length; i++) _toShiftTransforms[i] = sceneRoots[i].transform; + } + + private void AnchorAllStaticRenderers() + { + // create an anchor object at 0,0,0 + Transform anchor = new GameObject("NAK.StaticBatchAnchor").transform; + anchor.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + anchor.localScale = Vector3.one; + + // add to end of root transforms + _toShiftTransforms[^1] = anchor; + + // crawl all children and find Renderers part of static batch + foreach (Transform toShiftTransform in _toShiftTransforms) + { + var renderers = toShiftTransform.GetComponentsInChildren(true); + foreach (Renderer renderer in renderers) + { + if (renderer.isPartOfStaticBatch) // access staticBatchRootTransform using reflection and override it + RendererReflectionUtility.SetStaticBatchRootTransform(renderer, anchor); + } + } + } + + #endregion Private Methods + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 shift) + { + foreach (Transform toShiftTransform in _toShiftTransforms) + { + if (toShiftTransform == null) continue; // skip nulls + toShiftTransform.position += shift; + } + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs new file mode 100644 index 0000000..ee7b81b --- /dev/null +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftEventReceiver.cs @@ -0,0 +1,78 @@ +using UnityEngine; +using UnityEngine.Events; + +#if !UNITY_EDITOR +using ABI_RC.Core.Util.AssetFiltering; +#endif + +namespace NAK.OriginShift.Components +{ + public class OriginShiftEventReceiver : MonoBehaviour + { + #region Serialized Fields + + [SerializeField] private UnityEvent _onOriginShifted = new(); + [SerializeField] private bool _filterChunkBoundary; + [SerializeField] private Vector3 _chunkBoundaryMin = Vector3.zero; + [SerializeField] private Vector3 _chunkBoundaryMax = Vector3.one; + + #endregion Serialized Fields + +#if !UNITY_EDITOR + + #region Private Fields + + private bool _isInitialized; + + #endregion Private Fields + + #region Unity Events + + private void OnEnable() + { + if (!_isInitialized) + { + SharedFilter.SanitizeUnityEvents("OriginShiftEventReceiver", _onOriginShifted); + _isInitialized = true; + } + + OriginShiftManager.OnOriginShifted += HandleOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= HandleOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void HandleOriginShifted(Vector3 shift) + { + if (_filterChunkBoundary && !IsWithinChunkBoundary(shift)) + return; + + // wrap user-defined event because the user can't be trusted + try + { + _onOriginShifted.Invoke(); + } + catch (Exception e) + { + OriginShiftMod.Logger.Error("OriginShiftEventReceiver: Exception invoking OnOriginShifted event: " + e, this); + } + } + + private bool IsWithinChunkBoundary(Vector3 shift) + { + return shift.x >= _chunkBoundaryMin.x && shift.x <= _chunkBoundaryMax.x && + shift.y >= _chunkBoundaryMin.y && shift.y <= _chunkBoundaryMax.y && + shift.z >= _chunkBoundaryMin.z && shift.z <= _chunkBoundaryMax.z; + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs new file mode 100644 index 0000000..819b351 --- /dev/null +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftParticleSystemReceiver.cs @@ -0,0 +1,57 @@ +using UnityEngine; + +namespace NAK.OriginShift.Components +{ + public class OriginShiftParticleSystemReceiver : MonoBehaviour + { +#if !UNITY_EDITOR + + // max particles count cause i said so 2 + private static readonly ParticleSystem.Particle[] _tempParticles = new ParticleSystem.Particle[10000]; + + private ParticleSystem[] _particleSystems; + + #region Unity Events + + private void Start() + { + _particleSystems = GetComponentsInChildren(true); + if (_particleSystems.Length == 0) + { + OriginShiftMod.Logger.Error("OriginShiftParticleSystemReceiver: No ParticleSystems found on GameObject: " + gameObject.name, this); + enabled = false; + } + } + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 offset) + { + foreach (ParticleSystem particleSystem in _particleSystems) + ShiftParticleSystem(particleSystem, offset); + } + + private static void ShiftParticleSystem(ParticleSystem particleSystem, Vector3 offset) + { + int particleCount = particleSystem.GetParticles(_tempParticles); + for (int i = 0; i < particleCount; i++) _tempParticles[i].position += offset; + particleSystem.SetParticles(_tempParticles, particleCount); + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs new file mode 100644 index 0000000..ae2ca54 --- /dev/null +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftRigidbodyReceiver.cs @@ -0,0 +1,46 @@ +using UnityEngine; + +namespace NAK.OriginShift.Components +{ + public class OriginShiftRigidbodyReceiver : MonoBehaviour + { +#if !UNITY_EDITOR + + private Rigidbody _rigidbody; + + #region Unity Events + + private void Start() + { + _rigidbody = GetComponentInChildren(); + if (_rigidbody == null) + { + OriginShiftMod.Logger.Error("OriginShiftRigidbodyReceiver: No Rigidbody found on GameObject: " + gameObject.name, this); + enabled = false; + } + } + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 shift) + { + _rigidbody.position += shift; + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs new file mode 100644 index 0000000..da1140c --- /dev/null +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftTrailRendererReceiver.cs @@ -0,0 +1,57 @@ +using UnityEngine; + +namespace NAK.OriginShift.Components +{ + public class OriginShiftTrailRendererReceiver : MonoBehaviour + { +#if !UNITY_EDITOR + + // max positions count cause i said so + private static readonly Vector3[] _tempPositions = new Vector3[10000]; + + private TrailRenderer[] _trailRenderers; + + #region Unity Events + + private void Start() + { + _trailRenderers = GetComponentsInChildren(true); + if (_trailRenderers.Length == 0) + { + OriginShiftMod.Logger.Error("OriginShiftTrailRendererReceiver: No TrailRenderers found on GameObject: " + gameObject.name, this); + enabled = false; + } + } + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 offset) + { + foreach (TrailRenderer trailRenderer in _trailRenderers) + ShiftTrailRenderer(trailRenderer, offset); + } + + private static void ShiftTrailRenderer(TrailRenderer trailRenderer, Vector3 offset) + { + trailRenderer.GetPositions(_tempPositions); + for (var i = 0; i < _tempPositions.Length; i++) _tempPositions[i] += offset; + trailRenderer.SetPositions(_tempPositions); + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs b/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs new file mode 100644 index 0000000..1370e34 --- /dev/null +++ b/OriginShift/OriginShift/Components/Receivers/OriginShiftTransformReceiver.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace NAK.OriginShift.Components +{ + public class OriginShiftTransformReceiver : MonoBehaviour + { +#if !UNITY_EDITOR + + #region Unity Events + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 shift) + { + transform.position += shift; + } + + #endregion Origin Shift Events + +#endif + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs b/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs new file mode 100644 index 0000000..43edb78 --- /dev/null +++ b/OriginShift/OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs @@ -0,0 +1,57 @@ +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Systems.Movement; +using UnityEngine; + +namespace NAK.OriginShift.Extensions; + +public static class BetterBetterCharacterControllerExtensions +{ + /// + /// Offsets the player by the given vector. + /// This is a simple move operation that does not affect velocity, grounded state, movement parent, ect. + /// + /// + /// + public static void OffsetBy(this BetterBetterCharacterController controller, Vector3 offset) + { + controller.MoveTo(PlayerSetup.Instance.GetPlayerPosition() + offset); + } + + /// + /// Moves the player to the target position while keeping velocity, grounded state, movement parent, ect. + /// Allows moving the player in a way that is meant to be seamless, unlike TeleportTo or SetPosition. + /// + /// + /// + /// + public static void MoveTo(this BetterBetterCharacterController controller, Vector3 targetPos, + bool interpolate = false) + { + // character controller is not built to account for the player's VR offset + Vector3 vector = targetPos - PlayerSetup.Instance.GetPlayerPosition(); + Vector3 vector2 = controller.GetPosition() + vector; + + if (!CVRTools.IsWithinMaxBounds(vector2)) + { + // yeah, ill play your game + CommonTools.LogAuto(CommonTools.LogLevelType_t.Warning, + "Attempted to move player further than the maximum allowed bounds.", "", + "OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs", + "MoveTo", 19); + return; + } + + controller.TeleportPosition(vector2, interpolate); // move player + controller.SetVelocity(controller.characterMovement.velocity); // keep velocity + controller.UpdateColliderCenter(vector2, true); // update collider center + controller.characterMovement.UpdateCurrentPlatform(); // recalculate stored local offset + + // invoke event so ik can update + BetterBetterCharacterController.OnMovementParentMove.Invoke( + new BetterBetterCharacterController.PlayerMoveOffset( + PlayerSetup.Instance.GetPlayerPosition(), + vector, + Quaternion.identity)); + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs b/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs new file mode 100644 index 0000000..d73c2fb --- /dev/null +++ b/OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs @@ -0,0 +1,19 @@ +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.OriginShift.Extensions; + +public static class PlayerSetupExtensions +{ + /// + /// Utility method to offset the player's currently stored avatar movement data. + /// Needs to be called, otherwise outbound net ik will be one-frame behind on teleport events. + /// + /// + /// + public static void OffsetAvatarMovementData(this PlayerSetup playerSetup, Vector3 offset) + { + playerSetup._playerAvatarMovementData.RootPosition += offset; + playerSetup._playerAvatarMovementData.BodyPosition += offset; // why in world space -_- + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs b/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs new file mode 100644 index 0000000..19fe0fe --- /dev/null +++ b/OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs @@ -0,0 +1,50 @@ +using HarmonyLib; +using UnityEngine; + +namespace NAK.OriginShift.Hacks; + +#region Harmony Patches + +internal static class OcclusionCullingPatches +{ + // i wish i had this when working on the head hiding & shadow clones... would have had a much better + // method of hiding mesh that wouldn't have broken magica cloth :< (one day: make test mod to do that) + + [HarmonyPostfix] // after all onprecull listeners + [HarmonyPatch(typeof(Camera), "FireOnPreCull")] + private static void OnPreCullPostfix(Camera cam) + { + OcclusionCullingHack hackInstance = cam.GetComponent(); + if (hackInstance != null) hackInstance.OnPostFirePreCull(cam); + } + + [HarmonyPrefix] // before all onprerender listeners + [HarmonyPatch(typeof(Camera), "FireOnPreRender")] + private static void OnPreRenderPrefix(Camera cam) + { + OcclusionCullingHack hackInstance = cam.GetComponent(); + if (hackInstance != null) hackInstance.OnPreFirePreRender(cam); + } +} + +#endregion Harmony Patches + +/// +/// Attempted hack to fix occlusion culling for *static* objects. This does not fix dynamic objects, they will be culled +/// by the camera's frustum & original baked occlusion culling data. Nothing can be done about that. :> +/// +public class OcclusionCullingHack : MonoBehaviour +{ + private Vector3 originalPosition; + + internal void OnPostFirePreCull(Camera cam) + { + originalPosition = cam.transform.position; + cam.transform.position = OriginShiftManager.GetAbsolutePosition(originalPosition); + } + + internal void OnPreFirePreRender(Camera cam) + { + cam.transform.position = originalPosition; + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs b/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs new file mode 100644 index 0000000..8147e27 --- /dev/null +++ b/OriginShift/OriginShift/Hacks/OriginShiftOcclusionCullingDisabler.cs @@ -0,0 +1,45 @@ +using UnityEngine; + +namespace NAK.OriginShift.Hacks +{ + public class OriginShiftOcclusionCullingDisabler : MonoBehaviour + { + private Camera _camera; + private bool _originalCullingState; + + #region Unity Events + + private void Start() + { + _camera = GetComponent(); + if (_camera == null) + { + Debug.LogError("OriginShiftOcclusionCullingDisabler requires a Camera component on the same GameObject."); + enabled = false; + return; + } + _originalCullingState = _camera.useOcclusionCulling; + } + + private void OnEnable() + { + OriginShiftManager.OnStateChanged += OnOriginShiftStateChanged; + } + + private void OnDisable() + { + OriginShiftManager.OnStateChanged -= OnOriginShiftStateChanged; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state) + { + _camera.useOcclusionCulling = state != OriginShiftManager.OriginShiftState.Forced && _originalCullingState; + } + + #endregion Origin Shift Events + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/OriginShiftManager.cs b/OriginShift/OriginShift/OriginShiftManager.cs new file mode 100644 index 0000000..d39bedf --- /dev/null +++ b/OriginShift/OriginShift/OriginShiftManager.cs @@ -0,0 +1,254 @@ +using System; +using ABI_RC.Core.Base; +using ABI_RC.Core.Player; +using ABI.CCK.Components; +using JetBrains.Annotations; +using MagicaCloth; +using NAK.OriginShift.Components; +using NAK.OriginShift.Utility; +using UnityEngine; + +namespace NAK.OriginShift; + +public class OriginShiftManager : MonoBehaviour +{ + #region Singleton + + private static OriginShiftManager _instance; + + public static OriginShiftManager Instance + { + get + { + if (_instance) return _instance; + _instance = new GameObject("NAKOriginShiftManager").AddComponent(); + DontDestroyOnLoad(_instance.gameObject); + return _instance; + } + } + + private static bool _useOriginShift; + + public static bool UseOriginShift + { + get => _useOriginShift; + set + { + if (_useOriginShift == value) + return; + + if (value) + { + PlayerSetup.Instance.gameObject.AddComponentIfMissing(); + } + else + { + Instance.ResetOrigin(); + if (PlayerSetup.Instance.TryGetComponent(out OriginShiftMonitor originShiftMonitor)) + DestroyImmediate(originShiftMonitor); + } + + _useOriginShift = value; + } + } + + private static bool _compatibilityMode = true; + public static bool CompatibilityMode + { + get => _useOriginShift && _compatibilityMode; + set => _compatibilityMode = value; + } + + #endregion Singleton + + #region Shader Globals + + private static readonly int s_OriginShiftChunkOffset = Shader.PropertyToID("_OriginShiftChunkOffset"); // Vector3 + private static readonly int s_OriginShiftChunkPosition = Shader.PropertyToID("_OriginShiftChunkPosition"); // Vector3 + private static readonly int s_OriginShiftChunkThreshold = Shader.PropertyToID("_OriginShiftChunkThreshold"); // float + + private static void SetShaderGlobals(Vector3 chunk, float threshold) + { + Shader.SetGlobalVector(s_OriginShiftChunkOffset, chunk); + Shader.SetGlobalFloat(s_OriginShiftChunkThreshold, threshold); + Shader.SetGlobalVector(s_OriginShiftChunkPosition, chunk * threshold); + } + + private static void ResetShaderGlobals() + { + Shader.SetGlobalVector(s_OriginShiftChunkOffset, Vector3.zero); + Shader.SetGlobalFloat(s_OriginShiftChunkThreshold, -1f); + Shader.SetGlobalVector(s_OriginShiftChunkPosition, Vector3.zero); + } + + #endregion Shader Globals + + #region Actions + + public static Action OnStateChanged = delegate { }; + + public static Action OnOriginShifted = delegate { }; + public static Action OnPostOriginShifted = delegate { }; + + #endregion Actions + + #region Public Properties + + [PublicAPI] public bool IsOriginShifted => ChunkOffset != Vector3.zero; + [PublicAPI] public Vector3 ChunkOffset { get; internal set; } = Vector3.zero; + [PublicAPI] public Vector3 ChunkPosition => ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD; + + public enum OriginShiftState + { + Inactive, // world is not using Origin Shift + Active, // world is using Origin Shift + Forced // temp for this session, force world to use Origin Shift + } + + private OriginShiftState _currentState = OriginShiftState.Inactive; + [PublicAPI] public OriginShiftState CurrentState + { + get => _currentState; + private set + { + if (_currentState == value) + return; + + _currentState = value; + OnStateChanged.Invoke(value); + } + } + + #endregion Public Properties + + #region Manager Lifecycle + + private OriginShiftController _forceController; + + public void ForceManager() + { + if (CVRWorld.Instance == null) + { + OriginShiftMod.Logger.Error("Cannot force Origin Shift without a world."); + return; + } + OriginShiftMod.Logger.Msg("Forcing Origin Shift..."); + + _forceController = CVRWorld.Instance.gameObject.AddComponentIfMissing(); + _forceController.IsForced = true; + } + + public void SetupManager(bool isForced = false) + { + if (CVRWorld.Instance == null) + { + OriginShiftMod.Logger.Error("Cannot setup Origin Shift without a world."); + return; + } + OriginShiftMod.Logger.Msg("Setting up Origin Shift..."); + + CurrentState = isForced ? OriginShiftState.Forced : OriginShiftState.Active; + UseOriginShift = true; + } + + public void ResetManager() + { + OriginShiftMod.Logger.Msg("Resetting Origin Shift..."); + + if (_forceController) Destroy(_forceController); + + CurrentState = OriginShiftState.Inactive; + + ResetOrigin(); + ResetShaderGlobals(); + UseOriginShift = false; + } + + #endregion Manager Lifecycle + + #region Public Methods + + // Called by OriginShiftMonitor when the local player needs to shit + public void ShiftOrigin(Vector3 rawPosition) + { + if (!_useOriginShift) return; + + // create stopwatch + StopWatch stopwatch = new(); + stopwatch.Start(); + + // normalize + float halfThreshold = (OriginShiftController.ORIGIN_SHIFT_THRESHOLD / 2); + rawPosition += new Vector3(halfThreshold, halfThreshold, halfThreshold); + + // add to chunk + Vector3 chunkDifference; + Vector3 calculatedChunk = chunkDifference = ChunkOffset; + + calculatedChunk.x += Mathf.FloorToInt(rawPosition.x / OriginShiftController.ORIGIN_SHIFT_THRESHOLD); + calculatedChunk.y += Mathf.FloorToInt(rawPosition.y / OriginShiftController.ORIGIN_SHIFT_THRESHOLD); + calculatedChunk.z += Mathf.FloorToInt(rawPosition.z / OriginShiftController.ORIGIN_SHIFT_THRESHOLD); + + // get offset + chunkDifference = (ChunkOffset - calculatedChunk) * OriginShiftController.ORIGIN_SHIFT_THRESHOLD; + + // store & invoke + ChunkOffset = calculatedChunk; + OnOriginShifted.Invoke(chunkDifference); + OnPostOriginShifted.Invoke(chunkDifference); + + // set shader globals + SetShaderGlobals(ChunkOffset, OriginShiftController.ORIGIN_SHIFT_THRESHOLD); + + // log + stopwatch.Stop(); + OriginShiftMod.Logger.Msg($"Shifted Origin: {chunkDifference} in {stopwatch.ElapsedMilliseconds:F11}ms"); + } + + public void ResetOrigin() + { + if (!_useOriginShift) return; + + ShiftOrigin(-ChunkPosition); + ChunkOffset = Vector3.zero; + } + + #endregion Origin Shift Implementation + + #region Utility Methods + + public static Vector3 GetAbsolutePosition(Vector3 localizedPosition) + { + // absolute coordinates can be reconstructed using current chunk and threshold + localizedPosition += Instance.ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD; + return localizedPosition; + } + + public static Vector3 GetLocalizedPosition(Vector3 absolutePosition) + { + return absolutePosition - (Instance.ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD); + } + + #endregion Utility Methods + + #region Unity Events + +#if !UNITY_EDITOR + private void Update() + { + if (Input.GetKeyDown(KeyCode.P)) // press p to print chunk + OriginShiftMod.Logger.Msg($"Current Chunk: {ChunkOffset}"); + + // press o to toggle debug + if (Input.GetKeyDown(KeyCode.O)) + { + if (TryGetComponent(out DebugTextDisplay debugTextDisplay)) + Destroy(debugTextDisplay); + else + gameObject.AddComponent(); + } + } +#endif + + #endregion Unity Events +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs b/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs new file mode 100644 index 0000000..6d82d4b --- /dev/null +++ b/OriginShift/OriginShift/Player/Dynamics/OriginShiftDbAvatarReceiver.cs @@ -0,0 +1,68 @@ +using Unity.Mathematics; +using UnityEngine; +using Zettai; + +namespace NAK.OriginShift; + +public class OriginShiftDbAvatarReceiver : MonoBehaviour +{ + private DbJobsAvatarManager _avatarManager; + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + _avatarManager = GetComponent(); + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + private void OnOriginShifted(Vector3 shift) + { + // get all particles + var particles = _avatarManager.particlesArray; + for (var index = 0; index < particles.Length; index++) + { + ParticleStruct particle = particles[index]; + + float3 position = particle.m_Position; + position.x += shift.x; + position.y += shift.y; + position.z += shift.z; + particle.m_Position = position; + + position = particle.m_PrevPosition; + position.x += shift.x; + position.y += shift.y; + position.z += shift.z; + particle.m_PrevPosition = position; + + particles[index] = particle; + } + _avatarManager.particlesArray = particles; + + // get all transforminfo + var transformInfos = _avatarManager.transformInfoArray; + for (var index = 0; index < transformInfos.Length; index++) + { + TransformInfo transformInfo = transformInfos[index]; + + float3 position = transformInfo.position; + position.x += shift.x; + position.y += shift.y; + position.z += shift.z; + transformInfo.position = position; + + position = transformInfo.prevPosition; + position.x += shift.x; + position.y += shift.y; + position.z += shift.z; + transformInfo.prevPosition = position; + + transformInfos[index] = transformInfo; + } + _avatarManager.transformInfoArray = transformInfos; + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Player/OriginShiftMonitor.cs b/OriginShift/OriginShift/Player/OriginShiftMonitor.cs new file mode 100644 index 0000000..8cb3759 --- /dev/null +++ b/OriginShift/OriginShift/Player/OriginShiftMonitor.cs @@ -0,0 +1,93 @@ +using System.Collections; +using ABI_RC.Core; +using ABI_RC.Core.Player; +using NAK.OriginShift.Components; +using NAK.OriginShift.Extensions; +using UnityEngine; + +#if !UNITY_EDITOR +using ABI_RC.Systems.Movement; +#endif + +namespace NAK.OriginShift +{ + [DefaultExecutionOrder(int.MaxValue)] + public class OriginShiftMonitor : MonoBehaviour + { +#if !UNITY_EDITOR + private PlayerSetup _playerSetup; + private BetterBetterCharacterController _characterController; +#endif + + #region Unity Events + + private void Start() + { +#if !UNITY_EDITOR + _playerSetup = GetComponent(); + _characterController = GetComponent(); +#endif + OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted; + StartCoroutine(FixedUpdateCoroutine()); + } + + private void OnDestroy() + { + OriginShiftManager.OnPostOriginShifted -= OnPostOriginShifted; + StopAllCoroutines(); + } + + private void LateFixedUpdate() + { + // in CVR use GetPlayerPosition to account for VR offset + Vector3 position = PlayerSetup.Instance.GetPlayerPosition(); + + // respawn height check + Vector3 absPosition = OriginShiftManager.GetAbsolutePosition(position); + if (absPosition.y < BetterBetterCharacterController.Instance.respawnHeight) + { + RootLogic.Instance.Respawn(); + return; + } + + float halfThreshold = OriginShiftController.ORIGIN_SHIFT_THRESHOLD / 2; // i keep forgetting this + if (Mathf.Abs(position.x) > halfThreshold + || Mathf.Abs(position.y) > halfThreshold + || Mathf.Abs(position.z) > halfThreshold) + OriginShiftManager.Instance.ShiftOrigin(position); + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnPostOriginShifted(Vector3 shift) + { +#if UNITY_EDITOR + // shift our transform back + transform.position += shift; +#else + _characterController.OffsetBy(shift); + _playerSetup.OffsetAvatarMovementData(shift); +#endif + } + + #endregion Origin Shift Events + + #region LateFixedUpdate Implementation + + private readonly YieldInstruction _fixedUpdateYield = new WaitForFixedUpdate(); + + private IEnumerator FixedUpdateCoroutine() + { + while (true) + { + yield return _fixedUpdateYield; + LateFixedUpdate(); // we need to run after all physics (specifically, character controller) + } + // ReSharper disable once IteratorNeverReturns + } + + #endregion LateFixedUpdate Implementation + } +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs b/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs new file mode 100644 index 0000000..57785a3 --- /dev/null +++ b/OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs @@ -0,0 +1,47 @@ +using ABI_RC.Core.Player; +using UnityEngine; + +namespace NAK.OriginShift; + +public class OriginShiftNetIkReceiver : MonoBehaviour +{ + private PuppetMaster _puppetMaster; + + #region Unity Events + + private void Start() + { + _puppetMaster = GetComponent(); + if (_puppetMaster == null) + { + OriginShiftMod.Logger.Error("OriginShiftNetIkReceiver: No PuppetMaster found on GameObject: " + gameObject.name, this); + enabled = false; + } + } + + private void OnEnable() + { + OriginShiftManager.OnOriginShifted += OnOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnOriginShifted -= OnOriginShifted; + } + + #endregion Unity Events + + #region Origin Shift Events + + private void OnOriginShifted(Vector3 shift) + { + // would be nice if these were relative positions like sub-syncs :) + _puppetMaster._playerAvatarMovementDataCurrent.RootPosition += shift; + _puppetMaster._playerAvatarMovementDataCurrent.BodyPosition += shift; + _puppetMaster._playerAvatarMovementDataPast.RootPosition += shift; + _puppetMaster._playerAvatarMovementDataPast.BodyPosition += shift; + // later in frame puppetmaster will update remote player position + } + + #endregion Origin Shift Events +} \ No newline at end of file diff --git a/OriginShift/OriginShift/Utility/DebugTextDisplay.cs b/OriginShift/OriginShift/Utility/DebugTextDisplay.cs new file mode 100644 index 0000000..6555213 --- /dev/null +++ b/OriginShift/OriginShift/Utility/DebugTextDisplay.cs @@ -0,0 +1,99 @@ +#if !UNITY_EDITOR +using ABI_RC.Core.Player; +using NAK.OriginShift.Components; +using UnityEngine; + +namespace NAK.OriginShift.Utility; + +public class DebugTextDisplay : MonoBehaviour +{ + #region Private Variables + + private string _debugText = "Initializing..."; + private Color _textColor = Color.white; + + private bool _originShiftEventOccurred; + private const float _blendDuration = 1.0f; + private float _blendTime; + + #endregion Private Variables + + #region Unity Events + + private void OnEnable() + { + OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted; + } + + private void OnDisable() + { + OriginShiftManager.OnPostOriginShifted -= OnPostOriginShifted; + } + + private void OnGUI() + { + GUIStyle style = new() + { + fontSize = 25, + normal = { textColor = _textColor }, + alignment = TextAnchor.UpperRight + }; + + float screenWidth = Screen.width; + var xPosition = screenWidth - 10; + float yPosition = 10; + + GUI.Label(new Rect(xPosition - 490, yPosition, 500, 150), _debugText, style); + } + + #endregion Unity Events + + #region Public Methods + + public void UpdateDebugText(string newText) + { + _debugText = newText; + } + + #endregion Public Methods + + private void Update() + { + Vector3 currentChunk = OriginShiftManager.Instance.ChunkOffset; + Vector3 localCoordinates = PlayerSetup.Instance.GetPlayerPosition(); + Vector3 absoluteCoordinates = localCoordinates; + + // absolute coordinates can be reconstructed using current chunk and threshold + absoluteCoordinates += currentChunk * OriginShiftController.ORIGIN_SHIFT_THRESHOLD; + + // Update the debug text with the current coordinates + UpdateDebugText($"Local Coordinates:\n{localCoordinates}\n\n" + + $"Absolute Coordinates:\n{absoluteCoordinates}\n\n" + + $"Current Chunk:\n{currentChunk}"); + + // Blend back to white if the origin shift event occurred + if (_originShiftEventOccurred) + { + _blendTime += Time.deltaTime; + _textColor = Color.Lerp(Color.red, Color.white, _blendTime / _blendDuration); + if (_blendTime >= _blendDuration) + { + _originShiftEventOccurred = false; + _blendTime = 0.0f; + _textColor = Color.white; + } + } + } + + #region Origin Shift Events + + private void OnPostOriginShifted(Vector3 _) + { + _originShiftEventOccurred = true; + _textColor = Color.green; + _blendTime = 0.0f; + } + + #endregion Origin Shift Events +} +#endif \ No newline at end of file diff --git a/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs b/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs new file mode 100644 index 0000000..1140e46 --- /dev/null +++ b/OriginShift/OriginShift/Utility/RendererReflectionUtility.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using UnityEngine; + +namespace NAK.OriginShift.Utility; + +/// +/// Dumb little utility class to access the private staticBatchRootTransform property in the Renderer class. +/// Using this we can move static batched objects with the scene! :) +/// +public static class RendererReflectionUtility +{ + private static readonly PropertyInfo _staticBatchRootTransformProperty; + + static RendererReflectionUtility() + { + Type rendererType = typeof(Renderer); + _staticBatchRootTransformProperty = rendererType.GetProperty("staticBatchRootTransform", BindingFlags.NonPublic | BindingFlags.Instance); + if (_staticBatchRootTransformProperty == null) OriginShiftMod.Logger.Error("Property staticBatchRootTransform not found in Renderer class."); + } + + public static void SetStaticBatchRootTransform(Renderer renderer, Transform newTransform) + { + if (_staticBatchRootTransformProperty != null) + _staticBatchRootTransformProperty.SetValue(renderer, newTransform); + } + + public static Transform GetStaticBatchRootTransform(Renderer renderer) + { + if (_staticBatchRootTransformProperty != null) + return (Transform)_staticBatchRootTransformProperty.GetValue(renderer); + return null; + } +} diff --git a/OriginShift/Patches.cs b/OriginShift/Patches.cs new file mode 100644 index 0000000..71244d2 --- /dev/null +++ b/OriginShift/Patches.cs @@ -0,0 +1,276 @@ +#if !UNITY_EDITOR +using ABI_RC.Core.Base; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using ABI_RC.Systems.Camera; +using ABI_RC.Systems.Communications.Networking; +using ABI_RC.Systems.GameEventSystem; +using ABI_RC.Systems.Movement; +using ABI.CCK.Components; +using DarkRift; +using HarmonyLib; +using NAK.OriginShift.Components; +using NAK.OriginShift.Hacks; +using UnityEngine; +using Zettai; + +namespace NAK.OriginShift.Patches; + +internal static class BetterBetterCharacterControllerPatches +{ + internal static bool PreventNextClearMovementParent; + internal static bool PreventNextClearAccumulatedForces; + + [HarmonyPostfix] + [HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.Start))] + private static void Postfix_BetterCharacterController_Start(ref BetterBetterCharacterController __instance) + { + __instance.AddComponentIfMissing(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.ClearLastMovementParent))] + private static bool Prefix_BetterCharacterController_ClearLastMovementParent() + { + if (!PreventNextClearMovementParent) + return true; + + // skip this call if we are preventing it + PreventNextClearMovementParent = false; + Debug.Log("Prevented ClearLastMovementParent"); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.ClearAccumulatedForces))] + private static bool Prefix_BetterCharacterController_ClearAccumulatedForces() + { + if (!PreventNextClearAccumulatedForces) + return true; + + // skip this call if we are preventing it + PreventNextClearAccumulatedForces = false; + Debug.Log("Prevented ClearAccumulatedForces"); + return false; + } +} + +internal static class CVRSpawnablePatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Start))] + private static void Postfix_CVRSpawnable_Start(ref CVRSpawnable __instance) + { + Transform wrapper = __instance.transform.parent; + + // test adding to the wrapper of the spawnable + wrapper.AddComponentIfMissing(); + wrapper.AddComponentIfMissing(); + wrapper.AddComponentIfMissing(); + wrapper.AddComponentIfMissing(); + } + + [HarmonyPrefix] // inbound spawnable + [HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.UpdateFromNetwork))] + private static void Prefix_CVRSpawnable_UpdateFromNetwork(ref CVRSyncHelper.PropData propData) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position + { + Vector3 position = new(propData.PositionX, propData.PositionY, propData.PositionZ); // imagine not using Vector3 + position = OriginShiftManager.GetLocalizedPosition(position); + propData.PositionX = position.x; + propData.PositionY = position.y; + propData.PositionZ = position.z; + } + } +} + +internal static class RCC_SkidmarksManagerPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(RCC_SkidmarksManager), nameof(RCC_SkidmarksManager.Start))] + private static void Postfix_RCC_SkidMarkManager_Start(ref RCC_SkidmarksManager __instance) + { + __instance.gameObject.AddComponentIfMissing(); + } +} + +internal static class PlayerSetupPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] + private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance) + { + __instance.desktopCam.AddComponentIfMissing(); + __instance.vrCam.AddComponentIfMissing(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.GetPlayerMovementData))] + private static void Postfix_PlayerSetup_GetPlayerMovementData(ref PlayerAvatarMovementData __result) + { + if (OriginShiftManager.CompatibilityMode) + { + // adjust root position back to absolute world position + __result.RootPosition = OriginShiftManager.GetAbsolutePosition(__result.RootPosition); // player root + __result.BodyPosition = OriginShiftManager.GetAbsolutePosition(__result.BodyPosition); // player hips (pls fix, why in world space?) + } + } +} + +internal static class PortableCameraPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.Start))] + private static void Postfix_PortableCamera_Start(ref PortableCamera __instance) + { + __instance.cameraComponent.AddComponentIfMissing(); + } +} + +internal static class PathingCameraPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRPathCamController), nameof(CVRPathCamController.Start))] + private static void Postfix_CVRPathCamController_Start(ref CVRPathCamController __instance) + { + __instance.cam.AddComponentIfMissing(); + } +} + +internal static class Comms_ClientPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(Comms_Client), nameof(Comms_Client.SetPosition))] + private static void Prefix_Comms_Client_GetPlayerMovementData(ref Vector3 listenerPosition) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position + listenerPosition = OriginShiftManager.GetAbsolutePosition(listenerPosition); + } +} + +internal static class CVRSyncHelperPatches +{ + [HarmonyPrefix] // outbound spawnable + [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.UpdatePropValues))] + private static void Prefix_CVRSyncHelper_UpdatePropValues(ref Vector3 position) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position + position = OriginShiftManager.GetAbsolutePosition(position); + } + + [HarmonyPrefix] // outbound object sync + [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.MoveObject))] + private static void Prefix_CVRSyncHelper_MoveObject(ref float PosX, ref float PosY, ref float PosZ) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position + { + Vector3 position = new(PosX, PosY, PosZ); // imagine not using Vector3 + position = OriginShiftManager.GetAbsolutePosition(position); + PosX = position.x; + PosY = position.y; + PosZ = position.z; + } + } + + [HarmonyPrefix] // outbound spawn prop + [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.SpawnProp))] + private static void Prefix_CVRSyncHelper_SpawnProp(ref float posX, ref float posY, ref float posZ) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position + { + Vector3 position = new(posX, posY, posZ); // imagine not using Vector3 + position = OriginShiftManager.GetAbsolutePosition(position); + posX = position.x; + posY = position.y; + posZ = position.z; + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.SpawnPropFromNetwork))] + private static void Postfix_CVRSyncHelper_SpawnPropFromNetwork(Message message) + { + if (!OriginShiftManager.CompatibilityMode) + return; + + using DarkRiftReader reader = message.GetReader(); + reader.ReadString(); // objectId, don't care + + string instanceId = reader.ReadString(); + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find((match) => match.InstanceId == instanceId); + if (propData == null) + return; // uh oh + + Vector3 position = new(propData.PositionX, propData.PositionY, propData.PositionZ); // imagine not using Vector3 + position = OriginShiftManager.GetLocalizedPosition(position); + propData.PositionX = position.x; + propData.PositionY = position.y; + propData.PositionZ = position.z; + } +} + +internal static class CVRObjectSyncPatches +{ + [HarmonyPrefix] // inbound object sync + [HarmonyPatch(typeof(CVRObjectSync), nameof(CVRObjectSync.receiveNetworkData))] + [HarmonyPatch(typeof(CVRObjectSync), nameof(CVRObjectSync.receiveNetworkDataJoin))] + private static void Prefix_CVRObjectSync_Update(ref Vector3 position) + { + if (OriginShiftManager.CompatibilityMode) // adjust root position back to localized world position + position = OriginShiftManager.GetLocalizedPosition(position); + } +} + +internal static class PuppetMasterPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.Start))] + private static void Postfix_PuppetMaster_Start(ref PuppetMaster __instance) + { + __instance.gameObject.AddComponentIfMissing(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.CycleData))] + private static void Prefix_PuppetMaster_CycleData(ref PlayerAvatarMovementData ___PlayerAvatarMovementDataInput) + { + if (OriginShiftManager.CompatibilityMode) // && if user is not using OriginShift + { + // adjust root position back to absolute world position + ___PlayerAvatarMovementDataInput.RootPosition = OriginShiftManager.GetLocalizedPosition(___PlayerAvatarMovementDataInput.RootPosition); // player root + ___PlayerAvatarMovementDataInput.BodyPosition = OriginShiftManager.GetLocalizedPosition(___PlayerAvatarMovementDataInput.BodyPosition); // player hips (pls fix, why in world space?) + } + } +} + +//OriginShiftDbAvatarReceiver +internal static class DbJobsAvatarManagerPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(DbJobsAvatarManager), nameof(DbJobsAvatarManager.Awake))] + private static void Postfix_DbJobsAvatarManager_Start(ref DbJobsAvatarManager __instance) + { + __instance.gameObject.AddComponentIfMissing(); + } +} + +// CVRPortalManager +internal static class CVRPortalManagerPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRPortalManager), nameof(CVRPortalManager.Start))] + private static void Postfix_CVRPortalManager_Start(ref CVRPortalManager __instance) + { + // parent portal to the object below it using a physics cast + Transform portalTransform = __instance.transform; + Vector3 origin = portalTransform.position; + Vector3 direction = Vector3.down; + if (Physics.Raycast(origin, direction, out RaycastHit hit, 0.5f)) + portalTransform.SetParent(hit.transform); + } +} + +#endif \ No newline at end of file diff --git a/OriginShift/Properties/AssemblyInfo.cs b/OriginShift/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fdabf44 --- /dev/null +++ b/OriginShift/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +#if !UNITY_EDITOR +using NAK.OriginShift.Properties; +using MelonLoader; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.OriginShift))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.OriginShift))] + +[assembly: MelonInfo( + typeof(NAK.OriginShift.OriginShiftMod), + nameof(NAK.OriginShift), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/OriginShift" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 125, 126, 129)] +[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: HarmonyDontPatchAll] + +namespace NAK.OriginShift.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} +#endif \ No newline at end of file diff --git a/OriginShift/README.md b/OriginShift/README.md new file mode 100644 index 0000000..395a707 --- /dev/null +++ b/OriginShift/README.md @@ -0,0 +1,14 @@ +# OriginShift + +Experimental mod that allows world origin to be shifted to prevent floating point precision issues. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/OriginShift/Resources/OriginShift-Icon-Active.png b/OriginShift/Resources/OriginShift-Icon-Active.png new file mode 100644 index 0000000000000000000000000000000000000000..d110280438d69bfc7c4bb6b9f168db7ef5554081 GIT binary patch literal 13012 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

vo9mL-PdBL>GIe{g4p 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 literal 0 HcmV?d00001 diff --git a/OriginShift/Resources/OriginShift-Icon-Inactive.png b/OriginShift/Resources/OriginShift-Icon-Inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..eb15b14a0e7d7d54e5eda2df86b6cbc4f02a512e GIT binary patch literal 11885 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\.ManagedLibs\LibVLCSharp.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\LucHeart.CoreOSC.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\MagicaCloth.dll False @@ -192,22 +188,6 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.VR.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Photon3Unity3D.dll - False - - - $(MsBuildThisFileDirectory)\.ManagedLibs\PhotonRealtime.dll - False - - - $(MsBuildThisFileDirectory)\.ManagedLibs\PhotonVoice.API.dll - False - - - $(MsBuildThisFileDirectory)\.ManagedLibs\PhotonVoice.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll False @@ -568,10 +548,6 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.Services.Core.Threading.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.Services.Vivox.dll - False - $(MsBuildThisFileDirectory)\.ManagedLibs\Unity.TextMeshPro.dll False @@ -936,9 +912,5 @@ $(MsBuildThisFileDirectory)\.ManagedLibs\Valve.Newtonsoft.Json.dll False - - $(MsBuildThisFileDirectory)\.ManagedLibs\websocket-sharp.dll - False - diff --git a/copy_and_nstrip_dll.ps1 b/copy_and_nstrip_dll.ps1 index b81a8c6..448f48f 100644 --- a/copy_and_nstrip_dll.ps1 +++ b/copy_and_nstrip_dll.ps1 @@ -10,7 +10,7 @@ $cvrDefaultPath = "C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR" # $cvrDefaultPath = "E:\temp\CVR_Experimental" # Array with the dlls to strip -$dllsToStrip = @('Assembly-CSharp.dll','Assembly-CSharp-firstpass.dll','AVProVideo.Runtime.dll', 'Unity.TextMeshPro.dll', 'MagicaCloth.dll', 'MagicaClothV2.dll') +$dllsToStrip = @('Assembly-CSharp.dll','Assembly-CSharp-firstpass.dll','AVProVideo.Runtime.dll', 'Unity.TextMeshPro.dll', 'MagicaCloth.dll', 'MagicaClothV2.dll', 'ECM2.dll') # Array with the mods to grab $modNames = @("BTKUILib", "BTKSAImmersiveHud", "PortableMirrorMod", "VRBinding")