Skip to content

Commit b4b392d

Browse files
committed
[DesktopInteractions] initial release
1 parent bf58de4 commit b4b392d

5 files changed

Lines changed: 313 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<RootNamespace>PlayerCloneAttachment</RootNamespace>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<None Remove="Resources\playercloneattachment.assets" />
8+
</ItemGroup>
9+
</Project>

DesktopInteractions/Main.cs

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
using System.Reflection;
2+
using ABI_RC.Core.Player;
3+
using ABI_RC.Core.Savior;
4+
using ABI_RC.Systems.ChatBox;
5+
using ABI_RC.Systems.GameEventSystem;
6+
using ABI_RC.Systems.IK;
7+
using ABI.CCK.Components;
8+
using HarmonyLib;
9+
using MelonLoader;
10+
using RootMotion.FinalIK;
11+
using UnityEngine;
12+
13+
namespace NAK.DesktopInteractions;
14+
15+
public class DesktopInteractionsMod : MelonMod
16+
{
17+
private static readonly MelonPreferences_Category Category =
18+
MelonPreferences.CreateCategory(nameof(DesktopInteractions));
19+
20+
private static readonly MelonPreferences_Entry<bool> EntryTypingGesture =
21+
Category.CreateEntry("enable_typing_gesture", true,
22+
"Typing Gesture", description: "When enabled you will place your arm up to your ear when typing in ChatBox.");
23+
24+
private static readonly MelonPreferences_Entry<bool> EntryZoomGesture =
25+
Category.CreateEntry("enable_zoom_gesture", false,
26+
"Zoom Gesture", description: "When enabled you will cup your hands around your eyes while zooming.");
27+
28+
public override void OnInitializeMelon()
29+
{
30+
CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(OnPlayerSetupStart);
31+
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoad);
32+
CVRGameEventSystem.Avatar.OnLocalAvatarHeightScale.AddListener(OnLocalAvatarHeightScale);
33+
34+
HarmonyInstance.Patch(
35+
typeof(IKSystem).GetMethod(nameof(IKSystem.OnPreSolverUpdateGeneral),
36+
BindingFlags.NonPublic | BindingFlags.Instance),
37+
prefix: new HarmonyMethod(typeof(DesktopInteractionsMod).GetMethod(nameof(OnPreSolverUpdateGeneral),
38+
BindingFlags.NonPublic | BindingFlags.Static))
39+
);
40+
}
41+
42+
private static Transform _cameraTargetContainerTransform;
43+
private static Transform _earIKTargetTransform;
44+
private static Transform _leftEyeIKTargetTransform;
45+
private static Transform _rightEyeIKTargetTransform;
46+
private static Transform _calibratedEarIKTargetTransform;
47+
private static Transform _calibratedLeftEyeIKTargetTransform;
48+
private static Transform _calibratedRightEyeIKTargetTransform;
49+
50+
private static IKLimbController _leftArmController;
51+
private static IKLimbController _rightArmController;
52+
53+
private static void OnPlayerSetupStart()
54+
{
55+
Transform cameraTransform = PlayerSetup.Instance.desktopCamera.transform;
56+
57+
_cameraTargetContainerTransform = new GameObject("ScaledTargetsContainer").transform;
58+
_cameraTargetContainerTransform.SetParent(cameraTransform, false);
59+
_cameraTargetContainerTransform.localScale = Vector3.one * PlayerSetup.Instance.GetPlaySpaceScale() * 1.8f;
60+
61+
_earIKTargetTransform = new GameObject("LeftEarIKTarget").transform;
62+
_earIKTargetTransform.SetParent(_cameraTargetContainerTransform);
63+
_earIKTargetTransform.localPosition = new Vector3(-0.1141031f, -0.05610896f, 0.01008159f);
64+
_earIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-63.037f, -108.763f, 141.237f));
65+
_calibratedEarIKTargetTransform = new GameObject("CalibratedOffset").transform;
66+
_calibratedEarIKTargetTransform.SetParent(_earIKTargetTransform, false);
67+
68+
_leftEyeIKTargetTransform = new GameObject("LeftEyeIKTarget").transform;
69+
_leftEyeIKTargetTransform.SetParent(_cameraTargetContainerTransform);
70+
_leftEyeIKTargetTransform.localPosition = new Vector3(-0.0768f, -0.0551f, 0.0278f);
71+
_leftEyeIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-47.436f, -34.336f, 84.604f));
72+
_calibratedLeftEyeIKTargetTransform = new GameObject("CalibratedOffset").transform;
73+
_calibratedLeftEyeIKTargetTransform.SetParent(_leftEyeIKTargetTransform, false);
74+
75+
_rightEyeIKTargetTransform = new GameObject("RightEyeIKTarget").transform;
76+
_rightEyeIKTargetTransform.SetParent(_cameraTargetContainerTransform);
77+
_rightEyeIKTargetTransform.localPosition = new Vector3(0.0768f, -0.0551f, 0.0278f);
78+
_rightEyeIKTargetTransform.localRotation = Quaternion.Euler(new Vector3(-132.564f, -145.664f, 95.396f));
79+
_calibratedRightEyeIKTargetTransform = new GameObject("CalibratedOffset").transform;
80+
_calibratedRightEyeIKTargetTransform.SetParent(_rightEyeIKTargetTransform, false);
81+
82+
_leftArmController = new IKLimbController(8f);
83+
_rightArmController = new IKLimbController(8f);
84+
}
85+
86+
private static void OnLocalAvatarLoad(CVRAvatar _)
87+
{
88+
if (!IKSystem.Instance.IsAvatarCalibrated()) return; // IKSystem did not consider for setup
89+
90+
IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.Default);
91+
92+
VRIK vrik = IKSystem.vrik;
93+
Quaternion localHandRotationLeft = IKCalibrator.CalculateLocalRotation(vrik.references.root, vrik.references.leftHand);
94+
_calibratedEarIKTargetTransform.localRotation = localHandRotationLeft;
95+
_calibratedLeftEyeIKTargetTransform.localRotation = localHandRotationLeft;
96+
97+
Quaternion localHandRotationRight = IKCalibrator.CalculateLocalRotation(vrik.references.root, vrik.references.rightHand);
98+
_calibratedRightEyeIKTargetTransform.localRotation = localHandRotationRight;
99+
100+
IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.LastSaved);
101+
}
102+
103+
// I fucked the offsets and didn't account for scale until now, so we have to adjust it
104+
private static void OnLocalAvatarHeightScale(float height, float scale)
105+
=> _cameraTargetContainerTransform.localScale = Vector3.one * scale * 1.8f;
106+
107+
private static void OnPreSolverUpdateGeneral()
108+
{
109+
if (MetaPort.Instance.isUsingVr) return;
110+
111+
bool isTyping = EntryTypingGesture.Value && ChatBoxManager.Instance.LocalPlayerBubble.IsTypingIndicatorActive;
112+
bool isZooming = EntryZoomGesture.Value && CVR_DesktopCameraController.GetCurrentZoomModifier() > 0.25f;
113+
114+
_leftArmController.SetInfluence(10, isTyping, _calibratedEarIKTargetTransform);
115+
_leftArmController.SetInfluence(5, isZooming, _calibratedLeftEyeIKTargetTransform);
116+
_leftArmController.Update(Time.deltaTime);
117+
118+
IKSolverVR solver = IKSystem.vrik.solver;
119+
120+
IKSolverVR.Arm leftArm = solver.leftArm;
121+
leftArm.positionWeight = _leftArmController.Weight;
122+
leftArm.rotationWeight = _leftArmController.Weight;
123+
leftArm.IKPosition = _leftArmController.Position;
124+
leftArm.IKRotation = _leftArmController.Rotation;
125+
126+
_rightArmController.SetInfluence(5, isZooming, _calibratedRightEyeIKTargetTransform);
127+
_rightArmController.Update(Time.deltaTime);
128+
129+
IKSolverVR.Arm rightArm = solver.rightArm;
130+
rightArm.positionWeight = _rightArmController.Weight;
131+
rightArm.rotationWeight = _rightArmController.Weight;
132+
rightArm.IKPosition = _rightArmController.Position;
133+
rightArm.IKRotation = _rightArmController.Rotation;
134+
}
135+
}
136+
137+
public class IKLimbController(float blendSpeed)
138+
{
139+
private struct Influence
140+
{
141+
public int Priority;
142+
public bool IsActive;
143+
public Transform Target;
144+
}
145+
146+
private readonly Influence[] _influences = new Influence[8];
147+
private int _count;
148+
149+
private Vector3 _position;
150+
private Quaternion _rotation = Quaternion.identity;
151+
private float _weight;
152+
153+
private Transform _fromTarget;
154+
private Transform _toTarget;
155+
/*private Vector3 _fromPosition;
156+
private Quaternion _fromRotation;*/
157+
private float _blendT;
158+
159+
public Vector3 Position => _position;
160+
public Quaternion Rotation => _rotation;
161+
public float Weight => _weight;
162+
163+
public void SetInfluence(int priority, bool isActive, Transform target)
164+
{
165+
int index = -1;
166+
for (int i = 0; i < _count; i++)
167+
{
168+
if (_influences[i].Priority == priority)
169+
{
170+
index = i;
171+
break;
172+
}
173+
if (_influences[i].Priority < priority)
174+
{
175+
index = i;
176+
for (int j = _count; j > i; j--) _influences[j] = _influences[j - 1];
177+
_count++;
178+
break;
179+
}
180+
}
181+
182+
if (index == -1)
183+
{
184+
if (_count >= _influences.Length) return;
185+
index = _count++;
186+
}
187+
188+
_influences[index] = new Influence { Priority = priority, IsActive = isActive, Target = target };
189+
}
190+
191+
public void Update(float deltaTime)
192+
{
193+
Transform target = null;
194+
for (int i = 0; i < _count; i++)
195+
{
196+
if (!_influences[i].IsActive) continue;
197+
target = _influences[i].Target;
198+
break;
199+
}
200+
201+
float targetWeight;
202+
if (target != null)
203+
{
204+
targetWeight = 1f;
205+
206+
if (_toTarget != target)
207+
{
208+
/*if (_toTarget != null)
209+
{
210+
_fromPosition = _toTarget.position;
211+
_fromRotation = _toTarget.rotation;
212+
}
213+
else
214+
{
215+
_fromPosition = target.position;
216+
_fromRotation = target.rotation;
217+
}*/
218+
_fromTarget = _toTarget ?? target;
219+
_toTarget = target;
220+
_blendT = 0f;
221+
}
222+
223+
_blendT = Mathf.Min(_blendT + blendSpeed * deltaTime, 1f);
224+
_position = Vector3.Lerp(_fromTarget.position, _toTarget.position, _blendT);
225+
_rotation = Quaternion.Slerp(_fromTarget.rotation, _toTarget.rotation, _blendT);
226+
}
227+
else
228+
{
229+
targetWeight = 0f;
230+
_toTarget = null;
231+
}
232+
233+
_weight = Mathf.MoveTowards(_weight, targetWeight, blendSpeed * deltaTime);
234+
}
235+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using MelonLoader;
2+
using NAK.DesktopInteractions.Properties;
3+
using System.Reflection;
4+
5+
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
6+
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
7+
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
8+
[assembly: AssemblyTitle(nameof(NAK.DesktopInteractions))]
9+
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
10+
[assembly: AssemblyProduct(nameof(NAK.DesktopInteractions))]
11+
12+
[assembly: MelonInfo(
13+
typeof(NAK.DesktopInteractions.DesktopInteractionsMod),
14+
nameof(NAK.DesktopInteractions),
15+
AssemblyInfoParams.Version,
16+
AssemblyInfoParams.Author,
17+
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopInteractions"
18+
)]
19+
20+
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
21+
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
22+
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
23+
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
24+
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
25+
[assembly: HarmonyDontPatchAll]
26+
27+
namespace NAK.DesktopInteractions.Properties;
28+
internal static class AssemblyInfoParams
29+
{
30+
public const string Version = "1.0.0";
31+
public const string Author = "NotAKidoS";
32+
}

DesktopInteractions/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# DesktopInteractions
2+
3+
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.
4+
5+
---
6+
7+
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
8+
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
9+
10+
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
11+
12+
> 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.
13+
14+
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

DesktopInteractions/format.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"_id": -1,
3+
"name": "DesktopInteractions",
4+
"modversion": "1.0.0",
5+
"gameversion": "2025r181",
6+
"loaderversion": "0.7.2",
7+
"modtype": "Mod",
8+
"author": "NotAKidoS",
9+
"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.",
10+
"searchtags": [
11+
"gmod",
12+
"typing",
13+
"zooming",
14+
"desktop"
15+
],
16+
"requirements": [
17+
"None"
18+
],
19+
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/DesktopInteractions.dll",
20+
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopInteractions/",
21+
"changelog": "- Initial release",
22+
"embedcolor": "#f61963"
23+
}

0 commit comments

Comments
 (0)