i dont rememebr

This commit is contained in:
NotAKidoS 2024-01-01 11:58:25 -06:00
parent 374ab6c11e
commit 86828a94e2
48 changed files with 1637 additions and 841 deletions

View file

@ -1,137 +1,26 @@
using ABI.CCK.Components;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Util.Object_Behaviour;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.TrackingModules;
using cohtml.Net;
using ABI_RC.Core;
using ABI_RC.Systems.InputManagement;
using HarmonyLib;
using NAK.DesktopVRSwitch.Patches;
using NAK.DesktopVRSwitch.VRModeTrackers;
using UnityEngine;
using Valve.VR;
using NativeVRModeSwitchManager = ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager;
namespace NAK.DesktopVRSwitch.HarmonyPatches;
internal class CheckVRPatches
internal class CVRInputManagerPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(CheckVR), nameof(CheckVR.Awake))]
private static void Postfix_CheckVR_Start(ref CheckVR __instance)
[HarmonyPatch(typeof(CVRInputManager), "OnPostVRModeSwitch")]
private static void Postfix_CVRInputManager_OnPostVRModeSwitch(bool inVr, UnityEngine.Camera playerCamera)
{
try
{
__instance.gameObject.AddComponent<VRModeSwitchManager>();
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CheckVR_Start)}");
DesktopVRSwitch.Logger.Error(e);
}
RootLogic.Instance.ToggleMouse(inVr);
}
}
internal class IKSystemPatches
{
[HarmonyPostfix] //lazy fix so device indices can change properly
[HarmonyPatch(typeof(SteamVRTrackingModule), nameof(SteamVRTrackingModule.ModuleDestroy))]
private static void Postfix_SteamVRTrackingModule_ModuleDestroy(ref SteamVRTrackingModule __instance)
{
try
{
foreach (TrackingPoint t in __instance.TrackingPoints)
{
UnityEngine.Object.Destroy(t.referenceGameObject);
}
__instance.TrackingPoints.Clear();
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_SteamVRTrackingModule_ModuleDestroy)}");
DesktopVRSwitch.Logger.Error(e);
}
}
}
internal class CVRWorldPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.SetDefaultCamValues))]
[HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.CopyRefCamValues))]
private static void Postfix_CVRWorld_HandleCamValues()
{
try
{
ReferenceCameraPatch.OnWorldLoad();
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CVRWorld_HandleCamValues)}");
DesktopVRSwitch.Logger.Error(e);
}
}
}
internal class CameraFacingObjectPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(CameraFacingObject), nameof(CameraFacingObject.Start))]
private static void Postfix_CameraFacingObject_Start(ref CameraFacingObject __instance)
{
try
{
__instance.gameObject.AddComponent<CameraFacingObjectTracker>();
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CameraFacingObject_Start)}");
DesktopVRSwitch.Logger.Error(e);
}
}
}
internal class CVRPickupObjectPatches
internal class VRModeSwitchManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(CVRPickupObject), nameof(CVRPickupObject.Start))]
private static void Prefix_CVRPickupObject_Start(ref CVRPickupObject __instance)
[HarmonyPatch(typeof(NativeVRModeSwitchManager), "StartSwitchInternal")]
private static void Postfix_CVRInputManager_OnPostVRModeSwitch()
{
try
{
if (__instance.gripType == CVRPickupObject.GripType.Free)
return;
Transform vrOrigin = __instance.gripOrigin;
Transform desktopOrigin = vrOrigin != null ? vrOrigin.Find("[Desktop]") : null;
if (vrOrigin != null && desktopOrigin != null)
{
CVRPickupObjectTracker tracker = __instance.gameObject.AddComponent<CVRPickupObjectTracker>();
tracker._pickupObject = __instance;
tracker._storedGripOrigin = (!MetaPort.Instance.isUsingVr ? vrOrigin : desktopOrigin);
}
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Prefix_CVRPickupObject_Start)}");
DesktopVRSwitch.Logger.Error(e);
}
}
}
internal class SteamVRBehaviourPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(SteamVR_Behaviour), nameof(SteamVR_Behaviour.OnQuit))]
private static bool Prefix_SteamVR_Behaviour_OnQuit()
{
if (!ModSettings.EntrySwitchToDesktopOnExit.Value)
return true;
// If we don't switch fast enough, SteamVR will force close.
// World Transition might cause issues. Might need to override.
if (VRModeSwitchManager.Instance != null)
VRModeSwitchManager.Instance.AttemptSwitch();
return false;
CVRInputManager.Instance.inputEnabled = false;
}
}

View file

@ -1,84 +1,14 @@

using System;
using MelonLoader;
using NAK.DesktopVRSwitch.VRModeTrackers;
using UnityEngine;
namespace NAK.DesktopVRSwitch;
public class DesktopVRSwitch : MelonMod
{
internal static MelonLogger.Instance Logger;
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
RegisterVRModeTrackers();
// main manager
ApplyPatches(typeof(HarmonyPatches.CheckVRPatches));
// nameplate fixes
ApplyPatches(typeof(HarmonyPatches.CameraFacingObjectPatches));
// pickup fixes
ApplyPatches(typeof(HarmonyPatches.CVRPickupObjectPatches));
// lazy fix to reset iksystem
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
// post processing fixes
ApplyPatches(typeof(HarmonyPatches.CVRWorldPatches));
// prevent steamvr behaviour from closing game
ApplyPatches(typeof(HarmonyPatches.SteamVRBehaviourPatches));
InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
}
public override void OnUpdate()
{
if (!Input.GetKeyDown(KeyCode.F6) || !Input.GetKey(KeyCode.LeftControl))
return;
if (VRModeSwitchManager.Instance != null)
VRModeSwitchManager.Instance.AttemptSwitch();
}
private static void RegisterVRModeTrackers()
{
// Core trackers
VRModeSwitchManager.RegisterVRModeTracker(new CheckVRTracker());
VRModeSwitchManager.RegisterVRModeTracker(new MetaPortTracker());
// HUD trackers
VRModeSwitchManager.RegisterVRModeTracker(new CohtmlHudTracker());
VRModeSwitchManager.RegisterVRModeTracker(new HudOperationsTracker());
// Player trackers
VRModeSwitchManager.RegisterVRModeTracker(new PlayerSetupTracker());
VRModeSwitchManager.RegisterVRModeTracker(new MovementSystemTracker());
VRModeSwitchManager.RegisterVRModeTracker(new IKSystemTracker());
// Menu trackers
VRModeSwitchManager.RegisterVRModeTracker(new CVR_MenuManagerTracker());
VRModeSwitchManager.RegisterVRModeTracker(new ViewManagerTracker());
// Interaction trackers
VRModeSwitchManager.RegisterVRModeTracker(new CVRInputManagerTracker());
VRModeSwitchManager.RegisterVRModeTracker(new CVR_InteractableManagerTracker());
VRModeSwitchManager.RegisterVRModeTracker(new CVRGestureRecognizerTracker());
// Portable camera tracker
VRModeSwitchManager.RegisterVRModeTracker(new PortableCameraTracker());
// CVRWorld tracker - Must come after PlayerSetupTracker
VRModeSwitchManager.RegisterVRModeTracker(new CVRWorldTracker());
}
private static void InitializeIntegration(string modName, Action integrationAction)
{
if (RegisteredMelons.All(it => it.Info.Name != modName))
return;
Logger.Msg($"Initializing {modName} integration.");
integrationAction.Invoke();
ApplyPatches(typeof(HarmonyPatches.CVRInputManagerPatches));
ApplyPatches(typeof(HarmonyPatches.VRModeSwitchManagerPatches));
}
private void ApplyPatches(Type type)

View file

@ -1,32 +1,34 @@
using ABI_RC.Systems.UI;
using NAK.DesktopVRSwitch.VRModeTrackers;
using System.Collections;
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.IO;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.GameEventSystem;
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.VRModeSwitch;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.XR.Management;
namespace NAK.DesktopVRSwitch;
public class VRModeSwitchManager : MonoBehaviour
public class VRModeSwitchManager : MonoBehaviour
{
#region Static
public static VRModeSwitchManager Instance { get; private set; }
public static void RegisterVRModeTracker(VRModeTracker observer) => observer.TrackerInit();
public static void UnregisterVRModeTracker(VRModeTracker observer) => observer.TrackerDestroy();
#endregion
#region Variables
// Settings
public bool DesktopVRSwitchEnabled;
public bool UseWorldTransition = true;
public bool ReloadLocalAvatar = true;
public bool SwitchInProgress { get; private set; }
#endregion
#region Unity Methods
private void Awake()
@ -36,73 +38,125 @@ public class VRModeSwitchManager : MonoBehaviour
DestroyImmediate(this);
return;
}
Instance = this;
DesktopVRSwitchEnabled = MetaPort.Instance.settings.GetSettingsBool("ExperimentalDesktopVRSwitch");
MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged);
}
private void Update()
{
if (!DesktopVRSwitchEnabled)
return;
if (SwitchInProgress)
return;
if (CVRInputManager.Instance.switchMode)
AttemptSwitch();
}
#endregion
#region Public Methods
private static bool IsInXR() => XRGeneralSettings.Instance.Manager.activeLoader != null;
public void AttemptSwitch() => StartCoroutine(StartSwitchInternal());
public void AttemptSwitch()
{
if (SwitchInProgress)
return;
// dont allow switching during world transfer, itll explode violently
if (CVRObjectLoader.Instance.IsLoadingWorldToJoin())
return;
StartCoroutine(StartSwitchInternal());
}
#endregion
#region Private Methods
private void OnSettingsBoolChanged(string settingName, bool val)
{
if (settingName == "ExperimentalDesktopVRSwitch")
DesktopVRSwitchEnabled = val;
}
private IEnumerator StartSwitchInternal()
{
if (SwitchInProgress)
if (SwitchInProgress)
yield break;
NotifyOnPreSwitch();
bool useWorldTransition = UseWorldTransition;
SwitchInProgress = true;
yield return null;
if (useWorldTransition)
yield return StartCoroutine(StartTransition());
bool isUsingVr = IsInXR();
var wasInXr = IsInXR();
InvokeOnPreSwitch(isUsingVr);
InvokeOnPreSwitch(!wasInXr);
yield return StartCoroutine(XRAndReloadAvatar(!isUsingVr));
// Note: this assumes that wasInXr has been correctly set earlier in your method.
Task xrTask = wasInXr ? XRHandler.StopXR() : XRHandler.StartXR();
// Wait for the task to complete. This makes the coroutine wait here until the above thread is done.
yield return new WaitUntil(() => xrTask.IsCompleted || xrTask.IsFaulted);
// Check task status, handle any fault that occurred during the execution of the task.
if (xrTask.IsFaulted)
{
// Log and/or handle exceptions that occurred within the task.
Exception innerException = xrTask.Exception.InnerException; // The Exception that caused the Task to enter the faulted state
MelonLoader.MelonLogger.Error("Encountered an error while executing the XR task: " + innerException.Message);
// Handle the exception appropriately.
}
if (wasInXr != IsInXR())
{
ReloadAvatar();
InvokeOnPostSwitch(!wasInXr);
}
else
{
NotifyOnFailedSwitch();
InvokeOnFailedSwitch(!wasInXr);
}
if (useWorldTransition)
yield return StartCoroutine(ContinueTransition());
SwitchInProgress = false;
}
private IEnumerator XRAndReloadAvatar(bool start)
{
yield return StartCoroutine(start ? XRHandler.StartXR() : XRHandler.StopXR());
bool isUsingVr = IsInXR();
if (isUsingVr == start)
{
ReloadAvatar();
InvokeOnPostSwitch(start);
}
else
{
InvokeOnFailedSwitch(start);
}
}
private void ReloadAvatar()
{
if (!ReloadLocalAvatar)
if (!ReloadLocalAvatar)
return;
Utils.ClearLocalAvatar();
Utils.ReloadLocalAvatar();
// TODO: Is there a better way to reload only locally?
PlayerSetup.Instance.ClearAvatar();
AssetManagement.Instance.LoadLocalAvatar(MetaPort.Instance.currentAvatarGuid);
}
#endregion
private bool IsInXR()
{
return XRGeneralSettings.Instance.Manager.activeLoader != null;
}
private UnityEngine.Camera GetPlayerCamera(bool isVr)
{
return (isVr ? PlayerSetup.Instance.vrCamera : PlayerSetup.Instance.desktopCamera)
.GetComponent<UnityEngine.Camera>();
}
#endregion
#region Transition Coroutines
private IEnumerator StartTransition()
@ -123,38 +177,46 @@ public class VRModeSwitchManager : MonoBehaviour
#region Event Handling
public class VRModeEventArgs : EventArgs
private void InvokeOnPreSwitch(bool isUsingVr)
{
public bool IsUsingVr { get; }
public Camera PlayerCamera { get; }
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
public VRModeEventArgs(bool isUsingVr, Camera playerCamera)
{
IsUsingVr = isUsingVr;
PlayerCamera = playerCamera;
}
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPreSwitchInternal?.Invoke(isUsingVr, playerCamera);
CVRGameEventSystem.VRModeSwitch.OnPreSwitch?.Invoke(isUsingVr, playerCamera);
}
public static event EventHandler<VRModeEventArgs> OnPreVRModeSwitch;
public static event EventHandler<VRModeEventArgs> OnPostVRModeSwitch;
public static event EventHandler<VRModeEventArgs> OnFailVRModeSwitch;
private void InvokeOnPreSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPreVRModeSwitch, isUsingVr);
private void InvokeOnPostSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPostVRModeSwitch, isUsingVr);
private void InvokeOnFailedSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnFailVRModeSwitch, isUsingVr);
private void SafeInvokeUnityEvent(EventHandler<VRModeEventArgs> switchEvent, bool isUsingVr)
private void InvokeOnPostSwitch(bool isUsingVr)
{
try
{
Camera playerCamera = Utils.GetPlayerCameraObject(isUsingVr).GetComponent<Camera>();
switchEvent?.Invoke(this, new VRModeEventArgs(isUsingVr, playerCamera));
}
catch (Exception e)
{
DesktopVRSwitch.Logger.Error($"Error in event handler: {e}");
}
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPostSwitchInternal?.Invoke(isUsingVr, playerCamera);
CVRGameEventSystem.VRModeSwitch.OnPostSwitch?.Invoke(isUsingVr, playerCamera);
}
private void InvokeOnFailedSwitch(bool isUsingVr)
{
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnFailedSwitchInternal?.Invoke(isUsingVr, playerCamera);
CVRGameEventSystem.VRModeSwitch.OnFailedSwitch?.Invoke(isUsingVr, playerCamera);
}
#endregion
}
#region Notifications
private void NotifyOnPreSwitch()
{
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client",
"VR Mode Switch", "Switching to " + (IsInXR() ? "Desktop" : "VR") + " Mode");
}
private void NotifyOnFailedSwitch()
{
// TODO: Can we get reason it failed?
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client",
"VR Mode Switch", "Switch failed");
}
#endregion
}

View file

@ -1,4 +1,8 @@
using System.Collections;
#if !PLATFORM_ANDROID
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ABI_RC.Core.Savior;
using Unity.XR.OpenVR;
@ -6,62 +10,76 @@ using UnityEngine;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
using Valve.VR;
using Object = UnityEngine.Object;
namespace NAK.DesktopVRSwitch;
internal static class XRHandler
namespace ABI_RC.Systems.VRModeSwitch
{
internal static IEnumerator StartXR()
internal static class XRHandler
{
yield return XRGeneralSettings.Instance.Manager.InitializeLoader();
if (XRGeneralSettings.Instance.Manager.activeLoader != null)
XRGeneralSettings.Instance.Manager.StartSubsystems();
else
yield return StopXR();
yield return null;
}
internal static IEnumerator StopXR()
{
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
yield break;
// Forces SteamVR to reinitialize SteamVR_Input next switch
SteamVR_ActionSet_Manager.DisableAllActionSets();
SteamVR_Input.initialized = false;
// Remove SteamVR behaviour & render
UnityEngine.Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject);
SteamVR.enabled = false; // disposes SteamVR
Patches.SteamVRNullReferencePatch.DestroySteamVRInstancesImmediate();
// Disable UnityXR
XRGeneralSettings.Instance.Manager.StopSubsystems();
XRGeneralSettings.Instance.Manager.DeinitializeLoader();
// We don't really need to wait a frame on Stop()
yield return null;
}
internal static void SwitchLoader()
{
XRLoader item;
if (!CheckVR.Instance.forceOpenXr)
private static async Task InitializeXRLoader()
{
item = ScriptableObject.CreateInstance<OpenVRLoader>();
DesktopVRSwitch.Logger.Msg("Using XR Loader: SteamVR");
EnsureXRLoader();
XRGeneralSettings.Instance.Manager.InitializeLoaderSync();
await Task.Yield();
}
else
internal static async Task StartXR()
{
item = ScriptableObject.CreateInstance<OpenXRLoader>();
DesktopVRSwitch.Logger.Msg("Using XR Loader: OpenXR");
await InitializeXRLoader();
if (XRGeneralSettings.Instance.Manager.activeLoader != null)
XRGeneralSettings.Instance.Manager.StartSubsystems();
else
await StopXR(); // assuming StopXR is now an async method.
// Await a delay or equivalent method to wait for a frame.
await Task.Yield(); // This line is to simulate "waiting for the next frame" in an async way.
}
typeof(XRManagerSettings)
.GetField("m_Loaders", BindingFlags.Instance | BindingFlags.NonPublic)
?.SetValue(XRGeneralSettings.Instance.Manager, new List<XRLoader> { item });
internal static Task StopXR()
{
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
return Task.CompletedTask;
// Forces SteamVR to reinitialize SteamVR_Input next switch
SteamVR_ActionSet_Manager.DisableAllActionSets();
SteamVR_Input.initialized = false;
// Remove SteamVR behaviour & render
Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject);
SteamVR.enabled = false; // disposes SteamVR
// Disable UnityXR
XRGeneralSettings.Instance.Manager.StopSubsystems();
XRGeneralSettings.Instance.Manager.DeinitializeLoader();
return Task.CompletedTask;
// If we need to wait for something specific (like a frame), we use Task.Delay or equivalent.
// In this case, it seems like you don't need to wait after stopping XR,
// so we don't necessarily need an equivalent to 'yield return null' here.
}
private static void EnsureXRLoader()
{
Type selectedLoaderType = !CheckVR.Instance.forceOpenXr ? typeof(OpenVRLoader) : typeof(OpenXRLoader);
// dont do anything if we already have the loader selected
if (XRGeneralSettings.Instance.Manager.activeLoaders.Count > 0
&& XRGeneralSettings.Instance.Manager.activeLoaders[0].GetType() == selectedLoaderType)
return;
XRLoader newLoaderInstance = (XRLoader)ScriptableObject.CreateInstance(selectedLoaderType);
FieldInfo field = typeof(XRManagerSettings).GetField("m_Loaders",
BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null) return;
// destroy old loaders, set the new laoder
// this should not happen normally, but changing loader during runtime sounds funni
if (field.GetValue(XRGeneralSettings.Instance.Manager) is List<XRLoader> currentLoaders)
foreach (XRLoader loader in currentLoaders.Where(loader => loader != null)) Object.Destroy(loader);
field.SetValue(XRGeneralSettings.Instance.Manager, new List<XRLoader> { newLoaderInstance });
}
}
}
#endif