Removed Upright

Leading hand option
Hands extension with `Q`/`E`
Joints fix
Update to LeapSDK 5.16
This commit is contained in:
SDraw 2023-11-26 19:24:11 +03:00
parent b6a200d44c
commit aebf6c2c4e
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
22 changed files with 9131 additions and 6719 deletions

View file

@ -2,7 +2,6 @@
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_pam
@ -10,47 +9,72 @@ namespace ml_pam
[DisallowMultipleComponent]
class ArmMover : MonoBehaviour
{
const float c_offsetLimit = 0.5f;
enum HandState
{
Empty = 0,
Pickup,
Extended
}
const float c_offsetLimit = 0.5f;
const KeyCode c_leftKey = KeyCode.Q;
const KeyCode c_rightKey = KeyCode.E;
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f);
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f);
static readonly Quaternion ms_palmToLeft = Quaternion.Euler(0f, 0f, -90f);
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;
Vector2 m_armWeight = Vector2.zero;
Vector4 m_vrIKWeights = Vector4.zero;
Transform m_origRightHand = null;
Transform m_origLeftHand = null;
float m_armLength = 0f;
float m_playspaceScale = 1f;
bool m_enabled = true;
ArmIK m_armIK = null;
Transform m_target = null;
Transform m_rotationTarget = null;
Transform m_rootLeft = null;
Transform m_rootRight = null;
Transform m_leftTarget = null;
Transform m_rightTarget = null;
ArmIK m_armIKLeft = null;
ArmIK m_armIKRight = null;
CVRPickupObject m_pickup = null;
Matrix4x4 m_offset = Matrix4x4.identity;
bool m_targetActive = false;
Matrix4x4 m_offset;
HandState m_leftHandState = HandState.Empty;
HandState m_rightHandState = HandState.Empty;
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
m_target = new GameObject("ArmPickupTarget").transform;
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_target.localPosition = Vector3.zero;
m_target.localRotation = Quaternion.identity;
m_rootLeft = new GameObject("[ArmPickupLeft]").transform;
m_rootLeft.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootLeft.localPosition = Vector3.zero;
m_rootLeft.localRotation = Quaternion.identity;
m_rotationTarget = new GameObject("RotationTarget").transform;
m_rotationTarget.parent = m_target;
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * Settings.GrabOffset, 0f, 0f);
m_rotationTarget.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.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_rightTarget = new GameObject("Target").transform;
m_rightTarget.parent = m_rootRight;
m_rightTarget.localPosition = new Vector3(c_offsetLimit * Settings.GrabOffset, 0f, 0f);
m_rightTarget.localRotation = Quaternion.identity;
m_enabled = Settings.Enabled;
Settings.EnabledChange += this.SetEnabled;
Settings.GrabOffsetChange += this.SetGrabOffset;
Settings.LeadingHandChange += this.OnLeadingHandChange;
Settings.HandsExtensionChange += this.OnHandsExtensionChange;
}
void OnDestroy()
@ -61,33 +85,106 @@ namespace ml_pam
void Update()
{
if(m_enabled && !ReferenceEquals(m_pickup, null))
if(!ReferenceEquals(m_pickup, null) && (m_pickup == null))
OnPickupDrop(m_pickup);
switch(m_leftHandState)
{
if(m_pickup != null)
case HandState.Empty:
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_target.position = l_result * ms_pointVector;
if(Settings.HandsExtension && Input.GetKeyDown(c_leftKey))
{
m_leftHandState = HandState.Extended;
m_rootLeft.localPosition = new Vector3(0f, 0f, m_armLength * m_playspaceScale);
SetArmActive(Settings.LeadHand.Left, true);
}
}
else
this.OnPickupDrop(m_pickup);
break;
case HandState.Extended:
{
if(Input.GetKeyUp(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))
{
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))
{
m_rightHandState = HandState.Empty;
SetArmActive(Settings.LeadHand.Right, false);
}
}
break;
case HandState.Pickup:
{
if(m_pickup != null)
{
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
m_rootRight.position = l_result * ms_pointVector;
}
}
break;
}
}
// IK updates
// VRIK updates
void OnIKPreUpdate()
{
m_armWeight.Set(m_vrIK.solver.rightArm.positionWeight, m_vrIK.solver.rightArm.rotationWeight);
if(m_targetActive && (Mathf.Approximately(m_armWeight.x, 0f) || Mathf.Approximately(m_armWeight.y, 0f)))
if(m_enabled)
{
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIKWeights.Set(m_vrIK.solver.leftArm.positionWeight, m_vrIK.solver.leftArm.rotationWeight, m_vrIK.solver.rightArm.positionWeight, m_vrIK.solver.rightArm.rotationWeight);
if(m_leftHandState != HandState.Empty)
{
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_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.target = m_rightTarget;
}
}
}
void OnIKPostUpdate()
{
m_vrIK.solver.rightArm.positionWeight = m_armWeight.x;
m_vrIK.solver.rightArm.rotationWeight = m_armWeight.y;
if(m_enabled)
{
m_vrIK.solver.leftArm.positionWeight = m_vrIKWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_vrIKWeights.y;
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.rightArm.positionWeight = m_vrIKWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_vrIKWeights.w;
m_vrIK.solver.rightArm.target = m_origRightHand;
}
}
// Settings
@ -95,17 +192,93 @@ namespace ml_pam
{
m_enabled = p_state;
RefreshArmIK();
if(m_enabled)
RestorePickup();
{
if(m_leftHandState != HandState.Empty)
SetArmActive(Settings.LeadHand.Left, true);
if(m_rightHandState != HandState.Empty)
SetArmActive(Settings.LeadHand.Right, true);
OnHandsExtensionChange(Settings.HandsExtension);
}
else
RestoreVRIK();
SetArmActive(Settings.LeadHand.Both, false, true);
}
void SetGrabOffset(float p_value)
{
if(m_rotationTarget != null)
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * m_playspaceScale * p_value, 0f, 0f);
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);
}
void OnLeadingHandChange(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);
}
}
void OnHandsExtensionChange(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);
}
}
}
}
// Game events
@ -113,8 +286,9 @@ namespace ml_pam
{
m_vrIK = null;
m_origRightHand = null;
m_armIK = null;
m_targetActive = false;
m_armIKLeft = null;
m_armIKRight = null;
m_armLength = 0f;
}
internal void OnAvatarSetup()
@ -122,44 +296,30 @@ namespace ml_pam
// Recheck if user could switch to VR
if(m_inVR != Utils.IsInVR())
{
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_target.localPosition = Vector3.zero;
m_target.localRotation = Quaternion.identity;
m_rootLeft.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootLeft.localPosition = Vector3.zero;
m_rootLeft.localRotation = Quaternion.identity;
m_rootRight.parent = PlayerSetup.Instance.GetActiveCamera().transform;
m_rootRight.localPosition = Vector3.zero;
m_rootRight.localRotation = Quaternion.identity;
}
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
if(PlayerSetup.Instance._animator.isHuman)
if(!m_inVR && PlayerSetup.Instance._animator.isHuman)
{
Vector3 l_hipsPos = Vector3.zero;
Transform l_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
if(l_hips != null)
l_hipsPos = l_hips.localPosition;
HumanPose l_currentPose = new HumanPose();
HumanPoseHandler l_poseHandler = null;
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
if(!m_inVR)
{
l_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
l_poseHandler.GetHumanPose(ref l_currentPose);
TPoseHelper l_tpHelper = new TPoseHelper();
l_tpHelper.Assign(PlayerSetup.Instance._animator);
l_tpHelper.Apply();
HumanPose l_tPose = new HumanPose
{
bodyPosition = l_currentPose.bodyPosition,
bodyRotation = l_currentPose.bodyRotation,
muscles = new float[l_currentPose.muscles.Length]
};
for(int i = 0; i < l_tPose.muscles.Length; i++)
l_tPose.muscles[i] = ms_tposeMuscles[i];
l_poseHandler.SetHumanPose(ref l_tPose);
}
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null)
m_rotationTarget.localRotation = (ms_palmToLeft * (m_inVR ? ms_offsetRight : ms_offsetRightDesktop)) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
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;
if(m_vrIK == null)
{
@ -169,39 +329,56 @@ namespace ml_pam
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
m_armIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_armIK.solver.isLeft = false;
m_armIK.solver.SetChain(
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),
l_leftHand,
PlayerSetup.Instance._animator.transform
);
m_armIKLeft.solver.arm.target = m_leftTarget;
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),
l_hand,
l_rightHand,
PlayerSetup.Instance._animator.transform
);
m_armIK.solver.arm.target = m_rotationTarget;
m_armIK.solver.arm.positionWeight = 1f;
m_armIK.solver.arm.rotationWeight = 1f;
m_armIK.solver.IKPositionWeight = 0f;
m_armIK.solver.IKRotationWeight = 0f;
m_armIK.enabled = m_enabled;
m_armIKRight.solver.arm.target = m_rightTarget;
m_armIKRight.solver.arm.positionWeight = 1f;
m_armIKRight.solver.arm.rotationWeight = 1f;
m_armIKRight.solver.IKPositionWeight = 0f;
m_armIKRight.solver.IKRotationWeight = 0f;
m_armIKRight.enabled = false;
}
else
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_armLength = m_vrIK.solver.leftArm.mag * 1.25f;
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
}
l_poseHandler?.SetHumanPose(ref l_currentPose);
l_poseHandler?.Dispose();
if(l_hips != null)
l_hips.localPosition = l_hipsPos;
l_tpHelper.Restore();
l_tpHelper.Unassign();
}
if(m_enabled)
RestorePickup();
SetEnabled(m_enabled);
}
internal void OnPickupGrab(CVRPickupObject p_pickup, ControllerRay p_ray, Vector3 p_hit)
@ -224,20 +401,23 @@ namespace ml_pam
else
m_offset = m_pickup.transform.GetMatrix().inverse * Matrix4x4.Translate(p_hit);
if(m_enabled)
switch(Settings.LeadingHand)
{
if((m_vrIK != null) && !m_targetActive)
case Settings.LeadHand.Left:
m_leftHandState = HandState.Pickup;
break;
case Settings.LeadHand.Right:
m_rightHandState = HandState.Pickup;
break;
case Settings.LeadHand.Both:
{
m_vrIK.solver.rightArm.target = m_rotationTarget;
m_targetActive = true;
}
if(m_armIK != null)
{
m_armIK.solver.IKPositionWeight = 1f;
m_armIK.solver.IKRotationWeight = 1f;
m_leftHandState = HandState.Pickup;
m_rightHandState = HandState.Pickup;
}
break;
}
SetArmActive(Settings.LeadingHand, true);
}
}
@ -246,17 +426,22 @@ namespace ml_pam
if(m_pickup == p_pickup)
{
m_pickup = null;
if(m_enabled)
switch(Settings.LeadingHand)
{
RestoreVRIK();
if(m_armIK != null)
case Settings.LeadHand.Left:
m_leftHandState = HandState.Empty;
break;
case Settings.LeadHand.Right:
m_rightHandState = HandState.Empty;
break;
case Settings.LeadHand.Both:
{
m_armIK.solver.IKPositionWeight = 0f;
m_armIK.solver.IKRotationWeight = 0f;
m_leftHandState = HandState.Empty;
m_rightHandState = HandState.Empty;
}
break;
}
SetArmActive(Settings.LeadingHand, false);
}
}
@ -267,33 +452,23 @@ namespace ml_pam
}
// Arbitrary
void RestorePickup()
void SetArmActive(Settings.LeadHand p_hand, bool p_state, bool p_forced = false)
{
if((m_vrIK != null) && (m_pickup != null))
if(m_enabled || p_forced)
{
m_vrIK.solver.rightArm.target = m_rotationTarget;
m_targetActive = true;
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((m_armIK != null) && (m_pickup != null))
{
m_armIK.solver.IKPositionWeight = 1f;
m_armIK.solver.IKRotationWeight = 1f;
}
}
void RestoreVRIK()
{
if((m_vrIK != null) && m_targetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_targetActive = false;
}
}
void RefreshArmIK()
{
if(m_armIK != null)
m_armIK.enabled = m_enabled;
}
}
}

View file

@ -1,5 +1,5 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.0.8", "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)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.0.9", "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)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -1,12 +1,18 @@
# Pickup Arm Movement
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
# Usage
Available mod's settings in `Settings - Interactions - Pickup Arm Movement`:
* **Enable hand movement:** enables/disables arm tracking; default value - `true`.
* **Grab offset:** offset from pickup grab point; defalut value - `25`.
# Pickup Arm Movement
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
# Usage
Available mod's settings in `Settings - Interactions - 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`.
# Notes
* Made for desktop mode in mind.
* Compatible with [DekstopVRIK](https://github.com/NotAKidOnSteam/NAK_CVR_Mods).

View file

@ -1,104 +1,140 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_pam
{
static class Settings
{
public enum ModSetting
{
Enabled = 0,
GrabOffset
}
public static bool Enabled { get; private set; } = true;
public static float GrabOffset { get; private set; } = 0.25f;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
static public event Action<float> GrabOffsetChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("PAM", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
ms_category.CreateEntry(ModSetting.GrabOffset.ToString(), (int)(GrabOffset * 100f)),
};
Load();
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;
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.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
GrabOffset = (int)ms_entries[(int)ModSetting.GrabOffset].BoxedValue * 0.01f;
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.GrabOffset:
{
GrabOffset = int.Parse(p_value) * 0.01f;
GrabOffsetChange?.Invoke(GrabOffset);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
}
}
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_pam
{
static class Settings
{
public enum ModSetting
{
Enabled = 0,
GrabOffset,
LeadHand,
HandsExtension
}
public enum LeadHand
{
Left = 0,
Right,
Both
}
public static bool Enabled { get; private set; } = true;
public static float GrabOffset { get; private set; } = 0.25f;
public static LeadHand LeadingHand { get; private set; } = LeadHand.Right;
public static bool HandsExtension { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
static public event Action<float> GrabOffsetChange;
static public event Action<LeadHand> LeadingHandChange;
static public event Action<bool> HandsExtensionChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("PAM", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
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),
};
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;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;
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));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
case ModSetting.HandsExtension:
{
HandsExtension = bool.Parse(p_value);
HandsExtensionChange?.Invoke(HandsExtension);
} break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.GrabOffset:
{
GrabOffset = int.Parse(p_value) * 0.01f;
GrabOffsetChange?.Invoke(GrabOffset);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnDropdownUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.LeadHand:
{
LeadingHand = (LeadHand)int.Parse(p_value);
LeadingHandChange?.Invoke(LeadingHand);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
}
}

63
ml_pam/TPoseHelper.cs Normal file
View file

@ -0,0 +1,63 @@
using System.Reflection;
using UnityEngine;
using ABI_RC.Systems.IK.SubSystems;
namespace ml_pam
{
class TPoseHelper
{
static readonly float[] ms_tposeMuscles = typeof(BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
HumanPoseHandler m_poseHandler = null;
HumanPose m_oldPose;
HumanPose m_newPose;
Vector3 m_hipsLocalPos = Vector3.zero;
Transform m_hips = null;
public void Assign(Animator p_animator)
{
if(m_poseHandler != null)
{
m_poseHandler = new HumanPoseHandler(p_animator.avatar, p_animator.transform);
m_hips = p_animator.GetBoneTransform(HumanBodyBones.Hips);
}
}
public void Unassign()
{
m_poseHandler?.Dispose();
m_poseHandler = null;
m_oldPose = new HumanPose();
m_newPose = new HumanPose();
m_hips = null;
m_hipsLocalPos = Vector3.zero;
}
public void Apply()
{
if(m_hips != null)
m_hipsLocalPos = m_hips.localPosition;
if(m_poseHandler != null)
{
m_poseHandler.GetHumanPose(ref m_oldPose);
m_newPose.bodyPosition = m_oldPose.bodyPosition;
m_newPose.bodyRotation = m_oldPose.bodyRotation;
m_newPose.muscles = new float[m_oldPose.muscles.Length];
for(int i = 0, j = m_newPose.muscles.Length; i < j; i++)
m_newPose.muscles[i] = ms_tposeMuscles[i];
m_poseHandler.SetHumanPose(ref m_newPose);
}
}
public void Restore()
{
if(m_poseHandler != null)
m_poseHandler.SetHumanPose(ref m_oldPose);
if(m_hips != null)
m_hips.localPosition = m_hipsLocalPos;
}
}
}

View file

@ -1,74 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>PickupArmMovement</PackageId>
<Version>1.0.8</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>PickupArmMovement</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="PickupArmMovement.json" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<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>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>PickupArmMovement</PackageId>
<Version>1.0.9</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>PickupArmMovement</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="PickupArmMovement.json" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<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>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -1,32 +1,50 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Pickup Arm Movement</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable hand movement: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<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>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('PAM', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_PAM'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('PAM', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_PAM'));
}
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Pickup Arm Movement</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<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>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Leading hand: </div>
<div class ="option-input">
<div id="LeadHand" class ="inp_dropdown no-scroll" data-options="0:Left,1:Right,2:Both" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Hands extension (Q/E): </div>
<div class ="option-input">
<div id="HandsExtension" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('PAM', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_PAM'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('PAM', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_PAM'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('PAM', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_PAM'));
}