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 0000000..d110280 Binary files /dev/null and b/OriginShift/Resources/OriginShift-Icon-Active.png differ diff --git a/OriginShift/Resources/OriginShift-Icon-Forced.png b/OriginShift/Resources/OriginShift-Icon-Forced.png new file mode 100644 index 0000000..91c91df Binary files /dev/null and b/OriginShift/Resources/OriginShift-Icon-Forced.png differ diff --git a/OriginShift/Resources/OriginShift-Icon-Inactive.png b/OriginShift/Resources/OriginShift-Icon-Inactive.png new file mode 100644 index 0000000..eb15b14 Binary files /dev/null and b/OriginShift/Resources/OriginShift-Icon-Inactive.png differ diff --git a/OriginShift/format.json b/OriginShift/format.json new file mode 100644 index 0000000..93c0bb2 --- /dev/null +++ b/OriginShift/format.json @@ -0,0 +1,23 @@ +{ + "_id": 211, + "name": "RelativeSync", + "modversion": "1.0.3", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.", + "searchtags": [ + "relative", + "sync", + "movement", + "chair" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r29/RelativeSync.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync/", + "changelog": "- Enabled the Experimental settings to fix **local** jitter on Movement Parents by default\n- Adjusted BBCC No Interpolation fix to account for potential native fix (now respects initial value)", + "embedcolor": "#507e64" +} \ No newline at end of file diff --git a/References.Items.props b/References.Items.props index 95d943e..579dbba 100644 --- a/References.Items.props +++ b/References.Items.props @@ -132,10 +132,6 @@ $(MsBuildThisFileDirectory)\.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")