Compare commits

...

7 commits

Author SHA1 Message Date
SDraw
2c4fbb1731
Lower default impact sounds volume 2026-05-02 16:28:39 +03:00
SDraw
812b930bed
Version bump 2026-04-26 10:56:23 +03:00
SDraw
c25eaaf754
Merge pull request #5 from Slime-Senpai/master
Fix ml_vpc
2026-04-26 10:04:13 +03:00
Slime-Senpai
d8bb5881fc Add ml_vpc readme tutorial for linux 2026-04-25 17:29:07 +02:00
Slime-Senpai
e04724ed9d Fix ml_vpc 2026-04-25 17:08:07 +02:00
SDraw
2a06001100
Impact sounds 2026-04-18 22:42:47 +03:00
SDraw
15de34c0bd
Update README.md 2026-04-04 05:41:42 +00:00
27 changed files with 430 additions and 114 deletions

View file

@ -5,12 +5,14 @@ namespace ml_prm
public class PlayerRagdollMod : MelonLoader.MelonMod public class PlayerRagdollMod : MelonLoader.MelonMod
{ {
RagdollController m_controller = null; RagdollController m_controller = null;
SoundManager m_soundManager = null;
public override void OnInitializeMelon() public override void OnInitializeMelon()
{ {
Settings.Init(); Settings.Init();
GameEvents.Init(HarmonyInstance); GameEvents.Init(HarmonyInstance);
WorldManager.Init(); WorldManager.Init();
ResourcesHandler.ExtractResources();
} }
public override void OnLateInitializeMelon() public override void OnLateInitializeMelon()
@ -26,6 +28,8 @@ namespace ml_prm
yield return null; yield return null;
m_controller = new UnityEngine.GameObject("[PlayerRagdollMod]").AddComponent<RagdollController>(); m_controller = new UnityEngine.GameObject("[PlayerRagdollMod]").AddComponent<RagdollController>();
m_soundManager = new SoundManager(m_controller.transform);
m_soundManager.LoadSounds();
} }
System.Collections.IEnumerator WaitForWhitelist() System.Collections.IEnumerator WaitForWhitelist()
@ -40,6 +44,8 @@ namespace ml_prm
{ {
WorldManager.DeInit(); WorldManager.DeInit();
m_soundManager = null;
if(m_controller != null) if(m_controller != null)
UnityEngine.Object.Destroy(m_controller.gameObject); UnityEngine.Object.Destroy(m_controller.gameObject);
m_controller = null; m_controller = null;

View file

@ -34,7 +34,9 @@ namespace ml_prm
AngularDrag, AngularDrag,
RecoverDelay, RecoverDelay,
FallLimit, FallLimit,
GestureGrab GestureGrab,
ImpactSounds,
ImpactVolume
} }
const string c_ragdollKeyTooltip = "Switch ragdoll mode with '{0}' key"; const string c_ragdollKeyTooltip = "Switch ragdoll mode with '{0}' key";
@ -59,11 +61,13 @@ namespace ml_prm
static ToggleButton ms_buoyancyToggle = null; static ToggleButton ms_buoyancyToggle = null;
static ToggleButton ms_fallDamageToggle = null; static ToggleButton ms_fallDamageToggle = null;
static ToggleButton ms_gestureGrabToggle = null; static ToggleButton ms_gestureGrabToggle = null;
static ToggleButton ms_impactSoundsToggle = null;
static SliderFloat ms_velocityMultiplierSlider = null; static SliderFloat ms_velocityMultiplierSlider = null;
static SliderFloat ms_movementDragSlider = null; static SliderFloat ms_movementDragSlider = null;
static SliderFloat ms_angularMovementDragSlider = null; static SliderFloat ms_angularMovementDragSlider = null;
static SliderFloat ms_recoverDelaySlider = null; static SliderFloat ms_recoverDelaySlider = null;
static SliderFloat ms_fallLimitSlider = null; static SliderFloat ms_fallLimitSlider = null;
static SliderFloat ms_impactVolumeSlider = null;
static Button ms_resetButton = null; static Button ms_resetButton = null;
internal static void Init() internal static void Init()
@ -117,6 +121,9 @@ namespace ml_prm
ms_gestureGrabToggle = ms_category.AddToggle("Grab attaching", "Enable attaching of ragdolled body parts to pointers of 'grab' type<p>Warning: can lead to unpredictable physics behaviour in some cases", Settings.GestureGrab); ms_gestureGrabToggle = ms_category.AddToggle("Grab attaching", "Enable attaching of ragdolled body parts to pointers of 'grab' type<p>Warning: can lead to unpredictable physics behaviour in some cases", Settings.GestureGrab);
ms_gestureGrabToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.GestureGrab, state); ms_gestureGrabToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.GestureGrab, state);
ms_impactSoundsToggle = ms_category.AddToggle("Impact sounds", "Enable collision sound effects of ragdolled body parts", Settings.ImpactSounds);
ms_impactSoundsToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.ImpactSounds, state);
ms_velocityMultiplierSlider = ms_category.AddSlider("Velocity multiplier", "Velocity multiplier upon entering ragdoll state", Settings.VelocityMultiplier, 1f, 50f); ms_velocityMultiplierSlider = ms_category.AddSlider("Velocity multiplier", "Velocity multiplier upon entering ragdoll state", Settings.VelocityMultiplier, 1f, 50f);
ms_velocityMultiplierSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.VelocityMultiplier, value); ms_velocityMultiplierSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.VelocityMultiplier, value);
@ -133,6 +140,9 @@ namespace ml_prm
ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(Settings.FallLimit)); ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(Settings.FallLimit));
ms_fallLimitSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.FallLimit, value); ms_fallLimitSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.FallLimit, value);
ms_impactVolumeSlider = ms_category.AddSlider("Impact volume", "Volume of collision of ragdolled body parts", Settings.ImpactVolume * 100f, 0f, 100f);
ms_impactVolumeSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.ImpactVolume, value);
ms_resetButton = ms_category.AddButton("Reset settings", "", "Reset mod settings to default"); ms_resetButton = ms_category.AddButton("Reset settings", "", "Reset mod settings to default");
ms_resetButton.OnPress += Reset; ms_resetButton.OnPress += Reset;
} }
@ -202,6 +212,10 @@ namespace ml_prm
case UiIndex.GestureGrab: case UiIndex.GestureGrab:
Settings.SetSetting(Settings.ModSetting.GestureGrab, p_state); Settings.SetSetting(Settings.ModSetting.GestureGrab, p_state);
break; break;
case UiIndex.ImpactSounds:
Settings.SetSetting(Settings.ModSetting.ImpactSounds, p_state);
break;
} }
} }
catch(Exception e) catch(Exception e)
@ -238,6 +252,10 @@ namespace ml_prm
ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(p_value)); ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(p_value));
} }
break; break;
case UiIndex.ImpactVolume:
Settings.SetSetting(Settings.ModSetting.ImpactVolume, p_value * 0.01f);
break;
} }
} }
catch(Exception e) catch(Exception e)
@ -284,6 +302,9 @@ namespace ml_prm
OnToggleUpdate(UiIndex.GestureGrab, false); OnToggleUpdate(UiIndex.GestureGrab, false);
ms_gestureGrabToggle.ToggleValue = false; ms_gestureGrabToggle.ToggleValue = false;
OnToggleUpdate(UiIndex.ImpactSounds, true);
ms_impactSoundsToggle.ToggleValue = true;
OnSliderUpdate(UiIndex.VelocityMultiplier, 2f); OnSliderUpdate(UiIndex.VelocityMultiplier, 2f);
ms_velocityMultiplierSlider.SetSliderValue(2f); ms_velocityMultiplierSlider.SetSliderValue(2f);
@ -298,6 +319,9 @@ namespace ml_prm
OnSliderUpdate(UiIndex.FallLimit, 9.899494f); OnSliderUpdate(UiIndex.FallLimit, 9.899494f);
ms_fallLimitSlider.SetSliderValue(9.899494f); ms_fallLimitSlider.SetSliderValue(9.899494f);
OnSliderUpdate(UiIndex.ImpactVolume, 100f);
ms_impactVolumeSlider.SetSliderValue(25f);
} }
static void OnHotkeyKeyChanged(UnityEngine.KeyCode p_keyCode) static void OnHotkeyKeyChanged(UnityEngine.KeyCode p_keyCode)

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.2.6", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.2.7", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPriority(2)] [assembly: MelonLoader.MelonPriority(2)]
[assembly: MelonLoader.MelonAdditionalDependencies("BTKUILib")] [assembly: MelonLoader.MelonAdditionalDependencies("BTKUILib")]

View file

@ -17,7 +17,6 @@ Optional mod's settings page with [BTKUILib](https://github.com/BTK-Development/
* **Use gravity:** enables/disables gravity for ragdoll; `true` by default. * **Use gravity:** enables/disables gravity for ragdoll; `true` by default.
* Note: Forcibly enabled in worlds that don't allow flight. * 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. * **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. * **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. * **Auto recover:** enables automatic recovering after specific time delay; `false` by default.
* **Slipperiness:** enables/disable low friction of ragdoll; `false` by default. * **Slipperiness:** enables/disable low friction of ragdoll; `false` by default.
@ -30,9 +29,10 @@ Optional mod's settings page with [BTKUILib](https://github.com/BTK-Development/
* **Buoyancy:** enables floating in fluid volumes; `true` by default. * **Buoyancy:** enables floating in fluid volumes; `true` by default.
* Note: Forcibly enabled in worlds that don't allow flight. * Note: Forcibly enabled in worlds that don't allow flight.
* **Fall damage:** enables ragdoll when falling from specific height; `true` by default. * **Fall damage:** enables ragdoll when falling from specific height; `true` by default.
* **Gesture grab:** enables grabbing of ragdolled body parts by remote players with trigger/grab gesture; `false` by default. * **Grab attaching:** enables attaching of ragdolled body parts to pointers with `grab` type of avatars, props and world(s); `false` by default.
* Note: Can lead to unpredictable physics behaviour in some cases. * Note: Can lead to unpredictable physics behaviour in some cases.
* **Friends grab only:** Allow only friends to be able to grab your radgolled body parts; `true` by default. * **Impact sounds:** enables collision sound effects of ragdolled body parts; `true` by default.
* Note: Sounds can be replaced in `(game_folder)/UserData/PlayerRagdollMod` folder.
* **Velocity multiplier:** velocity force multiplier based on player's movement direction; `2.0` 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: Limited according to world's fly multiplier.
* Note: Forcibly set to `1.0` in worlds that don't allow flight. * Note: Forcibly set to `1.0` in worlds that don't allow flight.
@ -41,6 +41,7 @@ Optional mod's settings page with [BTKUILib](https://github.com/BTK-Development/
* **Angular movement drag:** angular movement resistance; `2.0` by default. * **Angular movement drag:** angular movement resistance; `2.0` by default.
* **Recover delay:** time delay for enabled `Auto recover` in seconds; `3.0` by default. * **Recover delay:** time delay for enabled `Auto recover` in seconds; `3.0` by default.
* **Fall limit:** height limit for fall damage; `5.0` by default. * **Fall limit:** height limit for fall damage; `5.0` by default.
* **Impact volume:** collision sounds volume of ragdolled body parts; `100.0` by default.
* **Reset settings:** resets mod settings to default. * **Reset settings:** resets mod settings to default.
Optional mod's settings in [UIExpansionKit](https://github.com/ddakebono/ChilloutMods): Optional mod's settings in [UIExpansionKit](https://github.com/ddakebono/ChilloutMods):

View file

@ -100,6 +100,15 @@ namespace ml_prm
Detach(); Detach();
} }
void OnCollisionEnter(Collision p_col)
{
if(Settings.ImpactSounds && m_ready && !m_rigidBody.isKinematic && (p_col.gameObject.layer != CVRLayers.PlayerClone))
{
if(p_col.impulse.magnitude > 5f)
SoundManager.Instance.PlaySound(SoundManager.ImpactType.Soft);
}
}
void OnContactEnter(ContactCollisionInfo p_col) void OnContactEnter(ContactCollisionInfo p_col)
{ {
if(m_ready && (RagdollController.Instance != null) && ContactManager.Exists) if(m_ready && (RagdollController.Instance != null) && ContactManager.Exists)

View file

@ -690,6 +690,9 @@ namespace ml_prm
m_lastRagdollPosition = m_puppetReferences.hips.position; m_lastRagdollPosition = m_puppetReferences.hips.position;
m_downTime = 0f; m_downTime = 0f;
if(Settings.ImpactSounds)
SoundManager.Instance.PlaySound(SoundManager.ImpactType.Hard);
m_ragdolled = true; m_ragdolled = true;
} }
} }

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace ml_prm
{
static class ResourcesHandler
{
const string c_modName = "PlayerRagdollMod";
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
static readonly List<string> ms_audioResources = new List<string>()
{
"body_medium_impact_hard1.wav",
"body_medium_impact_hard2.wav",
"body_medium_impact_hard3.wav",
"body_medium_impact_hard4.wav",
"body_medium_impact_hard5.wav",
"body_medium_impact_hard6.wav",
"body_medium_impact_soft1.wav",
"body_medium_impact_soft2.wav",
"body_medium_impact_soft3.wav",
"body_medium_impact_soft4.wav",
"body_medium_impact_soft5.wav",
"body_medium_impact_soft6.wav",
"body_medium_impact_soft7.wav"
};
public static void ExtractResources()
{
string l_dirPath = MelonLoader.Utils.MelonEnvironment.UserDataDirectory;
if(!Directory.Exists(l_dirPath))
Directory.CreateDirectory(l_dirPath);
l_dirPath = Path.Combine(l_dirPath, c_modName);
if(!Directory.Exists(l_dirPath))
Directory.CreateDirectory(l_dirPath);
foreach(string l_name in ms_audioResources)
{
string l_filePath = Path.Combine(l_dirPath, l_name);
if(!File.Exists(l_filePath))
ExtractAudioFile(l_name, l_filePath);
}
}
static void ExtractAudioFile(string p_name, string p_path)
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
try
{
Stream l_resourceStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources.sounds." + p_name);
Stream l_fileStream = File.Create(p_path);
l_resourceStream.CopyTo(l_fileStream);
l_fileStream.Flush();
l_fileStream.Close();
l_resourceStream.Close();
}
catch(Exception)
{
MelonLoader.MelonLogger.Warning("Unable to write '" + p_path + "' file, problems can occur.");
}
}
}
}

View file

@ -33,7 +33,9 @@ namespace ml_prm
Buoyancy, Buoyancy,
FallDamage, FallDamage,
FallLimit, FallLimit,
GestureGrab GestureGrab,
ImpactSounds,
ImpactVolume
} }
public static bool Hotkey { get; private set; } = true; public static bool Hotkey { get; private set; } = true;
@ -54,6 +56,8 @@ namespace ml_prm
public static bool FallDamage { get; private set; } = true; public static bool FallDamage { get; private set; } = true;
public static float FallLimit { get; private set; } = 9.899494f; public static float FallLimit { get; private set; } = 9.899494f;
public static bool GestureGrab { get; private set; } = false; public static bool GestureGrab { get; private set; } = false;
public static bool ImpactSounds { get; private set; } = true;
public static float ImpactVolume { get; private set; } = 0.25f;
public static readonly SettingEvent<bool> OnHotkeyChanged = new SettingEvent<bool>(); public static readonly SettingEvent<bool> OnHotkeyChanged = new SettingEvent<bool>();
public static readonly SettingEvent<KeyCode> OnHotkeyKeyChanged = new SettingEvent<KeyCode>(); public static readonly SettingEvent<KeyCode> OnHotkeyKeyChanged = new SettingEvent<KeyCode>();
@ -73,6 +77,8 @@ namespace ml_prm
public static readonly SettingEvent<bool> OnFallDamageChanged = new SettingEvent<bool>(); public static readonly SettingEvent<bool> OnFallDamageChanged = new SettingEvent<bool>();
public static readonly SettingEvent<float> OnFallLimitChanged = new SettingEvent<float>(); public static readonly SettingEvent<float> OnFallLimitChanged = new SettingEvent<float>();
public static readonly SettingEvent<bool> OnGestureGrabChanged = new SettingEvent<bool>(); public static readonly SettingEvent<bool> OnGestureGrabChanged = new SettingEvent<bool>();
public static readonly SettingEvent<bool> OnImpactSoundsChanged = new SettingEvent<bool>();
public static readonly SettingEvent<float> OnImpactVolumeChanged = new SettingEvent<float>();
static MelonLoader.MelonPreferences_Category ms_category = null; static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null; static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -100,7 +106,9 @@ namespace ml_prm
ms_category.CreateEntry(ModSetting.Buoyancy.ToString(), Buoyancy, null, null, true), ms_category.CreateEntry(ModSetting.Buoyancy.ToString(), Buoyancy, null, null, true),
ms_category.CreateEntry(ModSetting.FallDamage.ToString(), FallDamage, null, null, true), ms_category.CreateEntry(ModSetting.FallDamage.ToString(), FallDamage, null, null, true),
ms_category.CreateEntry(ModSetting.FallLimit.ToString(), FallLimit, null, null, true), ms_category.CreateEntry(ModSetting.FallLimit.ToString(), FallLimit, null, null, true),
ms_category.CreateEntry(ModSetting.GestureGrab.ToString(), GestureGrab, null, null, true) ms_category.CreateEntry(ModSetting.GestureGrab.ToString(), GestureGrab, null, null, true),
ms_category.CreateEntry(ModSetting.ImpactSounds.ToString(), ImpactSounds, null, null, true),
ms_category.CreateEntry(ModSetting.ImpactVolume.ToString(), ImpactVolume, null, null, true)
}; };
ms_entries[(int)ModSetting.HotkeyKey].OnEntryValueChangedUntyped.Subscribe(OnMelonSettingSave_HotkeyKey); ms_entries[(int)ModSetting.HotkeyKey].OnEntryValueChangedUntyped.Subscribe(OnMelonSettingSave_HotkeyKey);
@ -123,6 +131,8 @@ namespace ml_prm
FallDamage = (bool)ms_entries[(int)ModSetting.FallDamage].BoxedValue; FallDamage = (bool)ms_entries[(int)ModSetting.FallDamage].BoxedValue;
FallLimit = Mathf.Clamp((float)ms_entries[(int)ModSetting.FallLimit].BoxedValue, 4.5f, 44.5f); FallLimit = Mathf.Clamp((float)ms_entries[(int)ModSetting.FallLimit].BoxedValue, 4.5f, 44.5f);
GestureGrab = (bool)ms_entries[(int)ModSetting.GestureGrab].BoxedValue; GestureGrab = (bool)ms_entries[(int)ModSetting.GestureGrab].BoxedValue;
ImpactSounds = (bool)ms_entries[(int)ModSetting.ImpactSounds].BoxedValue;
ImpactVolume = Mathf.Clamp((float)ms_entries[(int)ModSetting.ImpactVolume].BoxedValue, 0f, 1f);
} }
static void OnMelonSettingSave_HotkeyKey(object p_oldValue, object p_newValue) static void OnMelonSettingSave_HotkeyKey(object p_oldValue, object p_newValue)
@ -232,6 +242,13 @@ namespace ml_prm
} }
break; break;
case ModSetting.ImpactSounds:
{
ImpactSounds = (bool)p_value;
OnImpactSoundsChanged.Invoke(ImpactSounds);
}
break;
// Floats // Floats
case ModSetting.VelocityMultiplier: case ModSetting.VelocityMultiplier:
{ {
@ -267,6 +284,13 @@ namespace ml_prm
OnFallLimitChanged.Invoke(FallLimit); OnFallLimitChanged.Invoke(FallLimit);
} }
break; break;
case ModSetting.ImpactVolume:
{
ImpactVolume = (float)p_value;
OnImpactVolumeChanged.Invoke(ImpactVolume);
}
break;
} }
if(ms_entries != null) if(ms_entries != null)

141
ml_prm/SoundManager.cs Normal file
View file

@ -0,0 +1,141 @@
using ABI_RC.Core;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace ml_prm
{
public class SoundManager
{
enum SoundType
{
ImpactHard1 = 0,
ImpactHard2,
ImpactHard3,
ImpactHard4,
ImpactHard5,
ImpactHard6,
ImpactSoft1,
ImpactSoft2,
ImpactSoft3,
ImpactSoft4,
ImpactSoft5,
ImpactSoft6,
ImpactSoft7,
Count
}
public enum ImpactType
{
Hard = 0,
Soft
}
const string c_modName = "PlayerRagdollMod";
public static SoundManager Instance { get; private set; } = null;
bool m_loaded = false;
readonly AudioClip[] m_clips = null;
AudioSource m_audioSource = null;
public SoundManager(Transform p_root)
{
m_clips = new AudioClip[(int)SoundType.Count];
for(int i = 0; i < (int)SoundType.Count; i++)
m_clips[i] = null;
GameObject l_audioSource = new GameObject("[ImpactSource]");
l_audioSource.transform.parent = p_root;
l_audioSource.transform.localPosition = Vector3.zero;
l_audioSource.transform.localRotation = Quaternion.identity;
m_audioSource = l_audioSource.AddComponent<AudioSource>();
m_audioSource.playOnAwake = false;
m_audioSource.loop = false;
m_audioSource.minDistance = 2f;
m_audioSource.maxDistance = 5f;
m_audioSource.dopplerLevel = 0f;
m_audioSource.panStereo = 0f;
m_audioSource.spatialBlend = 0f; // 2D
m_audioSource.spread = 0f;
m_audioSource.rolloffMode = AudioRolloffMode.Linear;
m_audioSource.outputAudioMixerGroup = RootLogic.Instance.mainSfx;
Instance = this;
}
~SoundManager()
{
if(Instance == this)
Instance = null;
if(m_audioSource != null)
Object.Destroy(m_audioSource);
m_audioSource = null;
}
internal void LoadSounds()
{
if(!m_loaded)
{
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard1, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard1.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard2, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard2.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard3, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard3.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard4, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard4.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard5, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard5.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactHard6, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_hard6.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft1, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft1.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft2, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft2.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft3, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft3.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft4, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft4.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft5, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft5.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft6, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft6.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.ImpactSoft7, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "body_medium_impact_soft7.wav")));
m_loaded = true;
}
}
IEnumerator LoadAudioClip(SoundType p_type, string p_path)
{
using UnityWebRequest l_uwr = UnityWebRequestMultimedia.GetAudioClip("file://" + p_path, AudioType.WAV);
((DownloadHandlerAudioClip)l_uwr.downloadHandler).streamAudio = true;
yield return l_uwr.SendWebRequest();
if((l_uwr.result == UnityWebRequest.Result.ConnectionError) || (l_uwr.result == UnityWebRequest.Result.ProtocolError))
{
MelonLoader.MelonLogger.Warning(l_uwr.error);
yield break;
}
AudioClip l_content;
AudioClip l_clip = (l_content = DownloadHandlerAudioClip.GetContent(l_uwr));
yield return l_content;
if(!l_uwr.isDone || (l_clip == null))
yield break;
m_clips[(int)p_type] = l_clip;
}
public void PlaySound(ImpactType p_type)
{
if(m_loaded)
{
int l_index = -1;
switch(p_type)
{
case ImpactType.Hard:
l_index = (int)SoundType.ImpactHard1 + Random.Range(0, 6);
break;
case ImpactType.Soft:
l_index = (int)SoundType.ImpactSoft1 + Random.Range(0, 7);
break;
}
if((l_index != -1) && (m_clips[l_index] != null))
m_audioSource.PlayOneShot(m_clips[l_index], Settings.ImpactVolume);
}
}
}
}

View file

@ -1,99 +1,127 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<PackageId>PlayerRagdollMod</PackageId> <PackageId>PlayerRagdollMod</PackageId>
<Version>1.2.6</Version> <Version>1.2.7</Version>
<Authors>SDraw</Authors> <Authors>SDraw</Authors>
<Company>SDraw</Company> <Company>SDraw</Company>
<Product>PlayerRagdollMod</Product> <Product>PlayerRagdollMod</Product>
<AssemblyName>PlayerRagdollMod</AssemblyName> <AssemblyName>PlayerRagdollMod</AssemblyName>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="resources/person.png" /> <None Remove="resources/person.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="resources/person.png" /> <EmbeddedResource Include="resources/person.png" />
</ItemGroup> <EmbeddedResource Include="resources\sounds\body_medium_impact_hard1.wav" />
<EmbeddedResource Include="resources\sounds\body_medium_impact_hard2.wav" />
<ItemGroup> <EmbeddedResource Include="resources\sounds\body_medium_impact_hard3.wav" />
<Reference Include="0Harmony"> <EmbeddedResource Include="resources\sounds\body_medium_impact_hard4.wav" />
<HintPath>$(CVRPath)/MelonLoader/net35/0Harmony.dll</HintPath> <EmbeddedResource Include="resources\sounds\body_medium_impact_hard5.wav" />
<Private>false</Private> <EmbeddedResource Include="resources\sounds\body_medium_impact_hard6.wav" />
<SpecificVersion>false</SpecificVersion> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft1.wav" />
</Reference> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft2.wav" />
<Reference Include="Assembly-CSharp"> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft3.wav" />
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Assembly-CSharp.dll</HintPath> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft4.wav" />
<Private>false</Private> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft5.wav" />
<SpecificVersion>false</SpecificVersion> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft6.wav" />
</Reference> <EmbeddedResource Include="resources\sounds\body_medium_impact_soft7.wav" />
<Reference Include="Assembly-CSharp-firstpass"> </ItemGroup>
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private> <ItemGroup>
<SpecificVersion>false</SpecificVersion> <Reference Include="0Harmony">
</Reference> <HintPath>$(CVRPath)/MelonLoader/net35/0Harmony.dll</HintPath>
<Reference Include="BTKUILib"> <Private>false</Private>
<HintPath>$(CVRPath)/Mods/BTKUILib.dll</HintPath> <SpecificVersion>false</SpecificVersion>
<Private>false</Private> </Reference>
<SpecificVersion>false</SpecificVersion> <Reference Include="Assembly-CSharp">
</Reference> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="BTKUILib">
<HintPath>$(CVRPath)/Mods/BTKUILib.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="ECM2"> <Reference Include="ECM2">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/ECM2.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/ECM2.dll</HintPath>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<Reference Include="MelonLoader"> <Reference Include="MelonLoader">
<HintPath>$(CVRPath)/MelonLoader/net35/MelonLoader.dll</HintPath> <HintPath>$(CVRPath)/MelonLoader/net35/MelonLoader.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.AnimationModule"> <Reference Include="UnityEngine.AnimationModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.AnimationModule.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.ClothModule"> <Reference Include="UnityEngine.AudioModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.ClothModule.dll</HintPath> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AudioModule.dll</HintPath>
<Private>false</Private> <SpecificVersion>false</SpecificVersion>
<SpecificVersion>false</SpecificVersion> <Private>false</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.ClothModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.CoreModule.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.ClothModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.InputLegacyModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.InputLegacyModule.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.PhysicsModule"> <Reference Include="UnityEngine.InputLegacyModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.PhysicsModule.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.InputLegacyModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.ParticleSystemModule"> <Reference Include="UnityEngine.PhysicsModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.ParticleSystemModule.dll</HintPath> <HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion> <SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
</ItemGroup> <Reference Include="UnityEngine.ParticleSystemModule">
<HintPath>$(CVRPath)/ChilloutVR_Data/Managed/UnityEngine.ParticleSystemModule.dll</HintPath>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Private>false</Private>
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;$(CVRPath)/Mods/&quot;" /> <SpecificVersion>false</SpecificVersion>
</Target> </Reference>
<Reference Include="UnityEngine.UnityWebRequestAudioModule">
</Project> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.UnityWebRequestModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;$(CVRPath)/Mods/&quot;" />
</Target>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -11,7 +11,7 @@ namespace ml_vpc
public override void OnInitializeMelon() public override void OnInitializeMelon()
{ {
HarmonyInstance.Patch(typeof(YoutubeDl).GetMethod("GetVideoMetaDataAsync", BindingFlags.NonPublic | BindingFlags.Static), HarmonyInstance.Patch(typeof(YoutubeDl).GetMethod("GetVideoMetaDataAsync", BindingFlags.Public | BindingFlags.Static),
new HarmonyLib.HarmonyMethod(typeof(VideoPlayerCookies).GetMethod(nameof(OnGetYoutubeVideoMetaData_Prefix), BindingFlags.NonPublic | BindingFlags.Static)) new HarmonyLib.HarmonyMethod(typeof(VideoPlayerCookies).GetMethod(nameof(OnGetYoutubeVideoMetaData_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
); );
@ -23,7 +23,7 @@ namespace ml_vpc
Settings.Init(); Settings.Init();
} }
static void OnGetYoutubeVideoMetaData_Prefix(ref string parameter) static void OnGetYoutubeVideoMetaData_Prefix(ref string youtubeUrl, ref string existingParameters)
{ {
try try
{ {
@ -34,36 +34,36 @@ namespace ml_vpc
{ {
case Settings.CookieMode.File: case Settings.CookieMode.File:
if (File.Exists(ms_cookiesPath)) if (File.Exists(ms_cookiesPath))
parameter += string.Format(" --cookies \"{0}\"", ms_cookiesPath); existingParameters += string.Format("--cookies \"{0}\"", ms_cookiesPath);
else else
MelonLoader.MelonLogger.Warning("Cookies file not found in: '" + ms_cookiesPath + "'"); MelonLoader.MelonLogger.Warning("Cookies file not found in: '" + ms_cookiesPath + "'");
break; break;
case Settings.CookieMode.BrowserFirefox: case Settings.CookieMode.BrowserFirefox:
parameter += " --cookies-from-browser firefox"; existingParameters += "--cookies-from-browser firefox";
break; break;
case Settings.CookieMode.BrowserBrave: case Settings.CookieMode.BrowserBrave:
parameter += " --cookies-from-browser brave"; existingParameters += "--cookies-from-browser brave";
break; break;
case Settings.CookieMode.BrowserChrome: case Settings.CookieMode.BrowserChrome:
parameter += " --cookies-from-browser chrome"; existingParameters += "--cookies-from-browser chrome";
break; break;
case Settings.CookieMode.BrowserChromium: case Settings.CookieMode.BrowserChromium:
parameter += " --cookies-from-browser chromium"; existingParameters += "--cookies-from-browser chromium";
break; break;
case Settings.CookieMode.BrowserEdge: case Settings.CookieMode.BrowserEdge:
parameter += " --cookies-from-browser edge"; existingParameters += "--cookies-from-browser edge";
break; break;
case Settings.CookieMode.BrowserOpera: case Settings.CookieMode.BrowserOpera:
parameter += " --cookies-from-browser opera"; existingParameters += "--cookies-from-browser opera";
break; break;
case Settings.CookieMode.BrowserSafari: case Settings.CookieMode.BrowserSafari:
parameter += " --cookies-from-browser safari"; existingParameters += "--cookies-from-browser safari";
break; break;
case Settings.CookieMode.BrowserVivaldi: case Settings.CookieMode.BrowserVivaldi:
parameter += " --cookies-from-browser vivaldi"; existingParameters += "--cookies-from-browser vivaldi";
break; break;
case Settings.CookieMode.BrowserWhale: case Settings.CookieMode.BrowserWhale:
parameter += " --cookies-from-browser whale"; existingParameters += "--cookies-from-browser whale";
break; break;
} }
} }

View file

@ -1,5 +1,5 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_vpc.VideoPlayerCookies), "VideoPlayerCookies", "1.0.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_vpc.VideoPlayerCookies), "VideoPlayerCookies", "1.0.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonAdditionalCredits("kafeijao")] [assembly: MelonLoader.MelonAdditionalCredits("kafeijao, Slime")]

View file

@ -27,6 +27,19 @@ Available mod's settings in `Settings - General - Video Player Cookies`:
* Firefox-based browsers: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt) extension * Firefox-based browsers: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt) extension
* Save result as file named `cookies.txt` in `<game_folder>/UserData` folder * Save result as file named `cookies.txt` in `<game_folder>/UserData` folder
# How to make it work on Linux (Tested on CachyOS / Arch with Firefox)
Because of Linux / Proton, the cookies.txt doesn't seem to work well, instead it's better to use browser mode, the following tutorial explains how to set it up (tested using Firefox)
* Go to the mod settings and select your browser
* Put a video and wait for it to fail, then look at the logs
* It should tell you an error because it can't find your cookies in a path, for Firefox it looks like `AppData/Local/Packages/Mozilla.Firefox_n80bbvh6b1yt2/LocalCache/Roaming/Mozilla/Firefox/Profiles/`
* Go to AppData in the drive_c of the game, on Arch this is usually located at `/home/[USER]/.local/share/Steam/steamapps/compatdata/661130/pfx/drive_c/users/steamuser/Appdata`
* Create all the folders that it requires until the last one, for Firefox this means the folders `Packages`, `Mozilla.Firefox_n80bbvh6b1yt2`, `LocalCache`, `Roaming`, `Mozilla` and `Firefox` but don't create the last one (`Profiles`) yet unless you don't intend to use your real browser
* Because your cookies could change or expire, instead of giving it the files once, we'll use a symlink to the real firefox on your machine. This is completely up to you and you could just copy the files or get them from somewhere else
* Now you can create a link that points to the folder of your browser on Linux. For Firefox, this is the `Profiles` folder that should point to `/home/[USER]/.mozilla/firefox/`
* If you followed all the steps, the next video you put should successfully get the cookies from your browser
# Notes # Notes
* After first use yt-dlp will remove unnecessary cookies from file automatically. * After first use yt-dlp will remove unnecessary cookies from file automatically.
* Cookies contain private information and access to your YouTube account, **do not share them to anyone**. * Cookies contain private information and access to your YouTube account, **do not share them to anyone**.

View file

@ -6,7 +6,7 @@
<AssemblyName>VideoPlayerCookies</AssemblyName> <AssemblyName>VideoPlayerCookies</AssemblyName>
<Authors>SDraw</Authors> <Authors>SDraw</Authors>
<Company>SDraw</Company> <Company>SDraw</Company>
<Version>1.0.4</Version> <Version>1.0.5</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">