Too many changes

This commit is contained in:
SDraw 2024-10-05 15:42:32 +03:00
parent 45557943c4
commit a22e5992d0
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
72 changed files with 1064 additions and 927 deletions

View file

@ -1,25 +1,15 @@
Merged set of MelonLoader mods for ChilloutVR.
**Table for game build 2023r175:**
| Full name | Short name | Latest version |
|:---------:|:----------:|:--------------:|
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | 1.4.1 [:arrow_down:](../../releases/latest/download/ml_amt.dll)|
| [Avatar Synced Look](/ml_asl/README.md) | ml_asl | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_asl.dll)|
| [Better Fingers Tracking](/ml_bft/README.md) | ml_bft | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_bft.dll)|
| [Desktop Head Tracking](/ml_dht/README.md) | ml_dht | 1.2.5 [:arrow_down:](../../releases/latest/download/ml_dht.dll)|
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.5.2 [:arrow_down:](../../releases/latest/download/ml_lme.dll)|
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.1.4 [:arrow_down:](../../releases/latest/download/ml_pam.dll)|
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.8 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)|
| [Player Ragdoll Mod](/ml_prm/README.md) | ml_prm | 1.1.9 [:arrow_down:](../../releases/latest/download/ml_prm.dll)|
| [Players Instance Notifier](/ml_pin/README.md) | ml_pin | 1.0.8 [:arrow_down:](../../releases/latest/download/ml_ml_pin.dll)|
| [Vive Extended Input](/ml_vei/README.md) | ml_vei | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_vei.dll)|
**Archived mods:**
| Full name | Short name | Notes |
|:---------:|:----------:|-------|
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications` |
| Desktop Reticle Switch | ml_drs | Boring functionality |
| Extended Game Notifications | ml_egn | In-game feature since 2023r172 update |
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update |
| Game Main Fixes | ml_gmf | In-game feature since 2023r172 update |
| Server Connection Info | ml_sci | Superseded by `Extended Game Notifications`
**Table for game build 2023r177:**
| Full name | Latest version |
|:---------:|:--------------:|
|[Avatar Motion Tweaker](/ml_amt/README.md)|1.5.0 [:arrow_down:](../../releases/latest/download/ml_amt.dll)|
|[Avatar Synced Look](/ml_asl/README.md)|1.1.0 [:arrow_down:](../../releases/latest/download/ml_asl.dll)|
|[Better Fingers Tracking](/ml_bft/README.md)|1.1.0 [:arrow_down:](../../releases/latest/download/ml_bft.dll)|
|[Desktop Head Tracking](/ml_dht/README.md)|1.3.0 [:arrow_down:](../../releases/latest/download/ml_dht.dll)|
|[Leap Motion Extension](/ml_lme/README.md)| 1.6.0 [:arrow_down:](../../releases/latest/download/ml_lme.dll)|
|[Pickup Arm Movement](/ml_pam/README.md)|1.2.0 [:arrow_down:](../../releases/latest/download/ml_pam.dll)|
|[Player Movement Copycat](/ml_pmc/README.md)|1.1.0 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)|
|[Player Ragdoll Mod](/ml_prm/README.md)|1.2.0 [:arrow_down:](../../releases/latest/download/ml_prm.dll)|
|[Players Instance Notifier](/ml_pin/README.md)|1.1.0 [:arrow_down:](../../releases/latest/download/ml_ml_pin.dll)|
|[Vive Extended Input](/ml_vei/README.md)|1.1.0 [:arrow_down:](../../releases/latest/download/ml_vei.dll)|

View file

@ -11,7 +11,7 @@ namespace ml_amt
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
struct IKState
struct IKInfo
{
public float m_weight;
public float m_locomotionWeight;
@ -20,7 +20,9 @@ namespace ml_amt
public bool m_bendNormalRight;
}
IKState m_ikState;
static MotionTweaker ms_instance = null;
IKInfo m_ikInfo;
VRIK m_vrIk = null;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
@ -37,10 +39,19 @@ namespace ml_amt
}
// Unity events
void Awake()
{
if(ms_instance != null)
{
DestroyImmediate(this);
return;
}
ms_instance = this;
DontDestroyOnLoad(this);
}
void Start()
{
DontDestroyOnLoad(this);
OnCrouchLimitChanged(Settings.CrouchLimit);
OnProneLimitChanged(Settings.ProneLimit);
@ -56,6 +67,9 @@ namespace ml_amt
void OnDestroy()
{
if(ms_instance == this)
ms_instance = null;
m_vrIk = null;
m_ikLimits = null;
m_parameters.Clear();
@ -170,11 +184,11 @@ namespace ml_amt
// IK events
void OnIKPreSolverUpdate()
{
m_ikState.m_weight = m_vrIk.solver.IKPositionWeight;
m_ikState.m_locomotionWeight = m_vrIk.solver.locomotion.weight;
m_ikState.m_plantFeet = m_vrIk.solver.plantFeet;
m_ikState.m_bendNormalLeft = m_vrIk.solver.leftLeg.useAnimatedBendNormal;
m_ikState.m_bendNormalRight = m_vrIk.solver.rightLeg.useAnimatedBendNormal;
m_ikInfo.m_weight = m_vrIk.solver.IKPositionWeight;
m_ikInfo.m_locomotionWeight = m_vrIk.solver.locomotion.weight;
m_ikInfo.m_plantFeet = m_vrIk.solver.plantFeet;
m_ikInfo.m_bendNormalLeft = m_vrIk.solver.leftLeg.useAnimatedBendNormal;
m_ikInfo.m_bendNormalRight = m_vrIk.solver.rightLeg.useAnimatedBendNormal;
if(!BodySystem.isCalibratedAsFullBody)
{
@ -200,11 +214,11 @@ namespace ml_amt
void OnIKPostSolverUpdate()
{
m_vrIk.solver.IKPositionWeight = m_ikState.m_weight;
m_vrIk.solver.locomotion.weight = m_ikState.m_locomotionWeight;
m_vrIk.solver.plantFeet = m_ikState.m_plantFeet;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = m_ikState.m_bendNormalLeft;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = m_ikState.m_bendNormalRight;
m_vrIk.solver.IKPositionWeight = m_ikInfo.m_weight;
m_vrIk.solver.locomotion.weight = m_ikInfo.m_locomotionWeight;
m_vrIk.solver.plantFeet = m_ikInfo.m_plantFeet;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = m_ikInfo.m_bendNormalLeft;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = m_ikInfo.m_bendNormalRight;
}
// Settings

View file

@ -6,15 +6,16 @@ namespace ml_amt
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -18,20 +18,6 @@ namespace ml_amt
public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance);
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldMovementLimit()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
{
l_result = CVRWorld.Instance.baseMovementSpeed;
l_result *= CVRWorld.Instance.sprintMultiplier;
l_result *= CVRWorld.Instance.inAirMovementMultiplier;
l_result *= CVRWorld.Instance.flyMultiplier;
}
return l_result;
}
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static void SetAvatarTPose()

View file

@ -6,7 +6,7 @@
<Company>SDraw</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.4.2</Version>
<Version>1.5.0</Version>
<Platforms>x64</Platforms>
<AssemblyName>AvatarMotionTweaker</AssemblyName>
</PropertyGroup>

View file

@ -1,10 +1,13 @@
using ABI_RC.Core.Player;
using System.Reflection;
using UnityEngine;
namespace ml_asl
{
public class AvatarSyncedLook : MelonLoader.MelonMod
{
readonly static Matrix4x4 ms_back = Matrix4x4.Translate(Vector3.back);
public override void OnInitializeMelon()
{
Settings.Init();
@ -19,7 +22,14 @@ namespace ml_asl
static void OnPlayerAvatarMovementDataUpdate_Postfix(ref PlayerSetup __instance, PlayerAvatarMovementData ____playerAvatarMovementData)
{
if(Settings.Enabled && (__instance.EyeMovementController != null))
{
____playerAvatarMovementData.EyeTrackingOverride = true;
if(__instance.EyeMovementController.CurrentTarget != null)
____playerAvatarMovementData.EyeTrackingPosition = __instance.EyeMovementController.CurrentTarget.GetPosition();
else
____playerAvatarMovementData.EyeTrackingPosition = (__instance.transform.GetMatrix() * ms_back).GetPosition();
}
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_asl.AvatarSyncedLook), "AvatarSyncedLook", "1.0.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_asl.AvatarSyncedLook), "AvatarSyncedLook", "1.1.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -1,14 +1,11 @@
# Avatar Synced Look
This mod Forces local player's eyes look direction to be synced for remote players.
This mod forces local player's eyes look direction to be synced for remote players.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_asl.dll` in `Mods` folder of game
* Put `AvatarSyncedLook.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions - Avatar Synced Look`:
* **Enabled:** sets eyes look direction to be synced or locally generated on remote users side; `true` by default.
# Notes
* Remote users with [EyeMovementFix](https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/EyeMovementFix) installed can't see synced look direction.

View file

@ -6,15 +6,16 @@ namespace ml_asl
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -1,5 +1,6 @@
using ABI_RC.Core.UI;
using System.Reflection;
using UnityEngine;
namespace ml_asl
{
@ -8,5 +9,11 @@ namespace ml_asl
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
// Extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
}
}

View file

@ -5,9 +5,10 @@
<Platforms>x64</Platforms>
<PackageId>AvatarSyncedLook</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>AvatarSyncedLook</Product>
<Version>1.0.4</Version>
<Version>1.1.0</Version>
<AssemblyName>AvatarSyncedLook</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -50,6 +51,11 @@
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>

View file

@ -7,10 +7,11 @@ namespace ml_bft
{
static class AssetsHandler
{
readonly static string ms_namespace = typeof(AssetsHandler).Namespace;
static readonly List<string> ms_assets = new List<string>()
{
"ovr_fingers.asset",
"oxr_fingers.asset"
"ovr_fingers.asset"
};
static readonly Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
@ -19,13 +20,12 @@ namespace ml_bft
public static void Load()
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
foreach(string l_assetName in ms_assets)
{
try
{
Stream l_assetStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_assetName);
Stream l_assetStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + l_assetName);
if(l_assetStream != null)
{
MemoryStream l_memorySteam = new MemoryStream((int)l_assetStream.Length);

View file

@ -45,18 +45,28 @@ namespace ml_bft
HumanBodyBones.RightRingProximal, HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,
HumanBodyBones.RightLittleProximal, HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal
};
static readonly (HumanBodyBones, HumanBodyBones, bool)[] ms_rotationFixChains =
static readonly (HumanBodyBones, HumanBodyBones, bool)[] ms_fingersChains =
{
(HumanBodyBones.LeftThumbProximal,HumanBodyBones.LeftThumbIntermediate,true), (HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,true),
(HumanBodyBones.LeftIndexProximal,HumanBodyBones.LeftIndexIntermediate,true), (HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,true),
(HumanBodyBones.LeftMiddleProximal,HumanBodyBones.LeftMiddleIntermediate,true), (HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,true),
(HumanBodyBones.LeftRingProximal,HumanBodyBones.LeftRingIntermediate,true), (HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,true),
(HumanBodyBones.LeftLittleProximal,HumanBodyBones.LeftLittleIntermediate,true), (HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal,true),
(HumanBodyBones.RightThumbProximal,HumanBodyBones.RightThumbIntermediate,false), (HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,false),
(HumanBodyBones.RightIndexProximal,HumanBodyBones.RightIndexIntermediate,false), (HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,false),
(HumanBodyBones.RightMiddleProximal,HumanBodyBones.RightMiddleIntermediate,false), (HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,false),
(HumanBodyBones.RightRingProximal,HumanBodyBones.RightRingIntermediate,false), (HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,false),
(HumanBodyBones.RightLittleProximal,HumanBodyBones.RightLittleIntermediate,false), (HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal,false)
(HumanBodyBones.LeftThumbProximal,HumanBodyBones.LeftThumbIntermediate,true), (HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,true), (HumanBodyBones.LeftThumbDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftIndexProximal,HumanBodyBones.LeftIndexIntermediate,true), (HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,true), (HumanBodyBones.LeftIndexDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftMiddleProximal,HumanBodyBones.LeftMiddleIntermediate,true), (HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,true), (HumanBodyBones.LeftMiddleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftRingProximal,HumanBodyBones.LeftRingIntermediate,true), (HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,true), (HumanBodyBones.LeftRingDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftLittleProximal,HumanBodyBones.LeftLittleIntermediate,true), (HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal,true), (HumanBodyBones.LeftLittleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.RightThumbProximal,HumanBodyBones.RightThumbIntermediate,false), (HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,false), (HumanBodyBones.RightThumbDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightIndexProximal,HumanBodyBones.RightIndexIntermediate,false), (HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,false), (HumanBodyBones.RightIndexDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightMiddleProximal,HumanBodyBones.RightMiddleIntermediate,false), (HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,false), (HumanBodyBones.RightMiddleDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightRingProximal,HumanBodyBones.RightRingIntermediate,false), (HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,false), (HumanBodyBones.RightRingDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightLittleProximal,HumanBodyBones.RightLittleIntermediate,false), (HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal,false),(HumanBodyBones.RightLittleDistal,HumanBodyBones.LastBone,false),
};
static readonly Vector3[] ms_directions =
{
Vector3.forward,
Vector3.back,
Vector3.left,
Vector3.right,
Vector3.up,
Vector3.down,
};
public static FingerSystem Instance { get; private set; } = null;
@ -104,39 +114,39 @@ namespace ml_bft
internal void OnAvatarSetup()
{
if(PlayerSetup.Instance._animator.isHuman)
Animator l_animator = PlayerSetup.Instance._animator;
if(l_animator.isHuman)
{
Utils.SetAvatarTPose();
InputHandler.Instance.Rebind(PlayerSetup.Instance.transform.rotation);
if(Settings.FixFingers)
foreach(var l_tuple in ms_fingersChains)
{
foreach(var l_tuple in ms_rotationFixChains)
{
ReorientateTowards(
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item1),
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item2),
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OXZ
);
ReorientateTowards(
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item1),
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item2),
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OYX
);
}
ReorientateTowards(
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OXZ
);
ReorientateTowards(
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
InputHandler.Instance.GetSourceForBone(l_tuple.Item1, l_tuple.Item3),
InputHandler.Instance.GetSourceForBone(l_tuple.Item2, l_tuple.Item3),
PlaneType.OYX
);
}
// Bind hands
m_leftHandOffset.m_source = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_leftHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_leftHandOffset.m_target = InputHandler.Instance.GetSourceForBone(HumanBodyBones.LeftHand, true);
if((m_leftHandOffset.m_source != null) && (m_leftHandOffset.m_target != null))
m_leftHandOffset.m_offset = Quaternion.Inverse(m_leftHandOffset.m_source.rotation) * m_leftHandOffset.m_target.rotation;
m_rightHandOffset.m_source = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
m_rightHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.RightHand);
m_rightHandOffset.m_target = InputHandler.Instance.GetSourceForBone(HumanBodyBones.RightHand, false);
if((m_rightHandOffset.m_source != null) && (m_rightHandOffset.m_target != null))
m_rightHandOffset.m_offset = Quaternion.Inverse(m_rightHandOffset.m_source.rotation) * m_rightHandOffset.m_target.rotation;
@ -144,7 +154,7 @@ namespace ml_bft
// Bind fingers
foreach(HumanBodyBones p_bone in ms_leftFingerBones)
{
Transform l_avatarBone = PlayerSetup.Instance._animator.GetBoneTransform(p_bone);
Transform l_avatarBone = l_animator.GetBoneTransform(p_bone);
Transform l_controllerBone = InputHandler.Instance.GetSourceForBone(p_bone, true);
if((l_avatarBone != null) && (l_controllerBone != null))
{
@ -157,7 +167,7 @@ namespace ml_bft
}
foreach(HumanBodyBones p_bone in ms_rightFingerBones)
{
Transform l_avatarBone = PlayerSetup.Instance._animator.GetBoneTransform(p_bone);
Transform l_avatarBone = l_animator.GetBoneTransform(p_bone);
Transform l_controllerBone = InputHandler.Instance.GetSourceForBone(p_bone, false);
if((l_avatarBone != null) && (l_controllerBone != null))
{
@ -262,13 +272,13 @@ namespace ml_bft
}
}
void ReorientateTowards(Transform p_target, Transform p_targetEnd, Transform p_source, Transform p_sourceEnd, PlaneType p_plane)
static void ReorientateTowards(Transform root, Transform p_source, Transform p_sourceEnd, Transform p_target, Transform p_targetEnd, PlaneType p_plane)
{
if((p_target != null) && (p_targetEnd != null) && (p_source != null) && (p_sourceEnd != null))
if((root != null) && (p_target != null) && (p_source != null))
{
Quaternion l_playerInv = Quaternion.Inverse(PlayerSetup.Instance.transform.rotation);
Vector3 l_targetDir = l_playerInv * (p_targetEnd.position - p_target.position);
Vector3 l_sourceDir = l_playerInv * (p_sourceEnd.position - p_source.position);
Quaternion l_rootInv = Quaternion.Inverse(root.rotation);
Vector3 l_targetDir = l_rootInv * (((p_targetEnd != null) ? p_targetEnd.position : GuessEnd(p_target)) - p_target.position);
Vector3 l_sourceDir = l_rootInv * (((p_sourceEnd != null) ? p_sourceEnd.position : GuessEnd(p_source)) - p_source.position);
switch(p_plane)
{
case PlaneType.OXZ:
@ -301,9 +311,30 @@ namespace ml_bft
if(p_plane == PlaneType.OYX)
l_diff = Quaternion.Euler(0f, 0f, l_diff.eulerAngles.y);
Quaternion l_adjusted = l_diff * (l_playerInv * p_target.rotation);
p_target.rotation = PlayerSetup.Instance.transform.rotation * l_adjusted;
Quaternion l_adjusted = l_diff * (l_rootInv * p_target.rotation);
p_target.rotation = root.rotation * l_adjusted;
}
}
static Vector3 GuessEnd(Transform p_target)
{
Vector3 l_result = p_target.position;
if(p_target.parent != null)
{
float l_dot = -1f;
Vector3 l_axisDir = p_target.position - p_target.parent.position;
foreach(Vector3 l_dir in ms_directions)
{
Vector3 l_rotDir = p_target.rotation * l_dir;
float l_stepDot = Vector3.Dot(l_rotDir, l_axisDir);
if(l_stepDot >= l_dot)
{
l_dot = l_stepDot;
l_result = p_target.position + l_rotDir;
}
}
}
return l_result;
}
}
}

View file

@ -90,6 +90,7 @@ namespace ml_bft
OnShowHandsChanged(Settings.ShowHands);
OnMotionRangeChanged(Settings.MotionRange);
Settings.OnSkeletalInputChanged.AddListener(this.OnSkeletalInputChanged);
Settings.OnShowHandsChanged.AddListener(this.OnShowHandsChanged);
Settings.OnMotionRangeChanged.AddListener(this.OnMotionRangeChanged);
}
@ -106,6 +107,7 @@ namespace ml_bft
m_skeletonAction = null;
Settings.OnSkeletalInputChanged.RemoveListener(this.OnSkeletalInputChanged);
Settings.OnShowHandsChanged.RemoveListener(this.OnShowHandsChanged);
Settings.OnMotionRangeChanged.RemoveListener(this.OnMotionRangeChanged);
}
@ -254,12 +256,17 @@ namespace ml_bft
}
// Settings
void OnSkeletalInputChanged(bool p_state)
{
OnShowHandsChanged(Settings.ShowHands);
}
void OnShowHandsChanged(bool p_state)
{
foreach(var l_render in m_renderers)
{
if(l_render != null)
l_render.enabled = p_state;
l_render.enabled = (Settings.SkeletalInput && p_state);
}
}

View file

@ -25,8 +25,7 @@ namespace ml_bft
if(MetaPort.Instance.isUsingVr)
SetupHandlers();
VRModeSwitchEvents.OnInitializeXR.AddListener(this.OnSwitchToVR);
VRModeSwitchEvents.OnDeinitializeXR.AddListener(this.OnSwitchToDesktop);
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(this.OnVRModeSwitch);
Settings.OnSkeletalInputChanged.AddListener(this.OnSkeletalInputChanged);
@ -39,6 +38,8 @@ namespace ml_bft
RemoveHandlers();
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(this.OnVRModeSwitch);
Settings.OnSkeletalInputChanged.RemoveListener(this.OnSkeletalInputChanged);
GameEvents.OnInputUpdate.RemoveListener(this.OnInputUpdate);
@ -133,23 +134,14 @@ namespace ml_bft
}
}
void OnSwitchToVR()
void OnVRModeSwitch(bool p_state)
{
try
{
SetupHandlers();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
void OnSwitchToDesktop()
{
try
{
RemoveHandlers();
if(Utils.IsInVR())
SetupHandlers();
else
RemoveHandlers();
}
catch(System.Exception e)
{

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_bft.BetterFingersTracking), "BetterFingersTracking", "1.0.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_bft.BetterFingersTracking), "BetterFingersTracking", "1.1.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -13,7 +13,7 @@ Available mod's settings in `Settings - Input & Key-Bindings - Better Fingers Tr
* **Filter humanoid limits:** Limits fingers rotations to be valid for Unity's Mechanim; `true` by default
* Note: Enabling this option ensures that visual representation of your fingers will be same for you and remote players, but it cancels out additional finger segments rotations that can be better visually in most cases.
* **Show hands model:** shows transparent hands model (mostly as debug option); `false` by default
* **Change fingers direction at bind:** tries to allign avatar's fingers for more accurate poses
* **Change fingers direction at bind:** tries to allign avatar's fingers for more accurate poses; `true` by default
# Notes
* Currently supports only SteamVR environment, OpenXR support is planned.

View file

@ -6,15 +6,16 @@ namespace ml_bft
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -32,7 +32,6 @@ namespace ml_bft
public static MotionRangeType MotionRange { get; private set; } = MotionRangeType.WithController;
public static bool ShowHands { get; private set; } = false;
public static bool MechanimFilter { get; private set; } = true;
public static bool FixFingers { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -41,7 +40,6 @@ namespace ml_bft
public static readonly SettingEvent<MotionRangeType> OnMotionRangeChanged = new SettingEvent<MotionRangeType>();
public static readonly SettingEvent<bool> OnShowHandsChanged = new SettingEvent<bool>();
public static readonly SettingEvent<bool> OnMechanimFilterChanged = new SettingEvent<bool>();
public static readonly SettingEvent<bool> OnFixFingersChanged = new SettingEvent<bool>();
internal static void Init()
{
@ -52,14 +50,12 @@ namespace ml_bft
ms_category.CreateEntry(ModSetting.SkeletalInput.ToString(), SkeletalInput),
ms_category.CreateEntry(ModSetting.MotionRange.ToString(), (int)MotionRange),
ms_category.CreateEntry(ModSetting.ShowHands.ToString(), ShowHands),
ms_category.CreateEntry(ModSetting.MechanimFilter.ToString(), MechanimFilter),
ms_category.CreateEntry(ModSetting.FixFingers.ToString(), FixFingers)
ms_category.CreateEntry(ModSetting.MechanimFilter.ToString(), MechanimFilter)
};
SkeletalInput = (bool)ms_entries[(int)ModSetting.SkeletalInput].BoxedValue;
MotionRange = (MotionRangeType)(int)ms_entries[(int)ModSetting.MotionRange].BoxedValue;
ShowHands = (bool)ms_entries[(int)ModSetting.ShowHands].BoxedValue;
FixFingers = (bool)ms_entries[(int)ModSetting.FixFingers].BoxedValue;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
@ -115,13 +111,6 @@ namespace ml_bft
OnMechanimFilterChanged.Invoke(MechanimFilter);
}
break;
case ModSetting.FixFingers:
{
FixFingers = l_value;
OnFixFingersChanged.Invoke(FixFingers);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = l_value;

View file

@ -1,4 +1,5 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.InputManagement;
@ -13,6 +14,8 @@ namespace ml_bft
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static bool IsInVR() => ((MetaPort.Instance != null) && MetaPort.Instance.isUsingVr);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index));
public static void SetAvatarTPose()

View file

@ -7,7 +7,7 @@
<Authors>SDraw</Authors>
<Company>SDraw</Company>
<Product>BetterFingersTracking</Product>
<Version>1.0.5</Version>
<Version>1.1.0</Version>
<AssemblyName>BetterFingersTracking</AssemblyName>
</PropertyGroup>

View file

@ -20,13 +20,6 @@
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Change fingers direction at bind: </div>
<div class ="option-input">
<div id="FixFingers" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Filter humanoid limits: </div>
<div class ="option-input">

View file

@ -2,9 +2,9 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Player.EyeMovement;
using ABI_RC.Systems.FaceTracking;
using ABI_RC.Systems.VRModeSwitch;
using RootMotion.FinalIK;
using System;
using System.Reflection;
using UnityEngine;
using ViveSR.anipal.Lip;
@ -13,11 +13,7 @@ namespace ml_dht
[DisallowMultipleComponent]
class HeadTracked : MonoBehaviour
{
static FieldInfo ms_emotePlaying = typeof(PlayerSetup).GetField("_emotePlaying", BindingFlags.NonPublic | BindingFlags.Instance);
bool m_enabled = false;
bool m_headTracking = true;
float m_smoothing = 0.5f;
static HeadTracked ms_instance = null;
CVRAvatar m_avatarDescriptor = null;
Transform m_camera = null;
@ -34,6 +30,9 @@ namespace ml_dht
Quaternion m_bindRotation;
Quaternion m_lastHeadRotation;
DataParser m_dataParser = null;
float m_smoothing = 0.5f;
internal HeadTracked()
{
m_lipData = new LipData_v2();
@ -45,14 +44,28 @@ namespace ml_dht
}
// Unity events
void Awake()
{
if(ms_instance != null)
{
DestroyImmediate(this);
return;
}
DontDestroyOnLoad(this);
ms_instance = this;
m_dataParser = new DataParser();
}
void Start()
{
OnEnabledChanged(Settings.Enabled);
OnHeadTrackingChanged(Settings.HeadTracking);
OnSmoothingChanged(Settings.Smoothing);
Settings.OnEnabledChanged.AddListener(this.OnEnabledChanged);
Settings.OnHeadTrackingChanged.AddListener(this.OnHeadTrackingChanged);
OnVRModeSwitch(true);
Settings.OnEnabledChanged.AddListener(this.OnEnabledOrHeadTrackingChanged);
Settings.OnHeadTrackingChanged.AddListener(this.OnEnabledOrHeadTrackingChanged);
Settings.OnSmoothingChanged.AddListener(this.OnSmoothingChanged);
GameEvents.OnAvatarClear.AddListener(this.OnAvatarClear);
@ -60,12 +73,19 @@ namespace ml_dht
GameEvents.OnAvatarReuse.AddListener(this.OnAvatarReuse);
GameEvents.OnEyeControllerUpdate.AddListener(this.OnEyeControllerUpdate);
GameEvents.OnFaceTrackingUpdate.AddListener(this.UpdateFaceTracking);
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(this.OnVRModeSwitch);
}
void OnDestroy()
{
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledChanged);
Settings.OnHeadTrackingChanged.RemoveListener(this.OnHeadTrackingChanged);
if(ms_instance == this)
ms_instance = null;
m_dataParser = null;
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledOrHeadTrackingChanged);
Settings.OnHeadTrackingChanged.RemoveListener(this.OnEnabledOrHeadTrackingChanged);
Settings.OnSmoothingChanged.RemoveListener(this.OnSmoothingChanged);
GameEvents.OnAvatarClear.RemoveListener(this.OnAvatarClear);
@ -73,12 +93,20 @@ namespace ml_dht
GameEvents.OnAvatarReuse.RemoveListener(this.OnAvatarReuse);
GameEvents.OnEyeControllerUpdate.RemoveListener(this.OnEyeControllerUpdate);
GameEvents.OnFaceTrackingUpdate.RemoveListener(this.UpdateFaceTracking);
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(this.OnVRModeSwitch);
}
void Update()
{
if(m_lipDataSent)
m_lipDataSent = false;
if(Settings.Enabled && (m_dataParser != null))
{
m_dataParser.Update();
UpdateTrackingData(ref m_dataParser.GetLatestTrackingData());
}
}
// Tracking updates
@ -98,11 +126,11 @@ namespace ml_dht
void OnLookIKPostUpdate()
{
if(m_enabled && m_headTracking && (m_headBone != null))
if(Settings.Enabled && Settings.HeadTracking && (m_headBone != null))
{
m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_avatarDescriptor.transform.rotation * (m_headRotation * m_bindRotation), m_smoothing);
if(!(bool)ms_emotePlaying.GetValue(PlayerSetup.Instance))
if(!PlayerSetup.Instance.IsEmotePlaying())
m_headBone.rotation = m_lastHeadRotation;
}
}
@ -142,7 +170,7 @@ namespace ml_dht
void OnEyeControllerUpdate(EyeMovementController p_component)
{
if(m_enabled)
if(this.enabled && Settings.Enabled)
{
// Gaze
if(Settings.EyeTracking && (m_camera != null))
@ -162,7 +190,7 @@ namespace ml_dht
void UpdateFaceTracking(CVRFaceTracking p_component, GameEvents.EventResult p_result)
{
if(p_component.isLocal && p_component.UseFacialTracking && m_enabled && Settings.FaceTracking)
if(this.enabled && Settings.Enabled && Settings.FaceTracking && p_component.isLocal && p_component.UseFacialTracking )
{
if(!m_lipDataSent)
{
@ -176,33 +204,27 @@ namespace ml_dht
}
}
// Settings
void OnEnabledChanged(bool p_state)
void OnVRModeSwitch(bool p_state)
{
if(m_enabled != p_state)
try
{
m_enabled = p_state;
TryRestoreHeadRotation();
this.enabled = !Utils.IsInVR();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
void OnHeadTrackingChanged(bool p_state)
// Settings
void OnEnabledOrHeadTrackingChanged(bool p_state)
{
if(m_headTracking != p_state)
{
m_headTracking = p_state;
TryRestoreHeadRotation();
}
if(Settings.Enabled && Settings.HeadTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
void OnSmoothingChanged(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
// Arbitrary
void TryRestoreHeadRotation()
{
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
}
}

View file

@ -1,12 +1,13 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using UnityEngine;
namespace ml_dht
{
public class DesktopHeadTracking : MelonLoader.MelonMod
{
DataParser m_dataParser = null;
HeadTracked m_localTracked = null;
HeadTracked m_tracked = null;
public override void OnInitializeMelon()
{
@ -26,24 +27,14 @@ namespace ml_dht
GameEvents.InitB(HarmonyInstance);
m_dataParser = new DataParser();
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
m_tracked = new GameObject("[DesktopHeadTracking]").AddComponent<HeadTracked>();
}
public override void OnDeinitializeMelon()
{
m_dataParser = null;
m_localTracked = null;
}
public override void OnUpdate()
{
if(Settings.Enabled && (m_dataParser != null))
{
m_dataParser.Update();
if(m_localTracked != null)
m_localTracked.UpdateTrackingData(ref m_dataParser.GetLatestTrackingData());
}
if(m_tracked != null)
Object.Destroy(m_tracked.gameObject);
m_tracked = null;
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.2.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.3.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -13,7 +13,7 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_dht.dll` in `Mods` folder of game
* Put `DesktopHeadTracking.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:

View file

@ -6,15 +6,16 @@ namespace ml_dht
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using System.Reflection;
@ -13,6 +14,8 @@ namespace ml_dht
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo ms_updateShapesLocal = typeof(CVRFaceTracking).GetMethod("UpdateShapesLocal", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((MetaPort.Instance != null) && MetaPort.Instance.isUsingVr);
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static void UpdateShapesLocal_Private(this CVRFaceTracking p_instance) => ms_updateShapesLocal?.Invoke(p_instance, ms_emptyArray);

View file

@ -4,10 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework>
<PackageId>DesktopHeadTracking</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>DesktopHeadTracking</Product>
<Version>1.2.5</Version>
<Version>1.3.0</Version>
<Platforms>x64</Platforms>
<AssemblyName>DesktopHeadTracking</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

View file

@ -7,6 +7,8 @@ namespace ml_lme
{
static class AssetsHandler
{
readonly static string ms_namespace = typeof(AssetsHandler).Namespace;
static readonly List<string> ms_assets = new List<string>()
{
"leapmotion_controller.asset",
@ -19,13 +21,12 @@ namespace ml_lme
public static void Load()
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
foreach(string l_assetName in ms_assets)
{
try
{
Stream l_assetStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_assetName);
Stream l_assetStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + l_assetName);
if(l_assetStream != null)
{
MemoryStream l_memorySteam = new MemoryStream((int)l_assetStream.Length);

View file

@ -8,6 +8,8 @@ namespace ml_lme
{
static class DependenciesHandler
{
readonly static string ms_namespace = typeof(DependenciesHandler).Namespace;
static readonly List<string> ms_libraries = new List<string>()
{
"LeapC.dll"
@ -16,11 +18,10 @@ namespace ml_lme
public static void ExtractDependencies()
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
foreach(string l_library in ms_libraries)
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_library);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + l_library);
if(!File.Exists(l_library))
{

View file

@ -123,107 +123,89 @@ namespace ml_lme
}
public Transform GetRoot() => m_root;
public Transform GetBone(HumanBodyBones p_bone)
public Transform GetLinkedBone(HumanBodyBones p_bone)
{
Transform l_result = null;
switch(p_bone)
{
case HumanBodyBones.LeftHand:
l_result = (m_left ? m_wrist : null);
break;
case HumanBodyBones.LeftThumbProximal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.ThumbProximal] : null);
break;
case HumanBodyBones.LeftThumbIntermediate:
l_result = (m_left ? m_fingersBones[(int)FingerBone.ThumbIntermediate] : null);
break;
case HumanBodyBones.LeftThumbDistal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.ThumbDistal] : null);
break;
case HumanBodyBones.LeftIndexProximal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.IndexProximal] : null);
break;
case HumanBodyBones.LeftIndexIntermediate:
l_result = (m_left ? m_fingersBones[(int)FingerBone.IndexIntermediate] : null);
break;
case HumanBodyBones.LeftIndexDistal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.IndexDistal] : null);
break;
case HumanBodyBones.LeftMiddleProximal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.MiddleProximal] : null);
break;
case HumanBodyBones.LeftMiddleIntermediate:
l_result = (m_left ? m_fingersBones[(int)FingerBone.MiddleIntermediate] : null);
break;
case HumanBodyBones.LeftMiddleDistal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.MiddleDistal] : null);
break;
case HumanBodyBones.LeftRingProximal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.RingProximal] : null);
break;
case HumanBodyBones.LeftRingIntermediate:
l_result = (m_left ? m_fingersBones[(int)FingerBone.RingIntermediate] : null);
break;
case HumanBodyBones.LeftRingDistal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.RingDistal] : null);
break;
case HumanBodyBones.LeftLittleProximal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.PinkyProximal] : null);
break;
case HumanBodyBones.LeftLittleIntermediate:
l_result = (m_left ? m_fingersBones[(int)FingerBone.PinkyIntermediate] : null);
break;
case HumanBodyBones.LeftLittleDistal:
l_result = (m_left ? m_fingersBones[(int)FingerBone.PinkyDistal] : null);
case HumanBodyBones.RightHand:
l_result = m_wrist;
break;
case HumanBodyBones.RightHand:
l_result = (!m_left ? m_wrist : null);
break;
case HumanBodyBones.LeftThumbProximal:
case HumanBodyBones.RightThumbProximal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.ThumbProximal] : null);
l_result = m_fingersBones[(int)FingerBone.ThumbProximal];
break;
case HumanBodyBones.LeftThumbIntermediate:
case HumanBodyBones.RightThumbIntermediate:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.ThumbIntermediate] : null);
l_result = m_fingersBones[(int)FingerBone.ThumbIntermediate];
break;
case HumanBodyBones.LeftThumbDistal:
case HumanBodyBones.RightThumbDistal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.ThumbDistal] : null);
l_result = m_fingersBones[(int)FingerBone.ThumbDistal];
break;
case HumanBodyBones.LeftIndexProximal:
case HumanBodyBones.RightIndexProximal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.IndexProximal] : null);
l_result = m_fingersBones[(int)FingerBone.IndexProximal];
break;
case HumanBodyBones.LeftIndexIntermediate:
case HumanBodyBones.RightIndexIntermediate:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.IndexIntermediate] : null);
l_result = m_fingersBones[(int)FingerBone.IndexIntermediate];
break;
case HumanBodyBones.LeftIndexDistal:
case HumanBodyBones.RightIndexDistal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.IndexDistal] : null);
l_result = m_fingersBones[(int)FingerBone.IndexDistal];
break;
case HumanBodyBones.LeftMiddleProximal:
case HumanBodyBones.RightMiddleProximal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.MiddleProximal] : null);
l_result = m_fingersBones[(int)FingerBone.MiddleProximal];
break;
case HumanBodyBones.LeftMiddleIntermediate:
case HumanBodyBones.RightMiddleIntermediate:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.MiddleIntermediate] : null);
l_result = m_fingersBones[(int)FingerBone.MiddleIntermediate];
break;
case HumanBodyBones.LeftMiddleDistal:
case HumanBodyBones.RightMiddleDistal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.MiddleDistal] : null);
l_result = m_fingersBones[(int)FingerBone.MiddleDistal];
break;
case HumanBodyBones.LeftRingProximal:
case HumanBodyBones.RightRingProximal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.RingProximal] : null);
l_result = m_fingersBones[(int)FingerBone.RingProximal];
break;
case HumanBodyBones.LeftRingIntermediate:
case HumanBodyBones.RightRingIntermediate:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.RingIntermediate] : null);
l_result = m_fingersBones[(int)FingerBone.RingIntermediate];
break;
case HumanBodyBones.LeftRingDistal:
case HumanBodyBones.RightRingDistal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.RingDistal] : null);
l_result = m_fingersBones[(int)FingerBone.RingDistal];
break;
case HumanBodyBones.LeftLittleProximal:
case HumanBodyBones.RightLittleProximal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.PinkyProximal] : null);
l_result = m_fingersBones[(int)FingerBone.PinkyProximal];
break;
case HumanBodyBones.LeftLittleIntermediate:
case HumanBodyBones.RightLittleIntermediate:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.PinkyIntermediate] : null);
l_result = m_fingersBones[(int)FingerBone.PinkyIntermediate];
break;
case HumanBodyBones.LeftLittleDistal:
case HumanBodyBones.RightLittleDistal:
l_result = (!m_left ? m_fingersBones[(int)FingerBone.PinkyDistal] : null);
l_result = m_fingersBones[(int)FingerBone.PinkyDistal];
break;
}
return l_result;
@ -234,5 +216,7 @@ namespace ml_lme
if(m_mesh != null)
m_mesh.SetActive(p_state);
}
public bool IsLeft() => m_left;
}
}

View file

@ -89,8 +89,7 @@ namespace ml_lme
MelonLoader.MelonCoroutines.Start(WaitForSettings());
MelonLoader.MelonCoroutines.Start(WaitForMaterial());
VRModeSwitchEvents.OnInitializeXR.AddListener(OnModeSwitch);
VRModeSwitchEvents.OnDeinitializeXR.AddListener(OnModeSwitch);
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(OnVRModeSwitch);
Settings.OnEnabledChanged.AddListener(this.OnEnableChanged);
Settings.OnInteractionChanged.AddListener(this.OnInteractionChanged);
@ -153,8 +152,7 @@ namespace ml_lme
m_lineRight = null;
MetaPort.Instance.settings.settingBoolChanged.RemoveListener(this.OnGameSettingBoolChange);
VRModeSwitchEvents.OnInitializeXR.RemoveListener(OnModeSwitch);
VRModeSwitchEvents.OnDeinitializeXR.RemoveListener(OnModeSwitch);
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(OnVRModeSwitch);
Settings.OnEnabledChanged.RemoveListener(this.OnEnableChanged);
Settings.OnInteractionChanged.RemoveListener(this.OnInteractionChanged);
@ -291,7 +289,7 @@ namespace ml_lme
m_handVisibleRight = false;
}
if(!ModSupporter.SkipFingersOverride() && (!m_inVR || !Utils.AreKnucklesInUse()))
if(!m_inVR || !Utils.AreKnucklesInUse())
SetGameFingersTracking(m_handVisibleRight || m_handVisibleLeft);
base.UpdateInput();
@ -384,7 +382,7 @@ namespace ml_lme
ResetGestures(false);
}
// Reset to default, FreedomFingers can go brrr, player should press funny controller button two times
// Reset to default
SetGameFingersTracking(m_inVR && Utils.AreKnucklesInUse() && !CVRInputManager._moduleXR.GestureToggleValue);
}
@ -450,23 +448,30 @@ namespace ml_lme
}
}
void OnModeSwitch()
void OnVRModeSwitch(bool p_state)
{
m_inVR = Utils.IsInVR();
base._inputManager.SetModuleAsLast(this);
if(m_handRayLeft != null)
try
{
m_handRayLeft.isDesktopRay = !m_inVR;
m_handRayLeft.SetVRActive(m_inVR);
}
if(m_handRayRight != null)
{
m_handRayRight.isDesktopRay = !m_inVR;
m_handRayRight.SetVRActive(m_inVR);
}
m_inVR = Utils.IsInVR();
base._inputManager.SetModuleAsLast(this);
OnEnableChanged(Settings.Enabled);
if(m_handRayLeft != null)
{
m_handRayLeft.isDesktopRay = !m_inVR;
m_handRayLeft.SetVRActive(m_inVR);
}
if(m_handRayRight != null)
{
m_handRayRight.isDesktopRay = !m_inVR;
m_handRayRight.SetVRActive(m_inVR);
}
OnEnableChanged(Settings.Enabled);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
// Arbitrary

View file

@ -19,9 +19,9 @@ namespace ml_lme
void Awake()
{
if((Instance != null) && (Instance != this))
if(Instance != null)
{
Object.DestroyImmediate(this);
DestroyImmediate(this);
return;
}
@ -44,8 +44,7 @@ namespace ml_lme
Settings.OnEnabledChanged.AddListener(this.OnEnableChanged);
Settings.OnTrackingModeChanged.AddListener(this.OnTrackingModeChanged);
m_leapTracking = new GameObject("[LeapTrackingRoot]").AddComponent<LeapTracking>();
m_leapTracking.transform.parent = this.transform;
m_leapTracking = this.gameObject.AddComponent<LeapTracking>();
OnEnableChanged(Settings.Enabled);
OnTrackingModeChanged(Settings.TrackingMode);
@ -102,7 +101,7 @@ namespace ml_lme
m_leapInput = new LeapInput();
CVRInputManager.Instance.AddInputModule(m_leapInput);
m_leapTracked = PlayerSetup.Instance.gameObject.AddComponent<LeapTracked>();
m_leapTracked = this.gameObject.AddComponent<LeapTracked>();
}
void Update()

View file

@ -77,18 +77,28 @@ namespace ml_lme
(HumanBodyBones.RightLittleIntermediate, false),
(HumanBodyBones.RightLittleDistal, false),
};
static readonly (HumanBodyBones, HumanBodyBones, bool)[] ms_rotationFixChains =
static readonly (HumanBodyBones, HumanBodyBones, bool)[] ms_fingersChains =
{
(HumanBodyBones.LeftThumbProximal,HumanBodyBones.LeftThumbIntermediate,true), (HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,true),
(HumanBodyBones.LeftIndexProximal,HumanBodyBones.LeftIndexIntermediate,true), (HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,true),
(HumanBodyBones.LeftMiddleProximal,HumanBodyBones.LeftMiddleIntermediate,true), (HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,true),
(HumanBodyBones.LeftRingProximal,HumanBodyBones.LeftRingIntermediate,true), (HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,true),
(HumanBodyBones.LeftLittleProximal,HumanBodyBones.LeftLittleIntermediate,true), (HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal,true),
(HumanBodyBones.RightThumbProximal,HumanBodyBones.RightThumbIntermediate,false), (HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,false),
(HumanBodyBones.RightIndexProximal,HumanBodyBones.RightIndexIntermediate,false), (HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,false),
(HumanBodyBones.RightMiddleProximal,HumanBodyBones.RightMiddleIntermediate,false), (HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,false),
(HumanBodyBones.RightRingProximal,HumanBodyBones.RightRingIntermediate,false), (HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,false),
(HumanBodyBones.RightLittleProximal,HumanBodyBones.RightLittleIntermediate,false), (HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal,false)
(HumanBodyBones.LeftThumbProximal,HumanBodyBones.LeftThumbIntermediate,true), (HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbDistal,true), (HumanBodyBones.LeftThumbDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftIndexProximal,HumanBodyBones.LeftIndexIntermediate,true), (HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexDistal,true), (HumanBodyBones.LeftIndexDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftMiddleProximal,HumanBodyBones.LeftMiddleIntermediate,true), (HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleDistal,true), (HumanBodyBones.LeftMiddleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftRingProximal,HumanBodyBones.LeftRingIntermediate,true), (HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingDistal,true), (HumanBodyBones.LeftRingDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.LeftLittleProximal,HumanBodyBones.LeftLittleIntermediate,true), (HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleDistal,true), (HumanBodyBones.LeftLittleDistal,HumanBodyBones.LastBone,true),
(HumanBodyBones.RightThumbProximal,HumanBodyBones.RightThumbIntermediate,false), (HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbDistal,false), (HumanBodyBones.RightThumbDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightIndexProximal,HumanBodyBones.RightIndexIntermediate,false), (HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexDistal,false), (HumanBodyBones.RightIndexDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightMiddleProximal,HumanBodyBones.RightMiddleIntermediate,false), (HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleDistal,false), (HumanBodyBones.RightMiddleDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightRingProximal,HumanBodyBones.RightRingIntermediate,false), (HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingDistal,false), (HumanBodyBones.RightRingDistal,HumanBodyBones.LastBone,false),
(HumanBodyBones.RightLittleProximal,HumanBodyBones.RightLittleIntermediate,false), (HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleDistal,false),(HumanBodyBones.RightLittleDistal,HumanBodyBones.LastBone,false),
};
static readonly Vector3[] ms_directions =
{
Vector3.forward,
Vector3.back,
Vector3.left,
Vector3.right,
Vector3.up,
Vector3.down,
};
public static readonly float[] ms_lastLeftFingerBones = new float[20];
@ -97,10 +107,6 @@ namespace ml_lme
VRIK m_vrIK = null;
Transform m_hips = null;
bool m_enabled = true;
bool m_fingersOnly = false;
bool m_trackElbows = true;
IKInfo m_vrIKInfo;
ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null;
@ -135,12 +141,11 @@ namespace ml_lme
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
OnEnabledChanged(Settings.Enabled);
OnFingersOnlyChanged(Settings.FingersOnly);
OnEnabledOrFingersOnlyChanged(Settings.Enabled || Settings.FingersOnly);
OnTrackElbowsChanged(Settings.TrackElbows);
Settings.OnEnabledChanged.AddListener(this.OnEnabledChanged);
Settings.OnFingersOnlyChanged.AddListener(this.OnFingersOnlyChanged);
Settings.OnEnabledChanged.AddListener(this.OnEnabledOrFingersOnlyChanged);
Settings.OnFingersOnlyChanged.AddListener(this.OnEnabledOrFingersOnlyChanged);
Settings.OnTrackElbowsChanged.AddListener(this.OnTrackElbowsChanged);
GameEvents.OnAvatarClear.AddListener(this.OnAvatarClear);
@ -165,8 +170,8 @@ namespace ml_lme
m_vrIK = null;
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledChanged);
Settings.OnFingersOnlyChanged.RemoveListener(this.OnFingersOnlyChanged);
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledOrFingersOnlyChanged);
Settings.OnFingersOnlyChanged.RemoveListener(this.OnEnabledOrFingersOnlyChanged);
Settings.OnTrackElbowsChanged.RemoveListener(this.OnTrackElbowsChanged);
GameEvents.OnAvatarClear.RemoveListener(this.OnAvatarClear);
@ -176,24 +181,24 @@ namespace ml_lme
void Update()
{
if(m_enabled)
if(Settings.Enabled)
{
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.IKPositionWeight = Mathf.Lerp(m_leftArmIK.solver.IKPositionWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKRotationWeight = Mathf.Lerp(m_leftArmIK.solver.IKRotationWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_leftArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftArmIK.solver.arm.bendGoalWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKPositionWeight = Mathf.Lerp(m_leftArmIK.solver.IKPositionWeight, (l_data.m_leftHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKRotationWeight = Mathf.Lerp(m_leftArmIK.solver.IKRotationWeight, (l_data.m_leftHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
if(Settings.TrackElbows)
m_leftArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftArmIK.solver.arm.bendGoalWeight, (l_data.m_leftHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKPositionWeight = Mathf.Lerp(m_rightArmIK.solver.IKPositionWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKRotationWeight = Mathf.Lerp(m_rightArmIK.solver.IKRotationWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_rightArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightArmIK.solver.arm.bendGoalWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKPositionWeight = Mathf.Lerp(m_rightArmIK.solver.IKPositionWeight, (l_data.m_rightHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKRotationWeight = Mathf.Lerp(m_rightArmIK.solver.IKRotationWeight, (l_data.m_rightHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
if(Settings.TrackElbows)
m_rightArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightArmIK.solver.arm.bendGoalWeight, (l_data.m_rightHand.m_present && !Settings.FingersOnly) ? 1f : 0f, 0.25f);
}
if((m_vrIK != null) && !m_fingersOnly)
if((m_vrIK != null) && !Settings.FingersOnly)
{
m_leftTargetActive = l_data.m_leftHand.m_present;
m_rightTargetActive = l_data.m_rightHand.m_present;
@ -203,7 +208,7 @@ namespace ml_lme
void LateUpdate()
{
if(m_enabled && (m_poseHandler != null))
if(Settings.Enabled && (m_poseHandler != null))
{
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present)
@ -276,24 +281,25 @@ namespace ml_lme
void OnAvatarSetup()
{
if(PlayerSetup.Instance._animator.isHuman)
Animator l_animator = PlayerSetup.Instance._animator;
if(l_animator.isHuman)
{
Utils.SetAvatarTPose();
m_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._animator.transform);
m_poseHandler = new HumanPoseHandler(l_animator.avatar, l_animator.transform);
m_poseHandler.GetHumanPose(ref m_pose);
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_hips = l_animator.GetBoneTransform(HumanBodyBones.Hips);
m_leftHandOffset.m_source = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_leftHandTarget.localRotation = ms_offsetLeft * (Quaternion.Inverse(PlayerSetup.Instance._avatar.transform.rotation) * m_leftHandOffset.m_source.rotation);
m_leftHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_leftHandTarget.localRotation = ms_offsetLeft * (Quaternion.Inverse(l_animator.transform.rotation) * m_leftHandOffset.m_source.rotation);
m_rightHandOffset.m_source = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
m_rightHandTarget.localRotation = ms_offsetRight * (Quaternion.Inverse(PlayerSetup.Instance._avatar.transform.rotation) * m_rightHandOffset.m_source.rotation);
m_rightHandOffset.m_source = l_animator.GetBoneTransform(HumanBodyBones.RightHand);
m_rightHandTarget.localRotation = ms_offsetRight * (Quaternion.Inverse(l_animator.transform.rotation) * m_rightHandOffset.m_source.rotation);
ParseFingersBones();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
m_vrIK = l_animator.GetComponent<VRIK>();
if(m_vrIK != null)
{
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreSolverUpdate);
@ -339,7 +345,7 @@ namespace ml_lme
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_vrIK.solver.leftArm.bendGoalWeight = (Settings.TrackElbows ? 1f : 0f);
}
if(m_rightTargetActive)
{
@ -353,7 +359,7 @@ namespace ml_lme
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_vrIK.solver.rightArm.bendGoalWeight = (Settings.TrackElbows ? 1f : 0f);
}
}
void OnIKPostSolverUpdate()
@ -377,30 +383,18 @@ namespace ml_lme
}
// Settings
void OnEnabledChanged(bool p_state)
void OnEnabledOrFingersOnlyChanged(bool p_state)
{
m_enabled = p_state;
RefreshArmIK();
ResetTargetsStates();
}
void OnFingersOnlyChanged(bool p_state)
{
m_fingersOnly = p_state;
RefreshArmIK();
ResetTargetsStates();
}
void OnTrackElbowsChanged(bool p_state)
{
m_trackElbows = p_state;
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.solver.arm.bendGoalWeight = (p_state ? 1f : 0f);
m_rightArmIK.solver.arm.bendGoalWeight = (p_state ? 1f : 0f);
}
ResetTargetsStates();
@ -415,41 +409,42 @@ namespace ml_lme
void SetupArmIK()
{
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
Animator l_animator = PlayerSetup.Instance._animator;
Transform l_chest = l_animator.GetBoneTransform(HumanBodyBones.UpperChest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
l_chest = l_animator.GetBoneTransform(HumanBodyBones.Chest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
l_chest = l_animator.GetBoneTransform(HumanBodyBones.Spine);
m_leftArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_leftArmIK = l_animator.gameObject.AddComponent<ArmIK>();
m_leftArmIK.solver.isLeft = true;
m_leftArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand),
PlayerSetup.Instance._animator.transform
l_animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
l_animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftHand),
l_animator.transform
);
m_leftArmIK.solver.arm.target = m_leftHandTarget;
m_leftArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_leftArmIK.solver.arm.bendGoalWeight = (Settings.TrackElbows ? 1f : 0f);
m_leftArmIK.enabled = (Settings.Enabled && !Settings.FingersOnly);
m_rightArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_rightArmIK = l_animator.gameObject.AddComponent<ArmIK>();
m_rightArmIK.solver.isLeft = false;
m_rightArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand),
PlayerSetup.Instance._animator.transform
l_animator.GetBoneTransform(HumanBodyBones.RightShoulder),
l_animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.RightHand),
l_animator.transform
);
m_rightArmIK.solver.arm.target = m_rightHandTarget;
m_rightArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.solver.arm.bendGoalWeight = (Settings.TrackElbows ? 1f : 0f);
m_rightArmIK.enabled = (Settings.Enabled && !Settings.FingersOnly);
}
void RemoveArmIK()
@ -467,8 +462,8 @@ namespace ml_lme
{
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
m_leftArmIK.enabled = (Settings.Enabled && !Settings.FingersOnly);
m_rightArmIK.enabled = (Settings.Enabled && !Settings.FingersOnly);
}
}
@ -476,42 +471,48 @@ namespace ml_lme
{
LeapTracking.Instance.Rebind(PlayerSetup.Instance.transform.rotation);
// Align rotations of leap fingers to avatar fingers
Animator l_animator = PlayerSetup.Instance._animator;
LeapHand l_leapLeft = LeapTracking.Instance.GetLeftHand();
LeapHand l_leapRight = LeapTracking.Instance.GetRightHand();
// Try to "fix" rotations, slightly inaccurate after 0YX plane rotation
foreach(var l_tuple in ms_rotationFixChains)
foreach(var l_tuple in ms_fingersChains)
{
ReorientateTowards(
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item1),
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item2),
l_tuple.Item3 ? LeapTracking.Instance.GetLeftHand().GetBone(l_tuple.Item1) : LeapTracking.Instance.GetRightHand().GetBone(l_tuple.Item1),
l_tuple.Item3 ? LeapTracking.Instance.GetLeftHand().GetBone(l_tuple.Item2) : LeapTracking.Instance.GetRightHand().GetBone(l_tuple.Item2),
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
l_tuple.Item3 ? l_leapLeft.GetLinkedBone(l_tuple.Item1) : l_leapRight.GetLinkedBone(l_tuple.Item1),
l_tuple.Item3 ? l_leapLeft.GetLinkedBone(l_tuple.Item2) : l_leapRight.GetLinkedBone(l_tuple.Item2),
PlaneType.OXZ
);
ReorientateTowards(
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item1),
PlayerSetup.Instance._animator.GetBoneTransform(l_tuple.Item2),
l_tuple.Item3 ? LeapTracking.Instance.GetLeftHand().GetBone(l_tuple.Item1) : LeapTracking.Instance.GetRightHand().GetBone(l_tuple.Item1),
l_tuple.Item3 ? LeapTracking.Instance.GetLeftHand().GetBone(l_tuple.Item2) : LeapTracking.Instance.GetRightHand().GetBone(l_tuple.Item2),
PlayerSetup.Instance.transform,
l_animator.GetBoneTransform(l_tuple.Item1),
(l_tuple.Item2 != HumanBodyBones.LastBone) ? l_animator.GetBoneTransform(l_tuple.Item2) : null,
l_tuple.Item3 ? l_leapLeft.GetLinkedBone(l_tuple.Item1) : l_leapRight.GetLinkedBone(l_tuple.Item1),
l_tuple.Item3 ? l_leapLeft.GetLinkedBone(l_tuple.Item2) : l_leapRight.GetLinkedBone(l_tuple.Item2),
PlaneType.OYX
);
}
// Bind
m_leftHandOffset.m_target = LeapTracking.Instance.GetLeftHand().GetBone(HumanBodyBones.LeftHand);
m_leftHandOffset.m_target = l_leapLeft.GetLinkedBone(HumanBodyBones.LeftHand);
if((m_leftHandOffset.m_source != null) && (m_leftHandOffset.m_target != null))
m_leftHandOffset.m_offset = Quaternion.Inverse(m_leftHandOffset.m_source.rotation) * m_leftHandOffset.m_target.rotation;
m_rightHandOffset.m_target = LeapTracking.Instance.GetRightHand().GetBone(HumanBodyBones.RightHand);
m_rightHandOffset.m_target = l_leapRight.GetLinkedBone(HumanBodyBones.RightHand);
if((m_rightHandOffset.m_source != null) && (m_rightHandOffset.m_target != null))
m_rightHandOffset.m_offset = Quaternion.Inverse(m_rightHandOffset.m_source.rotation) * m_rightHandOffset.m_target.rotation;
foreach(var l_link in ms_fingers)
{
Transform l_transform = PlayerSetup.Instance._animator.GetBoneTransform(l_link.Item1);
Transform l_transform = l_animator.GetBoneTransform(l_link.Item1);
if(l_transform != null)
{
RotationOffset l_offset = new RotationOffset();
l_offset.m_target = l_transform;
l_offset.m_source = (l_link.Item2 ? LeapTracking.Instance.GetLeftHand().GetBone(l_link.Item1) : LeapTracking.Instance.GetRightHand().GetBone(l_link.Item1));
l_offset.m_source = (l_link.Item2 ? l_leapLeft.GetLinkedBone(l_link.Item1) : l_leapRight.GetLinkedBone(l_link.Item1));
l_offset.m_offset = Quaternion.Inverse(l_offset.m_source.rotation) * l_offset.m_target.rotation;
if(l_link.Item2)
@ -522,13 +523,13 @@ namespace ml_lme
}
}
void ReorientateTowards(Transform p_target, Transform p_targetEnd, Transform p_source, Transform p_sourceEnd, PlaneType p_plane)
static void ReorientateTowards(Transform root, Transform p_source, Transform p_sourceEnd, Transform p_target, Transform p_targetEnd, PlaneType p_plane)
{
if((p_target != null) && (p_targetEnd != null) && (p_source != null) && (p_sourceEnd != null))
if((root != null) && (p_target != null) && (p_source != null))
{
Quaternion l_playerInv = Quaternion.Inverse(PlayerSetup.Instance.transform.rotation);
Vector3 l_targetDir = l_playerInv * (p_targetEnd.position - p_target.position);
Vector3 l_sourceDir = l_playerInv * (p_sourceEnd.position - p_source.position);
Quaternion l_rootInv = Quaternion.Inverse(root.rotation);
Vector3 l_targetDir = l_rootInv * (((p_targetEnd != null) ? p_targetEnd.position : GuessEnd(p_target)) - p_target.position);
Vector3 l_sourceDir = l_rootInv * (((p_sourceEnd != null) ? p_sourceEnd.position : GuessEnd(p_source)) - p_source.position);
switch(p_plane)
{
case PlaneType.OXZ:
@ -561,9 +562,30 @@ namespace ml_lme
if(p_plane == PlaneType.OYX)
l_diff = Quaternion.Euler(0f, 0f, l_diff.eulerAngles.y);
Quaternion l_adjusted = l_diff * (l_playerInv * p_target.rotation);
p_target.rotation = PlayerSetup.Instance.transform.rotation * l_adjusted;
Quaternion l_adjusted = l_diff * (l_rootInv * p_target.rotation);
p_target.rotation = root.rotation * l_adjusted;
}
}
static Vector3 GuessEnd(Transform p_target)
{
Vector3 l_result = p_target.position;
if(p_target.parent != null)
{
float l_dot = -1f;
Vector3 l_axisDir = p_target.position - p_target.parent.position;
foreach(Vector3 l_dir in ms_directions)
{
Vector3 l_rotDir = p_target.rotation * l_dir;
float l_stepDot = Vector3.Dot(l_rotDir, l_axisDir);
if(l_stepDot >= l_dot)
{
l_dot = l_stepDot;
l_result = p_target.position + l_rotDir;
}
}
}
return l_result;
}
}
}

View file

@ -1,6 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.VRModeSwitch;
using System.Collections;
using UnityEngine;
namespace ml_lme
@ -13,8 +11,8 @@ namespace ml_lme
static readonly Quaternion ms_hmdRotation = new Quaternion(0f, 0.7071068f, 0.7071068f, 0f);
static readonly Quaternion ms_screentopRotation = new Quaternion(0f, 0f, -1f, 0f);
bool m_inVR = false;
Transform m_root = null;
Transform m_offsetPoint = null;
GameObject m_leapHands = null;
LeapHand m_leapHandLeft = null;
LeapHand m_leapHandRight = null;
@ -22,26 +20,33 @@ namespace ml_lme
Transform m_leapElbowRight = null;
GameObject m_leapControllerModel = null;
float m_scaleRelation = 1f;
void Start()
{
if((Instance != null) && (Instance != this))
if(Instance != null)
{
Object.DestroyImmediate(this);
return;
}
Instance = this;
m_inVR = Utils.IsInVR();
m_root = new GameObject("Root").transform;
m_root.parent = this.transform;
m_root.localPosition = Vector3.zero;
m_root.localRotation = Quaternion.identity;
m_offsetPoint = new GameObject("OffsetPoint").transform;
m_offsetPoint.parent = m_root;
m_offsetPoint.localPosition = Vector3.zero;
m_offsetPoint.localRotation = Quaternion.identity;
m_leapElbowLeft = new GameObject("LeapElbowLeft").transform;
m_leapElbowLeft.parent = this.transform;
m_leapElbowLeft.parent = m_offsetPoint;
m_leapElbowLeft.localPosition = Vector3.zero;
m_leapElbowLeft.localRotation = Quaternion.identity;
m_leapElbowRight = new GameObject("LeapElbowRight").transform;
m_leapElbowRight.parent = this.transform;
m_leapElbowRight.parent = m_offsetPoint;
m_leapElbowRight.localPosition = Vector3.zero;
m_leapElbowRight.localRotation = Quaternion.identity;
@ -49,7 +54,7 @@ namespace ml_lme
if(m_leapControllerModel != null)
{
m_leapControllerModel.name = "LeapModel";
m_leapControllerModel.transform.parent = this.transform;
m_leapControllerModel.transform.parent = m_offsetPoint;
m_leapControllerModel.transform.localPosition = Vector3.zero;
m_leapControllerModel.transform.localRotation = Quaternion.identity;
}
@ -58,7 +63,7 @@ namespace ml_lme
if(m_leapHands != null)
{
m_leapHands.name = "LeapHands";
m_leapHands.transform.parent = this.transform;
m_leapHands.transform.parent = m_offsetPoint;
m_leapHands.transform.localPosition = Vector3.zero;
m_leapHands.transform.localRotation = Quaternion.identity;
@ -69,34 +74,21 @@ namespace ml_lme
OnModelVisibilityChanged(Settings.ModelVisibility);
OnVisualHandsChanged(Settings.VisualHands);
OnTrackingModeChanged(Settings.TrackingMode);
OnHeadAttachChanged(Settings.HeadAttach);
OnRootAngleChanged(Settings.RootAngle);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
VRModeSwitchEvents.OnInitializeXR.AddListener(this.OnAvatarSetup);
VRModeSwitchEvents.OnDeinitializeXR.AddListener(this.OnAvatarSetup);
Settings.OnDesktopOffsetChanged.AddListener(this.OnDesktopOffsetChanged);
Settings.OnEnabledChanged.AddListener(this.OnEnabledChanged);
Settings.OnModelVisibilityChanged.AddListener(this.OnModelVisibilityChanged);
Settings.OnVisualHandsChanged.AddListener(this.OnVisualHandsChanged);
Settings.OnTrackingModeChanged.AddListener(this.OnTrackingModeChanged);
Settings.OnRootAngleChanged.AddListener(this.OnRootAngleChanged);
Settings.OnHeadAttachChanged.AddListener(this.OnHeadAttachChanged);
Settings.OnHeadOffsetChanged.AddListener(this.OnHeadOffsetChanged);
Settings.OnDesktopOffsetChanged.AddListener(this.OnDesktopOffsetChanged);
Settings.OnRootAngleChanged.AddListener(this.OnRootAngleChanged);
GameEvents.OnAvatarClear.AddListener(this.OnAvatarClear);
GameEvents.OnAvatarSetup.AddListener(this.OnAvatarSetup);
GameEvents.OnPlayspaceScale.AddListener(this.OnPlayspaceScale);
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
OnHeadAttachChanged(Settings.HeadAttach);
}
void OnDestroy()
{
if(Instance == this)
@ -120,19 +112,23 @@ namespace ml_lme
Object.Destroy(m_leapControllerModel);
m_leapControllerModel = null;
VRModeSwitchEvents.OnInitializeXR.RemoveListener(this.OnAvatarSetup);
VRModeSwitchEvents.OnDeinitializeXR.RemoveListener(this.OnAvatarSetup);
if(m_offsetPoint != null)
Destroy(m_offsetPoint.gameObject);
m_offsetPoint = null;
Settings.OnDesktopOffsetChanged.RemoveListener(this.OnDesktopOffsetChanged);
if(m_root != null)
Destroy(m_root.gameObject);
m_root = null;
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledChanged);
Settings.OnModelVisibilityChanged.RemoveListener(this.OnModelVisibilityChanged);
Settings.OnVisualHandsChanged.RemoveListener(this.OnVisualHandsChanged);
Settings.OnTrackingModeChanged.RemoveListener(this.OnTrackingModeChanged);
Settings.OnRootAngleChanged.RemoveListener(this.OnRootAngleChanged);
Settings.OnHeadAttachChanged.RemoveListener(this.OnHeadAttachChanged);
Settings.OnHeadOffsetChanged.RemoveListener(this.OnHeadOffsetChanged);
Settings.OnDesktopOffsetChanged.RemoveListener(this.OnDesktopOffsetChanged);
Settings.OnRootAngleChanged.RemoveListener(this.OnRootAngleChanged);
GameEvents.OnAvatarClear.RemoveListener(this.OnAvatarClear);
GameEvents.OnAvatarSetup.RemoveListener(this.OnAvatarSetup);
GameEvents.OnPlayspaceScale.RemoveListener(this.OnPlayspaceScale);
}
@ -140,6 +136,10 @@ namespace ml_lme
{
if(Settings.Enabled)
{
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
m_root.position = l_camera.position;
m_root.rotation = (Settings.HeadAttach ? l_camera.rotation : PlayerSetup.Instance.GetPlayerRotation());
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present)
@ -185,21 +185,22 @@ namespace ml_lme
}
// Settings
void OnDesktopOffsetChanged(Vector3 p_offset)
void OnEnabledChanged(bool p_state)
{
if(!Settings.HeadAttach)
this.transform.localPosition = p_offset * (!m_inVR ? m_scaleRelation : 1f);
OnModelVisibilityChanged(Settings.ModelVisibility);
OnVisualHandsChanged(Settings.VisualHands);
}
void OnModelVisibilityChanged(bool p_state)
{
m_leapControllerModel.SetActive(p_state);
if(m_leapControllerModel != null)
m_leapControllerModel.SetActive(Settings.Enabled && p_state);
}
void OnVisualHandsChanged(bool p_state)
{
m_leapHandLeft?.SetMeshActive(p_state);
m_leapHandRight?.SetMeshActive(p_state);
m_leapHandLeft?.SetMeshActive(Settings.Enabled && p_state);
m_leapHandRight?.SetMeshActive(Settings.Enabled && p_state);
}
void OnTrackingModeChanged(Settings.LeapTrackingMode p_mode)
@ -218,51 +219,42 @@ namespace ml_lme
}
}
void OnRootAngleChanged(Vector3 p_angle)
{
this.transform.localRotation = Quaternion.Euler(p_angle);
}
void OnHeadAttachChanged(bool p_state)
{
if(!m_inVR)
{
this.transform.parent = (p_state ? PlayerSetup.Instance.desktopCamera.transform : PlayerSetup.Instance.desktopCameraRig.transform);
this.transform.localPosition = (p_state ? Settings.HeadOffset : Settings.DesktopOffset) * m_scaleRelation;
}
else
{
this.transform.parent = (p_state ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.vrCameraRig.transform);
this.transform.localPosition = (p_state ? Settings.HeadOffset : Settings.DesktopOffset);
}
this.transform.localScale = Vector3.one * (!m_inVR ? m_scaleRelation : 1f);
this.transform.localRotation = Quaternion.Euler(Settings.RootAngle);
if(m_offsetPoint != null)
m_offsetPoint.localPosition = (p_state ? Settings.HeadOffset : Settings.DesktopOffset);
}
void OnHeadOffsetChanged(Vector3 p_offset)
{
if(Settings.HeadAttach)
this.transform.localPosition = p_offset * (!m_inVR ? m_scaleRelation : 1f);
if(Settings.HeadAttach && (m_offsetPoint != null))
m_offsetPoint.localPosition = p_offset;
}
void OnDesktopOffsetChanged(Vector3 p_offset)
{
if(!Settings.HeadAttach && (m_offsetPoint != null))
m_offsetPoint.localPosition = p_offset;
}
void OnRootAngleChanged(Vector3 p_angle)
{
if(m_offsetPoint != null)
m_offsetPoint.localRotation = Quaternion.Euler(p_angle);
}
// Game events
void OnAvatarClear()
{
m_scaleRelation = 1f;
OnHeadAttachChanged(Settings.HeadAttach);
}
void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
OnHeadAttachChanged(Settings.HeadAttach);
}
void OnPlayspaceScale(float p_relation)
{
m_scaleRelation = p_relation;
OnHeadAttachChanged(Settings.HeadAttach);
try
{
if(m_root != null)
m_root.localScale = Vector3.one * p_relation;
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
// Utils

View file

@ -14,7 +14,6 @@ namespace ml_lme
Settings.Init();
AssetsHandler.Load();
GameEvents.Init(HarmonyInstance);
ModSupporter.Init();
MelonLoader.MelonCoroutines.Start(WaitForRootLogic());
}
@ -31,7 +30,7 @@ namespace ml_lme
while(ABI_RC.Core.RootLogic.Instance == null)
yield return null;
m_leapManager = new GameObject("LeapMotionManager").AddComponent<LeapManager>();
m_leapManager = new GameObject("[LeapMotionExtension]").AddComponent<LeapManager>();
}
}
}

View file

@ -1,33 +0,0 @@
using System.Collections;
using System.Linq;
namespace ml_lme
{
static class ModSupporter
{
static bool ms_copycatMod = false;
public static void Init()
{
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerMovementCopycat") != null)
MelonLoader.MelonCoroutines.Start(WaitForCopycatInstance());
}
// PlayerMovementCopycat support
static IEnumerator WaitForCopycatInstance()
{
while(ml_pmc.PoseCopycat.Instance == null)
yield return null;
ms_copycatMod = true;
}
static bool IsCopycating() => (ml_pmc.PoseCopycat.Instance.IsActive() && ml_pmc.PoseCopycat.Instance.IsFingerTrackingActive());
public static bool SkipFingersOverride()
{
bool l_result = false;
l_result |= (ms_copycatMod && IsCopycating());
return l_result;
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.5.2", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.6.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]

View file

@ -1,13 +1,13 @@
# Leap Motion Extension
This mod allows you to use your Leap Motion controller for hands and fingers tracking.
[![](.github/img_01.png)](https://youtu.be/nak1C8uibgc)
![](.github/img_01.png)
# Installation
* Install [latest Ultraleap Gemini tracking software](https://developer.leapmotion.com/tracking-software-download)
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_lme.dll` in `Mods` folder of game
* Put `LeapMotionExtension.dll` in `Mods` folder of game
# Usage
## Settings
@ -26,4 +26,4 @@ Available mod's settings in `Settings - Implementation - Leap Motion Tracking`:
* **Interact gesture threadhold:** activation limit for interaction based on hand gesture; 80 by default.
* **Grip gesture threadhold:** activation limit for grip based on hand gesture; 40 by default.
* **Filter humanoid limits:** Limits fingers rotations to be valid for Unity's Mechanim; `true` by default
* Note: Enabling this option ensures that visual representation of your fingers will be same for you and remote players, but it cancels out additional finger segments rotations that can be better visually in most cases.
* Note: Enabling this option ensures that visual representation of your fingers will be same for you and remote players, but it cancels out additional finger segments rotations that can be better visually in some cases.

View file

@ -6,15 +6,16 @@ namespace ml_lme
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -31,9 +31,9 @@ namespace ml_lme
if(CohtmlHud.Instance != null)
{
if(p_immediate)
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small);
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small, "", false);
else
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small);
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small, "", false);
}
}

View file

@ -4,10 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>LeapMotionExtension</PackageId>
<Version>1.5.2</Version>
<Version>1.6.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>LeapMotionExtension</Product>
<AssemblyName>LeapMotionExtension</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -68,11 +69,6 @@
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>

View file

@ -1,7 +1,9 @@
using ABI.CCK.Components;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Systems.VRModeSwitch;
using RootMotion.FinalIK;
using System.Collections;
using UnityEngine;
namespace ml_pam
@ -22,61 +24,79 @@ namespace ml_pam
public Transform m_rightHandTarget;
}
const float c_offsetLimit = 0.5f;
const KeyCode c_leftKey = KeyCode.Q;
const KeyCode c_rightKey = KeyCode.E;
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static ArmMover ms_instance = null;
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(270f, 90f, 0f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(270f, 270f, 0f);
bool m_inVR = false;
VRIK m_vrIK = null;
float m_armLength = 0f;
float m_playspaceScale = 1f;
Vector4 m_armsLength; // x,y - from upper arm to hand; z,w - from center to upper arm
Transform m_camera = null;
IKInfo m_ikInfo;
bool m_enabled = true;
IKInfo m_vrIKInfo;
Transform m_rootLeft = null;
Transform m_rootRight = null;
Transform m_root = null;
Transform m_leftTarget = null;
Transform m_rightTarget = null;
Transform m_leftRotationTarget = null;
Transform m_rightRotationTarget = null;
ArmIK m_armIKLeft = null;
ArmIK m_armIKRight = null;
CVRPickupObject m_pickup = null;
Matrix4x4 m_offset;
HandState m_leftHandState = HandState.Empty;
HandState m_rightHandState = HandState.Empty;
Vector2 m_handsWeights;
AvatarBoolParameter m_leftHandParameter = null;
AvatarBoolParameter m_rightHandParameter = null;
Coroutine m_disableTask = null;
// Unity events
void Awake()
{
if(ms_instance != null)
{
DestroyImmediate(this);
return;
}
ms_instance = this;
DontDestroyOnLoad(this);
}
void Start()
{
m_inVR = Utils.IsInVR();
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootLeft = new GameObject("[ArmPickupLeft]").transform;
m_rootLeft.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootLeft.localPosition = Vector3.zero;
m_rootLeft.localRotation = Quaternion.identity;
m_root = new GameObject("Root").transform;
m_root.parent = this.transform;
m_root.localPosition = Vector3.zero;
m_root.localRotation = Quaternion.identity;
m_leftTarget = new GameObject("Target").transform;
m_leftTarget.parent = m_rootLeft;
m_leftTarget.localPosition = new Vector3(c_offsetLimit * -Settings.GrabOffset, 0f, 0f);
m_leftTarget = new GameObject("TargetLeft").transform;
m_leftTarget.parent = m_root;
m_leftTarget.localPosition = Vector3.zero;
m_leftTarget.localRotation = Quaternion.identity;
m_rootRight = new GameObject("[ArmPickupRight]").transform;
m_rootRight.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootRight.localPosition = Vector3.zero;
m_rootRight.localRotation = Quaternion.identity;
m_leftRotationTarget = new GameObject("RotationTarget").transform;
m_leftRotationTarget.parent = m_leftTarget;
m_leftRotationTarget.localPosition = Vector3.zero;
m_leftRotationTarget.localRotation = Quaternion.identity;
m_rightTarget = new GameObject("Target").transform;
m_rightTarget.parent = m_rootRight;
m_rightTarget.localPosition = new Vector3(c_offsetLimit * Settings.GrabOffset, 0f, 0f);
m_rightTarget = new GameObject("TargetRight").transform;
m_rightTarget.parent = m_root;
m_rightTarget.localPosition = Vector3.zero;
m_rightTarget.localRotation = Quaternion.identity;
m_enabled = Settings.Enabled;
m_rightRotationTarget = new GameObject("RotationTarget").transform;
m_rightRotationTarget.parent = m_rightTarget;
m_rightRotationTarget.localPosition = Vector3.zero;
m_rightRotationTarget.localRotation = Quaternion.identity;
Settings.OnEnabledChanged.AddListener(this.OnEnabledChanged);
Settings.OnGrabOffsetChanged.AddListener(this.OnGrabOffsetChanged);
@ -86,25 +106,44 @@ namespace ml_pam
GameEvents.OnAvatarClear.AddListener(this.OnAvatarClear);
GameEvents.OnAvatarSetup.AddListener(this.OnAvatarSetup);
GameEvents.OnAvatarReuse.AddListener(this.OnAvatarReuse);
GameEvents.OnPlayspaceScale.AddListener(this.OnPlayspaceScale);
GameEvents.OnIKScaling.AddListener(this.OnIKScaling);
GameEvents.OnPickupGrab.AddListener(this.OnPickupGrab);
GameEvents.OnPickupDrop.AddListener(this.OnPickupDrop);
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(this.OnVRModeSwitch);
}
void OnDestroy()
{
if(ms_instance == this)
ms_instance = null;
if(m_disableTask != null)
StopCoroutine(m_disableTask);
m_disableTask = null;
RemoveArmIK();
if(m_rootLeft != null)
Destroy(m_rootLeft);
m_rootLeft = null;
if(m_leftRotationTarget != null)
Destroy(m_leftRotationTarget.gameObject);
m_leftRotationTarget = null;
if(m_leftTarget != null)
Destroy(m_leftTarget.gameObject);
m_leftTarget = null;
if(m_rootRight != null)
Destroy(m_rootRight);
m_rootRight = null;
if(m_rightRotationTarget != null)
Destroy(m_rightRotationTarget.gameObject);
m_rightRotationTarget = null;
if(m_rightTarget != null)
Destroy(m_rightTarget.gameObject);
m_rightTarget = null;
if(m_root != null)
Destroy(m_root.gameObject);
m_root = null;
m_pickup = null;
m_vrIK = null;
@ -116,13 +155,21 @@ namespace ml_pam
GameEvents.OnAvatarClear.RemoveListener(this.OnAvatarClear);
GameEvents.OnAvatarSetup.RemoveListener(this.OnAvatarSetup);
GameEvents.OnAvatarReuse.RemoveListener(this.OnAvatarReuse);
GameEvents.OnPlayspaceScale.RemoveListener(this.OnPlayspaceScale);
GameEvents.OnIKScaling.AddListener(this.OnIKScaling);
GameEvents.OnPickupGrab.RemoveListener(this.OnPickupGrab);
GameEvents.OnPickupDrop.RemoveListener(this.OnPickupDrop);
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(this.OnVRModeSwitch);
}
void Update()
{
if((m_root != null) && (m_camera != null))
{
m_root.position = m_camera.position;
m_root.rotation = m_camera.rotation;
}
if(!ReferenceEquals(m_pickup, null) && (m_pickup == null))
OnPickupDrop(m_pickup);
@ -130,205 +177,170 @@ namespace ml_pam
{
case HandState.Empty:
{
if(Settings.HandsExtension && Input.GetKeyDown(c_leftKey))
{
if(Settings.Enabled && Settings.HandsExtension && Input.GetKey(c_leftKey) && !ViewManager.Instance.IsAnyMenuOpen)
m_leftHandState = HandState.Extended;
m_rootLeft.localPosition = new Vector3(0f, 0f, m_armLength * m_playspaceScale);
SetArmActive(Settings.LeadHand.Left, true);
}
}
break;
case HandState.Extended:
{
if(Input.GetKeyUp(c_leftKey))
{
if(!Input.GetKey(c_leftKey))
m_leftHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Left, false);
}
}
break;
case HandState.Pickup:
{
if(m_pickup != null)
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_rootLeft.position = l_result * ms_pointVector;
}
}
break;
}
switch(m_rightHandState)
{
case HandState.Empty:
{
if(Settings.HandsExtension && Input.GetKeyDown(c_rightKey))
{
if(Settings.Enabled && Settings.HandsExtension && Input.GetKey(c_rightKey) && !ViewManager.Instance.IsAnyMenuOpen)
m_rightHandState = HandState.Extended;
m_rootRight.localPosition = new Vector3(0f, 0f, m_armLength * m_playspaceScale);
SetArmActive(Settings.LeadHand.Right, true);
}
}
break;
case HandState.Extended:
{
if(Input.GetKeyUp(c_rightKey))
{
if(!Input.GetKey(c_rightKey))
m_rightHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Right, false);
}
}
break;
case HandState.Pickup:
}
m_handsWeights.x = Mathf.Clamp01(m_handsWeights.x + ((m_leftHandState != HandState.Empty) ? 1f : -1f) * Time.unscaledDeltaTime * Settings.ExtensionSpeed);
m_handsWeights.y = Mathf.Clamp01(m_handsWeights.y + ((m_rightHandState != HandState.Empty) ? 1f : -1f) * Time.unscaledDeltaTime * Settings.ExtensionSpeed);
UpdateArmIK(m_armIKLeft, m_handsWeights.x);
UpdateArmIK(m_armIKRight, m_handsWeights.y);
m_leftHandParameter?.SetValue(m_leftHandState != HandState.Empty);
m_rightHandParameter?.SetValue(m_rightHandState != HandState.Empty);
if(m_leftHandState != HandState.Empty)
{
if(m_pickup != null)
{
if(m_pickup != null)
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_rootRight.position = l_result * ms_pointVector;
}
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_leftTarget.position = l_result.GetPosition();
m_leftTarget.rotation = l_result.rotation;
m_leftTarget.localPosition = Vector3.ClampMagnitude(m_leftTarget.localPosition, m_armsLength.x);
}
break;
else
{
m_leftTarget.localPosition = new Vector3(0f, 0f, m_armsLength.x);
m_leftTarget.localRotation = Quaternion.identity;
}
}
if(m_rightHandState != HandState.Empty)
{
if(m_pickup != null)
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_rightTarget.position = l_result.GetPosition();
m_rightTarget.rotation = l_result.rotation;
m_rightTarget.localPosition = Vector3.ClampMagnitude(m_rightTarget.localPosition, m_armsLength.y);
}
else
{
m_rightTarget.localPosition = new Vector3(0f, 0f, m_armsLength.y);
m_rightTarget.localRotation = Quaternion.identity;
}
}
}
void LateUpdate()
{
if((m_root != null) && (m_camera != null))
{
m_root.position = m_camera.position;
m_root.rotation = m_camera.rotation;
}
}
// VRIK updates
void OnIKPreUpdate()
{
if(m_enabled)
if(!Mathf.Approximately(m_handsWeights.x, 0f))
{
if(m_leftHandState != HandState.Empty)
{
m_vrIKInfo.m_leftHandTarget = m_vrIK.solver.leftArm.target;
m_vrIKInfo.m_armsWeights.x = m_vrIK.solver.leftArm.positionWeight;
m_vrIKInfo.m_armsWeights.y = m_vrIK.solver.leftArm.rotationWeight;
m_ikInfo.m_leftHandTarget = m_vrIK.solver.leftArm.target;
m_ikInfo.m_armsWeights.x = m_vrIK.solver.leftArm.positionWeight;
m_ikInfo.m_armsWeights.y = m_vrIK.solver.leftArm.rotationWeight;
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
m_vrIK.solver.leftArm.target = m_leftTarget;
}
if(m_rightHandState != HandState.Empty)
{
m_vrIKInfo.m_rightHandTarget = m_vrIK.solver.rightArm.target;
m_vrIKInfo.m_armsWeights.z = m_vrIK.solver.rightArm.positionWeight;
m_vrIKInfo.m_armsWeights.w = m_vrIK.solver.rightArm.rotationWeight;
m_vrIK.solver.leftArm.positionWeight = m_handsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_handsWeights.x;
m_vrIK.solver.leftArm.target = m_leftRotationTarget;
}
if(!Mathf.Approximately(m_handsWeights.y, 0f))
{
m_ikInfo.m_rightHandTarget = m_vrIK.solver.rightArm.target;
m_ikInfo.m_armsWeights.z = m_vrIK.solver.rightArm.positionWeight;
m_ikInfo.m_armsWeights.w = m_vrIK.solver.rightArm.rotationWeight;
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.target = m_rightTarget;
}
m_vrIK.solver.rightArm.positionWeight = m_handsWeights.y;
m_vrIK.solver.rightArm.rotationWeight = m_handsWeights.y;
m_vrIK.solver.rightArm.target = m_rightRotationTarget;
}
}
void OnIKPostUpdate()
{
if(m_enabled)
if(!Mathf.Approximately(m_handsWeights.x, 0f))
{
if(m_leftHandState != HandState.Empty)
{
m_vrIK.solver.leftArm.target = m_vrIKInfo.m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = m_vrIKInfo.m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_vrIKInfo.m_armsWeights.y;
}
if(m_rightHandState != HandState.Empty)
{
m_vrIK.solver.rightArm.target = m_vrIKInfo.m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = m_vrIKInfo.m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_vrIKInfo.m_armsWeights.w;
}
m_vrIK.solver.leftArm.target = m_ikInfo.m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = m_ikInfo.m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_ikInfo.m_armsWeights.y;
}
if(!Mathf.Approximately(m_handsWeights.y, 0f))
{
m_vrIK.solver.rightArm.target = m_ikInfo.m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = m_ikInfo.m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_ikInfo.m_armsWeights.w;
}
}
// Settings
void OnEnabledChanged(bool p_state)
{
m_enabled = p_state;
if(p_state)
{
if(m_leftHandState != HandState.Empty)
SetArmActive(Settings.LeadHand.Left, true);
if(m_rightHandState != HandState.Empty)
SetArmActive(Settings.LeadHand.Right, true);
if(this.enabled)
{
if(m_disableTask != null)
{
StopCoroutine(m_disableTask);
m_disableTask = null;
}
}
else
this.enabled = true;
OnHandsExtensionChanged(Settings.HandsExtension);
OnLeadingHandChanged(Settings.LeadingHand);
}
else
SetArmActive(Settings.LeadHand.Both, false, true);
{
m_leftHandState = HandState.Empty;
m_rightHandState = HandState.Empty;
m_disableTask = StartCoroutine(WaitToDisable());
}
}
void OnGrabOffsetChanged(float p_value)
{
if(m_leftTarget != null)
m_leftTarget.localPosition = new Vector3(c_offsetLimit * m_playspaceScale * -p_value, 0f, 0f);
if(m_rightTarget != null)
m_rightTarget.localPosition = new Vector3(c_offsetLimit * m_playspaceScale * p_value, 0f, 0f);
if(m_leftRotationTarget != null)
m_leftRotationTarget.localPosition = new Vector3(-m_armsLength.z * p_value * 2f, 0f, 0f);
if(m_rightRotationTarget != null)
m_rightRotationTarget.localPosition = new Vector3(m_armsLength.w * p_value * 2f, 0f, 0f);
}
void OnLeadingHandChanged(Settings.LeadHand p_hand)
{
if(m_pickup != null)
{
if(m_leftHandState == HandState.Pickup)
{
m_leftHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Left, false);
}
if(m_rightHandState == HandState.Pickup)
{
m_rightHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Right, false);
}
switch(p_hand)
{
case Settings.LeadHand.Left:
m_leftHandState = HandState.Pickup;
break;
case Settings.LeadHand.Right:
m_rightHandState = HandState.Pickup;
break;
case Settings.LeadHand.Both:
{
m_leftHandState = HandState.Pickup;
m_rightHandState = HandState.Pickup;
}
break;
}
SetArmActive(p_hand, true);
}
SetLeadingHandState((m_pickup != null) ? HandState.Pickup : HandState.Empty);
SetUnleadingHandState(HandState.Empty);
}
void OnHandsExtensionChanged(bool p_state)
{
if(m_enabled)
{
if(p_state)
{
if((m_leftHandState == HandState.Empty) && Input.GetKey(c_leftKey))
{
m_leftHandState = HandState.Extended;
SetArmActive(Settings.LeadHand.Left, true);
}
if((m_rightHandState == HandState.Empty) && Input.GetKey(c_rightKey))
{
m_rightHandState = HandState.Extended;
SetArmActive(Settings.LeadHand.Right, true);
}
}
else
{
if(m_leftHandState == HandState.Extended)
{
m_leftHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Left, false);
}
if(m_rightHandState == HandState.Extended)
{
m_rightHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Right, false);
}
}
}
if(m_leftHandState == HandState.Extended)
m_leftHandState = HandState.Empty;
if(m_rightHandState == HandState.Extended)
m_rightHandState = HandState.Empty;
}
// Game events
@ -337,65 +349,81 @@ namespace ml_pam
m_vrIK = null;
m_armIKLeft = null;
m_armIKRight = null;
m_armLength = 0f;
m_armsLength.Set(0f, 0f, 0f, 0f);
m_leftHandParameter = null;
m_rightHandParameter = null;
}
void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
ReparentRoots();
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
if(PlayerSetup.Instance._animator.isHuman)
{
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
Utils.SetAvatarTPose();
Transform l_leftHand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_leftHand != null)
m_leftTarget.localRotation = ms_offsetLeft * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_leftHand.GetMatrix()).rotation;
Transform l_rightHand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_rightHand != null)
m_rightTarget.localRotation = ms_offsetRight * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_rightHand.GetMatrix()).rotation;
Animator l_animator = PlayerSetup.Instance._animator;
Matrix4x4 l_avatarMatrixInv = l_animator.transform.GetMatrix().inverse; // Animator and avatar are on same game object
if(m_vrIK != null)
Transform l_leftHand = l_animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_leftHand != null)
m_leftRotationTarget.localRotation = ms_offsetLeft * (l_avatarMatrixInv * l_leftHand.GetMatrix()).rotation;
Transform l_rightHand = l_animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_rightHand != null)
m_rightRotationTarget.localRotation = ms_offsetRight * (l_avatarMatrixInv * l_rightHand.GetMatrix()).rotation;
m_armsLength.x = GetChainLength(new Transform[]{
l_animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftHand)
});
m_armsLength.y = GetChainLength(new Transform[]{
l_animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.RightHand)
});
m_armsLength.z = Mathf.Abs((l_avatarMatrixInv * l_animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).GetMatrix()).GetPosition().x);
m_armsLength.w = Mathf.Abs((l_avatarMatrixInv * l_animator.GetBoneTransform(HumanBodyBones.RightUpperArm).GetMatrix()).GetPosition().x);
if(!Utils.IsInVR())
{
m_armLength = m_vrIK.solver.leftArm.mag * 1.25f;
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
if(m_vrIK != null)
{
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
else
SetupArmIK();
}
else if(!m_inVR)
SetupArmIK();
}
m_leftHandParameter = new AvatarBoolParameter("LeftHandExtended", PlayerSetup.Instance.animatorManager);
m_rightHandParameter = new AvatarBoolParameter("RightHandExtended", PlayerSetup.Instance.animatorManager);
OnEnabledChanged(m_enabled);
OnEnabledChanged(Settings.Enabled);
OnGrabOffsetChanged(Settings.GrabOffset);
}
void OnAvatarReuse()
{
// Old VRIK is destroyed by game
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
ReparentRoots();
if(m_inVR)
if(Utils.IsInVR())
RemoveArmIK();
if(m_vrIK != null)
else
{
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
if(m_vrIK != null)
{
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
else
SetupArmIK();
}
else if(!m_inVR)
SetupArmIK();
OnEnabledChanged(m_enabled);
OnEnabledChanged(Settings.Enabled);
}
void OnPickupGrab(CVRPickupObject p_pickup, Vector3 p_hit)
@ -416,97 +444,82 @@ namespace ml_pam
}
}
else
m_offset = m_pickup.transform.GetMatrix().inverse * Matrix4x4.Translate(p_hit);
m_offset = m_pickup.transform.GetMatrix().inverse * Matrix4x4.TRS(p_hit, m_camera.rotation, Vector3.one);
switch(Settings.LeadingHand)
{
case Settings.LeadHand.Left:
m_leftHandState = HandState.Pickup;
break;
case Settings.LeadHand.Right:
m_rightHandState = HandState.Pickup;
break;
case Settings.LeadHand.Both:
{
m_leftHandState = HandState.Pickup;
m_rightHandState = HandState.Pickup;
}
break;
}
SetArmActive(Settings.LeadingHand, true);
if(Settings.Enabled)
OnLeadingHandChanged(Settings.LeadingHand);
}
}
void OnPickupDrop(CVRPickupObject p_pickup)
{
if(m_pickup == p_pickup)
if(ReferenceEquals(m_pickup, p_pickup) || (m_pickup == p_pickup))
{
m_pickup = null;
switch(Settings.LeadingHand)
{
case Settings.LeadHand.Left:
m_leftHandState = HandState.Empty;
break;
case Settings.LeadHand.Right:
m_rightHandState = HandState.Empty;
break;
case Settings.LeadHand.Both:
{
m_leftHandState = HandState.Empty;
m_rightHandState = HandState.Empty;
}
break;
}
SetArmActive(Settings.LeadingHand, false);
if(Settings.Enabled)
SetLeadingHandState(HandState.Empty);
}
}
void OnPlayspaceScale(float p_relation)
void OnIKScaling(float p_relation)
{
m_playspaceScale = p_relation;
OnGrabOffsetChanged(Settings.GrabOffset);
if(m_root != null)
m_root.localScale = Vector3.one * p_relation;
}
void OnVRModeSwitch(bool p_state)
{
try
{
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
this.enabled = !Utils.IsInVR();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
// Arbitrary
void SetupArmIK()
{
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
Animator l_animator = PlayerSetup.Instance._animator;
Transform l_chest = l_animator.GetBoneTransform(HumanBodyBones.UpperChest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
l_chest = l_animator.GetBoneTransform(HumanBodyBones.Chest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
l_chest = l_animator.GetBoneTransform(HumanBodyBones.Spine);
m_armIKLeft = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_armIKLeft.solver.isLeft = true;
m_armIKLeft.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand),
PlayerSetup.Instance._animator.transform
l_animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
l_animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.LeftHand),
l_animator.transform
);
m_armIKLeft.solver.arm.target = m_leftTarget;
m_armIKLeft.solver.arm.target = m_leftRotationTarget;
m_armIKLeft.solver.arm.positionWeight = 1f;
m_armIKLeft.solver.arm.rotationWeight = 1f;
m_armIKLeft.solver.IKPositionWeight = 0f;
m_armIKLeft.solver.IKRotationWeight = 0f;
m_armIKLeft.enabled = false;
m_armLength = m_armIKLeft.solver.arm.mag * 1.25f;
m_armIKRight = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_armIKRight.solver.isLeft = false;
m_armIKRight.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand),
PlayerSetup.Instance._animator.transform
l_animator.GetBoneTransform(HumanBodyBones.RightShoulder),
l_animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
l_animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
l_animator.GetBoneTransform(HumanBodyBones.RightHand),
l_animator.transform
);
m_armIKRight.solver.arm.target = m_rightTarget;
m_armIKRight.solver.arm.target = m_rightRotationTarget;
m_armIKRight.solver.arm.positionWeight = 1f;
m_armIKRight.solver.arm.rotationWeight = 1f;
m_armIKRight.solver.IKPositionWeight = 0f;
@ -525,39 +538,66 @@ namespace ml_pam
m_armIKRight = null;
}
void SetArmActive(Settings.LeadHand p_hand, bool p_state, bool p_forced = false)
void UpdateArmIK(ArmIK p_ik, float p_weight)
{
if(m_enabled || p_forced)
if(p_ik != null)
{
if(((p_hand == Settings.LeadHand.Left) || (p_hand == Settings.LeadHand.Both)) && (m_armIKLeft != null))
{
m_armIKLeft.enabled = m_enabled;
m_armIKLeft.solver.IKPositionWeight = (p_state ? 1f : 0f);
m_armIKLeft.solver.IKRotationWeight = (p_state ? 1f : 0f);
}
if(((p_hand == Settings.LeadHand.Right) || (p_hand == Settings.LeadHand.Both)) && (m_armIKRight != null))
{
m_armIKRight.enabled = m_enabled;
m_armIKRight.solver.IKPositionWeight = (p_state ? 1f : 0f);
m_armIKRight.solver.IKRotationWeight = (p_state ? 1f : 0f);
}
if((p_hand == Settings.LeadHand.Left) || (p_hand == Settings.LeadHand.Both))
m_leftHandParameter?.SetValue(p_state);
if((p_hand == Settings.LeadHand.Right) || (p_hand == Settings.LeadHand.Both))
m_rightHandParameter?.SetValue(p_state);
bool l_state = !Mathf.Approximately(p_weight, 0f);
p_ik.solver.IKPositionWeight = p_weight;
p_ik.solver.IKRotationWeight = p_weight;
p_ik.enabled = l_state;
}
}
void ReparentRoots()
void SetLeadingHandState(HandState p_state)
{
m_rootLeft.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootLeft.localPosition = Vector3.zero;
m_rootLeft.localRotation = Quaternion.identity;
switch(Settings.LeadingHand)
{
case Settings.LeadHand.Left:
m_leftHandState = p_state;
break;
case Settings.LeadHand.Right:
m_rightHandState = p_state;
break;
case Settings.LeadHand.Both:
{
m_leftHandState = p_state;
m_rightHandState = p_state;
}
break;
}
}
void SetUnleadingHandState(HandState p_state)
{
switch(Settings.LeadingHand)
{
case Settings.LeadHand.Left:
m_rightHandState = p_state;
break;
case Settings.LeadHand.Right:
m_leftHandState = p_state;
break;
}
}
m_rootRight.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootRight.localPosition = Vector3.zero;
m_rootRight.localRotation = Quaternion.identity;
IEnumerator WaitToDisable()
{
while(!Mathf.Approximately(m_handsWeights.x + m_handsWeights.y, 0f))
yield return null;
this.enabled = false;
m_disableTask = null;
}
static float GetChainLength(Transform[] p_chain)
{
float l_result = 0f;
for(int i = 0, j = p_chain.Length - 1; i < j; i++)
{
if((p_chain[i] != null) && (p_chain[i + 1] != null))
l_result += Vector3.Distance(p_chain[i].position, p_chain[i + 1].position);
}
return l_result;
}
}
}

View file

@ -34,7 +34,7 @@ namespace ml_pam
public static readonly GameEvent OnAvatarSetup = new GameEvent();
public static readonly GameEvent OnAvatarClear = new GameEvent();
public static readonly GameEvent OnAvatarReuse = new GameEvent();
public static readonly GameEvent<float> OnPlayspaceScale = new GameEvent<float>();
public static readonly GameEvent<float> OnIKScaling = new GameEvent<float>();
public static readonly GameEvent<CVRPickupObject, Vector3> OnPickupGrab = new GameEvent<CVRPickupObject, Vector3>();
public static readonly GameEvent<CVRPickupObject> OnPickupDrop = new GameEvent<CVRPickupObject>();
@ -61,9 +61,9 @@ namespace ml_pam
);
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
@ -120,11 +120,11 @@ namespace ml_pam
}
}
static void OnPlayspaceScale_Postfix(float ____avatarScaleRelation)
static void OnSetupIKScaling_Postfix(ref UnityEngine.Vector3 ___scaleDifference)
{
try
{
OnPlayspaceScale.Invoke(____avatarScaleRelation);
OnIKScaling.Invoke(1f + ___scaleDifference.y);
}
catch(Exception e)
{

View file

@ -1,32 +1,34 @@
using ABI_RC.Core.Player;
using UnityEngine;
namespace ml_pam
{
public class PickupArmMovement : MelonLoader.MelonMod
{
ArmMover m_localMover = null;
ArmMover m_mover = null;
public override void OnInitializeMelon()
{
Settings.Init();
GameEvents.Init(HarmonyInstance);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
MelonLoader.MelonCoroutines.Start(WaitForRootLogic());
}
System.Collections.IEnumerator WaitForLocalPlayer()
System.Collections.IEnumerator WaitForRootLogic()
{
while(PlayerSetup.Instance == null)
while(ABI_RC.Core.RootLogic.Instance == null)
yield return null;
while(ABI_RC.Core.Player.PlayerSetup.Instance == null)
yield return null;
m_localMover = PlayerSetup.Instance.gameObject.AddComponent<ArmMover>();
m_mover = new GameObject("[PickupArmMovement]").AddComponent<ArmMover>();
}
public override void OnDeinitializeMelon()
{
if(m_localMover != null)
UnityEngine.Object.Destroy(m_localMover);
m_localMover = null;
if(m_mover != null)
Object.Destroy(m_mover.gameObject);
m_mover = null;
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.1.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.2.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPriority(1)]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]

View file

@ -4,14 +4,15 @@ This mod adds arm tracking upon holding pickup in desktop mode.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_pam.dll` in `Mods` folder of game
* Put `PickupArmMovement.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions - Pickup Arm Movement`:
Available mod's settings in `Settings - Input & Key-Bindings - Pickup Arm Movement`:
* **Enable hand movement:** enables/disables arm tracking; default value - `true`.
* **Grab offset:** offset from pickup grab point; default value - `25`.
* **Leading hand:** hand that will be extended when gragging pickup; available values: `Left`, `Right`, `Both`; default value - `Right`.
* **Hands extension (Q\E):** extend left and right hand if `Q` and `E` keys are pressed; default value - `true`.
* **Hand extension speed::** smoothing speed multiplier between extended and animated hands; default value - `25`.
Available animator boolean parameters:
* **LeftHandExtended:`` indicates if left hand is extended.

View file

@ -6,15 +6,16 @@ namespace ml_pam
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResources(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -19,7 +19,8 @@ namespace ml_pam
Enabled = 0,
GrabOffset,
LeadHand,
HandsExtension
HandsExtension,
ExtensionSpeed
}
public enum LeadHand
{
@ -29,9 +30,10 @@ namespace ml_pam
}
public static bool Enabled { get; private set; } = true;
public static float GrabOffset { get; private set; } = 0.25f;
public static float GrabOffset { get; private set; } = 0.5f;
public static LeadHand LeadingHand { get; private set; } = LeadHand.Right;
public static bool HandsExtension { get; private set; } = true;
public static float ExtensionSpeed { get; private set; } = 10f;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -40,6 +42,7 @@ namespace ml_pam
public static readonly SettingEvent<float> OnGrabOffsetChanged = new SettingEvent<float>();
public static readonly SettingEvent<LeadHand> OnLeadingHandChanged = new SettingEvent<LeadHand>();
public static readonly SettingEvent<bool> OnHandsExtensionChanged = new SettingEvent<bool>();
public static readonly SettingEvent<float> OnExtensionSpeedChanged = new SettingEvent<float>();
internal static void Init()
{
@ -51,12 +54,14 @@ namespace ml_pam
ms_category.CreateEntry(ModSetting.GrabOffset.ToString(), (int)(GrabOffset * 100f)),
ms_category.CreateEntry(ModSetting.LeadHand.ToString(), (int)LeadHand.Right),
ms_category.CreateEntry(ModSetting.HandsExtension.ToString(), HandsExtension),
ms_category.CreateEntry(ModSetting.ExtensionSpeed.ToString(), (int)ExtensionSpeed),
};
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
GrabOffset = (int)ms_entries[(int)ModSetting.GrabOffset].BoxedValue * 0.01f;
LeadingHand = (LeadHand)(int)ms_entries[(int)ModSetting.LeadHand].BoxedValue;
HandsExtension = (bool)ms_entries[(int)ModSetting.HandsExtension].BoxedValue;
ExtensionSpeed = Math.Clamp((int)ms_entries[(int)ModSetting.ExtensionSpeed].BoxedValue, 1f, 50f);
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
@ -72,6 +77,7 @@ namespace ml_pam
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
@ -131,6 +137,13 @@ namespace ml_pam
OnGrabOffsetChanged.Invoke(GrabOffset);
}
break;
case ModSetting.ExtensionSpeed:
{
ExtensionSpeed = l_value;
OnExtensionSpeedChanged.Invoke(ExtensionSpeed);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = l_value;

View file

@ -4,10 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>PickupArmMovement</PackageId>
<Version>1.1.4</Version>
<Version>1.2.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>PickupArmMovement</Product>
<AssemblyName>PickupArmMovement</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

View file

@ -16,7 +16,7 @@
<div class ="row-wrapper">
<div class ="option-caption">Grab offset: </div>
<div class ="option-input">
<div id="GrabOffset" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="25"></div>
<div id="GrabOffset" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="50"></div>
</div>
</div>
@ -33,8 +33,15 @@
<div id="HandsExtension" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Hand extension speed: </div>
<div class ="option-input">
<div id="ExtensionSpeed" class ="inp_slider no-scroll" data-min="1" data-max="50" data-current="10"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
document.getElementById('settings-input').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_pin.PlayersInstanceNotifier), "PlayersInstanceNotifier", "1.0.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_pin.PlayersInstanceNotifier), "PlayersInstanceNotifier", "1.1.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -8,6 +8,7 @@ namespace ml_pin
static class ResourcesHandler
{
const string c_modName = "PlayersInstanceNotifier";
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
static readonly List<string> ms_audioResources = new List<string>()
{
@ -45,11 +46,10 @@ namespace ml_pin
static void ExtractAudioFile(string p_name, string p_path)
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_resourceStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_resourceStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
Stream l_fileStream = File.Create(p_path);
l_resourceStream.CopyTo(l_fileStream);
l_fileStream.Flush();
@ -66,11 +66,10 @@ namespace ml_pin
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -5,9 +5,9 @@
<Platforms>x64</Platforms>
<PackageId>PlayersInstanceNotifier</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>PlayersInstanceNotifier</Product>
<Version>1.0.9</Version>
<Version>1.1.0</Version>
<AssemblyName>PlayersInstanceNotifier</AssemblyName>
</PropertyGroup>

View file

@ -1,10 +1,11 @@
using ABI_RC.Core.Player;
using UnityEngine;
namespace ml_pmc
{
public class PlayerMovementCopycat : MelonLoader.MelonMod
{
PoseCopycat m_localCopycat = null;
PoseCopycat m_poseCopycat = null;
public override void OnInitializeMelon()
{
@ -17,9 +18,9 @@ namespace ml_pmc
public override void OnDeinitializeMelon()
{
if(m_localCopycat != null)
UnityEngine.Object.Destroy(m_localCopycat);
m_localCopycat = null;
if(m_poseCopycat != null)
Object.Destroy(m_poseCopycat.gameObject);
m_poseCopycat = null;
}
System.Collections.IEnumerator WaitForLocalPlayer()
@ -27,7 +28,7 @@ namespace ml_pmc
while(PlayerSetup.Instance == null)
yield return null;
m_localCopycat = PlayerSetup.Instance.gameObject.AddComponent<PoseCopycat>();
m_poseCopycat = new GameObject("[PlayerMovementCopycat]").AddComponent<PoseCopycat>();
}
}
}

View file

@ -10,7 +10,7 @@ using UnityEngine;
namespace ml_pmc
{
[DisallowMultipleComponent]
public class PoseCopycat : MonoBehaviour
class PoseCopycat : MonoBehaviour
{
public class CopycatEvent<T1>
{
@ -22,7 +22,7 @@ namespace ml_pmc
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
public static PoseCopycat Instance { get; private set; } = null;
static PoseCopycat ms_instance = null;
internal static readonly CopycatEvent<bool> OnCopycatChanged = new CopycatEvent<bool>();
Animator m_animator = null;
@ -43,10 +43,14 @@ namespace ml_pmc
void Awake()
{
if((Instance != null) && (Instance != this))
Object.Destroy(this);
else
Instance = this;
if(ms_instance != null)
{
DestroyImmediate(this);
return;
}
ms_instance = this;
DontDestroyOnLoad(this);
}
void Start()
@ -60,8 +64,8 @@ namespace ml_pmc
}
void OnDestroy()
{
if(Instance == this)
Instance = null;
if(ms_instance == this)
ms_instance = null;
m_poseHandler?.Dispose();
m_poseHandler = null;
@ -201,7 +205,7 @@ namespace ml_pmc
PlayerSetup.Instance.transform.rotation = l_result.rotation;
}
if(Vector3.Distance(this.transform.position, m_puppetParser.transform.position) > m_distanceLimit)
if(Vector3.Distance(PlayerSetup.Instance.GetPlayerPosition(), m_puppetParser.transform.position) > m_distanceLimit)
SetTarget(null);
}
else
@ -364,7 +368,7 @@ namespace ml_pmc
}
// Arbitrary
public void SetTarget(PuppetMaster p_target)
void SetTarget(PuppetMaster p_target)
{
if(m_animator != null)
{
@ -405,9 +409,6 @@ namespace ml_pmc
}
}
public bool IsActive() => m_active;
public bool IsFingerTrackingActive() => m_fingerTracking;
void OverrideIK()
{
if(!BodySystem.isCalibrating)

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_pmc.PlayerMovementCopycat), "PlayerMovementCopycat", "1.0.8", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_pmc.PlayerMovementCopycat), "PlayerMovementCopycat", "1.1.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPriority(3)]
[assembly: MelonLoader.MelonAdditionalDependencies("BTKUILib")]

View file

@ -5,7 +5,7 @@ Allows to copy pose, gestures and movement of your friends.
* Install [BTKUILib](https://github.com/BTK-Development/BTKUILib)
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_pmc.dll` in `Mods` folder of game
* Put `PlayerMovementCopycat.dll` in `Mods` folder of game
# Usage
Available options in BTKUILib players list upon player selection:

View file

@ -5,9 +5,10 @@
<Platforms>x64</Platforms>
<PackageId>PlayerMovementCopycat</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Company>SDraw</Company>
<Product>PlayerMovementCopycat</Product>
<Version>1.0.8</Version>
<Version>1.1.0</Version>
<AssemblyName>PlayerMovementCopycat</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

View file

@ -248,6 +248,5 @@ namespace ml_prm
}
return !ms_result.m_result;
}
}
}

View file

@ -42,14 +42,9 @@ namespace ml_prm
System.Collections.IEnumerator WaitForWhitelist()
{
// Whitelist the toggle script
FieldInfo l_field = typeof(SharedFilter).GetField("_localComponentWhitelist", BindingFlags.NonPublic | BindingFlags.Static);
HashSet<Type> l_hashSet = l_field?.GetValue(null) as HashSet<Type>;
while(l_hashSet == null)
{
l_hashSet = l_field?.GetValue(null) as HashSet<Type>;
while(SharedFilter.LocalComponentWhitelist == null)
yield return null;
}
l_hashSet.Add(typeof(RagdollToggle));
SharedFilter.LocalComponentWhitelist.Add(typeof(RagdollToggle));
}
}
}

View file

@ -43,6 +43,7 @@ namespace ml_prm
const string c_ragdollKeyTooltip = "Switch ragdoll mode with '{0}' key";
const string c_fallLimitTooltip = "Fall limit based on impact velocity<p>Current value corresponds to drop from {0} units with default gravity</p>";
readonly static string ms_namespace = typeof(ModUi).Namespace;
internal static readonly UiEvent OnSwitchChanged = new UiEvent();
static Page ms_page = null;
@ -345,7 +346,7 @@ namespace ml_prm
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
return l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
return l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
}
static float GetDropHeight(float p_speed, float p_gravity = 9.8f)

View file

@ -65,7 +65,7 @@ Now you can animate both parameters available:
![](.github/img_01.png)
Note: In order to work the game object needs to be active and the component enabled.
# Mods Integration
# Mods integration
You can use this mod's functions within your mod. To do this you need:
* Add mod's dll as reference in your project
* Access ragdoll controller with `ml_prm.RagdollController.Instance`
@ -75,9 +75,3 @@ Available methods:
* ```void SwitchRagdoll()```
* ```void Ragdoll()```
* ```void Unragdoll()```
# Notes
* If ragdoll state is enabled during emote, remote players see whole emote playing while local player sees ragdolling. It's tied to how game handles remote players, currently can be prevented with (choose one):
* Renaming avatar emote animations to not have default name or containing `Emote` substring.
* Holding any movement key right after activating ragdoll state.
* Add transition from `Any state` to state with A-pose/T-pose animation and condition with `Ragdolled` parameter on main avatar's animator layer.

View file

@ -26,8 +26,10 @@ namespace ml_prm
bool m_applyHipsPosition = false;
bool m_applyHipsRotation = false;
bool m_avatarReady = false;
bool m_ragdolled = false;
bool m_forcedSwitch = false;
Coroutine m_initTask = null;
Transform m_puppet = null;
Transform m_puppetRoot = null;
@ -36,10 +38,6 @@ namespace ml_prm
readonly List<System.Tuple<Transform, Transform>> m_boneLinks = null;
readonly List<System.Tuple<CharacterJoint, Vector3>> m_jointAnchors = null;
bool m_avatarReady = false;
Coroutine m_initCoroutine = null;
Vector3 m_ragdollLastPos = Vector3.zero;
RagdollToggle m_avatarRagdollToggle = null; // Custom component available for editor
AvatarBoolParameter m_ragdolledParameter = null;
PhysicMaterial m_physicsMaterial = null;
@ -49,6 +47,11 @@ namespace ml_prm
float m_groundedTime = 0f;
float m_downTime = float.MinValue;
Vector3 m_lastRagdollPosition;
Vector3 m_lastSeatPositon;
Vector3 m_seatVelocity;
Plane m_playerPlane;
internal RagdollController()
{
m_ragdollBodyHandlers = new List<RagdollBodypartHandler>();
@ -59,10 +62,14 @@ namespace ml_prm
// Unity events
void Awake()
{
if((Instance != null) && (Instance != this))
Object.Destroy(this);
else
Instance = this;
if(Instance != null)
{
DestroyImmediate(this);
return;
}
Instance = this;
DontDestroyOnLoad(this);
}
void Start()
@ -112,9 +119,9 @@ namespace ml_prm
if(Instance == this)
Instance = null;
if(m_initCoroutine != null)
StopCoroutine(m_initCoroutine);
m_initCoroutine = null;
if(m_initTask != null)
StopCoroutine(m_initTask);
m_initTask = null;
if(m_puppet != null)
Object.Destroy(m_puppet);
@ -160,7 +167,7 @@ namespace ml_prm
{
if(m_avatarReady)
{
if(!m_ragdolled && Settings.FallDamage && !BetterBetterCharacterController.Instance.IsFlying())
if(!m_ragdolled && Settings.FallDamage && !BetterBetterCharacterController.Instance.IsFlying() && !BetterBetterCharacterController.Instance.IsSitting())
{
bool l_grounded = BetterBetterCharacterController.Instance.IsGrounded();
bool l_inWater = BetterBetterCharacterController.Instance.IsInWater();
@ -170,6 +177,17 @@ namespace ml_prm
m_inAir = !(l_grounded || l_inWater);
}
if(!m_ragdolled && BetterBetterCharacterController.Instance.IsSitting()) // Those seats without velocity, smh
{
CVRSeat l_seat = BetterBetterCharacterController.Instance.GetCurrentSeat();
if(l_seat != null)
{
Vector3 l_pos = l_seat.transform.position;
m_seatVelocity = (l_pos - m_lastSeatPositon) / Time.deltaTime;
m_lastSeatPositon = l_pos;
}
}
if(m_ragdolled)
{
BodySystem.TrackingPositionWeight = 0f;
@ -178,7 +196,7 @@ namespace ml_prm
PlayerSetup.Instance.animatorManager.CancelEmote = true;
}
if(!m_ragdolled && !m_reachedGround && (BetterBetterCharacterController.Instance.IsOnGround() || BetterBetterCharacterController.Instance.IsInWater()))
if(!m_ragdolled && !m_reachedGround && (BetterBetterCharacterController.Instance.IsOnGround() || BetterBetterCharacterController.Instance.IsInWater() || BetterBetterCharacterController.Instance.IsSitting()))
{
m_groundedTime += Time.unscaledDeltaTime;
if(m_groundedTime >= 0.25f)
@ -210,9 +228,18 @@ namespace ml_prm
{
if(m_avatarReady && m_ragdolled)
{
Vector3 l_currentPos = m_puppetReferences.hips.position;
PlayerSetup.Instance.transform.position += (l_currentPos - m_ragdollLastPos);
m_ragdollLastPos = l_currentPos;
Vector3 l_diff = m_puppetReferences.hips.position - m_lastRagdollPosition;
m_playerPlane.SetNormalAndPosition(PlayerSetup.Instance.transform.rotation * Vector3.up, PlayerSetup.Instance.transform.position);
PlayerSetup.Instance.transform.position += l_diff;
m_lastRagdollPosition = m_puppetReferences.hips.position;
// Project on plane and fix our position if we under previous plane
if(m_playerPlane.GetDistanceToPoint(m_lastRagdollPosition) < 0f)
m_playerPlane.Flip();
float l_distance = m_playerPlane.GetDistanceToPoint(PlayerSetup.Instance.transform.position);
if(l_distance < 0f)
PlayerSetup.Instance.transform.position = m_playerPlane.ClosestPointOnPlane(PlayerSetup.Instance.transform.position);
}
}
@ -242,10 +269,10 @@ namespace ml_prm
// Game events
void OnAvatarClear()
{
if(m_initCoroutine != null)
if(m_initTask != null)
{
StopCoroutine(m_initCoroutine);
m_initCoroutine = null;
StopCoroutine(m_initTask);
m_initTask = null;
}
if(m_ragdolled)
@ -365,7 +392,7 @@ namespace ml_prm
m_avatarRagdollToggle = PlayerSetup.Instance._avatar.GetComponentInChildren<RagdollToggle>(true);
m_ragdolledParameter = new AvatarBoolParameter("Ragdolled", PlayerSetup.Instance.animatorManager);
m_initCoroutine = StartCoroutine(WaitForBodyHandlers());
m_initTask = StartCoroutine(WaitForBodyHandlers());
}
}
@ -381,7 +408,7 @@ namespace ml_prm
}
m_avatarReady = true;
m_initCoroutine = null;
m_initTask = null;
OnMovementDragChanged(Settings.MovementDrag);
OnAngularDragChanged(Settings.AngularDrag);
@ -414,6 +441,8 @@ namespace ml_prm
void OnSeatPreSit(CVRSeat p_seat)
{
m_lastSeatPositon = p_seat.transform.position;
if(!p_seat.occupied)
{
m_forcedSwitch = true;
@ -473,9 +502,8 @@ namespace ml_prm
if(m_avatarReady && m_ragdolled)
{
Vector3 l_pos = m_hips.position;
m_puppetReferences.hips.position = l_pos;
m_ragdollLastPos = l_pos;
m_puppetReferences.hips.position = m_hips.position;
m_lastRagdollPosition = m_puppetReferences.hips.position;
}
}
catch(System.Exception e)
@ -600,7 +628,7 @@ namespace ml_prm
void OnGestureGrabChanged(bool p_state)
{
if(m_avatarReady && m_ragdolled & !p_state)
if(m_avatarReady && m_ragdolled && !p_state)
{
foreach(var l_hanlder in m_ragdollBodyHandlers)
l_hanlder.Detach();
@ -622,13 +650,24 @@ namespace ml_prm
{
if(m_avatarReady && !m_ragdolled && CanRagdoll())
{
Vector3 l_velocity = Vector3.ClampMagnitude(BetterBetterCharacterController.Instance.velocity * (WorldManager.IsSafeWorld() ? Settings.VelocityMultiplier : 1f), WorldManager.GetMovementLimit());
Vector3 l_velocity = (BetterBetterCharacterController.Instance.IsSitting() ? m_seatVelocity : BetterBetterCharacterController.Instance.velocity);
l_velocity *= (WorldManager.IsSafeWorld() ? Settings.VelocityMultiplier : 1f);
l_velocity = Vector3.ClampMagnitude(l_velocity, WorldManager.GetMovementLimit());
if(Settings.ViewVelocity && WorldManager.IsSafeWorld())
{
float l_mag = l_velocity.magnitude;
l_velocity = PlayerSetup.Instance.GetActiveCamera().transform.forward * l_mag;
}
Vector3 l_playerPos = PlayerSetup.Instance.transform.position;
Quaternion l_playerRot = PlayerSetup.Instance.transform.rotation;
bool l_wasSitting = BetterBetterCharacterController.Instance.IsSitting();
if(BetterBetterCharacterController.Instance.IsSitting())
{
BetterBetterCharacterController.Instance.SetSitting(false);
l_wasSitting = true;
}
if(BetterBetterCharacterController.Instance.IsFlying())
BetterBetterCharacterController.Instance.ChangeFlight(false, true);
BetterBetterCharacterController.Instance.SetImmobilized(true);
@ -662,7 +701,12 @@ namespace ml_prm
l_handler.SetAngularVelocity(Vector3.zero);
}
m_ragdollLastPos = m_puppetReferences.hips.position;
if(l_wasSitting)
{
PlayerSetup.Instance.transform.position = l_playerPos;
PlayerSetup.Instance.transform.rotation = l_playerRot;
}
m_lastRagdollPosition = m_puppetReferences.hips.position;
m_downTime = 0f;
m_ragdolled = true;
@ -675,12 +719,6 @@ namespace ml_prm
{
BetterBetterCharacterController.Instance.TeleportPlayerTo(m_puppetReferences.hips.position, PlayerSetup.Instance.GetPlayerRotation().eulerAngles, false, false);
TryRestoreMovement();
if(!WorldManager.IsSafeWorld())
{
Vector3 l_vec = BetterBetterCharacterController.Instance.GetVelocity();
l_vec.y = Mathf.Clamp(l_vec.y, float.MinValue, 0f);
BetterBetterCharacterController.Instance.SetVelocity(l_vec);
}
BodySystem.TrackingPositionWeight = 1f;
IKSystem.Instance.applyOriginalHipPosition = m_applyHipsPosition;
IKSystem.Instance.applyOriginalHipRotation = m_applyHipsRotation;
@ -725,7 +763,6 @@ namespace ml_prm
bool l_result = m_reachedGround;
l_result &= !BodySystem.isCalibrating;
l_result &= !BetterBetterCharacterController.Instance.IsSitting();
l_result &= ((CombatSystem.Instance == null) || !CombatSystem.Instance.isDown);
return (l_result || m_forcedSwitch);
}

View file

@ -22,6 +22,7 @@ namespace ml_prm
public void RemoveListener(Action<T1, T2, T3> p_listener) => m_action -= p_listener;
public void Invoke(T1 p_objA, T2 p_objB, T3 p_objC) => m_action?.Invoke(p_objA, p_objB, p_objC);
}
public static readonly GestureEvent<PuppetMaster, GestureHand, bool> OnGestureState = new GestureEvent<PuppetMaster, GestureHand, bool>();
class PlayerEntry
@ -32,6 +33,8 @@ namespace ml_prm
public bool m_stateRight = false;
}
static RemoteGesturesManager ms_instance = null;
readonly List<PlayerEntry> m_entries = null;
internal RemoteGesturesManager()
@ -39,10 +42,20 @@ namespace ml_prm
m_entries = new List<PlayerEntry>();
}
void Awake()
{
if(ms_instance != null)
{
DestroyImmediate(this);
return;
}
ms_instance = this;
DontDestroyOnLoad(this);
}
void Start()
{
DontDestroyOnLoad(this);
CVRGameEventSystem.Player.OnJoinEntity.AddListener(OnRemotePlayerCreated);
CVRGameEventSystem.Player.OnLeaveEntity.AddListener(OnRemotePlayerDestroyed);
Settings.OnGestureGrabChanged.AddListener(OnGestureGrabChanged);
@ -50,6 +63,9 @@ namespace ml_prm
void OnDestroy()
{
if(ms_instance == this)
ms_instance = null;
m_entries.Clear();
CVRGameEventSystem.Player.OnJoinEntity.RemoveListener(OnRemotePlayerCreated);

View file

@ -8,6 +8,7 @@ using System.Reflection;
using UnityEngine;
using System.Linq;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Core.InteractionSystem;
namespace ml_prm
{
@ -17,6 +18,7 @@ namespace ml_prm
static readonly FieldInfo ms_referencePoints = typeof(PhysicsInfluencer).GetField("_referencePoints", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_influencerTouchingVolumes = typeof(PhysicsInfluencer).GetField("_touchingVolumes", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_influencerSubmergedColliders = typeof(PhysicsInfluencer).GetField("_submergedColliders", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_lastCVRSeat = typeof(BetterBetterCharacterController).GetField("_lastCvrSeat", BindingFlags.NonPublic | BindingFlags.Instance);
public static void ClearFluidVolumes(this BetterBetterCharacterController p_instance) => (ms_touchingVolumes.GetValue(p_instance) as List<FluidVolume>)?.Clear();
@ -58,5 +60,9 @@ namespace ml_prm
l_result |= ((FingerSystem.GetCurlNormalized(p_source._playerAvatarMovementDataCurrent.RightMiddle1Stretched) >= 0.5f) && (FingerSystem.GetCurlNormalized(p_source._playerAvatarMovementDataCurrent.RightMiddle2Stretched) >= 0.5f) && (FingerSystem.GetCurlNormalized(p_source._playerAvatarMovementDataCurrent.RightMiddle3Stretched) >= 0.5f));
return l_result;
}
public static CVRSeat GetCurrentSeat(this BetterBetterCharacterController p_instance) => (ms_lastCVRSeat?.GetValue(p_instance) as CVRSeat);
public static bool IsInRange(float p_value, float p_min, float p_max) => ((p_min <= p_value) && (p_value <= p_max));
}
}

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components;
using ABI_RC.Systems.GameEventSystem;
using System;
using UnityEngine;
namespace ml_prm
@ -22,18 +23,25 @@ namespace ml_prm
static void OnWorldLoad(string p_id)
{
ms_safeWorld = ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
ms_movementLimit = 1f;
GameObject l_restrictObj = GameObject.Find("[RagdollRestriction]");
ms_restrictedWorld = ((l_restrictObj == null) ? false : (l_restrictObj.scene.name != "DontDestroyOnLoad"));
if(CVRWorld.Instance != null)
try
{
ms_movementLimit = CVRWorld.Instance.baseMovementSpeed;
ms_movementLimit *= CVRWorld.Instance.sprintMultiplier;
ms_movementLimit *= CVRWorld.Instance.inAirMovementMultiplier;
ms_movementLimit *= CVRWorld.Instance.flyMultiplier;
ms_safeWorld = ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
ms_movementLimit = 1f;
GameObject l_restrictObj = GameObject.Find("[RagdollRestriction]");
ms_restrictedWorld = ((l_restrictObj == null) ? false : (l_restrictObj.scene.name != "DontDestroyOnLoad"));
if(CVRWorld.Instance != null)
{
ms_movementLimit = CVRWorld.Instance.baseMovementSpeed;
ms_movementLimit *= CVRWorld.Instance.sprintMultiplier;
ms_movementLimit *= CVRWorld.Instance.inAirMovementMultiplier;
ms_movementLimit *= CVRWorld.Instance.flyMultiplier;
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_vei.ViveExtendedInput), "ViveExtendedInput", "1.0.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_vei.ViveExtendedInput), "ViveExtendedInput", "1.1.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -1,5 +1,5 @@
# Vive Extended Input
This mod changes input behaviour for Vive controllers.
This mod adds few changes of input behaviour for Vive controllers.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)

View file

@ -6,15 +6,16 @@ namespace ml_vei
{
static class ResourcesHandler
{
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}

View file

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>ViveExtendedInput</PackageId>
<Version>1.0.5</Version>
<Version>1.1.0</Version>
<Authors>SDraw</Authors>
<Company>SDraw</Company>
<Product>ViveExtendedInput</Product>