[DesktopVRSwitch] - > DesktopXRSwitch

This commit is contained in:
NotAKidoS 2023-05-17 12:06:48 -05:00
parent 37fcf6ece1
commit 05374459be
21 changed files with 441 additions and 352 deletions

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Reference Include="Unity.XR.Management">
<HintPath>..\_ManagedLibs\Unity.XR.Management.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,135 @@
using NAK.Melons.DesktopXRSwitch.Patches;
using System.Collections;
using UnityEngine;
using UnityEngine.XR.Management;
using Valve.VR;
namespace NAK.Melons.DesktopXRSwitch;
public class DesktopXRSwitcher : MonoBehaviour
{
//Debug Settings
public bool _reloadLocalAvatar = true;
public bool _softVRSwitch = false;
//Internal Stuff
private bool _switchInProgress = false;
void Start()
{
//do not pause game, this breaks dynbones & trackers
SteamVR_Settings.instance.pauseGameWhenDashboardVisible = false;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.F6) && Input.GetKey(KeyCode.LeftControl))
{
SwitchXRMode();
}
}
public void SwitchXRMode()
{
if (_switchInProgress) return;
if (!IsInXR())
{
StartCoroutine(StartXRSystem());
}
else
{
StartCoroutine(StopXR());
}
}
public bool IsInXR() => XRGeneralSettings.Instance.Manager.activeLoader != null;
private IEnumerator StartXRSystem()
{
PreXRModeSwitch(true);
yield return null;
yield return XRGeneralSettings.Instance.Manager.InitializeLoader();
if (XRGeneralSettings.Instance.Manager.activeLoader != null)
{
DesktopXRSwitch.Logger.Msg("Starting OpenXR...");
XRGeneralSettings.Instance.Manager.StartSubsystems();
yield return null;
PostXRModeSwitch(true);
yield break;
}
DesktopXRSwitch.Logger.Error("Initializing XR Failed. Is there no XR device connected?");
FailedVRModeSwitch(true);
yield break;
}
private IEnumerator StopXR()
{
PreXRModeSwitch(false);
yield return null;
if (XRGeneralSettings.Instance.Manager.isInitializationComplete)
{
XRGeneralSettings.Instance.Manager.StopSubsystems();
XRGeneralSettings.Instance.Manager.DeinitializeLoader();
yield return null;
PostXRModeSwitch(false);
yield break;
}
DesktopXRSwitch.Logger.Error("Attempted to exit VR without a VR device loaded.");
FailedVRModeSwitch(false);
yield break;
}
//one frame after switch attempt
public void FailedVRModeSwitch(bool isXR)
{
if (_softVRSwitch) return;
//let tracked objects know a switch failed
XRModeSwitchTracker.FailXRModeSwitch(isXR);
}
//one frame before switch attempt
public void PreXRModeSwitch(bool isXR)
{
if (_softVRSwitch) return;
//let tracked objects know we are attempting to switch
XRModeSwitchTracker.PreXRModeSwitch(isXR);
}
//one frame after switch attempt
public void PostXRModeSwitch(bool isXR)
{
if (_softVRSwitch) return;
//close the menus
TryCatchHell.CloseCohtmlMenus();
//the base of VR checks
TryCatchHell.SetCheckVR(isXR);
TryCatchHell.SetMetaPort(isXR);
//game basics for functional gameplay post switch
TryCatchHell.RepositionCohtmlHud(isXR);
TryCatchHell.UpdateHudOperations(isXR);
TryCatchHell.DisableMirrorCanvas();
TryCatchHell.SwitchActiveCameraRigs(isXR);
TryCatchHell.SwitchCVRInputManager(isXR);
TryCatchHell.UpdateRichPresence();
TryCatchHell.UpdateGestureReconizerCam();
//let tracked objects know we switched
XRModeSwitchTracker.PostXRModeSwitch(isXR);
//reload avatar by default, optional for debugging
if (_reloadLocalAvatar)
{
TryCatchHell.ReloadLocalAvatar();
}
else
{
}
_switchInProgress = false;
}
}

View file

@ -0,0 +1,93 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Util.Object_Behaviour;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.MovementSystem;
using HarmonyLib;
using NAK.Melons.DesktopXRSwitch.Patches;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch.HarmonyPatches;
internal class PlayerSetupPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), "Start")]
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
{
__instance.gameObject.AddComponent<PlayerSetupTracker>();
if (CheckVR.Instance != null)
{
CheckVR.Instance.gameObject.AddComponent<DesktopXRSwitcher>();
return;
}
__instance.gameObject.AddComponent<DesktopXRSwitcher>();
DesktopXRSwitch.Logger.Error("CheckVR not found. Reverting to fallback method. This should never happen!");
}
}
internal class MovementSystemPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(MovementSystem), "Start")]
private static void Postfix_MovementSystem_Start(ref MovementSystem __instance)
{
__instance.gameObject.AddComponent<MovementSystemTracker>();
}
}
internal class CVRPickupObjectPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(CVRPickupObject), "Start")]
private static void Prefix_CVRPickupObject_Start(ref CVRPickupObject __instance)
{
if (__instance.gripType == CVRPickupObject.GripType.Free) return;
Transform vrOrigin = __instance.gripOrigin;
Transform desktopOrigin = __instance.gripOrigin.Find("[Desktop]");
if (vrOrigin != null && desktopOrigin != null)
{
var tracker = __instance.gameObject.AddComponent<CVRPickupObjectTracker>();
tracker.pickupObject = __instance;
tracker.storedGripOrigin = (!MetaPort.Instance.isUsingVr ? vrOrigin : desktopOrigin);
}
}
}
internal class CVRWorldPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(CVRWorld), "SetDefaultCamValues")]
private static void CVRWorld_SetDefaultCamValues_Postfix()
{
ReferenceCameraPatch.OnWorldLoad();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(CVRWorld), "CopyRefCamValues")]
private static void CVRWorld_CopyRefCamValues_Postfix()
{
ReferenceCameraPatch.OnWorldLoad();
}
}
internal class CameraFacingObjectPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(CameraFacingObject), "Start")]
private static void Postfix_CameraFacingObject_Start(ref CameraFacingObject __instance)
{
__instance.gameObject.AddComponent<CameraFacingObjectTracker>();
}
}
internal class IKSystemPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(IKSystem), "Start")]
private static void Postfix_IKSystem_Start(ref IKSystem __instance)
{
__instance.gameObject.AddComponent<IKSystemTracker>();
}
}

51
DesktopXRSwitch/Main.cs Normal file
View file

@ -0,0 +1,51 @@
using MelonLoader;
/**
I know the TryCatchHell thing might be a bit exessive, but it is
built so if a user that happens to have access to a build I do not,
I will have a good idea of what broke and where, and what to look out
for when updates/experimentals release. (which has happened a few times)
It is also just in case other mods break or tweak functionality that
could fuck with switching. Or if they try to detect switching and break...
The XRModeSwitchTracker system is also built so I can easily & quickly make adjustments to
components that may or may not change between builds without breaking the rest of the mod.
**/
namespace NAK.Melons.DesktopXRSwitch;
public class DesktopXRSwitch : MelonMod
{
internal static MelonLogger.Instance Logger;
public static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(DesktopXRSwitch));
public static readonly MelonPreferences_Entry<bool> EntryEnterCalibrationOnSwitch =
Category.CreateEntry("Enter Calibration on Switch", true, description: "Should you automatically be placed into calibration after switch if FBT is available? Overridden by Save Calibration IK setting.");
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
ApplyPatches(typeof(HarmonyPatches.CVRPickupObjectPatches));
ApplyPatches(typeof(HarmonyPatches.CVRWorldPatches));
ApplyPatches(typeof(HarmonyPatches.CameraFacingObjectPatches));
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
ApplyPatches(typeof(HarmonyPatches.MovementSystemPatches));
}
private void ApplyPatches(Type type)
{
try
{
HarmonyInstance.PatchAll(type);
}
catch (Exception e)
{
Logger.Msg($"Failed while patching {type.Name}!");
Logger.Error(e);
}
}
}

View file

@ -0,0 +1,31 @@
using ABI.CCK.Components;
using UnityEngine;
//Thanks Ben! I was scared of transpiler so I reworked a bit...
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class CVRPickupObjectTracker : MonoBehaviour
{
public CVRPickupObject pickupObject;
public Transform storedGripOrigin;
void Start()
{
XRModeSwitchTracker.OnPostXRModeSwitch += PostXRModeSwitch;
}
void OnDestroy()
{
XRModeSwitchTracker.OnPostXRModeSwitch -= PostXRModeSwitch;
}
public void PostXRModeSwitch(bool isXR, Camera activeCamera)
{
if (pickupObject != null)
{
if (pickupObject._controllerRay != null) pickupObject._controllerRay.DropObject(true);
(storedGripOrigin, pickupObject.gripOrigin) = (pickupObject.gripOrigin, storedGripOrigin);
}
}
}

View file

@ -0,0 +1,24 @@
using ABI_RC.Core.Util.Object_Behaviour;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class CameraFacingObjectTracker : MonoBehaviour
{
public CameraFacingObject cameraFacingObject;
void Start()
{
cameraFacingObject = GetComponent<CameraFacingObject>();
XRModeSwitchTracker.OnPostXRModeSwitch += PostXRModeSwitch;
}
void OnDestroy()
{
XRModeSwitchTracker.OnPostXRModeSwitch -= PostXRModeSwitch;
}
public void PostXRModeSwitch(bool isXR, Camera activeCamera)
{
cameraFacingObject.m_Camera = activeCamera;
}
}

View file

@ -0,0 +1,89 @@
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.IK.TrackingModules;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class IKSystemTracker : MonoBehaviour
{
public IKSystem ikSystem;
void Start()
{
ikSystem = GetComponent<IKSystem>();
XRModeSwitchTracker.OnPreXRModeSwitch += PreXRModeSwitch;
XRModeSwitchTracker.OnFailXRModeSwitch += FailedXRModeSwitch;
XRModeSwitchTracker.OnPostXRModeSwitch += PostXRModeSwitch;
}
void OnDestroy()
{
XRModeSwitchTracker.OnPreXRModeSwitch -= PreXRModeSwitch;
XRModeSwitchTracker.OnFailXRModeSwitch -= FailedXRModeSwitch;
XRModeSwitchTracker.OnPostXRModeSwitch -= PostXRModeSwitch;
}
public void PreXRModeSwitch(bool inXR, Camera activeCamera)
{
BodySystem.TrackingEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
BodySystem.TrackingLocomotionEnabled = false;
if (IKSystem.vrik != null)
IKSystem.vrik.enabled = false;
}
public void FailedXRModeSwitch(bool inXR, Camera activeCamera)
{
BodySystem.TrackingEnabled = true;
BodySystem.TrackingPositionWeight = 1f;
BodySystem.TrackingLocomotionEnabled = true;
if (IKSystem.vrik != null)
IKSystem.vrik.enabled = true;
}
public void PostXRModeSwitch(bool inXR, Camera activeCamera)
{
if (IKSystem.vrik != null)
DestroyImmediate(IKSystem.vrik);
//make sure you are fully tracking
BodySystem.TrackingEnabled = true;
BodySystem.TrackingPositionWeight = 1f;
BodySystem.TrackingLocomotionEnabled = true;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.isCalibrating = false;
BodySystem.isRecalibration = false;
//make it so you dont instantly end up in FBT from Desktop
IKSystem.firstAvatarLoaded = DesktopXRSwitch.EntryEnterCalibrationOnSwitch.Value;
//turn of finger tracking just in case user switched controllers
ikSystem.FingerSystem.controlActive = false;
//vrik should be deleted by avatar switch
SetupOpenXRTrackingModule(inXR);
}
void SetupOpenXRTrackingModule(bool enableVR)
{
var openXRTrackingModule = ikSystem._trackingModules.OfType<OpenXRTrackingModule>().FirstOrDefault();
if (openXRTrackingModule != null)
{
if (enableVR)
{
openXRTrackingModule.ModuleStart();
}
else
{
openXRTrackingModule.ModuleDestroy();
if (openXRTrackingModule != null)
ikSystem._trackingModules.Remove(openXRTrackingModule);
}
}
else if (enableVR)
{
var newVRModule = new OpenXRTrackingModule();
ikSystem.AddTrackingModule(newVRModule);
}
}
}

View file

@ -0,0 +1,54 @@
using ABI_RC.Systems.MovementSystem;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class MovementSystemTracker : MonoBehaviour
{
public MovementSystem movementSystem;
public Vector3 preSwitchWorldPosition;
public Quaternion preSwitchWorldRotation;
void Start()
{
movementSystem = GetComponent<MovementSystem>();
XRModeSwitchTracker.OnPreXRModeSwitch += PreXRModeSwitch;
XRModeSwitchTracker.OnPostXRModeSwitch += PostXRModeSwitch;
}
void OnDestroy()
{
XRModeSwitchTracker.OnPreXRModeSwitch -= PreXRModeSwitch;
XRModeSwitchTracker.OnPostXRModeSwitch -= PostXRModeSwitch;
}
public void PreXRModeSwitch(bool isXR, Camera activeCamera)
{
//correct rotationPivot y position, so we dont teleport up/down
Vector3 position = movementSystem.rotationPivot.transform.position;
position.y = movementSystem.transform.position.y;
preSwitchWorldPosition = position;
preSwitchWorldRotation = movementSystem.rotationPivot.transform.rotation;
//ChilloutVR does not use VRIK root right, so avatar root is VR player root.
//This causes desync between VR and Desktop positions & collision on switch.
//I correct for this in lazy way, but i use rotationPivot instead of avatar root,
//so the user can still switch even if avatar is null (if it failed to load for example).
movementSystem.ChangeCrouch(false);
movementSystem.ChangeProne(false);
}
public void PostXRModeSwitch(bool isXR, Camera activeCamera)
{
//immediatly update camera to new camera transform
movementSystem.rotationPivot = activeCamera.transform;
//lazy way of correcting Desktop & VR offset issue (game does the maths)
movementSystem.TeleportToPosRot(preSwitchWorldPosition, preSwitchWorldRotation, false);
//recenter desktop collision to player object
if (!isXR) movementSystem.UpdateColliderCenter(movementSystem.transform.position);
movementSystem.ChangeCrouch(false);
movementSystem.ChangeProne(false);
}
}

View file

@ -0,0 +1,142 @@
using ABI.CCK.Components;
using ABI_RC.Core.Base;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using HarmonyLib;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class PlayerSetupTracker : MonoBehaviour
{
public static PlayerSetupTracker Instance;
public PlayerSetup playerSetup;
public Traverse _initialCameraPosTraverse;
public Traverse _lookIKTraverse;
public Traverse _lastScaleTraverse;
public HumanPose avatarInitialHumanPose;
public HumanPoseHandler avatarHumanPoseHandler;
public GameObject avatarObject;
public CVRAvatar avatarDescriptor;
public Animator avatarAnimator;
public Vector3 initialPosition;
public Quaternion initialRotation;
void Start()
{
Instance = this;
playerSetup = GetComponent<PlayerSetup>();
_initialCameraPosTraverse = Traverse.Create(playerSetup).Field("initialCameraPos");
_lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK");
_lastScaleTraverse = Traverse.Create(playerSetup).Field("lastScale");
}
public void OnSetupAvatar(GameObject avatar)
{
avatarObject = avatar;
avatarDescriptor = avatarObject.GetComponent<CVRAvatar>();
avatarAnimator = avatarObject.GetComponent<Animator>();
initialPosition = avatarObject.transform.localPosition;
initialRotation = avatarObject.transform.localRotation;
if (avatarHumanPoseHandler == null)
{
avatarHumanPoseHandler = new HumanPoseHandler(avatarAnimator.avatar, avatarAnimator.transform);
}
avatarHumanPoseHandler.GetHumanPose(ref avatarInitialHumanPose);
}
public void OnClearAvatar()
{
avatarObject = null;
avatarDescriptor = null;
avatarAnimator = null;
initialPosition = Vector3.one;
initialRotation = Quaternion.identity;
avatarHumanPoseHandler = null;
}
public void ConfigureAvatarIK(bool isVR)
{
bool StateOnDisable = avatarAnimator.keepAnimatorStateOnDisable;
avatarAnimator.keepAnimatorStateOnDisable = true;
avatarAnimator.enabled = false;
//reset avatar offsets
avatarObject.transform.localPosition = initialPosition;
avatarObject.transform.localRotation = initialRotation;
avatarHumanPoseHandler.SetHumanPose(ref avatarInitialHumanPose);
if (isVR)
{
SwitchAvatarVr();
}
else
{
SwitchAvatarDesktop();
}
//lazy fix
_lastScaleTraverse.SetValue(Vector3.zero);
MethodInfo setPlaySpaceScaleMethod = playerSetup.GetType().GetMethod("CheckUpdateAvatarScaleToPlaySpaceRelation", BindingFlags.NonPublic | BindingFlags.Instance);
setPlaySpaceScaleMethod.Invoke(playerSetup, null);
avatarAnimator.keepAnimatorStateOnDisable = StateOnDisable;
avatarAnimator.enabled = true;
}
private void SwitchAvatarVr()
{
if (avatarDescriptor == null) return;
//remove LookAtIK if found
LookAtIK avatarLookAtIK = avatarObject.GetComponent<LookAtIK>();
if (avatarLookAtIK != null) DestroyImmediate(avatarLookAtIK);
//have IKSystem set up avatar for VR
//a good chunk of IKSystem initialization checks for existing
//stuff & if it is set up, so game is ready to handle it
if (avatarAnimator != null && avatarAnimator.isHuman)
{
IKSystem.Instance.InitializeAvatar(avatarDescriptor);
}
}
private void SwitchAvatarDesktop()
{
if (avatarDescriptor == null) return;
//remove VRIK & VRIKRootController if found
VRIK avatarVRIK = avatarObject.GetComponent<VRIK>();
VRIKRootController avatarVRIKRootController = avatarObject.GetComponent<VRIKRootController>();
if (avatarVRIK != null) DestroyImmediate(avatarVRIK);
if (avatarVRIKRootController != null) DestroyImmediate(avatarVRIKRootController);
//remove all TwistRelaxer components
TwistRelaxer[] avatarTwistRelaxers = avatarObject.GetComponentsInChildren<TwistRelaxer>();
for (int i = 0; i < avatarTwistRelaxers.Length; i++)
{
DestroyImmediate(avatarTwistRelaxers[i]);
}
Transform headTransform = avatarAnimator.GetBoneTransform(HumanBodyBones.Head);
if (headTransform != null)
{
//set DesktopCameraRig to head bone pos
playerSetup.desktopCameraRig.transform.position = headTransform.position;
//add LookAtIK & Configure
LookAtIK lookAtIK = avatarObject.AddComponentIfMissing<LookAtIK>();
lookAtIK.solver.head = new IKSolverLookAt.LookAtBone(headTransform);
lookAtIK.solver.headWeight = 0.75f;
lookAtIK.solver.target = playerSetup.desktopCameraTarget.transform;
_lookIKTraverse.SetValue(lookAtIK);
}
//set camera position & initial position for headbob (both modes end up with same number)
playerSetup.desktopCamera.transform.localPosition = (Vector3)_initialCameraPosTraverse.GetValue();
}
}

View file

@ -0,0 +1,91 @@
using ABI_RC.Core.Base;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using Aura2API;
using BeautifyEffect;
using UnityEngine;
using UnityEngine.AzureSky;
using UnityEngine.Rendering.PostProcessing;
using Object = UnityEngine.Object;
namespace NAK.Melons.DesktopXRSwitch.Patches;
internal class ReferenceCameraPatch
{
internal static void OnWorldLoad()
{
Camera activeCamera = (MetaPort.Instance.isUsingVr ? PlayerSetup.Instance.vrCamera : PlayerSetup.Instance.desktopCamera).GetComponent<Camera>();
Camera inactiveCamera = (MetaPort.Instance.isUsingVr ? PlayerSetup.Instance.desktopCamera : PlayerSetup.Instance.vrCamera).GetComponent<Camera>();
CopyToInactiveCam(activeCamera, inactiveCamera);
}
internal static void CopyToInactiveCam(Camera activeCam, Camera inactiveCam)
{
DesktopXRSwitch.Logger.Msg("Copying active camera settings & components to inactive camera.");
//steal basic settings
inactiveCam.farClipPlane = activeCam.farClipPlane;
inactiveCam.nearClipPlane = activeCam.nearClipPlane;
inactiveCam.cullingMask = activeCam.cullingMask;
inactiveCam.depthTextureMode = activeCam.depthTextureMode;
//steal post processing if added
PostProcessLayer ppLayerActiveCam = activeCam.GetComponent<PostProcessLayer>();
PostProcessLayer ppLayerInactiveCam = inactiveCam.AddComponentIfMissing<PostProcessLayer>();
if (ppLayerActiveCam != null && ppLayerInactiveCam != null)
{
ppLayerInactiveCam.enabled = ppLayerActiveCam.enabled;
ppLayerInactiveCam.volumeLayer = ppLayerActiveCam.volumeLayer;
}
//what even is this aura camera stuff
AuraCamera auraActiveCam = activeCam.GetComponent<AuraCamera>();
AuraCamera auraInactiveCam = inactiveCam.AddComponentIfMissing<AuraCamera>();
if (auraActiveCam != null && auraInactiveCam != null)
{
auraInactiveCam.enabled = auraActiveCam.enabled;
auraInactiveCam.frustumSettings = auraActiveCam.frustumSettings;
}
else
{
auraInactiveCam.enabled = false;
}
//flare layer thing? the sun :_:_:_:_:_:
FlareLayer flareActiveCam = activeCam.GetComponent<FlareLayer>();
FlareLayer flareInactiveCam = inactiveCam.AddComponentIfMissing<FlareLayer>();
if (flareActiveCam != null && flareInactiveCam != null)
{
flareInactiveCam.enabled = flareActiveCam.enabled;
}
else
{
flareInactiveCam.enabled = false;
}
//and now what the fuck is fog scattering
AzureFogScattering azureFogActiveCam = activeCam.GetComponent<AzureFogScattering>();
AzureFogScattering azureFogInactiveCam = inactiveCam.AddComponentIfMissing<AzureFogScattering>();
if (azureFogActiveCam != null && azureFogInactiveCam != null)
{
azureFogInactiveCam.fogScatteringMaterial = azureFogActiveCam.fogScatteringMaterial;
}
else
{
Object.Destroy(inactiveCam.GetComponent<AzureFogScattering>());
}
//why is there so many thingsssssssss
Beautify beautifyActiveCam = activeCam.GetComponent<Beautify>();
Beautify beautifyInactiveCam = inactiveCam.AddComponentIfMissing<Beautify>();
if (beautifyActiveCam != null && beautifyInactiveCam != null)
{
beautifyInactiveCam.quality = beautifyActiveCam.quality;
beautifyInactiveCam.profile = beautifyActiveCam.profile;
}
else
{
Object.Destroy(inactiveCam.gameObject.GetComponent<Beautify>());
}
}
}

View file

@ -0,0 +1,45 @@
using ABI_RC.Core.Player;
using UnityEngine;
using UnityEngine.Events;
namespace NAK.Melons.DesktopXRSwitch.Patches;
public class XRModeSwitchTracker
{
public static event UnityAction<bool, Camera> OnPreXRModeSwitch;
public static event UnityAction<bool, Camera> OnPostXRModeSwitch;
public static event UnityAction<bool, Camera> OnFailXRModeSwitch;
public static void PreXRModeSwitch(bool isXR)
{
TryCatchHell.TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Invoking XRModeSwitchTracker.OnPreXRModeSwitch.");
Camera activeCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
XRModeSwitchTracker.OnPreXRModeSwitch?.Invoke(isXR, activeCamera);
},
"Error while invoking XRModeSwitchTracker.OnPreXRModeSwitch. Did someone do a fucky?");
}
public static void PostXRModeSwitch(bool isXR)
{
TryCatchHell.TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Invoking XRModeSwitchTracker.OnPostXRModeSwitch.");
Camera activeCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
XRModeSwitchTracker.OnPostXRModeSwitch?.Invoke(isXR, activeCamera);
},
"Error while invoking XRModeSwitchTracker.OnPostXRModeSwitch. Did someone do a fucky?");
}
public static void FailXRModeSwitch(bool isXR)
{
TryCatchHell.TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Invoking XRModeSwitchTracker.OnFailXRModeSwitch.");
Camera activeCamera = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
XRModeSwitchTracker.OnFailXRModeSwitch?.Invoke(isXR, activeCamera);
},
"Error while invoking OnFailXRModeSwitch.OnPreXRModeSwitch. Did someone do a fucky?");
}
}

View file

@ -0,0 +1,30 @@
using MelonLoader;
using NAK.Melons.DesktopXRSwitch.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.Melons.DesktopXRSwitch))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.Melons.DesktopXRSwitch))]
[assembly: MelonInfo(
typeof(NAK.Melons.DesktopXRSwitch.DesktopXRSwitch),
nameof(NAK.Melons.DesktopXRSwitch),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidOnSteam/DesktopXRSwitch"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: HarmonyDontPatchAll]
namespace NAK.Melons.DesktopXRSwitch.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "4.3.4";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,190 @@
using ABI_RC.Core;
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.Camera;
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.InputManagement.InputModules;
using HarmonyLib;
using UnityEngine;
namespace NAK.Melons.DesktopXRSwitch;
internal class TryCatchHell
{
internal static void TryCatchWrapper(Action action, string errorMsg, params object[] msgArgs)
{
try
{
action();
}
catch (Exception ex)
{
DesktopXRSwitch.Logger.Error(string.Format(errorMsg, msgArgs));
DesktopXRSwitch.Logger.Msg(ex.Message);
}
}
internal static void CloseCohtmlMenus()
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Closing ViewManager & CVR_MenuManager menus.");
ViewManager.Instance.UiStateToggle(false);
CVR_MenuManager.Instance.ToggleQuickMenu(false);
},
"Setting CheckVR hasVrDeviceLoaded failed.");
}
internal static void SetCheckVR(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg($"Setting CheckVR hasVrDeviceLoaded to {isXR}.");
CheckVR.Instance.hasVrDeviceLoaded = isXR;
},
"Setting CheckVR hasVrDeviceLoaded failed.");
}
internal static void SetMetaPort(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg($"Setting MetaPort isUsingVr to {isXR}.");
MetaPort.Instance.isUsingVr = isXR;
},
"Setting MetaPort isUsingVr failed.");
}
internal static void RepositionCohtmlHud(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Configuring new hud affinity for CohtmlHud.");
CohtmlHud.Instance.gameObject.transform.parent = isXR ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.desktopCamera.transform;
CVRTools.ConfigureHudAffinity();
CohtmlHud.Instance.gameObject.transform.localScale = new Vector3(1.2f, 1f, 1.2f);
},
"Error parenting CohtmlHud to active camera.");
}
internal static void UpdateHudOperations(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Switching HudOperations worldLoadingItem & worldLoadStatus.");
HudOperations.Instance.worldLoadingItem = isXR ? HudOperations.Instance.worldLoadingItemVr : HudOperations.Instance.worldLoadingItemDesktop;
HudOperations.Instance.worldLoadStatus = isXR ? HudOperations.Instance.worldLoadStatusVr : HudOperations.Instance.worldLoadStatusDesktop;
},
"Failed switching HudOperations objects.");
}
internal static void DisableMirrorCanvas()
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Forcing PortableCamera canvas mirroring off.");
//tell the game we are in mirror mode so itll disable it (if enabled)
PortableCamera.Instance.mode = MirroringMode.Mirror;
PortableCamera.Instance.ChangeMirroring();
},
"Failed to disable PortableCamera canvas mirroring.");
}
internal static void SwitchActiveCameraRigs(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Switching active PlayerSetup camera rigs. Updating Desktop camera FOV.");
PlayerSetup.Instance.desktopCameraRig.SetActive(!isXR);
PlayerSetup.Instance.vrCameraRig.SetActive(isXR);
CVR_DesktopCameraController.UpdateFov();
//uicamera has script that copies fov from desktop cam
//toggling the cameras on/off resets aspect ratio
//so when rigs switch, that is already handled
},
"Failed to switch active camera rigs or update Desktop camera FOV.");
}
internal static void PauseInputInteractions(bool toggle)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg($"Setting CVRInputManager inputEnabled & CVR_InteractableManager enableInteractions to {!toggle}");
CVRInputManager.Instance.inputEnabled = !toggle;
CVR_InteractableManager.enableInteractions = !toggle;
},
"Failed to toggle CVRInputManager inputEnabled & CVR_InteractableManager enableInteractions.");
}
internal static void SwitchCVRInputManager(bool isXR)
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Resetting CVRInputManager inputs.");
//just in case
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;
//turn off finger tracking input
CVRInputManager.Instance.individualFingerTracking = false;
//add input module if you started in desktop
if (CVRInputManager._moduleOpenXR == null)
{
CVRInputManager.Instance.AddInputModule(CVRInputManager._moduleOpenXR = new CVRInputModule_OpenXR());
}
//enable xr input or whatnot
CVRInputManager._moduleOpenXR.InputEnabled = isXR;
},
"Failed to reset CVRInputManager inputs.");
}
internal static void ReloadLocalAvatar()
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Attempting to reload current local avatar from GUID.");
AssetManagement.Instance.LoadLocalAvatar(MetaPort.Instance.currentAvatarGuid);
},
"Failed to reload local avatar.");
}
internal static void UpdateRichPresence()
{
TryCatchWrapper(() =>
{
if (MetaPort.Instance.settings.GetSettingsBool("ImplementationRichPresenceDiscordEnabled", true))
{
DesktopXRSwitch.Logger.Msg("Forcing Discord Rich Presence update.");
MetaPort.Instance.settings.SetSettingsBool("ImplementationRichPresenceDiscordEnabled", false);
MetaPort.Instance.settings.SetSettingsBool("ImplementationRichPresenceDiscordEnabled", true);
}
if (MetaPort.Instance.settings.GetSettingsBool("ImplementationRichPresenceSteamEnabled", true))
{
DesktopXRSwitch.Logger.Msg("Forcing Steam Rich Presence update.");
MetaPort.Instance.settings.SetSettingsBool("ImplementationRichPresenceSteamEnabled", false);
MetaPort.Instance.settings.SetSettingsBool("ImplementationRichPresenceSteamEnabled", true);
}
},
"Failed to update Discord & Steam Rich Presence.");
}
internal static void UpdateGestureReconizerCam()
{
TryCatchWrapper(() =>
{
DesktopXRSwitch.Logger.Msg("Updating CVRGestureRecognizer _camera to active camera.");
Traverse.Create(CVRGestureRecognizer.Instance).Field("_camera").SetValue(PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>());
},
"Failed to update CVRGestureRecognizer camera.");
}
}

View file

@ -0,0 +1,23 @@
{
"_id": 103,
"name": "DesktopVRSwitch",
"modversion": "4.3.1",
"gameversion": "2022r170",
"loaderversion": "0.5.7",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Allows you to switch between Desktop and VR with a keybind.\n**Press Control + F6 to switch.**\n\nWhile this mod is a nice convienence feature to have access to, not every chillout system or mod is built to support it. I cannot possibly cover every edge case or mitigate issues with every mod. **Use at your own discretion.**",
"searchtags": [
"desktop",
"vr",
"switch",
"hotswap"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRSwitch/releases/download/v4.3.1/DesktopVRSwitch.dll",
"sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRSwitch/",
"changelog": "- Backported rewrite from Experimental & patched up to handle OpenVR.\n- Reinitialize SteamVR input on switch incase user restarted SteamVR or changed controllers. Correct fixedDeltaTime when entering Desktop.\n- Many tweaks to ensure everything in-game functions as expected.",
"embedcolor": "3498db"
}