diff --git a/IKAdjustments/HarmonyPatches.cs b/IKAdjustments/HarmonyPatches.cs new file mode 100644 index 0000000..13f27bd --- /dev/null +++ b/IKAdjustments/HarmonyPatches.cs @@ -0,0 +1,33 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Systems.IK; +using HarmonyLib; +using NAK.IKAdjustments.Systems; + +namespace NAK.IKAdjustments.HarmonyPatches; + +internal static class IKSystemPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(IKSystem), nameof(IKSystem.Start))] + private static void Postfix_IKSystem_Start(ref IKSystem __instance) + { + __instance.gameObject.AddComponent(); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(IKSystem), nameof(IKSystem.ResetIkSettings))] + private static void Postfix_IKSystem_ResetIkSettings() + { + IKAdjuster.Instance.ResetAllOffsets(); + } +} + +internal static class CVR_MenuManagerPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(CVR_MenuManager), nameof(CVR_MenuManager.ToggleQuickMenu), typeof(bool))] + private static void Postfix_CVR_MenuManager_ToggleQuickMenu(bool show) + { + if (show) IKAdjuster.Instance.ExitAdjustMode(); + } +} \ No newline at end of file diff --git a/IKAdjustments/IKAdjuster.cs b/IKAdjustments/IKAdjuster.cs new file mode 100644 index 0000000..a5ba4d0 --- /dev/null +++ b/IKAdjustments/IKAdjuster.cs @@ -0,0 +1,204 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.IK.SubSystems; +using ABI_RC.Systems.InputManagement; +using UnityEngine; + +namespace NAK.IKAdjustments.Systems; + +public class IKAdjuster : MonoBehaviour +{ + #region Grab Definitions + + public class GrabState + { + public bool handGrabbed; + public TrackingPoint tracker; + public Vector3 displayOffset; + public Quaternion displayOffsetRotation; + public GrabState otherGrab; + } + + public enum AdjustMode + { + Position = 0, + Rotation, + Both + } + + #endregion + + public static IKAdjuster Instance; + + public bool isAdjustMode; + + public float Setting_MaxGrabDistance = 0.2f; + public AdjustMode Setting_AdjustMode = AdjustMode.Position; + + private GrabState leftGrabState = new(); + private GrabState rightGrabState = new(); + + #region Unity Events + + private void Start() + { + Instance = this; + + leftGrabState.otherGrab = rightGrabState; + rightGrabState.otherGrab = leftGrabState; + } + + private void Update() + { + if (!isAdjustMode) return; + if (BodySystem.isCalibrating) + { + isAdjustMode = false; + return; + } + + UpdateGrabbing(true, ref leftGrabState); + UpdateGrabbing(false, ref rightGrabState); + } + + #endregion + + #region Public Methods + + public void EnterAdjustMode() + { + if (isAdjustMode) + return; + + isAdjustMode = true; + IKSystem.Instance.SetTrackingPointVisibility(true); + CVR_MenuManager.Instance.ToggleQuickMenu(false); + foreach (TrackingPoint tracker in IKSystem.Instance.AllTrackingPoints) tracker.ClearLineTarget(); + } + + public void ExitAdjustMode() + { + if (!isAdjustMode) + return; + + isAdjustMode = false; + IKSystem.Instance.SetTrackingPointVisibility(false); + } + + public void ResetAllOffsets() + { + foreach (TrackingPoint tracker in IKSystem.Instance.AllTrackingPoints) + { + tracker.offsetTransform.SetParent(tracker.displayObject.transform, true); + tracker.displayObject.transform.localPosition = Vector3.zero; + tracker.displayObject.transform.localRotation = Quaternion.identity; + tracker.offsetTransform.SetParent(tracker.referenceTransform, true); + } + } + + public void CycleAdjustMode() + { + var currentValue = (int)Setting_AdjustMode; + var numValues = Enum.GetValues(typeof(AdjustMode)).Length; + var nextValue = (currentValue + 1) % numValues; + Setting_AdjustMode = (AdjustMode)nextValue; + } + + #endregion + + #region Private Methods + + private void UpdateGrabbing(bool isLeft, ref GrabState grabState) + { + var isGrabbing = isLeft + ? CVRInputManager.Instance.gripLeftValue > 0.9f + : CVRInputManager.Instance.gripRightValue > 0.9f; + var isInteracting = isLeft + ? CVRInputManager.Instance.interactLeftValue > 0.9f + : CVRInputManager.Instance.interactRightValue > 0.9f; + Transform handTracker = isLeft + ? IKSystem.Instance.leftHandTracker.transform + : IKSystem.Instance.rightHandTracker.transform; + + if (grabState.tracker == null && !grabState.handGrabbed && isGrabbing) + { + OnGrab(handTracker, grabState); + } + else if (grabState.tracker != null) + { + if (!isGrabbing) + OnRelease(grabState); + else if (isInteracting) + OnReset(grabState); + else + Holding(handTracker, grabState); + } + + grabState.handGrabbed = isGrabbing; + } + + private void OnGrab(Transform handTracker, GrabState grabState) + { + Transform nearestTransform = FindNearestTransform(handTracker); + if (nearestTransform != null && + Vector3.Distance(nearestTransform.GetChild(0).position, handTracker.position) <= + Setting_MaxGrabDistance) + { + grabState.tracker = + IKSystem.Instance.AllTrackingPoints.Find(tp => tp.referenceTransform == nearestTransform); + if (grabState.otherGrab.tracker == grabState.tracker) OnRelease(grabState.otherGrab); + grabState.displayOffset = grabState.tracker.displayObject.transform.position - handTracker.position; + grabState.displayOffsetRotation = Quaternion.Inverse(handTracker.rotation) * + grabState.tracker.displayObject.transform.rotation; + grabState.tracker.offsetTransform.SetParent(grabState.tracker.displayObject.transform, true); + } + } + + private void OnRelease(GrabState grabState) + { + grabState.tracker.offsetTransform.SetParent(grabState.tracker.referenceTransform, true); + grabState.tracker.ClearLineTarget(); + grabState.tracker = null; + } + + private void OnReset(GrabState grabState) + { + grabState.tracker.displayObject.transform.localRotation = Quaternion.identity; + grabState.tracker.displayObject.transform.localPosition = Vector3.zero; + + grabState.tracker.offsetTransform.SetParent(grabState.tracker.referenceTransform, true); + grabState.tracker.ClearLineTarget(); + grabState.tracker = null; + } + + private void Holding(Transform handTracker, GrabState grabState) + { + switch (Setting_AdjustMode) + { + case AdjustMode.Position: + grabState.tracker.displayObject.transform.position = handTracker.position + grabState.displayOffset; + break; + case AdjustMode.Rotation: + grabState.tracker.displayObject.transform.rotation = + handTracker.rotation * grabState.displayOffsetRotation; + break; + case AdjustMode.Both: + grabState.tracker.displayObject.transform.rotation = + handTracker.rotation * grabState.displayOffsetRotation; + grabState.tracker.displayObject.transform.position = handTracker.position + grabState.displayOffset; + break; + } + + grabState.tracker.SetLineTarget(grabState.tracker.referenceTransform.position); + } + + private Transform FindNearestTransform(Transform handTransform) + { + var validTrackingPointTransforms = IKSystem.ValidTrackingPointTransforms; + if (validTrackingPointTransforms == null || validTrackingPointTransforms.Count == 0) return null; + return validTrackingPointTransforms + .OrderBy(t => Vector3.Distance(handTransform.position, t.GetChild(0).position)).FirstOrDefault(); + } + + #endregion +} \ No newline at end of file diff --git a/IKAdjustments/IKAdjustments.csproj b/IKAdjustments/IKAdjustments.csproj index 66a50a8..5b04c11 100644 --- a/IKAdjustments/IKAdjustments.csproj +++ b/IKAdjustments/IKAdjustments.csproj @@ -1,2 +1,23 @@ - + + + + netstandard2.1 + + + + + + + + + + + + + $(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll + False + + + + \ No newline at end of file diff --git a/IKAdjustments/Integrations/BTKUIAddon.cs b/IKAdjustments/Integrations/BTKUIAddon.cs new file mode 100644 index 0000000..f3cafb5 --- /dev/null +++ b/IKAdjustments/Integrations/BTKUIAddon.cs @@ -0,0 +1,43 @@ +using System.Runtime.CompilerServices; +using BTKUILib; +using BTKUILib.UIObjects; +using MelonLoader; +using NAK.IKAdjustments.Systems; + +namespace NAK.IKAdjustments.Integrations; + +public static class BTKUIAddon +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Initialize() + { + //Add myself to the Misc Menu + Page miscPage = QuickMenuAPI.MiscTabPage; + Category miscCategory = miscPage.AddCategory(IKAdjustments.SettingsCategory); + + // Add button + miscCategory.AddButton("Tracking Adjust", "", + "Adjust tracking points in this mode. Grip to adjust. Trigger to reset.") + .OnPress += () => { IKAdjuster.Instance.EnterAdjustMode(); }; + + // Reset Button + miscCategory.AddButton("Reset Offsets", "", "Reset all tracked point offsets.") + .OnPress += () => { IKAdjuster.Instance.ResetAllOffsets(); }; + + // Cyle GrabMode Button + miscCategory.AddButton("Cycle Mode", "", "Cycle grab mode. Position, Rotation, or Both.") + .OnPress += () => { IKAdjuster.Instance.CycleAdjustMode(); }; + } + + private static void AddMelonToggle(ref Category category, MelonPreferences_Entry entry) + { + category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; + } + + private static void AddMelonSlider(ref Page page, MelonPreferences_Entry entry, float min, float max, + int decimalPlaces = 2) + { + page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += + f => entry.Value = f; + } +} \ No newline at end of file diff --git a/IKAdjustments/Main.cs b/IKAdjustments/Main.cs new file mode 100644 index 0000000..6152110 --- /dev/null +++ b/IKAdjustments/Main.cs @@ -0,0 +1,39 @@ +using MelonLoader; +using NAK.IKAdjustments.HarmonyPatches; +using NAK.IKAdjustments.Integrations; + +namespace NAK.IKAdjustments; + +public class IKAdjustments : MelonMod +{ + internal static string SettingsCategory = nameof(IKAdjustments); + + public override void OnInitializeMelon() + { + ApplyPatches(typeof(IKSystemPatches)); + + InitializeIntegration("BTKUILib", BTKUIAddon.Initialize); + } + + private void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + LoggerInstance.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); + } + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } +} \ No newline at end of file diff --git a/IKAdjustments/Properties/AssemblyInfo.cs b/IKAdjustments/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cc433a9 --- /dev/null +++ b/IKAdjustments/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using MelonLoader; +using NAK.IKAdjustments.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.IKAdjustments))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.IKAdjustments))] + +[assembly: MelonInfo( + typeof(NAK.IKAdjustments.IKAdjustments), + nameof(NAK.IKAdjustments), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKAdjustments" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonOptionalDependencies("BTKUILib")] +[assembly: MelonColor(255, 155, 89, 182)] +[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: HarmonyDontPatchAll] + +namespace NAK.IKAdjustments.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file