From b4b392d20deb8da08c9f8ca23f317355b263fb0e Mon Sep 17 00:00:00 2001 From: NotAKid <37721153+NotAKidoS@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:48:37 -0600 Subject: [PATCH] [DesktopInteractions] initial release --- .../DesktopInteractions.csproj | 9 + DesktopInteractions/Main.cs | 235 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 +++ DesktopInteractions/README.md | 14 ++ DesktopInteractions/format.json | 23 ++ 5 files changed, 313 insertions(+) create mode 100644 DesktopInteractions/DesktopInteractions.csproj create mode 100644 DesktopInteractions/Main.cs create mode 100644 DesktopInteractions/Properties/AssemblyInfo.cs create mode 100644 DesktopInteractions/README.md create mode 100644 DesktopInteractions/format.json diff --git a/DesktopInteractions/DesktopInteractions.csproj b/DesktopInteractions/DesktopInteractions.csproj new file mode 100644 index 0000000..14fc5af --- /dev/null +++ b/DesktopInteractions/DesktopInteractions.csproj @@ -0,0 +1,9 @@ + + + + PlayerCloneAttachment + + + + + diff --git a/DesktopInteractions/Main.cs b/DesktopInteractions/Main.cs new file mode 100644 index 0000000..a92b976 --- /dev/null +++ b/DesktopInteractions/Main.cs @@ -0,0 +1,235 @@ +using System.Reflection; +using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; +using ABI_RC.Systems.ChatBox; +using ABI_RC.Systems.GameEventSystem; +using ABI_RC.Systems.IK; +using ABI.CCK.Components; +using HarmonyLib; +using MelonLoader; +using RootMotion.FinalIK; +using UnityEngine; + +namespace NAK.DesktopInteractions; + +public class DesktopInteractionsMod : MelonMod +{ + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(DesktopInteractions)); + + private static readonly MelonPreferences_Entry EntryTypingGesture = + Category.CreateEntry("enable_typing_gesture", true, + "Typing Gesture", description: "When enabled you will place your arm up to your ear when typing in ChatBox."); + + private static readonly MelonPreferences_Entry EntryZoomGesture = + Category.CreateEntry("enable_zoom_gesture", false, + "Zoom Gesture", description: "When enabled you will cup your hands around your eyes while zooming."); + + public override void OnInitializeMelon() + { + CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(OnPlayerSetupStart); + CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoad); + CVRGameEventSystem.Avatar.OnLocalAvatarHeightScale.AddListener(OnLocalAvatarHeightScale); + + HarmonyInstance.Patch( + typeof(IKSystem).GetMethod(nameof(IKSystem.OnPreSolverUpdateGeneral), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(DesktopInteractionsMod).GetMethod(nameof(OnPreSolverUpdateGeneral), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + } + + private static Transform _cameraTargetContainerTransform; + private static Transform _earIKTargetTransform; + private static Transform _leftEyeIKTargetTransform; + private static Transform _rightEyeIKTargetTransform; + private static Transform _calibratedEarIKTargetTransform; + private static Transform _calibratedLeftEyeIKTargetTransform; + private static Transform _calibratedRightEyeIKTargetTransform; + + private static IKLimbController _leftArmController; + private static IKLimbController _rightArmController; + + private static void OnPlayerSetupStart() + { + Transform cameraTransform = PlayerSetup.Instance.desktopCamera.transform; + + _cameraTargetContainerTransform = new GameObject("ScaledTargetsContainer").transform; + _cameraTargetContainerTransform.SetParent(cameraTransform, false); + _cameraTargetContainerTransform.localScale = Vector3.one * PlayerSetup.Instance.GetPlaySpaceScale() * 1.8f; + + _earIKTargetTransform = new GameObject("LeftEarIKTarget").transform; + _earIKTargetTransform.SetParent(_cameraTargetContainerTransform); + _earIKTargetTransform.localPosition = new Vector3(-0.1141031f, -0.05610896f, 0.01008159f); + _earIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-63.037f, -108.763f, 141.237f)); + _calibratedEarIKTargetTransform = new GameObject("CalibratedOffset").transform; + _calibratedEarIKTargetTransform.SetParent(_earIKTargetTransform, false); + + _leftEyeIKTargetTransform = new GameObject("LeftEyeIKTarget").transform; + _leftEyeIKTargetTransform.SetParent(_cameraTargetContainerTransform); + _leftEyeIKTargetTransform.localPosition = new Vector3(-0.0768f, -0.0551f, 0.0278f); + _leftEyeIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-47.436f, -34.336f, 84.604f)); + _calibratedLeftEyeIKTargetTransform = new GameObject("CalibratedOffset").transform; + _calibratedLeftEyeIKTargetTransform.SetParent(_leftEyeIKTargetTransform, false); + + _rightEyeIKTargetTransform = new GameObject("RightEyeIKTarget").transform; + _rightEyeIKTargetTransform.SetParent(_cameraTargetContainerTransform); + _rightEyeIKTargetTransform.localPosition = new Vector3(0.0768f, -0.0551f, 0.0278f); + _rightEyeIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-132.564f, -145.664f, 95.396f)); + _calibratedRightEyeIKTargetTransform = new GameObject("CalibratedOffset").transform; + _calibratedRightEyeIKTargetTransform.SetParent(_rightEyeIKTargetTransform, false); + + _leftArmController = new IKLimbController(8f); + _rightArmController = new IKLimbController(8f); + } + + private static void OnLocalAvatarLoad(CVRAvatar _) + { + if (!IKSystem.Instance.IsAvatarCalibrated()) return; // IKSystem did not consider for setup + + IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.Default); + + VRIK vrik = IKSystem.vrik; + Quaternion localHandRotationLeft = IKCalibrator.CalculateLocalRotation(vrik.references.root, vrik.references.leftHand); + _calibratedEarIKTargetTransform.localRotation = localHandRotationLeft; + _calibratedLeftEyeIKTargetTransform.localRotation = localHandRotationLeft; + + Quaternion localHandRotationRight = IKCalibrator.CalculateLocalRotation(vrik.references.root, vrik.references.rightHand); + _calibratedRightEyeIKTargetTransform.localRotation = localHandRotationRight; + + IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.LastSaved); + } + + // I fucked the offsets and didn't account for scale until now, so we have to adjust it + private static void OnLocalAvatarHeightScale(float height, float scale) + => _cameraTargetContainerTransform.localScale = Vector3.one * scale * 1.8f; + + private static void OnPreSolverUpdateGeneral() + { + if (MetaPort.Instance.isUsingVr) return; + + bool isTyping = EntryTypingGesture.Value && ChatBoxManager.Instance.LocalPlayerBubble.IsTypingIndicatorActive; + bool isZooming = EntryZoomGesture.Value && CVR_DesktopCameraController.GetCurrentZoomModifier() > 0.25f; + + _leftArmController.SetInfluence(10, isTyping, _calibratedEarIKTargetTransform); + _leftArmController.SetInfluence(5, isZooming, _calibratedLeftEyeIKTargetTransform); + _leftArmController.Update(Time.deltaTime); + + IKSolverVR solver = IKSystem.vrik.solver; + + IKSolverVR.Arm leftArm = solver.leftArm; + leftArm.positionWeight = _leftArmController.Weight; + leftArm.rotationWeight = _leftArmController.Weight; + leftArm.IKPosition = _leftArmController.Position; + leftArm.IKRotation = _leftArmController.Rotation; + + _rightArmController.SetInfluence(5, isZooming, _calibratedRightEyeIKTargetTransform); + _rightArmController.Update(Time.deltaTime); + + IKSolverVR.Arm rightArm = solver.rightArm; + rightArm.positionWeight = _rightArmController.Weight; + rightArm.rotationWeight = _rightArmController.Weight; + rightArm.IKPosition = _rightArmController.Position; + rightArm.IKRotation = _rightArmController.Rotation; + } +} + +public class IKLimbController(float blendSpeed) +{ + private struct Influence + { + public int Priority; + public bool IsActive; + public Transform Target; + } + + private readonly Influence[] _influences = new Influence[8]; + private int _count; + + private Vector3 _position; + private Quaternion _rotation = Quaternion.identity; + private float _weight; + + private Transform _fromTarget; + private Transform _toTarget; + /*private Vector3 _fromPosition; + private Quaternion _fromRotation;*/ + private float _blendT; + + public Vector3 Position => _position; + public Quaternion Rotation => _rotation; + public float Weight => _weight; + + public void SetInfluence(int priority, bool isActive, Transform target) + { + int index = -1; + for (int i = 0; i < _count; i++) + { + if (_influences[i].Priority == priority) + { + index = i; + break; + } + if (_influences[i].Priority < priority) + { + index = i; + for (int j = _count; j > i; j--) _influences[j] = _influences[j - 1]; + _count++; + break; + } + } + + if (index == -1) + { + if (_count >= _influences.Length) return; + index = _count++; + } + + _influences[index] = new Influence { Priority = priority, IsActive = isActive, Target = target }; + } + + public void Update(float deltaTime) + { + Transform target = null; + for (int i = 0; i < _count; i++) + { + if (!_influences[i].IsActive) continue; + target = _influences[i].Target; + break; + } + + float targetWeight; + if (target != null) + { + targetWeight = 1f; + + if (_toTarget != target) + { + /*if (_toTarget != null) + { + _fromPosition = _toTarget.position; + _fromRotation = _toTarget.rotation; + } + else + { + _fromPosition = target.position; + _fromRotation = target.rotation; + }*/ + _fromTarget = _toTarget ?? target; + _toTarget = target; + _blendT = 0f; + } + + _blendT = Mathf.Min(_blendT + blendSpeed * deltaTime, 1f); + _position = Vector3.Lerp(_fromTarget.position, _toTarget.position, _blendT); + _rotation = Quaternion.Slerp(_fromTarget.rotation, _toTarget.rotation, _blendT); + } + else + { + targetWeight = 0f; + _toTarget = null; + } + + _weight = Mathf.MoveTowards(_weight, targetWeight, blendSpeed * deltaTime); + } +} \ No newline at end of file diff --git a/DesktopInteractions/Properties/AssemblyInfo.cs b/DesktopInteractions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1f42b09 --- /dev/null +++ b/DesktopInteractions/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using MelonLoader; +using NAK.DesktopInteractions.Properties; +using System.Reflection; + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.DesktopInteractions))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.DesktopInteractions))] + +[assembly: MelonInfo( + typeof(NAK.DesktopInteractions.DesktopInteractionsMod), + nameof(NAK.DesktopInteractions), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopInteractions" +)] + +[assembly: MelonGame("ChilloutVR", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: HarmonyDontPatchAll] + +namespace NAK.DesktopInteractions.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/DesktopInteractions/README.md b/DesktopInteractions/README.md new file mode 100644 index 0000000..2ba4272 --- /dev/null +++ b/DesktopInteractions/README.md @@ -0,0 +1,14 @@ +# DesktopInteractions + +Adds IK-driven hand gestures to your avatar in Desktop: earpiece grab (GMOD-style) when typing in ChatBox, and binocular cupping when zooming. Both gestures are toggleable in settings. + +--- + +Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. +https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games + +> This mod is an independent creation 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/DesktopInteractions/format.json b/DesktopInteractions/format.json new file mode 100644 index 0000000..bcb57d6 --- /dev/null +++ b/DesktopInteractions/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "DesktopInteractions", + "modversion": "1.0.0", + "gameversion": "2025r181", + "loaderversion": "0.7.2", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Adds IK-driven hand gestures to your avatar in Desktop: earpiece grab (GMOD-style) when typing in ChatBox, and binocular cupping when zooming. Both gestures are toggleable in settings.", + "searchtags": [ + "gmod", + "typing", + "zooming", + "desktop" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/DesktopInteractions.dll", + "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopInteractions/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file