diff --git a/README.md b/README.md
index 5b3f57d..230bde6 100644
--- a/README.md
+++ b/README.md
@@ -11,4 +11,5 @@ Merged set of MelonLoader mods for ChilloutVR.
| Four Point Tracking | ml_fpt | 1.0.9 | Retired | Deprecated | In-game feature since 2022r170 update
| Leap Motion Extension | ml_lme | 1.3.3 | Yes, update review | Working |
| Pickup Arm Movement | ml_pam | 1.0.2 | Yes, update review| Working |
+| Player Ragdoll Mod | ml_prm | 1.0.0 | Yes, publish review | Working |
| Server Connection Info | ml_sci | 1.0.2 | Retired | Retired | Superseded by `Extended Game Notifications`
diff --git a/ml_amt/ml_amt.csproj.user b/ml_amt/ml_amt.csproj.user
index 04df561..d2e6c0a 100644
--- a/ml_amt/ml_amt.csproj.user
+++ b/ml_amt/ml_amt.csproj.user
@@ -1,6 +1,6 @@
- D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_dht/ml_dht.csproj.user b/ml_dht/ml_dht.csproj.user
index 04df561..d2e6c0a 100644
--- a/ml_dht/ml_dht.csproj.user
+++ b/ml_dht/ml_dht.csproj.user
@@ -1,6 +1,6 @@
- D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_drs/ml_drs.csproj b/ml_drs/ml_drs.csproj
index 41b71ab..3809559 100644
--- a/ml_drs/ml_drs.csproj
+++ b/ml_drs/ml_drs.csproj
@@ -35,6 +35,7 @@
False
False
+ D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll
C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll
@@ -44,8 +45,9 @@
C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll
False
-
- C:\Games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll
False
@@ -69,6 +71,6 @@
- copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"
+ copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"
\ No newline at end of file
diff --git a/ml_drs/ml_drs.csproj.user b/ml_drs/ml_drs.csproj.user
index 2539084..5c67b34 100644
--- a/ml_drs/ml_drs.csproj.user
+++ b/ml_drs/ml_drs.csproj.user
@@ -1,6 +1,6 @@
- C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_egn/ml_egn.csproj.user b/ml_egn/ml_egn.csproj.user
index 04df561..d2e6c0a 100644
--- a/ml_egn/ml_egn.csproj.user
+++ b/ml_egn/ml_egn.csproj.user
@@ -1,6 +1,6 @@
- D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_lme/ml_lme.csproj.user b/ml_lme/ml_lme.csproj.user
index 04df561..d2e6c0a 100644
--- a/ml_lme/ml_lme.csproj.user
+++ b/ml_lme/ml_lme.csproj.user
@@ -1,6 +1,6 @@
- D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_mods_cvr.sln b/ml_mods_cvr.sln
index d9193c5..19f4b60 100644
--- a/ml_mods_cvr.sln
+++ b/ml_mods_cvr.sln
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_egn", "ml_egn\ml_egn.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_pam", "ml_pam\ml_pam.csproj", "{3B5028DE-8C79-40DF-A1EF-BDB29D366125}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_prm", "ml_prm\ml_prm.csproj", "{ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -45,6 +47,10 @@ Global
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Debug|x64.Build.0 = Debug|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Release|x64.ActiveCfg = Release|x64
{3B5028DE-8C79-40DF-A1EF-BDB29D366125}.Release|x64.Build.0 = Release|x64
+ {ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Debug|x64.ActiveCfg = Debug|x64
+ {ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Debug|x64.Build.0 = Debug|x64
+ {ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Release|x64.ActiveCfg = Release|x64
+ {ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ml_pam/ml_pam.csproj.user b/ml_pam/ml_pam.csproj.user
index 04df561..d2e6c0a 100644
--- a/ml_pam/ml_pam.csproj.user
+++ b/ml_pam/ml_pam.csproj.user
@@ -1,6 +1,6 @@
- D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\
\ No newline at end of file
diff --git a/ml_prm/Main.cs b/ml_prm/Main.cs
new file mode 100644
index 0000000..806d6e2
--- /dev/null
+++ b/ml_prm/Main.cs
@@ -0,0 +1,98 @@
+using ABI_RC.Core.Player;
+using System;
+using System.Reflection;
+using ABI_RC.Core.InteractionSystem;
+using UnityEngine;
+
+namespace ml_prm
+{
+ public class PlayerRagdollMod : MelonLoader.MelonMod
+ {
+ static PlayerRagdollMod ms_instance = null;
+
+ RagdollController m_localController = 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(PlayerRagdollMod).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
+ );
+ HarmonyInstance.Patch(
+ typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
+ null,
+ new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
+ );
+ HarmonyInstance.Patch(
+ typeof(CVRSeat).GetMethod(nameof(CVRSeat.SitDown)),
+ new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnCVRSeatSitDown_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
+ null
+ );
+
+ MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
+ }
+
+ public override void OnDeinitializeMelon()
+ {
+ if(ms_instance == this)
+ ms_instance = null;
+ }
+
+ System.Collections.IEnumerator WaitForLocalPlayer()
+ {
+ while(PlayerSetup.Instance == null)
+ yield return null;
+
+ m_localController = PlayerSetup.Instance.gameObject.AddComponent();
+ }
+
+ static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
+ void OnAvatarClear()
+ {
+ try
+ {
+ if(m_localController != null)
+ m_localController.OnAvatarClear();
+ }
+ catch(Exception e)
+ {
+ MelonLoader.MelonLogger.Error(e);
+ }
+ }
+
+ static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
+ void OnSetupAvatar()
+ {
+ try
+ {
+ if(m_localController != null)
+ m_localController.OnAvatarSetup();
+ }
+ catch(Exception e)
+ {
+ MelonLoader.MelonLogger.Error(e);
+ }
+ }
+
+ static void OnCVRSeatSitDown_Prefix(ref CVRSeat __instance) => ms_instance?.OnCVRSeatSitDown(__instance);
+ void OnCVRSeatSitDown(CVRSeat p_seat)
+ {
+ try
+ {
+ if(m_localController != null)
+ m_localController.OnSeatSitDown(p_seat);
+ }
+ catch(Exception e)
+ {
+ MelonLoader.MelonLogger.Error(e);
+ }
+ }
+
+ }
+}
diff --git a/ml_prm/Properties/AssemblyInfo.cs b/ml_prm/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9e478a5
--- /dev/null
+++ b/ml_prm/Properties/AssemblyInfo.cs
@@ -0,0 +1,12 @@
+using System.Reflection;
+
+[assembly: AssemblyTitle("PlayerRagdollMod")]
+[assembly: AssemblyVersion("1.0.0")]
+[assembly: AssemblyFileVersion("1.0.0")]
+
+[assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.0.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
+[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
+[assembly: MelonLoader.MelonPriority(2)]
+[assembly: MelonLoader.MelonOptionalDependencies("BTKUILib")]
+[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
+[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
\ No newline at end of file
diff --git a/ml_prm/README.md b/ml_prm/README.md
new file mode 100644
index 0000000..9958531
--- /dev/null
+++ b/ml_prm/README.md
@@ -0,0 +1,21 @@
+# Player Ragdoll Mod
+This mod turns your player's avatar into ragdoll puppet.
+
+# Installation
+* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
+* Get [latest release DLL](../../../releases/latest):
+ * Put `ml_prm.dll` in `Mods` folder of game
+
+# Usage
+* Press `R` to turn into ragdoll and back.
+
+Optional mod's settings with [BTKUILib](https://github.com/BTK-Development/BTKUILib):
+* **Switch ragdoll:** turns into ragdoll state and back, made for VR usage primarily.
+* **Use hotkey:** enables/disables ragdoll state switch with `R` key; `true` by default.
+* **Restore position:** returns to position of ragdoll state activation upon ragdoll state exit; `false` by default.
+* **Velocity multiplier:** velocity force multiplier based on player's movement direction; `2.0` by default.
+
+# Notes
+* Incompatible with `Follow hips on IK override` option in AvatarMotionTweaker.
+* Even if locally ragdoll state is activated in the middle of playing emote, remote players still see whole emote animation.
+* Not suggested to activate fly mode with enabled ragdoll state.
diff --git a/ml_prm/RagdollController.cs b/ml_prm/RagdollController.cs
new file mode 100644
index 0000000..4615da4
--- /dev/null
+++ b/ml_prm/RagdollController.cs
@@ -0,0 +1,246 @@
+using ABI_RC.Core.InteractionSystem;
+using ABI_RC.Core.Player;
+using ABI_RC.Systems.MovementSystem;
+using RootMotion.Dynamics;
+using RootMotion.FinalIK;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace ml_prm
+{
+ class RagdollController : MonoBehaviour
+ {
+ VRIK m_vrIK = null;
+ float m_vrIkWeight = 1f;
+
+ bool m_enabled = false;
+
+ readonly List m_rigidBodies = null;
+ readonly List m_colliders = null;
+ Transform m_puppetRoot = null;
+ Transform m_puppet = null;
+ BipedRagdollReferences m_puppetReferences;
+ BipedRagdollReferences m_avatarReferences;
+ readonly List> m_boneLinks = null;
+
+ bool m_avatarReady = false;
+ Vector3 m_lastPosition = Vector3.zero;
+ Vector3 m_velocity = Vector3.zero;
+
+ internal RagdollController()
+ {
+ m_rigidBodies = new List();
+ m_colliders = new List();
+ m_boneLinks = new List>();
+ }
+
+ // Unity events
+ void Start()
+ {
+ m_puppetRoot = new GameObject("[PlayerAvatarPuppet]").transform;
+ m_puppetRoot.parent = PlayerSetup.Instance.transform;
+ m_puppetRoot.localPosition = Vector3.zero;
+ m_puppetRoot.localRotation = Quaternion.identity;
+
+ Settings.SwitchChange += this.SwitchRagdoll;
+ }
+
+ void OnDestroy()
+ {
+ Settings.SwitchChange -= this.SwitchRagdoll;
+ }
+
+ void Update()
+ {
+ Vector3 l_pos = PlayerSetup.Instance.transform.position;
+ m_velocity = (m_velocity + (l_pos - m_lastPosition) / Time.deltaTime) * 0.5f;
+ m_lastPosition = l_pos;
+
+ if(Settings.Hotkey && Input.GetKeyDown(KeyCode.R) && !ViewManager.Instance.isGameMenuOpen())
+ SwitchRagdoll();
+ }
+
+ void LateUpdate()
+ {
+ if(m_enabled && m_avatarReady)
+ {
+ foreach(var l_link in m_boneLinks)
+ l_link.Item1.CopyGlobal(l_link.Item2);
+ }
+ }
+
+ // Game events
+ internal void OnAvatarClear()
+ {
+ if(m_enabled)
+ MovementSystem.Instance.SetImmobilized(false);
+
+ if(m_puppet != null)
+ Object.Destroy(m_puppet.gameObject);
+ m_puppet = null;
+
+ m_vrIK = null;
+ m_enabled = false;
+ m_avatarReady = false;
+ m_rigidBodies.Clear();
+ m_colliders.Clear();
+ m_avatarReferences = new BipedRagdollReferences();
+ m_puppetReferences = new BipedRagdollReferences();
+ m_boneLinks.Clear();
+ }
+
+ internal void OnAvatarSetup()
+ {
+ if(PlayerSetup.Instance._animator.isHuman)
+ {
+ m_avatarReferences = BipedRagdollReferences.FromAvatar(PlayerSetup.Instance._animator);
+
+ m_puppet = new GameObject("Root").transform;
+ m_puppet.parent = m_puppetRoot;
+ m_puppet.localPosition = Vector3.zero;
+ m_puppet.localRotation = Quaternion.identity;
+
+ m_puppetReferences.root = m_puppet;
+ m_puppetReferences.hips = CloneTransform(m_avatarReferences.hips, m_puppetReferences.root, "Hips");
+ m_puppetReferences.spine = CloneTransform(m_avatarReferences.spine, m_puppetReferences.hips, "Spine");
+ m_puppetReferences.chest = CloneTransform(m_avatarReferences.chest, m_puppetReferences.spine, "Chest");
+ m_puppetReferences.head = CloneTransform(m_avatarReferences.head, m_puppetReferences.chest, "Head");
+
+ m_puppetReferences.leftUpperArm = CloneTransform(m_avatarReferences.leftUpperArm, m_puppetReferences.chest, "LeftUpperArm");
+ m_puppetReferences.leftLowerArm = CloneTransform(m_avatarReferences.leftLowerArm, m_puppetReferences.leftUpperArm, "LeftLowerArm");
+ m_puppetReferences.leftHand = CloneTransform(m_avatarReferences.leftHand, m_puppetReferences.leftLowerArm, "LeftHand");
+
+ m_puppetReferences.rightUpperArm = CloneTransform(m_avatarReferences.rightUpperArm, m_puppetReferences.chest, "RightUpperArm");
+ m_puppetReferences.rightLowerArm = CloneTransform(m_avatarReferences.rightLowerArm, m_puppetReferences.rightUpperArm, "RightLowerArm");
+ m_puppetReferences.rightHand = CloneTransform(m_avatarReferences.rightHand, m_puppetReferences.rightLowerArm, "RightHand");
+
+ m_puppetReferences.leftUpperLeg = CloneTransform(m_avatarReferences.leftUpperLeg, m_puppetReferences.hips, "LeftUpperLeg");
+ m_puppetReferences.leftLowerLeg = CloneTransform(m_avatarReferences.leftLowerLeg, m_puppetReferences.leftUpperLeg, "LeftLowerLeg");
+ m_puppetReferences.leftFoot = CloneTransform(m_avatarReferences.leftFoot, m_puppetReferences.leftLowerLeg, "LeftFoot");
+
+ m_puppetReferences.rightUpperLeg = CloneTransform(m_avatarReferences.rightUpperLeg, m_puppetReferences.hips, "RightUpperLeg");
+ m_puppetReferences.rightLowerLeg = CloneTransform(m_avatarReferences.rightLowerLeg, m_puppetReferences.rightUpperLeg, "RightLowerLeg");
+ m_puppetReferences.rightFoot = CloneTransform(m_avatarReferences.rightFoot, m_puppetReferences.rightLowerLeg, "RightFoot");
+
+ BipedRagdollCreator.Options l_options = BipedRagdollCreator.AutodetectOptions(m_puppetReferences);
+ l_options.joints = RagdollCreator.JointType.Character;
+ BipedRagdollCreator.Create(m_puppetReferences, l_options);
+
+ Transform[] l_puppetTransforms = m_puppetReferences.GetRagdollTransforms();
+ Transform[] l_avatarTransforms = m_avatarReferences.GetRagdollTransforms();
+ for(int i = 0; i < l_puppetTransforms.Length; i++)
+ {
+ if(l_puppetTransforms[i] != null)
+ {
+ Rigidbody l_body = l_puppetTransforms[i].GetComponent();
+ if(l_body != null)
+ {
+ m_rigidBodies.Add(l_body);
+ l_body.isKinematic = true;
+ l_body.angularDrag = 0.5f;
+ l_body.drag = 1.0f;
+ l_body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
+ }
+
+ CharacterJoint l_joint = l_puppetTransforms[i].GetComponent();
+ if(l_joint != null)
+ {
+ l_joint.enablePreprocessing = false;
+ l_joint.enableProjection = true;
+ }
+
+ Collider l_collider = l_puppetTransforms[i].GetComponent();
+ if(l_collider != null)
+ {
+ Physics.IgnoreCollision(MovementSystem.Instance.proxyCollider, l_collider, true);
+ l_collider.enabled = false;
+ m_colliders.Add(l_collider);
+ }
+
+ if(l_avatarTransforms[i] != null)
+ m_boneLinks.Add(System.Tuple.Create(l_puppetTransforms[i], l_avatarTransforms[i]));
+ }
+ }
+
+ m_vrIK = PlayerSetup.Instance._avatar.GetComponent();
+ if(m_vrIK != null)
+ {
+ m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
+ m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
+ }
+
+ m_avatarReady = true;
+ }
+ }
+
+ internal void OnSeatSitDown(CVRSeat p_seat)
+ {
+ if(m_enabled && m_avatarReady && !p_seat.occupied)
+ SwitchRagdoll();
+ }
+
+ // IK updates
+ void OnIKPreUpdate()
+ {
+ if(m_enabled)
+ {
+ m_vrIkWeight = m_vrIK.solver.IKPositionWeight;
+ m_vrIK.solver.IKPositionWeight = 0f;
+ }
+ }
+ void OnIKPostUpdate()
+ {
+ if(m_enabled)
+ m_vrIK.solver.IKPositionWeight = m_vrIkWeight;
+ }
+
+ // Arbitrary
+ public void SwitchRagdoll()
+ {
+ if(m_avatarReady && (MovementSystem.Instance.lastSeat == null))
+ {
+ m_enabled = !m_enabled;
+
+ MovementSystem.Instance.SetImmobilized(m_enabled);
+
+ if(m_enabled)
+ {
+ foreach(var l_link in m_boneLinks)
+ l_link.Item2.CopyGlobal(l_link.Item1);
+
+ foreach(Rigidbody l_body in m_rigidBodies)
+ l_body.isKinematic = false;
+
+ Vector3 l_velocity = m_velocity * Settings.Multiplier;
+ foreach(Rigidbody l_body in m_rigidBodies)
+ {
+ l_body.velocity = l_velocity;
+ l_body.angularVelocity = Vector3.zero;
+ }
+ }
+ else
+ {
+ foreach(Rigidbody l_body in m_rigidBodies)
+ l_body.isKinematic = true;
+
+ if(!Settings.RestorePosition && (m_puppetReferences.hips != null))
+ {
+ Vector3 l_pos = m_puppetReferences.hips.position;
+ PlayerSetup.Instance.transform.position = l_pos;
+ }
+ }
+
+ foreach(Collider l_collider in m_colliders)
+ l_collider.enabled = m_enabled;
+ }
+ }
+
+ static Transform CloneTransform(Transform p_source, Transform p_parent, string p_name)
+ {
+ Transform l_target = new GameObject(p_name).transform;
+ l_target.parent = p_parent;
+ p_source.CopyGlobal(l_target);
+ return l_target;
+ }
+ }
+}
diff --git a/ml_prm/Settings.cs b/ml_prm/Settings.cs
new file mode 100644
index 0000000..ea68fa3
--- /dev/null
+++ b/ml_prm/Settings.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ml_prm
+{
+ static class Settings
+ {
+ public enum ModSetting
+ {
+ Hotkey = 0,
+ Multiplier,
+ RestorePosition
+ }
+
+ public static bool Hotkey { get; private set; } = true;
+ public static float Multiplier { get; private set; } = 2f;
+ public static bool RestorePosition { get; private set; } = false;
+
+ static public event Action SwitchChange;
+ static public event Action HotkeyChange;
+ static public event Action RestorePositionChange;
+ static public event Action MultiplierChange;
+
+ static MelonLoader.MelonPreferences_Category ms_category = null;
+ static List ms_entries = null;
+
+ internal static void Init()
+ {
+ ms_category = MelonLoader.MelonPreferences.CreateCategory("PRM");
+ ms_entries = new List()
+ {
+ ms_category.CreateEntry(ModSetting.Hotkey.ToString(), Hotkey),
+ ms_category.CreateEntry(ModSetting.Multiplier.ToString(), Multiplier),
+ ms_category.CreateEntry(ModSetting.RestorePosition.ToString(), RestorePosition),
+ };
+
+ Hotkey = (bool)ms_entries[(int)ModSetting.Hotkey].BoxedValue;
+ Multiplier = (float)ms_entries[(int)ModSetting.Multiplier].BoxedValue;
+ RestorePosition = (bool)ms_entries[(int)ModSetting.RestorePosition].BoxedValue;
+
+ if(MelonLoader.MelonMod.RegisteredMelons.First(m => m.Info.Name == "BTKUILib") != null)
+ {
+ CreateBtkUi();
+ }
+ }
+
+ static void CreateBtkUi()
+ {
+ var l_categoryMain = BTKUILib.QuickMenuAPI.MiscTabPage.AddCategory("PlayerRagdollMod");
+ var l_page = l_categoryMain.AddPage("Player Ragdoll Settings", "", "PlayerRagdollMod settings", "PlayerRagdollMod");
+ l_page.MenuTitle = "Ragdoll settings";
+ var l_categoryMod = l_page.AddCategory("Settings");
+
+ l_categoryMod.AddButton("Switch ragdoll", "", "Switch between normal and ragdoll state").OnPress += () =>
+ {
+ SwitchChange?.Invoke();
+ };
+ l_categoryMod.AddToggle("Use hotkey", "Switch ragdoll mode with 'R' key", Hotkey).OnValueUpdated += (state) =>
+ {
+ Hotkey = state;
+ ms_entries[(int)ModSetting.Hotkey].BoxedValue = state;
+ HotkeyChange?.Invoke(Hotkey);
+ };
+ l_categoryMod.AddToggle("Restore position", "Bring avatar back where ragdoll state was activated", RestorePosition).OnValueUpdated += (state) =>
+ {
+ RestorePosition = state;
+ ms_entries[(int)ModSetting.RestorePosition].BoxedValue = state;
+ RestorePositionChange?.Invoke(Hotkey);
+ };
+ l_page.AddSlider("Velocity multiplier", "Velocity multiplier upon entering ragdoll state", Multiplier, 1f, 50f).OnValueUpdated += (value) =>
+ {
+ Multiplier = value;
+ ms_entries[(int)ModSetting.Multiplier].BoxedValue = value;
+ MultiplierChange?.Invoke(value);
+ };
+ }
+ }
+}
diff --git a/ml_prm/Utils.cs b/ml_prm/Utils.cs
new file mode 100644
index 0000000..76fa779
--- /dev/null
+++ b/ml_prm/Utils.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace ml_prm
+{
+ static class Utils
+ {
+ public static void CopyGlobal(this Transform p_source, Transform p_target)
+ {
+ p_target.position = p_source.position;
+ p_target.rotation = p_source.rotation;
+ }
+ }
+}
diff --git a/ml_prm/ml_prm.csproj b/ml_prm/ml_prm.csproj
new file mode 100644
index 0000000..8c2917c
--- /dev/null
+++ b/ml_prm/ml_prm.csproj
@@ -0,0 +1,140 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}
+ Library
+ Properties
+ ml_prm
+ ml_prm
+ v4.7.2
+ 512
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll
+ False
+
+
+ False
+ False
+
+
+ False
+ False
+
+
+ D:\games\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll
+ False
+
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll
+ False
+
+
+
+
+
+
+
+
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll
+ False
+
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.ClothModule.dll
+ False
+
+
+ False
+ False
+
+
+ False
+ False
+
+
+ False
+ D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"
+
+
\ No newline at end of file
diff --git a/ml_prm/ml_prm.csproj.user b/ml_prm/ml_prm.csproj.user
new file mode 100644
index 0000000..4d08fe9
--- /dev/null
+++ b/ml_prm/ml_prm.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\;D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\
+
+
\ No newline at end of file
diff --git a/ml_prm/vendor/RootMotion/ReadMe.md b/ml_prm/vendor/RootMotion/ReadMe.md
new file mode 100644
index 0000000..5455e7a
--- /dev/null
+++ b/ml_prm/vendor/RootMotion/ReadMe.md
@@ -0,0 +1,2 @@
+* Buy https://assetstore.unity.com/packages/tools/physics/puppetmaster-48977
+* Put `PuppetMaster` and `RagdollManager` scripts in this folder