This commit is contained in:
NotAKidoS 2023-02-24 20:07:48 -06:00
parent 16e888c64d
commit cbd65aa813
8 changed files with 635 additions and 539 deletions

View file

@ -1,169 +1,149 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
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.Reflection;
using UnityEngine;
using UnityEngine.Events;
namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIK : MonoBehaviour
{
public static DesktopVRIK Instance;
public DesktopVRIKCalibrator Calibrator;
public static bool
Setting_Enabled,
Setting_EnforceViewPosition,
Setting_EmoteVRIK,
Setting_EmoteLookAtIK;
// DesktopVRIK Settings
public bool
Setting_Enabled = true,
Setting_HipMovement = true,
Setting_ResetOnLand = true,
Setting_PlantFeet = true,
Setting_EnforceViewPosition;
public float
Setting_BodyLeanWeight,
Setting_BodyHeadingLimit,
Setting_PelvisHeadingWeight,
Setting_ChestHeadingWeight;
public static float
Setting_BodyLeanWeight = 0.5f,
Setting_BodyAngleLimit = 0f;
public Transform viewpoint;
public Vector3 eyeOffset;
// Internal Stuff
private float
ik_SimulatedRootAngle;
private bool
ms_lastGrounded,
ps_emoteIsPlaying;
static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
void Start()
{
Calibrator = new DesktopVRIKCalibrator();
Instance = this;
DesktopVRIKMod.UpdateAllSettings();
}
public void ChangeViewpointHandling(bool enabled)
public void OnSetupAvatarDesktop()
{
if (Setting_EnforceViewPosition == enabled) return;
Setting_EnforceViewPosition = enabled;
if (enabled)
{
PlayerSetup.Instance.desktopCamera.transform.localPosition = Vector3.zero;
return;
}
PlayerSetup.Instance.desktopCamera.transform.localPosition = eyeOffset;
if (!Setting_Enabled) return;
Calibrator.SetupDesktopVRIK();
ik_SimulatedRootAngle = transform.eulerAngles.y;
}
public void AlternativeOnPreSolverUpdate()
{
//this order matters, rotation offset will be choppy if avatar is not cenetered first
//public void OnReCalibrateAvatar()
//{
// Calibrator.RecalibrateDesktopVRIK();
// ik_SimulatedRootAngle = transform.eulerAngles.y;
//}
DesktopVRIK_Helper.Instance?.OnUpdateVRIK();
public bool OnApplyAvatarScaleToIk(float height)
{
if (Calibrator.vrik != null)
{
Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * height;
Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * height;
return true;
}
return false;
}
public void OnPlayerSetupUpdate(bool isEmotePlaying)
{
bool changed = isEmotePlaying != ps_emoteIsPlaying;
if (changed)
{
ps_emoteIsPlaying = isEmotePlaying;
Calibrator.vrik.transform.localPosition = Vector3.zero;
Calibrator.vrik.transform.localRotation = Quaternion.identity;
if (Calibrator.lookAtIK != null)
{
Calibrator.lookAtIK.enabled = !isEmotePlaying;
}
BodySystem.TrackingEnabled = !isEmotePlaying;
Calibrator.vrik.solver?.Reset();
}
}
public void OnPreSolverUpdate()
{
if (ps_emoteIsPlaying) return;
bool isGrounded = (bool)ms_isGrounded.GetValue(MovementSystem.Instance);
// Calculate everything that affects weight
float weight = Calibrator.vrik.solver.IKPositionWeight;
weight *= (1 - MovementSystem.Instance.movementVector.magnitude);
weight *= isGrounded ? 1f : 0f;
// Reset avatar offset (VRIK will literally make you walk away from root otherwise)
IKSystem.vrik.transform.localPosition = Vector3.zero;
IKSystem.vrik.transform.localRotation = Quaternion.identity;
Calibrator.vrik.transform.localPosition = Vector3.zero;
Calibrator.vrik.transform.localRotation = Quaternion.identity;
IKSystem.vrik.solver.plantFeet = true;
}
// Plant feet is nice for Desktop
Calibrator.vrik.solver.plantFeet = Setting_PlantFeet;
public Animator animator;
// This is nice for walk cycles
//Calibrator.vrik.solver.spine.rotateChestByHands = Setting_RotateChestByHands * weight;
public VRIK AlternativeCalibration(CVRAvatar avatar)
//reset solver if weight changes dramatically
if (Setting_ResetOnLand)
{
animator = avatar.GetComponent<Animator>();
Transform avatarHeadBone = animator.GetBoneTransform(HumanBodyBones.Head);
//Stuff to make bad armatures work (Fuck you Default Robot Kyle)
avatar.transform.localPosition = Vector3.zero;
//ikpose layer (specified by avatar author)
int ikposeLayerIndex = animator.GetLayerIndex("IKPose");
int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes");
if (ikposeLayerIndex != -1)
if (isGrounded && !ms_lastGrounded)
{
animator.SetLayerWeight(ikposeLayerIndex, 1f);
if (locoLayerIndex != -1)
Calibrator.vrik.solver.Reset();
}
ms_lastGrounded = isGrounded;
}
// Old VRChat hip movement emulation
if (Setting_BodyLeanWeight > 0)
{
animator.SetLayerWeight(locoLayerIndex, 0f);
}
animator.Update(0f);
float weightedAngle = Setting_BodyLeanWeight * weight;
float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x;
angle = (angle > 180) ? angle - 360 : angle;
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, IKSystem.Instance.avatar.transform.right);
Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation);
}
VRIK vrik = avatar.gameObject.AddComponent<VRIK>();
vrik.AutoDetectReferences();
//fuck toes
vrik.references.leftToes = null;
vrik.references.rightToes = null;
vrik.fixTransforms = true;
vrik.solver.plantFeet = false;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 0.75f;
vrik.solver.spine.minHeadHeight = -100f;
vrik.solver.spine.bodyRotStiffness = 0.15f;
vrik.solver.spine.headClampWeight = 1f;
vrik.solver.spine.maintainPelvisPosition = 1f;
vrik.solver.spine.neckStiffness = 0f;
vrik.solver.locomotion.weight = 0f;
vrik.solver.spine.bodyPosStiffness = 0f;
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.pelvisPositionWeight = 0f;
vrik.solver.leftArm.positionWeight = 0f;
vrik.solver.leftArm.rotationWeight = 0f;
vrik.solver.rightArm.positionWeight = 0f;
vrik.solver.rightArm.rotationWeight = 0f;
vrik.solver.leftLeg.positionWeight = 0f;
vrik.solver.leftLeg.rotationWeight = 0f;
vrik.solver.rightLeg.positionWeight = 0f;
vrik.solver.rightLeg.rotationWeight = 0f;
vrik.solver.IKPositionWeight = 0f;
BodySystem.TrackingLeftArmEnabled = false;
BodySystem.TrackingRightArmEnabled = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
//Custom funky AF head ik shit
foreach (Transform transform in DesktopVRIK_Helper.Instance.ik_HeadFollower)
// Make root heading follow within a set limit
if (Setting_BodyHeadingLimit > 0)
{
if (transform.name == "Head IK Target")
float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_SimulatedRootAngle);
float angleMaxDelta = Mathf.Abs(currentAngle);
if (angleMaxDelta > weightedAngleLimit)
{
Destroy(transform.gameObject);
currentAngle = Mathf.Sign(currentAngle) * weightedAngleLimit;
ik_SimulatedRootAngle = Mathf.MoveTowardsAngle(ik_SimulatedRootAngle, transform.eulerAngles.y, angleMaxDelta - weightedAngleLimit);
}
}
DesktopVRIK_Helper.Instance.avatar_HeadBone = avatarHeadBone;
DesktopVRIK_Helper.Instance.ik_HeadFollower.position = avatarHeadBone.position;
DesktopVRIK_Helper.Instance.ik_HeadFollower.rotation = Quaternion.identity;
VRIKCalibrator.CalibrateHead(vrik, DesktopVRIK_Helper.Instance.ik_HeadFollower.transform, IKSystem.Instance.headAnchorPositionOffset, IKSystem.Instance.headAnchorRotationOffset);
DesktopVRIK_Helper.Instance.ik_HeadFollower.localRotation = Quaternion.identity;
//force immediate calibration before animator decides to fuck us
vrik.solver.SetToReferences(vrik.references);
vrik.solver.Initiate(vrik.transform);
if (ikposeLayerIndex != -1)
Calibrator.vrik.solver.spine.rootHeadingOffset = currentAngle;
if (Setting_PelvisHeadingWeight > 0)
{
animator.SetLayerWeight(ikposeLayerIndex, 0f);
if (locoLayerIndex != -1)
Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Pelvis, new Vector3(0f, currentAngle * Setting_PelvisHeadingWeight, 0f));
Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, -currentAngle * Setting_PelvisHeadingWeight, 0f));
}
if (Setting_ChestHeadingWeight > 0)
{
animator.SetLayerWeight(locoLayerIndex, 1f);
Calibrator.vrik.solver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, currentAngle * Setting_ChestHeadingWeight, 0f));
}
}
//Find eyeoffset
eyeOffset = PlayerSetup.Instance.desktopCamera.transform.localPosition;
viewpoint = avatarHeadBone.Find("LocalHeadPoint");
ChangeViewpointHandling(Setting_EnforceViewPosition);
//reset ikpose layer
if (ikposeLayerIndex != -1)
{
animator.SetLayerWeight(ikposeLayerIndex, 0f);
if (locoLayerIndex != -1)
{
animator.SetLayerWeight(locoLayerIndex, 1f);
}
}
vrik?.onPreSolverUpdate.AddListener(new UnityAction(this.AlternativeOnPreSolverUpdate));
DesktopVRIK_Helper.Instance?.OnResetIK();
return vrik;
}
}

View file

@ -18,9 +18,6 @@
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="BTKUILib">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
</Reference>
@ -44,6 +41,10 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Integrations\" />
</ItemGroup>
<Target Name="Deploy" AfterTargets="Build">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" />
<Message Text="Copied $(TargetPath) to C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" Importance="high" />

View file

@ -0,0 +1,450 @@
using ABI.CCK.Components;
using ABI_RC.Core;
using ABI_RC.Core.Base;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using HarmonyLib;
using RootMotion.FinalIK;
using UnityEngine;
using UnityEngine.Events;
namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIKCalibrator
{
public DesktopVRIKCalibrator()
{
// Get base game scripts.
ikSystem = IKSystem.Instance;
playerSetup = PlayerSetup.Instance;
// Get traverse to private shit in iksystem.
_vrikTraverse = Traverse.Create(ikSystem).Field("_vrik");
_avatarTraverse = Traverse.Create(ikSystem).Field("_avatar");
_animatorManagerTraverse = Traverse.Create(ikSystem).Field("_animatorManager");
_poseHandlerTraverse = Traverse.Create(ikSystem).Field("_poseHandler");
_avatarRootHeightTraverse = Traverse.Create(ikSystem).Field("_avatarRootHeight");
// Get traverse to private shit in playersetup.
_lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK");
}
//Settings
public bool Setting_UseVRIKToes = true;
public bool Setting_FindUnmappedToes = true;
//DesktopVRIK
public CVRAvatar avatar;
public Animator animator;
public Transform avatarTransform;
public VRIK vrik;
public LookAtIK lookAtIK;
public HumanPoseHandler humanPoseHandler;
public HumanPose initialHumanPose;
public bool fixTransformsRequired;
public float initialFootDistance;
public float initialStepThreshold;
//Traverse
private IKSystem ikSystem;
private PlayerSetup playerSetup;
private Traverse _vrikTraverse;
private Traverse _lookIKTraverse;
private Traverse _avatarTraverse;
private Traverse _animatorManagerTraverse;
private Traverse _poseHandlerTraverse;
private Traverse _avatarRootHeightTraverse;
//public void RecalibrateDesktopVRIK()
//{
// if (avatar != null)
// {
// //calibrate VRIK
// CalibrateDesktopVRIK();
// }
// else
// {
// //we never calibrated
// SetupDesktopVRIK();
// }
//}
public void SetupDesktopVRIK()
{
//store avatar root transform & center it
avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
animator = avatar.GetComponent<Animator>();
avatarTransform = avatar.transform;
avatarTransform.localPosition = Vector3.zero;
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
//prepare for VRIK
PrepareIKSystem();
CalibrateDesktopVRIK();
//add presolver update listener
vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate));
}
private void CalibrateDesktopVRIK()
{
//calibrate VRIK
PrepareAvatarVRIK();
SetAvatarIKPose(true);
CalculateInitialIKScaling();
CalibrateHeadIK();
ForceInitiateVRIKSolver();
SetAvatarIKPose(false);
}
private void PrepareIKSystem()
{
// Get the animator manager and human pose handler
var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>();
humanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>();
// Store the avatar component
_avatarTraverse.SetValue(avatar);
// Set the animator for the IK system
ikSystem.animator = animator;
if (ikSystem.animator != null)
{
animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController);
}
// Set the avatar height float
float avatarHeight = ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y;
_avatarRootHeightTraverse.SetValue(avatarHeight);
// Create a new human pose handler and dispose the old one
if (humanPoseHandler != null)
{
humanPoseHandler.Dispose();
_poseHandlerTraverse.SetValue(null);
}
humanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform);
_poseHandlerTraverse.SetValue(humanPoseHandler);
// Find valid human bones
IKSystem.BoneExists.Clear();
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone != HumanBodyBones.LastBone)
{
IKSystem.BoneExists.Add(bone, ikSystem.animator.GetBoneTransform(bone) != null);
}
}
// Prepare BodySystem for calibration
BodySystem.TrackingLeftArmEnabled = false;
BodySystem.TrackingRightArmEnabled = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
}
private void PrepareAvatarVRIK()
{
//add and configure VRIK
vrik = avatar.gameObject.AddComponentIfMissing<VRIK>();
vrik.AutoDetectReferences();
ConfigureVRIKReferences();
_vrikTraverse.SetValue(vrik);
//in testing, not really needed
//only required if Setting_FindUnmappedToes
//and non-human mapped toes are found
vrik.fixTransforms = fixTransformsRequired;
//default solver settings
vrik.solver.locomotion.weight = 0f;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 0.75f;
vrik.solver.spine.minHeadHeight = 0f;
vrik.solver.IKPositionWeight = 1f;
//disable to not bleed into anims
vrik.solver.spine.chestClampWeight = 0f;
vrik.solver.spine.maintainPelvisPosition = 0f;
//for body leaning
vrik.solver.spine.neckStiffness = 0.0001f; //cannot be 0
vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0.2f;
//disable so avatar doesnt try and walk away
//fixes nameplate spazzing on remote
vrik.solver.locomotion.velocityFactor = 0f;
vrik.solver.locomotion.maxVelocity = 0f;
//disable so PAM & BID dont make body shake
vrik.solver.spine.rotateChestByHands = 0f;
//enable so knees on fucked models work better
vrik.solver.leftLeg.useAnimatedBendNormal = true;
vrik.solver.rightLeg.useAnimatedBendNormal = true;
//enable to prioritize LookAtIK
vrik.solver.spine.headClampWeight = 0.2f;
//disable to not go on tippytoes
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.rotationWeight = 1f;
//vrik.solver.spine.maintainPelvisPosition = 1f;
//vrik.solver.locomotion.weight = 0f;
//vrik.solver.spine.positionWeight = 0f;
//vrik.solver.spine.pelvisPositionWeight = 0f;
//vrik.solver.leftArm.positionWeight = 0f;
//vrik.solver.leftArm.rotationWeight = 0f;
//vrik.solver.rightArm.positionWeight = 0f;
//vrik.solver.rightArm.rotationWeight = 0f;
//vrik.solver.leftLeg.positionWeight = 0f;
//vrik.solver.leftLeg.rotationWeight = 0f;
//vrik.solver.rightLeg.positionWeight = 0f;
//vrik.solver.rightLeg.rotationWeight = 0f;
//vrik.solver.IKPositionWeight = 0f;
//THESE ARE CONFIGURABLE IN GAME IK SETTINGS
//vrik.solver.leftLeg.target = null;
//vrik.solver.leftLeg.bendGoal = null;
//vrik.solver.leftLeg.positionWeight = 0f;
//vrik.solver.leftLeg.bendGoalWeight = 0f;
//vrik.solver.rightLeg.target = null;
//vrik.solver.rightLeg.bendGoal = null;
//vrik.solver.rightLeg.positionWeight = 0f;
//vrik.solver.rightLeg.bendGoalWeight = 0f;
//vrik.solver.spine.pelvisTarget = null;
//vrik.solver.spine.chestGoal = null;
//vrik.solver.spine.positionWeight = 0f;
//vrik.solver.spine.rotationWeight = 0f;
//vrik.solver.spine.pelvisPositionWeight = 0f;
//vrik.solver.spine.pelvisRotationWeight = 0f;
//vrik.solver.spine.chestGoalWeight = 0f;
}
private void CalculateInitialIKScaling()
{
// Get distance between feets and thighs (fixes some weird armatures)
float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position);
float thighDistance = Vector3.Distance(vrik.references.leftThigh.position, vrik.references.rightThigh.position);
float greatestDistance = Mathf.Min(footDistance, thighDistance);
initialFootDistance = greatestDistance * 0.5f;
initialStepThreshold = greatestDistance * 0.4f;
//set initial values now, as avatars without scaling dont apply it
vrik.solver.locomotion.footDistance = initialFootDistance;
vrik.solver.locomotion.stepThreshold = initialStepThreshold;
}
private void CalibrateHeadIK()
{
// Lazy HeadIKTarget calibration
if (vrik.solver.spine.headTarget == null)
{
vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
}
vrik.solver.spine.headTarget.parent = vrik.references.head;
vrik.solver.spine.headTarget.localPosition = Vector3.zero;
vrik.solver.spine.headTarget.localRotation = Quaternion.identity;
}
private void SetAvatarIKPose(bool enforceTPose)
{
int ikposeLayerIndex = animator.GetLayerIndex("IKPose");
int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes");
// Use custom IKPose if found.
if (ikposeLayerIndex != -1 && locoLayerIndex != -1)
{
animator.SetLayerWeight(ikposeLayerIndex, enforceTPose ? 1f : 0f);
animator.SetLayerWeight(locoLayerIndex, enforceTPose ? 0f : 1f);
animator.Update(0f);
return;
}
// Otherwise use DesktopVRIK IKPose & revert afterwards.
if (enforceTPose)
{
humanPoseHandler.GetHumanPose(ref initialHumanPose);
humanPoseHandler.GetHumanPose(ref ikSystem.humanPose);
for (int i = 0; i < IKPoseMuscles.Length; i++)
{
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ikSystem.humanPose.muscles);
}
humanPoseHandler.SetHumanPose(ref ikSystem.humanPose);
}
else
{
humanPoseHandler.SetHumanPose(ref initialHumanPose);
}
}
private void ForceInitiateVRIKSolver()
{
//force immediate calibration before animator decides to fuck us
vrik.solver.SetToReferences(vrik.references);
vrik.solver.Initiate(vrik.transform);
}
private void ConfigureVRIKReferences()
{
fixTransformsRequired = false;
Transform leftShoulderBone = vrik.references.leftShoulder;
Transform rightShoulderBone = vrik.references.rightShoulder;
Transform assumedChest = leftShoulderBone?.parent;
// Repair chest & spine bone references (valve models were messed up)
if (assumedChest != null && rightShoulderBone.parent == assumedChest &&
vrik.references.chest != assumedChest)
{
vrik.references.chest = assumedChest;
vrik.references.spine = assumedChest.parent;
}
if (!Setting_UseVRIKToes)
{
vrik.references.leftToes = null;
vrik.references.rightToes = null;
}
else if (Setting_FindUnmappedToes)
{
Transform leftToes = vrik.references.leftToes;
Transform rightToes = vrik.references.rightToes;
if (leftToes == null && rightToes == null)
{
leftToes = FindUnmappedToe(vrik.references.leftFoot);
rightToes = FindUnmappedToe(vrik.references.rightFoot);
if (leftToes != null && rightToes != null)
{
fixTransformsRequired = true;
vrik.references.leftToes = leftToes;
vrik.references.rightToes = rightToes;
}
}
}
// Fix error when there is no finger bones
// Making up bullshit cause VRIK is evil otherwise
if (vrik.references.leftHand.childCount == 0)
{
vrik.solver.leftArm.wristToPalmAxis = Vector3.up;
vrik.solver.leftArm.palmToThumbAxis = -Vector3.forward;
}
if (vrik.references.rightHand.childCount == 0)
{
vrik.solver.rightArm.wristToPalmAxis = Vector3.up;
vrik.solver.rightArm.palmToThumbAxis = Vector3.forward;
}
}
private Transform FindUnmappedToe(Transform foot)
{
foreach (Transform bone in foot)
{
if (bone.name.ToLowerInvariant().Contains("toe") ||
bone.name.ToLowerInvariant().EndsWith("_end"))
{
return bone;
}
}
return null;
}
private static readonly float[] IKPoseMuscles = new float[]
{
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

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
namespace NAK.Melons.DesktopVRIK;
internal class DesktopVRIK_Helper : MonoBehaviour
{
public static DesktopVRIK_Helper Instance;
//Avatar
public Transform avatar_HeadBone;
//DesktopVRIK
public Transform ik_HeadFollower;
public Quaternion ik_HeadRotation;
public static void CreateInstance()
{
Transform helper = new GameObject("[DesktopVRIK] Virtual Rig").transform;
helper.parent = PlayerSetup.Instance.transform;
helper.localPosition = Vector3.zero;
helper.localRotation = Quaternion.identity;
helper.gameObject.AddComponent<DesktopVRIK_Helper>();
}
void Start()
{
Instance = this;
Transform headFollower = new GameObject("HeadBone_Follower").transform;
headFollower.parent = transform;
headFollower.localPosition = new Vector3(0f, 1.8f, 0f);
headFollower.localRotation = Quaternion.identity;
ik_HeadFollower = headFollower;
}
public void OnUpdateVRIK()
{
if (avatar_HeadBone != null)
{
float globalWeight = (1 - MovementSystem.Instance.movementVector.magnitude);
globalWeight *= IKSystem.vrik.solver.locomotion.weight;
//the most important thing ever
//IKSystem.vrik.solver.spine.rotationWeight = globalWeight;
HeadIK_FollowPosition();
HeadIK_RotateWithWeight(globalWeight);
HeadIK_FollowWithinAngle(globalWeight);
ik_HeadFollower.rotation = ik_HeadRotation;
}
}
public void OnResetIK()
{
ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z);
}
public void HeadIK_FollowPosition()
{
ik_HeadFollower.position = new Vector3(transform.position.x, avatar_HeadBone.position.y, transform.position.z);
}
public void HeadIK_FollowWithinAngle(float weight)
{
if (DesktopVRIK.Setting_BodyAngleLimit != 0)
{
float weightedAngle = DesktopVRIK.Setting_BodyAngleLimit * weight;
float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_HeadRotation.eulerAngles.y);
if (Mathf.Abs(currentAngle) > weightedAngle)
{
float fixedCurrentAngle = currentAngle > 0 ? currentAngle : -currentAngle;
float clampedAngle = Mathf.MoveTowardsAngle(ik_HeadRotation.eulerAngles.y, transform.eulerAngles.y, fixedCurrentAngle - weightedAngle);
ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, clampedAngle, 0);
}
}
else
{
ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z);
}
}
public void HeadIK_RotateWithWeight(float weight)
{
//VRChat hip movement emulation
if (DesktopVRIK.Setting_BodyLeanWeight != 0)
{
float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x;
if (angle > 180) angle -= 360;
float leanAmount = angle * weight * DesktopVRIK.Setting_BodyLeanWeight;
ik_HeadRotation = Quaternion.Euler(leanAmount * 0.33f, ik_HeadRotation.eulerAngles.y, 0);
}
else
{
ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, ik_HeadRotation.eulerAngles.y, transform.eulerAngles.z);
}
}
}

View file

@ -1,34 +1,21 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using HarmonyLib;
using RootMotion.FinalIK;
using UnityEngine;
/**
The process of calibrating VRIK is fucking painful.
Immediatly doing GetHumanPose() and then SetHumanPose() fixed heels in ground for all avatars.
Setting the avatars rotation to identity, head rotation offset to head bone world rotation, and then calibrating head IK target (kinda) fixed only robot kyle.
Enforcing a TPose only fixed my ferret avatars right shoulder.
Mix and matching these, and changing order, fucks with random specific avatars with fucky armatures.
MOST AVATARS DONT EVEN CHANGE, ITS JUST THESE FEW SPECIFIC ONES
I NEED to look into an IKPose controller...
Avatars of Note:
TurtleNeck Ferret- broken/inverted right shoulder
Space Robot Kyle- head ik target is rotated -90 90 0, so body/neck is fucked (Fuck you Default Robot Kyle)
Exteratta- the knees bend backwards like a fucking chicken... what the fuck im enforcing a tpose nowww
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.
Most other avatars play just fine. Never changes even when adding Tpose, rotating the avatar, headikrotationoffset, ect...
WHY (Fuck you Default Robot Kyle)
Most other avatars play just fine.
**/
@ -36,210 +23,44 @@ namespace NAK.Melons.DesktopVRIK.HarmonyPatches;
class PlayerSetupPatches
{
private static bool emotePlayed = false;
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), "SetupAvatarGeneral")]
static void SetupDesktopIKSystem(ref CVRAvatar ____avatarDescriptor, ref Animator ____animator)
[HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")]
static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator)
{
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled)
if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman)
{
if (____avatarDescriptor != null && ____animator != null && ____animator.isHuman)
{
//this will stop at the useless isVr return (the function is only ever called by vr anyways...)
IKSystem.Instance.InitializeAvatar(____avatarDescriptor);
}
DesktopVRIK.Instance?.OnSetupAvatarDesktop();
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), "Update")]
private static void CorrectVRIK(ref bool ____emotePlaying, ref LookAtIK ___lookIK)
static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying)
{
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled)
{
bool changed = ____emotePlaying != emotePlayed;
if (changed)
{
emotePlayed = ____emotePlaying;
IKSystem.vrik.transform.localPosition = Vector3.zero;
IKSystem.vrik.transform.localRotation = Quaternion.identity;
if (DesktopVRIK.Setting_EmoteLookAtIK && ___lookIK != null)
{
___lookIK.enabled = !____emotePlaying;
}
if (DesktopVRIK.Setting_EmoteVRIK)
{
BodySystem.TrackingEnabled = !____emotePlaying;
IKSystem.vrik.solver?.Reset();
DesktopVRIK_Helper.Instance?.OnResetIK();
}
}
}
DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying);
}
//should probably patch movement system instead
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), "HandleDesktopCameraPosition")]
private static void Prefix_PlayerSetup_HandleDesktopCameraPosition
(
bool ignore,
ref PlayerSetup __instance,
ref MovementSystem
____movementSystem,
ref int ___headBobbingLevel
)
{
if (___headBobbingLevel != 2)
{
return;
}
if (!DesktopVRIK.Setting_Enabled || !DesktopVRIK.Setting_EnforceViewPosition)
{
return;
}
if (____movementSystem.disableCameraControl && !ignore)
{
return;
}
if (DesktopVRIK.Instance.viewpoint == null)
{
return;
}
__instance.desktopCamera.transform.position = DesktopVRIK.Instance.viewpoint.position;
return;
}
//[HarmonyPostfix]
//[HarmonyPatch(typeof(PlayerSetup), "ReCalibrateAvatar")]
//static void Postfix_PlayerSetup_ReCalibrateAvatar()
//{
// DesktopVRIK.Instance?.OnReCalibrateAvatar();
//}
}
class IKSystemPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(IKSystem), "InitializeAvatar")]
private static void InitializeDesktopAvatarVRIK(CVRAvatar avatar, ref VRIK ____vrik, ref HumanPoseHandler ____poseHandler, ref HumanPose ___humanPose)
[HarmonyPatch(typeof(IKSystem), "Start")]
private static void Postfix_IKSystem_Start(ref IKSystem __instance)
{
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled)
{
if (IKSystem.Instance.animator != null && IKSystem.Instance.animator.avatar != null && IKSystem.Instance.animator.avatar.isHuman)
{
if (____poseHandler == null)
{
____poseHandler = new HumanPoseHandler(IKSystem.Instance.animator.avatar, IKSystem.Instance.animator.transform);
__instance.gameObject.AddComponent<DesktopVRIK>();
}
____poseHandler.GetHumanPose(ref ___humanPose);
for (int i = 0; i < IKPoseMuscles.Length; i++)
[HarmonyPrefix]
[HarmonyPatch(typeof(IKSystem), "ApplyAvatarScaleToIk")]
private static bool Prefix_IKSystem_ApplyAvatarScaleToIk(float height)
{
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ___humanPose.muscles);
}
____poseHandler.SetHumanPose(ref ___humanPose);
____vrik = DesktopVRIK.Instance.AlternativeCalibration(avatar);
IKSystem.Instance.ApplyAvatarScaleToIk(avatar.viewPosition.y);
return !(bool)DesktopVRIK.Instance?.OnApplyAvatarScaleToIk(height);
}
}
}
private static readonly float[] IKPoseMuscles = new float[]
{
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

@ -1,48 +0,0 @@
using BTKUILib;
using BTKUILib.UIObjects;
using System.Runtime.CompilerServices;
namespace NAK.Melons.DesktopVRIK;
public static class BTKUIAddon
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Init()
{
//Add myself to the Misc Menu
Page miscPage = QuickMenuAPI.MiscTabPage;
Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory);
AddMelonToggle(ref miscCategory, DesktopVRIKMod.m_entryEnabled);
//Add my own page to not clog up Misc Menu
Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK");
desktopVRIKPage.MenuTitle = "DesktopVRIK Settings";
desktopVRIKPage.MenuSubtitle = "Simplified settings for VRIK on Desktop.";
Category desktopVRIKCategory = desktopVRIKPage.AddCategory("DesktopVRIK");
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnabled);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnforceViewPosition);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteVRIK);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteLookAtIK);
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyLeanWeight, 0, 1f);
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyAngleLimit, 0, 90f);
}
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)
{
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max).OnValueUpdated += f => entry.Value = f;
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using MelonLoader;
using MelonLoader;
using UnityEngine;
namespace NAK.Melons.DesktopVRIK;
@ -8,69 +7,71 @@ public class DesktopVRIKMod : MelonMod
{
internal const string SettingsCategory = "DesktopVRIK";
internal static MelonPreferences_Category m_categoryDesktopVRIK;
internal static MelonPreferences_Entry<bool> m_entryEnabled,
internal static MelonPreferences_Entry<bool>
m_entryEnabled,
m_entryEnforceViewPosition,
m_entryEmoteVRIK,
m_entryEmoteLookAtIK;
m_entryResetIKOnLand,
m_entryPlantFeet,
m_entryUseVRIKToes,
m_entryFindUnmappedToes;
internal static MelonPreferences_Entry<float>
m_entryBodyLeanWeight,
m_entryBodyAngleLimit;
m_entryBodyHeadingLimit,
m_entryPelvisHeadingWeight,
m_entryChestHeadingWeight;
public override void OnInitializeMelon()
{
m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory);
m_entryEnabled = m_categoryDesktopVRIK.CreateEntry<bool>("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce View Position", false, description: "Corrects view position to use VRIK offsets.");
m_entryEmoteVRIK = m_categoryDesktopVRIK.CreateEntry<bool>("Disable Emote VRIK", true, description: "Disable VRIK while emoting. Only disable if you are ok with looking dumb.");
m_entryEmoteLookAtIK = m_categoryDesktopVRIK.CreateEntry<bool>("Disable Emote LookAtIK", true, description: "Disable LookAtIK while emoting. This setting doesn't really matter, as LookAtIK isn't networked while doing an emote.");
m_entryResetIKOnLand = m_categoryDesktopVRIK.CreateEntry<bool>("Reset IK On Land", true, description: "Reset Solver IK when landing on the ground.");
m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving.");
m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry<bool>("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you.");
m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry<bool>("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?");
m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry<float>("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable.");
m_entryBodyAngleLimit = m_categoryDesktopVRIK.CreateEntry<float>("Body Angle Limit", 0f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable. (this setting only affects the feet due to chillout not setting up the player controller for VRIK)");
m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry<float>("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable.");
m_entryPelvisHeadingWeight = m_categoryDesktopVRIK.CreateEntry<float>("Pelvis Heading Weight", 0.25f, description: "How much the pelvis will face the heading limit. Set to 0 to align with head.");
m_entryChestHeadingWeight = m_categoryDesktopVRIK.CreateEntry<float>("Chest Heading Weight", 0.75f, description: "How much the chest will face the heading limit. Set to 0 to align with head.");
foreach (var setting in m_categoryDesktopVRIK.Entries)
{
setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings);
}
//BTKUILib Misc Support
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
{
MelonLogger.Msg("Initializing BTKUILib support.");
BTKUIAddon.Init();
}
//Apply patches (i stole)
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
InitializeIntegrations();
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while (PlayerSetup.Instance == null)
yield return null;
DesktopVRIK_Helper.CreateInstance();
PlayerSetup.Instance.gameObject.AddComponent<DesktopVRIK>();
while (DesktopVRIK.Instance == null)
yield return null;
UpdateAllSettings();
}
private void UpdateAllSettings()
internal static void UpdateAllSettings()
{
if (!DesktopVRIK.Instance) return;
DesktopVRIK.Setting_Enabled = m_entryEnabled.Value;
DesktopVRIK.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value);
DesktopVRIK.Setting_BodyAngleLimit = Mathf.Clamp(m_entryBodyAngleLimit.Value, 0f, 90f);
DesktopVRIK.Setting_EmoteVRIK = m_entryEmoteVRIK.Value;
DesktopVRIK.Setting_EmoteLookAtIK = m_entryEmoteLookAtIK.Value;
DesktopVRIK.Instance.ChangeViewpointHandling(m_entryEnforceViewPosition.Value);
// DesktopVRIK Settings
DesktopVRIK.Instance.Setting_Enabled = m_entryEnabled.Value;
DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value);
DesktopVRIK.Instance.Setting_ResetOnLand = m_entryResetIKOnLand.Value;
DesktopVRIK.Instance.Setting_PlantFeet = m_entryPlantFeet.Value;
DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(m_entryBodyHeadingLimit.Value, 0f, 90f);
DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(m_entryPelvisHeadingWeight.Value));
DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(m_entryChestHeadingWeight.Value));
// Calibration Settings
DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value;
DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value;
}
private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
private static void InitializeIntegrations()
{
//BTKUILib Misc Tab
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
{
MelonLogger.Msg("Initializing BTKUILib support.");
//BTKUIAddon.Init();
}
}
private void ApplyPatches(Type type)
{
try

View file

@ -1,8 +1,7 @@
using DesktopVRIK.Properties;
using MelonLoader;
using MelonLoader;
using NAK.Melons.DesktopVRIK.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
@ -24,9 +23,9 @@ using System.Reflection;
[assembly: MelonOptionalDependencies("BTKUILib")]
[assembly: HarmonyDontPatchAll]
namespace DesktopVRIK.Properties;
namespace NAK.Melons.DesktopVRIK.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "2.0.5";
public const string Version = "4.0.0";
public const string Author = "NotAKidoS";
}