From 74803a0e09b93d68eb17b544b5ace398f4eabca1 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Tue, 11 Jul 2023 22:53:09 -0500 Subject: [PATCH] [AlternateIKSystem] Add BTKUI support. Fixed teleporting not resetting IK. --- AlternateIKSystem/HarmonyPatches.cs | 9 ++- AlternateIKSystem/IK/IKCalibrator.cs | 2 +- .../IK/IKHandlers/IKHandlerDesktop.cs | 33 +++----- AlternateIKSystem/IK/IKManager.cs | 11 ++- AlternateIKSystem/Integrations/BTKUIAddon.cs | 80 +++++++++++++++++++ AlternateIKSystem/Main.cs | 11 +++ AlternateIKSystem/ModSettings.cs | 51 +++++++++++- 7 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 AlternateIKSystem/Integrations/BTKUIAddon.cs diff --git a/AlternateIKSystem/HarmonyPatches.cs b/AlternateIKSystem/HarmonyPatches.cs index 11f4073..86a20bf 100644 --- a/AlternateIKSystem/HarmonyPatches.cs +++ b/AlternateIKSystem/HarmonyPatches.cs @@ -86,12 +86,13 @@ internal class PlayerSetupPatches { try { - CVRMovementParent currentParent = __instance._movementSystem._currentParent; - if (currentParent?._referencePoint == null) + if (IKManager.Instance == null) return; - if (IKManager.Instance != null) - __runOriginal = !IKManager.Instance.OnPlayerHandleMovementParent(currentParent); + CVRMovementParent currentParent = __instance._movementSystem._currentParent; + __runOriginal = currentParent?._referencePoint != null + ? IKManager.Instance.OnPlayerHandleMovementParent(currentParent) + : IKManager.Instance.OnPlayerTeleported(); } catch (Exception e) { diff --git a/AlternateIKSystem/IK/IKCalibrator.cs b/AlternateIKSystem/IK/IKCalibrator.cs index 17e92c8..545f8af 100644 --- a/AlternateIKSystem/IK/IKCalibrator.cs +++ b/AlternateIKSystem/IK/IKCalibrator.cs @@ -13,7 +13,7 @@ internal static class IKCalibrator vrik = animator.gameObject.AddComponent(); vrik.AutoDetectReferences(); - if (!ModSettings.EntryUseVRIKToes.Value) + if (!ModSettings.EntryUseToesForVRIK.Value) { vrik.references.leftToes = null; vrik.references.rightToes = null; diff --git a/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs b/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs index c7bb41f..5ca6cdb 100644 --- a/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs +++ b/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs @@ -70,27 +70,16 @@ internal class IKHandlerDesktop : IKHandler #region VRIK Solver Events - //TODO: properly expose these settings - - private bool EntryPlantFeet = true; - - private float EntryBodyLeanWeight = 1f; - private bool EntryProneThrusting = true; - - private float EntryBodyHeadingLimit = 30f; - private float EntryPelvisHeadingWeight = 0.25f; - private float EntryChestHeadingWeight = 0.75f; - private float _ikSimulatedRootAngle = 0f; private void OnPreSolverUpdate() { - _solver.plantFeet = EntryPlantFeet; + _solver.plantFeet = ModSettings.EntryPlantFeet.Value; // Emulate old VRChat hip movement - if (EntryBodyLeanWeight > 0) + if (ModSettings.EntryBodyLeanWeight.Value > 0) { - float weightedAngle = EntryProneThrusting ? 1f : EntryBodyLeanWeight * _solver.locomotion.weight; + float weightedAngle = ModSettings.EntryProneThrusting.Value ? 1f : ModSettings.EntryBodyLeanWeight.Value * _solver.locomotion.weight; float angle = IKManager.Instance._desktopCamera.localEulerAngles.x; angle = angle > 180 ? angle - 360 : angle; Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, _vrik.transform.right); @@ -98,9 +87,9 @@ internal class IKHandlerDesktop : IKHandler } // Make root heading follow within a set limit - if (EntryBodyHeadingLimit > 0) + if (ModSettings.EntryBodyHeadingLimit.Value > 0) { - float weightedAngleLimit = EntryBodyHeadingLimit * _solver.locomotion.weight; + float weightedAngleLimit = ModSettings.EntryBodyHeadingLimit.Value * _solver.locomotion.weight; float deltaAngleRoot = Mathf.DeltaAngle(IKManager.Instance.transform.eulerAngles.y, _ikSimulatedRootAngle); float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot); @@ -109,18 +98,18 @@ internal class IKHandlerDesktop : IKHandler deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit; _ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, IKManager.Instance.transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit); } - + _solver.spine.rootHeadingOffset = deltaAngleRoot; - if (EntryPelvisHeadingWeight > 0) + if (ModSettings.EntryPelvisHeadingWeight.Value > 0) { - _solver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * EntryPelvisHeadingWeight, 0f); - _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * EntryPelvisHeadingWeight, 0f); + _solver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f); + _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f); } - if (EntryChestHeadingWeight > 0) + if (ModSettings.EntryChestHeadingWeight.Value > 0) { - _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * EntryChestHeadingWeight, 0f); + _solver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryChestHeadingWeight.Value, 0f); } } } diff --git a/AlternateIKSystem/IK/IKManager.cs b/AlternateIKSystem/IK/IKManager.cs index cfe2229..6e5cbd4 100644 --- a/AlternateIKSystem/IK/IKManager.cs +++ b/AlternateIKSystem/IK/IKManager.cs @@ -20,7 +20,7 @@ public class IKManager : MonoBehaviour private static LookAtIK _lookAtIk; public static LookAtIK lookAtIk => _lookAtIk; - private bool _isAvatarInitialized = false; + private bool _isAvatarInitialized; // IK Handling private IKHandler _ikHandler; @@ -160,6 +160,15 @@ public class IKManager : MonoBehaviour return true; } + public bool OnPlayerTeleported() + { + if (!_isAvatarInitialized) + return false; + + _vrik?.solver.Reset(); + return true; + } + #endregion #region Private Methods diff --git a/AlternateIKSystem/Integrations/BTKUIAddon.cs b/AlternateIKSystem/Integrations/BTKUIAddon.cs new file mode 100644 index 0000000..2c03496 --- /dev/null +++ b/AlternateIKSystem/Integrations/BTKUIAddon.cs @@ -0,0 +1,80 @@ +using BTKUILib; +using BTKUILib.UIObjects; +using System.Runtime.CompilerServices; + +namespace NAK.AlternateIKSystem.Integrations; + +public static class BTKUIAddon +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Initialize() + { + // Add mod to the Misc Menu + Page miscPage = QuickMenuAPI.MiscTabPage; + Category miscCategory = miscPage.AddCategory(ModSettings.SettingsCategory); + + AddMelonToggle(ref miscCategory, ModSettings.EntryEnabled); + + SetupGeneralIKConfigurationPage(ref miscCategory); + SetupDesktopIKConfigurationPage(ref miscCategory); + //SetupHalfBodyIKConfigurationPage(ref miscCategory); + } + + private static void SetupGeneralIKConfigurationPage(ref Category parentCategory) + { + Page generalIKPage = parentCategory.AddPage("General IK Settings", "", "Configure the settings for general IK.", ModSettings.SettingsCategory); + generalIKPage.MenuTitle = "General IK Settings"; + Category generalIKCategory = generalIKPage.AddCategory(generalIKPage.MenuTitle); + + // General Settings + AddMelonToggle(ref generalIKCategory, ModSettings.EntryPlantFeet); + + // Calibration Settings + AddMelonToggle(ref generalIKCategory, ModSettings.EntryUseToesForVRIK); + + // Fine-tuning Settings + AddMelonToggle(ref generalIKCategory, ModSettings.EntryResetFootstepsOnIdle); + + // Lerp Speed + AddMelonSlider(ref generalIKPage, ModSettings.EntryIKLerpSpeed, 0, 20f, 0); + } + + private static void SetupDesktopIKConfigurationPage(ref Category parentCategory) + { + Page desktopIKPage = parentCategory.AddPage("Desktop IK Settings", "", "Configure the settings for desktop IK.", ModSettings.SettingsCategory); + desktopIKPage.MenuTitle = "Desktop IK Settings"; + Category desktopIKCategory = desktopIKPage.AddCategory(desktopIKPage.MenuTitle); + + // Funny Settings + AddMelonToggle(ref desktopIKCategory, ModSettings.EntryProneThrusting); + + // Body Leaning Weight + AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyLeanWeight, 0, 1f, 1); + + // Max Root Heading Limit & Weights + AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyHeadingLimit, 0, 90f, 0); + AddMelonSlider(ref desktopIKPage, ModSettings.EntryPelvisHeadingWeight, 0, 1f, 1); + AddMelonSlider(ref desktopIKPage, ModSettings.EntryChestHeadingWeight, 0, 1f, 1); + } + + private static void SetupHalfBodyIKConfigurationPage(ref Category parentCategory) + { + Page halfBodyIKPage = parentCategory.AddPage("HalfBody IK Settings", "", "Configure the settings for halfbody IK.", ModSettings.SettingsCategory); + halfBodyIKPage.MenuTitle = "HalfBody IK Settings"; + Category halfBodyIKCategory = halfBodyIKPage.AddCategory(halfBodyIKPage.MenuTitle); + } + + #region Melon Pref Helpers + + private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry entry) + { + category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; + } + + private static void AddMelonSlider(ref Page page, MelonLoader.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; + } + + #endregion +} \ No newline at end of file diff --git a/AlternateIKSystem/Main.cs b/AlternateIKSystem/Main.cs index 033c084..d82f2cf 100644 --- a/AlternateIKSystem/Main.cs +++ b/AlternateIKSystem/Main.cs @@ -20,6 +20,17 @@ public class AlternateIKSystem : MelonMod ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); + + InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize); + } + + private static void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + Logger.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); } private void ApplyPatches(Type type) diff --git a/AlternateIKSystem/ModSettings.cs b/AlternateIKSystem/ModSettings.cs index ae014ba..25adcd5 100644 --- a/AlternateIKSystem/ModSettings.cs +++ b/AlternateIKSystem/ModSettings.cs @@ -9,9 +9,52 @@ public static class ModSettings public static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(SettingsCategory); - public static readonly MelonPreferences_Entry EntryEnabled = - Category.CreateEntry("Enabled", true, description: "Toggle AlternateIKSystem entirely. Requires avatar reload."); + // Shared Settings - public static readonly MelonPreferences_Entry EntryUseVRIKToes = - Category.CreateEntry("Use VRIK Toes", false, description: "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar."); + public static readonly MelonPreferences_Entry EntryEnabled = + Category.CreateEntry("Enabled", true, + description: "Toggle AlternateIKSystem entirely. Requires avatar reload."); + + public static readonly MelonPreferences_Entry EntryUseToesForVRIK = + Category.CreateEntry("Use VRIK Toes", false, + description: + "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar."); + + public static readonly MelonPreferences_Entry EntryPlantFeet = + Category.CreateEntry("Enforce Plant Feet", true, + description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement."); + + public static readonly MelonPreferences_Entry EntryResetFootstepsOnIdle = + Category.CreateEntry("Reset Footsteps on Idle", false, + description: + "Determines if the Locomotion Footsteps will be reset to their calibration position when entering idle."); + + public static readonly MelonPreferences_Entry EntryIKLerpSpeed = + Category.CreateEntry("IK Lerp Speed", 10f, + description: "Determines fast the IK & Locomotion weights blend after entering idle. Set to 0 to disable."); + + // Desktop Settings + + public static readonly MelonPreferences_Entry EntryBodyLeanWeight = + Category.CreateEntry("Body Lean Weight", 0.5f, + description: "Adds rotational influence to the body solver when looking up/down. Set to 0 to disable."); + + public static readonly MelonPreferences_Entry EntryBodyHeadingLimit = + Category.CreateEntry("Body Heading Limit", 20f, + description: + "Specifies the maximum angle the lower body can have relative to the head when rotating. Set to 0 to disable."); + + public static readonly MelonPreferences_Entry EntryPelvisHeadingWeight = + Category.CreateEntry("Pelvis Heading Weight", 0.25f, + description: + "Determines how much the pelvis will face the Body Heading Limit. Set to 0 to align with head."); + + public static readonly MelonPreferences_Entry EntryChestHeadingWeight = + Category.CreateEntry("Chest Heading Weight", 0.75f, + description: + "Determines how much the chest will face the Body Heading Limit. Set to 0 to align with head."); + + public static readonly MelonPreferences_Entry EntryProneThrusting = + Category.CreateEntry("Prone Thrusting", false, + description: "Allows Body Lean Weight to take effect while crouched or prone."); } \ No newline at end of file