├── .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 | -
24 | Info
25 |
26 | -
27 | Getting Started
28 |
29 | - Usage
30 | - License
31 | - Acknowledgements
32 |
33 |
34 |
35 |
36 |
37 |
38 | ## Info
39 |
40 |
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