├── VREnhancements ├── AudioFix.cs ├── Properties │ └── AssemblyInfo.cs ├── MainPatcher.cs ├── CinematicsAnimations.cs ├── UIFader.cs ├── ParticleFX.cs ├── PDAFixes.cs ├── VehicleHUDManager.cs ├── VREnhancements.csproj ├── CameraFixes.cs ├── AdditionalVROptions.cs └── UIElementsFixes.cs ├── LICENSE ├── VREnhancements.sln ├── .gitattributes ├── README.md └── .gitignore /VREnhancements/AudioFix.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine; 3 | using FMODUnity; 4 | 5 | namespace VREnhancements 6 | { 7 | class AudioFix 8 | { 9 | [HarmonyPatch(typeof(SNCameraRoot), nameof(SNCameraRoot.Awake))] 10 | class Awake_Patch 11 | { 12 | static void Postfix(SNCameraRoot __instance) 13 | { 14 | if (SNCameraRoot.main.mainCam) 15 | { 16 | //remove the audio listeners from the PlayerCameras object that does not rotate with the VR headset 17 | Object.Destroy(__instance.gameObject.GetComponent()); 18 | Object.Destroy(__instance.gameObject.GetComponent()); 19 | //add new listener to the main camera that does rotate with the VR headset 20 | SNCameraRoot.main.mainCam.gameObject.AddComponent(); 21 | SNCameraRoot.main.mainCam.gameObject.AddComponent(); 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 IWhoI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /VREnhancements.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VREnhancements", "VREnhancements\VREnhancements.csproj", "{D4862B92-0958-4D5A-BED1-090E70B842A9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{79246417-607E-4543-8992-7373A5FFBE88}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D4862B92-0958-4D5A-BED1-090E70B842A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D4862B92-0958-4D5A-BED1-090E70B842A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D4862B92-0958-4D5A-BED1-090E70B842A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D4862B92-0958-4D5A-BED1-090E70B842A9}.Release|Any CPU.Build.0 = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ExtensibilityGlobals) = postSolution 25 | SolutionGuid = {8C56A968-5237-45F2-8629-DEDD11F80F39} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /VREnhancements/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("VREnhancements")] 9 | [assembly: AssemblyDescription("Modifications to improve the VR Experience in Subnautica")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Who")] 12 | [assembly: AssemblyProduct("VREnhancements")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d4862b92-0958-4d5a-bed1-090e70b842a9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("3.2.2.0")] 37 | -------------------------------------------------------------------------------- /VREnhancements/MainPatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.XR; 4 | using BepInEx; 5 | using HarmonyLib; 6 | using BepInEx.Configuration; 7 | 8 | namespace VREnhancements 9 | { 10 | [BepInPlugin("com.whotnt.subnautica.vrenhancements.mod", "VREnhancements", "3.2.1")] 11 | //This entire class was created by ChatGPT based on the previous code to only patch whenever a VR headset is active! 12 | public class MainPatcher : BaseUnityPlugin 13 | { 14 | public static ConfigFile VRConfig = new ConfigFile(Paths.ConfigPath + "\\VREnhancements.cfg", true); 15 | private bool patched = false; 16 | 17 | private void Awake() 18 | { 19 | // Subscribe to VR device loaded event 20 | XRDevice.deviceLoaded += OnVRDeviceLoaded; 21 | 22 | // Optional: If VR is already active at startup, patch immediately 23 | if (XRSettings.isDeviceActive) 24 | { 25 | OnVRDeviceLoaded(XRSettings.loadedDeviceName); 26 | } 27 | } 28 | 29 | private void OnVRDeviceLoaded(string loadedDeviceName) 30 | { 31 | if (patched) return; // Prevent double-patching 32 | 33 | try 34 | { 35 | Harmony harmony = new Harmony("com.whotnt.subnautica.vrenhancements.mod"); 36 | harmony.PatchAll(); 37 | patched = true; 38 | Console.WriteLine($"[VR Enhancements] Patched for device: {loadedDeviceName}"); 39 | } 40 | catch (Exception ex) 41 | { 42 | Debug.Log("Error with VR Enhancements patching: " + ex.Message); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /VREnhancements/CinematicsAnimations.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace VREnhancements 4 | { 5 | class CinematicsAnimations 6 | { 7 | [HarmonyPatch(typeof(GameOptions), nameof(GameOptions.GetVrAnimationMode))] 8 | class GetVRAnimationMode_Patch 9 | { 10 | static bool Prefix(ref bool __result) 11 | { 12 | //if VR animations are enabled in the menu then we need to return false to get animations to play. 13 | __result = !GameOptions.enableVrAnimations; 14 | return false;//dont execute the original method since we are only using the menu option to determine if animations play. 15 | } 16 | } 17 | 18 | [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.OnPilotModeBegin))] 19 | class OnPilotModeBegin_Patch 20 | { 21 | 22 | static bool Prefix(Vehicle __instance) 23 | { 24 | if (__instance.mainAnimator) 25 | { 26 | __instance.mainAnimator.SetBool("vr_active", GameOptions.GetVrAnimationMode()); 27 | } 28 | return true; 29 | } 30 | } 31 | 32 | [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.OnPilotModeEnd))] 33 | class OnPilotModeEnd_Patch 34 | { 35 | static bool Prefix(Vehicle __instance) 36 | { 37 | if (__instance.mainAnimator) 38 | { 39 | __instance.mainAnimator.SetBool("vr_active", GameOptions.GetVrAnimationMode()); 40 | } 41 | return true; 42 | } 43 | } 44 | 45 | [HarmonyPatch(typeof(uGUI_BuilderMenu), nameof(uGUI_BuilderMenu.Close))] 46 | class BuilderMenu_Close_Patch 47 | { 48 | //VRViewModelAngle was being locked in the Open method but not reset in the close method 49 | //This fixes the wrong orientation of the the player model during animations after using the builder tool. 50 | static void Postfix() 51 | { 52 | MainCameraControl.main.ResetLockedVRViewModelAngle(); 53 | } 54 | 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /VREnhancements/UIFader.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | 5 | namespace VREnhancements 6 | { 7 | //TODO: Rewrite this as a Dynamic HUD class since that is all it's really being used for. 8 | //Make it so that everything related to the Dynamic HUD happens here instead of multiple other classes as it is currently. 9 | class UIFader : MonoBehaviour 10 | { 11 | CanvasGroup cg; 12 | Coroutine fadeCR; 13 | bool fading = false; 14 | bool autoFadeOut = false; 15 | public float autoFadeDelay = 1; 16 | 17 | void Awake() 18 | { 19 | cg = this.gameObject.GetComponent(); 20 | if (!cg) 21 | cg = this.gameObject.AddComponent(); 22 | } 23 | 24 | void Update() 25 | { 26 | if (fading) 27 | return; 28 | //if the element is visible and autofade is true then auto fade. 29 | if(autoFadeOut && cg.alpha > 0) 30 | { 31 | Fade(0, 1, autoFadeDelay, false); 32 | } 33 | } 34 | 35 | public float GetAlpha() 36 | { 37 | return cg.alpha; 38 | } 39 | public void SetAutoFade(bool enabled) 40 | { 41 | autoFadeOut = enabled; 42 | if (!enabled) 43 | Fade(AdditionalVROptions.HUD_Alpha.Value, 0, 0, true); 44 | } 45 | public void Fade(float targetAlpha, float fadeSpeed = 1, float delaySeconds = 0, bool reset = false) 46 | { 47 | if (this.gameObject.activeInHierarchy) 48 | { 49 | //if currently fading and reset true, stop current fade and start new fade 50 | if (fadeCR != null && fading && reset) 51 | { 52 | StopCoroutine(fadeCR); 53 | fadeCR = StartCoroutine(FadeCG(targetAlpha, fadeSpeed, delaySeconds)); 54 | } 55 | else if (!fading) 56 | fadeCR = StartCoroutine(FadeCG(targetAlpha, fadeSpeed, delaySeconds)); 57 | } 58 | } 59 | IEnumerator FadeCG(float targetAlpha, float fadeSpeed, float seconds) 60 | { 61 | fading = true; 62 | if(seconds > 0) 63 | yield return new WaitForSeconds(seconds); 64 | float newAlpha = cg.alpha; 65 | if (fadeSpeed <= 0) 66 | cg.alpha = targetAlpha; 67 | else 68 | while (newAlpha != targetAlpha) 69 | { 70 | newAlpha = Mathf.MoveTowards(newAlpha, targetAlpha, fadeSpeed * Time.deltaTime); 71 | cg.alpha = newAlpha; 72 | yield return null; 73 | } 74 | fading = false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Subnautica VR Enhancements Mod 2 | This mod fixes the majority of issues with Subnautica in VR as shown here [https://www.youtube.com/watch?v=aTnEtO-YqMg](https://www.youtube.com/watch?v=aTnEtO-YqMg). You can download the mod [here](https://github.com/IWhoI/SubnauticaVREnhancements/releases) or [here](https://www.nexusmods.com/subnautica/mods/173?tab=description). The mod started as direct edits to the Assembly-CSharp.dll for the game to fix problems that were annoying me in VR and I never intended for it to be a mod so the code will not be as clean and structured as it could be. A lot of the code is from Googling and following patterns of other mods so I'm sure better programmers will have better ways to accomplish some of the things that the mod does. The code also contains commented out experimental stuff. 3 | 4 | ## Mod Features 5 | - Adds an option to enable VR animations under the General Tab in Options which allows the opening cinematic to play and re-enables the fire in the pod. Animations for climbing ladders, entering and exiting habitats and vehicles also work as well. 6 | - Fixes the badly positioned player model. The Seaglide will no longer block your entire field of view, all tools are fully visible. For those that had a problem with the PDA being too close, it has now been moved further back, tilted and scaled up to improve visibility. The initial open distance is also configurable. 7 | - Subtitles were not visible before and have been shifted up so they are now visible in VR and the height and scale is adjustable. 8 | - A slider has been added to adjust the walk speed in VR since the default VR walk speed was 60% of the non-VR walking speed. 9 | - The mouse cursor was originally invisible in VR and even when using the gaze based cursor, the alignment of the cursor was wrong. I made the cursor visible when using mouse and keyboard and not using the gaze based cursor option. Also fixed the cursor alignment issue on menus in the world like the scanner room and cyclops menus. Renaming beacons seem to work properly now as well as long as you keep the cursor on the text field, when using the mouse and keyboard. You can also use the mouse to drag and drop items on the toolbar and to rearrange the toolbar items. 10 | - Fixed an issue with incorrect sound direction when turning with your head instead of the game controller or mouse. 11 | - Makes the loading screen more comfortable to look at by removing the background image and displaying the Alterra logo and loading text in the middle of a black screen. 12 | - Scaled down the HUD for the cyclops and drone cameras to make the edges more visible. 13 | - Added an option in the in game menu to re-center the VR position so you don't have to blindly search for the F2 key on the keyboard while the headset is on. 14 | - The Sunbeam timer is now visible in VR. 15 | - You can now move your head independently of the PDA position. 16 | - When piloting vehicles, the HUD is now attached to the vehicle instead of your head. 17 | - Added options for customizing the HUD opacity, distance, scale and element separation. 18 | - Auto Re-centering VR is done in the main menu and after loading a game. 19 | - Performance optimization for the water sun shafts. 20 | - Resized and repositioned menus for comfort 21 | - Replaced the VR Cursor with a better one 22 | - Disabled the Reticle until needed 23 | - Fixed a problem where resource tracker blips would only render to the right eye in some cases 24 | 25 | If I have the time I will be adding more improvements in later versions. 26 | -------------------------------------------------------------------------------- /VREnhancements/ParticleFX.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Unity.Collections; 6 | 7 | namespace VREnhancements 8 | { 9 | class ParticleFX 10 | { 11 | [HarmonyPatch(typeof(VFXDestroyAfterSeconds), nameof(VFXDestroyAfterSeconds.OnEnable))] 12 | class VFXDestroyAfterSeconds_OnEnable_Patch 13 | { 14 | static void Postfix(VFXDestroyAfterSeconds __instance) 15 | { 16 | foreach (ParticleSystemRenderer particleSR in __instance.GetComponentsInChildren(true)) 17 | { 18 | particleSR.allowRoll = false; 19 | particleSR.alignment = ParticleSystemRenderSpace.Facing; 20 | } 21 | } 22 | } 23 | [HarmonyPatch(typeof(AmbientParticles), nameof(AmbientParticles.Init))] 24 | class AmbientParticles_Init_Patch 25 | { 26 | static void Postfix(AmbientParticles __instance) 27 | { 28 | foreach (ParticleSystemRenderer particleSR in __instance.GetComponentsInChildren(true)) 29 | { 30 | particleSR.allowRoll = false; 31 | particleSR.alignment = ParticleSystemRenderSpace.Facing; 32 | } 33 | 34 | } 35 | } 36 | 37 | [HarmonyPatch(typeof(VFXController), nameof(VFXController.SpawnFX))] 38 | class VFXController_Start_Patch 39 | { 40 | static void Postfix(VFXController __instance, int i) 41 | { 42 | if (__instance.emitters[i].fxPS != null) 43 | { 44 | foreach (ParticleSystemRenderer particleSR in __instance.emitters[i].fxPS.gameObject.GetComponentsInChildren(true)) 45 | { 46 | particleSR.allowRoll = false; 47 | particleSR.alignment = ParticleSystemRenderSpace.Facing; 48 | } 49 | } 50 | } 51 | } 52 | 53 | [HarmonyPatch(typeof(PlayerBreathBubbles), nameof(PlayerBreathBubbles.MakeBubbles))] 54 | class PlayerBreathBubbles_MakeBubbles_Patch 55 | { 56 | static void Postfix(PlayerBreathBubbles __instance, GameObject ___bubbles) 57 | { 58 | if (___bubbles) 59 | { 60 | foreach (ParticleSystemRenderer particleSR in ___bubbles.GetComponentsInChildren(true)) 61 | { 62 | particleSR.allowRoll = false; 63 | particleSR.alignment = ParticleSystemRenderSpace.Facing; 64 | } 65 | } 66 | } 67 | } 68 | 69 | [HarmonyPatch(typeof(ResourceTracker), nameof(ResourceTracker.Start))] 70 | class ResourceTracker_Start_Patch 71 | { 72 | static void Postfix(ResourceTracker __instance, TechType ___techType) 73 | { 74 | if (___techType == TechType.HeatArea) 75 | { 76 | foreach (ParticleSystemRenderer particleSR in __instance.gameObject.GetComponentsInChildren(true)) 77 | { 78 | particleSR.allowRoll = false; 79 | particleSR.alignment = ParticleSystemRenderSpace.Facing; 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /VREnhancements/PDAFixes.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine; 3 | using RootMotion.FinalIK; 4 | using System; 5 | using static VFXParticlesPool; 6 | 7 | namespace VREnhancements 8 | { 9 | //TODO: Consider squashing the PDA model in the y axis and placing the PDA screen above the PDA to make it much larger 10 | class PDAFixes 11 | { 12 | static readonly float pdaScale = 1.45f; 13 | static readonly float screenScale = 0.0003f; 14 | static readonly float pdaXOffset = -0.35f; 15 | public static float pdaDistance=0.4f; 16 | static readonly float pdaXRot = 220f; 17 | static readonly float pdaYRot = 30f; 18 | static readonly float pdaZRot = 75f; 19 | static GameObject leftHandTarget; 20 | static FullBodyBipedIK myIK; 21 | 22 | public static void SetPDADistance(float distance) 23 | { 24 | pdaDistance = distance; 25 | } 26 | /* 27 | * Do this to make sure the PDA opens in front the player if allowing pitch control with input. 28 | [HarmonyPatch(typeof(PDA), nameof(PDA.Open))] 29 | class Prefix_PDA_Open_Patch 30 | { 31 | static bool Prefix(PDA __instance) 32 | { 33 | MainCameraControl.main.cameraOffsetTransform.localRotation = Quaternion.identity; 34 | MainCameraControl.main.cameraUPTransform.localRotation = Quaternion.identity; 35 | MainCameraControl.main.transform.localRotation = Quaternion.Euler(0, MainCameraControl.main.transform.localRotation.eulerAngles.y, MainCameraControl.main.transform.localRotation.eulerAngles.z); 36 | return true; 37 | } 38 | }*/ 39 | 40 | [HarmonyPatch(typeof(PDA), nameof(PDA.Open))] 41 | class PDA_Open_Patch 42 | { 43 | static void Postfix(PDA __instance, bool __result) 44 | { 45 | //if the PDA was opened 46 | if (__result) 47 | { 48 | //set the PDA and PDA Screen scale 49 | uGUI_CanvasScaler contentScreen = __instance.ui.GetComponent(); 50 | __instance.transform.localScale = new Vector3(pdaScale, pdaScale, 1f); 51 | contentScreen.transform.localScale = Vector3.one * screenScale; 52 | contentScreen.SetAnchor(__instance.screenAnchor); 53 | if (!leftHandTarget) 54 | leftHandTarget = new GameObject(); 55 | leftHandTarget.transform.parent = Player.main.camRoot.transform; 56 | //TODO: This is probably needlessly complicated and could be done in a simpler way 57 | Transform armsTransform = Player.main.armsController.transform; 58 | Transform leftHTParentTf = leftHandTarget.transform.parent.transform; 59 | if (Player.main.motorMode != Player.MotorMode.Vehicle) 60 | leftHandTarget.transform.localPosition = leftHTParentTf.InverseTransformPoint(Player.main.playerController.forwardReference.position + armsTransform.right * pdaXOffset + Vector3.up * -0.15f + new Vector3(armsTransform.forward.x, 0f, armsTransform.forward.z).normalized * pdaDistance); 61 | else 62 | leftHandTarget.transform.localPosition = leftHTParentTf.InverseTransformPoint(leftHTParentTf.position + leftHTParentTf.right * pdaXOffset + leftHTParentTf.forward * pdaDistance + leftHTParentTf.up * -0.15f); 63 | leftHandTarget.transform.rotation = armsTransform.rotation * Quaternion.Euler(pdaXRot, pdaYRot, pdaZRot); 64 | } 65 | } 66 | } 67 | 68 | //this stops the model from snapping to 0 y rotation and turning back to head rotation when closing the PDA 69 | [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.ResetLockedVRViewModelAngle))] 70 | class MainCameraControl_ResetVRViewModelAngle_Patch 71 | { 72 | static bool Prefix() 73 | { 74 | //do the reset in PDA.Deactivated which runs after the closing animation to prevent the player model rotation from snapping to y=0 while closing. 75 | if(Player.main.GetPDA().isInUse) 76 | return false; 77 | else 78 | return true; 79 | } 80 | } 81 | 82 | [HarmonyPatch(typeof(PDA), nameof(PDA.Deactivated))] 83 | class PDA_Deactivated_Patch 84 | { 85 | static void Postfix() 86 | { 87 | //this was being done before the closing animation in PDA.Close and caused the player model rotation to snap to y=0 while closing 88 | MainCameraControl.main.ResetLockedVRViewModelAngle(); 89 | } 90 | } 91 | 92 | [HarmonyPatch(typeof(PDA), nameof(PDA.Close))] 93 | class PDA_Close_Patch 94 | { 95 | static void Postfix() 96 | { 97 | if (leftHandTarget) 98 | { 99 | GameObject.Destroy(leftHandTarget); 100 | } 101 | } 102 | } 103 | 104 | [HarmonyPatch(typeof(PDA), nameof(PDA.ManagedUpdate))] 105 | class PDA_Update_Patch 106 | { 107 | static bool Prefix() 108 | { 109 | if (leftHandTarget) 110 | { 111 | myIK.solver.leftHandEffector.target = leftHandTarget.transform; 112 | } 113 | return true; 114 | } 115 | } 116 | 117 | [HarmonyPatch(typeof(ArmsController), nameof(ArmsController.Reconfigure))] 118 | class ArmsCon_Reconfigure_Patch 119 | { 120 | static void Postfix(ArmsController __instance, ref bool ___reconfigureWorldTarget) 121 | { 122 | //This fixes a bug in the original game code where reconfigureWorldTarget was set to true in SetWorldIKTarget but never reset to false after running Reconfigure 123 | //This caused the PDA ik target to work until piloting a vehicle which called SetWorldIKTarget and continuously called Reconfigure every frame after. 124 | ___reconfigureWorldTarget = false; 125 | } 126 | } 127 | 128 | [HarmonyPatch(typeof(ArmsController), nameof(ArmsController.Start))] 129 | class ArmsCon_Start_Patch 130 | { 131 | static void Postfix(ArmsController __instance, FullBodyBipedIK ___ik) 132 | { 133 | //get the private ik field from ArmsController to use it in PDA Update method 134 | myIK = ___ik; 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /VREnhancements/VehicleHUDManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | 4 | namespace VREnhancements 5 | { 6 | class VehicleHUDManager : MonoBehaviour 7 | { 8 | public static GameObject vehicleCanvas; 9 | uGUI_CanvasScaler canvasScaler; 10 | Transform barsPanel; 11 | Transform quickSlots; 12 | public static Transform seamothHUD; 13 | public static Transform exosuitHUD; 14 | Transform compass; 15 | Transform HUDContent; 16 | Vector3 seamothHUDPos = new Vector3(450, -280, 1000); 17 | Vector3 seamothCompassPos = new Vector3(0, 450, 950); 18 | Vector3 seamothQuickSlotsPos = new Vector3(0, -450, 1100); 19 | Vector3 seamothBarsPanelPos = new Vector3(-600, -420, 900); 20 | Vector3 exosuitHUDPos = new Vector3(700, -200, 600); 21 | Vector3 exosuitCompassPos = new Vector3(0, 400, 700); 22 | Vector3 exosuitQuickSlotsPos = new Vector3(0, -700, 700); 23 | Vector3 exosuitBarsPanelPos = new Vector3(-700, -300, 500); 24 | Vector3 originalCompassPos; 25 | Vector3 originalQuickSlotsPos; 26 | Vector3 originalBarsPanelPos; 27 | 28 | void Awake() 29 | { 30 | //create a new worldspace canvas for vehicles 31 | vehicleCanvas = new GameObject("VRVehicleCanvas"); 32 | DontDestroyOnLoad(vehicleCanvas); 33 | Canvas canvas = vehicleCanvas.AddComponent(); 34 | canvas.renderMode = RenderMode.WorldSpace; 35 | canvas.sortingLayerName = "HUD"; 36 | vehicleCanvas.AddComponent(); 37 | canvasScaler = vehicleCanvas.AddComponent(); 38 | //the canvasScaler moves elements in front of the UI camera based on the mode. 39 | //In inversed mode it moves the elements so that they look like they are attached to the canvasScaler anchor from the main camera's perspective. 40 | canvasScaler.vrMode = uGUI_CanvasScaler.Mode.Inversed; 41 | //TODO:Not that important but figure out how to get raycasts working on the new canvas so drag and drop will work on the quickslots while PDA is open in the vehicle 42 | //vehicleCanvas.AddComponent(); 43 | vehicleCanvas.layer = LayerMask.NameToLayer("UI"); 44 | vehicleCanvas.transform.localScale = Vector3.one * 0.001f; 45 | HUDContent = GameObject.Find("HUD/Content").transform; 46 | seamothHUD = HUDContent.Find("Seamoth").transform; 47 | exosuitHUD = HUDContent.Find("Exosuit").transform; 48 | quickSlots = HUDContent.Find("QuickSlots").transform; 49 | compass = HUDContent.Find("DepthCompass").transform; 50 | barsPanel = HUDContent.Find("BarsPanel").transform; 51 | seamothHUD.SetParent(vehicleCanvas.transform, false);//move the vehicle specific HUD elements to the new vehicle Canvas 52 | seamothHUD.localPosition = seamothHUDPos; 53 | exosuitHUD.SetParent(vehicleCanvas.transform, false); 54 | exosuitHUD.localPosition = exosuitHUDPos; 55 | vehicleCanvas.SetActive(false); 56 | } 57 | 58 | void Update() 59 | { 60 | Player player = Player.main; 61 | if (player) 62 | { 63 | if (player.inSeamoth || player.inExosuit) 64 | { 65 | if(!vehicleCanvas.activeInHierarchy) 66 | { 67 | //save the original HUD element positions before moving them 68 | originalCompassPos = compass.localPosition; 69 | originalQuickSlotsPos = quickSlots.localPosition; 70 | originalBarsPanelPos = barsPanel.localPosition; 71 | //move the elements 72 | compass.SetParent(vehicleCanvas.transform, false); 73 | quickSlots.SetParent(vehicleCanvas.transform, false); 74 | barsPanel.SetParent(vehicleCanvas.transform, false); 75 | //set custom element positions based on vehicle 76 | if (player.inSeamoth) 77 | { 78 | compass.localPosition = seamothCompassPos; 79 | quickSlots.localPosition = seamothQuickSlotsPos; 80 | barsPanel.localPosition = seamothBarsPanelPos; 81 | 82 | } 83 | else 84 | { 85 | compass.localPosition = exosuitCompassPos; 86 | quickSlots.localPosition = exosuitQuickSlotsPos; 87 | barsPanel.localPosition = exosuitBarsPanelPos; 88 | } 89 | //TODO:Make the anchor be the vehicle instead so the hud will not move with the head if I get the upper body ik working while piloting 90 | canvasScaler.SetAnchor(SNCameraRoot.main.mainCam.transform.parent); 91 | vehicleCanvas.SetActive(true); 92 | } 93 | //using vehicleCanvas.transform.up to compensate for the rotation done by the canvas scaler in inversed mode. 94 | if (player.inSeamoth) 95 | seamothHUD.rotation = Quaternion.LookRotation(seamothHUD.position,vehicleCanvas.transform.up); 96 | else 97 | exosuitHUD.rotation = Quaternion.LookRotation(exosuitHUD.position, vehicleCanvas.transform.up); 98 | quickSlots.rotation = Quaternion.LookRotation(quickSlots.position, vehicleCanvas.transform.up); 99 | compass.rotation = Quaternion.LookRotation(compass.position, vehicleCanvas.transform.up); 100 | barsPanel.rotation = Quaternion.LookRotation(barsPanel.position, vehicleCanvas.transform.up); 101 | } 102 | else if(vehicleCanvas.activeInHierarchy) 103 | { 104 | //if not in seamoth or exosuit but vehicleCanvas is active then move elements back to the normal HUD and disable vehicleCanvas 105 | compass.SetParent(HUDContent, false); 106 | compass.localPosition = originalCompassPos; 107 | quickSlots.SetParent(HUDContent, false); 108 | quickSlots.localPosition = originalQuickSlotsPos; 109 | barsPanel.SetParent(HUDContent, false); 110 | barsPanel.localPosition = originalBarsPanelPos; 111 | vehicleCanvas.SetActive(false); 112 | //reset the rotation to look at the UI camera at (0,0,0); 113 | UIElementsFixes.UpdateHUDLookAt(); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /VREnhancements/VREnhancements.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D4862B92-0958-4D5A-BED1-090E70B842A9} 8 | Library 9 | Properties 10 | VREnhancements 11 | VREnhancements 12 | v4.7.2 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | C:\Games\Subnautica\Bepinex\core\0Harmony20.dll 39 | 40 | 41 | C:\Games\Steam\steamapps\common\Subnautica\Subnautica_Data\Managed\publicized_assemblies\Assembly-CSharp-firstpass_publicized.dll 42 | 43 | 44 | False 45 | C:\Games\Steam\steamapps\common\Subnautica\Subnautica_Data\Managed\publicized_assemblies\Assembly-CSharp_publicized.dll 46 | 47 | 48 | C:\Games\Subnautica\BepInEx\core\BepInEx.dll 49 | 50 | 51 | False 52 | C:\Games\Subnautica\Subnautica_Data\Managed\FMODUnity.dll 53 | 54 | 55 | False 56 | C:\Games\Subnautica\Bepinex\plugins\QModManager\QModInstaller.dll 57 | 58 | 59 | False 60 | C:\Games\Steam\steamapps\common\Subnautica\Subnautica_Data\Managed\publicized_assemblies\Steam_Assembly-CSharp-firstpass_publicized.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | False 70 | C:\Games\Subnautica\Subnautica_Data\Managed\Unity.TextMeshPro.dll 71 | 72 | 73 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.dll 74 | 75 | 76 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.AnimationModule.dll 77 | 78 | 79 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.AudioModule.dll 80 | 81 | 82 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.CoreModule.dll 83 | 84 | 85 | False 86 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.InputLegacyModule.dll 87 | 88 | 89 | False 90 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.ParticleSystemModule.dll 91 | 92 | 93 | False 94 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.PhysicsModule.dll 95 | 96 | 97 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.TextRenderingModule.dll 98 | 99 | 100 | False 101 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.UI.dll 102 | 103 | 104 | False 105 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.UIModule.dll 106 | 107 | 108 | C:\Games\Subnautica\Subnautica_Data\Managed\UnityEngine.VRModule.dll 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | xcopy /y /d "$(TargetPath)" "C:\Games\Steam\steamapps\common\Subnautica\BepInEx\plugins\VR Enhancements" 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /VREnhancements/CameraFixes.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using System.Reflection; 3 | using UnityEngine; 4 | using UnityEngine.XR; 5 | 6 | namespace VREnhancements 7 | { 8 | class CameraFixes 9 | { 10 | static Transform forwardRefTransform; 11 | static Transform headRig; 12 | [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnUpdate))] 13 | class MCC_Update_Patch 14 | { 15 | static float yOffset; 16 | static void Postfix(MainCameraControl __instance) 17 | { 18 | if (!__instance.cinematicMode && Player.main.motorMode != Player.MotorMode.Vehicle) 19 | { 20 | //offset the body/seaglide a little more to improve visibility while piloting the seaglide 21 | if (Player.main.motorMode == Player.MotorMode.Seaglide) 22 | yOffset = -0.18f; 23 | else 24 | yOffset = -0.08f; 25 | //move the body so that the head bone always tracks the headset/camera position 26 | __instance.viewModel.transform.localPosition = __instance.viewModel.transform.parent.worldToLocalMatrix.MultiplyPoint(forwardRefTransform.position - (headRig.position - __instance.viewModel.transform.position) + forwardRefTransform.up * yOffset + forwardRefTransform.forward * -0.02f); 27 | } 28 | } 29 | 30 | } 31 | [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.Awake))] 32 | class MCC_Awake_Patch 33 | { 34 | static void Postfix(MainCameraControl __instance) 35 | { 36 | forwardRefTransform = MainCamera.camera.transform; 37 | headRig = __instance.viewModel.transform.Find("player_view/export_skeleton/head_rig"); 38 | } 39 | 40 | } 41 | /*TODO: Check how difficult it is to fix the Seaglide and PDA problems with this attempt to decouple movement from head direction 42 | * [HarmonyPatch(MethodType.Getter)] 43 | [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.forwardReference))] 44 | class PlayerCon_fwdRef_Patch 45 | { 46 | static void Postfix(PlayerController __instance, ref Transform __result) 47 | { 48 | Transform temp = new GameObject().transform; 49 | temp.rotation = Quaternion.Euler(__result.rotation.eulerAngles.x, MainCameraControl.main.transform.rotation.eulerAngles.y, 0); 50 | __result = temp; 51 | } 52 | 53 | }*/ 54 | 55 | [HarmonyPatch(typeof(MainGameController), nameof(MainGameController.StartGame))] 56 | class MGC_StartGame_Patch 57 | { 58 | //fix the camera position to match head position 59 | static void Postfix() 60 | { 61 | MainCameraControl.main.cameraOffsetTransform.localPosition = new Vector3(0f, 0f, 0.15f); 62 | } 63 | } 64 | 65 | [HarmonyPatch(typeof(MainGameController), nameof(MainGameController.ResetOrientation))] 66 | class MGC_ResetOrientation_Patch 67 | { 68 | //I'm not sure if this is necessary after doing it in StartGame above but there may be a case where 69 | //it gets reset during gameplay so making sure it get fixed when VRUtil.Recenter is called. 70 | //fix the camera position to match head position 71 | static void Postfix() 72 | { 73 | MainCameraControl.main.cameraOffsetTransform.localPosition = new Vector3(0f, 0f, 0.15f); 74 | } 75 | } 76 | 77 | [HarmonyPatch(typeof(PlayerCinematicController), nameof(PlayerCinematicController.SkipCinematic))] 78 | class SkipCinematic_Patch 79 | { 80 | //replace skipcinematic method to disable VR recenter after a cinematic. Only the player should initiate a recenter. 81 | static bool Prefix(PlayerCinematicController __instance, Player player, ref Player ___player) 82 | { 83 | ___player = player; 84 | if (player) 85 | { 86 | Transform playerTransform = player.GetComponent(); 87 | Transform MCCTransform = MainCameraControl.main.GetComponent(); 88 | if (Traverse.Create(__instance).Method("UseEndTransform").GetValue())//execute private bool method 89 | { 90 | player.playerController.SetEnabled(false); 91 | //the following is what was removed from the original method 92 | /*if (XRSettings.enabled) 93 | { 94 | MainCameraControl.main.ResetCamera(); 95 | VRUtil.Recenter(); 96 | }*/ 97 | playerTransform.position = __instance.endTransform.position; 98 | playerTransform.rotation = __instance.endTransform.rotation; 99 | MCCTransform.rotation = playerTransform.rotation; 100 | } 101 | player.playerController.SetEnabled(true); 102 | player.cinematicModeActive = false; 103 | } 104 | if (__instance.informGameObject != null) 105 | { 106 | __instance.informGameObject.SendMessage("OnPlayerCinematicModeEnd", __instance, SendMessageOptions.DontRequireReceiver); 107 | } 108 | return false;//don't execute original SkipCinematic method 109 | } 110 | } 111 | 112 | [HarmonyPatch(typeof(CyclopsExternalCams), nameof(CyclopsExternalCams.SetActive))] 113 | class EnterCameraView_Patch 114 | { 115 | //removed the VRUtil.Recenter call from the original method when activating the camera view 116 | static bool Prefix(CyclopsExternalCams __instance, bool value, ref bool ___active) 117 | { 118 | if (___active == value) return false; 119 | if (value) 120 | { 121 | ___active = true;//only set active inside here since setting it outside will cause issues when running the original method after returning true when value is false. 122 | InputHandlerStack.main.Push(__instance); 123 | MainCameraControl.main.enabled = false; 124 | Player.main.SetHeadVisible(true); 125 | __instance.cameraLight.enabled = true; 126 | Traverse.Create(__instance).Method("ChangeCamera", 0).GetValue(); 127 | if (__instance.lightingPanel) 128 | { 129 | __instance.lightingPanel.TempTurnOffFloodlights(); 130 | } 131 | } 132 | return true; 133 | } 134 | } 135 | 136 | [HarmonyPatch(typeof(CyclopsCameraInput), nameof(CyclopsCameraInput.HandleInput))] 137 | class CyclopsCameraInput_HandleInput_Patch 138 | { 139 | 140 | static void Postfix(CyclopsCameraInput __instance, Light ___cameraLight) 141 | { 142 | if(___cameraLight) 143 | ___cameraLight.transform.rotation = SNCameraRoot.main.mainCam.transform.rotation; 144 | } 145 | } 146 | 147 | [HarmonyPatch(typeof(ArmsController), nameof(ArmsController.Start))] 148 | class ArmsController_Start__Patch 149 | { 150 | 151 | static void Postfix(ArmsController __instance) 152 | { 153 | //this is a quick fix to disable translucent player body but there may be a better way to fix this since the body isn't translucent when the PDA is open 154 | //TODO: Figure out a better way to prevent the translucent body 155 | foreach (SkinnedMeshRenderer renderer in __instance.GetComponentsInChildren(true)) 156 | { 157 | renderer.fadeAmount = 0; 158 | } 159 | } 160 | } 161 | [HarmonyPatch(typeof(WaterSunShaftsOnCamera), nameof(WaterSunShaftsOnCamera.Awake))] 162 | class SunShafts_Awake_Patch 163 | { 164 | static void Postfix(WaterSunShaftsOnCamera __instance) 165 | { 166 | __instance.reduction = 6;//default is 2. console is 4. This improves performance with little noticable difference to the sun shafts. 167 | } 168 | 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /VREnhancements/AdditionalVROptions.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEngine.UI; 5 | using UnityEngine; 6 | using TMPro; 7 | using BepInEx; 8 | using BepInEx.Configuration; 9 | using static OVRHaptics; 10 | 11 | namespace VREnhancements 12 | { 13 | class AdditionalVROptions 14 | { 15 | static int generalTabIndex = -1; 16 | //using BepInEx config system to save config. I'm not sure if there is a better way to do this but it works. 17 | public static ConfigEntry dynamicHUD; 18 | public static ConfigEntry enableVRAnimations; 19 | //public static ConfigEntry disableInputPitch; 20 | public static ConfigEntry walkingSpeed; 21 | public static ConfigEntry PDA_Distance; 22 | public static ConfigEntry HUD_Alpha; 23 | public static ConfigEntry HUD_Distance; 24 | public static ConfigEntry HUD_Scale; 25 | public static ConfigEntry HUD_Separation; 26 | 27 | //this will load/create the configuration values in the VREnhancements.cfg file in the BepInEx config folder. 28 | public static void LoadVRConfig() 29 | { 30 | enableVRAnimations = MainPatcher.VRConfig.Bind("General", "enableVRAnimations", true, "Wether or not animations for climbing ladders etc are enabled"); 31 | GameOptions.enableVrAnimations = enableVRAnimations.Value; 32 | walkingSpeed = MainPatcher.VRConfig.Bind("General", "walkingSpeed", 1.0f, "Default VR walking speed is 60%(0.6) of the base game walk speed."); 33 | VROptions.groundMoveScale = walkingSpeed.Value; 34 | /* disabled this since I didn't want to take the time to figure out how to fix the problem where the input pitch direction changes if 35 | * recentering is done while your head is rotated left or right. 36 | disableInputPitch = MainPatcher.VRConfig.Bind("Input", "disableInputPitch", true, "Wether or not looking up and down is possible with an input device"); 37 | VROptions.disableInputPitch = disableInputPitch.Value; 38 | */ 39 | dynamicHUD = MainPatcher.VRConfig.Bind("UI", "dynamicHUD", true, "Wether or not the dynamic HUD is enabled"); 40 | PDA_Distance = MainPatcher.VRConfig.Bind("UI", "PDA_Distance", 0.4f, "The distance that the PDA is held"); 41 | HUD_Alpha = MainPatcher.VRConfig.Bind("UI", "HUD_Alpha", 1.0f, "Opacity of the HUD. 1 is fully opaque"); 42 | HUD_Distance = MainPatcher.VRConfig.Bind("UI", "HUD_Distance", 1.5f, "Distance of the HUD in meters"); 43 | HUD_Scale = MainPatcher.VRConfig.Bind("UI", "HUD_Scale", 1.0f, "Size of the HUD"); 44 | HUD_Separation = MainPatcher.VRConfig.Bind("UI", "HUD_Separation", 0, "Preset for the spacing between elements of the HUD. 0 - Default, 1-Small, 2-Medium, 3-Large"); 45 | } 46 | 47 | [HarmonyPatch(typeof(uGUI_TabbedControlsPanel), nameof(uGUI_TabbedControlsPanel.AddTab))] 48 | class AddTab_Patch 49 | { 50 | static void Postfix(int __result, string label) 51 | { 52 | //get the tabIndex of the general tab to be able to use it in AddGeneralTab_Postfix 53 | if (label.Equals("General")) 54 | generalTabIndex = __result; 55 | } 56 | } 57 | 58 | [HarmonyPatch(typeof(uGUI_OptionsPanel), nameof(uGUI_OptionsPanel.AddTabs))] 59 | class GeneralTab_VROptionsPatch 60 | { 61 | static void Postfix(uGUI_OptionsPanel __instance) 62 | { 63 | __instance.AddHeading(generalTabIndex, "General VR Options"); 64 | __instance.AddToggleOption(generalTabIndex, "Enable VR Animations", GameOptions.enableVrAnimations, delegate (bool v) 65 | { 66 | enableVRAnimations.Value = GameOptions.enableVrAnimations = v; 67 | //playerAnimator vr_active is normally set in the Start function of Player so we need to update it if option changed during gameplay 68 | if (Player.main) 69 | Player.main.playerAnimator.SetBool("vr_active", !v); 70 | }); 71 | __instance.AddSliderOption(generalTabIndex, "Walk Speed(VR Default: 60%)", VROptions.groundMoveScale * 100, 50, 100, (float)walkingSpeed.DefaultValue * 100, 1f, delegate (float v) 72 | { 73 | walkingSpeed.Value = VROptions.groundMoveScale = v / 100f; 74 | }, SliderLabelMode.Float, "F0"); 75 | /* see note in LoadConfig for why this is disabled 76 | __instance.AddToggleOption(generalTabIndex, "Disable Vertical Input", disableInputPitch.Value, delegate (bool v) 77 | { 78 | disableInputPitch.Value = VROptions.disableInputPitch = v; 79 | VRUtil.Recenter(); 80 | });*/ 81 | __instance.AddHeading(generalTabIndex, "VR User Interface Options"); 82 | __instance.AddSliderOption(generalTabIndex, "PDA Distance", PDA_Distance.Value * 100f, 25, 40, (float)PDA_Distance.DefaultValue * 100, 1f, delegate (float v) 83 | { 84 | PDA_Distance.Value = v / 100f; 85 | PDAFixes.SetPDADistance(PDA_Distance.Value); 86 | }, SliderLabelMode.Float, "F0"); 87 | __instance.AddToggleOption(generalTabIndex, "Dynamic HUD", dynamicHUD.Value, delegate (bool v) 88 | { 89 | dynamicHUD.Value = v; 90 | UIElementsFixes.SetDynamicHUD(v); 91 | }); 92 | __instance.AddSliderOption(generalTabIndex, "HUD Opacity %", HUD_Alpha.Value * 100f, 40, 100, (float)HUD_Alpha.DefaultValue*100, 1f, delegate (float v) 93 | { 94 | HUD_Alpha.Value = v / 100f; 95 | UIElementsFixes.UpdateHUDOpacity(HUD_Alpha.Value); 96 | }, SliderLabelMode.Float, "F0"); 97 | __instance.AddSliderOption(generalTabIndex, "HUD Distance (cm)", HUD_Distance.Value * 100f, 75, 200f, (float)HUD_Distance.DefaultValue * 100, 1f, delegate (float v) 98 | { 99 | HUD_Distance.Value = v / 100f; 100 | UIElementsFixes.UpdateHUDDistance(HUD_Distance.Value); 101 | }, SliderLabelMode.Float, "F0"); 102 | __instance.AddSliderOption(generalTabIndex, "HUD Scale %", HUD_Scale.Value * 100f, 50, 200, (float)HUD_Scale.DefaultValue * 100, 1f, delegate (float v) 103 | { 104 | HUD_Scale.Value = v / 100f; 105 | UIElementsFixes.UpdateHUDScale(HUD_Scale.Value); 106 | }, SliderLabelMode.Float, "F0"); 107 | __instance.AddChoiceOption(generalTabIndex, "HUD Separation", new string[] { "Default", "Small", "Medium", "Large" }, HUD_Separation.Value, delegate (int separation) 108 | { 109 | HUD_Separation.Value = separation; 110 | UIElementsFixes.UpdateHUDSeparation(separation); 111 | }); 112 | } 113 | } 114 | //Adds Recenter VR button to the in game menu. 115 | [HarmonyPatch(typeof(IngameMenu), nameof(IngameMenu.Awake))] 116 | class IGM_Awake_Patch 117 | { 118 | private static Button recenterVRButton; 119 | //code copied from the quit to desktop mod and modified 120 | static void Postfix(IngameMenu __instance) 121 | { 122 | if (__instance && recenterVRButton == null) 123 | { 124 | //Clone the quitToMainMenuButton and update it 125 | Button menuButton = __instance.quitToMainMenuButton.transform.parent.GetChild(0).gameObject.GetComponent