diff --git a/EzCurls/EzCurls.csproj b/EzCurls/EzCurls.csproj new file mode 100644 index 0000000..66a50a8 --- /dev/null +++ b/EzCurls/EzCurls.csproj @@ -0,0 +1,2 @@ + + diff --git a/EzCurls/InputModuleCurlAdjuster.cs b/EzCurls/InputModuleCurlAdjuster.cs new file mode 100644 index 0000000..8ea076e --- /dev/null +++ b/EzCurls/InputModuleCurlAdjuster.cs @@ -0,0 +1,141 @@ +using ABI_RC.Core.Savior; +using ABI_RC.Systems.IK.SubSystems; + +namespace NAK.EzCurls; + +internal class InputModuleCurlAdjuster : CVRInputModule +{ + public static InputModuleCurlAdjuster Instance; + + // Curl clamping/adjustment + public bool UseCurlSnapping = false; + public float SnappedCurlValue = 0.5f; + public float RangeStartPercent = 0.5f; + public float RangeEndPercent = 0.8f; + + // Curl smoothing/averaging + public bool UseCurlSmoothing = false; + public bool DontSmoothExtremes = true; + public float CurlSimilarityThreshold = 0.5f; + public float CurlSmoothingFactor = 0.5f; + + public new void Start() + { + Instance = this; + base.Start(); + EzCurls.OnSettingsChanged(); + } + + public override void UpdateInput() => DoCurlAdjustments(); + public override void UpdateImportantInput() => DoCurlAdjustments(); + + private void DoCurlAdjustments() + { + if (!_inputManager.individualFingerTracking) + return; + + if (UseCurlSmoothing) + { + SmoothCurls( + ref _inputManager.fingerCurlLeftIndex, + ref _inputManager.fingerCurlLeftMiddle, + ref _inputManager.fingerCurlLeftRing, + ref _inputManager.fingerCurlLeftPinky + ); + + SmoothCurls( + ref _inputManager.fingerCurlRightIndex, + ref _inputManager.fingerCurlRightMiddle, + ref _inputManager.fingerCurlRightRing, + ref _inputManager.fingerCurlRightPinky + ); + } + + if (UseCurlSnapping) + { + SnapCurls(ref _inputManager.fingerCurlLeftIndex); + SnapCurls(ref _inputManager.fingerCurlLeftMiddle); + SnapCurls(ref _inputManager.fingerCurlLeftRing); + SnapCurls(ref _inputManager.fingerCurlLeftPinky); + + SnapCurls(ref _inputManager.fingerCurlRightIndex); + SnapCurls(ref _inputManager.fingerCurlRightMiddle); + SnapCurls(ref _inputManager.fingerCurlRightRing); + SnapCurls(ref _inputManager.fingerCurlRightPinky); + } + } + + private void SnapCurls(ref float fingerCurl) + { + if (fingerCurl >= RangeStartPercent && fingerCurl <= RangeEndPercent) + fingerCurl = SnappedCurlValue; + } + + private void SmoothCurls(ref float index, ref float middle, ref float ring, ref float pinky) + { + List values = new List { index, middle, ring, pinky }; + + for (int i = 0; i < values.Count; i++) + { + for (int j = i + 1; j < values.Count; j++) + { + if (Math.Abs(values[i] - values[j]) <= CurlSimilarityThreshold) + { + // Compute new smoothed values + float smoothedValue = (values[i] + values[j]) / 2; + + // Calculate smoothing factors for both values + float smoothingFactor1 = CalculateSmoothingFactor(values[i]); + float smoothingFactor2 = CalculateSmoothingFactor(values[j]); + + // Adjust both values towards the smoothed value + values[i] = values[i] + smoothingFactor1 * CurlSmoothingFactor * (smoothedValue - values[i]); + values[j] = values[j] + smoothingFactor2 * CurlSmoothingFactor * (smoothedValue - values[j]); + } + } + } + + index = values[0]; + middle = values[1]; + ring = values[2]; + pinky = values[3]; + } + + private void SmoothCurlsNear(ref float index, ref float middle, ref float ring, ref float pinky) + { + List values = new List { index, middle, ring, pinky }; + + for (int i = 0; i < values.Count - 1; i++) + { + if (Math.Abs(values[i] - values[i + 1]) <= CurlSimilarityThreshold) + { + // Compute new smoothed value + float smoothedValue = (values[i] + values[i + 1]) / 2; + + // Calculate smoothing factors for both values + float smoothingFactor1 = CalculateSmoothingFactor(values[i]); + float smoothingFactor2 = CalculateSmoothingFactor(values[i + 1]); + + // Adjust both values towards the smoothed value + values[i] = values[i] + smoothingFactor1 * CurlSmoothingFactor * (smoothedValue - values[i]); + values[i + 1] = values[i + 1] + smoothingFactor2 * CurlSmoothingFactor * (smoothedValue - values[i + 1]); + } + } + + index = values[0]; + middle = values[1]; + ring = values[2]; + pinky = values[3]; + } + + // calculate the smoothing factor based on the curl value + private float CalculateSmoothingFactor(float curlValue) + { + if (!DontSmoothExtremes) + return 1f; + + // Compute the distance from the center (0.5) and square it + float dist = curlValue - 0.5f; + return 1.0f - 4 * dist * dist; + } +} \ No newline at end of file diff --git a/EzCurls/Main.cs b/EzCurls/Main.cs new file mode 100644 index 0000000..903cf04 --- /dev/null +++ b/EzCurls/Main.cs @@ -0,0 +1,79 @@ +using ABI_RC.Core.Savior; +using MelonLoader; +using System.Reflection; + +namespace NAK.EzCurls; + +public class EzCurls : MelonMod +{ + internal const string SettingsCategory = nameof(EzCurls); + + public static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(SettingsCategory); + + public static readonly MelonPreferences_Entry EntryUseCurlSnapping = + Category.CreateEntry("UseCurlSnapping", true, + description: "Finger curl snapping to a specified value when in the specified range."); + + public static readonly MelonPreferences_Entry EntrySnappedCurlValue = + Category.CreateEntry("SnappedCurlValue", 0.5f, + description: "The value to which the finger curl snaps within the range."); + + public static readonly MelonPreferences_Entry EntryRangeStartPercent = + Category.CreateEntry("RangeStartPercent", 0.5f, + description: "The minimum value for the SnappedCurlValue range."); + + public static readonly MelonPreferences_Entry EntryRangeEndPercent = + Category.CreateEntry("RangeEndPercent", 0.8f, + description: "The maximum value for the SnappedCurlValue range."); + + public static readonly MelonPreferences_Entry EntryUseCurlSmoothing = + Category.CreateEntry("UseCurlSmoothing", false, + description: "Finger curl smoothing to average out similar finger positions."); + + public static readonly MelonPreferences_Entry EntryDontSmoothExtremes = + Category.CreateEntry("DontSmoothExtremes", true, + description: "Should the finger curl smoothing be less effective on curls towards 0 or 1?"); + + public static readonly MelonPreferences_Entry EntryCurlSimilarityThreshold = + Category.CreateEntry("CurlSimilarityThreshold", 0.1f, + description: "The threshold for curl similarity during curl smoothing."); + + public static readonly MelonPreferences_Entry EntryCurlSmoothingFactor = + Category.CreateEntry("CurlSmoothingFactor", 0.5f, + description: "The multiplier for curl smoothing."); + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( + typeof(InputModuleSteamVR).GetMethod(nameof(InputModuleSteamVR.Start)), + prefix: new HarmonyLib.HarmonyMethod(typeof(EzCurls).GetMethod(nameof(OnInputModuleSteamVRStart_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) + ); + + foreach (MelonPreferences_Entry setting in Category.Entries) + setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); + } + + public static void OnSettingsChanged(object oldValue = null, object newValue = null) + { + if (InputModuleCurlAdjuster.Instance == null) + return; + + // curl snapping + InputModuleCurlAdjuster.Instance.UseCurlSnapping = EntryUseCurlSnapping.Value; + InputModuleCurlAdjuster.Instance.SnappedCurlValue = EntrySnappedCurlValue.Value; + InputModuleCurlAdjuster.Instance.RangeStartPercent = EntryRangeStartPercent.Value; + InputModuleCurlAdjuster.Instance.RangeEndPercent = EntryRangeEndPercent.Value; + + // curl smoothing + InputModuleCurlAdjuster.Instance.UseCurlSmoothing = EntryUseCurlSmoothing.Value; + InputModuleCurlAdjuster.Instance.DontSmoothExtremes = EntryDontSmoothExtremes.Value; + InputModuleCurlAdjuster.Instance.CurlSimilarityThreshold = EntryCurlSimilarityThreshold.Value; + InputModuleCurlAdjuster.Instance.CurlSmoothingFactor = EntryCurlSmoothingFactor.Value; + } + + private static void OnInputModuleSteamVRStart_Prefix(ref InputModuleSteamVR __instance) + { + __instance.gameObject.AddComponent(); + } +} \ No newline at end of file diff --git a/EzCurls/Properties/AssemblyInfo.cs b/EzCurls/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9aa7ad6 --- /dev/null +++ b/EzCurls/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using MelonLoader; +using NAK.EzCurls.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.EzCurls))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.EzCurls))] + +[assembly: MelonInfo( + typeof(NAK.EzCurls.EzCurls), + nameof(NAK.EzCurls), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] + +namespace NAK.EzCurls.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/EzCurls/README.md b/EzCurls/README.md new file mode 100644 index 0000000..12379fa --- /dev/null +++ b/EzCurls/README.md @@ -0,0 +1,14 @@ +# EzCurls + +A mod that should hopefully make finger curls more predictable. + +--- + +Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive. + +> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. + +> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. diff --git a/EzCurls/format.json b/EzCurls/format.json new file mode 100644 index 0000000..15f3f72 --- /dev/null +++ b/EzCurls/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "EzCurls", + "modversion": "1.0.0", + "gameversion": "2022r170p1", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "A mod that should hopefully make finger curls more predictable.", + "searchtags": [ + "aas", + "sync", + "naked", + "buffer" + ], + "requirements": [ + "UIExpansionKit" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r13/EzCurls.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls/", + "changelog": "- Initial CVRMG release", + "embedcolor": "7d7d7d" +} \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index c06e1bf..002bf24 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -73,6 +73,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustm EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EzCurls", "EzCurls\EzCurls.csproj", "{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -219,6 +221,10 @@ Global {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU {77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE