Move many mods to Deprecated folder, fix spelling

This commit is contained in:
NotAKidoS 2025-04-03 02:57:35 -05:00
parent 5e822cec8d
commit 0042590aa6
539 changed files with 7475 additions and 3120 deletions

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn />
</PropertyGroup>
<ItemGroup>
<Reference Include="BTKUILib">
<HintPath>$(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,161 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using HarmonyLib;
using NAK.DesktopVRIK.IK;
using UnityEngine;
/**
The process of calibrating VRIK is fucking painful.
Avatars of Note:
TurtleNeck Ferret- close feet, far shoulders, nonideal rig.
Space Robot Kyle- the worst bone rolls on the planet, tpose/headikcalibration fixed it mostly... ish.
Exteratta- knees bend backwards without proper tpose.
Chito- left foot is far back without proper tpose & foot ik distance, was uploaded in falling anim state.
Atlas (portal2)- Wide stance, proper feet distance needed to be calculated.
Freddy (gmod)- Doesn't have any fingers, wristToPalmAxis & palmToThumbAxis needed to be set manually.
Small Cheese- Emotes are angled wrong due to maxRootAngle..???
Custom knee bend normal is needed for avatars that scale incredibly small. Using animated knee bend will cause
knees to bend in completely wrong directions. We turn it off though when in crouch/prone, as it can bleed
into animations.
Most other avatars play just fine.
**/
namespace NAK.DesktopVRIK.HarmonyPatches;
internal class PlayerSetupPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
{
__instance.gameObject.AddComponent<IKManager>();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatarDesktop))]
private static void Postfix_PlayerSetup_SetupAvatarDesktop(ref PlayerSetup __instance)
{
if (!ModSettings.EntryEnabled.Value)
return;
try
{
IKManager.Instance?.OnAvatarInitialized(__instance._avatar);
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatarDesktop)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))]
private static void Postfix_PlayerSetup_ClearAvatar()
{
try
{
IKManager.Instance?.OnAvatarDestroyed();
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_ClearAvatar)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupIKScaling))]
private static void Prefix_PlayerSetup_SetupIKScaling(ref Vector3 ___scaleDifference, ref bool __runOriginal)
{
try
{
if (IKManager.Instance != null)
__runOriginal = !IKManager.Instance.OnPlayerScaled(1f + ___scaleDifference.y);
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_SetupIKScaling)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetSitting))]
private static void Postfix_PlayerSetup_SetSitting(ref bool ___isCurrentlyInSeat)
{
try
{
IKManager.Instance?.OnPlayerSeatedStateChanged(___isCurrentlyInSeat);
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetSitting)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(IKSystem), nameof(IKSystem.OffsetMovementParent))]
private static void Prefix_IKSystem_OffsetMovementParent(CVRMovementParent currentParent, ref IKSystem __instance, ref bool __runOriginal)
{
try
{
__runOriginal = true;
if (IKManager.Instance == null || !IKManager.Instance.IsAvatarInitialized())
return;
if (currentParent != null && currentParent._referencePoint != null)
__runOriginal = IKManager.Instance.OnPlayerHandleMovementParent(currentParent);
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Prefix_IKSystem_OffsetMovementParent)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ResetIk))]
private static void Prefix_PlayerSetup_ResetIk(ref PlayerSetup __instance, ref bool __runOriginal)
{
try
{
__runOriginal = true;
if (IKManager.Instance == null || !IKManager.Instance.IsAvatarInitialized())
return;
CVRMovementParent currentParent = __instance._movementSystem._currentParent;
if (currentParent != null && currentParent._referencePoint != null)
__runOriginal = IKManager.Instance.OnPlayerHandleMovementParent(currentParent);
else
__runOriginal = IKManager.Instance.OnPlayerTeleported();
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Prefix_PlayerSetup_ResetIk)}");
DesktopVRIK.Logger.Error(e);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Update))]
private static void Postfix_PlayerSetup_Update()
{
try
{
IKManager.Instance?.OnPlayerUpdate();
}
catch (Exception e)
{
DesktopVRIK.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_Update)}");
DesktopVRIK.Logger.Error(e);
}
}
}

View file

@ -0,0 +1,231 @@
using RootMotion.FinalIK;
using UnityEngine;
using Object = UnityEngine.Object;
namespace NAK.DesktopVRIK.IK;
internal static class IKCalibrator
{
#region VRIK Solver Setup
public static VRIK SetupVrIk(Animator animator)
{
if (animator.gameObject.TryGetComponent(out VRIK vrik))
Object.DestroyImmediate(vrik);
vrik = animator.gameObject.AddComponent<VRIK>();
vrik.AutoDetectReferences();
if (!ModSettings.EntryUseToesForVRIK.Value)
{
vrik.references.leftToes = null;
vrik.references.rightToes = null;
}
vrik.solver.SetToReferences(vrik.references);
GuessWristPalmAxis(vrik.references.leftHand, vrik.references.leftForearm, vrik.solver.leftArm);
GuessWristPalmAxis(vrik.references.rightHand, vrik.references.rightForearm, vrik.solver.rightArm);
SafePalmToThumbAxis(vrik.references.leftHand, vrik.references.leftForearm, vrik.solver.leftArm,
animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal));
SafePalmToThumbAxis(vrik.references.rightHand, vrik.references.rightForearm, vrik.solver.rightArm,
animator.GetBoneTransform(HumanBodyBones.RightThumbProximal));
AddTwistRelaxer(vrik.references.leftForearm, vrik, vrik.references.leftHand);
AddTwistRelaxer(vrik.references.rightForearm, vrik, vrik.references.rightHand);
//vrik.solver.leftArm.shoulderRotationMode = (IKSolverVR.Arm.ShoulderRotationMode)IkTweaksSettings.ShoulderMode;
//vrik.solver.rightArm.shoulderRotationMode = (IKSolverVR.Arm.ShoulderRotationMode)IkTweaksSettings.ShoulderMode;
// zero all weights controlled by BodyControl
vrik.solver.locomotion.weight = 0f;
vrik.solver.IKPositionWeight = 0f;
vrik.solver.spine.pelvisPositionWeight = 0f;
vrik.solver.spine.pelvisRotationWeight = 0f;
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.rotationWeight = 0f;
vrik.solver.leftLeg.positionWeight = 0f;
vrik.solver.leftLeg.rotationWeight = 0f;
vrik.solver.rightLeg.positionWeight = 0f;
vrik.solver.rightLeg.rotationWeight = 0f;
vrik.solver.leftArm.positionWeight = 0f;
vrik.solver.leftArm.rotationWeight = 0f;
vrik.solver.rightArm.positionWeight = 0f;
vrik.solver.rightArm.rotationWeight = 0f;
vrik.solver.leftLeg.bendGoalWeight = 0f;
vrik.solver.rightLeg.bendGoalWeight = 0f;
// these weights are fine
vrik.solver.leftArm.shoulderRotationWeight = 0.8f;
vrik.solver.rightArm.shoulderRotationWeight = 0.8f;
vrik.solver.leftLeg.bendToTargetWeight = 0.75f;
vrik.solver.rightLeg.bendToTargetWeight = 0.75f;
// hack to prevent death
vrik.fixTransforms = !animator.enabled;
// Avatar Motion Tweaker uses this hack!
vrik.solver.leftLeg.useAnimatedBendNormal = false;
vrik.solver.rightLeg.useAnimatedBendNormal = false;
// purposefully initiating early
vrik.solver.Initiate(vrik.transform);
vrik.solver.Reset();
return vrik;
}
private static void GuessWristPalmAxis(Transform hand, Transform forearm, IKSolverVR.Arm arm)
{
arm.wristToPalmAxis = VRIKCalibrator.GuessWristToPalmAxis(
hand,
forearm
);
}
private static void SafePalmToThumbAxis(Transform hand, Transform forearm, IKSolverVR.Arm arm, Transform thumbBone = null)
{
if (hand.childCount == 0)
{
arm.palmToThumbAxis = Vector3.one;
return;
}
arm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(
hand,
forearm,
thumbBone
);
}
private static void AddTwistRelaxer(Transform forearm, VRIK ik, Transform hand)
{
if (forearm == null) return;
TwistRelaxer twistRelaxer = forearm.gameObject.AddComponent<TwistRelaxer>();
twistRelaxer.ik = ik;
twistRelaxer.weight = 0.5f;
twistRelaxer.child = hand;
twistRelaxer.parentChildCrossfade = 0.8f;
}
#endregion
#region VRIK Configuration
public static void ConfigureDesktopVrIk(VRIK vrik)
{
// From DesktopVRIK
// https://github.com/NotAKidoS/NAK_CVR_Mods/blob/fca0a32257311f044d1a9d6e68269baa4a65a45c/DesktopVRIK/DesktopVRIKCalibrator.cs#L219C2-L247C103
vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0.2f;
vrik.solver.spine.neckStiffness = 0.0001f;
vrik.solver.spine.rotateChestByHands = 0f;
vrik.solver.spine.minHeadHeight = 0f;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 1f;
vrik.solver.spine.chestClampWeight = 0f;
vrik.solver.spine.headClampWeight = 0.2f;
vrik.solver.spine.maintainPelvisPosition = 0f;
vrik.solver.spine.moveBodyBackWhenCrouching = 0f;
vrik.solver.locomotion.velocityFactor = 0f;
vrik.solver.locomotion.maxVelocity = 0f;
vrik.solver.locomotion.rootSpeed = 1000f;
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.rotationWeight = 1f;
vrik.solver.spine.maxRootAngle = 180f;
vrik.solver.plantFeet = true;
}
public static void ConfigureHalfBodyVrIk(VRIK vrik)
{
// From IKTweaks
// https://github.com/knah/VRCMods/blob/a22bb73a5e40c75152c6e5db2a7a9afb13e42ba5/IKTweaks/FullBodyHandling.cs#L384C1-L394C71
vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0f;
vrik.solver.spine.neckStiffness = 0.5f;
vrik.solver.spine.rotateChestByHands = .25f;
vrik.solver.spine.minHeadHeight = -100f;
vrik.solver.locomotion.angleThreshold = 60f;
vrik.solver.locomotion.maxLegStretch = 1f;
vrik.solver.spine.chestClampWeight = 0f;
vrik.solver.spine.headClampWeight = 0f;
vrik.solver.spine.maintainPelvisPosition = 0f;
vrik.solver.spine.moveBodyBackWhenCrouching = 0f;
vrik.solver.locomotion.velocityFactor = 0.4f;
vrik.solver.locomotion.maxVelocity = 0.4f;
vrik.solver.locomotion.rootSpeed = 20f;
vrik.solver.spine.positionWeight = 1f;
vrik.solver.spine.rotationWeight = 1f;
vrik.solver.spine.maxRootAngle = 25f;
vrik.solver.plantFeet = false;
}
#endregion
#region VRIK Calibration
public static void SetupHeadIKTarget(VRIK vrik, Transform parent = null)
{
Transform existingTarget = parent?.Find("Head IK Target");
if (existingTarget != null)
Object.DestroyImmediate(existingTarget.gameObject);
parent ??= vrik.references.head;
vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
vrik.solver.spine.headTarget.SetParent(parent);
vrik.solver.spine.headTarget.localPosition = Vector3.zero;
vrik.solver.spine.headTarget.localRotation = parent == vrik.references.head
? Quaternion.identity
: CalculateLocalRotation(vrik.references.root, vrik.references.head);
}
public static void SetupHandIKTarget(VRIK vrik, Transform handAnchor, bool isLeft)
{
Transform parent = handAnchor.parent;
Transform handRef = isLeft ? vrik.references.leftHand : vrik.references.rightHand;
handAnchor.SetParent(parent);
handAnchor.localPosition = Vector3.zero;
handAnchor.localRotation = CalculateLocalRotation(vrik.references.root, handRef);
if (isLeft)
vrik.solver.leftArm.target = handAnchor;
else
vrik.solver.rightArm.target = handAnchor;
}
#endregion
#region Private Methods
private static Quaternion CalculateLocalRotation(Transform root, Transform reference)
{
Vector3 forward = Quaternion.Inverse(reference.rotation) * root.forward;
Vector3 upwards = Quaternion.Inverse(reference.rotation) * root.up;
return Quaternion.Inverse(reference.rotation * Quaternion.LookRotation(forward, upwards)) * reference.rotation;
}
#endregion
}

View file

@ -0,0 +1,163 @@
using ABI_RC.Core.InteractionSystem;
using ABI.CCK.Components;
using ABI_RC.Systems.IK.SubSystems;
using NAK.DesktopVRIK.IK.VRIKHelpers;
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.DesktopVRIK.IK.IKHandlers;
internal abstract class IKHandler
{
#region Variables
internal VRIK _vrik;
internal IKSolverVR _solver;
// VRIK Calibration Info
internal VRIKLocomotionData _locomotionData;
// Last Movement Parent Info
internal Vector3 _movementPosition;
internal Quaternion _movementRotation;
internal CVRMovementParent _movementParent;
// Solver Info
internal float _scaleDifference = 1f;
internal float _ikWeight = 1f;
internal float _locomotionWeight = 1f;
internal float _ikSimulatedRootAngle;
internal bool _wasTrackingLocomotion;
#endregion
#region Virtual Game Methods
public virtual void OnInitializeIk() { }
public virtual void OnPlayerScaled(float scaleDifference)
{
VRIKUtils.ApplyScaleToVRIK
(
_vrik,
_locomotionData,
_scaleDifference = scaleDifference
);
}
public virtual void OnPlayerHandleMovementParent(CVRMovementParent currentParent, Vector3 platformPivot)
{
Vector3 currentPosition = currentParent._referencePoint.position;
Quaternion currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f);
if (_movementParent != null && _movementParent == currentParent)
{
Vector3 deltaPosition = currentPosition - _movementPosition;
Quaternion deltaRotation = Quaternion.identity;
if (currentParent.orientationMode == CVRMovementParent.OrientationMode.RotateWithParent)
deltaRotation = Quaternion.Inverse(_movementRotation) * currentRotation;
_solver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
_ikSimulatedRootAngle = Mathf.Repeat(_ikSimulatedRootAngle + deltaRotation.eulerAngles.y, 360f);
}
_movementParent = currentParent;
_movementPosition = currentPosition;
_movementRotation = currentRotation;
}
#endregion
#region Virtual IK Weights
public virtual void UpdateWeights()
{
Update_HeadWeight();
Update_LeftArmWeight();
Update_RightArmWeight();
Update_LeftLegWeight();
Update_RightLegWeight();
Update_PelvisWeight();
Update_LocomotionWeight();
Update_IKPositionWeight();
}
public virtual void UpdateTracking() { }
protected virtual void Update_HeadWeight()
{
// There is no Head tracking setting
_solver.spine.rotationWeight = _solver.spine.positionWeight =
GetTargetWeight(BodySystem.TrackingEnabled, _solver.spine.headTarget != null);
}
protected virtual void Update_LeftArmWeight()
{
_solver.leftArm.rotationWeight = _solver.leftArm.positionWeight =
GetTargetWeight(BodySystem.TrackingLeftArmEnabled, _solver.leftArm.target != null);
}
protected virtual void Update_RightArmWeight()
{
_solver.rightArm.rotationWeight = _solver.rightArm.positionWeight =
GetTargetWeight(BodySystem.TrackingRightArmEnabled, _solver.rightArm.target != null);
}
protected virtual void Update_LeftLegWeight()
{
_solver.leftLeg.rotationWeight = _solver.leftLeg.positionWeight =
GetTargetWeight(BodySystem.TrackingLeftLegEnabled, _solver.leftLeg.target != null);
}
protected virtual void Update_RightLegWeight()
{
_solver.rightLeg.rotationWeight = _solver.rightLeg.positionWeight =
GetTargetWeight(BodySystem.TrackingRightLegEnabled, _solver.rightLeg.target != null);
}
protected virtual void Update_PelvisWeight()
{
// There is no Pelvis tracking setting
_solver.spine.pelvisRotationWeight = _solver.spine.pelvisPositionWeight =
GetTargetWeight(BodySystem.TrackingEnabled, _solver.spine.pelvisTarget != null);
}
protected virtual void Update_LocomotionWeight()
{
_solver.locomotion.weight = _locomotionWeight = Mathf.Lerp(_locomotionWeight, BodySystem.TrackingLocomotionEnabled ? 1f : 0f,
Time.deltaTime * ModSettings.EntryIKLerpSpeed.Value * 2f);
}
protected virtual void Update_IKPositionWeight()
{
_solver.IKPositionWeight = _ikWeight = Mathf.Lerp(_ikWeight, BodySystem.TrackingEnabled ? BodySystem.TrackingPositionWeight : 0f,
Time.deltaTime * ModSettings.EntryIKLerpSpeed.Value);
}
public virtual void Reset()
{
_ikSimulatedRootAngle = _vrik.transform.eulerAngles.y;
_solver.Reset();
if(ModSettings.EntryResetFootstepsOnIdle.Value)
VRIKUtils.ResetToInitialFootsteps(_vrik, _locomotionData, _scaleDifference);
}
#endregion
#region Private Methods
private float GetTargetWeight(bool isTracking, bool hasTarget)
{
return isTracking && hasTarget ? 1f : 0f;
}
#endregion
}

View file

@ -0,0 +1,126 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using NAK.DesktopVRIK.IK.VRIKHelpers;
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.DesktopVRIK.IK.IKHandlers;
internal class IKHandlerDesktop : IKHandler
{
public IKHandlerDesktop(VRIK vrik)
{
_vrik = vrik;
_solver = vrik.solver;
}
#region Game Overrides
public override void OnInitializeIk()
{
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktop);
}
#endregion
#region Weight Overrides
public override void UpdateWeights()
{
// Reset avatar local position
_vrik.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
base.UpdateWeights();
}
public override void UpdateTracking()
{
BodySystem.TrackingEnabled = ShouldTrackAll();
BodySystem.TrackingLocomotionEnabled = BodySystem.TrackingEnabled && ShouldTrackLocomotion();
ResetSolverIfNeeded();
}
#endregion
#region VRIK Solver Events
private void OnPreSolverUpdateDesktop()
{
// Reset avatar local position
_vrik.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
_solver.plantFeet = ModSettings.EntryPlantFeet.Value;
// Emulate old VRChat hip movement
if (ModSettings.EntryBodyLeanWeight.Value > 0)
{
float weightedAngle = ModSettings.EntryBodyLeanWeight.Value * (ModSettings.EntryProneThrusting.Value ? 1f: _solver.locomotion.weight);
float angle = IKManager.Instance._desktopCamera.localEulerAngles.x;
angle = angle > 180 ? angle - 360 : angle;
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, _vrik.transform.right);
_solver.spine.headRotationOffset *= rotation;
}
// Make root heading follow within a set limit
if (ModSettings.EntryBodyHeadingLimit.Value > 0)
{
float weightedAngleLimit = ModSettings.EntryBodyHeadingLimit.Value * _solver.locomotion.weight;
float deltaAngleRoot = Mathf.DeltaAngle(IKManager.Instance.transform.eulerAngles.y, _ikSimulatedRootAngle);
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
if (absDeltaAngleRoot > weightedAngleLimit)
{
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
_ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, IKManager.Instance.transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
}
_solver.spine.rootHeadingOffset = deltaAngleRoot;
if (ModSettings.EntryPelvisHeadingWeight.Value > 0)
{
_solver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f);
_solver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * ModSettings.EntryPelvisHeadingWeight.Value, 0f);
}
if (ModSettings.EntryChestHeadingWeight.Value > 0)
{
_solver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * ModSettings.EntryChestHeadingWeight.Value, 0f);
}
}
}
#endregion
#region Private Methods
private bool ShouldTrackAll()
{
return !PlayerSetup.Instance._emotePlaying;
}
private bool ShouldTrackLocomotion()
{
bool isMoving = MovementSystem.Instance.movementVector.magnitude > 0f;
bool isGrounded = MovementSystem.Instance._isGrounded;
bool isCrouching = MovementSystem.Instance.crouching;
bool isProne = MovementSystem.Instance.prone;
bool isFlying = MovementSystem.Instance.flying;
bool isSitting = MovementSystem.Instance.sitting;
bool isSwimming = MovementSystem.Instance.GetSubmerged();
bool isStanding = PlayerSetup.Instance.avatarUpright >=
Mathf.Max(PlayerSetup.Instance.avatarProneLimit, PlayerSetup.Instance.avatarCrouchLimit);
return !(isMoving || isCrouching || isProne || isFlying || isSwimming || isSitting || !isGrounded || !isStanding);
}
private void ResetSolverIfNeeded()
{
if (_wasTrackingLocomotion == BodySystem.TrackingLocomotionEnabled) return;
_wasTrackingLocomotion = BodySystem.TrackingLocomotionEnabled;
Reset();
}
#endregion
}

View file

@ -0,0 +1,416 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using NAK.DesktopVRIK.IK.IKHandlers;
using NAK.DesktopVRIK.IK.VRIKHelpers;
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.DesktopVRIK.IK;
public class IKManager : MonoBehaviour
{
public static IKManager Instance;
#region Variables
private static VRIK _vrik;
private static IKSolverVR _solver;
private bool _isAvatarInitialized;
// IK Handling
private IKHandler _ikHandler;
// Player Info
internal Transform _desktopCamera;
internal Transform _vrCamera;
// Avatar Info
private Animator _animator;
internal Transform _hipTransform;
// Animator Info
private int _animLocomotionLayer = -1;
private int _animIKPoseLayer = -1;
private const string _locomotionLayerName = "Locomotion/Emotes";
private const string _ikposeLayerName = "IKPose";
// Pose Info
internal HumanPoseHandler _humanPoseHandler;
internal HumanPose _humanPose;
internal HumanPose _humanPoseInitial;
#endregion
#region Unity Methods
private void Start()
{
if (Instance != null)
{
Destroy(this);
return;
}
Instance = this;
_desktopCamera = PlayerSetup.Instance.desktopCamera.transform;
_vrCamera = PlayerSetup.Instance.vrCamera.transform;
}
private void Update()
{
_ikHandler?.UpdateWeights();
}
#endregion
#region Avatar Events
public void OnAvatarInitialized(GameObject inAvatar)
{
if (MetaPort.Instance.isUsingVr)
return;
if (_isAvatarInitialized)
return;
if (!inAvatar.TryGetComponent(out _animator))
return;
if (_animator.avatar == null || !_animator.avatar.isHuman)
return;
_animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
_animIKPoseLayer = _animator.GetLayerIndex(_ikposeLayerName);
_animLocomotionLayer = _animator.GetLayerIndex(_locomotionLayerName);
_hipTransform = _animator.GetBoneTransform(HumanBodyBones.Hips);
_humanPoseHandler?.Dispose();
_humanPoseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform);
_humanPoseHandler.GetHumanPose(ref _humanPose);
_humanPoseHandler.GetHumanPose(ref _humanPoseInitial);
InitializeDesktopIk();
_isAvatarInitialized = true;
}
public void OnAvatarDestroyed()
{
if (!_isAvatarInitialized)
return;
_vrik = null;
_solver = null;
_animator = null;
_animIKPoseLayer = -1;
_animLocomotionLayer = -1;
_hipTransform = null;
_humanPoseHandler?.Dispose();
_humanPoseHandler = null;
_ikHandler = null;
_isAvatarInitialized = false;
}
#endregion
#region Game Events
public bool OnPlayerScaled(float scaleDifference)
{
if (_ikHandler == null)
return false;
DesktopVRIK.Logger.Msg(scaleDifference);
_ikHandler.OnPlayerScaled(scaleDifference);
return true;
}
public void OnPlayerSeatedStateChanged(bool isSitting)
{
if (_ikHandler == null)
return;
_ikHandler.Reset();
}
public bool OnPlayerHandleMovementParent(CVRMovementParent movementParent)
{
if (_ikHandler == null)
return false;
_ikHandler.OnPlayerHandleMovementParent(movementParent, GetPlayerPosition());
return true;
}
public bool OnPlayerTeleported()
{
if (_ikHandler == null)
return false;
_ikHandler.Reset();
return true;
}
public void OnPlayerUpdate()
{
if (_ikHandler == null)
return;
_ikHandler.UpdateTracking();
}
#endregion
#region IK Initialization
private void InitializeDesktopIk()
{
SetupIkGeneral();
IKCalibrator.ConfigureDesktopVrIk(_vrik);
_ikHandler = new IKHandlerDesktop(_vrik);
IKCalibrator.SetupHeadIKTarget(_vrik);
InitializeIkGeneral();
_ikHandler.OnInitializeIk();
}
private void SetupIkGeneral()
{
_animator.transform.position = GetPlayerPosition();
_animator.transform.rotation = GetPlayerRotation();
SetAvatarPose(AvatarPose.Default);
_vrik = IKCalibrator.SetupVrIk(_animator);
_solver = _vrik.solver;
}
private void InitializeIkGeneral()
{
SetAvatarPose(AvatarPose.IKPose);
VRIKUtils.CalculateInitialIKScaling(_vrik, ref _ikHandler._locomotionData);
VRIKUtils.CalculateInitialFootsteps(_vrik, ref _ikHandler._locomotionData);
_solver.Initiate(_vrik.transform); // initiate a second time
SetAvatarPose(AvatarPose.Initial);
VRIKUtils.ApplyScaleToVRIK(_vrik, _ikHandler._locomotionData, 1f);
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateGeneral);
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktopSwimming);
_vrik.onPostSolverUpdate.AddListener(OnPostSolverUpdateGeneral);
}
#endregion
#region Public Methods
public bool IsAvatarInitialized()
{
return _isAvatarInitialized;
}
public Vector3 GetPlayerPosition()
{
if (!MetaPort.Instance.isUsingVr)
return transform.position;
Vector3 vrPosition = _vrCamera.transform.position;
vrPosition.y = transform.position.y;
return vrPosition;
}
public Quaternion GetPlayerRotation()
{
if (!MetaPort.Instance.isUsingVr)
return transform.rotation;
Vector3 vrForward = _vrCamera.transform.forward;
vrForward.y = 0f;
return Quaternion.LookRotation(vrForward, Vector3.up);
}
#endregion
#region VRIK Solver Events General
private void OnPreSolverUpdateDesktopSwimming()
{
if (_solver.IKPositionWeight < 0.9f)
return;
Vector3 headPosition = _animator.GetBoneTransform(HumanBodyBones.Head).position;
// rotate hips around head bone to simulate diving
if (MovementSystem.Instance._playerIsSubmerged)
{
const float maxAngleForward = 85f;
const float maxAngleBackward = -85f;
float lookAngle = _desktopCamera.localEulerAngles.x;
if (lookAngle > 180) lookAngle -= 360;
lookAngle = Mathf.Clamp(lookAngle, maxAngleBackward, maxAngleForward);
float multiplier = Mathf.Lerp(0f, 1f, (MovementSystem.Instance.movementVector.magnitude - 0.5f) * 2f); // blend in when moving w/ sprint
multiplier *= (-1f * MovementSystem.Instance.GetDepth()); // blend out when at surface
_solver.IKPositionWeight = 1f - (-1f * multiplier); // disable IK completely when fast swimming (only applicable to DesktopVRIK)
_hipTransform.RotateAround(headPosition, _animator.transform.right, lookAngle * multiplier);
}
// offset avatar hips so head bone is centered on avatar root
// this fixes animations becoming scrunched up once VRIK solves!
_hipTransform.position -= (headPosition - _animator.transform.position) with { y = 0f };
}
private void OnPreSolverUpdateGeneral()
{
if (_solver.IKPositionWeight < 0.9f)
return;
Vector3 hipPos = _hipTransform.position;
Quaternion hipRot = _hipTransform.rotation;
_humanPoseHandler.GetHumanPose(ref _humanPose);
for (var i = 0; i < _humanPose.muscles.Length; i++)
{
switch (BodySystem.boneResetMasks[i])
{
case BodySystem.BoneResetMask.Never:
break;
case BodySystem.BoneResetMask.Spine:
_humanPose.muscles[i] *= 1 - _solver.spine.pelvisPositionWeight;
break;
case BodySystem.BoneResetMask.LeftArm:
_humanPose.muscles[i] *= 1 - _solver.leftArm.positionWeight;
break;
case BodySystem.BoneResetMask.RightArm:
_humanPose.muscles[i] *= 1 - _solver.rightArm.positionWeight;
break;
case BodySystem.BoneResetMask.LeftLeg:
_humanPose.muscles[i] *= 1 - _solver.leftLeg.positionWeight;
break;
case BodySystem.BoneResetMask.RightLeg:
_humanPose.muscles[i] *= 1 - _solver.rightLeg.positionWeight;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
_humanPoseHandler.SetHumanPose(ref _humanPose);
_hipTransform.position = hipPos;
_hipTransform.rotation = hipRot;
}
// "NetIk Pass", or "Additional Humanoid Pass" hack
private void OnPostSolverUpdateGeneral()
{
Vector3 hipPos = _hipTransform.position;
Quaternion hipRot = _hipTransform.rotation;
_humanPoseHandler.GetHumanPose(ref _humanPose);
_humanPoseHandler.SetHumanPose(ref _humanPose);
_hipTransform.position = hipPos;
_hipTransform.rotation = hipRot;
}
#endregion
#region Avatar Pose Utilities
private enum AvatarPose
{
Default = 0,
Initial = 1,
IKPose = 2,
TPose = 3,
APose = 4
}
private void SetAvatarPose(AvatarPose pose)
{
switch (pose)
{
case AvatarPose.Default:
SetMusclesToValue(0f);
break;
case AvatarPose.Initial:
if (HasCustomIKPose())
SetCustomLayersWeights(0f, 1f);
_humanPoseHandler.SetHumanPose(ref _humanPoseInitial);
break;
case AvatarPose.IKPose:
if (HasCustomIKPose())
{
SetCustomLayersWeights(1f, 0f);
return;
}
SetMusclesToPose(MusclePoses.IKPoseMuscles);
break;
case AvatarPose.TPose:
SetMusclesToPose(MusclePoses.TPoseMuscles);
break;
case AvatarPose.APose:
SetMusclesToPose(MusclePoses.APoseMuscles);
break;
}
}
private bool HasCustomIKPose()
{
return _animLocomotionLayer != -1 && _animIKPoseLayer != -1;
}
private void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
{
_animator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight);
_animator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight);
_animator.Update(0f);
}
private void SetMusclesToValue(float value)
{
_humanPoseHandler.GetHumanPose(ref _humanPose);
for (var i = 0; i < BodySystem.boneResetMasks.Length; i++)
{
if (BodySystem.boneResetMasks[i] != BodySystem.BoneResetMask.Never)
_humanPose.muscles[i] = value;
}
_humanPose.bodyPosition = Vector3.up;
_humanPose.bodyRotation = Quaternion.identity;
_humanPoseHandler.SetHumanPose(ref _humanPose);
}
private void SetMusclesToPose(float[] muscles)
{
_humanPoseHandler.GetHumanPose(ref _humanPose);
for (var i = 0; i < BodySystem.boneResetMasks.Length; i++)
{
if (BodySystem.boneResetMasks[i] != BodySystem.BoneResetMask.Never)
_humanPose.muscles[i] = muscles[i];
}
_humanPose.bodyPosition = Vector3.up;
_humanPose.bodyRotation = Quaternion.identity;
_humanPoseHandler.SetHumanPose(ref _humanPose);
}
#endregion
}

View file

@ -0,0 +1,46 @@
namespace NAK.DesktopVRIK.IK;
public static class MusclePoses
{
public static readonly float[] TPoseMuscles =
{
0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0.6001086f, 8.6213E-05f,
-0.0003308152f, 0.9999163f, -9.559652E-06f, 3.41413E-08f, -3.415095E-06f, -1.024528E-07f, 0.6001086f,
8.602679E-05f, -0.0003311098f, 0.9999163f, -9.510122E-06f, 1.707468E-07f, -2.732077E-06f, 2.035554E-15f,
-2.748694E-07f, 2.619475E-07f, 0.401967f, 0.3005583f, 0.04102772f, 0.9998822f, -0.04634236f, 0.002522987f,
0.0003842837f, -2.369134E-07f, -2.232262E-07f, 0.4019674f, 0.3005582f, 0.04103433f, 0.9998825f,
-0.04634996f, 0.00252335f, 0.000383302f, -1.52127f, 0.2634507f, 0.4322457f, 0.6443988f, 0.6669409f,
-0.4663372f, 0.8116828f, 0.8116829f, 0.6678119f, -0.6186608f, 0.8116842f, 0.8116842f, 0.6677991f,
-0.619225f, 0.8116842f, 0.811684f, 0.6670032f, -0.465875f, 0.811684f, 0.8116836f, -1.520098f, 0.2613016f,
0.432256f, 0.6444503f, 0.6668426f, -0.4670413f, 0.8116828f, 0.8116828f, 0.6677986f, -0.6192409f, 0.8116841f,
0.811684f, 0.6677839f, -0.6198869f, 0.8116839f, 0.8116838f, 0.6668782f, -0.4667901f, 0.8116842f, 0.811684f
};
public static readonly float[] APoseMuscles =
{
0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0.6001087f, 0f,
-0.0003306383f, 0.9999163f, 0f, 0f, 0f, 0f, 0.6001087f, 0f, -0.0003306384f, 0.9999163f, 0f, 0f, 0f, 0f, 0f,
0f, -0.1071228f, 0.258636f, 0.1567371f, 0.9998825f, -0.0463457f, 0.002523606f, 0.0003833446f, 0f, 0f,
-0.1036742f, 0.2589961f, 0.1562322f, 0.9998825f, -0.04634446f, 0.002522176f, 0.0003835156f, -1.52127f,
0.2634749f, 0.4322476f, 0.6443989f, 0.6669405f, -0.4663376f, 0.8116828f, 0.8116829f, 0.6678116f,
-0.6186616f, 0.8116839f, 0.8116837f, 0.6677991f, -0.6192248f, 0.8116839f, 0.8116842f, 0.6670038f,
-0.4658763f, 0.8116841f, 0.811684f, -1.520108f, 0.2612858f, 0.4322585f, 0.6444519f, 0.6668428f, -0.4670413f,
0.8116831f, 0.8116828f, 0.6677985f, -0.6192364f, 0.8116842f, 0.8116842f, 0.667784f, -0.6198866f, 0.8116841f,
0.8116835f, 0.6668782f, -0.4667891f, 0.8116841f, 0.811684f
};
public static readonly float[] IKPoseMuscles =
{
0.00133321f, 8.195831E-06f, 8.537738E-07f, -0.002669832f, -7.651234E-06f, -0.001659694f, 0f, 0f, 0f,
0.04213953f, 0.0003007996f, -0.008032114f, -0.03059979f, -0.0003182998f, 0.009640567f, 0f, 0f, 0f, 0f, 0f,
0f, 0.5768794f, 0.01061097f, -0.1127839f, 0.9705755f, 0.07972051f, -0.0268422f, 0.007237188f, 0f,
0.5768792f, 0.01056608f, -0.1127519f, 0.9705756f, 0.07971933f, -0.02682396f, 0.007229362f, 0f,
-5.651802E-06f, -3.034899E-07f, 0.4100508f, 0.3610304f, -0.0838329f, 0.9262537f, 0.1353517f, -0.03578902f,
0.06005657f, -4.95989E-06f, -1.43007E-06f, 0.4096187f, 0.363263f, -0.08205152f, 0.9250782f, 0.1345718f,
-0.03572125f, 0.06055461f, -1.079177f, 0.2095419f, 0.6140652f, 0.6365265f, 0.6683931f, -0.4764312f,
0.8099416f, 0.8099371f, 0.6658203f, -0.7327053f, 0.8113618f, 0.8114051f, 0.6643661f, -0.40341f, 0.8111364f,
0.8111367f, 0.6170399f, -0.2524227f, 0.8138723f, 0.8110135f, -1.079171f, 0.2095456f, 0.6140658f, 0.6365255f,
0.6683878f, -0.4764301f, 0.8099402f, 0.8099376f, 0.6658241f, -0.7327023f, 0.8113653f, 0.8113793f, 0.664364f,
-0.4034042f, 0.811136f, 0.8111364f, 0.6170469f, -0.2524345f, 0.8138595f, 0.8110138f
};
}

View file

@ -0,0 +1,25 @@
using UnityEngine;
namespace NAK.DesktopVRIK.IK.VRIKHelpers;
public struct VRIKLocomotionData
{
public Vector3 InitialFootPosLeft;
public Vector3 InitialFootPosRight;
public Quaternion InitialFootRotLeft;
public Quaternion InitialFootRotRight;
public float InitialFootDistance;
public float InitialStepThreshold;
public float InitialStepHeight;
public void Clear()
{
InitialFootPosLeft = Vector3.zero;
InitialFootPosRight = Vector3.zero;
InitialFootRotLeft = Quaternion.identity;
InitialFootRotRight = Quaternion.identity;
InitialFootDistance = 0f;
InitialStepThreshold = 0f;
InitialStepHeight = 0f;
}
}

View file

@ -0,0 +1,62 @@
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.DesktopVRIK.IK.VRIKHelpers;
public static class VRIKUtils
{
public static void CalculateInitialIKScaling(VRIK vrik, ref VRIKLocomotionData locomotionData)
{
// Get distance between feet and thighs
float scaleModifier = Mathf.Max(1f, vrik.references.pelvis.lossyScale.x);
float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position);
locomotionData.InitialFootDistance = footDistance * 0.5f;
locomotionData.InitialStepThreshold = footDistance * scaleModifier;
locomotionData.InitialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
}
public static void CalculateInitialFootsteps(VRIK vrik, ref VRIKLocomotionData locomotionData)
{
Transform root = vrik.references.root;
Transform leftFoot = vrik.references.leftFoot;
Transform rightFoot = vrik.references.rightFoot;
// Calculate the world rotation of the root bone at the current frame
Quaternion rootWorldRot = root.rotation;
// Calculate the world rotation of the left and right feet relative to the root bone
locomotionData.InitialFootPosLeft = root.InverseTransformPoint(leftFoot.position);
locomotionData.InitialFootPosRight = root.InverseTransformPoint(rightFoot.position);
locomotionData.InitialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation;
locomotionData.InitialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation;
}
public static void ResetToInitialFootsteps(VRIK vrik, VRIKLocomotionData locomotionData, float scaleModifier)
{
Transform root = vrik.references.root;
Quaternion rootWorldRot = vrik.references.root.rotation;
// hack, use parent transform instead as setting feet position moves root (root.parent), but does not work for VR
var footsteps = vrik.solver.locomotion.footsteps;
footsteps[0].Reset(rootWorldRot, root.TransformPoint(locomotionData.InitialFootPosLeft),
rootWorldRot * locomotionData.InitialFootRotLeft);
footsteps[1].Reset(rootWorldRot, root.TransformPoint(locomotionData.InitialFootPosRight),
rootWorldRot * locomotionData.InitialFootRotRight);
}
public static void ApplyScaleToVRIK(VRIK vrik, VRIKLocomotionData locomotionData, float scaleModifier)
{
IKSolverVR.Locomotion locomotionSolver = vrik.solver.locomotion;
locomotionSolver.footDistance = locomotionData.InitialFootDistance * scaleModifier;
locomotionSolver.stepThreshold = locomotionData.InitialStepThreshold * scaleModifier;
ScaleStepHeight(locomotionSolver.stepHeight, locomotionData.InitialStepHeight * scaleModifier);
}
private static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag)
{
var keyframes = stepHeightCurve.keys;
keyframes[1].value = mag;
stepHeightCurve.keys = keyframes;
}
}

View file

@ -0,0 +1,21 @@

namespace NAK.DesktopVRIK.Integrations;
public static class AMTAddon
{
#region Variables
public static bool integration_AMT = false;
#endregion
#region Initialization
public static void Initialize()
{
integration_AMT = true;
DesktopVRIK.Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
}
#endregion
}

View file

@ -0,0 +1,72 @@
using BTKUILib;
using BTKUILib.UIObjects;
using System.Runtime.CompilerServices;
namespace NAK.DesktopVRIK.Integrations;
public static class BTKUIAddon
{
#region Initialization
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Initialize()
{
// Add mod to the Misc Menu
Page miscPage = QuickMenuAPI.MiscTabPage;
Category miscCategory = miscPage.AddCategory(ModSettings.SettingsCategory);
AddMelonToggle(ref miscCategory, ModSettings.EntryEnabled);
SetupDesktopIKConfigurationPage(ref miscCategory);
}
#endregion
#region Pages Setup
private static void SetupDesktopIKConfigurationPage(ref Category parentCategory)
{
Page desktopIKPage = parentCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", ModSettings.SettingsCategory);
desktopIKPage.MenuTitle = "DesktopVRIK Settings";
Category desktopIKCategory = desktopIKPage.AddCategory(desktopIKPage.MenuTitle);
// General Settings
AddMelonToggle(ref desktopIKCategory, ModSettings.EntryPlantFeet);
// Calibration Settings
AddMelonToggle(ref desktopIKCategory, ModSettings.EntryUseToesForVRIK);
// Fine-tuning Settings
AddMelonToggle(ref desktopIKCategory, ModSettings.EntryResetFootstepsOnIdle);
// Funny Settings
AddMelonToggle(ref desktopIKCategory, ModSettings.EntryProneThrusting);
// Body Leaning Weight
AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyLeanWeight, 0, 1f, 1);
// Max Root Heading Limit & Weights
AddMelonSlider(ref desktopIKPage, ModSettings.EntryBodyHeadingLimit, 0, 90f, 0);
AddMelonSlider(ref desktopIKPage, ModSettings.EntryPelvisHeadingWeight, 0, 1f, 1);
AddMelonSlider(ref desktopIKPage, ModSettings.EntryChestHeadingWeight, 0, 1f, 1);
// Lerp Speed
AddMelonSlider(ref desktopIKPage, ModSettings.EntryIKLerpSpeed, 0, 20f, 0);
}
#endregion
#region Melon Pref Helpers
private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry<bool> entry)
{
category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
}
private static void AddMelonSlider(ref Page page, MelonLoader.MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2)
{
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
}
#endregion
}

View file

@ -0,0 +1,40 @@
using MelonLoader;
namespace NAK.DesktopVRIK;
public class DesktopVRIK : MelonMod
{
internal static MelonLogger.Instance Logger;
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
InitializeIntegration("AvatarMotionTweaker", Integrations.AMTAddon.Initialize);
}
private static void InitializeIntegration(string modName, Action integrationAction)
{
if (RegisteredMelons.All(it => it.Info.Name != modName))
return;
Logger.Msg($"Initializing {modName} integration.");
integrationAction.Invoke();
}
private void ApplyPatches(Type type)
{
try
{
HarmonyInstance.PatchAll(type);
}
catch (Exception e)
{
Logger.Msg($"Failed while patching {type.Name}!");
Logger.Error(e);
}
}
}

View file

@ -0,0 +1,43 @@
using MelonLoader;
namespace NAK.DesktopVRIK;
public static class ModSettings
{
internal const string SettingsCategory = nameof(DesktopVRIK);
public static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(SettingsCategory);
// Desktop VRIK Settings
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
Category.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
Category.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
Category.CreateEntry("Reset Footsteps on Idle", false, description: "Determines if the Locomotion Footsteps will be reset to their calibration position when entering idle.");
public static readonly MelonPreferences_Entry<bool> EntryUseToesForVRIK =
Category.CreateEntry("Use VRIK Toes", false, description: "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar.");
public static readonly MelonPreferences_Entry<float> EntryBodyLeanWeight =
Category.CreateEntry("Body Lean Weight", 0.5f, description: "Adds rotational influence to the body solver when looking up/down. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<float> EntryBodyHeadingLimit =
Category.CreateEntry("Body Heading Limit", 20f, description: "Specifies the maximum angle the lower body can have relative to the head when rotating. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<float> EntryPelvisHeadingWeight =
Category.CreateEntry("Pelvis Heading Weight", 0.25f, description: "Determines how much the pelvis will face the Body Heading Limit. Set to 0 to align with head.");
public static readonly MelonPreferences_Entry<float> EntryChestHeadingWeight =
Category.CreateEntry("Chest Heading Weight", 0.75f, description: "Determines how much the chest will face the Body Heading Limit. Set to 0 to align with head.");
public static readonly MelonPreferences_Entry<float> EntryIKLerpSpeed =
Category.CreateEntry("IK Lerp Speed", 10f, description: "Determines fast the IK & Locomotion weights blend after entering idle. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<bool> EntryProneThrusting =
Category.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take effect while crouched or prone.");
}

View file

@ -0,0 +1,33 @@
using MelonLoader;
using NAK.DesktopVRIK.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.DesktopVRIK))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.DesktopVRIK))]
[assembly: MelonInfo(
typeof(NAK.DesktopVRIK.DesktopVRIK),
nameof(NAK.DesktopVRIK),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopVRIK"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonOptionalDependencies("BTKUILib", "AvatarMotionTweaker")]
[assembly: MelonColor(255, 155, 89, 182)]
[assembly: MelonAuthorColor(255, 158, 21, 32)]
[assembly: HarmonyDontPatchAll]
namespace NAK.DesktopVRIK.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "4.2.9";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,16 @@
## DesktopVRIK
Adds VRIK to Desktop ChilloutVR avatars. No longer will you be a liveless sliding statue~!
(adds the small feet stepping when looking around on Desktop)
https://user-images.githubusercontent.com/37721153/221870123-fbe4f5e8-8d6e-4a43-aa5e-f2188e6491a9.mp4
## Configuration:
* Configurable body lean weight. Set to 0 to disable.
* Configurable max root heading angle with chest/pelvis weight settings. Set to 0 to disable.
* Options to disable VRIK from using mapped toes &/or find unmapped (non-human) toe bones.
## Relevant Feedback Posts:
https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars

View file

@ -0,0 +1,24 @@
{
"_id": 117,
"name": "DesktopVRIK",
"modversion": "4.2.9",
"gameversion": "2023r173",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nOptional BTKUILib integration.",
"searchtags": [
"desktop",
"vrik",
"ik",
"feet",
"fish"
],
"requirements": [
"BTKUILib"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r22/DesktopVRIK.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DesktopVRIK/",
"changelog": "- Fixes for 2023r173.\n- Fixed ResetFootstepsOnIdle not working as intended.\n- Added swimming pitch control.\n- Added active body offset.",
"embedcolor": "#9b59b6"
}