diff --git a/.github/workflows/build-debug.yaml b/.github/workflows/build-debug.yaml index 8137cbc4..a3e79048 100644 --- a/.github/workflows/build-debug.yaml +++ b/.github/workflows/build-debug.yaml @@ -8,13 +8,16 @@ on: jobs: build: - runs-on: self-hosted + runs-on: ubuntu-latest env: DOTNET_INSTALL_DIR: ~/.dotnet steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 - name: Set up variables id: vars @@ -35,14 +38,26 @@ jobs: - name: Build app run: dotnet build + - name: Configure GPG Key + run: | + echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + - name: Prepare and bundle package run: | # Set up template - unzip ~/lcvr/package-template.zip -d ./package + mkdir package + git --work-tree=./package checkout origin/thunderstore ./ - # Copy and sign debug binary + # Copy and sign debug binaries cp bin/Debug/netstandard2.1/LCVR.dll ./package/BepInEx/plugins/LCVR/ + cp bin/Debug/netstandard2.1/LCVR.Preload.dll ./package/BepInEx/patchers/LCVR/ gpg --output ./package/BepInEx/plugins/LCVR/LCVR.dll.sig --detach-sig ./package/BepInEx/plugins/LCVR/LCVR.dll + gpg --output ./package/BepInEx/patchers/LCVR/LCVR.Preload.dll.sig --detach-sig ./package/BepInEx/patchers/LCVR/LCVR.Preload.dll + + # Move assets + mv ./package/lethalcompanyvr ./package/BepInEx/plugins/LCVR/lethalcompanyvr # Copy docs and license cp CHANGELOG.md ./package/CHANGELOG.md diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index 15e24b59..443e87ee 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -8,13 +8,16 @@ on: jobs: build: - runs-on: self-hosted + runs-on: ubuntu-latest env: DOTNET_INSTALL_DIR: ~/.dotnet steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 - name: Set up variables id: vars @@ -41,14 +44,26 @@ jobs: - name: Build app run: dotnet build -c Release + - name: Configure GPG Key + run: | + echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + - name: Prepare and bundle package run: | # Set up template - unzip ~/lcvr/package-template.zip -d ./package + mkdir package + git --work-tree=./package checkout origin/thunderstore ./ - # Copy and sign release binary + # Copy and sign release binaries cp bin/Release/netstandard2.1/LCVR.dll ./package/BepInEx/plugins/LCVR/ + cp bin/Release/netstandard2.1/LCVR.Preload.dll ./package/BepInEx/patchers/LCVR/ gpg --output ./package/BepInEx/plugins/LCVR/LCVR.dll.sig --detach-sig ./package/BepInEx/plugins/LCVR/LCVR.dll + gpg --output ./package/BepInEx/patchers/LCVR/LCVR.Preload.dll.sig --detach-sig ./package/BepInEx/patchers/LCVR/LCVR.Preload.dll + + # Move assets + mv ./package/lethalcompanyvr ./package/BepInEx/plugins/LCVR/lethalcompanyvr # Copy docs and license cp CHANGELOG.md ./package/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cf1e6e..8373e857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +# 1.3.2 + +**Additions**: + +- Added support for V64 features +- Added the XR Occlusion Mesh as a performance option. Will cause artifacts on low framerates. +- Added XR Mirror View patches and options that allow moving the monitor view around a bit. Invalid values will cause the monitor view to break. *(Has no effect when using custom camera, except for in the main and pause menus)* +- Added an option to change UI press sensitivity, which has also been made more sensitive by default +- Added a toast notification for when VR fails informing the user to **check the damn logs** +- Added an experiment that can be enabled by passing `--lcvr-disable-car-ownership-patch` to the start options, which might fix the car exploding randomly + +**Changes**: + +- The face-locked UI position and rotation is now smoothed a bit +- Moved some startup logic to a patcher plugin, which means restarting is no longer required when first using LCVR +- Optimized the networking system a bit more +- The main menu and pause menu resolutions have been increased by 20% +- Moved the VR settings into the settings menu +- Most settings can now be changed from within the game + +**Bug fixes:** + +- Fixed error spam in Maneater baby and spray paint items +- Fixed TZP steering the player towards the wrong direction +- Fixed potential denial-of-service exploit in networking system +- Fixed issues with positioning when standing on top of physics objects (car, elevator) +- Fixed crash on death in V64 caused by stack corruption + # 1.3.1 ## V60! v62 diff --git a/COMPILING.md b/COMPILING.md index 384fca68..ff161fac 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -26,4 +26,4 @@ Alternitavely if you're building a finished product run this command: $ dotnet build --configuration Release ``` -The built plugin assembly can now be found inside the `bin` folder. +The built plugin assemblies can now be found inside the `bin` folder. diff --git a/Docs/Thunderstore/README.md b/Docs/Thunderstore/README.md index 42abec5d..71420330 100644 --- a/Docs/Thunderstore/README.md +++ b/Docs/Thunderstore/README.md @@ -61,7 +61,9 @@ Here is a list of LCVR versions and which version(s) of Lethal Company it suppor | LCVR | Lethal Company | |-------------------|-------------------| -| v1.3.0 *(LATEST)* | V56 | +| v1.3.2 *(LATEST)* | V64 | +| v1.3.1 | V62 | +| v1.3.0 | V56 | | v1.2.5 | V50 | | v1.2.4 | V50 | | v1.2.3 | V50 | diff --git a/LCVR.csproj b/LCVR.csproj index 2a703975..188cb53a 100644 --- a/LCVR.csproj +++ b/LCVR.csproj @@ -4,7 +4,8 @@ netstandard2.1 LCVR Collecting Scrap in VR - 1.3.1 + 1.3.2 + DaXcess true 12.0 LethalCompanyVR @@ -16,7 +17,8 @@ - none + embedded + true @@ -24,9 +26,9 @@ - - - + + + @@ -35,7 +37,7 @@ - + @@ -55,26 +57,12 @@ - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - + - + \ No newline at end of file diff --git a/LCVR.sln b/LCVR.sln index 9d740adc..6a617c4e 100644 --- a/LCVR.sln +++ b/LCVR.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.8.34309.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCVR", "LCVR.csproj", "{75F4DDC9-D61E-4664-9F81-AE3B61E405E3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LCVR.Preload", "Preloader\LCVR.Preload.csproj", "{86CF97A2-AF07-4FB0-8A69-95F9A047D2AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {75F4DDC9-D61E-4664-9F81-AE3B61E405E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {75F4DDC9-D61E-4664-9F81-AE3B61E405E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {75F4DDC9-D61E-4664-9F81-AE3B61E405E3}.Release|Any CPU.Build.0 = Release|Any CPU + {86CF97A2-AF07-4FB0-8A69-95F9A047D2AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86CF97A2-AF07-4FB0-8A69-95F9A047D2AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86CF97A2-AF07-4FB0-8A69-95F9A047D2AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86CF97A2-AF07-4FB0-8A69-95F9A047D2AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Preloader/.gitignore b/Preloader/.gitignore new file mode 100644 index 00000000..19957773 --- /dev/null +++ b/Preloader/.gitignore @@ -0,0 +1,7 @@ +*.suo +*.user + +/.vs +/.idea +/obj +/bin \ No newline at end of file diff --git a/Preloader/LCVR.Preload.csproj b/Preloader/LCVR.Preload.csproj new file mode 100644 index 00000000..95b5f24b --- /dev/null +++ b/Preloader/LCVR.Preload.csproj @@ -0,0 +1,34 @@ + + + + netstandard2.1 + LCVR Preloader + DaXcess + 1.3.2 + true + 12.0 + LCVR.Preload + Copyright (c) DaXcess 2024 + https://lcvr.daxcess.io + https://github.com/DaXcess/LCVR + enable + enable + LCVR.Preload + + + + ../bin/Debug + false + none + + + + ../bin/Release + none + + + + + + + diff --git a/Preloader/Preload.cs b/Preloader/Preload.cs new file mode 100644 index 00000000..6358d826 --- /dev/null +++ b/Preloader/Preload.cs @@ -0,0 +1,100 @@ +using System.Reflection; +using BepInEx; +using BepInEx.Logging; +using Mono.Cecil; + +namespace LCVR.Preload; + +public static class Preload +{ + public static IEnumerable TargetDLLs { get; } = []; + + private const string VR_MANIFEST = """ + { + "name": "OpenXR XR Plugin", + "version": "1.8.2", + "libraryName": "UnityOpenXR", + "displays": [ + { + "id": "OpenXR Display" + } + ], + "inputs": [ + { + "id": "OpenXR Input" + } + ] + } + """; + + private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("LCVR.Preload"); + + public static void Initialize() + { + Logger.LogInfo("Setting up VR runtime assets"); + + SetupRuntimeAssets(); + + Logger.LogInfo("We're done here. Goodbye!"); + } + + /// + /// Place required runtime libraries and configuration in the game files to allow VR to be started + /// + private static void SetupRuntimeAssets() + { + var root = Path.Combine(Paths.GameRootPath, "Lethal Company_Data"); + var subsystems = Path.Combine(root, "UnitySubsystems"); + if (!Directory.Exists(subsystems)) + Directory.CreateDirectory(subsystems); + + var openXr = Path.Combine(subsystems, "UnityOpenXR"); + if (!Directory.Exists(openXr)) + Directory.CreateDirectory(openXr); + + var manifest = Path.Combine(openXr, "UnitySubsystemsManifest.json"); + if (!File.Exists(manifest)) + File.WriteAllText(manifest, VR_MANIFEST); + + var plugins = Path.Combine(root, "Plugins"); + var oxrPluginTarget = Path.Combine(plugins, "UnityOpenXR.dll"); + var oxrLoaderTarget = Path.Combine(plugins, "openxr_loader.dll"); + + var current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + var oxrPlugin = Path.Combine(current, "RuntimeDeps/UnityOpenXR.dll"); + var oxrLoader = Path.Combine(current, "RuntimeDeps/openxr_loader.dll"); + + if (!CopyResourceFile(oxrPlugin, oxrPluginTarget)) + Logger.LogWarning("Could not find plugin UnityOpenXR.dll, VR might not work!"); + + if (!CopyResourceFile(oxrLoader, oxrLoaderTarget)) + Logger.LogWarning("Could not find plugin openxr_loader.dll, VR might not work!"); + } + + /// + /// Helper function for SetupRuntimeAssets() to copy resource files and return false if the source does not exist + /// + private static bool CopyResourceFile(string sourceFile, string destinationFile) + { + if (!File.Exists(sourceFile)) + return false; + + if (File.Exists(destinationFile)) + { + var sourceHash = Utils.ComputeHash(File.ReadAllBytes(sourceFile)); + var destHash = Utils.ComputeHash(File.ReadAllBytes(destinationFile)); + + if (sourceHash.SequenceEqual(destHash)) + return true; + } + + File.Copy(sourceFile, destinationFile, true); + + return true; + } + + public static void Patch(AssemblyDefinition assembly) + { + // No-op + } +} \ No newline at end of file diff --git a/Preloader/Utils.cs b/Preloader/Utils.cs new file mode 100644 index 00000000..a6c59739 --- /dev/null +++ b/Preloader/Utils.cs @@ -0,0 +1,13 @@ +using System.Security.Cryptography; + +namespace LCVR.Preload; + +public static class Utils +{ + public static byte[] ComputeHash(byte[] input) + { + using var sha = SHA256.Create(); + + return sha.ComputeHash(input); + } +} diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs deleted file mode 100644 index d9cd93cf..00000000 --- a/Properties/Resources.Designer.cs +++ /dev/null @@ -1,96 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace LCVR.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LCVR.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] lethalcompanyvr { - get { - object obj = ResourceManager.GetObject("lethalcompanyvr", resourceCulture); - return ((byte[])(obj)); - } - } - - /// - /// Looks up a localized string similar to { - /// "name": "OpenXR XR Plugin", - /// "version": "1.8.2", - /// "libraryName": "UnityOpenXR", - /// "displays": [ - /// { - /// "id": "OpenXR Display" - /// } - /// ], - /// "inputs": [ - /// { - /// "id": "OpenXR Input" - /// } - /// ] - ///}. - /// - internal static string UnitySubsystemsManifest { - get { - return ResourceManager.GetString("UnitySubsystemsManifest", resourceCulture); - } - } - } -} diff --git a/Properties/Resources.resx b/Properties/Resources.resx deleted file mode 100644 index b029ac2d..00000000 --- a/Properties/Resources.resx +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Resources\lethalcompanyvr;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - - - { - "name": "OpenXR XR Plugin", - "version": "1.8.2", - "libraryName": "UnityOpenXR", - "displays": [ - { - "id": "OpenXR Display" - } - ], - "inputs": [ - { - "id": "OpenXR Input" - } - ] -} - - \ No newline at end of file diff --git a/README.md b/README.md index 42a7a2f4..2cef3f30 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,9 @@ Here is a list of LCVR versions and which version(s) of Lethal Company it suppor | LCVR | Lethal Company | |-------------------|-------------------| -| v1.3.1 *(BETA)* | V62 | -| v1.3.0 *(LATEST)* | V56 | +| v1.3.2 *(LATEST)* | V64 | +| v1.3.1 | V62 | +| v1.3.0 | V56 | | v1.2.5 | V50 | | v1.2.4 | V50 | | v1.2.3 | V50 | @@ -74,7 +75,7 @@ Also make sure you know how to use BepInEx Dependencies and assembly referencing To install the mod from the source code, you will first have to compile the mod. Instructions for this are available in [COMPILING.md](COMPILING.md). -Next up you'll need to grab a copy of some **Runtime Dependencies**. You can either grab these from [one of the releases](https://github.com/DaXcess/LCVR/releases), or if you truly want the no hand holding experience, you can retrieve them from a Unity project. +Next up you'll need to grab a copy of some **Runtime Dependencies** and the [**Asset Bundle**](https://github.com/DaXcess/LCVR/blob/thunderstore/lethalcompanyvr). You can grab both of these from [the thunderstore branch](https://github.com/DaXcess/LCVR/tree/thunderstore). You can also manually retrieve the **Runtime Dependencies** from a manually compiled Unity project. ## Retrieving Runtime Dependencies from a Unity Project @@ -107,4 +108,27 @@ Once BepInEx has been installed and run at least once, you can start installing First of all, in the `BepInEx/plugins` folder, create a new folder called `LCVR` (doesn't have to be named that specifically, but makes identification easier). Inside this folder, place the `LCVR.dll` file that was generated during the [COMPILING.md](COMPILING.md) steps. -After this has been completed, create a new directory called `RuntimeDeps` (has to be named exactly that) inside of the `LCVR` folder. Inside this folder you will need to put the DLLs that you have retrieved during the [Retrieving Runtime Depenencies](#retrieving-runtime-dependencies-from-a-unity-project) step. You can now run the game with LCVR installed. +After this has been completed, create a new directory called `RuntimeDeps` (has to be named exactly that) inside the `LCVR` folder. Inside this folder you will need to put the following DLLs: + +- UnityEngine.SpatialTracking.dll +- Unity.XR.CoreUtils.dll +- Unity.XR.Interaction.Toolkit.dll +- Unity.XR.Management.dll +- Unity.XR.OpenXR.dll + +These files should have been retrieved either during the [Retrieving Runtime Dependencies](#retrieving-runtime-dependencies-from-a-unity-project) step, or from grabbing them from the latest release. + +Next up, grab the **Asset Bundle** from one of the releases, and place them into the same folder as the `LCVR.dll` file. This asset bundle file needs to be called `lethalcompanyvr`. + +Finally, in the `BepInEx/patchers` folder, also create a new folder called `LCVR` (again, doesn't have to be exact). Inside this folder, place the `LCVR.Preload.dll` file that was also generated during the [COMPILING.md](COMPILING.md) steps. + +In this folder, also create a new directory called `RuntimeDeps` (again, has to be exactly named that), and place the following DLLs inside: + +- openxr_loader.dll +- UnityOpenXR.dll + +Your directory structure (excluding BepInEx internals) should now look like this: + +![image](https://github.com/user-attachments/assets/6cfc3384-6f0c-4d93-9a62-11bb5a009b69) + +You can now run the game with LCVR installed. diff --git a/Resources/lethalcompanyvr b/Resources/lethalcompanyvr deleted file mode 100644 index 67bff24b..00000000 Binary files a/Resources/lethalcompanyvr and /dev/null differ diff --git a/Source/Assets/AssetManager.cs b/Source/Assets/AssetManager.cs index 66d71d30..e6b11665 100644 --- a/Source/Assets/AssetManager.cs +++ b/Source/Assets/AssetManager.cs @@ -1,3 +1,4 @@ +using System.IO; using LCVR.Input; using UnityEngine; using UnityEngine.InputSystem; @@ -24,7 +25,7 @@ internal static class AssetManager public static Shader TMPAlwaysOnTop; public static InputActionAsset VRActions; - public static InputActionAsset TrackingActions; + public static InputActionAsset DefaultXRActions; public static InputActionAsset NullActions; public static RemappableControls RemappableControls; @@ -33,13 +34,15 @@ internal static class AssetManager public static Sprite KofiImage; public static Sprite DiscordImage; public static Sprite WarningImage; - public static Sprite SettingsImage; + public static Sprite SprintImage; public static AudioClip DoorLocked; public static bool LoadAssets() { - assetBundle = AssetBundle.LoadFromMemory(Properties.Resources.lethalcompanyvr); + assetBundle = + AssetBundle.LoadFromFile(Path.Combine(Path.GetDirectoryName(Plugin.Config.AssemblyPath)!, + "lethalcompanyvr")); if (assetBundle == null) { @@ -58,7 +61,7 @@ public static bool LoadAssets() SteeringWheelPoints = assetBundle.LoadAsset("SnapPointContainer"); VRActions = assetBundle.LoadAsset("VRActions"); - TrackingActions = assetBundle.LoadAsset("TrackingActions"); + DefaultXRActions = assetBundle.LoadAsset("DefaultXRActions"); NullActions = assetBundle.LoadAsset("NullPlayerActions"); TMPAlwaysOnTop = assetBundle.LoadAsset("TextMeshPro Always On Top"); @@ -73,7 +76,7 @@ public static bool LoadAssets() KofiImage = assetBundle.LoadAsset("Ko-Fi"); DiscordImage = assetBundle.LoadAsset("Discord"); WarningImage = assetBundle.LoadAsset("Warning"); - SettingsImage = assetBundle.LoadAsset("lcsettings-icon"); + SprintImage = assetBundle.LoadAsset("Aguy"); DoorLocked = assetBundle.LoadAsset("doorlocked"); diff --git a/Source/Config.cs b/Source/Config.cs index b9fe03c7..657044b0 100644 --- a/Source/Config.cs +++ b/Source/Config.cs @@ -4,8 +4,9 @@ namespace LCVR; -public class Config(ConfigFile file) +public class Config(string assemblyPath, ConfigFile file) { + public string AssemblyPath { get; } = assemblyPath; public ConfigFile File { get; } = file; // General configuration @@ -17,6 +18,7 @@ public class Config(ConfigFile file) // Performance configuration + public ConfigEntry EnableOcclusionMesh { get; } = file.Bind("Performance", "EnableOcclusionMesh", true, "The occlusion mesh will cause the game to stop rendering pixels outside of the lens views, which increases performance."); public ConfigEntry EnableDynamicResolution { get; } = file.Bind("Performance", "EnableDynamicResolution", false, "Whether or not dynamic resolution should be enabled. Required for most of these settings to have an effect."); public ConfigEntry DynamicResolutionUpscaleFilter { get; } = file.Bind("Performance", "DynamicResolutionUpscaleFilter", DynamicResUpscaleFilter.EdgeAdaptiveScalingUpres, new ConfigDescription("The filter/algorithm that will be used to perform dynamic resolution upscaling. Defaulted to FSR (Edge Adaptive Scaling).", new AcceptableValueEnum())); public ConfigEntry DynamicResolutionPercentage { get; } = file.Bind("Performance", "DynamicResolutionPercentage", 80f, new ConfigDescription("The percentage of resolution to scale the game down to. The lower the value, the harder the upscale filter has to work which will result in quality loss.", new AcceptableValueRange(0, 100))); @@ -30,7 +32,8 @@ public class Config(ConfigFile file) public ConfigEntry SnapTurnSize { get; } = file.Bind("Input", "SnapTurnSize", 45f, new ConfigDescription("The amount of rotation that is applied when performing a snap turn. Requires turn provider to be set to snap.", new AcceptableValueRange(10, 180))); public ConfigEntry ToggleSprint { get; } = file.Bind("Input", "ToggleSprint", false, "Whether the sprint button should toggle sprint instead of having to hold it down."); public ConfigEntry MovementSprintToggleCooldown { get; } = file.Bind("Input", "MovementSprintToggleCooldown", 1f, new ConfigDescription("The amount of seconds that you need to stand still for sprint to be toggled off automatically. Requires sprint toggle to be enabled.", new AcceptableValueRange(0, 60))); - + public ConfigEntry ButtonPressPoint { get; } = file.Bind("Input", "ButtonPressPoint", 0.25f, new ConfigDescription("The amount of force required to register a UI button press. The lower the value, the more sensitive UI presses become.", new AcceptableValueRange(0, 1))); + // UI configuration public ConfigEntry EnablePitchLockedCanvas { get; } = file.Bind("UI", "EnablePitchLockedCanvas", true, "Whether most of the camera-locked UI elements should only (smoothly) rotate on the Y axis, instead of being stuck on your face."); @@ -47,6 +50,8 @@ public class Config(ConfigFile file) public ConfigEntry LODBias { get; } = file.Bind("Rendering", "LODBias", 2f, new ConfigDescription("The LOD bias is a multiplier that dictates when an LOD must reduce their quality. Higher values means that more detailed LODs will persist for longer.", new AcceptableValueRange(1, 5))); public ConfigEntry DisableLensDistortion { get; } = file.Bind("Rendering", "DisableLensDistortion", false, "Disables the warping effects that you experience when you are under water, use the TZP-inhalant and more."); public ConfigEntry SpectatorLightRemovesVolumetrics { get; } = file.Bind("Rendering", "SpectatorLightRemovesVolumetrics", false, "When spectating, also disable all volumetrics (fog) while the fullbright lighting is enabled for more visibility."); + public ConfigEntry MirrorXOffset { get; } = file.Bind("Rendering", "MirrorXOffset", 0f, new ConfigDescription("The X offset that is added to the XR Mirror View shader. Do not touch if you don't know what this means.", new AcceptableValueRange(-1, 1))); + public ConfigEntry MirrorYOffset { get; } = file.Bind("Rendering", "MirrorYOffset", 0f, new ConfigDescription("The Y offset that is added to the XR Mirror View shader. Do not touch if you don't know what this means.", new AcceptableValueRange(-1, 1))); // Interaction configuration diff --git a/Source/Entrypoint.cs b/Source/Entrypoint.cs index dcff3983..0f492e04 100644 --- a/Source/Entrypoint.cs +++ b/Source/Entrypoint.cs @@ -17,21 +17,12 @@ private static void OnGameEntered() StartOfRound.Instance.StartCoroutine(Start()); } + // ReSharper disable Unity.PerformanceAnalysis private static IEnumerator Start() { yield return new WaitUntil(() => StartOfRound.Instance.activeCamera != null); // Setup session manager (required for both VR and NonVR) new GameObject("LCVR Session Manager").AddComponent(); - - // Setup Dissonance for VR movement comms - // yield return DNet.Initialize(); - } - - [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.OnDestroy))] - [HarmonyPostfix] - private static void OnGameLeave() - { - // DNet.Shutdown(); } } diff --git a/Source/Input/Actions.cs b/Source/Input/Actions.cs index 6aff1058..afebac19 100644 --- a/Source/Input/Actions.cs +++ b/Source/Input/Actions.cs @@ -21,17 +21,17 @@ public class Actions private Actions() { - HeadPosition = AssetManager.TrackingActions.FindAction("Head/Position"); - HeadRotation = AssetManager.TrackingActions.FindAction("Head/Rotation"); - HeadTrackingState = AssetManager.TrackingActions.FindAction("Head/Tracking State"); + HeadPosition = AssetManager.DefaultXRActions.FindAction("Head/Position"); + HeadRotation = AssetManager.DefaultXRActions.FindAction("Head/Rotation"); + HeadTrackingState = AssetManager.DefaultXRActions.FindAction("Head/Tracking State"); - LeftHandPosition = AssetManager.TrackingActions.FindAction("Left/Position"); - LeftHandRotation = AssetManager.TrackingActions.FindAction("Left/Rotation"); - LeftHandTrackingState = AssetManager.TrackingActions.FindAction("Left/Tracking State"); + LeftHandPosition = AssetManager.DefaultXRActions.FindAction("Left/Position"); + LeftHandRotation = AssetManager.DefaultXRActions.FindAction("Left/Rotation"); + LeftHandTrackingState = AssetManager.DefaultXRActions.FindAction("Left/Tracking State"); - RightHandPosition = AssetManager.TrackingActions.FindAction("Right/Position"); - RightHandRotation = AssetManager.TrackingActions.FindAction("Right/Rotation"); - RightHandTrackingState = AssetManager.TrackingActions.FindAction("Right/Tracking State"); + RightHandPosition = AssetManager.DefaultXRActions.FindAction("Right/Position"); + RightHandRotation = AssetManager.DefaultXRActions.FindAction("Right/Rotation"); + RightHandTrackingState = AssetManager.DefaultXRActions.FindAction("Right/Tracking State"); } public InputAction this[string name] => IngamePlayerSettings.Instance.playerInput.actions[name]; diff --git a/Source/Items/VRBeltBagItem.cs b/Source/Items/VRBeltBagItem.cs new file mode 100644 index 00000000..2aa932ce --- /dev/null +++ b/Source/Items/VRBeltBagItem.cs @@ -0,0 +1,69 @@ +using LCVR.Input; +using LCVR.Player; +using UnityEngine; +using UnityEngine.XR; + +namespace LCVR.Items; + +public class VRBeltBagItem : VRItem +{ + private ShakeDetector shake; + private ShakeDetector shakeBig; + private BeltBagInventoryUI inventoryUI; + + private float lastShakeTime; + private float lastItemDrop; + private float minWaitTime = 0.3f; + + protected override void Awake() + { + base.Awake(); + + if (!IsLocal) + return; + + shake = new ShakeDetector(transform, 0.06f, true); + shakeBig = new ShakeDetector(transform, 0.1f, true); + inventoryUI = FindObjectOfType(true); + + shake.onShake += () => + { + if (Time.realtimeSinceStartup - lastShakeTime > 0.5f) + return; + + if (item.currentPlayerChecking != StartOfRound.Instance.localPlayerController) + return; + + if (transform.forward.y > -0.6) + return; + + if (Time.realtimeSinceStartup - lastItemDrop < minWaitTime) + return; + + if (item.objectsInBag.Count < 1) + return; + + inventoryUI.RemoveItemFromUI(item.objectsInBag.Count - 1); + lastItemDrop = Time.realtimeSinceStartup; + lastShakeTime = Time.realtimeSinceStartup; + minWaitTime = Random.Range(0.1f, 0.3f); + + RoundManager.PlayRandomClip(item.bagAudio, item.grabItemInBagSFX, audibleNoiseID: -1); + VRSession.VibrateController(XRNode.RightHand, 0.1f, 0.5f); + }; + + shakeBig.onShake += () => + { + lastShakeTime = Time.realtimeSinceStartup; + }; + } + + protected override void OnUpdate() + { + if (!IsLocal) + return; + + shake.Update(); + shakeBig.Update(); + } +} \ No newline at end of file diff --git a/Source/Items/VRManeaterBaby.cs b/Source/Items/VRManeaterBaby.cs index c97cffb9..2dbe8f46 100644 --- a/Source/Items/VRManeaterBaby.cs +++ b/Source/Items/VRManeaterBaby.cs @@ -26,7 +26,7 @@ internal class VRManEaterBaby : VRItem var source = VRSession.Instance.LocalPlayer.PrimaryController.transform; shake = new ShakeDetector(source, 0.015f, true); - shakeBig = new ShakeDetector(source, 0.04f, true); + shakeBig = new ShakeDetector(source, 0.05f, true); shake.onShake += OnShakeMotion; shakeBig.onShake += OnShakeBigMotion; @@ -123,6 +123,9 @@ private IEnumerator KeepRockingHard() protected override void OnUpdate() { + if (!IsLocal) + return; + shake.Update(); shakeBig.Update(); } diff --git a/Source/Items/VRSprayPaintItem.cs b/Source/Items/VRSprayPaintItem.cs index 26a059af..a668ff43 100644 --- a/Source/Items/VRSprayPaintItem.cs +++ b/Source/Items/VRSprayPaintItem.cs @@ -38,6 +38,9 @@ private void OnShakeMotion() protected override void OnUpdate() { + if (!IsLocal) + return; + shake.Update(); } } diff --git a/Source/Native.cs b/Source/Native.cs index b97ae910..2296bab6 100644 --- a/Source/Native.cs +++ b/Source/Native.cs @@ -82,6 +82,44 @@ private static extern bool GetTokenInformation(IntPtr hToken, uint tokenInformat [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr handle); + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct NotifyIconData + { + public int cbSize; + public IntPtr hWnd; + public int uID; + public int uFlags; + public int uCallbackMessage; + public IntPtr hIcon; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szTip; + + public int dwState; + public int dwStateMask; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string szInfo; + + public int uTimeoutOrVersion; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szInfoTitle; + + public int dwInfoFlags; + public Guid guidItem; + public IntPtr hBalloonIcon; + } + + [DllImport("Shell32.dll", CharSet = CharSet.Auto)] + private static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData lpData); + + [DllImport("Kernel32.dll")] + private static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + private static extern IntPtr LoadIcon(IntPtr a, IntPtr b); + public static bool RegOpenSubKey(ref IntPtr hKey, string lpSubKey, int samDesired) { var result = RegOpenKeyEx(hKey, lpSubKey, 0, samDesired, out var hNewKey) == 0; @@ -94,6 +132,24 @@ public static bool RegOpenSubKey(ref IntPtr hKey, string lpSubKey, int samDesire return true; } + public static void ShowNotification(string title, string message) + { + var data = new NotifyIconData + { + cbSize = Marshal.SizeOf(), + hWnd = GetConsoleWindow(), + uFlags = 0x13, + dwInfoFlags = 0x3, + uCallbackMessage = 0x401, + hIcon = LoadIcon(IntPtr.Zero, new IntPtr(32512)), + szInfo = message, + szInfoTitle = title + }; + + Shell_NotifyIcon(0, ref data); + Shell_NotifyIcon(2, ref data); + } + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); private static string GetWindowText(IntPtr hWnd) diff --git a/Source/Networking/Channel.cs b/Source/Networking/Channel.cs index cdc055e0..329496a3 100644 --- a/Source/Networking/Channel.cs +++ b/Source/Networking/Channel.cs @@ -25,6 +25,7 @@ public void SendPacket(byte[] packet) else bw.Write(false); + bw.Write((uint)packet.Length); bw.Write(packet); network.BroadcastPacket(NetworkSystem.MessageType.Channel, mem.ToArray()); diff --git a/Source/Networking/NetworkSystem.cs b/Source/Networking/NetworkSystem.cs index 65061598..d1d309dd 100644 --- a/Source/Networking/NetworkSystem.cs +++ b/Source/Networking/NetworkSystem.cs @@ -19,14 +19,17 @@ namespace LCVR.Networking; public class NetworkSystem : MonoBehaviour { /// Protocol Version, increase this every time a change is made that is not compatible with older versions - private const ushort PROTOCOL_VERSION = 6; - + private const ushort PROTOCOL_VERSION = 7; + + /// Packet size limit to prevent denial-of-service attacks + private const uint PACKET_MAX_SIZE = 4 * 1024; + private static NetworkSystem _instance; public static NetworkSystem Instance => _instance == null ? _instance = new GameObject("VR Network System").AddComponent() : _instance; - + private DissonanceComms dissonance; private BaseClient network; @@ -34,7 +37,7 @@ public class NetworkSystem : MonoBehaviour /// List of active clients in the session /// private readonly Dictionary> clients = []; - + /// /// List of VR players /// @@ -44,9 +47,9 @@ public class NetworkSystem : MonoBehaviour /// Player ID lookup table /// private readonly Dictionary playerIdByName = []; - + /// - /// List of client IDs (from Dissonance Voice) which support DNet + /// List of client IDs (from Dissonance Voice) which support LCVR networking /// private static readonly HashSet subscribers = []; @@ -54,12 +57,12 @@ public class NetworkSystem : MonoBehaviour /// List of active channels we have, which can be used to communicate data over /// private readonly Dictionary> channels = []; - + private ushort? LocalId => network?._serverNegotiator.LocalId; - + public bool Initialized { get; private set; } public VRNetPlayer[] Players => players.Values.ToArray(); - + private void Awake() { StartCoroutine(Initialize()); @@ -69,7 +72,7 @@ private void OnDestroy() { if (!dissonance) return; - + dissonance.OnPlayerJoinedSession -= OnPlayerJoinedSession; dissonance.OnPlayerLeftSession -= OnPlayerLeftSession; } @@ -78,18 +81,18 @@ private void OnDestroy() private IEnumerator Initialize() { yield return new WaitUntil(() => StartOfRound.Instance != null); - + dissonance = FindObjectOfType(); network = FindObjectOfType().Client; - + // Wait until Dissonance Voip has been set up yield return new WaitUntil(() => LocalId.HasValue); - + Logger.LogDebug("Connected to Dissonance Voip"); dissonance.OnPlayerJoinedSession += OnPlayerJoinedSession; dissonance.OnPlayerLeftSession += OnPlayerLeftSession; - + foreach (var player in dissonance.Players) if (!player.IsLocalPlayer && network._peers.TryGetClientInfoByName(player.Name, out var client)) { @@ -116,7 +119,7 @@ private void OnPlayerJoinedSession(VoicePlayerState player) { if (!network._peers.TryGetClientInfoByName(player.Name, out var client)) return; - + clients.Add(client.PlayerId, client); playerIdByName.Add(player.Name, client.PlayerId); } @@ -125,7 +128,7 @@ private void OnPlayerLeftSession(VoicePlayerState player) { if (!playerIdByName.TryGetValue(player.Name, out var id)) return; - + if (players.TryGetValue(id, out var networkPlayer)) Destroy(networkPlayer); @@ -157,7 +160,7 @@ internal void SendPacket(MessageType type, byte[] payload, params ClientInfo clients.TryGetValue(key, out _)).Select(value => clients[value]) .ToList(); - + network.SendReliableP2P(targets, ConstructPacket(type, sender, payload)); } @@ -176,24 +179,24 @@ private static byte[] ConstructPacket(MessageType type, ushort sender, byte[] pa { using var memory = new MemoryStream(); using var writer = new BinaryWriter(memory); - + // Magic writer.Write((ushort)51083); - + // Message type writer.Write((byte)type); - + // Sender Id writer.Write(sender); - + // Rest of payload writer.Write(payload); return memory.ToArray(); } - + #endregion - + #region PACKET HANDLING public void OnPacketReceived(MessageType messageType, ushort sender, BinaryReader reader) @@ -203,11 +206,11 @@ public void OnPacketReceived(MessageType messageType, ushort sender, BinaryReade case MessageType.HandshakeRequest: HandleHandshakeRequest(sender, reader.ReadUInt16()); break; - + case MessageType.HandshakeResponse: StartCoroutine(HandleHandshakeResponse(sender, reader.ReadBoolean())); break; - + case MessageType.Channel: HandleChannelMessage(sender, reader); break; @@ -230,16 +233,17 @@ private void HandleHandshakeRequest(ushort sender, ushort protocol) Logger.LogError($"Cannot send handshake response to {sender}: Client info is missing!"); return; } - + Logger.LogDebug($"Received handshake request from {sender}"); SendPacket(MessageType.HandshakeResponse, [VRSession.InVR ? (byte)1 : (byte)0], target); } + // ReSharper disable Unity.PerformanceAnalysis private IEnumerator HandleHandshakeResponse(ushort sender, bool inVR) { Logger.LogDebug($"Received handshake response from {sender}"); - + subscribers.Add(sender); if (!inVR) @@ -247,27 +251,29 @@ private IEnumerator HandleHandshakeResponse(ushort sender, bool inVR) // Wait until client is a part of the peers list yield return new WaitUntilTimeout(10, () => network._peers.TryGetClientInfoById(sender, out _)); - + if (!network._peers.TryGetClientInfoById(sender, out var client)) { - Logger.LogError($"Failed to resolve client for Player Id {sender} after 10s. No VR movements will be synchronized."); + Logger.LogError( + $"Failed to resolve client for Player Id {sender} after 10s. No VR movements will be synchronized."); yield break; } - + var player = dissonance.FindPlayer(client.PlayerName); if (player == null) { - Logger.LogError($"Failed to resolve client for Player {client.PlayerName}. No VR movements will be synchronized."); + Logger.LogError( + $"Failed to resolve client for Player {client.PlayerName}. No VR movements will be synchronized."); yield break; } yield return new WaitUntil(() => player.Tracker != null); - + // Ignore players that have already been registered if (players.ContainsKey(sender)) yield break; - + var playerObject = ((NfgoPlayer)player.Tracker!).gameObject; var playerController = playerObject.GetComponent(); var networkPlayer = playerObject.AddComponent(); @@ -279,7 +285,7 @@ private IEnumerator HandleHandshakeResponse(ushort sender, bool inVR) Logger.LogError( "VR player already exists? Destroying VR player script! Player will look like a vanilla player."); - + Destroy(networkPlayer); OnPlayerLeftSession(player); } @@ -295,15 +301,31 @@ private void HandleChannelMessage(ushort sender, BinaryReader reader) if (!channels.TryGetValue(type, out var channelList)) return; + var length = reader.ReadUInt32(); + if (length > PACKET_MAX_SIZE) + return; + + var data = reader.ReadBytes((int)length); + if (instanceId.HasValue) channelList.Where(channel => channel.InstanceId == instanceId.Value) - .Do(channel => channel.ReceivedPacket(sender, reader.Clone())); + .Do(channel => + { + using var wrappedData = new BinaryReader(new MemoryStream(data)); + + channel.ReceivedPacket(sender, wrappedData); + }); else - channelList.Do(channel => channel.ReceivedPacket(sender, reader.Clone())); + channelList.Do(channel => + { + using var wrappedData = new BinaryReader(new MemoryStream(data)); + + channel.ReceivedPacket(sender, wrappedData); + }); } - + #endregion - + #region CHANNELS public Channel CreateChannel(ChannelType type, ulong? instanceId = null) @@ -323,7 +345,7 @@ internal void CloseChannel(Channel channel) channelList.Remove(channel); } - + #endregion public enum MessageType : byte diff --git a/Source/Networking/Serialization.cs b/Source/Networking/Serialization.cs index 1de71bf5..43fc9c48 100644 --- a/Source/Networking/Serialization.cs +++ b/Source/Networking/Serialization.cs @@ -9,7 +9,7 @@ namespace LCVR.Networking; /// -/// A special serializer that can be used within DNet. +/// A special serializer that can be used within the networking system. /// /// This serializer allows easy serialization from structs and classes to bytes and vice-versa. /// It supports most basic primitive types (numbers, bools, strings), Vector3's, Quaternions, and any other class @@ -19,6 +19,8 @@ namespace LCVR.Networking; /// public static class Serialization { + private const uint MAX_ARRAY_LENGTH = 4096; + private static readonly Dictionary typeCache = []; private static IEnumerable GetFields(Type type) @@ -68,6 +70,9 @@ public static byte[] Serialize(object @object) if (value.GetType().IsArray) { var array = (Array)value; + if (array.Length > MAX_ARRAY_LENGTH) + throw new ArgumentException($"Tried to serialize an array larger than {MAX_ARRAY_LENGTH} elements"); + bw.Write(array.Length); foreach (var arrayEl in array) @@ -113,6 +118,10 @@ private static object DeserializeInternal(BinaryReader br, Type type) if (field.FieldType.IsArray) { var size = br.ReadInt32(); + if (size > MAX_ARRAY_LENGTH) + throw new ArgumentException( + $"Tried to deserialize an array larger than {MAX_ARRAY_LENGTH} elements"); + var targetArray = Array.CreateInstance(field.FieldType.GetElementType()!, size); for (var i = 0; i < size; i++) diff --git a/Source/Networking/VRNetPlayer.cs b/Source/Networking/VRNetPlayer.cs index 7171671e..869df166 100644 --- a/Source/Networking/VRNetPlayer.cs +++ b/Source/Networking/VRNetPlayer.cs @@ -15,6 +15,7 @@ namespace LCVR.Networking; /// /// A behaviour that is attached to other VR players /// +[DefaultExecutionOrder(-100)] public class VRNetPlayer : MonoBehaviour { private GameObject playerGhost; @@ -294,6 +295,8 @@ private void Update() private void LateUpdate() { + UpdateBones(); + var positionOffset = new Vector3(0, crouchState switch { CrouchState.Roomscale => 0.1f, @@ -367,6 +370,12 @@ private void LateUpdate() if (StartOfRound.Instance.localPlayerController.localVisorTargetPoint is not null) usernameBillboard.LookAt(StartOfRound.Instance.localPlayerController.localVisorTargetPoint); } + + private void UpdateBones() + { + Bones.ServerItemHolder.localPosition = new Vector3(0.002f, 0.056f, -0.046f); + Bones.ServerItemHolder.localRotation = Quaternion.identity; + } public void UpdateIKWeights() { diff --git a/Source/Patches/BuildingPatches.cs b/Source/Patches/BuildingPatches.cs index 60aa2afe..b1481896 100644 --- a/Source/Patches/BuildingPatches.cs +++ b/Source/Patches/BuildingPatches.cs @@ -32,6 +32,37 @@ private static void OnUpdate(ShipBuildModeManager __instance) __instance.ghostObject.eulerAngles.y + Time.deltaTime * pivotAmount * 1f, __instance.ghostObject.eulerAngles.z); } + + /// + /// Make the entering of build mode check the target structure from the hand, instead of from the camera + /// + [HarmonyPatch(typeof(ShipBuildModeManager), nameof(ShipBuildModeManager.EnterBuildMode))] + [HarmonyTranspiler] + private static IEnumerable ShipBuilderEnterFromHand(IEnumerable instructions) + { + var matcher = new CodeMatcher(instructions); + + for (var i = 0; i < 2; i++) + { + matcher.MatchForward(false, + new CodeMatch(OpCodes.Ldfld, + Field(typeof(PlayerControllerB), nameof(PlayerControllerB.gameplayCamera)))) + .Advance(-2) + .SetOpcodeAndAdvance(OpCodes.Nop) + .RemoveInstructions(3) + .InsertAndAdvance( + new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(VRSession), nameof(VRSession.Instance))), + new CodeInstruction(OpCodes.Callvirt, + PropertyGetter(typeof(VRSession), nameof(VRSession.LocalPlayer))), + new CodeInstruction(OpCodes.Callvirt, + PropertyGetter(typeof(VRPlayer), nameof(VRPlayer.PrimaryController))), + new CodeInstruction(OpCodes.Callvirt, + PropertyGetter(typeof(VRController), nameof(VRController.InteractOrigin))) + ); + } + + return matcher.InstructionEnumeration(); + } /// /// Make the placement of objects follow the hand rotation instead of the head rotation diff --git a/Source/Patches/CarPatches.cs b/Source/Patches/CarPatches.cs index a9f44a3e..d22539de 100644 --- a/Source/Patches/CarPatches.cs +++ b/Source/Patches/CarPatches.cs @@ -67,6 +67,9 @@ private static IEnumerable RemovePlayerAnimations(IEnumerable + /// Prevent the cursor from showing up when playing in VR + /// + [HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)] + [HarmonyPrefix] + private static void PatchCursorVisibility(ref bool value) + { + if (Chainloader.PluginInfos.ContainsKey("com.sinai.unityexplorer")) + return; + + value = false; + } + + /// + /// Prevent the cursor from getting locked when playing in VR + /// + [HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)] + [HarmonyPrefix] + private static void PatchCursorLockState(ref CursorLockMode value) + { + if (Chainloader.PluginInfos.ContainsKey("com.sinai.unityexplorer")) + return; + + value = CursorLockMode.None; + } +} diff --git a/Source/Patches/HarmonyPatcher.cs b/Source/Patches/HarmonyPatcher.cs index a677d508..a9b6767a 100644 --- a/Source/Patches/HarmonyPatcher.cs +++ b/Source/Patches/HarmonyPatcher.cs @@ -7,23 +7,23 @@ namespace LCVR.Patches; internal static class HarmonyPatcher { - private static readonly Harmony vrPatcher = new("io.daxcess.lcvr"); - private static readonly Harmony universalPatcher = new("io.daxcess.lcvr-universal"); + private static readonly Harmony VRPatcher = new("io.daxcess.lcvr"); + private static readonly Harmony UniversalPatcher = new("io.daxcess.lcvr-universal"); public static void PatchUniversal() { - Patch(universalPatcher, LCVRPatchTarget.Universal); + Patch(UniversalPatcher, LCVRPatchTarget.Universal); } public static void PatchVR() { - Patch(vrPatcher, LCVRPatchTarget.VROnly); + Patch(VRPatcher, LCVRPatchTarget.VROnly); if (!Plugin.Flags.HasFlag(Flags.ItemOffsetEditor)) return; Logger.LogWarning("Item offset editor is enabled!"); - vrPatcher.CreateClassProcessor(typeof(ItemOffsetEditorPatches)).Patch(); + VRPatcher.CreateClassProcessor(typeof(ItemOffsetEditorPatches)).Patch(); } private static void Patch(Harmony patcher, LCVRPatchTarget target) @@ -49,7 +49,7 @@ private static void Patch(Harmony patcher, LCVRPatchTarget target) } catch (Exception e) { - Logger.LogError($"Failed to apply patches from {type}: {e.Message}"); + Logger.LogError($"Failed to apply patches from {type}: {e.Message}, {e.InnerException}"); } }); } diff --git a/Source/Patches/ItemPatches.cs b/Source/Patches/ItemPatches.cs index eac622ce..f48af78d 100644 --- a/Source/Patches/ItemPatches.cs +++ b/Source/Patches/ItemPatches.cs @@ -61,7 +61,7 @@ private static bool HandleUpdateItemOffset(GrabbableObject item) return true; // If the item isn't held, we don't care - if (item.playerHeldBy == null) + if (item.playerHeldBy == null || item.playerHeldBy.currentlyHeldObjectServer != item) return true; // Don't set custom offset if remote player is not in VR diff --git a/Source/Patches/Items/BeltItemPatches.cs b/Source/Patches/Items/BeltItemPatches.cs new file mode 100644 index 00000000..0af3a941 --- /dev/null +++ b/Source/Patches/Items/BeltItemPatches.cs @@ -0,0 +1,45 @@ +using HarmonyLib; + +namespace LCVR.Patches.Items; + +[LCVRPatch] +[HarmonyPatch] +internal static class BeltItemPatches +{ + /// + /// Prevent the "Open Bag" interaction trigger from colliding with the interact laser + /// + [HarmonyPatch(typeof(BeltBagItem), nameof(BeltBagItem.EquipItem))] + [HarmonyPostfix] + private static void OnEquipBag(BeltBagItem __instance) + { + __instance.GetComponentInChildren().enabled = false; + } + + /// + /// Re-enable the "Open Bag" trigger when dropping the item + /// + [HarmonyPatch(typeof(BeltBagItem), nameof(BeltBagItem.DiscardItem))] + [HarmonyPostfix] + private static void OnDiscardBag(BeltBagItem __instance) + { + __instance.GetComponentInChildren().enabled = true; + } + + /// + /// Allow the trigger button to also close the bag + /// + [HarmonyPatch(typeof(BeltBagItem), nameof(BeltBagItem.ItemActivate))] + [HarmonyPrefix] + private static bool CloseBagPatch(BeltBagItem __instance, bool buttonDown) + { + if (!buttonDown) + return false; + + if (__instance.currentPlayerChecking != StartOfRound.Instance.localPlayerController) + return true; + + StartOfRound.Instance.localPlayerController.SetInSpecialMenu(false); + return false; + } +} diff --git a/Source/Patches/PlayerControllerPatches.cs b/Source/Patches/PlayerControllerPatches.cs index 740547b0..dc63a687 100644 --- a/Source/Patches/PlayerControllerPatches.cs +++ b/Source/Patches/PlayerControllerPatches.cs @@ -105,6 +105,32 @@ private static IEnumerable Transpiler(IEnumerable + /// Apply walking force based on camera rotation instead of player rotation + /// + [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.Update))] + [HarmonyTranspiler] + private static IEnumerable WalkTowardsCameraDirection(IEnumerable instructions) + { + return new CodeMatcher(instructions) + .MatchForward(false, + new CodeMatch(OpCodes.Stfld, Field(typeof(PlayerControllerB), nameof(PlayerControllerB.walkForce)))) + .Advance(-18) + .RemoveInstructions(14) + .InsertAndAdvance( + new CodeInstruction(OpCodes.Call, ((Func)GetWalkForce).Method)) + .InstructionEnumeration(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Vector3 GetWalkForce(PlayerControllerB player) + { + var rotation = Quaternion.Euler(0, player.gameplayCamera.transform.eulerAngles.y, 0); + + return rotation * Vector3.right * player.moveInputVector.x + + rotation * Vector3.forward * player.moveInputVector.y; + } + } + /// /// Prevent the local player visor from being moved when the player dies /// @@ -329,6 +355,22 @@ static Vector3 GetClampedCameraPosition(PlayerControllerB player) return player.transform.position + Vector3.up * Mathf.Clamp(actualHeight, 0.5f, 2.35f); } } + + /// + /// Allow interacting with items even when we're inside of a special menu + /// + [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.ActivateItem_performed))] + [HarmonyTranspiler] + private static IEnumerable AllowActivateDuringMenu(IEnumerable instructions) + { + return new CodeMatcher(instructions) + .MatchForward(false, + new CodeMatch(OpCodes.Ldfld, Field(typeof(PlayerControllerB), nameof(PlayerControllerB.inSpecialMenu)))) + .Advance(-1) + .SetOpcodeAndAdvance(OpCodes.Nop) + .RemoveInstructions(3) + .InstructionEnumeration(); + } } [LCVRPatch(LCVRPatchTarget.Universal)] diff --git a/Source/Patches/Spectating/AIPatches.cs b/Source/Patches/Spectating/AIPatches.cs index b8dc382f..c1519345 100644 --- a/Source/Patches/Spectating/AIPatches.cs +++ b/Source/Patches/Spectating/AIPatches.cs @@ -44,19 +44,25 @@ private static bool CanBeHeard() /// [HarmonyPatch(typeof(EnemyAI), nameof(EnemyAI.CheckLineOfSightForPlayer))] [HarmonyTranspiler] - private static IEnumerable LineOfSightPlayerIgnoreDeadPlayer(IEnumerable instructions, ILGenerator generator) + private static IEnumerable LineOfSightPlayerIgnoreDeadPlayer( + IEnumerable instructions, ILGenerator generator) { return new CodeMatcher(instructions, generator) - .MatchForward(false, new CodeMatch(i => i.opcode == OpCodes.Ldfld && (FieldInfo)i.operand == Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) + .MatchForward(false, + new CodeMatch(i => i.opcode == OpCodes.Ldfld && (FieldInfo)i.operand == + Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) .Advance(-1) - .Insert(new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(StartOfRound), nameof(StartOfRound.Instance)))) + .Insert(new CodeInstruction(OpCodes.Call, + PropertyGetter(typeof(StartOfRound), nameof(StartOfRound.Instance)))) .CreateLabel(out var label) .Advance(1) - .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) + .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, + Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) .InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_1)) .InsertAndAdvance(new CodeInstruction(OpCodes.Ldelem_Ref)) - .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isPlayerDead)))) - .InsertBranchAndAdvance(OpCodes.Brtrue, 77) + .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, + Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isPlayerDead)))) + .InsertBranchAndAdvance(OpCodes.Brtrue, 78) .MatchForward(false, new CodeMatch(OpCodes.Blt)) .Advance(1) .MatchForward(false, new CodeMatch(OpCodes.Blt)) @@ -65,22 +71,28 @@ private static IEnumerable LineOfSightPlayerIgnoreDeadPlayer(IE } /// - /// Prevent closest player line of sight detection for dead players + /// Prevent "closest player line of sight detection" for dead players /// [HarmonyPatch(typeof(EnemyAI), nameof(EnemyAI.CheckLineOfSightForClosestPlayer))] [HarmonyTranspiler] - private static IEnumerable LineOfSightClosestPlayerIgnoreDeadPlayer(IEnumerable instructions, ILGenerator generator) + private static IEnumerable LineOfSightClosestPlayerIgnoreDeadPlayer( + IEnumerable instructions, ILGenerator generator) { return new CodeMatcher(instructions, generator) - .MatchForward(false, new CodeMatch(i => i.opcode == OpCodes.Ldfld && (FieldInfo)i.operand == Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) + .MatchForward(false, + new CodeMatch(i => i.opcode == OpCodes.Ldfld && (FieldInfo)i.operand == + Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) .Advance(-1) - .Insert(new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(StartOfRound), nameof(StartOfRound.Instance)))) + .Insert(new CodeInstruction(OpCodes.Call, + PropertyGetter(typeof(StartOfRound), nameof(StartOfRound.Instance)))) .CreateLabel(out var label) .Advance(1) - .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) + .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, + Field(typeof(StartOfRound), nameof(StartOfRound.allPlayerScripts)))) .InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_S, 4)) .InsertAndAdvance(new CodeInstruction(OpCodes.Ldelem_Ref)) - .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isPlayerDead)))) + .InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, + Field(typeof(PlayerControllerB), nameof(PlayerControllerB.isPlayerDead)))) .InsertBranchAndAdvance(OpCodes.Brtrue, 79) .MatchForward(false, new CodeMatch(OpCodes.Blt)) .Advance(1) diff --git a/Source/Patches/Spectating/Patches.cs b/Source/Patches/Spectating/Patches.cs index e45029cd..bd6904fe 100644 --- a/Source/Patches/Spectating/Patches.cs +++ b/Source/Patches/Spectating/Patches.cs @@ -127,7 +127,7 @@ private static void OnPlayerDeath(PlayerControllerB __instance) shipDoorWall.GetComponent().isTrigger = true; // Make sure any cars are not owned by us if we're the host - if (NetworkManager.Singleton.IsHost) + if (NetworkManager.Singleton.IsHost && !Plugin.Flags.HasFlag(Flags.ExperimentalDisableCarOwnershipPatch)) { var alivePlayer = StartOfRound.Instance.allPlayerScripts.FirstOrDefault(player => !player.isPlayerDead); if (alivePlayer) diff --git a/Source/Patches/UIPatches.cs b/Source/Patches/UIPatches.cs index b97d5497..ee6662f4 100644 --- a/Source/Patches/UIPatches.cs +++ b/Source/Patches/UIPatches.cs @@ -1,5 +1,4 @@ -using BepInEx.Bootstrap; -using HarmonyLib; +using HarmonyLib; using LCVR.Assets; using LCVR.Player; using LCVR.UI; @@ -8,7 +7,9 @@ using TMPro; using UnityEngine; using UnityEngine.InputSystem.UI; +using UnityEngine.SceneManagement; using UnityEngine.UI; +using UnityEngine.XR; using UnityEngine.XR.Interaction.Toolkit.UI; namespace LCVR.Patches; @@ -28,22 +29,22 @@ private static void OnPreInitMenuShown(PreInitSceneScript __instance) InitMenuScene(canvas); - if (Plugin.Flags.HasFlag(Flags.InvalidGameAssembly)) - { - var textObject = - Object.Instantiate(canvas.gameObject.Find("GameObject/LANOrOnline/OnlineButton/Text (TMP) (1)")); - var text = textObject.GetComponent(); - - text.transform.parent = __instance.launchSettingsPanelsContainer.transform; - text.transform.localPosition = new Vector3(200, -30, 0); - text.transform.localScale = Vector3.one; - text.text = "Invalid Game Assembly Detected!\nYou are using an unsupported version of the game!"; - text.autoSizeTextContainer = true; - text.color = new Color(0.9434f, 0.9434f, 0.0434f, 1); - text.alignment = TextAlignmentOptions.Center; - text.fontSize = 18; - text.raycastTarget = false; - } + if (!Plugin.Flags.HasFlag(Flags.InvalidGameAssembly)) + return; + + var textObject = + Object.Instantiate(canvas.gameObject.Find("GameObject/LANOrOnline/OnlineButton/Text (TMP) (1)")); + var text = textObject.GetComponent(); + + text.transform.parent = __instance.launchSettingsPanelsContainer.transform; + text.transform.localPosition = new Vector3(200, -30, 0); + text.transform.localScale = Vector3.one; + text.text = "Invalid Game Assembly Detected!\nYou are using an unsupported version of the game!"; + text.autoSizeTextContainer = true; + text.color = new Color(0.9434f, 0.9434f, 0.0434f, 1); + text.alignment = TextAlignmentOptions.Center; + text.fontSize = 18; + text.raycastTarget = false; } /// @@ -117,6 +118,8 @@ private static void InitMenuScene(Canvas canvas) leftControllerInteractor.rayOriginTransform.localRotation = Quaternion.Euler(60, 347, 90); rightControllerInteractor.rayOriginTransform.localRotation = Quaternion.Euler(60, 347, 270); + + XRSettings.eyeTextureResolutionScale = 1.2f; } /// @@ -242,31 +245,6 @@ private static void ForceNewInputSystem(XRUIInputModule __instance) [HarmonyPatch] internal static class UniversalUIPatches { - /// - /// This function runs when the pre-init menu is shown - /// - [HarmonyPatch(typeof(PreInitSceneScript), nameof(PreInitSceneScript.Start))] - [HarmonyPostfix] - private static void OnPreInitMenuShown(PreInitSceneScript __instance) - { - if (!Plugin.Flags.HasFlag(Flags.RestartRequired)) - return; - - var canvas = __instance.launchSettingsPanelsContainer.GetComponentInParent().gameObject; - var textObject = Object.Instantiate(canvas.Find("GameObject/LANOrOnline/OnlineButton/Text (TMP) (1)")); - var text = textObject.GetComponent(); - - text.transform.parent = canvas.Find("GameObject").transform; - text.transform.localPosition = new Vector3(200, -170, 0); - text.transform.localScale = Vector3.one; - text.text = "VR Setup Complete!\nYou must restart your game to go into VR!\nIgnore this if you want to play without VR."; - text.autoSizeTextContainer = true; - text.color = new Color(0.9434f, 0.0434f, 0.0434f, 1); - text.alignment = TextAlignmentOptions.Center; - text.fontSize = 18; - text.raycastTarget = false; - } - #if DEBUG internal static bool debugScreenSeen; #endif @@ -281,58 +259,69 @@ private static void OnMainMenuShown(MenuManager __instance) if (__instance.isInitScene) return; - InjectSettingsScreen(); - #if DEBUG InjectDebugScreen(); #endif } - private static void InjectSettingsScreen() + /// + /// Create the VR settings menu when the UI loads + /// + [HarmonyPatch(typeof(SettingsOption), nameof(SettingsOption.OnEnable))] + [HarmonyPostfix] + private static void InjectSettingsMenu(SettingsOption __instance) { - if (Plugin.Config.DisableSettingsButton.Value) + if (Plugin.Config.DisableSettingsButton.Value || __instance.name is not "SetToDefault") return; + + var isInGame = SceneManager.GetActiveScene().name is not "MainMenu"; + if (isInGame && !VRSession.InVR) + return; + + Object.Destroy(__instance); - // Add button to main menu - var container = GameObject.Find("Canvas/MenuContainer"); - var mainButtons = container.Find("MainButtons"); - var settingsObject = new GameObject("LCVRSettings"); - - settingsObject.transform.parent = mainButtons.transform; - settingsObject.transform.localPosition = new Vector3(370, - -130 + (Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig") ? -38.5f : 0f), 0); - settingsObject.transform.localEulerAngles = Vector3.zero; - settingsObject.transform.localScale = Vector3.one * 0.7f; - - var settingsImage = settingsObject.AddComponent(); - var settingsButton = settingsObject.AddComponent public NonNativeKeyboard TerminalKeyboard { get; internal set; } + /// + /// The sprint icon used for toggle sprint + /// + public Image SprintIcon { get; private set; } + private void Awake() { // Create canvasses @@ -145,6 +152,7 @@ private void Awake() // Object scanner: Custom handler var objectScanner = GameObject.Find("ObjectScanner"); objectScanner.transform.parent = null; + objectScanner.transform.localScale = Vector3.one * 0.01f; var globalScanInfo = GameObject.Find("GlobalScanInfo"); @@ -168,7 +176,13 @@ private void Awake() redGlowBodyParts = GameObject.Find("RedGlowBodyParts"); weightUi = GameObject.Find("WeightUI"); pttIcon = GameObject.Find("PTTIcon"); + + sprintIcon = Instantiate(pttIcon, pttIcon.transform.parent); + sprintIcon.name = "SprintIcon"; + sprintIcon.GetComponent().sprite = AssetManager.SprintImage; + SprintIcon = sprintIcon.GetComponent(); + if (Plugin.Config.DisableArmHUD.Value) { selfRed.transform.SetParent(FaceCanvas.transform, false); @@ -177,6 +191,7 @@ private void Awake() redGlowBodyParts.transform.SetParent(FaceCanvas.transform, false); weightUi.transform.SetParent(FaceCanvas.transform, false); pttIcon.transform.SetParent(FaceCanvas.transform, false); + sprintIcon.transform.SetParent(FaceCanvas.transform, false); selfRed.transform.localPosition = self.transform.localPosition = @@ -184,19 +199,14 @@ private void Awake() sprintMeter.transform.localPosition = new Vector3(-284 + xOffset, 80 + yOffset, 0); weightUi.transform.localPosition = new Vector3(-195 + xOffset, 25 + yOffset, 0); pttIcon.transform.localPosition = new Vector3(-195 + xOffset, 165 + yOffset, 0); + sprintIcon.transform.localPosition = new Vector3(-175 + xOffset, 115 + yOffset, 0); - selfRed.transform.localRotation = - self.transform.localRotation = - sprintMeter.transform.localRotation = - redGlowBodyParts.transform.localRotation = - weightUi.transform.localRotation = - pttIcon.transform.localRotation = Quaternion.identity; - - selfRed.transform.localScale = - self.transform.localScale = - sprintMeter.transform.localScale = - redGlowBodyParts.transform.localScale = - pttIcon.transform.localScale = Vector3.one * 2; + ((IEnumerable) [selfRed, self, sprintMeter, redGlowBodyParts, weightUi, pttIcon, sprintIcon]) + .Do(e => + { + e.transform.localRotation = Quaternion.identity; + e.transform.localScale = Vector3.one * 2; + }); weightUi.transform.localScale = Vector3.one; weightUi.transform.Find("Weight").localScale = Vector3.one * 1.4f; @@ -213,6 +223,7 @@ private void Awake() redGlowBodyParts.transform.SetParent(LeftHandCanvas.transform, false); weightUi.transform.SetParent(LeftHandCanvas.transform, false); pttIcon.transform.SetParent(LeftHandCanvas.transform, false); + sprintIcon.transform.SetParent(LeftHandCanvas.transform, false); selfRed.transform.localPosition = self.transform.localPosition = @@ -220,21 +231,14 @@ private void Awake() sprintMeter.transform.localPosition = new Vector3(-50, 100, 72); weightUi.transform.localPosition = new Vector3(-50, 80, 65); pttIcon.transform.localPosition = new Vector3(-50, 145, 35); + sprintIcon.transform.localPosition = new Vector3(-50, 124, 22); - // idk what the official formatting rule is for this kind of code but I guess this looks fine - selfRed.transform.localRotation = - self.transform.localRotation = - sprintMeter.transform.localRotation = - redGlowBodyParts.transform.localRotation = - weightUi.transform.localRotation = - pttIcon.transform.localRotation = Quaternion.Euler(0, 90, 0); - - selfRed.transform.localScale = - self.transform.localScale = - sprintMeter.transform.localScale = - redGlowBodyParts.transform.localScale = - weightUi.transform.localScale = - pttIcon.transform.localScale = Vector3.one; + ((IEnumerable) [selfRed, self, sprintMeter, redGlowBodyParts, weightUi, pttIcon, sprintIcon]) + .Do(e => + { + e.transform.localEulerAngles = new Vector3(0, 90, 0); + e.transform.localScale = Vector3.one; + }); weightUi.transform.Find("Weight").localScale = Vector3.one * 0.7f; } @@ -369,7 +373,13 @@ private void Awake() endgameStats.SetParent(endgameStatsContainer, false); endgameStats.localPosition = Vector3.zero; endgameStats.localRotation = Quaternion.identity; - + + // Meteor Shower Alert + var meteorShowerContainer = specialHud.transform.Find("MeteorShowerWarning"); + meteorShowerContainer.Find("Image/Image").gameObject.SetActive(false); // Remove BG + meteorShowerContainer.localScale = Vector3.one * 0.8f; + meteorShowerContainer.localPosition = Vector3.down * 100; + // Loading Screen: In front of eyes var loadingScreen = GameObject.Find("LoadingText"); @@ -416,6 +426,7 @@ private void Awake() PauseMenuCanvas.worldCamera = GameObject.Find("UICamera").GetComponent(); PauseMenuCanvas.renderMode = RenderMode.WorldSpace; PauseMenuCanvas.transform.position = new Vector3(0, -999, 0); + PauseMenuCanvas.transform.localScale = Vector3.one * 0.01f; var follow = PauseMenuCanvas.gameObject.AddComponent(); follow.sourceTransform = VRSession.Instance.UICamera.transform; @@ -434,6 +445,9 @@ private void Awake() MoveToFront(PitchLockedCanvas); MoveToFront(WorldInteractionCanvas); MoveToFront(objectScanner.transform); + + // Set up belt bag UI + FindObjectOfType(true).gameObject.AddComponent(); } private static void MoveToFront(Component component) @@ -467,9 +481,10 @@ private void LateUpdate() transform.position = camTransform.position; - // Face canvas - FaceCanvas.transform.localPosition = camTransform.forward * 0.5f; - FaceCanvas.transform.rotation = camTransform.rotation; + // Face canvas + FaceCanvas.transform.localPosition = + Vector3.Lerp(FaceCanvas.transform.localPosition, camTransform.forward * 0.5f, 0.4f); + FaceCanvas.transform.rotation = Quaternion.Slerp(FaceCanvas.transform.rotation, camTransform.rotation, 0.4f); // Interaction canvas WorldInteractionCanvas.transform.rotation = @@ -508,6 +523,7 @@ public void HideHUD(bool hide) redGlowBodyParts.SetActive(!hide); weightUi.SetActive(!hide); pttIcon.SetActive(!hide); + sprintIcon.SetActive(!hide); // Keep clock UI for spectators to be able to see the time @@ -559,15 +575,16 @@ public void ToggleSpectatorLight(bool? active = null) private void InitializeKeyboard() { var canvas = GameObject.Find("Systems/UI/Canvas").GetComponent(); - MenuKeyboard = Instantiate(AssetManager.Keyboard).GetComponent(); + MenuKeyboard = Instantiate(AssetManager.Keyboard, canvas.transform).GetComponent(); - MenuKeyboard.transform.SetParent(canvas.transform, false); MenuKeyboard.transform.localPosition = new Vector3(0, -470, -40); MenuKeyboard.transform.localEulerAngles = new Vector3(13, 0, 0); MenuKeyboard.transform.localScale = Vector3.one * 0.8f; MenuKeyboard.gameObject.Find("keyboard_Alpha/Deny_Button").SetActive(false); MenuKeyboard.gameObject.Find("keyboard_Alpha/Confirm_Button").SetActive(false); + + MenuKeyboard.SubmitOnEnter = true; var component = canvas.gameObject.AddComponent(); component.keyboard = MenuKeyboard; diff --git a/Source/Utils.cs b/Source/Utils.cs index 5079dcde..437b9094 100644 --- a/Source/Utils.cs +++ b/Source/Utils.cs @@ -10,7 +10,6 @@ using System.Text; using System; using System.Collections; -using System.IO; using GameNetcodeStuff; namespace LCVR; @@ -146,26 +145,26 @@ private static void AddActionBasedControllerBinds(this ActionBasedController con controller.trackingStateAction = new InputActionProperty(hand.TrackingState()); controller.enableInputActions = actionsEnabled; - controller.selectAction = new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Select")); + controller.selectAction = new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Select")); controller.selectActionValue = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Select Value")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Select Value")); controller.activateAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Activate")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Activate")); controller.activateActionValue = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Activate Value")); - controller.uiPressAction = new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/UI Press")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Activate Value")); + controller.uiPressAction = new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/UI Press")); controller.uiPressActionValue = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/UI Press Value")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/UI Press Value")); controller.uiScrollAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/UI Scroll")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/UI Scroll")); controller.rotateAnchorAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Rotate Anchor")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Rotate Anchor")); controller.translateAnchorAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Translate Anchor")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Translate Anchor")); controller.scaleToggleAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Scale Toggle")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Scale Toggle")); controller.scaleDeltaAction = - new InputActionProperty(AssetManager.TrackingActions.FindAction($"{hand}/Scale Delta")); + new InputActionProperty(AssetManager.DefaultXRActions.FindAction($"{hand}/Scale Delta")); } public static bool Raycast(this Ray ray, out RaycastHit hit, float maxDistance = Mathf.Infinity, @@ -174,13 +173,6 @@ public static bool Raycast(this Ray ray, out RaycastHit hit, float maxDistance = return UnityEngine.Physics.Raycast(ray, out hit, maxDistance, layerMask); } - public static bool BoxCast(this Ray ray, float radius, out RaycastHit hit, float maxDistance = Mathf.Infinity, - int layerMask = UnityEngine.Physics.DefaultRaycastLayers) - { - return UnityEngine.Physics.BoxCast(ray.origin, Vector3.one * radius, ray.direction, out hit, - Quaternion.identity, maxDistance, layerMask); - } - public enum Hand { Left, @@ -229,19 +221,4 @@ public static IEnumerator NopRoutine() { yield break; } -} - -public static class BinaryReaderExtensions -{ - public static BinaryReader Clone(this BinaryReader reader) - { - var mem = new MemoryStream(); - var pos = reader.BaseStream.Position; - - reader.BaseStream.CopyTo(mem); - reader.BaseStream.Position = pos; - mem.Position = 0; - - return new BinaryReader(mem); - } } \ No newline at end of file