├── .gitignore ├── FUNDING.yml ├── Properties └── AssemblyInfo.cs ├── README.md ├── TrackingRotator.csproj ├── TrackingRotator.sln ├── TrackingRotatorMod.cs ├── Utils ├── AMAPIManager.cs └── UIXManager.cs └── trackingrotator /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | *.user -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: nitrog0d -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using TrackingRotator; 5 | 6 | [assembly: ComVisible(false)] 7 | [assembly: Guid("652febb3-e7bd-4f5c-8ac4-51e36b19bc9d")] 8 | [assembly: AssemblyTitle(ModBuildInfo.Name)] 9 | [assembly: AssemblyProduct(ModBuildInfo.Name)] 10 | [assembly: AssemblyCopyright("Created by " + ModBuildInfo.Author)] 11 | [assembly: AssemblyVersion(ModBuildInfo.Version)] 12 | [assembly: AssemblyFileVersion(ModBuildInfo.Version)] 13 | [assembly: MelonInfo(typeof(TrackingRotatorMod), ModBuildInfo.Name, ModBuildInfo.Version, ModBuildInfo.Author, ModBuildInfo.DownloadLink)] 14 | [assembly: MelonGame(ModBuildInfo.GameDeveloper, ModBuildInfo.Game)] 15 | [assembly: MelonOptionalDependencies("UIExpansionKit", "ActionMenuApi")] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrackingRotator 2 | [![GitHub All Releases](https://img.shields.io/github/downloads/nitrog0d/TrackingRotator/total?style=for-the-badge)](https://github.com/nitrog0d/TrackingRotator/releases) 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nitrog0d/TrackingRotator?style=for-the-badge)](https://github.com/nitrog0d/TrackingRotator/releases/latest) 4 | [![Support me on Patreon](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F1177520&query=data.attributes.patron_count&suffix=%20Patrons&color=FF5441&label=Patreon&logo=Patreon&logoColor=FF5441&style=for-the-badge)](https://patreon.com/nitrog0d) 5 | 6 | Video preview soon. 7 | 8 | A mod that lets you rotate your tracking, it was made with "play while laying down" in mind. 9 | * UIExpansionKit Integration, first releases and actual algorithm by [nitro.](https://github.com/nitrog0d) 10 | * AssetBundle, AMAPI Integration and Integrations' Management/Organization by [Davi](https://github.com/d-mageek). 11 | 12 | * **Warning:** The VRChat team is not very keen on modding or reverse engineering the game, while the mod does not include anything that would ruin the fun for others, using it may still be a bannable offence. 13 | 14 | * **USE IT AT YOUR OWN RISK**, we are not responsible for any bans or any punishments you may get by using this mod! 15 | 16 | ## Installation 17 | * **Make sure you have run the [MelonLoader Installer](https://github.com/LavaGang/MelonLoader.Installer/releases/latest/download/MelonLoader.Installer.exe) by [LavaGang](https://github.com/LavaGang) first (feel free to join the [VRChat Modding Group Discord](https://discord.gg/jgvc9Fd) for help!).** 18 | * Download the [latest version](https://github.com/nitrog0d/TrackingRotator/releases/latest/download/TrackingRotator.dll) of the mod. 19 | * **Install (at least one is required):** 20 | * [UIExpansionKit](https://github.com/knah/VRCMods) by [knah](https://github.com/knah), or/and 21 | * [ActionMenuApi](https://github.com/gompocp/ActionMenuApi) by [gompo](https://github.com/gompocp). 22 | * Drag/copy the DLL files that you have downloaded into the Mods folder. 23 | * That's it! Now just run the game and the mod should be installed! 24 | 25 | ## How to use 26 | * Open the Quick menu and click the "Tracking rotation" button. 27 | * You can change the rotation value, high precision value and "Reset rotation when a new world loads" in Mod settings (Quick menu -> Settings -> Mod settings). -------------------------------------------------------------------------------- /TrackingRotator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D} 8 | Library 9 | Properties 10 | TrackingRotator 11 | TrackingRotator 12 | v4.7.2 13 | 512 14 | true 15 | D:\Jogos\SteamLibrary\steamapps\common\VRChat\MelonLoader 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | $(MelonLoaderPath)\..\Mods\ActionMenuApi.dll 43 | 44 | 45 | $(MelonLoaderPath)\Managed\Assembly-CSharp.dll 46 | 47 | 48 | $(MelonLoaderPath)\Managed\Il2Cppmscorlib.dll 49 | 50 | 51 | $(MelonLoaderPath)\MelonLoader.dll 52 | 53 | 54 | $(MelonLoaderPath)\..\Mods\UIExpansionKit.dll 55 | 56 | 57 | $(MelonLoaderPath)\Managed\UnhollowerBaseLib.dll 58 | 59 | 60 | $(MelonLoaderPath)\Managed\UnityEngine.AssetBundleModule.dll 61 | 62 | 63 | $(MelonLoaderPath)\Managed\UnityEngine.CoreModule.dll 64 | 65 | 66 | 67 | 68 | 69 | xcopy /f /y "$(TargetPath)" "$(MelonLoaderPath)\..\Mods\$(TargetFileName)*" 70 | 71 | -------------------------------------------------------------------------------- /TrackingRotator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31005.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrackingRotator", "TrackingRotator.csproj", "{652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {652FEBB3-E7BD-4F5C-8AC4-51E36B19BC9D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {184DD356-DD8B-4738-9E83-AE05BC75CF89} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TrackingRotatorMod.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | using UnityEngine; 3 | using System.Linq; 4 | using UnhollowerRuntimeLib; 5 | using System.Collections; 6 | using TrackingRotator.Utils; 7 | using Il2CppSystem.Reflection; 8 | 9 | namespace TrackingRotator 10 | { 11 | public static class ModBuildInfo { 12 | public const string Name = "TrackingRotator"; 13 | public const string Author = "Davi & nitro."; // <3 14 | public const string Version = "1.0.2"; 15 | public const string DownloadLink = "https://github.com/nitrog0d/TrackingRotator/releases/latest/download/TrackingRotator.dll"; 16 | public const string GameDeveloper = "VRChat"; 17 | public const string Game = "VRChat"; 18 | } 19 | 20 | public class TrackingRotatorMod : MelonMod 21 | { 22 | 23 | private const string ModCategory = "TrackingRotator"; 24 | private const string UIXIntegration = "UIXIntegration"; 25 | private const string AMAPIIntegration = "AMAPIIntegration"; 26 | private const string RotationValuePref = "RotationValue"; 27 | private const string HighPrecisionRotationValuePref = "HighPrecisionRotationValue"; 28 | private const string ResetRotationOnSceneChangePref = "ResetRotationOnSceneChange"; 29 | 30 | private static float rotationValue = 0f; 31 | private static float highPrecisionRotationValue = 0f; 32 | private static bool resetRotationOnSceneChange, IsUsingUIX, IsUsingAMAPI = false; 33 | private static bool UIXintegration, AMAPIintegration = true; 34 | 35 | public static bool highPrecision = false; 36 | public static Transform transform; 37 | public static Transform cameraTransform; 38 | public static Quaternion originalRotation; 39 | 40 | public override void OnApplicationStart() 41 | { 42 | MelonPreferences.CreateCategory(ModCategory, "Tracking Rotator"); 43 | MelonPreferences.CreateEntry(ModCategory, UIXIntegration, true, "Integrate with UiExpansionKit?"); 44 | MelonPreferences.CreateEntry(ModCategory, AMAPIIntegration, true, "Integrate with Action Menu?"); 45 | MelonPreferences.CreateEntry(ModCategory, RotationValuePref, 22.5f, "Rotation value"); 46 | MelonPreferences.CreateEntry(ModCategory, HighPrecisionRotationValuePref, 1f, "High precision rotation value"); 47 | MelonPreferences.CreateEntry(ModCategory, ResetRotationOnSceneChangePref, false, "Reset rotation when a new world loads"); 48 | OnPreferencesSaved(); 49 | 50 | Integrations(); 51 | 52 | MelonLogger.Msg("Mod loaded."); 53 | } 54 | 55 | public override void OnPreferencesSaved() 56 | { 57 | UIXintegration = MelonPreferences.GetEntryValue(ModCategory, UIXIntegration); 58 | AMAPIintegration = MelonPreferences.GetEntryValue(ModCategory, AMAPIIntegration); 59 | rotationValue = MelonPreferences.GetEntryValue(ModCategory, RotationValuePref); 60 | highPrecisionRotationValue = MelonPreferences.GetEntryValue(ModCategory, HighPrecisionRotationValuePref); 61 | resetRotationOnSceneChange = MelonPreferences.GetEntryValue(ModCategory, ResetRotationOnSceneChangePref); 62 | } 63 | 64 | 65 | public override void OnSceneWasLoaded(int buildIndex, string sceneName) 66 | { 67 | if (resetRotationOnSceneChange && cameraTransform) cameraTransform.localRotation = originalRotation; 68 | } 69 | 70 | public static IEnumerator WaitForUiInit() 71 | { 72 | while (Object.FindObjectOfType() == null) 73 | yield return null; 74 | 75 | var camera = Object.FindObjectOfType(); 76 | var Transform = camera.GetIl2CppType().GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.FieldType == Il2CppType.Of()).ToArray()[0]; 77 | cameraTransform = Transform.GetValue(camera).Cast(); 78 | originalRotation = cameraTransform.localRotation; 79 | transform = Camera.main.transform; 80 | 81 | if (IsUsingAMAPI) typeof(AMAPIManager).GetMethod("ActionMenuIntegration").Invoke(null, null); 82 | } 83 | 84 | public static void Move(Vector3 direction) 85 | { 86 | cameraTransform.Rotate(direction, highPrecision ? highPrecisionRotationValue : rotationValue, Space.World); 87 | } 88 | 89 | private static void Integrations() 90 | { 91 | if (AMAPIintegration) 92 | { 93 | if (MelonHandler.Mods.Any(x => x.Info.Name.Equals("ActionMenuApi"))) 94 | { 95 | Assets.OnApplicationStart(); 96 | IsUsingAMAPI = true; 97 | } 98 | else MelonLogger.Warning("For a better experience, please consider using ActionMenuApi."); 99 | } 100 | else MelonLogger.Warning("Integration with ActionMenuApi has been deactivated on Settings."); 101 | 102 | if (UIXintegration) 103 | { 104 | if (MelonHandler.Mods.Any(x => x.Info.Name.Equals("UI Expansion Kit"))) 105 | { 106 | typeof(UIXManager).GetMethod("OnApplicationStart").Invoke(null, null); 107 | IsUsingUIX = true; 108 | } 109 | else MelonLogger.Warning("For a better experience, please consider using UIExpansionKit."); 110 | } 111 | else MelonLogger.Warning("Integration with UIExpansionKit has been deactivated on Settings."); 112 | 113 | if (!AMAPIintegration && !UIXintegration) 114 | MelonLogger.Warning("Both integrations (Action Menu and UiExpansionKit) have been deactivated. " + 115 | "The mod cannot run without those, therefore, expect it to fail. If this was not intended, " + 116 | "please consider activating at least one of the integrations on Settings."); 117 | 118 | if (!IsUsingAMAPI && !IsUsingUIX) 119 | { 120 | MelonLogger.Error("Failed to load both integrations with UIExpansionKit and ActionMenuApi! The mod will not be loaded."); 121 | } 122 | else MelonCoroutines.Start(WaitForUiInit()); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Utils/AMAPIManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ActionMenuApi.Api; 3 | using UnhollowerRuntimeLib; 4 | using System.Collections; 5 | using MelonLoader; 6 | using System.IO; 7 | using System.Reflection; 8 | using static TrackingRotator.TrackingRotatorMod; 9 | 10 | namespace TrackingRotator.Utils 11 | { 12 | internal static class AMAPIManager 13 | { 14 | public static void ActionMenuIntegration() 15 | { 16 | VRCActionMenuPage.AddSubMenu(ActionMenuPage.Main, "Tracking Rotator", () => 17 | { 18 | CustomSubMenu.AddButton("Forward", () => Move(transform.right), Assets.Forward); //X+ 19 | CustomSubMenu.AddButton("Backward", () => Move(-transform.right), Assets.Backward); //X- 20 | CustomSubMenu.AddButton("Tilt Left", () => Move(transform.forward), Assets.TLeft); //Z+ 21 | CustomSubMenu.AddButton("Tilt Right", () => Move(-transform.forward), Assets.TRight); //Z- 22 | CustomSubMenu.AddButton("Left", () => Move(-transform.up), Assets.Left); //Y- 23 | CustomSubMenu.AddButton("Right", () => Move(transform.up), Assets.Right); //Y+ 24 | 25 | CustomSubMenu.AddSubMenu("Other", () => 26 | { 27 | CustomSubMenu.AddButton("Reset", () => cameraTransform.localRotation = originalRotation, Assets.Reset); 28 | CustomSubMenu.AddToggle("High precision", highPrecision, b => highPrecision = b, Assets.HP); 29 | }, Assets.Other); 30 | }, Assets.Main); 31 | } 32 | } 33 | 34 | // The code below was based on Lily's... 35 | // https://github.com/KortyBoi/VRChat-TeleporterVR/blob/main/Utils/ResourceManager.cs 36 | // And also knah's! 37 | // https://github.com/knah/VRCMods/blob/master/UIExpansionKit 38 | internal static class Assets 39 | { 40 | private static AssetBundle Bundle; 41 | public static Texture2D Main, Forward, Backward, TLeft, TRight, Left, Right, Other, Reset, HP; 42 | 43 | public static void OnApplicationStart() { MelonCoroutines.Start(LoadAssets()); } 44 | 45 | private static IEnumerator LoadAssets() 46 | { 47 | try 48 | { 49 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TrackingRotator.trackingrotator")) 50 | { 51 | using (var memoryStream = new MemoryStream((int)stream.Length)) 52 | { 53 | stream.CopyTo(memoryStream); 54 | Bundle = AssetBundle.LoadFromMemory_Internal(memoryStream.ToArray(), 0); 55 | Bundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 56 | try { Main = LoadTexture("Main.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Main.png"); } 57 | try { Forward = LoadTexture("Forward.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Forward.png"); } 58 | try { Backward = LoadTexture("Backward.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Backward.png"); } 59 | try { TLeft = LoadTexture("TLeft.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: TLeft.png"); } 60 | try { TRight = LoadTexture("TRight.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: TRight.png"); } 61 | try { Left = LoadTexture("Left.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Left.png"); } 62 | try { Right = LoadTexture("Right.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Right.png"); } 63 | try { Other = LoadTexture("Other.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Other.png"); } 64 | try { Reset = LoadTexture("Reset.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: Reset.png"); } 65 | try { HP = LoadTexture("HP.png"); } catch { MelonLogger.Error("Failed to load image from asset bundle: HP.png"); } 66 | } 67 | } 68 | } catch { MelonLogger.Warning("Failed to load AssetBundle! ActionMenuApi will have its icons completely broken."); } 69 | yield break; 70 | } 71 | 72 | private static Texture2D LoadTexture(string Texture) 73 | { 74 | Texture2D Texture2 = Bundle.LoadAsset_Internal(Texture, Il2CppType.Of()).Cast(); 75 | Texture2.hideFlags |= HideFlags.DontUnloadUnusedAsset; 76 | Texture2.hideFlags = HideFlags.HideAndDontSave; 77 | return Texture2; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Utils/UIXManager.cs: -------------------------------------------------------------------------------- 1 | using UIExpansionKit.API; 2 | using static TrackingRotator.TrackingRotatorMod; 3 | 4 | namespace TrackingRotator.Utils 5 | { 6 | internal static class UIXManager 7 | { 8 | public static void OnApplicationStart() => ExpansionKitApi.GetExpandedMenu(ExpandedMenu.QuickMenu).AddSimpleButton("Tracking rotation", ShowRotationMenu); 9 | 10 | // Based on knah's ViewPointTweaker mod, https://github.com/knah/VRCMods/blob/master/ViewPointTweaker 11 | private static ICustomShowableLayoutedMenu rotationMenu = null; 12 | private static void ShowRotationMenu() 13 | { 14 | if (rotationMenu == null) 15 | { 16 | rotationMenu = ExpansionKitApi.CreateCustomQuickMenuPage(LayoutDescription.QuickMenu4Columns); 17 | 18 | rotationMenu.AddSpacer(); 19 | rotationMenu.AddSimpleButton("Forward", () => Move(transform.right)); 20 | rotationMenu.AddSpacer(); 21 | rotationMenu.AddSpacer(); 22 | 23 | rotationMenu.AddSimpleButton("Tilt Left", () => Move(transform.forward)); 24 | rotationMenu.AddSimpleButton("Reset", () => cameraTransform.localRotation = originalRotation); 25 | rotationMenu.AddSimpleButton("Tilt Right", () => Move(-transform.forward)); 26 | rotationMenu.AddSpacer(); 27 | 28 | rotationMenu.AddSpacer(); 29 | rotationMenu.AddSimpleButton("Backward", () => Move(-transform.right)); 30 | rotationMenu.AddSimpleButton("Left", () => Move(-transform.up)); 31 | rotationMenu.AddSimpleButton("Right", () => Move(transform.up)); 32 | 33 | rotationMenu.AddToggleButton("High precision", b => highPrecision = b, () => highPrecision); 34 | rotationMenu.AddSpacer(); 35 | rotationMenu.AddSpacer(); 36 | rotationMenu.AddSimpleButton("Back", rotationMenu.Hide); 37 | } 38 | 39 | rotationMenu.Show(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /trackingrotator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrog0d/TrackingRotator/dd0632253089303ede84f4aecedbdf950b1a2052/trackingrotator --------------------------------------------------------------------------------