├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── AMAPI.md ├── ActionMenuApi ├── ActionMenuApi.cs ├── ActionMenuApi.csproj ├── Api │ ├── AMUtils.cs │ ├── ActionMenuPage.cs │ ├── CustomSubMenu.cs │ └── VRCActionMenuPage.cs ├── BuildInfo.cs ├── Helpers │ ├── Constants.cs │ ├── ExtensionMethods.cs │ └── Utilities.cs ├── Logger.cs ├── Managers │ ├── FourAxisPuppetManager.cs │ ├── InputManager.cs │ ├── ModsFolderManager.cs │ ├── RadialPuppetManager.cs │ └── ResourcesManager.cs ├── Patches.cs ├── Pedals │ ├── PedalButton.cs │ ├── PedalFourAxis.cs │ ├── PedalRadial.cs │ ├── PedalStruct.cs │ ├── PedalSubMenu.cs │ └── PedalToggle.cs ├── Types │ ├── ActionMenuHand.cs │ ├── ActionMenuPageType.cs │ ├── Insertion.cs │ └── PedalType.cs └── actionmenuapi.icons ├── ActionMenuTestMod ├── ActionMenuTestMod.cs ├── ActionMenuTestMod.csproj └── customicons ├── ActionMenuUtils ├── ActionMenuUtils.csproj ├── BuildInfo.cs ├── Main.cs ├── ModSettings.cs ├── Utils.cs └── icons ├── Assets └── preview.gif ├── CHANGELOG.md ├── Common ├── _dummy2_.dll └── _dummy_.dll ├── Directory.Build.props ├── LICENSE ├── Libs └── .here ├── README.md ├── RELEASE.md ├── StandaloneThirdPerson ├── BuildInfo.cs ├── CameraBehindMode.cs ├── CameraMode.cs ├── Main.cs ├── ModSettings.cs ├── QMEnableDisableListener.cs ├── StandaloneThirdPerson.csproj └── Utils.cs ├── Tools ├── ILRepack │ ├── ILRepack.Lib.MSBuild.Task.dll │ ├── ILRepack.Lib.MSBuild.Task.targets │ └── ILRepack.dll ├── IntegrityCheckGenerator │ ├── IntegrityCheckGenerator.cs │ └── IntegrityCheckGenerator.csproj ├── IntegrityCheckWeaver │ ├── DummyThree.cs │ ├── IntegrityCheckWeaver.csproj │ ├── Program.cs │ └── Utils.cs ├── MDBGenerator │ ├── MDBGenerator.csproj │ └── Program.cs └── ModJsonGenerator │ ├── Mod.cs │ ├── ModJsonGenerator.csproj │ ├── ModJsonInfoAttribute.cs │ └── Program.cs ├── UpdateChecker ├── BuildInfo.cs ├── Main.cs ├── Mod.cs ├── ModVersion.cs └── UpdateChecker.csproj ├── VRChatMods.sln └── WorldPredownload ├── BuildInfo.cs ├── Cache └── CacheManager.cs ├── Constants.cs ├── DownloadManager ├── DownloadComplete.cs ├── DownloadInfo.cs ├── DownloadProgress.cs ├── DownloadType.cs └── WorldDownloadManager.cs ├── Helpers ├── Delegates.cs ├── ExtensionMethods.cs ├── UserAgent.cs ├── Utilities.cs └── XRefUtils.cs ├── Main.cs ├── ModSettings.cs ├── Patches.cs ├── UI ├── FriendButton.cs ├── HudIcon.cs ├── InviteButton.cs ├── LoadingScreen.cs ├── PortalButton.cs ├── WorldButton.cs └── WorldDownloadStatus.cs ├── WorldPredownload.csproj └── gompowpd /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | on: 3 | push: 4 | branches: [ master ] 5 | create: 6 | tags: 7 | - '*' 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Checkout VRCLibs 14 | uses: actions/checkout@v2 15 | with: 16 | repository: ${{ secrets.VRCLibsRepo }} 17 | token: ${{ secrets.VRCLibsRepoToken }} 18 | path: Libs 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: 6.0.x 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Build 29 | run: dotnet build --no-restore --configuration release 30 | 31 | - name: Create Release 32 | uses: softprops/action-gh-release@v1 33 | if: startsWith(github.ref, 'refs/tags/') 34 | with: 35 | body_path: Release.md 36 | files: | 37 | Output/Release/ActionMenuApi.dll 38 | Output/Release/ActionMenuApi.dll.mdb 39 | Output/Release/ActionMenuApi.xml 40 | Output/Release/ActionMenuTestMod.dll 41 | Output/Release/ActionMenuTestMod.mdb 42 | Output/Release/ActionMenuUtils.dll 43 | Output/Release/ActionMenuUtils.dll.mdb 44 | Output/Release/StandaloneThirdPerson.dll 45 | Output/Release/StandaloneThirdPerson.dll.mdb 46 | Output/Release/UpdateChecker.dll 47 | Output/Release/UpdateChecker.dll.mdb 48 | Output/Release/WorldPredownload.dll 49 | Output/Release/WorldPredownload.dll.mdb 50 | 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Output 2 | obj/ 3 | .vs/ 4 | .idea 5 | ModJsons/ 6 | packages/ 7 | *.user 8 | Dev 9 | LibsAndroid -------------------------------------------------------------------------------- /AMAPI.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![License][license-shield]][license-url] 4 | 5 | 6 |
7 |

8 | 9 |

ActionMenuApi

10 | 11 |

12 |
13 | Request Feature 14 |

15 | 16 | 17 | 18 | 19 | 20 |
21 |

Table of Contents

22 |
    23 |
  1. 24 | Info 25 |
  2. 26 |
  3. 27 | Getting Started 28 |
  4. 29 |
  5. Usage
  6. 30 |
  7. License
  8. 31 |
  9. Acknowledgements
  10. 32 |
33 |
34 | 35 | 36 | 37 | 38 | ## Info 39 | 40 | Preview 41 | 42 | 43 | This mod doesn't do anything on it's own. 44 | 45 | It provides an easy way for modders to add integration with the action menu. 46 | 47 | It supports the use of the 48 | 49 | * Radial Puppet 50 | * Four Axis Puppet 51 | * Button 52 | * Toggle Button 53 | * Sub Menus 54 | 55 | Additionally allows mods to add their menus to a dedicated section on the action menu to prevent clutter. 56 | 57 | ## Getting Started 58 | 59 | To use simply add ActionMenuApi to your mods folder and reference it in your project same way as with UIX 60 | 61 | 62 | 63 | ## Usage 64 | 65 | ```cs 66 | using ActionMenuApi.Api; 67 | using ActionMenuApi.Pedals; 68 | /* 69 | 70 | Code 71 | 72 | */ 73 | 74 | //Call in OnApplicationStart() 75 | //To add a button to the main page of the action menu 76 | // Page to add to Action for onClick Text Texture locked 77 | VRCActionMenuPage.AddButton(ActionMenuPage.Main, "Button",() => MelonLogger.Msg("Pressed Button"), buttonIcon true); 78 | 79 | //To add a toggle to the main page of the action menu 80 | VRCActionMenuPage.AddToggle(ActionMenuPage.Main,"Toggle", testBool, b => testBool = b, toggleIcon); 81 | 82 | //To add a radial pedal to the main page of the action menu 83 | VRCActionMenuPage.AddRadialPuppet(ActionMenuPageType.Main, "Radial",f => testFloatValue = f, testFloatValue, radialIcon); 84 | 85 | //To add a submenu to the main page of the action menu and add a toggle and button to it 86 | VRCActionMenuPage.AddSubMenu(ActionMenuPageType.Main, 87 | "Sub Menu", 88 | delegate { 89 | MelonLogger.Msg("Sub Menu Opened"); 90 | CustomSubMenu.AddButton("Pressed Button In Sub Menu", () => MelonLogger.Msg("Pressed Button In Sub Menu"), buttonIcon); 91 | CustomSubMenu.AddToggle("Sub Menu Toggle",testBool2, b => testBool2 = b, toggleIcon); 92 | }, 93 | subMenuIcon 94 | ); 95 | ``` 96 | 97 | When you lock/update a pedal in anyway, you must call either `AMUtils.ResetMenu()` or `AMUtils.RefreshMenu()` so that these changes will be visible. If you are after locking a submenu its advised that you call `AMUtils.ResetMenu()` so that in case the user is already in the submenu it'll pushed them out of it. If you are just locking a button pedal or something, you can just call `AMUtils.RefreshMenu()` to rebuild the current action menu submenu. 98 | 99 | > NOTE FOR PEOPLE USING THE LOCKING FUNCTIONALITY FOR RISKY FUNCTIONS: It is advised that in the case that my reflection to reset/refresh the action menu fails you have a boolean check in the pedal trigger event so that the action can't run anyway if it fails 100 | 101 | 102 | _For a mod example check out the test mod [here](https://github.com/gompocp/ActionMenuApi/tree/main/ActionMenuTestMod)_ 103 | 104 | 105 | 106 | ## License 107 | 108 | Distributed under the GPL-3.0 License. See `LICENSE` for more information. 109 | 110 | 111 | 112 | Project Link: [https://github.com/gompoc/VRChatMods](https://github.com/gompoc/VRChatMods) 113 | 114 | 115 | ## Acknowledgements 116 | 117 | * XRef method from [BenjaminZehowlt](https://github.com/BenjaminZehowlt/DynamicBonesSafety/blob/master/DynamicBonesSafetyMod.cs) 118 | * [Knah](https://github.com/knah/VRCMods/) assetbundle loading example and his solution structure 119 | 120 | 121 | [license-shield]: https://img.shields.io/github/license/gompoc/VRChatMods.svg?style=for-the-badge 122 | [license-url]: https://github.com/gompoc/VRChatMods/blob/main/LICENSE 123 | -------------------------------------------------------------------------------- /ActionMenuApi/ActionMenuApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using ActionMenuApi.Managers; 4 | using MelonLoader; 5 | 6 | #pragma warning disable 1591 7 | 8 | namespace ActionMenuApi 9 | { 10 | public partial class ActionMenuApi : MelonMod 11 | { 12 | 13 | public override void OnApplicationStart() 14 | { 15 | ResourcesManager.LoadTextures(); 16 | MelonCoroutines.Start(WaitForActionMenuInit()); 17 | try 18 | { 19 | Patches.PatchAll(HarmonyInstance); 20 | } 21 | catch (Exception e) 22 | { 23 | MelonLogger.Error($"Patching failed with exception: {e.Message}"); 24 | } 25 | } 26 | 27 | private IEnumerator WaitForActionMenuInit() 28 | { 29 | while (ActionMenuDriver.prop_ActionMenuDriver_0 == null) //VRCUIManager Init is too early 30 | yield return null; 31 | if (string.IsNullOrEmpty(ID)) yield break; 32 | ResourcesManager.InitLockGameObject(); 33 | RadialPuppetManager.Setup(); 34 | FourAxisPuppetManager.Setup(); 35 | } 36 | 37 | public override void OnUpdate() 38 | { 39 | RadialPuppetManager.OnUpdate(); 40 | FourAxisPuppetManager.OnUpdate(); 41 | } 42 | 43 | private static string ID = "gompo"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ActionMenuApi/ActionMenuApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net472 4 | true 5 | true 6 | true 7 | false 8 | true 9 | 10 | 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ActionMenuApi/Api/AMUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Managers; 4 | using MelonLoader; 5 | using UnityEngine; 6 | 7 | namespace ActionMenuApi.Api 8 | { 9 | /// 10 | /// General Action Menu Things 11 | /// 12 | public static class AMUtils 13 | { 14 | /// 15 | /// Trigger a refresh for the action menus 16 | /// 17 | public static void RefreshActionMenu() 18 | { 19 | try 20 | { 21 | Utilities.RefreshAM(); 22 | } 23 | catch (Exception e) 24 | { 25 | MelonLogger.Warning( 26 | $"Refresh failed (oops). This may or may not be an oof if another exception immediately follows after this exception: {e}"); 27 | //This is semi-abusable if this fails so its probably a good idea to have a fail-safe to protect sensitive functions that are meant to be locked 28 | Utilities.ResetMenu(); 29 | } 30 | } 31 | 32 | /// 33 | /// Trigger a complete reset for the action menus 34 | /// 35 | public static void ResetMenu() 36 | { 37 | Utilities.ResetMenu(); 38 | } 39 | 40 | /// 41 | /// Add a mod to a dedicated section of the action menu with other mods 42 | /// 43 | /// Button text 44 | /// 45 | /// Function called when your mod page is opened. Add your methods calls to other AMAPI methods such 46 | /// AddRadialPedalToSubMenu to add buttons to the submenu it creates when clicked 47 | /// 48 | /// (optional) The Button Icon 49 | /// (optional) Starting state of pedal 50 | public static void AddToModsFolder(string text, Action openFunc, Texture2D icon = null, bool locked = false) 51 | { 52 | ModsFolderManager.AddMod(() => { CustomSubMenu.AddSubMenu(text, openFunc, icon, locked); }); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /ActionMenuApi/Api/ActionMenuPage.cs: -------------------------------------------------------------------------------- 1 | namespace ActionMenuApi.Api 2 | { 3 | /// 4 | /// Supported existing vrchat pages that you can add pedals to 5 | /// 6 | public enum ActionMenuPage 7 | { 8 | /// 9 | /// The more "advanced" options in the action menu. Can change menu opacity, size, position etc. here 10 | /// 11 | Config, 12 | 13 | /// 14 | /// The page that shows when you open the emojis page of the action menu 15 | /// 16 | Emojis, 17 | 18 | /// 19 | /// The page that shows when you open the expression page of the action menu using an sdk3 avatar 20 | /// 21 | Expression, 22 | 23 | /// 24 | /// The page that shows when you open the expression page of the action menu using an sdk2 avatar. Has emotes 1-8 25 | /// 26 | SDK2Expression, 27 | 28 | /// 29 | /// The default page that shows when you open the action menu 30 | /// 31 | Main, 32 | 33 | /// 34 | /// The menu opacity page. Has 25%, 50%, 75%, 100% 35 | /// 36 | MenuOpacity, 37 | 38 | /// 39 | /// The menu size page. Has Small, Medium, Large 40 | /// 41 | MenuSize, //Not Implemented 42 | 43 | /// 44 | /// The nameplates config page of the action menu 45 | /// 46 | Nameplates, 47 | 48 | /// 49 | /// The nameplates opacity page 0%,20%,40%,60%,80%,100% 50 | /// 51 | NameplatesOpacity, 52 | 53 | /// 54 | /// The nameplates visibility page shown,icons only,hidden 55 | /// 56 | NameplatesVisibilty, 57 | 58 | /// 59 | /// The nameplates size page tiny,small,normal,medium,large 60 | /// 61 | NameplatesSize, 62 | 63 | /// 64 | /// The options page, toggle mic, close menu etc. 65 | /// 66 | Options, 67 | 68 | /// 69 | /// Mods page (duh?) 70 | /// 71 | Mods 72 | } 73 | } -------------------------------------------------------------------------------- /ActionMenuApi/BuildInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ActionMenuApi; 3 | using MelonLoader; 4 | using ModJsonGenerator; 5 | 6 | 7 | 8 | [assembly: MelonGame("VRChat", "VRChat")] 9 | [assembly: MelonInfo(typeof(ActionMenuApi.ActionMenuApi), ModConstants.NAME, ModConstants.VERSION, ModConstants.AUTHOR, ModConstants.DOWNLOAD_LINK)] 10 | [assembly: ModJsonInfo( 11 | 201, 12 | "This mod doesn't do anything on it's own. \n" + 13 | "It provides an easy way for modders to add integration with the action menu.\n" + 14 | "It supports the use of the:\n" + 15 | "- Radial Puppet\n" + 16 | "- Four Axis Puppet\n" + 17 | "- Button\n" + 18 | "- Toggle Button\n" + 19 | "- Sub Menus\n" + 20 | "\n" + 21 | "Additionally allows mods to add their menus to a dedicated section on the action menu to prevent clutter.\n" + 22 | "Example mod and documentation can be found on github", 23 | new []{"action menu", "api", "radial menu"}, 24 | null, 25 | "TODO", 26 | "#2ad9f7" 27 | ) 28 | ] 29 | [assembly:AssemblyVersion(ModConstants.VERSION)] 30 | [assembly:AssemblyFileVersion(ModConstants.VERSION)] 31 | [assembly:AssemblyTitle(ModConstants.NAME)] 32 | [assembly:AssemblyDescription(ModConstants.NAME)] 33 | [assembly:AssemblyCopyright("Created by " + ModConstants.AUTHOR)] 34 | 35 | namespace ActionMenuApi; 36 | public static class ModConstants 37 | { 38 | public const string VERSION = "1.0.0"; 39 | public const string NAME = "ActionMenuApi"; 40 | public const string AUTHOR = "gompo"; 41 | public const string DOWNLOAD_LINK = "https://github.com/gompoc/VRChatMods/releases/"; 42 | } -------------------------------------------------------------------------------- /ActionMenuApi/Helpers/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace ActionMenuApi.Helpers 2 | { 3 | internal static class Constants 4 | { 5 | public const int MAX_PEDALS_PER_PAGE = 7; 6 | public const float PI = 3.14159274f; 7 | public const float RAD_TO_DEG = 180 / PI; 8 | public const string LOCKED_PEDAL_OVERLAY_GAMEOBJECT_NAME = "AMAPI Lock Icon"; 9 | public const string MODS_FOLDER_NAME = "Mods"; 10 | public const string LEFT_HORIZONTAL = "Horizontal"; 11 | public const string LEFT_TRIGGER = "Oculus_CrossPlatform_PrimaryIndexTrigger"; 12 | public const string LEFT_VERTICAL = "Vertical"; 13 | public const string RIGHT_HORIZONTAL = "Oculus_CrossPlatform_SecondaryThumbstickHorizontal"; 14 | public const string RIGHT_TRIGGER = "Oculus_CrossPlatform_SecondaryIndexTrigger"; 15 | public const string RIGHT_VERTICAL = "Oculus_CrossPlatform_SecondaryThumbstickVertical"; 16 | } 17 | } -------------------------------------------------------------------------------- /ActionMenuApi/Logger.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | 3 | namespace ActionMenuApi 4 | { 5 | internal static class Logger 6 | { 7 | public static void Log(string message) 8 | { 9 | #if DEBUG 10 | MelonLogger.Msg(message); 11 | #endif 12 | } 13 | 14 | public static void LogWarning(string message) 15 | { 16 | #if DEBUG 17 | MelonLogger.Warning(message); 18 | #endif 19 | } 20 | 21 | public static void LogError(string message) 22 | { 23 | #if DEBUG 24 | MelonLogger.Error(message); 25 | #endif 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ActionMenuApi/Managers/FourAxisPuppetManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Types; 4 | using MelonLoader; 5 | using UnityEngine; 6 | using UnityEngine.XR; 7 | 8 | namespace ActionMenuApi.Managers 9 | { 10 | internal static class FourAxisPuppetManager 11 | { 12 | private static AxisPuppetMenu fourAxisPuppetMenuRight; 13 | private static AxisPuppetMenu fourAxisPuppetMenuLeft; 14 | private static ActionMenuHand hand; 15 | private static bool open; 16 | public static AxisPuppetMenu current { get; private set; } 17 | 18 | public static Vector2 fourAxisPuppetValue { get; set; } 19 | 20 | public static Action onUpdate { get; set; } 21 | 22 | public static void Setup() 23 | { 24 | fourAxisPuppetMenuLeft = Utilities 25 | .CloneGameObject("UserInterface/ActionMenu/Container/MenuL/ActionMenu/AxisPuppetMenu", 26 | "UserInterface/ActionMenu/Container/MenuL/ActionMenu").GetComponent(); 27 | fourAxisPuppetMenuRight = Utilities 28 | .CloneGameObject("UserInterface/ActionMenu/Container/MenuR/ActionMenu/AxisPuppetMenu", 29 | "UserInterface/ActionMenu/Container/MenuR/ActionMenu").GetComponent(); 30 | } 31 | 32 | public static void OnUpdate() 33 | { 34 | //Probably a better more efficient way to do all this 35 | if (current != null && current.gameObject.gameObject.active) 36 | { 37 | if (XRDevice.isPresent) 38 | { 39 | if (hand == ActionMenuHand.Right) 40 | { 41 | if (Input.GetAxis(Constants.RIGHT_TRIGGER) >= 0.4f) 42 | { 43 | CloseFourAxisMenu(); 44 | return; 45 | } 46 | } 47 | else if (hand == ActionMenuHand.Left) 48 | { 49 | if (Input.GetAxis(Constants.LEFT_TRIGGER) >= 0.4f) 50 | { 51 | CloseFourAxisMenu(); 52 | return; 53 | } 54 | } 55 | } 56 | else if (Input.GetMouseButtonUp(0)) 57 | { 58 | CloseFourAxisMenu(); 59 | return; 60 | } 61 | 62 | fourAxisPuppetValue = (hand == ActionMenuHand.Left ? InputManager.LeftInput : InputManager.RightInput) / 16; 63 | var x = fourAxisPuppetValue.x; 64 | var y = fourAxisPuppetValue.y; 65 | if (x >= 0) 66 | { 67 | current.GetFillLeft().SetAlpha(0); 68 | current.GetFillRight().SetAlpha(x); 69 | } 70 | else 71 | { 72 | current.GetFillLeft().SetAlpha(Math.Abs(x)); 73 | current.GetFillRight().SetAlpha(0); 74 | } 75 | 76 | if (y >= 0) 77 | { 78 | current.GetFillDown().SetAlpha(0); 79 | current.GetFillUp().SetAlpha(y); 80 | } 81 | else 82 | { 83 | current.GetFillDown().SetAlpha(Math.Abs(y)); 84 | current.GetFillUp().SetAlpha(0); 85 | } 86 | 87 | UpdateMathStuff(); 88 | CallUpdateAction(); 89 | } 90 | } 91 | 92 | public static void OpenFourAxisMenu(string title, Action update, PedalOption pedalOption) 93 | { 94 | if (open) return; 95 | switch (hand = Utilities.GetActionMenuHand()) 96 | { 97 | case ActionMenuHand.Invalid: 98 | return; 99 | case ActionMenuHand.Left: 100 | current = fourAxisPuppetMenuLeft; 101 | open = true; 102 | break; 103 | case ActionMenuHand.Right: 104 | current = fourAxisPuppetMenuRight; 105 | open = true; 106 | break; 107 | } 108 | Input.ResetInputAxes(); 109 | InputManager.ResetMousePos(); 110 | onUpdate = update; 111 | current.gameObject.SetActive(true); 112 | current.GetTitle().text = title; 113 | var actionMenu = Utilities.GetActionMenuOpener().GetActionMenu(); 114 | actionMenu.DisableInput(); 115 | actionMenu.SetMainMenuOpacity(0.5f); 116 | current.transform.localPosition = pedalOption.GetActionButton().transform.localPosition; 117 | } 118 | 119 | private static void CallUpdateAction() 120 | { 121 | try 122 | { 123 | onUpdate?.Invoke(fourAxisPuppetValue); 124 | } 125 | catch (Exception e) 126 | { 127 | MelonLogger.Error($"Exception caught in onUpdate action passed to Four Axis Puppet: {e}"); 128 | } 129 | } 130 | 131 | public static void CloseFourAxisMenu() 132 | { 133 | if (current == null) return; 134 | CallUpdateAction(); 135 | current.gameObject.SetActive(false); 136 | current = null; 137 | open = false; 138 | hand = ActionMenuHand.Invalid; 139 | var actionMenu = Utilities.GetActionMenuOpener().GetActionMenu(); 140 | actionMenu.SetMainMenuOpacity(); 141 | actionMenu.EnableInput(); 142 | } 143 | 144 | private static void UpdateMathStuff() 145 | { 146 | var mousePos = hand == ActionMenuHand.Left ? InputManager.LeftInput : InputManager.RightInput; 147 | current.GetCursor().transform.localPosition = mousePos * 4; 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /ActionMenuApi/Managers/InputManager.cs: -------------------------------------------------------------------------------- 1 | using ActionMenuApi.Helpers; 2 | using MelonLoader; 3 | using UnityEngine; 4 | using UnityEngine.XR; 5 | 6 | namespace ActionMenuApi.Managers 7 | { 8 | internal static class InputManager 9 | { 10 | private static Vector2 mouseAxis; 11 | public static Vector2 LeftInput 12 | { 13 | get 14 | { 15 | if (XRDevice.isPresent) 16 | return new Vector2(Input.GetAxis(Constants.LEFT_HORIZONTAL), Input.GetAxis(Constants.LEFT_VERTICAL)) * 16; 17 | mouseAxis.x = Mathf.Clamp(mouseAxis.x+Input.GetAxis("Mouse X"), -16f, 16f); 18 | mouseAxis.y = Mathf.Clamp(mouseAxis.y+Input.GetAxis("Mouse Y"), -16f, 16f); 19 | var translatedHit = Utilities.GetIntersection(mouseAxis.x, mouseAxis.y, Mathf.Max(Mathf.Abs(mouseAxis.x),Mathf.Abs(mouseAxis.y))); 20 | if (translatedHit.x1 > 0 && mouseAxis.x > 0) 21 | return new Vector2((float)translatedHit.x1, (float)translatedHit.y1); 22 | return new Vector2((float)translatedHit.x2, (float)translatedHit.y2);; 23 | } 24 | } 25 | public static Vector2 RightInput 26 | { 27 | get 28 | { 29 | if (XRDevice.isPresent) 30 | return new Vector2(Input.GetAxis(Constants.RIGHT_HORIZONTAL), Input.GetAxis(Constants.RIGHT_VERTICAL)) * 16; 31 | mouseAxis.x = Mathf.Clamp(mouseAxis.x+Input.GetAxis("Mouse X"), -16f, 16f); 32 | mouseAxis.y = Mathf.Clamp(mouseAxis.y+Input.GetAxis("Mouse Y"), -16f, 16f); 33 | var translatedHit = Utilities.GetIntersection(mouseAxis.x, mouseAxis.y, Mathf.Max(Mathf.Abs(mouseAxis.x),Mathf.Abs(mouseAxis.y))); 34 | if (translatedHit.x1 > 0 && mouseAxis.x > 0) 35 | return new Vector2((float)translatedHit.x1, (float)translatedHit.y1); 36 | return new Vector2((float)translatedHit.x2, (float)translatedHit.y2);; 37 | } 38 | } 39 | 40 | public static void ResetMousePos() 41 | { 42 | mouseAxis.x = 0f; 43 | mouseAxis.y = 0f; 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /ActionMenuApi/Managers/ModsFolderManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ActionMenuApi.Api; 4 | using ActionMenuApi.Helpers; 5 | 6 | namespace ActionMenuApi.Managers 7 | { 8 | internal static class ModsFolderManager 9 | { 10 | public static List mods = new(); 11 | public static List> splitMods; 12 | 13 | private static readonly Action openFunc = () => 14 | { 15 | if (mods.Count <= Constants.MAX_PEDALS_PER_PAGE) 16 | { 17 | foreach (var action in mods) action(); 18 | } 19 | else 20 | { 21 | if (splitMods == null) splitMods = mods.Split(Constants.MAX_PEDALS_PER_PAGE); 22 | for (var i = 0; i < splitMods.Count && i < Constants.MAX_PEDALS_PER_PAGE; i++) 23 | { 24 | var index = i; 25 | CustomSubMenu.AddSubMenu($"Page {i + 1}", () => 26 | { 27 | foreach (var action in splitMods[index]) action(); 28 | }, ResourcesManager.GetPageIcon(i + 1)); 29 | } 30 | } 31 | }; 32 | 33 | public static void AddMod(Action openingAction) 34 | { 35 | mods.Add(openingAction); 36 | } 37 | 38 | /*public void RemoveMod(Action openingAction) 39 | { 40 | mods.Remove(openingAction); 41 | }*/ 42 | 43 | public static void AddMainPageButton() 44 | { 45 | CustomSubMenu.AddSubMenu(Constants.MODS_FOLDER_NAME, openFunc, ResourcesManager.GetModsSectionIcon()); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /ActionMenuApi/Managers/RadialPuppetManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Types; 4 | using MelonLoader; 5 | using UnhollowerBaseLib; 6 | using UnityEngine; 7 | using UnityEngine.XR; 8 | using VRC.SDK3.Avatars.ScriptableObjects; 9 | 10 | namespace ActionMenuApi.Managers 11 | { 12 | internal static class RadialPuppetManager 13 | { 14 | private static RadialPuppetMenu radialPuppetMenuRight; 15 | private static RadialPuppetMenu radialPuppetMenuLeft; 16 | private static RadialPuppetMenu current; 17 | private static ActionMenuHand hand; 18 | private static bool open; 19 | private static bool restricted; 20 | private static float currentValue; 21 | 22 | public static float radialPuppetValue { get; set; } 23 | public static Action onUpdate { get; set; } 24 | 25 | public static void Setup() 26 | { 27 | radialPuppetMenuLeft = Utilities 28 | .CloneGameObject("UserInterface/ActionMenu/Container/MenuL/ActionMenu/RadialPuppetMenu", 29 | "UserInterface/ActionMenu/Container/MenuL/ActionMenu").GetComponent(); 30 | radialPuppetMenuRight = Utilities 31 | .CloneGameObject("UserInterface/ActionMenu/Container/MenuR/ActionMenu/RadialPuppetMenu", 32 | "UserInterface/ActionMenu/Container/MenuR/ActionMenu").GetComponent(); 33 | } 34 | 35 | public static void OnUpdate() 36 | { 37 | //Probably a better more efficient way to do all this 38 | if (current != null && current.gameObject.gameObject.active) 39 | { 40 | if (XRDevice.isPresent) 41 | { 42 | if (hand == ActionMenuHand.Right) 43 | { 44 | if (Input.GetAxis(Constants.RIGHT_TRIGGER) >= 0.4f) 45 | { 46 | CloseRadialMenu(); 47 | return; 48 | } 49 | } 50 | else if (hand == ActionMenuHand.Left) 51 | { 52 | if (Input.GetAxis(Constants.LEFT_TRIGGER) >= 0.4f) 53 | { 54 | CloseRadialMenu(); 55 | return; 56 | } 57 | } 58 | } 59 | else if (Input.GetMouseButtonUp(0)) 60 | { 61 | CloseRadialMenu(); 62 | return; 63 | } 64 | 65 | UpdateMathStuff(); 66 | CallUpdateAction(); 67 | } 68 | } 69 | 70 | public static void OpenRadialMenu(float startingValue, Action onUpdate, string title, PedalOption pedalOption, bool restricted = false) 71 | { 72 | if (open) return; 73 | switch (Utilities.GetActionMenuHand()) 74 | { 75 | case ActionMenuHand.Invalid: 76 | return; 77 | case ActionMenuHand.Left: 78 | current = radialPuppetMenuLeft; 79 | hand = ActionMenuHand.Left; 80 | open = true; 81 | break; 82 | case ActionMenuHand.Right: 83 | current = radialPuppetMenuRight; 84 | hand = ActionMenuHand.Right; 85 | open = true; 86 | break; 87 | 88 | } 89 | 90 | RadialPuppetManager.restricted = restricted; 91 | Input.ResetInputAxes(); 92 | InputManager.ResetMousePos(); 93 | current.gameObject.SetActive(true); 94 | current.GetFill().SetFillAngle(startingValue * 360); //Please dont break 95 | RadialPuppetManager.onUpdate = onUpdate; 96 | currentValue = startingValue; 97 | 98 | current.GetTitle().text = title; 99 | current.GetCenterText().text = $"{Mathf.Round(startingValue * 100f)}%"; 100 | current.GetFill().UpdateGeometry(); 101 | current.transform.localPosition = pedalOption.GetActionButton().transform.localPosition; //new Vector3(-256f, 0, 0); 102 | var angleOriginal = Utilities.ConvertFromEuler(startingValue * 360); 103 | var eulerAngle = Utilities.ConvertFromDegToEuler(angleOriginal); 104 | var actionMenu = Utilities.GetActionMenuOpener().GetActionMenu(); 105 | actionMenu.DisableInput(); 106 | actionMenu.SetMainMenuOpacity(0.5f); 107 | current.UpdateArrow(angleOriginal, eulerAngle); 108 | } 109 | 110 | public static void CloseRadialMenu() 111 | { 112 | if (current == null) return; 113 | CallUpdateAction(); 114 | current.gameObject.SetActive(false); 115 | current = null; 116 | open = false; 117 | hand = ActionMenuHand.Invalid; 118 | var actionMenu = Utilities.GetActionMenuOpener().GetActionMenu(); 119 | actionMenu.EnableInput(); 120 | actionMenu.SetMainMenuOpacity(); 121 | } 122 | 123 | private static void CallUpdateAction() 124 | { 125 | try 126 | { 127 | onUpdate?.Invoke(current.GetFill().GetFillAngle() / 360f); 128 | } 129 | catch (Exception e) 130 | { 131 | MelonLogger.Error($"Exception caught in onUpdate action passed to Radial Puppet: {e}"); 132 | } 133 | } 134 | 135 | private static void UpdateMathStuff() 136 | { 137 | var mousePos = hand == ActionMenuHand.Left ? InputManager.LeftInput : InputManager.RightInput; 138 | radialPuppetMenuRight.GetCursor().transform.localPosition = mousePos * 4; 139 | 140 | if (Vector2.Distance(mousePos, Vector2.zero) > 12) 141 | { 142 | var angleOriginal = Mathf.Round(Mathf.Atan2(mousePos.y, mousePos.x) * Constants.RAD_TO_DEG); 143 | var eulerAngle = Utilities.ConvertFromDegToEuler(angleOriginal); 144 | var normalisedAngle = eulerAngle / 360f; 145 | if (Math.Abs(normalisedAngle - currentValue) < 0.0001f) return; 146 | if (!restricted) 147 | { 148 | current.SetAngle(eulerAngle); 149 | current.UpdateArrow(angleOriginal, eulerAngle); 150 | } 151 | else 152 | { 153 | if (currentValue > normalisedAngle) 154 | { 155 | if (currentValue - normalisedAngle < 0.5f) 156 | { 157 | current.SetAngle(eulerAngle); 158 | current.UpdateArrow(angleOriginal, eulerAngle); 159 | currentValue = normalisedAngle; 160 | } 161 | else 162 | { 163 | current.SetAngle(360); 164 | current.UpdateArrow(90, 360); 165 | currentValue = 1f; 166 | } 167 | } 168 | else 169 | { 170 | if (normalisedAngle - currentValue < 0.5f) 171 | { 172 | current.SetAngle(eulerAngle); 173 | current.UpdateArrow(angleOriginal, eulerAngle); 174 | currentValue = normalisedAngle; 175 | } 176 | else 177 | { 178 | current.SetAngle(0); 179 | current.UpdateArrow(90, 0); 180 | currentValue = 0; 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /ActionMenuApi/Managers/ResourcesManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using ActionMenuApi.Helpers; 4 | using MelonLoader; 5 | using UnhollowerRuntimeLib; 6 | using UnityEngine; 7 | using UnityEngine.UI; 8 | 9 | namespace ActionMenuApi.Managers 10 | { 11 | internal static class ResourcesManager 12 | { 13 | private static GameObject lockPrefab; 14 | private static Texture2D pageOne; 15 | private static Texture2D pageTwo; 16 | private static Texture2D pageThree; 17 | private static Texture2D pageFour; 18 | private static Texture2D pageFive; 19 | private static Texture2D pageSix; 20 | private static Texture2D pageSeven; 21 | private static Texture2D locked; 22 | private static Texture2D modsSectionIcon; 23 | 24 | public static void LoadTextures() 25 | { 26 | AssetBundle iconsAssetBundle; 27 | using (var stream = Assembly.GetExecutingAssembly() 28 | .GetManifestResourceStream("ActionMenuApi.actionmenuapi.icons")) 29 | using (var tempStream = new MemoryStream((int) stream.Length)) 30 | { 31 | stream.CopyTo(tempStream); 32 | 33 | iconsAssetBundle = AssetBundle.LoadFromMemory_Internal(tempStream.ToArray(), 0); 34 | iconsAssetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 35 | } 36 | 37 | modsSectionIcon = iconsAssetBundle 38 | .LoadAsset_Internal("Assets/ActionMenuApi/vrcmg.png", Il2CppType.Of()).Cast(); 39 | modsSectionIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 40 | pageOne = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/1.png", Il2CppType.Of()) 41 | .Cast(); 42 | pageOne.hideFlags |= HideFlags.DontUnloadUnusedAsset; 43 | pageTwo = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/2.png", Il2CppType.Of()) 44 | .Cast(); 45 | pageTwo.hideFlags |= HideFlags.DontUnloadUnusedAsset; 46 | pageThree = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/3.png", Il2CppType.Of()) 47 | .Cast(); 48 | pageThree.hideFlags |= HideFlags.DontUnloadUnusedAsset; 49 | pageFour = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/4.png", Il2CppType.Of()) 50 | .Cast(); 51 | pageFour.hideFlags |= HideFlags.DontUnloadUnusedAsset; 52 | pageFive = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/5.png", Il2CppType.Of()) 53 | .Cast(); 54 | pageFive.hideFlags |= HideFlags.DontUnloadUnusedAsset; 55 | pageSix = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/6.png", Il2CppType.Of()) 56 | .Cast(); 57 | pageSix.hideFlags |= HideFlags.DontUnloadUnusedAsset; 58 | pageSeven = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/7.png", Il2CppType.Of()) 59 | .Cast(); 60 | pageSeven.hideFlags |= HideFlags.DontUnloadUnusedAsset; 61 | locked = iconsAssetBundle.LoadAsset_Internal("Assets/ActionMenuApi/locked.png", Il2CppType.Of()) 62 | .Cast(); 63 | locked.hideFlags |= HideFlags.DontUnloadUnusedAsset; 64 | MelonLogger.Msg("Loaded textures"); 65 | } 66 | 67 | public static void InitLockGameObject() 68 | { 69 | lockPrefab = Object.Instantiate(ActionMenuDriver.prop_ActionMenuDriver_0.GetRightOpener().GetActionMenu() 70 | .GetPedalOptionPrefab().GetComponent().GetActionButton().gameObject.GetChild("Inner") 71 | .GetChild("Folder Icon")); 72 | Object.DontDestroyOnLoad(lockPrefab); 73 | lockPrefab.active = false; 74 | lockPrefab.gameObject.name = Constants.LOCKED_PEDAL_OVERLAY_GAMEOBJECT_NAME; 75 | lockPrefab.GetComponent().texture = locked; 76 | MelonLogger.Msg("Created lock gameobject"); 77 | } 78 | 79 | public static Texture2D GetPageIcon(int pageIndex) 80 | { 81 | switch (pageIndex) 82 | { 83 | case 1: 84 | return pageOne; 85 | case 2: 86 | return pageTwo; 87 | case 3: 88 | return pageThree; 89 | case 4: 90 | return pageFour; 91 | case 5: 92 | return pageFive; 93 | case 6: 94 | return pageSix; 95 | case 7: 96 | return pageSeven; 97 | default: 98 | return null; 99 | } 100 | } 101 | 102 | public static void AddLockChildIcon(GameObject parent) 103 | { 104 | var lockedGameObject = Object.Instantiate(lockPrefab, parent.transform, false); 105 | lockedGameObject.SetActive(true); 106 | lockedGameObject.transform.localPosition = new Vector3(50, -25, 0); 107 | lockedGameObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); 108 | } 109 | 110 | public static Texture2D GetModsSectionIcon() 111 | { 112 | return modsSectionIcon; 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Types; 3 | using UnityEngine; 4 | 5 | namespace ActionMenuApi.Pedals 6 | { 7 | public sealed class PedalButton : PedalStruct 8 | { 9 | public PedalButton(string text, Texture2D icon, Action triggerEvent, bool locked = false) 10 | { 11 | this.text = text; 12 | this.icon = icon; 13 | this.triggerEvent = delegate { triggerEvent(); }; 14 | Type = PedalType.Button; 15 | this.locked = locked; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalFourAxis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Managers; 4 | using ActionMenuApi.Types; 5 | using UnityEngine; 6 | 7 | namespace ActionMenuApi.Pedals 8 | { 9 | public sealed class PedalFourAxis : PedalStruct 10 | { 11 | public PedalFourAxis(string text, Texture2D icon, Action onUpdate, string topButtonText, 12 | string rightButtonText, string downButtonText, string leftButtonText, bool locked = false) 13 | { 14 | this.text = text; 15 | this.icon = icon; 16 | triggerEvent = delegate 17 | { 18 | FourAxisPuppetManager.OpenFourAxisMenu(text, onUpdate, pedal); 19 | FourAxisPuppetManager.current.GetButtonUp().SetButtonText(topButtonText); 20 | FourAxisPuppetManager.current.GetButtonRight().SetButtonText(rightButtonText); 21 | FourAxisPuppetManager.current.GetButtonDown().SetButtonText(downButtonText); 22 | FourAxisPuppetManager.current.GetButtonLeft().SetButtonText(leftButtonText); 23 | }; 24 | Type = PedalType.FourAxisPuppet; 25 | this.locked = locked; 26 | } 27 | 28 | public PedalOption pedal { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalRadial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Managers; 4 | using ActionMenuApi.Types; 5 | using UnityEngine; 6 | 7 | namespace ActionMenuApi.Pedals 8 | { 9 | public sealed class PedalRadial : PedalStruct 10 | { 11 | public float currentValue; 12 | 13 | public PedalRadial(string text, float startingValue, Texture2D icon, Action onUpdate, 14 | bool locked = false, bool restricted = false) 15 | { 16 | this.text = text; 17 | currentValue = startingValue; 18 | this.icon = icon; 19 | triggerEvent = delegate 20 | { 21 | var combinedAction = (Action) Delegate.Combine(new Action(delegate(float f) 22 | { 23 | startingValue = f; 24 | pedal.SetButtonPercentText($"{Math.Round(startingValue * 100)}%"); 25 | }), onUpdate); 26 | RadialPuppetManager.OpenRadialMenu(startingValue, combinedAction, text, pedal, restricted); 27 | }; 28 | Type = PedalType.RadialPuppet; 29 | this.locked = locked; 30 | this.restricted = restricted; 31 | } 32 | 33 | public PedalOption pedal { get; set; } 34 | public bool restricted { get; } 35 | } 36 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalStruct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Types; 3 | using UnityEngine; 4 | 5 | namespace ActionMenuApi.Pedals 6 | { 7 | public abstract class PedalStruct 8 | { 9 | public string text { get; set; } 10 | public Texture2D icon { get; set; } 11 | public Action triggerEvent { get; protected set; } 12 | public PedalType Type { get; protected set; } 13 | public bool locked { get; set; } 14 | public bool shouldAdd { get; set; } = true; 15 | } 16 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalSubMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Types; 4 | using MelonLoader; 5 | using UnityEngine; 6 | 7 | namespace ActionMenuApi.Pedals 8 | { 9 | public sealed class PedalSubMenu : PedalStruct 10 | { 11 | public PedalSubMenu(Action onSubMenuOpen, string text = null, Texture2D icon = null, Action onSubMenuClose = null, bool locked = false) 12 | { 13 | this.text = text; 14 | this.icon = icon; 15 | this.OnSubMenuOpen += onSubMenuOpen; 16 | this.OnSubMenuClose += onSubMenuClose; 17 | this.OnSubMenuClose += delegate 18 | { 19 | IsOpen = false; 20 | }; 21 | triggerEvent = delegate(ActionMenu menu) 22 | { 23 | IsOpen = true; 24 | menu.PushPage(this._openFunc, this._closeFunc, icon, text); 25 | }; 26 | Type = PedalType.SubMenu; 27 | this.locked = locked; 28 | } 29 | 30 | private Action _openFunc; 31 | /// 32 | /// Triggered when the submenu is opened *duh* 33 | /// 34 | public event Action OnSubMenuOpen 35 | { 36 | add 37 | { 38 | if (_openFunc is null) 39 | _openFunc = value; 40 | else 41 | _openFunc = (Action)Delegate.Combine(_openFunc, value); 42 | } 43 | remove 44 | { 45 | if (_openFunc is not null) 46 | _openFunc = (Action)Delegate.Remove(_openFunc, value); 47 | } 48 | } 49 | 50 | private Action _closeFunc; 51 | 52 | /// 53 | /// Triggered when the sub menu is close *duh* 54 | /// 55 | public event Action OnSubMenuClose 56 | { 57 | add 58 | { 59 | if (_closeFunc is null) 60 | _closeFunc = value; 61 | else 62 | _closeFunc = (Action)Delegate.Combine(_closeFunc, value); 63 | } 64 | remove 65 | { 66 | if (_closeFunc is not null) 67 | _closeFunc = (Action)Delegate.Remove(_closeFunc, value); 68 | } 69 | } 70 | 71 | public bool IsOpen { get; internal set; } 72 | } 73 | } -------------------------------------------------------------------------------- /ActionMenuApi/Pedals/PedalToggle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ActionMenuApi.Helpers; 3 | using ActionMenuApi.Types; 4 | using UnityEngine; 5 | 6 | namespace ActionMenuApi.Pedals 7 | { 8 | public sealed class PedalToggle : PedalStruct 9 | { 10 | public PedalToggle(string text, Action onToggle, bool toggled, Texture2D icon = null, 11 | bool locked = false) 12 | { 13 | this.text = text; 14 | this.toggled = toggled; 15 | this.icon = icon; 16 | triggerEvent = delegate 17 | { 18 | //MelonLogger.Msg($"Old state: {this.toggled}, New state: {!this.toggled}"); 19 | this.toggled = !this.toggled; 20 | if (this.toggled) 21 | pedal.SetPedalTypeIcon(Utilities.GetExpressionsIcons().typeToggleOn); 22 | else 23 | pedal.SetPedalTypeIcon(Utilities.GetExpressionsIcons().typeToggleOff); 24 | onToggle.Invoke(toggled); 25 | }; 26 | Type = PedalType.Toggle; 27 | this.locked = locked; 28 | } 29 | 30 | public bool toggled { get; set; } 31 | 32 | public PedalOption pedal { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /ActionMenuApi/Types/ActionMenuHand.cs: -------------------------------------------------------------------------------- 1 | namespace ActionMenuApi.Types 2 | { 3 | internal enum ActionMenuHand 4 | { 5 | Invalid, 6 | Left, 7 | Right 8 | } 9 | } -------------------------------------------------------------------------------- /ActionMenuApi/Types/ActionMenuPageType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ActionMenuApi.Types 4 | { 5 | /// 6 | /// Defines the already existing vrchat pages that you can add pedals to 7 | /// 8 | [Obsolete("Please use ActionMenuApi.Api.ActionMenuPage", false)] 9 | public enum ActionMenuPageType 10 | { 11 | /// 12 | /// The more "advanced" options in the action menu. Can change menu opacity, size, position etc. here 13 | /// 14 | Config, 15 | 16 | /// 17 | /// The page that shows when you open the emojis page of the action menu 18 | /// 19 | Emojis, 20 | 21 | /// 22 | /// The page that shows when you open the expression page of the action menu using an sdk3 avatar 23 | /// 24 | Expression, 25 | 26 | /// 27 | /// The page that shows when you open the expression page of the action menu using an sdk2 avatar. Has emotes 1-8 28 | /// 29 | SDK2Expression, 30 | 31 | /// 32 | /// The default page that shows when you open the action menu 33 | /// 34 | Main, 35 | 36 | /// 37 | /// The menu opacity page. Has 25%, 50%, 75%, 100% 38 | /// 39 | MenuOpacity, 40 | 41 | /// 42 | /// The menu size page. Has Small, Medium, Large 43 | /// 44 | MenuSize, //Not Implemented 45 | 46 | /// 47 | /// The nameplates config page of the action menu 48 | /// 49 | Nameplates, 50 | 51 | /// 52 | /// The nameplates opacity page 0%,20%,40%,60%,80%,100% 53 | /// 54 | NameplatesOpacity, 55 | 56 | /// 57 | /// The nameplates visibility page shown,icons only,hidden 58 | /// 59 | NameplatesVisibilty, 60 | 61 | /// 62 | /// The nameplates size page tiny,small,normal,medium,large 63 | /// 64 | NameplatesSize, 65 | 66 | /// 67 | /// The options page, toggle mic, close menu etc. 68 | /// 69 | Options 70 | } 71 | } -------------------------------------------------------------------------------- /ActionMenuApi/Types/Insertion.cs: -------------------------------------------------------------------------------- 1 | namespace ActionMenuApi.Types 2 | { 3 | /// 4 | /// Whether your pedal on the vrchat pages should be placed before or after vrchat's pedals 5 | /// 6 | public enum Insertion 7 | { 8 | /// 9 | /// Place your pedal before vrchats 10 | /// 11 | Pre, 12 | 13 | /// 14 | /// Place your pedal after vrchats 15 | /// 16 | Post 17 | } 18 | } -------------------------------------------------------------------------------- /ActionMenuApi/Types/PedalType.cs: -------------------------------------------------------------------------------- 1 | namespace ActionMenuApi.Types 2 | { 3 | public enum PedalType 4 | { 5 | Button, 6 | Toggle, 7 | SubMenu, 8 | FourAxisPuppet, 9 | RadialPuppet 10 | } 11 | } -------------------------------------------------------------------------------- /ActionMenuApi/actionmenuapi.icons: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gompoc/VRChatMods/2e0bb03fe0a54665eb83cf9bbe4f517f4a0e52b6/ActionMenuApi/actionmenuapi.icons -------------------------------------------------------------------------------- /ActionMenuTestMod/ActionMenuTestMod.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using ActionMenuApi; 4 | using ActionMenuApi.Api; 5 | using ActionMenuApi.Pedals; 6 | using MelonLoader; 7 | using UnhollowerRuntimeLib; 8 | using UnityEngine; 9 | 10 | [assembly: MelonInfo(typeof(ActionMenuTestMod.ActionMenuTestMod), "ActionMenuTestMod", "1.0.0", "gompo")] 11 | [assembly: MelonGame("VRChat", "VRChat")] 12 | 13 | namespace ActionMenuTestMod 14 | { 15 | // Icons from https://uxwing.com/ 16 | public partial class ActionMenuTestMod : MelonMod 17 | { 18 | 19 | private float testFloatValue = 50; 20 | private float testFloatValue2 = 50; 21 | private bool testBool = false; 22 | private bool testBool2 = false; 23 | private bool riskyFunctionsAllowed = false; 24 | private Vector2 testVector = new (); 25 | private Vector2 testVector2 = new (); 26 | private static float x = 0; 27 | private static float y = 0; 28 | private static float z = 0; 29 | private static AssetBundle iconsAssetBundle = null; 30 | private static Texture2D toggleIcon; 31 | private static Texture2D radialIcon; 32 | private static Texture2D subMenuIcon; 33 | private static Texture2D buttonIcon; 34 | 35 | public override void OnApplicationStart() 36 | { 37 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ActionMenuTestMod.customicons")) 38 | using (var tempStream = new MemoryStream((int) stream.Length)) 39 | { 40 | stream.CopyTo(tempStream); 41 | iconsAssetBundle = AssetBundle.LoadFromMemory_Internal(tempStream.ToArray(), 0); 42 | iconsAssetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 43 | } 44 | 45 | radialIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Icons/sound-full.png", Il2CppType.Of()).Cast(); 46 | radialIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 47 | toggleIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Icons/zero.png", Il2CppType.Of()).Cast(); 48 | toggleIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 49 | subMenuIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Icons/file-transfer.png", Il2CppType.Of()).Cast(); 50 | subMenuIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 51 | buttonIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Icons/cloud-data-download.png", Il2CppType.Of()).Cast(); 52 | buttonIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 53 | 54 | VRCActionMenuPage.AddButton(ActionMenuPage.Main, "Button",() => MelonLogger.Msg("Pressed Button"), buttonIcon); 55 | 56 | PedalSubMenu subMenu = VRCActionMenuPage.AddSubMenu(ActionMenuPage.Config, "Toggle", () => { }, null, true); 57 | subMenu.locked = true; 58 | 59 | AMUtils.AddToModsFolder( 60 | "Test Stuff", 61 | delegate 62 | { 63 | CustomSubMenu.AddToggle("Risky Functions", !riskyFunctionsAllowed, (b) => 64 | { 65 | riskyFunctionsAllowed = !b; 66 | AMUtils.RefreshActionMenu(); //Refresh menu to update the locked state of the pedal 67 | }); 68 | //No properties here are saved because I'm lazy af 69 | CustomSubMenu.AddToggle("Enable Hax", false, b => { }, buttonIcon,riskyFunctionsAllowed); 70 | CustomSubMenu.AddRadialPuppet("Volume", f => { }, 0, buttonIcon, riskyFunctionsAllowed); 71 | CustomSubMenu.AddRestrictedRadialPuppet("Volume Restricted", f => { }, 0, buttonIcon, riskyFunctionsAllowed); 72 | CustomSubMenu.AddSubMenu("Whatever", () => { }, buttonIcon, riskyFunctionsAllowed); 73 | CustomSubMenu.AddButton("Risky Function", () => 74 | { 75 | MelonLogger.Msg("Locked Pedal Func ran"); 76 | }, buttonIcon, riskyFunctionsAllowed); 77 | CustomSubMenu.AddFourAxisPuppet("Move", vector2 => { }, toggleIcon, riskyFunctionsAllowed); 78 | }, 79 | subMenuIcon 80 | ); 81 | 82 | AMUtils.AddToModsFolder( 83 | "New Cube Stuff", 84 | delegate 85 | { 86 | CustomSubMenu.AddFourAxisPuppet("Reposition cube X/Y", (v) => RePositionCubeXY(v), buttonIcon); 87 | CustomSubMenu.AddFourAxisPuppet("Reposition cube Z/Y", RePositionCubeZY, toggleIcon); 88 | CustomSubMenu.AddFourAxisPuppet("Reposition cube X/Z", RePositionCubeXZ, toggleIcon); 89 | CustomSubMenu.AddRadialPuppet("X",RotateCubeX, x,radialIcon); //Rotation a bit borked 90 | CustomSubMenu.AddToggle("Test Toggle", testBool2, (b) => testBool2 = b); 91 | CustomSubMenu.AddRadialPuppet("Y",RotateCubeY, y,radialIcon); 92 | CustomSubMenu.AddRadialPuppet("Z",RotateCubeZ, z,radialIcon); 93 | CustomSubMenu.AddButton("Spawn Cube", CreateCube, buttonIcon); 94 | CustomSubMenu.AddButton("Tp Cube To Player",() => _controllingGameObject.transform.localPosition = VRCPlayer.field_Internal_Static_VRCPlayer_0.transform.localPosition, buttonIcon); 95 | }, 96 | subMenuIcon, 97 | false 98 | ); 99 | 100 | for (int i = 0; i < 2; i++) //Set to a high number if you want to test the page functionality 101 | { 102 | AMUtils.AddToModsFolder($"Example Mod {i+2}", () => {}, subMenuIcon); 103 | } 104 | } 105 | 106 | public override void OnUpdate() 107 | { 108 | if (Input.GetKeyUp(KeyCode.P)) 109 | { 110 | AMUtils.RefreshActionMenu(); 111 | } 112 | 113 | if (Input.GetKeyUp(KeyCode.I)) 114 | { 115 | AMUtils.ResetMenu(); 116 | } 117 | } 118 | 119 | private static void CreateCube() 120 | { 121 | _controllingGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube); 122 | _controllingGameObject.GetComponent().enabled = false; 123 | var eulerAngles = _controllingGameObject.transform.eulerAngles; 124 | x = eulerAngles.x*360; 125 | y = eulerAngles.y*360; 126 | z = eulerAngles.z*360; 127 | } 128 | 129 | 130 | private static void RePositionCubeXY(Vector3 v) 131 | { 132 | _controllingGameObject.transform.localPosition += v/25; 133 | } 134 | private static void RePositionCubeZY(Vector2 v) 135 | { 136 | _controllingGameObject.transform.localPosition += new Vector3(0, v.y/25, v.x/25); 137 | } 138 | private static void RePositionCubeXZ(Vector2 v) 139 | { 140 | _controllingGameObject.transform.localPosition += new Vector3(v.x/25, 0, v.y/25); 141 | } 142 | 143 | private static void RotateCubeX(float rotation) 144 | { 145 | //This is the incorrect way of rotating the gameobject and it breaks from 90-270 as quaternions and euler angle conversions are a bit fucky 146 | Vector3 old = _controllingGameObject.transform.eulerAngles; 147 | _controllingGameObject.transform.eulerAngles = new Vector3((rotation)*360, old.y, old.z); 148 | x = rotation; 149 | } 150 | 151 | private static void RotateCubeY(float rotation) 152 | { 153 | Vector3 old = _controllingGameObject.transform.eulerAngles; 154 | //MelonLogger.Msg($"Old Angles: {old.ToString()}"); 155 | _controllingGameObject.transform.eulerAngles = new Vector3(old.x, (rotation)*360, old.z); 156 | y = rotation; 157 | } 158 | private static void RotateCubeZ(float rotation) 159 | { 160 | Vector3 old = _controllingGameObject.transform.eulerAngles; 161 | //MelonLogger.Msg($"Old Angles: {old.ToString()}"); 162 | _controllingGameObject.transform.eulerAngles = new Vector3(old.x, old.y, (rotation)*360); 163 | z = rotation; 164 | } 165 | 166 | private static GameObject _controllingGameObject; 167 | } 168 | } -------------------------------------------------------------------------------- /ActionMenuTestMod/ActionMenuTestMod.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | true 6 | 1.0.0.0 7 | 9 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ActionMenuTestMod/customicons: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gompoc/VRChatMods/2e0bb03fe0a54665eb83cf9bbe4f517f4a0e52b6/ActionMenuTestMod/customicons -------------------------------------------------------------------------------- /ActionMenuUtils/ActionMenuUtils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | true 6 | true 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ActionMenuUtils/BuildInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ActionMenuUtils; 3 | using MelonLoader; 4 | using ModJsonGenerator; 5 | 6 | 7 | 8 | [assembly: MelonGame("VRChat", "VRChat")] 9 | [assembly: MelonInfo(typeof(ActionMenuUtils.Main), ModConstants.NAME, ModConstants.VERSION, ModConstants.AUTHOR, ModConstants.DOWNLOAD_LINK)] 10 | [assembly: ModJsonInfo( 11 | 140, 12 | "Lets you respawn, go home, rejoin instance and reset you avatar to the default robot or a custom one all using the action menu", 13 | new []{"action menu", "respawn", "go home", "reset avatar"}, 14 | new []{"[ActionMenuApi](https://api.vrcmg.com/v0/mods/201/ActionMenuApi.dll)", "[UIExpansionKit](https://api.vrcmg.com/v0/mods/55/UIExpansionKit.dll)"}, 15 | "Fixed issues with respawn and go home buttons", 16 | "#2ad9f7" 17 | ) 18 | ] 19 | 20 | [assembly:AssemblyVersion(ModConstants.VERSION)] 21 | [assembly:AssemblyFileVersion(ModConstants.VERSION)] 22 | [assembly:AssemblyTitle(ModConstants.NAME)] 23 | [assembly:AssemblyDescription(ModConstants.NAME)] 24 | [assembly:AssemblyCopyright("Created by " + ModConstants.AUTHOR)] 25 | 26 | namespace ActionMenuUtils; 27 | public static class ModConstants 28 | { 29 | public const string VERSION = "2.0.4"; 30 | public const string NAME = "ActionMenuUtils"; 31 | public const string AUTHOR = "gompo"; 32 | public const string DOWNLOAD_LINK = "https://github.com/gompoc/VRChatMods/releases/"; 33 | } -------------------------------------------------------------------------------- /ActionMenuUtils/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using ActionMenuApi.Api; 5 | using MelonLoader; 6 | using UIExpansionKit.API; 7 | using UnhollowerRuntimeLib; 8 | using UnityEngine; 9 | using VRC; 10 | 11 | namespace ActionMenuUtils 12 | { 13 | public partial class Main : MelonMod 14 | { 15 | private static AssetBundle iconsAssetBundle; 16 | private static Texture2D respawnIcon; 17 | private static Texture2D helpIcon; 18 | private static Texture2D goHomeIcon; 19 | private static Texture2D resetAvatarIcon; 20 | private static Texture2D rejoinInstanceIcon; 21 | 22 | public override void OnApplicationStart() 23 | { 24 | try 25 | { 26 | if (string.IsNullOrEmpty(ID)) return; 27 | //Adapted from knah's JoinNotifier mod found here: https://github.com/knah/VRCMods/blob/master/JoinNotifier/JoinNotifierMod.cs 28 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ActionMenuUtils.icons")) 29 | { 30 | using var tempStream = new MemoryStream((int)stream.Length); 31 | stream.CopyTo(tempStream); 32 | 33 | iconsAssetBundle = AssetBundle.LoadFromMemory_Internal(tempStream.ToArray(), 0); 34 | iconsAssetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; 35 | } 36 | 37 | respawnIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Refresh.png", Il2CppType.Of()).Cast(); 38 | respawnIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 39 | helpIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Help.png", Il2CppType.Of()).Cast(); 40 | helpIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 41 | goHomeIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Home.png", Il2CppType.Of()).Cast(); 42 | goHomeIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 43 | resetAvatarIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Avatar.png", Il2CppType.Of()).Cast(); 44 | resetAvatarIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 45 | rejoinInstanceIcon = iconsAssetBundle.LoadAsset_Internal("Assets/Resources/Pin.png", Il2CppType.Of()).Cast(); 46 | rejoinInstanceIcon.hideFlags |= HideFlags.DontUnloadUnusedAsset; 47 | } 48 | catch (Exception e) { 49 | MelonLogger.Warning("Consider checking for newer version as mod possibly no longer working, Exception occured OnAppStart(): " + e.Message); 50 | } 51 | ModSettings.RegisterSettings(); 52 | ModSettings.Apply(); 53 | SetupAMAPIButtons(); 54 | SetupUIXButtons(); 55 | } 56 | 57 | private static void SetupUIXButtons() 58 | { 59 | ExpansionKitApi.GetExpandedMenu(ExpandedMenu.AvatarMenu).AddSimpleButton("Set AMU Reset Avatar", 60 | () => 61 | { 62 | var avatarId = GameObject.Find("UserInterface/MenuContent/Screens/Avatar/AvatarPreviewBase/MainRoot/MainModel").GetComponent().field_Internal_ApiAvatar_0.id; 63 | var fallbackAvatarId = GameObject.Find("UserInterface/MenuContent/Screens/Avatar/AvatarPreviewBase/FallbackRoot/FallbackModel").GetComponent().field_Internal_ApiAvatar_0?.id; 64 | ModSettings.customAvatarId = avatarId; 65 | ModSettings.fallbackAvatarId = fallbackAvatarId; 66 | ModSettings.Save(); 67 | #if DEBUG 68 | MelonLogger.Msg($"{avatarId},{fallbackAvatarId}"); 69 | #endif 70 | 71 | }, 72 | g => 73 | { 74 | UIXAvatarMenuButton = g; 75 | UIXAvatarMenuButton.active = ModSettings.enableCustomAvatarReset; 76 | }); 77 | 78 | } 79 | 80 | public static GameObject UIXAvatarMenuButton; 81 | 82 | private static void SetupAMAPIButtons() 83 | { 84 | VRCActionMenuPage.AddSubMenu(ActionMenuPage.Options, "SOS", 85 | () => 86 | { 87 | //Respawn 88 | if (ModSettings.confirmRespawn) 89 | CustomSubMenu.AddSubMenu("Respawn", 90 | () => CustomSubMenu.AddButton("Confirm Respawn", Utils.Respawn, respawnIcon), 91 | respawnIcon 92 | ); 93 | else 94 | CustomSubMenu.AddButton("Respawn", Utils.Respawn, respawnIcon); 95 | 96 | //Reset Avatar 97 | if (ModSettings.confirmAvatarReset) 98 | CustomSubMenu.AddSubMenu("Reset Avatar", 99 | () => CustomSubMenu.AddButton("Confirm Reset Avatar", Utils.ResetAvatar, resetAvatarIcon), 100 | resetAvatarIcon 101 | ); 102 | else 103 | CustomSubMenu.AddButton("Reset Avatar", Utils.ResetAvatar, resetAvatarIcon); 104 | 105 | //Instance Rejoin 106 | if (ModSettings.confirmInstanceRejoin) 107 | CustomSubMenu.AddSubMenu("Rejoin Instance", 108 | () => CustomSubMenu.AddButton("Confirm Instance Rejoin", Utils.RejoinInstance, rejoinInstanceIcon), 109 | rejoinInstanceIcon 110 | ); 111 | else 112 | CustomSubMenu.AddButton("Rejoin Instance", Utils.RejoinInstance, rejoinInstanceIcon); 113 | 114 | //Go Home 115 | if (ModSettings.confirmGoHome) 116 | CustomSubMenu.AddSubMenu("Go Home", 117 | () => CustomSubMenu.AddButton("Confirm Go Home",Utils.Home, goHomeIcon), 118 | goHomeIcon 119 | ); 120 | else 121 | CustomSubMenu.AddButton("Go Home",Utils.Home, goHomeIcon); 122 | 123 | }, helpIcon 124 | ); 125 | } 126 | 127 | public override void OnPreferencesLoaded() => ModSettings.Apply(); 128 | public override void OnPreferencesSaved() => ModSettings.Apply(); 129 | 130 | private static string ID = "gompo"; 131 | } 132 | } -------------------------------------------------------------------------------- /ActionMenuUtils/ModSettings.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | 3 | namespace ActionMenuUtils 4 | { 5 | public static class ModSettings 6 | { 7 | private static string categoryName = "ActionMenuRespawn"; //Old Name 8 | private static string categoryDisplayName = "ActionMenuUtils"; //New Name 9 | public static bool confirmRespawn { get; private set; } = false; 10 | public static bool confirmGoHome { get; private set; } = false; 11 | public static bool confirmAvatarReset { get; private set; } = false; 12 | public static bool confirmInstanceRejoin { get; private set; } = true; 13 | //public static bool forceGoHome { get; private set; } = false; 14 | 15 | public static bool enableCustomAvatarReset { get; private set; } = false; 16 | 17 | public static string customAvatarId; 18 | public static string fallbackAvatarId; 19 | 20 | 21 | private static MelonPreferences_Entry ConfirmRespawn, 22 | ConfirmGoHome, 23 | ConfirmAvatarReset, 24 | ConfirmInstanceRejoin, 25 | //ForceGoHome, 26 | EnableCustomAvatarReset; 27 | 28 | private static MelonPreferences_Entry CustomAvatarId, FallbackAvatarId; 29 | 30 | public static void RegisterSettings() 31 | { 32 | var category = MelonPreferences.CreateCategory(categoryName, categoryDisplayName); 33 | ConfirmRespawn = category.CreateEntry("ConfirmRespawn", confirmRespawn, "Add a confirmation for respawn"); 34 | ConfirmGoHome = category.CreateEntry( "ConfirmGoHome", confirmGoHome, "Add a confirmation for go home"); 35 | //ForceGoHome = category.CreateEntry("ForceGoHome", forceGoHome, "Skip the go home popup"); 36 | ConfirmAvatarReset = category.CreateEntry("ConfirmAvatarReset", confirmAvatarReset, "Add a confirmation for avatar reset"); 37 | ConfirmInstanceRejoin = category.CreateEntry("ConfirmInstanceReJoin", confirmInstanceRejoin, "Add a confirmation for rejoin instance"); 38 | EnableCustomAvatarReset = category.CreateEntry("EnableCustomAvatarReset", enableCustomAvatarReset, "Use custom avatar when resetting"); 39 | CustomAvatarId = category.CreateEntry("CustomAvatarId", customAvatarId, "", true) as MelonPreferences_Entry; 40 | FallbackAvatarId = category.CreateEntry("FallbackAvatarId", fallbackAvatarId, "", true) as MelonPreferences_Entry; 41 | } 42 | 43 | public static void Apply() 44 | { 45 | confirmRespawn = ConfirmRespawn.Value; 46 | confirmGoHome = ConfirmGoHome.Value; 47 | //forceGoHome = ForceGoHome.Value; 48 | confirmAvatarReset = ConfirmAvatarReset.Value; 49 | confirmInstanceRejoin = ConfirmInstanceRejoin.Value; 50 | enableCustomAvatarReset = EnableCustomAvatarReset.Value; 51 | customAvatarId = CustomAvatarId.Value; 52 | fallbackAvatarId = FallbackAvatarId.Value; 53 | 54 | if(Main.UIXAvatarMenuButton is not null) 55 | Main.UIXAvatarMenuButton.active = enableCustomAvatarReset; 56 | } 57 | 58 | public static void Save() 59 | { 60 | CustomAvatarId.Value = customAvatarId; 61 | FallbackAvatarId.Value = fallbackAvatarId; 62 | MelonPreferences.Save(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /ActionMenuUtils/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text.RegularExpressions; 5 | using MelonLoader; 6 | using UnhollowerRuntimeLib.XrefScans; 7 | using VRC.Animation; 8 | using VRC.Core; 9 | using VRC.SDKBase; 10 | 11 | namespace ActionMenuUtils 12 | { 13 | internal static class Utils 14 | { 15 | //Gracefully taken from Advanced Invites https://github.com/Psychloor/AdvancedInvites/blob/master/AdvancedInvites/Utilities.cs#L356 16 | private static bool XRefScanFor(this MethodBase methodBase, string searchTerm) 17 | { 18 | return XrefScanner.XrefScan(methodBase).Any( 19 | xref => xref.Type == XrefType.Global && xref.ReadAsObject()?.ToString().IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0); 20 | } 21 | 22 | private static GoHomeDelegate GetGoHomeDelegate 23 | { 24 | get 25 | { 26 | if (goHomeDelegate != null) return goHomeDelegate; 27 | MethodInfo goHomeMethod = typeof(VRCFlowManager).GetMethods(BindingFlags.Public | BindingFlags.Instance).First( 28 | m => m.GetParameters().Length == 0 && m.ReturnType == typeof(void) && m.XRefScanFor("Going to Home Location: ")); 29 | 30 | goHomeDelegate = (GoHomeDelegate)Delegate.CreateDelegate( 31 | typeof(GoHomeDelegate), 32 | VRCFlowManager.prop_VRCFlowManager_0, 33 | goHomeMethod); 34 | return goHomeDelegate; 35 | } 36 | } 37 | 38 | private static void GoHome() => GetGoHomeDelegate(); 39 | private static GoHomeDelegate goHomeDelegate; 40 | private delegate void GoHomeDelegate(); 41 | 42 | private static RespawnDelegate GetRespawnDelegate 43 | { 44 | get 45 | { 46 | if (respawnDelegate != null) return respawnDelegate; 47 | MethodInfo respawnMethod = typeof(VRCPlayer).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Single( 48 | m => m.GetParameters().Length == 0 && m.ReturnType == typeof(void) && m.XRefScanFor("Respawned while not in a room.")); 49 | 50 | respawnDelegate = (RespawnDelegate)Delegate.CreateDelegate( 51 | typeof(RespawnDelegate), 52 | VRCPlayer.field_Internal_Static_VRCPlayer_0, 53 | respawnMethod); 54 | return respawnDelegate; 55 | } 56 | } 57 | 58 | private static RespawnDelegate respawnDelegate; 59 | private delegate void RespawnDelegate(); 60 | 61 | public static void Respawn() 62 | { 63 | GetRespawnDelegate(); 64 | VRCPlayer.field_Internal_Static_VRCPlayer_0.GetComponent().Reset(); 65 | } 66 | 67 | public static void RejoinInstance() 68 | { 69 | var instance = RoomManager.field_Internal_Static_ApiWorldInstance_0; 70 | Networking.GoToRoom($"{instance.world.id}:{instance.instanceId}"); 71 | } 72 | 73 | public static void Home() => GetGoHomeDelegate(); 74 | // { 75 | // if (ModSettings.forceGoHome) 76 | // GoHome(); 77 | // else 78 | // GameObject.Find("UserInterface/Canvas_QuickMenu(Clone)/Container/Window/QMParent/Menu_Dashboard/ScrollRect/Viewport/VerticalLayoutGroup/Buttons_QuickActions/Button_GoHome").GetComponent