Better ones

This commit is contained in:
SDraw 2022-09-15 21:16:01 +00:00 committed by SDraw
parent 73e0cd6077
commit f364d6d2aa
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
15 changed files with 332 additions and 186 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -13,10 +13,13 @@ namespace ml_amt
ms_instance = this; ms_instance = this;
Settings.Init(); Settings.Init();
Settings.IKOverrideChange += this.OnIKOverrideChange; Settings.IKOverrideCrouchChange += this.OnIKOverrideCrouchChange;
Settings.CrouchLimitChange += this.OnCrouchLimitChange; Settings.CrouchLimitChange += this.OnCrouchLimitChange;
Settings.DetectPoseChange += this.OnDetectPoseChange; Settings.IKOverrideProneChange += this.OnIKOverrideProneChange;
Settings.ProneLimitChange += this.OnProneLimitChange; Settings.ProneLimitChange += this.OnProneLimitChange;
Settings.PoseTransitionsChange += this.OnPoseTransitonsChange;
Settings.AdjustedMovementChange += this.OnAdjustedMovementChange;
Settings.IKOverrideFlyChange += this.OnIKOverrideFlyChange;
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
@ -38,33 +41,50 @@ namespace ml_amt
yield return null; yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>(); m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
m_localTweaker.SetIKOverride(Settings.IKOverride); m_localTweaker.SetIKOverrideCrouch(Settings.IKOverrideCrouch);
m_localTweaker.SetCrouchLimit(Settings.CrouchLimit); m_localTweaker.SetCrouchLimit(Settings.CrouchLimit);
m_localTweaker.SetDetectPose(Settings.DetectPose); m_localTweaker.SetIKOverrideCrouch(Settings.IKOverrideProne);
m_localTweaker.SetProneLimit(Settings.ProneLimit); m_localTweaker.SetProneLimit(Settings.ProneLimit);
m_localTweaker.SetPoseTransitions(Settings.PoseTransitions);
m_localTweaker.SetAdjustedMovement(Settings.AdjustedMovement);
m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly);
} }
void OnIKOverrideCrouchChange(bool p_state)
void OnIKOverrideChange(bool p_state)
{ {
if(m_localTweaker != null) if(m_localTweaker != null)
m_localTweaker.SetIKOverride(p_state); m_localTweaker.SetIKOverrideCrouch(p_state);
} }
void OnCrouchLimitChange(float p_value) void OnCrouchLimitChange(float p_value)
{ {
if(m_localTweaker != null) if(m_localTweaker != null)
m_localTweaker.SetCrouchLimit(p_value); m_localTweaker.SetCrouchLimit(p_value);
} }
void OnDetectPoseChange(bool p_state) void OnIKOverrideProneChange(bool p_state)
{ {
if(m_localTweaker != null) if(m_localTweaker != null)
m_localTweaker.SetDetectPose(p_state); m_localTweaker.SetIKOverrideProne(p_state);
} }
void OnProneLimitChange(float p_value) void OnProneLimitChange(float p_value)
{ {
if(m_localTweaker != null) if(m_localTweaker != null)
m_localTweaker.SetProneLimit(p_value); m_localTweaker.SetProneLimit(p_value);
} }
void OnPoseTransitonsChange(bool p_state)
{
if(m_localTweaker != null)
m_localTweaker.SetPoseTransitions(p_state);
}
void OnAdjustedMovementChange(bool p_state)
{
if(m_localTweaker != null)
m_localTweaker.SetAdjustedMovement(p_state);
}
void OnIKOverrideFlyChange(bool p_state)
{
if(m_localTweaker != null)
m_localTweaker.SetIKOverrideFly(p_state);
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear(); static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear() void OnAvatarClear()

View file

@ -1,4 +1,5 @@
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK; using RootMotion.FinalIK;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@ -8,11 +9,14 @@ namespace ml_amt
class MotionTweaker : MonoBehaviour class MotionTweaker : MonoBehaviour
{ {
static System.Reflection.FieldInfo ms_rootVelocity = typeof(IKSolverVR).GetField("rootVelocity", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); static System.Reflection.FieldInfo ms_rootVelocity = typeof(IKSolverVR).GetField("rootVelocity", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
static System.Reflection.FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
enum ParameterType enum ParameterType
{ {
Upright Upright,
GroundedRaw
} }
enum ParameterSyncType enum ParameterSyncType
{ {
Local, Local,
@ -41,18 +45,21 @@ namespace ml_amt
bool m_avatarReady = false; bool m_avatarReady = false;
bool m_compatibleAvatar = false; bool m_compatibleAvatar = false;
float m_upright = 1f;
bool m_ikOverride = true;
float m_currentUpright = 1f;
PoseState m_poseState = PoseState.Standing; PoseState m_poseState = PoseState.Standing;
bool m_ikOverrideCrouch = true;
float m_crouchLimit = 0.65f; float m_crouchLimit = 0.65f;
bool m_customCrouchLimit = false; bool m_customCrouchLimit = false;
bool m_detectPose = true; bool m_ikOverrideProne = true;
float m_proneLimit = 0.3f; float m_proneLimit = 0.3f;
bool m_customProneLimit = false; bool m_customProneLimit = false;
bool m_poseTransitions = true;
bool m_adjustedMovement = true;
bool m_ikOverrideFly = true;
bool m_customLocomotionOffset = false; bool m_customLocomotionOffset = false;
Vector3 m_locomotionOffset = Vector3.zero; Vector3 m_locomotionOffset = Vector3.zero;
@ -71,36 +78,51 @@ namespace ml_amt
Matrix4x4 l_hmdMatrix = PlayerSetup.Instance.transform.GetMatrix().inverse * (PlayerSetup.Instance._inVr ? PlayerSetup.Instance.vrHeadTracker.transform.GetMatrix() : PlayerSetup.Instance.desktopCameraRig.transform.GetMatrix()); Matrix4x4 l_hmdMatrix = PlayerSetup.Instance.transform.GetMatrix().inverse * (PlayerSetup.Instance._inVr ? PlayerSetup.Instance.vrHeadTracker.transform.GetMatrix() : PlayerSetup.Instance.desktopCameraRig.transform.GetMatrix());
float l_currentHeight = Mathf.Clamp((l_hmdMatrix * ms_pointVector).y, 0f, float.MaxValue); float l_currentHeight = Mathf.Clamp((l_hmdMatrix * ms_pointVector).y, 0f, float.MaxValue);
float l_avatarViewHeight = Mathf.Clamp(PlayerSetup.Instance.GetViewPointHeight() * PlayerSetup.Instance._avatar.transform.localScale.y, 0f, float.MaxValue); float l_avatarViewHeight = Mathf.Clamp(PlayerSetup.Instance.GetViewPointHeight() * PlayerSetup.Instance._avatar.transform.localScale.y, 0f, float.MaxValue);
m_currentUpright = Mathf.Clamp((((l_currentHeight > 0f) && (l_avatarViewHeight > 0f)) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f); m_upright = Mathf.Clamp((((l_currentHeight > 0f) && (l_avatarViewHeight > 0f)) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f);
PoseState l_poseState = (m_currentUpright <= m_proneLimit) ? PoseState.Proning : ((m_currentUpright <= m_crouchLimit) ? PoseState.Crouching : PoseState.Standing); PoseState l_poseState = (m_upright <= m_proneLimit) ? PoseState.Proning : ((m_upright <= m_crouchLimit) ? PoseState.Crouching : PoseState.Standing);
if((m_vrIk != null) && m_vrIk.enabled) if((m_vrIk != null) && m_vrIk.enabled)
{ {
if(m_ikOverride && (m_poseState != l_poseState) && (l_poseState == PoseState.Standing)) if(m_poseState != l_poseState)
ms_rootVelocity.SetValue(m_vrIk.solver, Vector3.zero); {
// Weird fix of torso shaking
if(m_ikOverrideCrouch && (l_poseState == PoseState.Standing))
ms_rootVelocity.SetValue(m_vrIk.solver, Vector3.zero);
if(m_ikOverrideProne && !m_ikOverrideCrouch && (l_poseState == PoseState.Crouching))
ms_rootVelocity.SetValue(m_vrIk.solver, Vector3.zero);
}
if(m_detectPose && !m_compatibleAvatar && !PlayerSetup.Instance.fullBodyActive) if(m_poseTransitions && !m_compatibleAvatar && !PlayerSetup.Instance.fullBodyActive)
{ {
switch(l_poseState) switch(l_poseState)
{ {
case PoseState.Standing: case PoseState.Standing:
{ {
PlayerSetup.Instance._movementSystem.ChangeCrouch(false); if(m_adjustedMovement)
PlayerSetup.Instance._movementSystem.ChangeProne(false); {
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false); // Forced to stop transitioning to standing locomotion MovementSystem.Instance.ChangeCrouch(false); //
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false); // Forced to stop transitioning to standing locomotion MovementSystem.Instance.ChangeProne(false); // Affects movement speed
}
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false); //
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false); // Force to stop transitioning to standing locomotion while moving
} }
break; break;
case PoseState.Crouching: case PoseState.Crouching:
PlayerSetup.Instance._movementSystem.ChangeCrouch(true); {
if(m_adjustedMovement)
MovementSystem.Instance.ChangeCrouch(true);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", true); PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", true);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false); PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false);
break; }
break;
case PoseState.Proning: case PoseState.Proning:
{ {
PlayerSetup.Instance._movementSystem.ChangeProne(true); if(m_adjustedMovement)
MovementSystem.Instance.ChangeProne(true);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false); PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", true); PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", true);
} }
@ -122,10 +144,24 @@ namespace ml_amt
switch(l_param.m_sync) switch(l_param.m_sync)
{ {
case ParameterSyncType.Local: case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_currentUpright); PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_upright);
break; break;
case ParameterSyncType.Synced: case ParameterSyncType.Synced:
PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, m_currentUpright); PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, m_upright);
break;
}
}
break;
case ParameterType.GroundedRaw:
{
switch(l_param.m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetBool(l_param.m_hash, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance));
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance) ? 1f : 0f);
break; break;
} }
} }
@ -207,45 +243,65 @@ namespace ml_amt
void OnIKPreUpdate() void OnIKPreUpdate()
{ {
if(m_ikOverride) m_locomotionWeight = m_vrIk.solver.locomotion.weight;
{
m_locomotionWeight = m_vrIk.solver.locomotion.weight; if((m_ikOverrideCrouch && (m_poseState != PoseState.Standing)) || (m_ikOverrideProne && (m_poseState == PoseState.Proning)))
if(m_poseState != PoseState.Standing) m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.locomotion.weight = 0f; if(m_ikOverrideFly && MovementSystem.Instance.flying)
} m_vrIk.solver.locomotion.weight = 0f;
} }
void OnIKPostUpdate() void OnIKPostUpdate()
{ {
if(m_ikOverride) m_vrIk.solver.locomotion.weight = m_locomotionWeight;
m_vrIk.solver.locomotion.weight = m_locomotionWeight;
} }
public void SetIKOverride(bool p_state) public void SetIKOverrideCrouch(bool p_state)
{ {
m_ikOverride = p_state; m_ikOverrideCrouch = p_state;
} }
public void SetCrouchLimit(float p_value) public void SetCrouchLimit(float p_value)
{ {
if(!m_customCrouchLimit) if(!m_customCrouchLimit)
m_crouchLimit = Mathf.Clamp(p_value, 0f, 1f); m_crouchLimit = Mathf.Clamp(p_value, 0f, 1f);
} }
public void SetDetectPose(bool p_state) public void SetIKOverrideProne(bool p_state)
{ {
m_detectPose = p_state; m_ikOverrideProne = p_state;
if(!m_detectPose && m_avatarReady && !m_compatibleAvatar && PlayerSetup.Instance._inVr)
{
PlayerSetup.Instance._movementSystem.ChangeCrouch(false);
PlayerSetup.Instance._movementSystem.ChangeProne(false);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false);
}
} }
public void SetProneLimit(float p_value) public void SetProneLimit(float p_value)
{ {
if(!m_customProneLimit) if(!m_customProneLimit)
m_proneLimit = Mathf.Clamp(p_value, 0f, 1f); m_proneLimit = Mathf.Clamp(p_value, 0f, 1f);
} }
public void SetPoseTransitions(bool p_state)
{
m_poseTransitions = p_state;
if(!m_poseTransitions && m_avatarReady && !m_compatibleAvatar && PlayerSetup.Instance._inVr)
{
if(m_adjustedMovement)
{
MovementSystem.Instance.ChangeCrouch(false);
MovementSystem.Instance.ChangeProne(false);
}
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Crouching", false);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool("Prone", false);
}
}
public void SetAdjustedMovement(bool p_state)
{
m_adjustedMovement = p_state;
if(!m_adjustedMovement && m_poseTransitions && m_avatarReady && !m_compatibleAvatar && PlayerSetup.Instance._inVr)
{
MovementSystem.Instance.ChangeCrouch(false);
MovementSystem.Instance.ChangeProne(false);
}
}
public void SetIKOverrideFly(bool p_state)
{
m_ikOverrideFly = p_state;
}
} }
} }

View file

@ -1,10 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyTitle("AvatarMotionTweaker")] [assembly: AssemblyTitle("AvatarMotionTweaker")]
[assembly: AssemblyVersion("1.0.8")] [assembly: AssemblyVersion("1.0.9")]
[assembly: AssemblyFileVersion("1.0.8")] [assembly: AssemblyFileVersion("1.0.9")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.0.8", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.0.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -10,40 +10,29 @@ This mod adds features for AAS animator and avatar locomotion behaviour.
# Usage # Usage
Available mod's settings in `Settings - Implementation - Avatar Motion Tweaker`: Available mod's settings in `Settings - Implementation - Avatar Motion Tweaker`:
* **IK locomotion override:** disables legs locomotion/autostep upon HMD reaching height of `CrouchLimit`; default value - `true`. * **IK override while crouching:** disables legs locomotion/autostep upon HMD reaching `Crouch limit`; default value - `true`.
* **Crouch limit:** defines first limit; default value - `65`. * **Crouch limit:** defines crouch limit; default value - `65`.
* Note: Can be overrided by avatar. For this avatar has to have child gameobject with name `CrouchLimit`, its Y-axis location will be used as limit, should be in range [0.0, 1.0]. * Note: Can be overrided by avatar. For this avatar has to have child gameobject with name `CrouchLimit`, its Y-axis location will be used as limit, should be in range [0.0, 1.0].
* **Detect pose (regular avatars):** forces regular avatars' animations to transit to crouching/proning animation states; default value - `true`. * **IK override while crouching:** disables legs locomotion/autostep upon HMD reaching height of `Prone limit`; default value - `true`.
* Note: Avatar is considered as regular if its animator doesn't have `Upright` parameter. * **Prone limit:** defines second limit; default value - `30`.
* **Prone limit (regular avatars):** defines second limit; default value - `30`.
* Note: Can be overrided by avatar. For this avatar has to have child gameobject with name `ProneLimit`, its Y-axis location will be used as limit, should be in range [0.0, 1.0]. * Note: Can be overrided by avatar. For this avatar has to have child gameobject with name `ProneLimit`, its Y-axis location will be used as limit, should be in range [0.0, 1.0].
* Note: Has no effect for mod compatible avatars. * **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **Pose transitions:** allows regular avatars animator to transit in crouch/prone states; default value - `true`.
* Note: Avatar is considered as regular if its AAS animator doesn't have `Upright` parameter.
* **Adjusted pose movement speed:** scales movement speed upon crouching/proning; default value - `true`.
* Note: Requires enabled `Pose transitions` option.
Available additional parameters for AAS animator: Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height. Range - [0.0,1.0] (0.0 - floor, 1.0 - full standing). * **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0].
* Note: Can be set as local-only (not synced) if starts with `#` character. * Note: Can be set as local-only (not synced) if starts with `#` character.
* Note: Defining this parameter in AAS animator will consider avatar as compatible with mod. * Note: Defining this parameter in AAS animator will consider avatar as compatible with mod.
* **`GroundedRaw`:** defines instant grounding state of player instead of delayed default parameter `Grounded`.
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional avatars tweaks: Additional avatars tweaks:
* If avatar has child object with name `LocomotionOffset` its local position will be used for offsetting VRIK locomotion center. * If avatar has child object with name `LocomotionOffset` its local position will be used for offsetting VRIK locomotion center.
## Advanced usage in AAS animator for mixed desktop and VR
* To differentiate between desktop and VR players use `CVR Parameter Stream` component on avatar's root gameobject. As example, `InVR` and `InFBT` are boolean typed animator parameters:
![](.github/img_02.png)
* Add additional transitions between standing, crouching and proning blend trees:
![](.github/img_03.png)
* Add conditions for new VR transitions:
* Standing -> Crouching:
![](.github/img_04.png)
* Crouching -> Standing:
![](.github/img_05.png)
* Crouching -> Proning:
![](.github/img_06.png)
* Proning -> Crouching:
![](.github/img_07.png)
* Add condition check for all desktop transitions:
![](.github/img_08.png)
# Notes # Notes
* Usage of `Upright` parameter for transition between poses (standing/crouching/proning) in desktop mode is useless, because in this case your animations are updating value of `Upright` parameter, not the other way around. * Usage of `Upright` parameter for transition between poses (standing/crouching/proning) in desktop mode is useless, because in this case your animations are updating value of `Upright` parameter, not the other way around.
* Please, keep your avatar root object at identity scale. Thank you. * **Adjusted pose movement speed** option isn't applied to compatible avatars, in progress.
* Please, keep your avatars' root object at identity scale. Thank you.

View file

@ -9,34 +9,46 @@ namespace ml_amt
{ {
enum ModSetting enum ModSetting
{ {
IKOverride = 0, IKOverrideCrouch = 0,
CrouchLimit, CrouchLimit,
DetectPose, IKOverrideProne,
ProneLimit ProneLimit,
PoseTransitions,
AdjustedMovement,
IKOverrideFly
}; };
static bool ms_ikOverride = true; static bool ms_ikOverrideCrouch = true;
static float ms_crouchLimit = 0.65f; static float ms_crouchLimit = 0.65f;
static bool ms_detectPose = true; static bool ms_ikOverrideProne = true;
static float ms_proneLimit = 0.3f; static float ms_proneLimit = 0.3f;
static bool ms_poseTransitions = true;
static bool ms_adjustedMovement = true;
static bool ms_ikOverrideFly = true;
static MelonLoader.MelonPreferences_Category ms_category = null; static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null; static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> IKOverrideChange; static public event Action<bool> IKOverrideCrouchChange;
static public event Action<float> CrouchLimitChange; static public event Action<float> CrouchLimitChange;
static public event Action<bool> DetectPoseChange; static public event Action<bool> IKOverrideProneChange;
static public event Action<float> ProneLimitChange; static public event Action<float> ProneLimitChange;
static public event Action<bool> PoseTransitionsChange;
static public event Action<bool> AdjustedMovementChange;
static public event Action<bool> IKOverrideFlyChange;
public static void Init() public static void Init()
{ {
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT"); ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>(); ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverride.ToString(), true)); ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideCrouch.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), 65)); ms_entries.Add(ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), 65));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DetectPose.ToString(), true)); ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideProne.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), 30)); ms_entries.Add(ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), 30));
ms_entries.Add(ms_category.CreateEntry(ModSetting.PoseTransitions.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AdjustedMovement.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), true));
Load(); Load();
@ -67,10 +79,13 @@ namespace ml_amt
static void Load() static void Load()
{ {
ms_ikOverride = (bool)ms_entries[(int)ModSetting.IKOverride].BoxedValue; ms_ikOverrideCrouch = (bool)ms_entries[(int)ModSetting.IKOverrideCrouch].BoxedValue;
ms_crouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f; ms_crouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
ms_detectPose = (bool)ms_entries[(int)ModSetting.DetectPose].BoxedValue; ms_ikOverrideProne = (bool)ms_entries[(int)ModSetting.IKOverrideProne].BoxedValue;
ms_proneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f; ms_proneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f;
ms_poseTransitions = (bool)ms_entries[(int)ModSetting.PoseTransitions].BoxedValue;
ms_adjustedMovement = (bool)ms_entries[(int)ModSetting.AdjustedMovement].BoxedValue;
ms_ikOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
} }
static void OnSliderUpdate(string p_name, string p_value) static void OnSliderUpdate(string p_name, string p_value)
@ -83,13 +98,15 @@ namespace ml_amt
{ {
ms_crouchLimit = int.Parse(p_value) * 0.01f; ms_crouchLimit = int.Parse(p_value) * 0.01f;
CrouchLimitChange?.Invoke(ms_crouchLimit); CrouchLimitChange?.Invoke(ms_crouchLimit);
} break; }
break;
case ModSetting.ProneLimit: case ModSetting.ProneLimit:
{ {
ms_proneLimit = int.Parse(p_value) * 0.01f; ms_proneLimit = int.Parse(p_value) * 0.01f;
ProneLimitChange?.Invoke(ms_proneLimit); ProneLimitChange?.Invoke(ms_proneLimit);
} break; }
break;
} }
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value); ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
@ -102,16 +119,38 @@ namespace ml_amt
{ {
switch(l_setting) switch(l_setting)
{ {
case ModSetting.IKOverride: case ModSetting.IKOverrideCrouch:
{ {
ms_ikOverride = bool.Parse(p_value); ms_ikOverrideCrouch = bool.Parse(p_value);
IKOverrideChange?.Invoke(ms_ikOverride); IKOverrideCrouchChange?.Invoke(ms_ikOverrideCrouch);
} break; }
break;
case ModSetting.DetectPose: case ModSetting.IKOverrideProne:
{ {
ms_detectPose = bool.Parse(p_value); ms_ikOverrideProne = bool.Parse(p_value);
DetectPoseChange?.Invoke(ms_detectPose); IKOverrideProneChange?.Invoke(ms_ikOverrideProne);
}
break;
case ModSetting.PoseTransitions:
{
ms_poseTransitions = bool.Parse(p_value);
PoseTransitionsChange?.Invoke(ms_poseTransitions);
}
break;
case ModSetting.AdjustedMovement:
{
ms_adjustedMovement = bool.Parse(p_value);
AdjustedMovementChange?.Invoke(ms_adjustedMovement);
}
break;
case ModSetting.IKOverrideFly:
{
ms_ikOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(ms_ikOverrideFly);
} break; } break;
} }
@ -119,24 +158,33 @@ namespace ml_amt
} }
} }
public static bool IKOverrideCrouch
{
get => ms_ikOverrideCrouch;
}
public static float CrouchLimit public static float CrouchLimit
{ {
get => ms_crouchLimit; get => ms_crouchLimit;
} }
public static bool IKOverrideProne
public static bool IKOverride
{ {
get => ms_ikOverride; get => ms_ikOverrideProne;
} }
public static bool DetectPose
{
get => ms_detectPose;
}
public static float ProneLimit public static float ProneLimit
{ {
get => ms_proneLimit; get => ms_proneLimit;
} }
public static bool PoseTransitions
{
get => ms_poseTransitions;
}
public static bool AdjustedMovement
{
get => ms_adjustedMovement;
}
public static bool IKOverrideFly
{
get => ms_ikOverrideFly;
}
} }
} }

View file

@ -181,9 +181,9 @@ function inp_toggle_mod_amt(_obj, _callbackName) {
</div> </div>
<div class ="row-wrapper"> <div class ="row-wrapper">
<div class ="option-caption">IK locomotion override: </div> <div class ="option-caption">IK override while crouching: </div>
<div class ="option-input"> <div class ="option-input">
<div id="IKOverride" class ="inp_toggle no-scroll" data-current="true"></div> <div id="IKOverrideCrouch" class ="inp_toggle no-scroll" data-current="true"></div>
</div> </div>
</div> </div>
@ -195,18 +195,39 @@ function inp_toggle_mod_amt(_obj, _callbackName) {
</div> </div>
<div class ="row-wrapper"> <div class ="row-wrapper">
<div class ="option-caption">Detect pose (regular avatars): </div> <div class ="option-caption">IK override while proning: </div>
<div class ="option-input"> <div class ="option-input">
<div id="DetectPose" class ="inp_toggle no-scroll" data-current="true"></div> <div id="IKOverrideProne" class ="inp_toggle no-scroll" data-current="true"></div>
</div> </div>
</div> </div>
<div class ="row-wrapper"> <div class ="row-wrapper">
<div class ="option-caption">Prone limit (regular avatars): </div> <div class ="option-caption">Prone limit: </div>
<div class ="option-input"> <div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="30"></div> <div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="30"></div>
</div> </div>
</div> </div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Pose transitions: </div>
<div class ="option-input">
<div id="PoseTransitions" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted pose movement speed: </div>
<div class ="option-input">
<div id="AdjustedMovement" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`; `;
document.getElementById('settings-implementation').appendChild(l_block); document.getElementById('settings-implementation').appendChild(l_block);

View file

@ -9,17 +9,19 @@ namespace ml_fpt
public class FourPointTracking : MelonLoader.MelonMod public class FourPointTracking : MelonLoader.MelonMod
{ {
static FourPointTracking ms_instance = null; static FourPointTracking ms_instance = null;
bool m_ready = false;
IndexIK m_indexIk = null; IndexIK m_indexIk = null;
CVR_IK_Calibrator m_ikCalibrator = null; CVR_IK_Calibrator m_ikCalibrator = null;
RootMotion.FinalIK.VRIK m_vrIK = null;
RuntimeAnimatorController m_runtimeAnimator = null;
bool m_inCalibration = false; bool m_calibrationActive = false;
object m_calibrationTask = null;
int m_hipsTrackerIndex = -1; int m_hipsTrackerIndex = -1;
Transform m_hips = null;
RuntimeAnimatorController m_oldRuntimeAnimator = null;
RootMotion.FinalIK.VRIK m_origVrIk = null;
bool m_playerReady = false;
public override void OnApplicationStart() public override void OnApplicationStart()
{ {
@ -35,54 +37,6 @@ namespace ml_fpt
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
} }
public override void OnUpdate()
{
if(m_playerReady && m_inCalibration && (m_hipsTrackerIndex != -1))
{
if(m_origVrIk != null)
m_origVrIk.enabled = false;
m_ikCalibrator.enabled = false;
m_indexIk.enabled = false;
Transform l_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(true, l_hips);
if((CVRInputManager.Instance.interactLeftValue > 0.9f) && (CVRInputManager.Instance.interactRightValue > 0.9f))
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.position = l_hips.position;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.rotation = l_hips.rotation;
if((m_origVrIk != null) && (m_origVrIk.solver?.spine != null))
{
m_origVrIk.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target;
m_origVrIk.solver.spine.pelvisPositionWeight = 1f;
m_origVrIk.solver.spine.pelvisRotationWeight = 1f;
m_origVrIk.solver.OnPreUpdate -= this.OverrideIKWeight;
m_origVrIk.solver.IKPositionWeight = 1f;
m_origVrIk.enabled = true;
}
m_indexIk.enabled = true;
m_ikCalibrator.enabled = true;
PlayerSetup.Instance._animator.runtimeAnimatorController = m_oldRuntimeAnimator;
m_ikCalibrator.leftHandModel.SetActive(false);
m_ikCalibrator.rightHandModel.SetActive(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
CVR_InteractableManager.enableInteractions = true;
if(PlayerSetup.Instance._avatar.GetComponent<ABI.CCK.Components.CVRAvatar>().avatarUsesAdvancedSettings)
PlayerSetup.Instance.LoadCurrentAvatarSettingsDefault();
Reset();
ShowHudNotification("Calibration completed");
}
}
}
System.Collections.IEnumerator WaitForMainMenuView() System.Collections.IEnumerator WaitForMainMenuView()
{ {
while(ViewManager.Instance == null) while(ViewManager.Instance == null)
@ -111,16 +65,16 @@ namespace ml_fpt
m_indexIk = PlayerSetup.Instance.gameObject.GetComponent<IndexIK>(); m_indexIk = PlayerSetup.Instance.gameObject.GetComponent<IndexIK>();
m_ikCalibrator = PlayerSetup.Instance.gameObject.GetComponent<CVR_IK_Calibrator>(); m_ikCalibrator = PlayerSetup.Instance.gameObject.GetComponent<CVR_IK_Calibrator>();
m_playerReady = true; m_ready = true;
} }
void StartCalibration() void StartCalibration()
{ {
if(m_playerReady && !m_inCalibration && PlayerSetup.Instance._inVr && !PlayerSetup.Instance.avatarIsLoading && PlayerSetup.Instance._animator.isHuman && !m_ikCalibrator.inFullbodyCalibration && !m_ikCalibrator.avatarCalibratedAsFullBody) if(m_ready && !m_calibrationActive && PlayerSetup.Instance._inVr && !PlayerSetup.Instance.avatarIsLoading && PlayerSetup.Instance._animator.isHuman && !m_ikCalibrator.inFullbodyCalibration && !m_ikCalibrator.avatarCalibratedAsFullBody)
{ {
for(int i = 0; i < PlayerSetup.Instance._trackerManager.trackerNames.Length; i++) for(int i = 0; i < PlayerSetup.Instance._trackerManager.trackerNames.Length; i++)
{ {
if(PlayerSetup.Instance._trackerManager.trackerNames[i] == "vive_tracker_waist") if((PlayerSetup.Instance._trackerManager.trackerNames[i] == "vive_tracker_waist") && PlayerSetup.Instance._trackerManager.trackers[i].active)
{ {
m_hipsTrackerIndex = i; m_hipsTrackerIndex = i;
break; break;
@ -129,19 +83,22 @@ namespace ml_fpt
if(m_hipsTrackerIndex != -1) if(m_hipsTrackerIndex != -1)
{ {
m_oldRuntimeAnimator = PlayerSetup.Instance._animator.runtimeAnimatorController; m_runtimeAnimator = PlayerSetup.Instance._animator.runtimeAnimatorController;
PlayerSetup.Instance._animator.runtimeAnimatorController = PlayerSetup.Instance.tPoseAnimatorController; PlayerSetup.Instance._animator.runtimeAnimatorController = PlayerSetup.Instance.tPoseAnimatorController;
m_origVrIk = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>(); m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
if(m_origVrIk != null) m_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
m_origVrIk.solver.OnPreUpdate += this.OverrideIKWeight;
if(m_vrIK != null)
m_vrIK.solver.OnPreUpdate += this.OverrideIKWeight;
m_ikCalibrator.leftHandModel.SetActive(true); m_ikCalibrator.leftHandModel.SetActive(true);
m_ikCalibrator.rightHandModel.SetActive(true); m_ikCalibrator.rightHandModel.SetActive(true);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(true); PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(true);
CVR_InteractableManager.enableInteractions = false; CVR_InteractableManager.enableInteractions = false;
m_inCalibration = true; m_calibrationActive = true;
m_calibrationTask = MelonLoader.MelonCoroutines.Start(CalibrationTask());
ViewManager.Instance.ForceUiStatus(false); ViewManager.Instance.ForceUiStatus(false);
ShowHudNotification("Calibration started"); ShowHudNotification("Calibration started");
@ -152,19 +109,71 @@ namespace ml_fpt
else else
ShowMenuAlert("Calibraton requirements aren't met: be in VR, be not in FBT or avatar calibration, humanoid avatar"); ShowMenuAlert("Calibraton requirements aren't met: be in VR, be not in FBT or avatar calibration, humanoid avatar");
} }
void Reset() System.Collections.IEnumerator CalibrationTask()
{ {
m_inCalibration = false; while(m_calibrationActive)
m_hipsTrackerIndex = -1; {
m_oldRuntimeAnimator = null; if(m_vrIK != null)
m_origVrIk = null; m_vrIK.enabled = false;
m_ikCalibrator.enabled = false;
m_indexIk.enabled = false;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(true, m_hips);
if((CVRInputManager.Instance.interactLeftValue > 0.9f) && (CVRInputManager.Instance.interactRightValue > 0.9f))
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.position = m_hips.position;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.rotation = m_hips.rotation;
if(m_vrIK != null)
{
m_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target;
m_vrIK.solver.spine.pelvisPositionWeight = 1f;
m_vrIK.solver.spine.pelvisRotationWeight = 1f;
m_vrIK.solver.OnPreUpdate -= this.OverrideIKWeight;
m_vrIK.solver.IKPositionWeight = 1f;
m_vrIK.enabled = true;
}
m_indexIk.enabled = true;
m_ikCalibrator.enabled = true;
PlayerSetup.Instance._animator.runtimeAnimatorController = m_runtimeAnimator;
m_ikCalibrator.leftHandModel.SetActive(false);
m_ikCalibrator.rightHandModel.SetActive(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration completed");
}
yield return null;
}
m_calibrationTask = null; // Idk if it's safe or not
} }
void OverrideIKWeight() void OverrideIKWeight()
{ {
if(m_inCalibration && (m_origVrIk != null)) if(m_calibrationActive)
m_origVrIk.solver.IKPositionWeight = 0f; {
m_vrIK.solver.IKPositionWeight = 0f;
}
}
void Reset()
{
m_vrIK = null;
m_runtimeAnimator = null;
m_calibrationActive = false;
m_calibrationTask = null;
m_hipsTrackerIndex = -1;
m_hips = null;
} }
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear(); static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
@ -172,8 +181,11 @@ namespace ml_fpt
{ {
try try
{ {
if(m_inCalibration) if(m_calibrationActive)
{ {
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_indexIk.enabled = true; m_indexIk.enabled = true;
m_ikCalibrator.enabled = true; m_ikCalibrator.enabled = true;

View file

@ -1,10 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyTitle("FourPointTracking")] [assembly: AssemblyTitle("FourPointTracking")]
[assembly: AssemblyVersion("1.0.4")] [assembly: AssemblyVersion("1.0.5")]
[assembly: AssemblyFileVersion("1.0.4")] [assembly: AssemblyFileVersion("1.0.5")]
[assembly: MelonLoader.MelonInfo(typeof(ml_fpt.FourPointTracking), "FourPointTracking", "1.0.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_fpt.FourPointTracking), "FourPointTracking", "1.0.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]