diff --git a/PickupPushPull/HarmonyPatches.cs b/PickupPushPull/HarmonyPatches.cs new file mode 100644 index 0000000..885ce60 --- /dev/null +++ b/PickupPushPull/HarmonyPatches.cs @@ -0,0 +1,34 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using HarmonyLib; +using UnityEngine; +using NAK.Melons.PickupPushPull.InputModules; + +namespace NAK.Melons.PickupPushPull.HarmonyPatches; + +[HarmonyPatch] +internal class HarmonyPatches +{ + //uses code from https://github.com/ljoonal/CVR-Plugins/tree/main/RotateIt + //GPL-3.0 license - Thank you ljoonal for being smart brain :plead: + [HarmonyPostfix] + [HarmonyPatch(typeof(CVRPickupObject), "Update")] + public static void GrabbedObjectPatch(ref CVRPickupObject __instance) + { + // Need to only run when the object is grabbed by the local player + if (__instance._controllerRay == null) return; + + //and only if its a prop we support + if (__instance.gripType == CVRPickupObject.GripType.Origin) return; + + Quaternion originalRotation = __instance.transform.rotation; + Transform referenceTransform = __instance._controllerRay.transform; + + __instance.transform.RotateAround(__instance.transform.position, referenceTransform.right, PickupPushPull_Module.Instance.objectRotation.y * Time.deltaTime); + __instance.transform.RotateAround(__instance.transform.position, referenceTransform.up, PickupPushPull_Module.Instance.objectRotation.x * Time.deltaTime); + + // Add the new difference between the og rotation and our newly added rotation the the stored offset. + __instance.initialRotationalOffset *= Quaternion.Inverse(__instance.transform.rotation) * originalRotation; + } +} \ No newline at end of file diff --git a/PickupPushPull/InputModules/Info/EI_SteamVR_Info.cs b/PickupPushPull/InputModules/Info/EI_SteamVR_Info.cs new file mode 100644 index 0000000..49410d7 --- /dev/null +++ b/PickupPushPull/InputModules/Info/EI_SteamVR_Info.cs @@ -0,0 +1,33 @@ +using ABI_RC.Core.Savior; +using System.Reflection; + +namespace NAK.Melons.PickupPushPull.InputModules.Info; + +// Stolen from my scrapped Enhanced Input mod +internal class EI_SteamVR_Info +{ + //General Inputs + internal static readonly FieldInfo im_vrMovementAction = typeof(InputModuleSteamVR).GetField("vrMovementAction", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrJumpAction = typeof(InputModuleSteamVR).GetField("vrJumpAction", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrLookAction = typeof(InputModuleSteamVR).GetField("vrLookAction", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrMuteAction = typeof(InputModuleSteamVR).GetField("vrMuteAction", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrMenuButton = typeof(InputModuleSteamVR).GetField("vrMenuButton", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrTriggerValue = typeof(InputModuleSteamVR).GetField("vrTriggerValue", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrGripValue = typeof(InputModuleSteamVR).GetField("vrGripValue", BindingFlags.NonPublic | BindingFlags.Instance); + //Vive Controllers + internal static readonly FieldInfo im_vrTouchPadValue = typeof(InputModuleSteamVR).GetField("vrTouchPadValue", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrTouchPadClick = typeof(InputModuleSteamVR).GetField("vrTouchPadClick", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_vrTouchPadTouch = typeof(InputModuleSteamVR).GetField("vrTouchPadTouch", BindingFlags.NonPublic | BindingFlags.Instance); + //Knuckles Controllers + internal static readonly FieldInfo im_steamVrIndexSkeletonLeft = typeof(InputModuleSteamVR).GetField("steamVrIndexSkeletonLeft", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrIndexSkeletonRight = typeof(InputModuleSteamVR).GetField("steamVrIndexSkeletonRight", BindingFlags.NonPublic | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrIndexGestureToggle = typeof(InputModuleSteamVR).GetField("steamVrIndexGestureToggle", BindingFlags.NonPublic | BindingFlags.Instance); + //Touch Controllers + internal static readonly FieldInfo im_steamVrTriggerTouch = typeof(InputModuleSteamVR).GetField("steamVrTriggerTouch", BindingFlags.Public | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrGripTouch = typeof(InputModuleSteamVR).GetField("steamVrGripTouch", BindingFlags.Public | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrStickTouch = typeof(InputModuleSteamVR).GetField("steamVrStickTouch", BindingFlags.Public | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrButtonATouch = typeof(InputModuleSteamVR).GetField("steamVrButtonATouch", BindingFlags.Public | BindingFlags.Instance); + internal static readonly FieldInfo im_steamVrButtonBTouch = typeof(InputModuleSteamVR).GetField("steamVrButtonBTouch", BindingFlags.Public | BindingFlags.Instance); + //SteamVR Specific + //internal static readonly FieldInfo im_steamVrVibration = typeof(InputModuleSteamVR).GetField("steamVrVibration", BindingFlags.NonPublic | BindingFlags.Instance); +} diff --git a/PickupPushPull/InputModules/PickupPushPull_Module.cs b/PickupPushPull/InputModules/PickupPushPull_Module.cs new file mode 100644 index 0000000..bcd6ce5 --- /dev/null +++ b/PickupPushPull/InputModules/PickupPushPull_Module.cs @@ -0,0 +1,201 @@ +using ABI.CCK.Components; +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using NAK.Melons.PickupPushPull.InputModules.Info; +using System.Reflection; +using UnityEngine; +using UnityEngine.Events; +using Valve.VR; + +namespace NAK.Melons.PickupPushPull.InputModules; + +public class PickupPushPull_Module : CVRInputModule +{ + //Reflection shit + private static readonly FieldInfo _grabbedObject = typeof(ControllerRay).GetField("grabbedObject", BindingFlags.NonPublic | BindingFlags.Instance); + + //Global stuff + public static PickupPushPull_Module Instance; + public Vector2 objectRotation = Vector2.zero; + + //Global settings + public float Setting_PushPullSpeed = 100f; + public float Setting_RotationSpeed = 200f; + public bool Setting_EnableRotation = false; + + //Desktop settings + public bool Desktop_UseZoomForRotate = true; + + //VR settings + public BindingOptionsVR.BindHand VR_RotateHand = BindingOptionsVR.BindHand.LeftHand; + public BindingOptionsVR.BindingOptions VR_RotateBind = BindingOptionsVR.BindingOptions.ButtonATouch; + private SteamVR_Action_Boolean VR_RotateBind_Boolean; + + //Local stuff + private CVRInputManager _inputManager; + private ControllerRay desktopControllerRay; + private float deadzoneRightValue; + private bool controlGamepadEnabled; + + //SteamVR Input + private SteamVR_Action_Vector2 vrLookAction; + private SteamVR_Action_Boolean steamVrTriggerTouch; + private SteamVR_Action_Boolean steamVrGripTouch; + private SteamVR_Action_Boolean steamVrStickTouch; + private SteamVR_Action_Boolean steamVrButtonATouch; + private SteamVR_Action_Boolean steamVrButtonBTouch; + + public new void Start() + { + _inputManager = CVRInputManager.Instance; + Instance = this; + base.Start(); + + //Get desktop controller ray + desktopControllerRay = PlayerSetup.Instance.desktopCamera.GetComponent(); + + //Touch Controllers + InputModuleSteamVR inputModuleSteamVR = GetComponent(); + vrLookAction = (SteamVR_Action_Vector2)EI_SteamVR_Info.im_vrLookAction.GetValue(inputModuleSteamVR); + steamVrTriggerTouch = (SteamVR_Action_Boolean)EI_SteamVR_Info.im_steamVrTriggerTouch.GetValue(inputModuleSteamVR); + steamVrGripTouch = (SteamVR_Action_Boolean)EI_SteamVR_Info.im_steamVrGripTouch.GetValue(inputModuleSteamVR); + steamVrStickTouch = (SteamVR_Action_Boolean)EI_SteamVR_Info.im_steamVrStickTouch.GetValue(inputModuleSteamVR); + steamVrButtonATouch = (SteamVR_Action_Boolean)EI_SteamVR_Info.im_steamVrButtonATouch.GetValue(inputModuleSteamVR); + steamVrButtonBTouch = (SteamVR_Action_Boolean)EI_SteamVR_Info.im_steamVrButtonBTouch.GetValue(inputModuleSteamVR); + + controlGamepadEnabled = (bool)MetaPort.Instance.settings.GetSettingsBool("ControlEnableGamepad", false); + MetaPort.Instance.settings.settingBoolChanged.AddListener(new UnityAction(SettingsBoolChanged)); + + deadzoneRightValue = (float)MetaPort.Instance.settings.GetSettingInt("ControlDeadZoneRight", 0) / 100f; + MetaPort.Instance.settings.settingIntChanged.AddListener(new UnityAction(SettingsIntChanged)); + + UpdateVRBinding(); + } + + private void SettingsBoolChanged(string name, bool value) + { + if (name == "ControlEnableGamepad") + controlGamepadEnabled = value; + } + + private void SettingsIntChanged(string name, int value) + { + if (name == "ControlDeadZoneRight") + deadzoneRightValue = (float)value / 100f; + } + + public void UpdateVRBinding() + { + switch (VR_RotateBind) + { + case BindingOptionsVR.BindingOptions.ButtonATouch: + VR_RotateBind_Boolean = steamVrButtonATouch; + break; + case BindingOptionsVR.BindingOptions.ButtonBTouch: + VR_RotateBind_Boolean = steamVrButtonBTouch; + break; + case BindingOptionsVR.BindingOptions.StickTouch: + VR_RotateBind_Boolean = steamVrStickTouch; + break; + case BindingOptionsVR.BindingOptions.TriggerTouch: + VR_RotateBind_Boolean = steamVrTriggerTouch; + break; + case BindingOptionsVR.BindingOptions.GripTouch: + VR_RotateBind_Boolean = steamVrGripTouch; + break; + default: + break; + } + } + + //this will run while menu is being hovered + public override void UpdateImportantInput() + { + objectRotation = Vector2.zero; + } + + //this will only run outside of menus + public override void UpdateInput() + { + objectRotation = Vector2.zero; + + CVRPickupObject desktopObject = (CVRPickupObject)_grabbedObject.GetValue(desktopControllerRay); + if (desktopObject != null && desktopObject.gripType == CVRPickupObject.GripType.Free) + { + //Desktop Input + DoDesktopInput(); + //Gamepad Input + DoGamepadInput(); + } + + //VR Input + if (!MetaPort.Instance.isUsingVr) return; + DoSteamVRInput(); + } + + private void DoDesktopInput() + { + if (!Desktop_UseZoomForRotate) return; + + //mouse rotation when zoomed + if (Setting_EnableRotation && _inputManager.zoom) + { + objectRotation.x += Setting_RotationSpeed * _inputManager.rawLookVector.x; + objectRotation.y += Setting_RotationSpeed * _inputManager.rawLookVector.y * -1; + _inputManager.lookVector = Vector2.zero; + _inputManager.zoom = false; + return; + } + } + + private void DoGamepadInput() + { + if (!controlGamepadEnabled) return; + + //not sure how to make settings for this + bool button1 = Input.GetButton("Controller Left Button"); + bool button2 = Input.GetButton("Controller Right Button"); + + if (button1 || button2) + { + //Rotation + if (Setting_EnableRotation && button2) + { + objectRotation.x += Setting_RotationSpeed * _inputManager.rawLookVector.x; + objectRotation.y += Setting_RotationSpeed * _inputManager.rawLookVector.y * -1; + _inputManager.lookVector = Vector2.zero; + return; + } + + _inputManager.objectPushPull += _inputManager.rawLookVector.y * Setting_PushPullSpeed * Time.deltaTime; + _inputManager.lookVector = Vector2.zero; + } + } + + private void DoSteamVRInput() + { + CVRPickupObject leftObject = (CVRPickupObject)_grabbedObject.GetValue(PlayerSetup.Instance.leftRay); + CVRPickupObject rightObject = (CVRPickupObject)_grabbedObject.GetValue(PlayerSetup.Instance.rightRay); + if (leftObject == null && rightObject == null) return; + + bool canRotate = (leftObject != null && leftObject.gripType == CVRPickupObject.GripType.Free) || + (rightObject != null && rightObject.gripType == CVRPickupObject.GripType.Free); + + if (Setting_EnableRotation && canRotate && VR_RotateBind_Boolean.GetState((SteamVR_Input_Sources)VR_RotateHand)) + { + Vector2 rawLookVector = new Vector2(CVRTools.AxisDeadZone(vrLookAction.GetAxis(SteamVR_Input_Sources.Any).x, deadzoneRightValue, true), + CVRTools.AxisDeadZone(vrLookAction.GetAxis(SteamVR_Input_Sources.Any).y, deadzoneRightValue, true)); + + objectRotation.x += Setting_RotationSpeed * rawLookVector.x; + objectRotation.y += Setting_RotationSpeed * rawLookVector.y * -1; + + _inputManager.lookVector = Vector2.zero; + return; + } + + CVRInputManager.Instance.objectPushPull += CVRInputManager.Instance.floatDirection * Setting_PushPullSpeed * Time.deltaTime; + } + +} \ No newline at end of file diff --git a/PickupPushPull/Main.cs b/PickupPushPull/Main.cs new file mode 100644 index 0000000..7b4db34 --- /dev/null +++ b/PickupPushPull/Main.cs @@ -0,0 +1,108 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using HarmonyLib; +using MelonLoader; +using UnityEngine; +using Valve.VR; +using NAK.Melons.PickupPushPull.InputModules; + +namespace NAK.Melons.PickupPushPull; + +public class PickupPushPull : MelonMod +{ + private static MelonPreferences_Category Category_PickupPushPull; + private static MelonPreferences_Entry Setting_PushPullSpeed, Setting_RotateSpeed; + private static MelonPreferences_Entry Setting_EnableRotation, Setting_Desktop_UseZoomForRotate; + private static MelonPreferences_Entry Setting_VR_RotateHand; + private static MelonPreferences_Entry Setting_VR_RotateBind; + + public override void OnInitializeMelon() + { + Category_PickupPushPull = MelonPreferences.CreateCategory(nameof(PickupPushPull)); + + //Global settings + Setting_PushPullSpeed = Category_PickupPushPull.CreateEntry("Push Pull Speed", 2f, description: "Up/down on right joystick for VR. Left buSettingr + Up/down on right joystick for Gamepad."); + Setting_RotateSpeed = Category_PickupPushPull.CreateEntry("Rotate Speed", 6f); + Setting_EnableRotation = Category_PickupPushPull.CreateEntry("Enable Rotation", false, description: "Hold left trigger in VR or right buSettingr on Gamepad."); + + //Desktop settings + Setting_Desktop_UseZoomForRotate = Category_PickupPushPull.CreateEntry("Desktop Use Zoom For Rotate", true, description: "Use zoom bind for rotation while a prop is held."); + + //VR settings + Setting_VR_RotateHand = Category_PickupPushPull.CreateEntry("VR Hand", BindingOptionsVR.BindHand.LeftHand); + + //bruh + foreach (var setting in Category_PickupPushPull.Entries) + { + setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings); + } + + //special setting + Setting_VR_RotateBind = Category_PickupPushPull.CreateEntry("VR Binding", BindingOptionsVR.BindingOptions.ButtonATouch); + Setting_VR_RotateBind.OnEntryValueChangedUntyped.Subscribe(OnUpdateVRBinding); + + MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); + } + + + System.Collections.IEnumerator WaitForLocalPlayer() + { + while (PlayerSetup.Instance == null) + yield return null; + + CVRInputManager.Instance.gameObject.AddComponent(); + + //update BlackoutController settings after it initializes + while (PickupPushPull_Module.Instance == null) + yield return null; + + UpdateVRBinding(); + UpdateAllSettings(); + } + + private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); + private void OnUpdateVRBinding(object arg1, object arg2) => UpdateVRBinding(); + + private void UpdateAllSettings() + { + if (!PickupPushPull_Module.Instance) return; + + //Global settings + PickupPushPull_Module.Instance.Setting_PushPullSpeed = Setting_PushPullSpeed.Value * 50; + PickupPushPull_Module.Instance.Setting_RotationSpeed = Setting_RotateSpeed.Value * 50; + PickupPushPull_Module.Instance.Setting_EnableRotation = Setting_EnableRotation.Value; + //Desktop settings + PickupPushPull_Module.Instance.Desktop_UseZoomForRotate = Setting_Desktop_UseZoomForRotate.Value; + //VR settings + PickupPushPull_Module.Instance.VR_RotateHand = Setting_VR_RotateHand.Value; + } + + private void UpdateVRBinding() + { + //VR special settings + PickupPushPull_Module.Instance.VR_RotateBind = Setting_VR_RotateBind.Value; + PickupPushPull_Module.Instance.UpdateVRBinding(); + } +} + +public class BindingOptionsVR +{ + public enum BindHand + { + Any, + LeftHand, + RightHand + } + public enum BindingOptions + { + //Only oculus bindings have by default + ButtonATouch, + ButtonBTouch, + TriggerTouch, + //doesnt work? + StickTouch, + //Index only + GripTouch + } +} \ No newline at end of file diff --git a/PickupPushPull/PickupPushPull.csproj b/PickupPushPull/PickupPushPull.csproj new file mode 100644 index 0000000..b987c6c --- /dev/null +++ b/PickupPushPull/PickupPushPull.csproj @@ -0,0 +1,37 @@ + + + + + net472 + enable + latest + false + + + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\SteamVR.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll + + + + + + + + + diff --git a/PickupPushPull/PickupPushPull.sln b/PickupPushPull/PickupPushPull.sln new file mode 100644 index 0000000..670bd55 --- /dev/null +++ b/PickupPushPull/PickupPushPull.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PickupPushPull", "PickupPushPull.csproj", "{817132E7-2DE5-412F-A34C-A325C368F42B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {817132E7-2DE5-412F-A34C-A325C368F42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {817132E7-2DE5-412F-A34C-A325C368F42B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {817132E7-2DE5-412F-A34C-A325C368F42B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {817132E7-2DE5-412F-A34C-A325C368F42B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D636F9FB-C0CF-40CE-8105-E5238CDE0902} + EndGlobalSection +EndGlobal diff --git a/PickupPushPull/Properties/AssemblyInfo.cs b/PickupPushPull/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ec73d05 --- /dev/null +++ b/PickupPushPull/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using NAK.Melons.PickupPushPull.Properties; +using MelonLoader; +using System.Reflection; + + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.Melons.PickupPushPull))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.Melons.PickupPushPull))] + +[assembly: MelonInfo( + typeof(NAK.Melons.PickupPushPull.PickupPushPull), + nameof(NAK.Melons.PickupPushPull), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/PickupPushPull" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace NAK.Melons.PickupPushPull.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "3.0.2"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/PickupPushPull/format.json b/PickupPushPull/format.json new file mode 100644 index 0000000..661d26e --- /dev/null +++ b/PickupPushPull/format.json @@ -0,0 +1,23 @@ +{ + "_id": 91, + "name": "PickupPushPull", + "modversion": "3.0.2", + "gameversion": "2022r170", + "loaderversion": "0.5.7", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Allows you to push & pull pickups with Mouse, Gamepad, & VR.\nCan also optionally rotate props via keybind.\n\nIndex users will need to manually bind the missing SteamVR binds.", + "searchtags": [ + "pull", + "push", + "pickup", + "prop" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/PickupPushPull/releases/download/v3.0.2/PickupPushPull.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/PickupPushPull/", + "changelog": "- Fixed issue where ControlEnableGamepad setting was improperly checked on startup.", + "embedcolor": "804221" +} \ No newline at end of file diff --git a/README.md b/README.md index 738b698..7394748 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,22 @@ https://user-images.githubusercontent.com/37721153/189479474-41e93dff-a695-42f2- Disables the CVRPathCameraController by default while keeping the flight binding. Using UIExpansionKit or similar you can toggle both while in game. +# PickupPushPull +Allows you to push & pull props with Gamepad and VR. + +Simply maps Gamepad & VR joystick input to objectPushPull. + +Hold left bumper on Gamepad to use objectPushPull. + +You can also rotate props while holding down the selected bind in VR, or right bumper on Gamepad. + +Desktop can use the zoom bind while holding props without pickup origin to rotate with mouse. + +As you can tell, i have no fucking clue how to use GitHub. + + +https://user-images.githubusercontent.com/37721153/188521473-9d180795-785a-4ba0-b97f-1e9163d1ba14.mp4 + ---