diff --git a/DesktopVRSwitch/Main.cs b/DesktopVRSwitch/Main.cs index a4d85ef..c568a8f 100644 --- a/DesktopVRSwitch/Main.cs +++ b/DesktopVRSwitch/Main.cs @@ -7,6 +7,7 @@ using MelonLoader; using RootMotion.FinalIK; using System.Collections; using UnityEngine; +using Object = UnityEngine.Object; using UnityEngine.XR; using Valve.VR; @@ -14,7 +15,6 @@ namespace DesktopVRSwitch; public class DesktopVRSwitch : MelonMod { - private static System.Object melon; private static bool isAttemptingSwitch = false; private static float timedSwitch = 0f; @@ -25,15 +25,16 @@ public class DesktopVRSwitch : MelonMod { //start attempt isAttemptingSwitch = true; - melon = MelonCoroutines.Start(AttemptPlatformSwitch()); + MelonCoroutines.Start(AttemptPlatformSwitch()); + //how long we wait until we assume an error occured timedSwitch = Time.time + 10f; } + //catch if coroutine just decided to not finish... which happens? if (isAttemptingSwitch && Time.time > timedSwitch) { isAttemptingSwitch = false; - MelonCoroutines.Stop(melon); - MelonLogger.Msg("Timer exceeded. Something is wrong."); + MelonLogger.Error("Timer exceeded. Something is wrong and coroutine failed partway."); } } @@ -41,12 +42,38 @@ public class DesktopVRSwitch : MelonMod { bool toVR = !MetaPort.Instance.isUsingVr; - //load SteamVR/OpenVR if entering VR - MelonCoroutines.Start(LoadDevice("OpenVR", toVR)); + //load or unload SteamVR + if (toVR) + { + //force SteamVR to fully initialize, this does all and more than what i did with LoadDevice() + SteamVR.Initialize(true); + + //Just to make sure. Game does this natively when entering VR. + SteamVR_Settings.instance.pauseGameWhenDashboardVisible = false; - //we need to wait a frame or meet doom :shock: :shock: :stare: - //we are waiting a frame in LoadDevice after LoadDeviceByName() - yield return new WaitForEndOfFrame(); + //TODO: something needs to be done to reinitialize SteamVR_Input or SteamVR_Actions + //If you restart SteamVR after already have been in VRMode, the steamvr action handles break + //ive tried: + //SteamVR_Input.Initialize(true) + //SteamVR_Actions.PreInitialize() + //Destroying SteamVR_Settings on DesktopMode + //Destroying SteamVR_Behavior on DesktopMode + //Destroying SteamVR_Render on DesktopMode + //Combinations of all of these.. + //Its probably really simple, but I just cannot figure out how. + } + else + { + //force SteamVR to let go of Chillout + XRSettings.LoadDeviceByName("None"); + XRSettings.enabled = false; + + //destroy [SteamVR] gameobject as next SteamVR.Initialize creates a new one + Object.Destroy(SteamVR_Behaviour.instance.gameObject); + + //what even does this do that is actually important? + SteamVR.SafeDispose(); + } CloseMenuElements(toVR); @@ -57,6 +84,10 @@ public class DesktopVRSwitch : MelonMod yield return new WaitForEndOfFrame(); SetPlayerSetup(toVR); + SwitchActiveCameraRigs(toVR); + CreateTempVRIK(toVR); + QuickCalibrate(toVR); + RepositionCohtmlHud(toVR); yield return new WaitForEndOfFrame(); @@ -64,10 +95,7 @@ public class DesktopVRSwitch : MelonMod yield return new WaitForEndOfFrame(); - SetSteamVRInstances(toVR); - - yield return new WaitForEndOfFrame(); - + //right here is the fucker most likely to break ReloadCVRInputManager(); //some menus have 0.5s wait(), so to be safe @@ -78,45 +106,12 @@ public class DesktopVRSwitch : MelonMod yield return null; isAttemptingSwitch = false; } - private static IEnumerator LoadDevice(string newDevice, bool isVR) - { - if (isVR) - { - if (String.Compare(XRSettings.loadedDeviceName, newDevice, true) != 0) - { - XRSettings.LoadDeviceByName(newDevice); - yield return null; - XRSettings.enabled = true; - SteamVR.settings.pauseGameWhenDashboardVisible = false; - if (SteamVR_Behaviour.instance.enabled == false) - { - SteamVR_Behaviour.instance.enabled = true; - SteamVR_Render.instance.enabled = true; - } - } - else - { - MelonLogger.Msg("OpenVR device already loaded!"); - MelonCoroutines.Stop(melon); - yield return null; - XRSettings.enabled = true; - if (SteamVR_Behaviour.instance.enabled == false) - { - SteamVR_Behaviour.instance.enabled = true; - SteamVR_Render.instance.enabled = true; - } - isAttemptingSwitch = false; - } - } - else - { - //holyfuck that was a lot of trial and error - SteamVR.enabled = false; - yield return new WaitForEndOfFrame(); - XRSettings.LoadDeviceByName("None"); - yield return null; - } - } + + + + //shitton of try catch below + + // shouldn't be that important, right? private static void CloseMenuElements(bool isVR) @@ -144,112 +139,166 @@ public class DesktopVRSwitch : MelonMod private static void SetMetaPort(bool isVR) { - if (MetaPort.Instance == null) + try { - MelonLogger.Msg("MetaPort Instance not found!!!"); - return; + MelonLogger.Msg($"Set MetaPort isUsingVr to {isVR}."); + MetaPort.Instance.isUsingVr = isVR; } - MelonLogger.Msg($"Set MetaPort isUsingVr to {isVR}."); - MetaPort.Instance.isUsingVr = isVR; - } - - //uh huh - private static void SetSteamVRInstances(bool isVR) - { - if (SteamVR_Behaviour.instance == null) + catch (Exception) { - MelonLogger.Msg("SteamVR Instances not found!!!"); - return; + MelonLogger.Error("Setting MetaPort isUsingVr failed. Is MetaPort.Instance invalid?"); + MelonLogger.Msg("MetaPort.Instance: " + MetaPort.Instance); + throw; } - MelonLogger.Msg($"Set SteamVR monobehavior instances to {isVR}."); - SteamVR_Behaviour.instance.enabled = isVR; - SteamVR_Render.instance.enabled = isVR; - //set again just in case on desktop & disabling - XRSettings.enabled = isVR; } private static void SetPlayerSetup(bool isVR) { - if (PlayerSetup.Instance == null) + try { - MelonLogger.Msg("PlayerSetup Instance not found!!!"); - return; + MelonLogger.Msg($"Set PlayerSetup instance to {isVR}."); + PlayerSetup.Instance._inVr = isVR; } - - if (isVR) + catch (Exception) { - MelonLogger.Msg("Creating temp VRIK component."); - VRIK ik = (VRIK)PlayerSetup.Instance._avatar.GetComponent(typeof(VRIK)); - if (ik == null) + MelonLogger.Error("Setting PlayerSetup _inVr failed. Is PlayerSetup.Instance invalid?"); + MelonLogger.Msg("PlayerSetup.Instance: " + PlayerSetup.Instance); + throw; + } + } + + private static void CreateTempVRIK(bool isVR) + { + try + { + if (isVR) { - ik = PlayerSetup.Instance._avatar.AddComponent(); + MelonLogger.Msg("Creating temp VRIK component."); + VRIK ik = (VRIK)PlayerSetup.Instance._avatar.GetComponent(typeof(VRIK)); + if (ik == null) + { + ik = PlayerSetup.Instance._avatar.AddComponent(); + } + ik.solver.IKPositionWeight = 0f; + ik.enabled = false; + } + else + { + MelonLogger.Msg("Temp VRIK component is not needed. Ignoring."); } - ik.solver.IKPositionWeight = 0f; - ik.enabled = false; } - - MelonLogger.Msg($"Set PlayerSetup instance to {isVR}."); - PlayerSetup.Instance._inVr = isVR; - - //we invoke calibrate to get VRIK and calibrator instance set up, faster than full recalibrate - MelonLogger.Msg("Called CalibrateAvatar() on PlayerSetup.Instance. Expect a few errors from PlayerSetup Update() and LateUpdate()."); - PlayerSetup.Instance.CalibrateAvatar(); - - MelonLogger.Msg("Switched active camera rigs."); - PlayerSetup.Instance.desktopCameraRig.SetActive(!isVR); - PlayerSetup.Instance.vrCameraRig.SetActive(isVR); - - if (CohtmlHud.Instance == null) + catch (Exception) { - MelonLogger.Msg("CohtmlHud Instance not found!!!"); - return; + MelonLogger.Error("Temp creation of VRIK on avatar failed. Is PlayerSetup.Instance invalid?"); + MelonLogger.Msg("PlayerSetup.Instance: " + PlayerSetup.Instance); + throw; } - MelonLogger.Msg("Parented CohtmlHud to active camera."); - CohtmlHud.Instance.gameObject.transform.parent = isVR ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.desktopCamera.transform; + } - //i think the VR offset depends on headset... cant find where in the games code it is though so could be wrong... ? - CohtmlHud.Instance.gameObject.transform.localPosition = isVR ? new Vector3(-0.2f, -0.391f, 1.244f) : new Vector3(0f, 0f, 1.3f); - CohtmlHud.Instance.gameObject.transform.localRotation = Quaternion.Euler( new Vector3(0f, 180f, 0f) ); + private static void QuickCalibrate(bool isVR) + { + try + { + //we invoke calibrate to get VRIK and calibrator instance set up, faster than full recalibrate + MelonLogger.Msg("Called CalibrateAvatar() on PlayerSetup.Instance. Expect a few errors from PlayerSetup Update() and LateUpdate()."); + PlayerSetup.Instance.CalibrateAvatar(); + } + catch (Exception) + { + MelonLogger.Error("CalibrateAvatar() failed. Is PlayerSetup.Instance invalid?"); + MelonLogger.Msg("PlayerSetup.Instance: " + PlayerSetup.Instance); + throw; + } + } + + private static void SwitchActiveCameraRigs(bool isVR) + { + try + { + MelonLogger.Msg("Switched active camera rigs."); + PlayerSetup.Instance.desktopCameraRig.SetActive(!isVR); + PlayerSetup.Instance.vrCameraRig.SetActive(isVR); + } + catch (Exception) + { + MelonLogger.Error("Error switching active cameras. Are the camera rigs invalid?"); + MelonLogger.Msg("PlayerSetup.Instance.desktopCameraRig: " + PlayerSetup.Instance.desktopCameraRig); + MelonLogger.Msg("PlayerSetup.Instance.vrCameraRig: " + PlayerSetup.Instance.vrCameraRig); + throw; + } + } + + private static void RepositionCohtmlHud(bool isVR) + { + try + { + MelonLogger.Msg("Parented CohtmlHud to active camera."); + CohtmlHud.Instance.gameObject.transform.parent = isVR ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.desktopCamera.transform; + //i think the VR offset may be different between headsets, but i cannot find where in games code they are set + CohtmlHud.Instance.gameObject.transform.localPosition = isVR ? new Vector3(-0.2f, -0.391f, 1.244f) : new Vector3(0f, 0f, 1.3f); + CohtmlHud.Instance.gameObject.transform.localRotation = Quaternion.Euler(new Vector3(0f, 180f, 0f)); + } + catch (Exception) + { + MelonLogger.Error("Error parenting CohtmlHud to active camera. Is CohtmlHud.Instance invalid?"); + MelonLogger.Msg("CohtmlHud.Instance: " + CohtmlHud.Instance); + throw; + } } //hopefully whatever rework was hinted at doesn't immediatly break this private static void SetMovementSystem(bool isVR) { - if (MovementSystem.Instance == null) + try { - MelonLogger.Msg("MovementSystem Instance not found!!!"); - return; + MelonLogger.Msg($"Set MovementSystem instance to {isVR}."); + MovementSystem.Instance.isVr = true; + } + catch (Exception) + { + MelonLogger.Error("Setting MovementSystem isVr failed. Is MovementSystem.Instance invalid?"); + MelonLogger.Msg("MovementSystem.Instance: " + MovementSystem.Instance); + throw; } - MelonLogger.Msg($"Set MovementSystem instance to {isVR}."); - MovementSystem.Instance.isVr = true; } private static void ReloadCVRInputManager() { - if (CVRInputManager.Instance == null) + try { - MelonLogger.Msg("CVRInputManager Instance not found!!!"); - return; + MelonLogger.Msg("Set CVRInputManager reload to True. Input should reload next frame..."); + CVRInputManager.Instance.reload = true; + //just in case + CVRInputManager.Instance.inputEnabled = true; + CVRInputManager.Instance.blockedByUi = false; + //sometimes head can get stuck, so just in case + CVRInputManager.Instance.independentHeadToggle = false; + //just nice to load into desktop with idle gesture + CVRInputManager.Instance.gestureLeft = 0f; + CVRInputManager.Instance.gestureLeftRaw = 0f; + CVRInputManager.Instance.gestureRight = 0f; + CVRInputManager.Instance.gestureRightRaw = 0f; + } + catch (Exception) + { + MelonLogger.Error("CVRInputManager reload failed. Is CVRInputManager.Instance invalid?"); + MelonLogger.Msg("CVRInputManager.Instance: " + CVRInputManager.Instance); + throw; } - MelonLogger.Msg("Set CVRInputManager reload to True. Input should reload next frame..."); - CVRInputManager.Instance.reload = true; - CVRInputManager.Instance.inputEnabled = true; - CVRInputManager.Instance.blockedByUi = false; - CVRInputManager.Instance.independentHeadToggle = false; - CVRInputManager.Instance.gestureLeft = 0f; - CVRInputManager.Instance.gestureLeftRaw = 0f; - CVRInputManager.Instance.gestureRight = 0f; - CVRInputManager.Instance.gestureRightRaw = 0f; } private static void Recalibrate() { - if (PlayerSetup.Instance == null) + try { - MelonLogger.Msg("PlayerSetup Instance not found!!!"); - return; + MelonLogger.Msg("Called ReCalibrateAvatar() on PlayerSetup.Instance. Will take a second..."); + PlayerSetup.Instance.ReCalibrateAvatar(); + } + catch (Exception) + { + MelonLogger.Error("ReCalibrateAvatar() failed. Is PlayerSetup.Instance invalid?"); + MelonLogger.Msg("PlayerSetup.Instance: " + PlayerSetup.Instance); + throw; } - MelonLogger.Msg("Called ReCalibrateAvatar() on PlayerSetup.Instance. Will take a second..."); - PlayerSetup.Instance.ReCalibrateAvatar(); } } \ No newline at end of file