Moved PlayerRagdollMod and DesktopHeadTracking to archived

Fixed for r173 game build
This commit is contained in:
SDraw 2023-11-10 02:12:12 +03:00
parent a232c2ce13
commit 9886bdc154
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
43 changed files with 3490 additions and 3600 deletions

View file

@ -1,120 +1,118 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections;
using System.Reflection;
namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static AvatarMotionTweaker ms_instance = null;
MotionTweaker m_localTweaker = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
ModSupporter.Init();
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_localTweaker = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnAvatarClear();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnSetupAvatar();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnCalibrate();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnPlayspaceScale_Postfix() => ms_instance?.OnPlayspaceScale();
void OnPlayspaceScale()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnPlayspaceScale();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections;
using System.Reflection;
namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static AvatarMotionTweaker ms_instance = null;
MotionTweaker m_localTweaker = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_localTweaker = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnAvatarClear();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnSetupAvatar();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnCalibrate();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnPlayspaceScale_Postfix() => ms_instance?.OnPlayspaceScale();
void OnPlayspaceScale()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnPlayspaceScale();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,49 +0,0 @@
using System.Collections;
using System.Linq;
namespace ml_amt
{
static class ModSupporter
{
static bool ms_ragdollMod = false;
static bool ms_copycatMod = false;
public static void Init()
{
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerRagdollMod") != null)
MelonLoader.MelonCoroutines.Start(WaitForRagdollInstance());
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerMovementCopycat") != null)
MelonLoader.MelonCoroutines.Start(WaitForCopycatInstance());
}
// PlayerRagdollMod support
static IEnumerator WaitForRagdollInstance()
{
while(ml_prm.RagdollController.Instance == null)
yield return null;
ms_ragdollMod = true;
}
static bool IsRagdolled() => ml_prm.RagdollController.Instance.IsRagdolled();
// 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();
public static bool SkipHipsOverride()
{
bool l_result = false;
l_result |= (ms_ragdollMod && IsRagdolled());
l_result |= (ms_copycatMod && IsCopycating());
return l_result;
}
}
}

View file

@ -1,345 +1,316 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Collections.Generic;
using UnityEngine;
namespace ml_amt
{
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
struct IKState
{
public float m_weight;
public float m_locomotionWeight;
public bool m_plantFeet;
public bool m_bendNormalLeft;
public bool m_bendNormalRight;
}
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
IKState m_ikState;
VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
Transform m_avatarHips = null;
bool m_inVR = false;
bool m_avatarReady = false;
bool m_grounded = false;
bool m_groundedRaw = false;
bool m_moving = false;
bool m_locomotionOverride = false;
bool m_ikOverrideFly = true;
bool m_ikOverrideJump = true;
bool m_detectEmotes = true;
bool m_emoteActive = false;
bool m_followHips = true;
Vector3 m_hipsToPlayer = Vector3.zero;
Vector3 m_massCenter = Vector3.zero;
Transform m_ikLimits = null;
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
{
m_parameters = new List<AvatarParameter>();
}
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
SetIKOverrideFly(Settings.IKOverrideFly);
SetIKOverrideJump(Settings.IKOverrideJump);
SetDetectEmotes(Settings.DetectEmotes);
SetFollowHips(Settings.FollowHips);
Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.FollowHipsChange += this.SetFollowHips;
Settings.MassCenterChange += this.OnMassCenterChange;
}
void OnDestroy()
{
Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.FollowHipsChange -= this.SetFollowHips;
Settings.MassCenterChange -= this.OnMassCenterChange;
}
void Update()
{
if(m_avatarReady)
{
m_grounded = MovementSystem.Instance.IsGrounded();
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
UpdateIKLimits();
if(m_avatarHips != null)
{
Vector4 l_hipsToPoint = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_avatarHips.GetMatrix()) * ms_pointVector;
m_hipsToPlayer.Set(l_hipsToPoint.x, 0f, l_hipsToPoint.z);
}
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
AnimatorStateInfo l_animState = PlayerSetup.Instance._animator.GetCurrentAnimatorStateInfo(m_locomotionLayer);
m_emoteActive = (l_animState.tagHash == ms_emoteHash);
}
if(m_parameters.Count > 0)
{
foreach(AvatarParameter l_param in m_parameters)
l_param.Update(this);
}
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIk = null;
m_locomotionLayer = -1;
m_grounded = false;
m_groundedRaw = false;
m_avatarReady = false;
m_avatarScale = 1f;
m_locomotionOffset = Vector3.zero;
m_emoteActive = false;
m_moving = false;
m_locomotionOverride = false;
m_hipsToPlayer = Vector3.zero;
m_avatarHips = null;
m_massCenter = Vector3.zero;
m_ikLimits = null;
m_parameters.Clear();
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(Settings.CrouchLimit);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(Settings.ProneLimit);
}
internal void OnSetupAvatar()
{
m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarHips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Upright, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid());
// Avatar custom IK limits
m_ikLimits = PlayerSetup.Instance._avatar.transform.Find("[IKLimits]");
UpdateIKLimits();
// Apply VRIK tweaks
if(m_vrIk != null)
{
m_locomotionOffset = m_vrIk.solver.locomotion.offset;
m_massCenter = m_locomotionOffset;
if(m_vrIk.solver.HasToes())
{
Transform l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftFoot);
if(l_foot == null)
l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightFoot);
Transform l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftToes);
if(l_toe == null)
l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightToes);
if((l_foot != null) && (l_toe != null))
{
Vector3 l_footPos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_foot.GetMatrix()) * ms_pointVector;
Vector3 l_toePos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_toe.GetMatrix()) * ms_pointVector;
m_massCenter = new Vector3(0f, 0f, l_toePos.z - l_footPos.z);
}
}
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
m_vrIk.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIk.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
m_avatarReady = true;
}
internal void OnCalibrate()
{
if(m_avatarReady && (m_vrIk != null) && (m_vrIk.solver.spine.pelvisTarget != null) && (m_vrIk.solver.leftLeg.target == null) && (m_vrIk.solver.rightLeg.target == null))
{
// Do not consider 4PT as FBT (!!!)
m_vrIk.solver.spine.bodyPosStiffness = 0.55f;
m_vrIk.solver.spine.bodyRotStiffness = 0.1f;
m_vrIk.solver.spine.neckStiffness = 0.5f;
m_vrIk.solver.spine.chestClampWeight = 0.55f;
m_vrIk.solver.spine.moveBodyBackWhenCrouching = 0.5f;
m_vrIk.solver.spine.maxRootAngle = 25f;
m_vrIk.fixTransforms = false;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true;
IKSystem.Instance.applyOriginalHipRotation = true;
}
}
internal void OnPlayspaceScale()
{
if((m_vrIk != null) && Settings.MassCenter)
m_vrIk.solver.locomotion.offset = m_massCenter * GetRelativeScale();
}
// IK events
void OnIKPreUpdate()
{
bool l_locomotionOverride = false;
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;
if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.IKPositionWeight = 0f;
if(!BodySystem.isCalibratedAsFullBody)
{
if(PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarCrouchLimit)
{
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideFly && MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideJump && !m_grounded && !MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
}
bool l_solverActive = !Mathf.Approximately(m_vrIk.solver.IKPositionWeight, 0f);
if(l_locomotionOverride && l_solverActive && (m_followHips && !MovementSystem.Instance.sitting) && (!m_moving || (PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarCrouchLimit)) && m_inVR && !BodySystem.isCalibratedAsFullBody && !ModSupporter.SkipHipsOverride())
{
m_vrIk.solver.plantFeet = false;
if((IKSystem.VrikRootController != null) && !MovementSystem.Instance.sitting)
IKSystem.VrikRootController.enabled = false;
PlayerSetup.Instance._avatar.transform.localPosition = m_hipsToPlayer;
}
if(m_locomotionOverride && !l_locomotionOverride)
{
m_vrIk.solver.Reset();
if((IKSystem.VrikRootController != null) && !MovementSystem.Instance.sitting)
IKSystem.VrikRootController.enabled = true;
}
m_locomotionOverride = l_locomotionOverride;
}
void OnIKPostUpdate()
{
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;
}
// Settings
internal void SetCrouchLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(p_value);
}
internal void SetProneLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(p_value);
}
internal void SetIKOverrideFly(bool p_state)
{
m_ikOverrideFly = p_state;
}
internal void SetIKOverrideJump(bool p_state)
{
m_ikOverrideJump = p_state;
}
internal void SetDetectEmotes(bool p_state)
{
m_detectEmotes = p_state;
}
internal void SetFollowHips(bool p_state)
{
m_followHips = p_state;
}
void OnMassCenterChange(bool p_state)
{
if(m_vrIk != null)
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? (m_massCenter * GetRelativeScale()) : m_locomotionOffset);
}
// Arbitrary
float GetRelativeScale()
{
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
}
void UpdateIKLimits()
{
if(m_ikLimits != null)
{
Vector3 l_values = m_ikLimits.localPosition;
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(l_values.x);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(l_values.y);
}
}
// Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw;
public bool GetMoving() => m_moving;
}
}
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Collections.Generic;
using UnityEngine;
namespace ml_amt
{
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
struct IKState
{
public float m_weight;
public float m_locomotionWeight;
public bool m_plantFeet;
public bool m_bendNormalLeft;
public bool m_bendNormalRight;
}
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
IKState m_ikState;
VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
bool m_inVR = false;
bool m_avatarReady = false;
bool m_grounded = false;
bool m_groundedRaw = false;
bool m_moving = false;
bool m_locomotionOverride = false;
bool m_ikOverrideFly = true;
bool m_ikOverrideJump = true;
bool m_detectEmotes = true;
bool m_emoteActive = false;
Vector3 m_massCenter = Vector3.zero;
Transform m_ikLimits = null;
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
{
m_parameters = new List<AvatarParameter>();
}
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
SetIKOverrideFly(Settings.IKOverrideFly);
SetIKOverrideJump(Settings.IKOverrideJump);
SetDetectEmotes(Settings.DetectEmotes);
Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.MassCenterChange += this.OnMassCenterChange;
}
void OnDestroy()
{
Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.MassCenterChange -= this.OnMassCenterChange;
}
void Update()
{
if(m_avatarReady)
{
m_grounded = MovementSystem.Instance.IsGrounded();
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
UpdateIKLimits();
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
AnimatorStateInfo l_animState = PlayerSetup.Instance._animator.GetCurrentAnimatorStateInfo(m_locomotionLayer);
m_emoteActive = (l_animState.tagHash == ms_emoteHash);
}
if(m_parameters.Count > 0)
{
foreach(AvatarParameter l_param in m_parameters)
l_param.Update(this);
}
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIk = null;
m_locomotionLayer = -1;
m_grounded = false;
m_groundedRaw = false;
m_avatarReady = false;
m_avatarScale = 1f;
m_locomotionOffset = Vector3.zero;
m_emoteActive = false;
m_moving = false;
m_locomotionOverride = false;
m_massCenter = Vector3.zero;
m_ikLimits = null;
m_parameters.Clear();
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(Settings.CrouchLimit);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(Settings.ProneLimit);
}
internal void OnSetupAvatar()
{
m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Upright, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid());
// Avatar custom IK limits
m_ikLimits = PlayerSetup.Instance._avatar.transform.Find("[IKLimits]");
UpdateIKLimits();
// Apply VRIK tweaks
if(m_vrIk != null)
{
m_locomotionOffset = m_vrIk.solver.locomotion.offset;
m_massCenter = m_locomotionOffset;
if(m_vrIk.solver.HasToes())
{
Transform l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftFoot);
if(l_foot == null)
l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightFoot);
Transform l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftToes);
if(l_toe == null)
l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightToes);
if((l_foot != null) && (l_toe != null))
{
Vector3 l_footPos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_foot.GetMatrix()) * ms_pointVector;
Vector3 l_toePos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_toe.GetMatrix()) * ms_pointVector;
m_massCenter = new Vector3(0f, 0f, l_toePos.z - l_footPos.z);
}
}
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
m_vrIk.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIk.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
m_avatarReady = true;
}
internal void OnCalibrate()
{
if(m_avatarReady && (m_vrIk != null) && (m_vrIk.solver.spine.pelvisTarget != null) && (m_vrIk.solver.leftLeg.target == null) && (m_vrIk.solver.rightLeg.target == null))
{
// Do not consider 4PT as FBT (!!!)
m_vrIk.solver.spine.bodyPosStiffness = 0.55f;
m_vrIk.solver.spine.bodyRotStiffness = 0.1f;
m_vrIk.solver.spine.neckStiffness = 0.5f;
m_vrIk.solver.spine.chestClampWeight = 0.55f;
m_vrIk.solver.spine.moveBodyBackWhenCrouching = 0.5f;
m_vrIk.solver.spine.maxRootAngle = 25f;
m_vrIk.fixTransforms = false;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true;
IKSystem.Instance.applyOriginalHipRotation = true;
}
}
internal void OnPlayspaceScale()
{
if((m_vrIk != null) && Settings.MassCenter)
m_vrIk.solver.locomotion.offset = m_massCenter * GetRelativeScale();
}
// IK events
void OnIKPreUpdate()
{
bool l_locomotionOverride = false;
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;
if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.IKPositionWeight = 0f;
if(!BodySystem.isCalibratedAsFullBody)
{
if(PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarCrouchLimit)
{
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideFly && MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideJump && !m_grounded && !MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
}
if(m_locomotionOverride && !l_locomotionOverride)
{
m_vrIk.solver.Reset();
if((IKSystem.VrikRootController != null) && !MovementSystem.Instance.sitting)
IKSystem.VrikRootController.enabled = true;
}
m_locomotionOverride = l_locomotionOverride;
}
void OnIKPostUpdate()
{
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;
}
// Settings
internal void SetCrouchLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(p_value);
}
internal void SetProneLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(p_value);
}
internal void SetIKOverrideFly(bool p_state)
{
m_ikOverrideFly = p_state;
}
internal void SetIKOverrideJump(bool p_state)
{
m_ikOverrideJump = p_state;
}
internal void SetDetectEmotes(bool p_state)
{
m_detectEmotes = p_state;
}
void OnMassCenterChange(bool p_state)
{
if(m_vrIk != null)
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? (m_massCenter * GetRelativeScale()) : m_locomotionOffset);
}
// Arbitrary
float GetRelativeScale()
{
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
}
void UpdateIKLimits()
{
if(m_ikLimits != null)
{
Vector3 l_values = m_ikLimits.localPosition;
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(l_values.x);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(l_values.y);
}
}
// Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw;
public bool GetMoving() => m_moving;
}
}

View file

@ -1,5 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.3.3", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_prm", "ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.3.4", "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,36 +1,33 @@
# Avatar Motion Tweaker
This mod adds features for AAS animator and avatar locomotion behaviour.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_amt.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Crouch limit:** defines crouch limit; default value - `75`.
* **Prone limit:** defines prone limit; default value - `40`.
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Follow hips on IK override:** adjusts avatar position to overcome animation snapping on IK override; default value - `true`.
* Note: Works best with animations that have root transform position (XZ) based on center of mass.
* Note: Made for four point tracking (head, hands and hips) in mind.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
Available additional parameters for AAS animator:
* **`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: Shouldn't be used for transitions between poses in desktop mode. In desktop mode its value is driven by avatar animations. Use `CVR Parameter Stream` for detecting desktop/VR modes and change AAS animator transitions accordingly.
* **`GroundedRaw`:** defines instant grounding state of player instead of delayed default parameter `Grounded`; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
* **`Moving`:** defines movement state of player; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
# Avatar Motion Tweaker
This mod adds features for AAS animator and avatar locomotion behaviour.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_amt.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Crouch limit:** defines crouch limit; default value - `75`.
* **Prone limit:** defines prone limit; default value - `40`.
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
Available additional parameters for AAS animator:
* **`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: Shouldn't be used for transitions between poses in desktop mode. In desktop mode its value is driven by avatar animations. Use `CVR Parameter Stream` for detecting desktop/VR modes and change AAS animator transitions accordingly.
* **`GroundedRaw`:** defines instant grounding state of player instead of delayed default parameter `Grounded`; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
* **`Moving`:** defines movement state of player; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
* Avatars can have controlled IK crouch and prone limits. For that create `[IKLimits]` GameObject parented to avatar's root. Its local X and Y positions will be used as crouch and prone limits respectively and can be changed via animations. Values should be in range of [0;1].

View file

@ -1,159 +1,148 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_amt
{
static class Settings
{
enum ModSetting
{
CrouchLimit,
ProneLimit,
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
FollowHips,
MassCenter
};
public static float CrouchLimit { get; private set; } = 0.75f;
public static float ProneLimit { get; private set; } = 0.4f;
public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true;
public static bool FollowHips { get; private set; } = true;
public static bool MassCenter { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<float> CrouchLimitChange;
static public event Action<float> ProneLimitChange;
static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> FollowHipsChange;
static public event Action<bool> MassCenterChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), (int)(CrouchLimit * 100f)),
ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), (int)(ProneLimit * 100f)),
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.FollowHips.ToString(), FollowHips),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter)
};
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
ProneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f;
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
FollowHips = (bool)ms_entries[(int)ModSetting.FollowHips].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].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("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
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 OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.CrouchLimit:
{
CrouchLimit = int.Parse(p_value) * 0.01f;
CrouchLimitChange?.Invoke(CrouchLimit);
}
break;
case ModSetting.ProneLimit:
{
ProneLimit = int.Parse(p_value) * 0.01f;
ProneLimitChange?.Invoke(ProneLimit);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.IKOverrideFly:
{
IKOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(IKOverrideFly);
}
break;
case ModSetting.IKOverrideJump:
{
IKOverrideJump = bool.Parse(p_value);
IKOverrideJumpChange?.Invoke(IKOverrideJump);
}
break;
case ModSetting.DetectEmotes:
{
DetectEmotes = bool.Parse(p_value);
DetectEmotesChange?.Invoke(DetectEmotes);
}
break;
case ModSetting.FollowHips:
{
FollowHips = bool.Parse(p_value);
FollowHipsChange?.Invoke(FollowHips);
}
break;
case ModSetting.MassCenter:
{
MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_amt
{
static class Settings
{
enum ModSetting
{
CrouchLimit,
ProneLimit,
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
FollowHips,
MassCenter
};
public static float CrouchLimit { get; private set; } = 0.75f;
public static float ProneLimit { get; private set; } = 0.4f;
public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true;
public static bool MassCenter { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<float> CrouchLimitChange;
static public event Action<float> ProneLimitChange;
static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> MassCenterChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), (int)(CrouchLimit * 100f)),
ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), (int)(ProneLimit * 100f)),
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter)
};
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
ProneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f;
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].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("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
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 OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.CrouchLimit:
{
CrouchLimit = int.Parse(p_value) * 0.01f;
CrouchLimitChange?.Invoke(CrouchLimit);
}
break;
case ModSetting.ProneLimit:
{
ProneLimit = int.Parse(p_value) * 0.01f;
ProneLimitChange?.Invoke(ProneLimit);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.IKOverrideFly:
{
IKOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(IKOverrideFly);
}
break;
case ModSetting.IKOverrideJump:
{
IKOverrideJump = bool.Parse(p_value);
IKOverrideJumpChange?.Invoke(IKOverrideJump);
}
break;
case ModSetting.DetectEmotes:
{
DetectEmotes = bool.Parse(p_value);
DetectEmotesChange?.Invoke(DetectEmotes);
}
break;
case ModSetting.MassCenter:
{
MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

View file

@ -1,95 +1,87 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.3.3</Version>
<Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="AvatarMotionTweaker.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="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_prm">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_prm.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.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.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>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.3.4</Version>
<Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="AvatarMotionTweaker.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.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.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,68 +1,61 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></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">IK override while jumping: </div>
<div class ="option-input">
<div id="IKOverrideJump" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Follow hips on IK override: </div>
<div class ="option-input">
<div id="FollowHips" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('AMT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_AMT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('AMT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_AMT'));
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></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">Follow hips on IK override: </div>
<div class ="option-input">
<div id="FollowHips" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('AMT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_AMT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('AMT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_AMT'));
}