Moved PlayerRagdollMod and DesktopHeadTracking to archived

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

BIN
archived/ml_dht/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

View file

@ -0,0 +1,185 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
using ViveSR.anipal.Lip;
namespace ml_dht
{
[DisallowMultipleComponent]
class HeadTracked : MonoBehaviour
{
static FieldInfo ms_emotePlaying = typeof(PlayerSetup).GetField("_emotePlaying", BindingFlags.NonPublic | BindingFlags.Instance);
bool m_enabled = false;
bool m_headTracking = true;
bool m_blinking = true;
bool m_eyeTracking = true;
float m_smoothing = 0.5f;
bool m_mirrored = false;
bool m_faceOverride = true;
CVRAvatar m_avatarDescriptor = null;
LookAtIK m_lookIK = null;
Transform m_headBone = null;
Vector3 m_headPosition;
Quaternion m_headRotation;
Vector2 m_gazeDirection;
float m_blinkProgress = 0f;
Vector2 m_mouthShapes;
float m_eyebrowsProgress = 0f;
Quaternion m_bindRotation;
Quaternion m_lastHeadRotation;
// Unity events
void Start()
{
Settings.EnabledChange += this.SetEnabled;
Settings.HeadTrackingChange += this.SetHeadTracking;
Settings.EyeTrackingChange += this.SetEyeTracking;
Settings.BlinkingChange += this.SetBlinking;
Settings.SmoothingChange += this.SetSmoothing;
Settings.MirroredChange += this.SetMirrored;
Settings.FaceOverrideChange += this.SetFaceOverride;
}
void OnDestroy()
{
Settings.EnabledChange -= this.SetEnabled;
Settings.HeadTrackingChange -= this.SetHeadTracking;
Settings.EyeTrackingChange -= this.SetEyeTracking;
Settings.BlinkingChange -= this.SetBlinking;
Settings.SmoothingChange -= this.SetSmoothing;
Settings.MirroredChange -= this.SetMirrored;
Settings.FaceOverrideChange -= this.SetFaceOverride;
}
// Tracking updates
public void UpdateTrackingData(ref TrackingData p_data)
{
m_headPosition.Set(p_data.m_headPositionX * (m_mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ);
m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (m_mirrored ? -1f : 1f), p_data.m_headRotationZ * (m_mirrored ? -1f : 1f), p_data.m_headRotationW);
m_gazeDirection.Set(m_mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
m_blinkProgress = p_data.m_blink;
m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape);
m_eyebrowsProgress = p_data.m_brows;
}
void OnLookIKPostUpdate()
{
if(m_enabled && m_headTracking && (m_headBone != null))
{
m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_avatarDescriptor.transform.rotation * (m_headRotation * m_bindRotation), m_smoothing);
if(!(bool)ms_emotePlaying.GetValue(PlayerSetup.Instance))
m_headBone.rotation = m_lastHeadRotation;
}
}
// Game events
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(m_eyeTracking)
{
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
p_component.manualViewTarget = true;
p_component.targetViewPosition = l_camera.position + l_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(m_blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
internal void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
if(m_enabled && m_faceOverride)
{
if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false;
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y))) * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Pout] = ((m_mouthShapes.y > 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Left] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Right] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.LipSyncWasUpdated = true;
p_component.UpdateLipShapes();
}
}
internal void OnSetupAvatar()
{
m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>();
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
m_lookIK = PlayerSetup.Instance._avatar.GetComponent<LookAtIK>();
if(m_headBone != null)
m_bindRotation = (m_avatarDescriptor.transform.GetMatrix().inverse * m_headBone.GetMatrix()).rotation;
if(m_lookIK != null)
m_lookIK.solver.OnPostUpdate += this.OnLookIKPostUpdate;
}
internal void OnAvatarClear()
{
m_avatarDescriptor = null;
m_lookIK = null;
m_headBone = null;
m_lastHeadRotation = Quaternion.identity;
m_bindRotation = Quaternion.identity;
}
// Settings
internal void SetEnabled(bool p_state)
{
if(m_enabled != p_state)
{
m_enabled = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
}
internal void SetHeadTracking(bool p_state)
{
if(m_headTracking != p_state)
{
m_headTracking = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
}
internal void SetEyeTracking(bool p_state)
{
m_eyeTracking = p_state;
}
internal void SetBlinking(bool p_state)
{
m_blinking = p_state;
}
internal void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
internal void SetMirrored(bool p_state)
{
m_mirrored = p_state;
}
internal void SetFaceOverride(bool p_state)
{
m_faceOverride = p_state;
}
}
}

146
archived/ml_dht/Main.cs Normal file
View file

@ -0,0 +1,146 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using System.Reflection;
namespace ml_dht
{
public class DesktopHeadTracking : MelonLoader.MelonMod
{
static DesktopHeadTracking ms_instance = null;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
HeadTracked m_localTracked = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
m_mapReader = new MemoryMapReader();
m_buffer = new byte[1024];
m_mapReader.Open("head/data");
// Patches
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVRFaceTracking).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForPlayer());
}
System.Collections.IEnumerator WaitForPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
m_localTracked.SetEnabled(Settings.Enabled);
m_localTracked.SetHeadTracking(Settings.HeadTracking);
m_localTracked.SetEyeTracking(Settings.EyeTracking);
m_localTracked.SetBlinking(Settings.Blinking);
m_localTracked.SetMirrored(Settings.Mirrored);
m_localTracked.SetSmoothing(Settings.Smoothing);
m_localTracked.SetFaceOverride(Settings.FaceOverride);
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_mapReader?.Close();
m_mapReader = null;
m_buffer = null;
m_localTracked = null;
}
public override void OnUpdate()
{
if(Settings.Enabled && m_mapReader.Read(ref m_buffer))
{
m_trackingData = TrackingData.ToObject(m_buffer);
if(m_localTracked != null)
m_localTracked.UpdateTrackingData(ref m_trackingData);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localTracked != null)
m_localTracked.OnSetupAvatar();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localTracked != null)
m_localTracked.OnAvatarClear();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnEyeControllerUpdate_Postfix(ref CVREyeController __instance) => ms_instance?.OnEyeControllerUpdate(__instance);
void OnEyeControllerUpdate(CVREyeController p_component)
{
try
{
if(p_component.isLocal && (m_localTracked != null))
m_localTracked.OnEyeControllerUpdate(p_component);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnFaceTrackingUpdate_Postfix(ref CVRFaceTracking __instance) => ms_instance?.OnFaceTrackingUpdate(__instance);
void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
try
{
if(p_component.isLocal && (m_localTracked != null))
m_localTracked.OnFaceTrackingUpdate(p_component);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,55 @@
using System.IO;
using System.IO.MemoryMappedFiles;
namespace ml_dht
{
class MemoryMapReader
{
MemoryMappedFile m_file = null;
MemoryMappedViewStream m_stream = null;
int m_dataSize = 0;
public bool Open(string p_path, int p_dataSize = 1024)
{
if(m_file == null)
{
m_dataSize = p_dataSize;
m_file = MemoryMappedFile.CreateOrOpen(p_path, m_dataSize, MemoryMappedFileAccess.ReadWrite);
m_stream = m_file.CreateViewStream(0, m_dataSize, MemoryMappedFileAccess.Read);
}
return (m_file != null);
}
public bool Read(ref byte[] p_data)
{
bool l_result = false;
if((m_stream != null) && m_stream.CanRead)
{
try
{
m_stream.Seek(0, SeekOrigin.Begin);
m_stream.Read(p_data, 0, (p_data.Length > m_dataSize) ? m_dataSize : p_data.Length);
l_result = true;
}
catch(System.Exception) { }
}
return l_result;
}
public void Close()
{
if(m_file != null)
{
m_stream.Close();
m_stream.Dispose();
m_stream = null;
m_file.Dispose();
m_file = null;
m_dataSize = 0;
}
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.1.2-ex", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

29
archived/ml_dht/README.md Normal file
View file

@ -0,0 +1,29 @@
# Desktop Head Tracking
This mod adds desktop head tracking based on data from memory-mapped file `head/data`.
Refer to `TrackingData.cs` for reference in case of implementing own software.
[![](.github/img_01.png)](https://youtu.be/jgcFhSNi9DM)
# Features
* Head rotation
* Eyes gaze direction
* Blinking
* Basic mouth shapes
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_dht.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:
* **Enabled:** enables mod's activity; default value - `false`.
* **Use head tracking:** enables head tracking; default value - `true`.
* **Use eyes tracking:** uses eyes tracking from data; default value - `true`.
* **Use blinking:** uses blinking from data; default value - `true`.
* **Mirrored movement:** mirrors movement and gaze along 0YZ plane; default value - `false`.
* **Movement smoothing:** smoothing factor between new and old movement data; default value - `50`.
* **Override face tracking:** overrides and activates avatar's `VRC Face Tracking` components. List of used shapes: `Jaw_Open`, `Mouth_Pout`, `Mouth_Smile_Left`, `Mouth_Smile_Right`; default value - `true`.
# Known compatible tracking software
* [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_dht
{
static class Scripts
{
public static string GetEmbeddedScript(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}
catch(Exception) { }
return l_result;
}
}
}

163
archived/ml_dht/Settings.cs Normal file
View file

@ -0,0 +1,163 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_dht
{
static class Settings
{
enum ModSetting
{
Enabled = 0,
HeadTracking,
EyeTracking,
Blinking,
Mirrored,
Smoothing,
FaceOverride
}
public static bool Enabled { get; private set; } = false;
public static bool HeadTracking { get; private set; } = true;
public static bool EyeTracking { get; private set; } = true;
public static bool Blinking { get; private set; } = true;
public static bool Mirrored { get; private set; } = false;
public static float Smoothing { get; private set; } = 0.5f;
public static bool FaceOverride { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
static public event Action<bool> HeadTrackingChange;
static public event Action<bool> EyeTrackingChange;
static public event Action<bool> BlinkingChange;
static public event Action<bool> MirroredChange;
static public event Action<float> SmoothingChange;
static public event Action<bool> FaceOverrideChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("DHT");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), HeadTracking),
ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), EyeTracking),
ms_category.CreateEntry(ModSetting.Blinking.ToString(), Blinking),
ms_category.CreateEntry(ModSetting.Mirrored.ToString(), Mirrored),
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 50f)),
ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), FaceOverride)
};
Load();
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
FaceOverride = (bool)ms_entries[(int)ModSetting.FaceOverride].BoxedValue;
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Smoothing:
{
Smoothing = int.Parse(p_value) * 0.01f;
SmoothingChange?.Invoke(Smoothing);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
case ModSetting.HeadTracking:
{
HeadTracking = bool.Parse(p_value);
HeadTrackingChange?.Invoke(HeadTracking);
}
break;
case ModSetting.EyeTracking:
{
EyeTracking = bool.Parse(p_value);
EyeTrackingChange?.Invoke(EyeTracking);
}
break;
case ModSetting.Blinking:
{
Blinking = bool.Parse(p_value);
BlinkingChange?.Invoke(Blinking);
}
break;
case ModSetting.Mirrored:
{
Mirrored = bool.Parse(p_value);
MirroredChange?.Invoke(Mirrored);
}
break;
case ModSetting.FaceOverride:
{
FaceOverride = bool.Parse(p_value);
FaceOverrideChange?.Invoke(FaceOverride);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Runtime.InteropServices;
struct TrackingData
{
public float m_headPositionX; // Not used yet
public float m_headPositionY; // Not used yet
public float m_headPositionZ; // Not used yet
public float m_headRotationX;
public float m_headRotationY;
public float m_headRotationZ;
public float m_headRotationW;
public float m_gazeX; // Range - [0;1], 0.5 - center
public float m_gazeY; // Range - [0;1], 0.5 - center
public float m_blink; // Range - [0;1], 1.0 - closed
public float m_mouthOpen; // Range - [0;1]
public float m_mouthShape; // Range - [-1;1], -1 - wide, 1 - narrow
public float m_brows; // Range - [-1;1], -1 - up, 1 - down; not used yet
static public byte[] ToBytes(TrackingData p_faceData)
{
int l_size = Marshal.SizeOf(p_faceData);
byte[] l_arr = new byte[l_size];
IntPtr ptr = Marshal.AllocHGlobal(l_size);
Marshal.StructureToPtr(p_faceData, ptr, true);
Marshal.Copy(ptr, l_arr, 0, l_size);
Marshal.FreeHGlobal(ptr);
return l_arr;
}
static public TrackingData ToObject(byte[] p_buffer)
{
TrackingData l_faceData = new TrackingData();
int l_size = Marshal.SizeOf(l_faceData);
IntPtr l_ptr = Marshal.AllocHGlobal(l_size);
Marshal.Copy(p_buffer, 0, l_ptr, l_size);
l_faceData = (TrackingData)Marshal.PtrToStructure(l_ptr, l_faceData.GetType());
Marshal.FreeHGlobal(l_ptr);
return l_faceData;
}
}

18
archived/ml_dht/Utils.cs Normal file
View file

@ -0,0 +1,18 @@
using ABI_RC.Core.UI;
using System.Reflection;
using UnityEngine;
namespace ml_dht
{
static class Utils
{
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
public static void ExecuteScript(this CohtmlControlledViewDisposable p_viewDisposable, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_viewDisposable))?.ExecuteScript(p_script);
}
}

View file

@ -0,0 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<PackageId>DesktopHeadTracking</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>DesktopHeadTracking</Product>
<Version>1.1.2</Version>
<Platforms>x64</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="DesktopHeadTracking.json" />
<None Remove="resources\menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -0,0 +1,245 @@
// Add settings
var g_modSettingsDHT = [];
engine.on('updateModSettingDHT', function (_name, _value) {
for (var i = 0; i < g_modSettingsDHT.length; i++) {
if (g_modSettingsDHT[i].name == _name) {
g_modSettingsDHT[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_slider_mod_dht(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.minValue = parseFloat(_obj.getAttribute('data-min'));
this.maxValue = parseFloat(_obj.getAttribute('data-max'));
this.percent = 0;
this.value = parseFloat(_obj.getAttribute('data-current'));
this.dragActive = false;
this.name = _obj.id;
this.type = _obj.getAttribute('data-type');
this.stepSize = _obj.getAttribute('data-stepSize') || 0;
this.format = _obj.getAttribute('data-format') || '{value}';
var self = this;
if (this.stepSize != 0)
this.value = Math.round(this.value / this.stepSize) * this.stepSize;
else
this.value = Math.round(this.value);
this.valueLabelBackground = document.createElement('div');
this.valueLabelBackground.className = 'valueLabel background';
this.valueLabelBackground.innerHTML = this.format.replace('{value}', this.value);
this.obj.appendChild(this.valueLabelBackground);
this.valueBar = document.createElement('div');
this.valueBar.className = 'valueBar';
this.valueBar.setAttribute('style', 'width: ' + (((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
this.obj.appendChild(this.valueBar);
this.valueLabelForeground = document.createElement('div');
this.valueLabelForeground.className = 'valueLabel foreground';
this.valueLabelForeground.innerHTML = this.format.replace('{value}', this.value);
this.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
this.valueBar.appendChild(this.valueLabelForeground);
this.mouseDown = function (_e) {
self.dragActive = true;
self.mouseMove(_e, false);
}
this.mouseMove = function (_e, _write) {
if (self.dragActive) {
var rect = _obj.getBoundingClientRect();
var start = rect.left;
var end = rect.right;
self.percent = Math.min(Math.max((_e.clientX - start) / rect.width, 0), 1);
var value = self.percent;
value *= (self.maxValue - self.minValue);
value += self.minValue;
if (self.stepSize != 0) {
value = Math.round(value / self.stepSize);
self.value = value * self.stepSize;
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
}
else
self.value = Math.round(value);
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
engine.call(self.callbackName, self.name, "" + self.value);
self.displayImperial();
}
}
this.mouseUp = function (_e) {
self.mouseMove(_e, true);
self.dragActive = false;
}
_obj.addEventListener('mousedown', this.mouseDown);
document.addEventListener('mousemove', this.mouseMove);
document.addEventListener('mouseup', this.mouseUp);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
if (self.stepSize != 0)
self.value = Math.round(value * self.stepSize) / self.stepSize;
else
self.value = Math.round(value);
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
self.displayImperial();
}
this.displayImperial = function () {
var displays = document.querySelectorAll('.imperialDisplay');
for (var i = 0; i < displays.length; i++) {
var binding = displays[i].getAttribute('data-binding');
if (binding == self.name) {
var realFeet = ((self.value * 0.393700) / 12);
var feet = Math.floor(realFeet);
var inches = Math.floor((realFeet - feet) * 12);
displays[i].innerHTML = feet + "&apos;" + inches + '&apos;&apos;';
}
}
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue
}
}
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_dht(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.value = _obj.getAttribute('data-current');
this.name = _obj.id;
this.type = _obj.getAttribute('data-type');
var self = this;
this.mouseDown = function (_e) {
self.value = self.value == "True" ? "False" : "True";
self.updateState();
}
this.updateState = function () {
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
engine.call(self.callbackName, self.name, self.value);
}
_obj.addEventListener('mousedown', this.mouseDown);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
self.value = value;
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
}
this.updateValue(this.value);
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Desktop Head Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use head tracking: </div>
<div class ="option-input">
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use eyes tracking: </div>
<div class ="option-input">
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use blinking: </div>
<div class ="option-input">
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mirrored movement: </div>
<div class ="option-input">
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Movement smoothing: </div>
<div class ="option-input">
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Override face tracking: </div>
<div class ="option-input">
<div id="FaceOverride" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Update sliders in new menu block
let l_sliders = l_block.querySelectorAll('.inp_slider');
for (var i = 0; i < l_sliders.length; i++) {
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_slider_mod_dht(l_sliders[i], 'MelonMod_DHT_Call_InpSlider');
}
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_toggle_mod_dht(l_toggles[i], 'MelonMod_DHT_Call_InpToggle');
}
}

View file

@ -0,0 +1,43 @@
using ABI_RC.Core;
using System.Text.RegularExpressions;
using UnityEngine;
namespace ml_prm
{
class AvatarBoolParameter
{
public readonly string m_name;
public readonly int m_hash = 0;
public readonly bool m_sync;
readonly CVRAnimatorManager m_manager = null;
public AvatarBoolParameter(string p_name, CVRAnimatorManager p_manager)
{
m_name = p_name;
m_manager = p_manager;
Regex l_regex = new Regex("^#?" + p_name + '$');
foreach(var l_param in m_manager.animator.parameters)
{
if(l_regex.IsMatch(l_param.name) && (l_param.type == AnimatorControllerParameterType.Bool))
{
m_name = l_param.name;
m_hash = l_param.nameHash;
m_sync = (l_param.name[0] != '#');
break;
}
}
}
public void SetValue(bool p_value)
{
if(m_hash != 0)
{
if(m_sync)
m_manager.SetAnimatorParameterBool(m_name, p_value);
else
m_manager.animator.SetBool(m_hash, p_value);
}
}
}
}

216
archived/ml_prm/Main.cs Normal file
View file

@ -0,0 +1,216 @@
using ABI.CCK.Components;
using ABI_RC.Core;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util.AssetFiltering;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
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();
ModUi.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(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnSetupIKScaling_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
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.StartCalibration)),
new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnStartCalibration_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
HarmonyInstance.Patch(
typeof(RootLogic).GetMethod(nameof(RootLogic.SpawnOnWorldInstance)),
new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnWorldSpawn_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
HarmonyInstance.Patch(
typeof(CombatSystem).GetMethods().First(m => (!m.IsGenericMethod && m.Name == nameof(CombatSystem.Down))),
new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnCombatDown_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
HarmonyInstance.Patch(
typeof(MovementSystem).GetMethod(nameof(MovementSystem.ChangeFlight)),
null,
new HarmonyLib.HarmonyMethod(typeof(PlayerRagdollMod).GetMethod(nameof(OnChangeFlight_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
// Whitelist the toggle script
(typeof(SharedFilter).GetField("_localComponentWhitelist", BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) as HashSet<Type>)?.Add(typeof(RagdollToggle));
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_localController = null;
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localController = PlayerSetup.Instance.gameObject.AddComponent<RagdollController>();
ModUi.SwitchChange += this.OnSwitchActivation;
}
void OnSwitchActivation()
{
if(m_localController != null)
m_localController.SwitchRagdoll();
}
// Patches
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 OnSetupIKScaling_Postfix(ref UnityEngine.Vector3 ___scaleDifference) => ms_instance?.OnSetupIKScaling(___scaleDifference.y);
void OnSetupIKScaling(float p_scaleDifference)
{
try
{
if(m_localController != null)
m_localController.OnAvatarScaling(1f + p_scaleDifference);
}
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);
}
}
static void OnStartCalibration_Prefix() => ms_instance?.OnStartCalibration();
void OnStartCalibration()
{
try
{
if(m_localController != null)
m_localController.OnStartCalibration();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnWorldSpawn_Prefix() => ms_instance?.OnWorldSpawn();
void OnWorldSpawn()
{
try
{
if(m_localController != null)
m_localController.OnWorldSpawn();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCombatDown_Prefix(ref CombatSystem __instance)
{
if((__instance == CombatSystem.Instance) && !__instance.isDown)
ms_instance?.OnCombatDown();
}
void OnCombatDown()
{
try
{
if(m_localController != null)
m_localController.OnCombatDown();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnChangeFlight_Postfix() => ms_instance?.OnChangeFlight();
void OnChangeFlight()
{
try
{
if(m_localController != null)
m_localController.OnChangeFlight();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

198
archived/ml_prm/ModUi.cs Normal file
View file

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace ml_prm
{
static class ModUi
{
enum UiIndex
{
Hotkey = 0,
Gravity,
PointersReaction,
IgnoreLocal,
CombatReaction,
AutoRecover,
Slipperiness,
Bounciness,
ViewVelocity,
JumpRecover,
VelocityMultiplier,
MovementDrag,
AngularDrag,
RecoverDelay
}
static public event Action SwitchChange;
static List<object> ms_uiElements = null;
internal static void Init()
{
ms_uiElements = new List<object>();
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "BTKUILib") != null)
CreateUi();
}
// Separated method, otherwise exception is thrown, funny CSharp and optional references, smh
static void CreateUi()
{
BTKUILib.QuickMenuAPI.PrepareIcon("PlayerRagdollMod", "PRM-Person", GetIconStream("person.png"));
var l_modRoot = new BTKUILib.UIObjects.Page("PlayerRagdollMod", "MainPage", true, "PRM-Person");
l_modRoot.MenuTitle = "Player Ragdoll Mod";
l_modRoot.MenuSubtitle = "Become a ragdoll and change various settings for people amusement";
var l_modCategory = l_modRoot.AddCategory("Settings");
l_modCategory.AddButton("Switch ragdoll", "PRM-Person", "Switch between normal and ragdoll state").OnPress += () => SwitchChange?.Invoke();
ms_uiElements.Add(l_modCategory.AddToggle("Use hotkey", "Switch ragdoll mode with 'R' key", Settings.Hotkey));
(ms_uiElements[(int)UiIndex.Hotkey] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Hotkey, state);
ms_uiElements.Add(l_modCategory.AddToggle("Use gravity", "Apply gravity to ragdoll", Settings.Gravity));
(ms_uiElements[(int)UiIndex.Gravity] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Gravity, state);
ms_uiElements.Add(l_modCategory.AddToggle("Pointers reaction", "React to trigger colliders with CVRPointer component of 'ragdoll' type", Settings.PointersReaction));
(ms_uiElements[(int)UiIndex.PointersReaction] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.PointersReaction, state);
ms_uiElements.Add(l_modCategory.AddToggle("Ignore local pointers", "Ignore local avatar's CVRPointer components of 'ragdoll' type", Settings.IgnoreLocal));
(ms_uiElements[(int)UiIndex.IgnoreLocal] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.IgnoreLocal, state);
ms_uiElements.Add(l_modCategory.AddToggle("Combat reaction", "Ragdoll upon combat system death", Settings.CombatReaction));
(ms_uiElements[(int)UiIndex.CombatReaction] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.CombatReaction, state);
ms_uiElements.Add(l_modCategory.AddToggle("Auto recover", "Automatically unragdoll after set recover delay", Settings.AutoRecover));
(ms_uiElements[(int)UiIndex.AutoRecover] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.AutoRecover, state);
ms_uiElements.Add(l_modCategory.AddToggle("Slipperiness", "Enables/disables friction of ragdoll", Settings.Slipperiness));
(ms_uiElements[(int)UiIndex.Slipperiness] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Slipperiness, state);
ms_uiElements.Add(l_modCategory.AddToggle("Bounciness", "Enables/disables bounciness of ragdoll", Settings.Bounciness));
(ms_uiElements[(int)UiIndex.Bounciness] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Bounciness, state);
ms_uiElements.Add(l_modCategory.AddToggle("View direction velocity", "Apply velocity to camera view direction", Settings.ViewVelocity));
(ms_uiElements[(int)UiIndex.ViewVelocity] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.ViewVelocity, state);
ms_uiElements.Add(l_modCategory.AddToggle("Jump recover", "Recover from ragdoll state by jumping", Settings.JumpRecover));
(ms_uiElements[(int)UiIndex.JumpRecover] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => OnToggleUpdate(UiIndex.JumpRecover, state);
ms_uiElements.Add(l_modRoot.AddSlider("Velocity multiplier", "Velocity multiplier upon entering ragdoll state", Settings.VelocityMultiplier, 1f, 50f));
(ms_uiElements[(int)UiIndex.VelocityMultiplier] as BTKUILib.UIObjects.Components.SliderFloat).OnValueUpdated += (value) => OnSliderUpdate(UiIndex.VelocityMultiplier, value);
ms_uiElements.Add(l_modRoot.AddSlider("Movement drag", "Movement resistance", Settings.MovementDrag, 0f, 50f));
(ms_uiElements[(int)UiIndex.MovementDrag] as BTKUILib.UIObjects.Components.SliderFloat).OnValueUpdated += (value) => OnSliderUpdate(UiIndex.MovementDrag, value);
ms_uiElements.Add(l_modRoot.AddSlider("Angular movement drag", "Rotation movement resistance", Settings.AngularDrag, 0f, 50f));
(ms_uiElements[(int)UiIndex.AngularDrag] as BTKUILib.UIObjects.Components.SliderFloat).OnValueUpdated += (value) => OnSliderUpdate(UiIndex.AngularDrag, value);
ms_uiElements.Add(l_modRoot.AddSlider("Recover delay (seconds)", "Recover delay for automatic recover", Settings.RecoverDelay, 1f, 10f));
(ms_uiElements[(int)UiIndex.RecoverDelay] as BTKUILib.UIObjects.Components.SliderFloat).OnValueUpdated += (value) => OnSliderUpdate(UiIndex.RecoverDelay, value);
l_modCategory.AddButton("Reset settings", "", "Reset mod settings to default").OnPress += Reset;
}
static void OnToggleUpdate(UiIndex p_index, bool p_state, bool p_force = false)
{
switch(p_index)
{
case UiIndex.Hotkey:
Settings.SetSetting(Settings.ModSetting.Hotkey, p_state);
break;
case UiIndex.Gravity:
Settings.SetSetting(Settings.ModSetting.Gravity, p_state);
break;
case UiIndex.PointersReaction:
Settings.SetSetting(Settings.ModSetting.PointersReaction, p_state);
break;
case UiIndex.IgnoreLocal:
Settings.SetSetting(Settings.ModSetting.IgnoreLocal, p_state);
break;
case UiIndex.CombatReaction:
Settings.SetSetting(Settings.ModSetting.CombatReaction, p_state);
break;
case UiIndex.AutoRecover:
Settings.SetSetting(Settings.ModSetting.AutoRecover, p_state);
break;
case UiIndex.Slipperiness:
Settings.SetSetting(Settings.ModSetting.Slipperiness, p_state);
break;
case UiIndex.Bounciness:
Settings.SetSetting(Settings.ModSetting.Bounciness, p_state);
break;
case UiIndex.ViewVelocity:
Settings.SetSetting(Settings.ModSetting.ViewVelocity, p_state);
break;
case UiIndex.JumpRecover:
Settings.SetSetting(Settings.ModSetting.JumpRecover, p_state);
break;
}
if(p_force)
(ms_uiElements[(int)p_index] as BTKUILib.UIObjects.Components.ToggleButton).ToggleValue = p_state;
}
static void OnSliderUpdate(UiIndex p_index, float p_value, bool p_force = false)
{
switch(p_index)
{
case UiIndex.VelocityMultiplier:
Settings.SetSetting(Settings.ModSetting.VelocityMultiplier, p_value);
break;
case UiIndex.MovementDrag:
Settings.SetSetting(Settings.ModSetting.MovementDrag, p_value);
break;
case UiIndex.AngularDrag:
Settings.SetSetting(Settings.ModSetting.AngularDrag, p_value);
break;
case UiIndex.RecoverDelay:
Settings.SetSetting(Settings.ModSetting.RecoverDelay, p_value);
break;
}
if(p_force)
(ms_uiElements[(int)p_index] as BTKUILib.UIObjects.Components.SliderFloat).SetSliderValue(p_value);
}
static void Reset()
{
OnToggleUpdate(UiIndex.Hotkey, true, true);
OnToggleUpdate(UiIndex.Gravity, true, true);
OnToggleUpdate(UiIndex.PointersReaction, true, true);
OnToggleUpdate(UiIndex.IgnoreLocal, true, true);
OnToggleUpdate(UiIndex.CombatReaction, true, true);
OnToggleUpdate(UiIndex.AutoRecover, false, true);
OnToggleUpdate(UiIndex.Slipperiness, false, true);
OnToggleUpdate(UiIndex.Bounciness, false, true);
OnToggleUpdate(UiIndex.ViewVelocity, false, true);
OnToggleUpdate(UiIndex.JumpRecover, false, true);
OnSliderUpdate(UiIndex.VelocityMultiplier, 2f, true);
OnSliderUpdate(UiIndex.MovementDrag, 2f, true);
OnSliderUpdate(UiIndex.AngularDrag, 2f, true);
OnSliderUpdate(UiIndex.RecoverDelay, 3f, true);
}
static Stream GetIconStream(string p_name)
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
return l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
}
}
}

View file

@ -0,0 +1,7 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.0.11", "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)]
[assembly: MelonLoader.MelonAdditionalCredits("kafeijao, NotAKidOnSteam")]

68
archived/ml_prm/README.md Normal file
View file

@ -0,0 +1,68 @@
# Player Ragdoll Mod
This mod turns 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 page 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.
* **Use gravity:** enables/disables gravity for ragdoll; `true` by default.
* Note: Forcibly enabled in worlds that don't allow flight.
* **Pointers reaction:** enables ragdoll state when player collides with trigger colliders and particle systems with CVRPointer component of `ragdoll` type (avatars, props and world included); `true` by default.
* **Ignore local pointers:** enables/disables ignoring of CVRPointer components of `ragdoll` type on local player's avatar; `true` by default.
* **Combat reaction:** enables ragdoll state upon death in worlds with combat system; `true` by default.
* **Auto recover:** enables automatic recovering after specific time delay; `false` by default.
* **Slipperiness:** enables/disable low friction of ragdoll; `false` by default.
* Note: Forcibly disabled in worlds that don't allow flight.
* **Bounciness:** enables/disable bounce force of ragdoll; `false` by default.
* Note: Forcibly disabled in worlds that don't allow flight.
* **View direction velocity:** apply velocity to camera view direction instead of player movement direction; `false` by default.
* Note: Forcibly disabled in worlds that don't allow flight.
* **Jump recover:** enables recovering from ragdoll state by jumping; `false` by default.
* **Velocity multiplier:** velocity force multiplier based on player's movement direction; `2.0` by default.
* Note: Limited according to world's fly multiplier.
* Note: Forcibly set to `1.0` in worlds that don't allow flight.
* **Movement drag:** movement resistance; `2.0` by default.
* Note: Forcibly set to `1.0` in worlds that don't allow flight.
* **Angular movement drag:** angular movement resistance; `2.0` by default.
* **Recover delay:** time delay for enabled `Auto recover` in seconds; `3.0` by default.
* **Reset settings:** resets mod settings to default.
Optional mod's settings in [UIExpansionKit](https://github.com/ddakebono/ChilloutMods):
* **Hotkey:** system key that is used to switch ragdoll state; `R` by default.
Available additional parameters for AAS animator:
* **`Ragdolled`:** defines current ragdoll state; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
# Unity Editor Script
You can also trigger the ragdoll via animations on your avatar. To do this you need:
* Download and import the `ml_prm_editor_script.unitypackage` into your unity project
* Add the component `Ragdoll Toggle` anywhere inside of your avatar's hierarchy.
Now you can animate both parameters available:
- **Should Override:** whether the animation should override the toggled state of the ragdoll.
- **Is On:** whether the ragdoll state is On or Off (only works if `Should Override` is also On).
![](.github/img_01.png)
Note: In order to work the game object needs to be active and the component enabled.
# Mods Integration
You can use this mod's functions within your mod. To do this you need:
* Add mod's dll as reference in your project
* Access ragdoll controller with `ml_prm.RagdollController.Instance`
Available methods:
* ```bool IsRagdolled()```
* ```void SwitchRagdoll()```
# Notes
* If ragdoll state is enabled during emote, remote players see whole emote playing while local player sees ragdolling. It's tied to how game handles remote players, currently can be prevented with (choose one):
* Renaming avatar emote animations to not have default name or containing `Emote` substring.
* Holding any movement key right after activating ragdoll state.

View file

@ -0,0 +1,570 @@
using ABI.CCK.Components;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.MovementSystem;
using RootMotion.Dynamics;
using RootMotion.FinalIK;
using System.Collections.Generic;
using UnityEngine;
namespace ml_prm
{
[DisallowMultipleComponent]
public class RagdollController : MonoBehaviour
{
const float c_defaultFriction = 0.6f;
public static RagdollController Instance { get; private set; } = null;
VRIK m_vrIK = null;
float m_vrIkWeight = 1f;
bool m_inVr = false;
bool m_enabled = false;
readonly List<Rigidbody> m_rigidBodies = null;
readonly List<Collider> m_colliders = null;
Transform m_puppetRoot = null;
Transform m_puppet = null;
BipedRagdollReferences m_puppetReferences;
readonly List<System.Tuple<Transform, Transform>> m_boneLinks = null;
readonly List<System.Tuple<CharacterJoint, Vector3>> m_jointAnchors = null;
bool m_avatarReady = false;
Vector3 m_lastPosition = Vector3.zero;
Vector3 m_velocity = Vector3.zero;
Vector3 m_ragdollLastPos = Vector3.zero;
RagdollToggle m_avatarRagdollToggle = null;
RagdollTrigger m_customTrigger = null;
AvatarBoolParameter m_ragdolledParameter = null;
readonly PhysicMaterial m_physicsMaterial = null;
bool m_reachedGround = true;
float m_groundedTime = 0f;
float m_downTime = float.MinValue;
internal RagdollController()
{
if(Instance == null)
Instance = this;
m_rigidBodies = new List<Rigidbody>();
m_colliders = new List<Collider>();
m_boneLinks = new List<System.Tuple<Transform, Transform>>();
m_jointAnchors = new List<System.Tuple<CharacterJoint, Vector3>>();
m_physicsMaterial = new PhysicMaterial("Ragdoll");
m_physicsMaterial.dynamicFriction = c_defaultFriction;
m_physicsMaterial.staticFriction = c_defaultFriction;
m_physicsMaterial.frictionCombine = PhysicMaterialCombine.Average;
m_physicsMaterial.bounciness = 0f;
m_physicsMaterial.bounceCombine = PhysicMaterialCombine.Average;
}
~RagdollController()
{
if(Instance == this)
Instance = null;
}
// Unity events
void Start()
{
m_inVr = Utils.IsInVR();
m_puppetRoot = new GameObject("[PlayerAvatarPuppet]").transform;
m_puppetRoot.parent = PlayerSetup.Instance.transform;
m_puppetRoot.localPosition = Vector3.zero;
m_puppetRoot.localRotation = Quaternion.identity;
m_customTrigger = MovementSystem.Instance.proxyCollider.gameObject.AddComponent<RagdollTrigger>();
Settings.MovementDragChange += this.OnMovementDragChange;
Settings.AngularDragChange += this.OnAngularDragChange;
Settings.GravityChange += this.OnGravityChange;
Settings.SlipperinessChange += this.OnPhysicsMaterialChange;
Settings.BouncinessChange += this.OnPhysicsMaterialChange;
}
void OnDestroy()
{
if(m_customTrigger != null)
{
Object.Destroy(m_customTrigger);
m_customTrigger = null;
}
Settings.MovementDragChange -= this.OnMovementDragChange;
Settings.AngularDragChange -= this.OnAngularDragChange;
Settings.GravityChange -= this.OnGravityChange;
Settings.SlipperinessChange -= this.OnPhysicsMaterialChange;
Settings.BouncinessChange -= this.OnPhysicsMaterialChange;
}
void Update()
{
if(m_avatarReady && m_enabled)
{
Vector3 l_dif = m_puppetReferences.hips.position - m_ragdollLastPos;
PlayerSetup.Instance.transform.position += l_dif;
m_puppetReferences.hips.position -= l_dif;
m_ragdollLastPos = m_puppetReferences.hips.position;
}
if(m_avatarReady && !m_enabled)
{
Vector3 l_pos = PlayerSetup.Instance.transform.position;
m_velocity = (m_velocity + (l_pos - m_lastPosition) / Time.deltaTime) * 0.5f;
m_lastPosition = l_pos;
if(!m_reachedGround && MovementSystem.Instance.IsGrounded())
{
m_groundedTime += Time.deltaTime;
if(m_groundedTime >= 0.25f)
m_reachedGround = true;
}
}
if(m_avatarReady && m_enabled && !BodySystem.isCalibrating)
BodySystem.TrackingPositionWeight = 0f;
if(m_avatarReady && m_enabled && Settings.AutoRecover)
{
m_downTime += Time.deltaTime;
if(m_downTime >= Settings.RecoverDelay)
{
SwitchRagdoll();
m_downTime = float.MinValue; // One attepmt to recover
}
}
if((m_avatarRagdollToggle != null) && m_avatarRagdollToggle.isActiveAndEnabled && m_avatarRagdollToggle.shouldOverride && (m_enabled != m_avatarRagdollToggle.isOn))
SwitchRagdoll();
if((m_customTrigger != null) && m_customTrigger.GetStateWithReset() && m_avatarReady && !m_enabled && Settings.PointersReaction)
SwitchRagdoll();
if(Settings.Hotkey && Input.GetKeyDown(Settings.HotkeyKey) && !ViewManager.Instance.isGameMenuOpen())
SwitchRagdoll();
if(m_avatarReady && m_enabled && CVRInputManager.Instance.jump && Settings.JumpRecover)
SwitchRagdoll();
}
void LateUpdate()
{
if(m_avatarReady)
{
if(m_enabled)
{
if(!BodySystem.isCalibrating)
{
BodySystem.TrackingPositionWeight = 0f;
foreach(var l_link in m_boneLinks)
l_link.Item1.CopyGlobal(l_link.Item2);
}
}
else
{
foreach(var l_link in m_boneLinks)
l_link.Item2.CopyGlobal(l_link.Item1);
}
}
}
// Game events
internal void OnAvatarClear()
{
if(m_enabled && (MovementSystem.Instance != null))
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_avatarRagdollToggle = null;
m_ragdolledParameter = null;
m_rigidBodies.Clear();
m_colliders.Clear();
m_puppetReferences = new BipedRagdollReferences();
m_boneLinks.Clear();
m_jointAnchors.Clear();
m_reachedGround = true;
m_groundedTime = 0f;
m_downTime = float.MinValue;
m_puppetRoot.localScale = Vector3.one;
}
internal void OnAvatarSetup()
{
m_inVr = Utils.IsInVR();
if(PlayerSetup.Instance._animator.isHuman)
{
BipedRagdollReferences l_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(l_avatarReferences.hips, m_puppetReferences.root, "Hips");
m_puppetReferences.spine = CloneTransform(l_avatarReferences.spine, m_puppetReferences.hips, "Spine");
if(l_avatarReferences.chest != null)
m_puppetReferences.chest = CloneTransform(l_avatarReferences.chest, m_puppetReferences.spine, "Chest");
m_puppetReferences.head = CloneTransform(l_avatarReferences.head, (m_puppetReferences.chest != null) ? m_puppetReferences.chest : m_puppetReferences.spine, "Head");
m_puppetReferences.leftUpperArm = CloneTransform(l_avatarReferences.leftUpperArm, (m_puppetReferences.chest != null) ? m_puppetReferences.chest : m_puppetReferences.spine, "LeftUpperArm");
m_puppetReferences.leftLowerArm = CloneTransform(l_avatarReferences.leftLowerArm, m_puppetReferences.leftUpperArm, "LeftLowerArm");
m_puppetReferences.leftHand = CloneTransform(l_avatarReferences.leftHand, m_puppetReferences.leftLowerArm, "LeftHand");
m_puppetReferences.rightUpperArm = CloneTransform(l_avatarReferences.rightUpperArm, (m_puppetReferences.chest != null) ? m_puppetReferences.chest : m_puppetReferences.spine, "RightUpperArm");
m_puppetReferences.rightLowerArm = CloneTransform(l_avatarReferences.rightLowerArm, m_puppetReferences.rightUpperArm, "RightLowerArm");
m_puppetReferences.rightHand = CloneTransform(l_avatarReferences.rightHand, m_puppetReferences.rightLowerArm, "RightHand");
m_puppetReferences.leftUpperLeg = CloneTransform(l_avatarReferences.leftUpperLeg, m_puppetReferences.hips, "LeftUpperLeg");
m_puppetReferences.leftLowerLeg = CloneTransform(l_avatarReferences.leftLowerLeg, m_puppetReferences.leftUpperLeg, "LeftLowerLeg");
m_puppetReferences.leftFoot = CloneTransform(l_avatarReferences.leftFoot, m_puppetReferences.leftLowerLeg, "LeftFoot");
m_puppetReferences.rightUpperLeg = CloneTransform(l_avatarReferences.rightUpperLeg, m_puppetReferences.hips, "RightUpperLeg");
m_puppetReferences.rightLowerLeg = CloneTransform(l_avatarReferences.rightLowerLeg, m_puppetReferences.rightUpperLeg, "RightLowerLeg");
m_puppetReferences.rightFoot = CloneTransform(l_avatarReferences.rightFoot, m_puppetReferences.rightLowerLeg, "RightFoot");
// Move to world origin to overcome possible issues, maybe?
m_puppetRoot.position = Vector3.zero;
m_puppetRoot.rotation = Quaternion.identity;
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 = l_avatarReferences.GetRagdollTransforms();
for(int i = 0; i < l_puppetTransforms.Length; i++)
{
if(l_puppetTransforms[i] != null)
{
Rigidbody l_body = l_puppetTransforms[i].GetComponent<Rigidbody>();
if(l_body != null)
{
m_rigidBodies.Add(l_body);
l_body.isKinematic = true;
l_body.angularDrag = Settings.AngularDrag;
l_body.drag = (Utils.IsWorldSafe() ? Settings.MovementDrag : 1f);
l_body.useGravity = (!Utils.IsWorldSafe() || Settings.Gravity);
l_body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
l_body.gameObject.layer = LayerMask.NameToLayer("PlayerLocal");
}
CharacterJoint l_joint = l_puppetTransforms[i].GetComponent<CharacterJoint>();
if(l_joint != null)
{
l_joint.enablePreprocessing = false;
l_joint.enableProjection = true;
m_jointAnchors.Add(System.Tuple.Create(l_joint, l_joint.connectedAnchor));
}
Collider l_collider = l_puppetTransforms[i].GetComponent<Collider>();
if(l_collider != null)
{
Physics.IgnoreCollision(l_collider, MovementSystem.Instance.proxyCollider, true);
Physics.IgnoreCollision(l_collider, MovementSystem.Instance.controller, true);
Physics.IgnoreCollision(l_collider, MovementSystem.Instance.forceCollider, true);
l_collider.enabled = false;
l_collider.sharedMaterial = m_physicsMaterial;
l_collider.material = m_physicsMaterial;
m_colliders.Add(l_collider);
}
if(l_avatarTransforms[i] != null)
m_boneLinks.Add(System.Tuple.Create(l_puppetTransforms[i], l_avatarTransforms[i]));
}
}
// And return back
m_puppetRoot.localPosition = Vector3.zero;
m_puppetRoot.localRotation = Quaternion.identity;
m_puppetRoot.gameObject.SetActive(false);
m_vrIK = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
if(m_vrIK != null)
{
m_vrIK.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIK.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
m_avatarRagdollToggle = PlayerSetup.Instance._avatar.GetComponentInChildren<RagdollToggle>(true);
m_ragdolledParameter = new AvatarBoolParameter("Ragdolled", PlayerSetup.Instance.animatorManager);
m_avatarReady = true;
}
}
internal void OnAvatarScaling(float p_scaleDifference)
{
if(m_avatarReady)
{
m_puppetRoot.localScale = Vector3.one * p_scaleDifference;
foreach(var l_pair in m_jointAnchors)
l_pair.Item1.connectedAnchor = l_pair.Item2 * p_scaleDifference;
}
}
internal void OnSeatSitDown(CVRSeat p_seat)
{
if(m_avatarReady && m_enabled && !p_seat.occupied)
SwitchRagdoll();
}
internal void OnStartCalibration()
{
if(m_avatarReady && m_enabled)
SwitchRagdoll();
}
internal void OnWorldSpawn()
{
if(m_avatarReady && m_enabled)
SwitchRagdoll();
OnGravityChange(Settings.Gravity);
OnPhysicsMaterialChange(true);
OnMovementDragChange(Settings.MovementDrag);
}
internal void OnCombatDown()
{
if(m_avatarReady && !m_enabled && Settings.CombatReaction)
{
m_reachedGround = true;
SwitchRagdoll();
}
}
internal void OnChangeFlight()
{
if(m_avatarReady && m_enabled && MovementSystem.Instance.flying)
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;
else
{
foreach(var l_link in m_boneLinks)
l_link.Item2.CopyGlobal(l_link.Item1);
}
}
// Settings
void OnMovementDragChange(float p_value)
{
if(m_avatarReady)
{
float l_drag = (Utils.IsWorldSafe() ? p_value : 1f);
foreach(Rigidbody l_body in m_rigidBodies)
{
l_body.drag = l_drag;
if(m_enabled)
l_body.WakeUp();
}
}
}
void OnAngularDragChange(float p_value)
{
if(m_avatarReady)
{
foreach(Rigidbody l_body in m_rigidBodies)
{
l_body.angularDrag = p_value;
if(m_enabled)
l_body.WakeUp();
}
}
}
void OnGravityChange(bool p_state)
{
if(m_avatarReady)
{
bool l_gravity = (!Utils.IsWorldSafe() || p_state);
foreach(Rigidbody l_body in m_rigidBodies)
l_body.useGravity = l_gravity;
}
}
void OnPhysicsMaterialChange(bool p_state)
{
if(m_physicsMaterial != null)
{
bool l_slipperiness = (Settings.Slipperiness && Utils.IsWorldSafe());
bool l_bounciness = (Settings.Bounciness && Utils.IsWorldSafe());
m_physicsMaterial.dynamicFriction = (l_slipperiness ? 0f : c_defaultFriction);
m_physicsMaterial.staticFriction = (l_slipperiness ? 0f : c_defaultFriction);
m_physicsMaterial.frictionCombine = (l_slipperiness ? PhysicMaterialCombine.Minimum : PhysicMaterialCombine.Average);
m_physicsMaterial.bounciness = (l_bounciness ? 1f : 0f);
m_physicsMaterial.bounceCombine = (l_bounciness ? PhysicMaterialCombine.Maximum : PhysicMaterialCombine.Average);
}
}
// Arbitrary
public void SwitchRagdoll()
{
if(m_avatarReady)
{
if(!m_enabled)
{
if(IsSafeToRagdoll() && m_reachedGround)
{
// Eject player from seat
if(MovementSystem.Instance.lastSeat != null)
{
Vector3 l_pos = PlayerSetup.Instance.transform.position;
Quaternion l_rot = PlayerSetup.Instance.transform.rotation;
if(MetaPort.Instance.isUsingVr)
{
MetaPort.Instance.isUsingVr = false;
MovementSystem.Instance.lastSeat.ExitSeat();
MetaPort.Instance.isUsingVr = true;
}
else
MovementSystem.Instance.lastSeat.ExitSeat();
PlayerSetup.Instance.transform.position = l_pos;
PlayerSetup.Instance.transform.rotation = Quaternion.Euler(0f, l_rot.eulerAngles.y, 0f);
}
if(MovementSystem.Instance.flying)
MovementSystem.Instance.ChangeFlight(false);
bool l_crouch = MovementSystem.Instance.crouching;
bool l_prone = MovementSystem.Instance.prone;
MovementSystem.Instance.SetImmobilized(true);
MovementSystem.Instance.ChangeCrouch(l_crouch);
MovementSystem.Instance.ChangeProne(l_prone);
PlayerSetup.Instance.animatorManager.SetAnimatorParameterTrigger("CancelEmote");
m_ragdolledParameter.SetValue(true);
if(!BodySystem.isCalibrating)
BodySystem.TrackingPositionWeight = 0f;
if(!Utils.IsWorldSafe())
{
m_reachedGround = false; // Force player to unragdoll and reach ground first
m_groundedTime = 0f;
}
m_puppetRoot.gameObject.SetActive(true);
foreach(Rigidbody l_body in m_rigidBodies)
l_body.isKinematic = false;
Vector3 l_velocity = Vector3.ClampMagnitude(m_velocity * (Utils.IsWorldSafe() ? Settings.VelocityMultiplier : 1f), Utils.GetWorldMovementLimit());
if(Settings.ViewVelocity && Utils.IsWorldSafe())
{
float l_mag = l_velocity.magnitude;
l_velocity = PlayerSetup.Instance.GetActiveCamera().transform.forward * l_mag;
}
foreach(Rigidbody l_body in m_rigidBodies)
{
l_body.velocity = l_velocity;
l_body.angularVelocity = Vector3.zero;
}
foreach(Collider l_collider in m_colliders)
l_collider.enabled = true;
m_ragdollLastPos = m_puppetReferences.hips.position;
m_downTime = 0f;
m_enabled = true;
}
}
else
{
if(IsSafeToUnragdoll())
{
MovementSystem.Instance.SetImmobilized(false);
if(!Utils.IsWorldSafe())
{
Vector3 l_vec = MovementSystem.Instance.GetAppliedGravity();
l_vec.y = Mathf.Clamp(l_vec.y, float.MinValue, 0f);
MovementSystem.Instance.SetAppliedGravity(l_vec);
}
m_ragdolledParameter.SetValue(false);
if(!BodySystem.isCalibrating)
BodySystem.TrackingPositionWeight = 1f;
m_puppetRoot.gameObject.SetActive(false);
foreach(Rigidbody l_body in m_rigidBodies)
l_body.isKinematic = true;
PlayerSetup.Instance.transform.position = m_puppetReferences.hips.position;
if(m_inVr)
PlayerSetup.Instance.transform.position -= (PlayerSetup.Instance.transform.rotation * PlayerSetup.Instance._avatar.transform.localPosition);
foreach(Collider l_collider in m_colliders)
l_collider.enabled = false;
if(m_vrIK != null)
m_vrIK.solver.Reset();
m_lastPosition = PlayerSetup.Instance.transform.position;
m_velocity = Vector3.zero;
m_downTime = float.MinValue;
m_enabled = false;
}
}
}
}
public bool IsRagdolled() => (m_avatarReady && 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;
}
static bool IsSafeToRagdoll()
{
bool l_result = true;
l_result &= !BodySystem.isCalibrating; // Not calibrating
l_result &= ((CombatSystem.Instance == null) || !CombatSystem.Instance.isDown); // Non-combat world or not dead
return l_result;
}
static bool IsSafeToUnragdoll()
{
bool l_result = true;
l_result &= ((CombatSystem.Instance == null) || !CombatSystem.Instance.isDown); // Non-combat world or not dead
return l_result;
}
}
}

View file

@ -0,0 +1,12 @@
using UnityEngine;
namespace ml_prm
{
public class RagdollToggle : MonoBehaviour
{
[Tooltip("Whether or not is should use the isOn property to override the current Ragdoll State of the Avatar.")]
[SerializeField] public bool shouldOverride;
[Tooltip("Whether Ragdoll State is active or not on the Avatar. Requires shouldOverride to be true to work.")]
[SerializeField] public bool isOn;
}
}

View file

@ -0,0 +1,103 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using UnityEngine;
namespace ml_prm
{
[DisallowMultipleComponent]
class RagdollTrigger : MonoBehaviour
{
const string c_ragdollPointerType = "ragdoll";
Collider m_collider = null;
Collider m_lastColliderTrigger = null;
ParticleSystem m_lastParticleSystemTrigger = null;
bool m_triggered = false;
void Start()
{
m_collider = this.GetComponent<Collider>();
CVRParticlePointerManager.volumes.Add(new RagdollTriggerVolume(m_collider, this));
CVRParticlePointerManager.UpdateParticleSystems();
}
void OnDestroy()
{
CVRParticlePointerManager.RemoveTrigger(m_collider);
}
void Update()
{
if(!ReferenceEquals(m_lastColliderTrigger, null))
{
if(m_lastColliderTrigger != null)
{
if(!m_collider.bounds.Intersects(m_lastColliderTrigger.bounds))
m_lastColliderTrigger = null;
}
else
m_lastColliderTrigger = null;
}
if(!ReferenceEquals(m_lastParticleSystemTrigger, null))
{
if(m_lastParticleSystemTrigger != null)
{
if(m_lastParticleSystemTrigger.particleCount == 0)
m_lastParticleSystemTrigger = null;
}
else
m_lastParticleSystemTrigger = null;
}
}
void OnTriggerEnter(Collider p_other)
{
CVRPointer l_pointer = p_other.GetComponent<CVRPointer>();
if((l_pointer != null) && (l_pointer.type == c_ragdollPointerType) && !IsIgnored(l_pointer.transform) && (m_lastColliderTrigger != p_other))
{
m_lastColliderTrigger = p_other;
m_triggered = true;
}
}
void OnTriggerExit(Collider p_other)
{
if(m_lastColliderTrigger == p_other)
m_lastColliderTrigger = null;
}
public void OnPointerParticleEnter(CVRPointer p_pointer)
{
if(!this.gameObject.activeInHierarchy)
return;
if((p_pointer.type == c_ragdollPointerType) && !IsIgnored(p_pointer.transform) && (m_lastParticleSystemTrigger != p_pointer.particleSystem))
{
m_lastParticleSystemTrigger = p_pointer.particleSystem;
m_triggered = true;
}
}
public void OnPointerParticleExit(CVRPointer p_pointer)
{
// This seems to be very unreliable, and it's causing weird behavior
// if (!gameObject.activeInHierarchy) return;
// if(m_lastParticleSystemTrigger == p_pointer.particleSystem)
// m_lastParticleSystemTrigger = null;
}
public bool GetStateWithReset()
{
bool l_state = m_triggered;
m_triggered = false;
return l_state;
}
static bool IsIgnored(Transform p_transform)
{
return (Settings.IgnoreLocal && (p_transform.root == PlayerSetup.Instance.transform));
}
}
}

View file

@ -0,0 +1,22 @@
using ABI_RC.Core.Savior;
using ABI.CCK.Components;
using UnityEngine;
namespace ml_prm
{
class RagdollTriggerVolume : CVRTriggerVolume
{
readonly RagdollTrigger m_trigger = null;
public Collider collider { get; set; }
internal RagdollTriggerVolume(Collider p_collider, RagdollTrigger p_trigger)
{
collider = p_collider;
m_trigger = p_trigger;
}
public void TriggerEnter(CVRPointer pointer) => m_trigger.OnPointerParticleEnter(pointer);
public void TriggerExit(CVRPointer pointer) => m_trigger.OnPointerParticleExit(pointer);
}
}

223
archived/ml_prm/Settings.cs Normal file
View file

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace ml_prm
{
static class Settings
{
public enum ModSetting
{
Hotkey = 0,
HotkeyKey,
VelocityMultiplier,
MovementDrag,
AngularDrag,
Gravity,
PointersReaction,
IgnoreLocal,
CombatReaction,
AutoRecover,
RecoverDelay,
Slipperiness,
Bounciness,
ViewVelocity,
JumpRecover
}
public static bool Hotkey { get; private set; } = true;
public static KeyCode HotkeyKey { get; private set; } = KeyCode.R;
public static float VelocityMultiplier { get; private set; } = 2f;
public static float MovementDrag { get; private set; } = 2f;
public static float AngularDrag { get; private set; } = 2f;
public static bool Gravity { get; private set; } = true;
public static bool PointersReaction { get; private set; } = true;
public static bool IgnoreLocal { get; private set; } = true;
public static bool CombatReaction { get; private set; } = true;
public static bool AutoRecover { get; private set; } = false;
public static float RecoverDelay { get; private set; } = 3f;
public static bool Slipperiness { get; private set; } = false;
public static bool Bounciness { get; private set; } = false;
public static bool ViewVelocity { get; private set; } = false;
public static bool JumpRecover { get; private set; } = false;
static public event Action<bool> HotkeyChange;
static public event Action<KeyCode> HotkeyKeyChange;
static public event Action<float> VelocityMultiplierChange;
static public event Action<float> MovementDragChange;
static public event Action<float> AngularDragChange;
static public event Action<bool> GravityChange;
static public event Action<bool> PointersReactionChange;
static public event Action<bool> IgnoreLocalChange;
static public event Action<bool> CombatReactionChange;
static public event Action<bool> AutoRecoverChange;
static public event Action<float> RecoverDelayChange;
static public event Action<bool> SlipperinessChange;
static public event Action<bool> BouncinessChange;
static public event Action<bool> ViewVelocityChange;
static public event Action<bool> JumpRecoverChange;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("PRM", "Player Ragdoll Mod");
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Hotkey.ToString(), Hotkey, null, null, true),
ms_category.CreateEntry(ModSetting.HotkeyKey.ToString(), HotkeyKey, "Hotkey"),
ms_category.CreateEntry(ModSetting.VelocityMultiplier.ToString(), VelocityMultiplier, null, null, true),
ms_category.CreateEntry(ModSetting.MovementDrag.ToString(), MovementDrag, null, null, true),
ms_category.CreateEntry(ModSetting.AngularDrag.ToString(), AngularDrag, null, null, true),
ms_category.CreateEntry(ModSetting.Gravity.ToString(), Gravity, null, null, true),
ms_category.CreateEntry(ModSetting.PointersReaction.ToString(), PointersReaction, null, null, true),
ms_category.CreateEntry(ModSetting.IgnoreLocal.ToString(), IgnoreLocal, null, null, true),
ms_category.CreateEntry(ModSetting.CombatReaction.ToString(), CombatReaction, null, null, true),
ms_category.CreateEntry(ModSetting.AutoRecover.ToString(), AutoRecover, null, null, true),
ms_category.CreateEntry(ModSetting.RecoverDelay.ToString(), RecoverDelay, null, null, true),
ms_category.CreateEntry(ModSetting.Slipperiness.ToString(), Slipperiness, null, null, true),
ms_category.CreateEntry(ModSetting.Bounciness.ToString(), Bounciness, null, null, true),
ms_category.CreateEntry(ModSetting.ViewVelocity.ToString(), ViewVelocity, null, null, true),
ms_category.CreateEntry(ModSetting.JumpRecover.ToString(), JumpRecover, null, null, true)
};
ms_entries[(int)ModSetting.HotkeyKey].OnEntryValueChangedUntyped.Subscribe(OnMelonSettingSave_HotkeyKey);
Hotkey = (bool)ms_entries[(int)ModSetting.Hotkey].BoxedValue;
HotkeyKey = (KeyCode)ms_entries[(int)ModSetting.HotkeyKey].BoxedValue;
VelocityMultiplier = Mathf.Clamp((float)ms_entries[(int)ModSetting.VelocityMultiplier].BoxedValue, 1f, 50f);
MovementDrag = Mathf.Clamp((float)ms_entries[(int)ModSetting.MovementDrag].BoxedValue, 0f, 50f);
AngularDrag = Mathf.Clamp((float)ms_entries[(int)ModSetting.AngularDrag].BoxedValue, 0f, 50f);
Gravity = (bool)ms_entries[(int)ModSetting.Gravity].BoxedValue;
PointersReaction = (bool)ms_entries[(int)ModSetting.PointersReaction].BoxedValue;
IgnoreLocal = (bool)ms_entries[(int)ModSetting.IgnoreLocal].BoxedValue;
CombatReaction = (bool)ms_entries[(int)ModSetting.CombatReaction].BoxedValue;
AutoRecover = (bool)ms_entries[(int)ModSetting.AutoRecover].BoxedValue;
RecoverDelay = Mathf.Clamp((float)ms_entries[(int)ModSetting.RecoverDelay].BoxedValue, 1f, 10f);
Slipperiness = (bool)ms_entries[(int)ModSetting.Slipperiness].BoxedValue;
Bounciness = (bool)ms_entries[(int)ModSetting.Bounciness].BoxedValue;
ViewVelocity = (bool)ms_entries[(int)ModSetting.ViewVelocity].BoxedValue;
JumpRecover = (bool)ms_entries[(int)ModSetting.JumpRecover].BoxedValue;
}
static void OnMelonSettingSave_HotkeyKey(object p_oldValue, object p_newValue)
{
if(p_newValue is KeyCode)
{
HotkeyKey = (KeyCode)p_newValue;
HotkeyKeyChange?.Invoke(HotkeyKey);
}
}
public static void SetSetting(ModSetting p_settings, object p_value)
{
switch(p_settings)
{
// Booleans
case ModSetting.Hotkey:
{
Hotkey = (bool)p_value;
HotkeyChange?.Invoke((bool)p_value);
}
break;
case ModSetting.Gravity:
{
Gravity = (bool)p_value;
GravityChange?.Invoke((bool)p_value);
}
break;
case ModSetting.PointersReaction:
{
PointersReaction = (bool)p_value;
PointersReactionChange?.Invoke((bool)p_value);
}
break;
case ModSetting.IgnoreLocal:
{
IgnoreLocal = (bool)p_value;
IgnoreLocalChange?.Invoke((bool)p_value);
}
break;
case ModSetting.CombatReaction:
{
CombatReaction = (bool)p_value;
CombatReactionChange?.Invoke((bool)p_value);
}
break;
case ModSetting.AutoRecover:
{
AutoRecover = (bool)p_value;
AutoRecoverChange?.Invoke((bool)p_value);
}
break;
case ModSetting.Slipperiness:
{
Slipperiness = (bool)p_value;
SlipperinessChange?.Invoke((bool)p_value);
}
break;
case ModSetting.Bounciness:
{
Bounciness = (bool)p_value;
BouncinessChange?.Invoke((bool)p_value);
}
break;
case ModSetting.ViewVelocity:
{
ViewVelocity = (bool)p_value;
ViewVelocityChange?.Invoke((bool)p_value);
}
break;
case ModSetting.JumpRecover:
{
JumpRecover = (bool)p_value;
JumpRecoverChange?.Invoke((bool)p_value);
}
break;
// Floats
case ModSetting.VelocityMultiplier:
{
VelocityMultiplier = (float)p_value;
VelocityMultiplierChange?.Invoke((float)p_value);
}
break;
case ModSetting.MovementDrag:
{
MovementDrag = (float)p_value;
MovementDragChange?.Invoke((float)p_value);
}
break;
case ModSetting.AngularDrag:
{
AngularDrag = (float)p_value;
AngularDragChange?.Invoke((float)p_value);
}
break;
case ModSetting.RecoverDelay:
{
RecoverDelay = (float)p_value;
RecoverDelayChange?.Invoke((float)p_value);
}
break;
}
if(ms_entries != null)
ms_entries[(int)p_settings].BoxedValue = p_value;
}
}
}

39
archived/ml_prm/Utils.cs Normal file
View file

@ -0,0 +1,39 @@
using ABI.CCK.Components;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.MovementSystem;
using System.Reflection;
using UnityEngine;
namespace ml_prm
{
static class Utils
{
static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_appliedGravity = typeof(MovementSystem).GetField("_appliedGravity", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldMovementLimit()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
{
l_result = CVRWorld.Instance.baseMovementSpeed;
l_result *= CVRWorld.Instance.sprintMultiplier;
l_result *= CVRWorld.Instance.inAirMovementMultiplier;
l_result *= CVRWorld.Instance.flyMultiplier;
}
return l_result;
}
public static bool IsGrounded(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(p_instance);
public static Vector3 GetAppliedGravity(this MovementSystem p_instance) => (Vector3)ms_appliedGravity.GetValue(p_instance);
public static void SetAppliedGravity(this MovementSystem p_instance, Vector3 p_vec) => ms_appliedGravity.SetValue(p_instance, p_vec);
public static void CopyGlobal(this Transform p_source, Transform p_target)
{
p_target.position = p_source.position;
p_target.rotation = p_source.rotation;
}
}
}

View file

@ -0,0 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>PlayerRagdollMod</PackageId>
<Version>1.0.11</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>PlayerRagdollMod</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="PlayerRagdollMod.json" />
<None Remove="resources\person.png" />
<None Remove="vendor\RootMotion\info.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\person.png" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="BTKUILib">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.ClothModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.ClothModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.ParticleSystemModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.ParticleSystemModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -0,0 +1 @@
Put `PuppetMaster` and `RagdollMaster` folders from [PuppetMaster asset](https://assetstore.unity.com/packages/tools/physics/puppetmaster-48977) here.

View file

@ -0,0 +1 @@
https://assetstore.unity.com/packages/tools/physics/puppetmaster-48977