();
+ }
}
}
diff --git a/ml_prm/ModUi.cs b/ml_prm/ModUi.cs
index bbd90cc..616da80 100644
--- a/ml_prm/ModUi.cs
+++ b/ml_prm/ModUi.cs
@@ -34,7 +34,10 @@ namespace ml_prm
MovementDrag,
AngularDrag,
RecoverDelay,
- FallLimit
+ FallLimit,
+ GestureGrab,
+ FriendsGrab,
+ GrabDistance
}
const string c_ragdollKeyTooltip = "Switch ragdoll mode with '{0}' key";
@@ -58,11 +61,14 @@ namespace ml_prm
static ToggleButton ms_jumpRecoverToggle = null;
static ToggleButton ms_buoyancyToggle = null;
static ToggleButton ms_fallDamageToggle = null;
+ static ToggleButton ms_gestureGrabToggle = null;
+ static ToggleButton ms_friendsGrabToggle = null;
static SliderFloat ms_velocityMultiplierSlider = null;
static SliderFloat ms_movementDragSlider = null;
static SliderFloat ms_angularMovementDragSlider = null;
static SliderFloat ms_recoverDelaySlider = null;
static SliderFloat ms_fallLimitSlider = null;
+ static SliderFloat ms_grabDistanceSlider = null;
static Button ms_resetButton = null;
internal static void Init()
@@ -77,7 +83,7 @@ namespace ml_prm
ms_ragdollButton = ms_category.AddButton("Switch ragdoll", "PRM-Person", "Switch between normal and ragdoll state");
ms_ragdollButton.OnPress += OnSwitch;
-
+
ms_hotkeyToggle = ms_category.AddToggle("Use hotkey", "Switch ragdoll mode with 'R' key", Settings.Hotkey);
ms_hotkeyToggle.ToggleTooltip = string.Format(c_ragdollKeyTooltip, Settings.HotkeyKey);
ms_hotkeyToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Hotkey, state);
@@ -116,6 +122,12 @@ namespace ml_prm
ms_fallDamageToggle = ms_category.AddToggle("Fall damage", "Enable ragdoll when falling from height", Settings.FallDamage);
ms_fallDamageToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.FallDamage, state);
+ ms_gestureGrabToggle = ms_category.AddToggle("Gesture grab", "Enable grabbing of ragdolled body parts by remote players with trigger gestureWarning: can lead to unpredictable physics behaviour in some cases", Settings.GestureGrab);
+ ms_gestureGrabToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.GestureGrab, state);
+
+ ms_friendsGrabToggle = ms_category.AddToggle("Friends grab only", " ", Settings.FriendsGrab);
+ ms_friendsGrabToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.FriendsGrab, state);
+
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);
@@ -132,6 +144,9 @@ namespace ml_prm
ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(Settings.FallLimit));
ms_fallLimitSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.FallLimit, value);
+ ms_grabDistanceSlider = ms_category.AddSlider("Grab distance", "Minimal distance for successful grab", Settings.GrabDistance, 0f, 1f);
+ ms_grabDistanceSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.GrabDistance, value);
+
ms_resetButton = ms_category.AddButton("Reset settings", "", "Reset mod settings to default");
ms_resetButton.OnPress += Reset;
}
@@ -201,6 +216,14 @@ namespace ml_prm
case UiIndex.FallDamage:
Settings.SetSetting(Settings.ModSetting.FallDamage, p_state);
break;
+
+ case UiIndex.GestureGrab:
+ Settings.SetSetting(Settings.ModSetting.GestureGrab, p_state);
+ break;
+
+ case UiIndex.FriendsGrab:
+ Settings.SetSetting(Settings.ModSetting.FriendsGrab, p_state);
+ break;
}
}
catch(Exception e)
@@ -237,6 +260,10 @@ namespace ml_prm
ms_fallLimitSlider.SliderTooltip = string.Format(c_fallLimitTooltip, GetDropHeight(p_value));
}
break;
+
+ case UiIndex.GrabDistance:
+ Settings.SetSetting(Settings.ModSetting.GrabDistance, p_value);
+ break;
}
}
catch(Exception e)
@@ -283,6 +310,12 @@ namespace ml_prm
OnToggleUpdate(UiIndex.FallDamage, true);
ms_fallDamageToggle.ToggleValue = true;
+ OnToggleUpdate(UiIndex.GestureGrab, false);
+ ms_gestureGrabToggle.ToggleValue = false;
+
+ OnToggleUpdate(UiIndex.FriendsGrab, true);
+ ms_friendsGrabToggle.ToggleValue = true;
+
OnSliderUpdate(UiIndex.VelocityMultiplier, 2f);
ms_velocityMultiplierSlider.SetSliderValue(2f);
@@ -297,6 +330,9 @@ namespace ml_prm
OnSliderUpdate(UiIndex.FallLimit, 9.899494f);
ms_fallLimitSlider.SetSliderValue(9.899494f);
+
+ OnSliderUpdate(UiIndex.GrabDistance, 0.1f);
+ ms_grabDistanceSlider.SetSliderValue(0.1f);
}
static void OnHotkeyKeyChanged(UnityEngine.KeyCode p_keyCode)
diff --git a/ml_prm/Properties/AssemblyInfo.cs b/ml_prm/Properties/AssemblyInfo.cs
index 1729147..c0c841c 100644
--- a/ml_prm/Properties/AssemblyInfo.cs
+++ b/ml_prm/Properties/AssemblyInfo.cs
@@ -1,4 +1,4 @@
-[assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.1.8", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
+[assembly: MelonLoader.MelonInfo(typeof(ml_prm.PlayerRagdollMod), "PlayerRagdollMod", "1.1.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPriority(2)]
[assembly: MelonLoader.MelonAdditionalDependencies("BTKUILib")]
diff --git a/ml_prm/README.md b/ml_prm/README.md
index af7d4d0..7353c9f 100644
--- a/ml_prm/README.md
+++ b/ml_prm/README.md
@@ -29,6 +29,9 @@ Optional mod's settings page with [BTKUILib](https://github.com/BTK-Development/
* **Buoyancy:** enables floating in fluid volumes; `true` by default.
* Note: Forcibly enabled in worlds that don't allow flight.
* **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 gesture; `false` by default.
+ * 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.
* **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.
@@ -37,6 +40,7 @@ Optional mod's settings page with [BTKUILib](https://github.com/BTK-Development/
* **Angular movement drag:** angular movement resistance; `2.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.
+* **Grab distance:** minimal distance for successful grab; `0.1` by default.
* **Reset settings:** resets mod settings to default.
Optional mod's settings in [UIExpansionKit](https://github.com/ddakebono/ChilloutMods):
diff --git a/ml_prm/RagdollBodypartHandler.cs b/ml_prm/RagdollBodypartHandler.cs
index 7f58f55..db2c2cf 100644
--- a/ml_prm/RagdollBodypartHandler.cs
+++ b/ml_prm/RagdollBodypartHandler.cs
@@ -18,6 +18,11 @@ namespace ml_prm
bool m_shouldHaveInfluencer = false;
bool m_activeGravity = true;
+ bool m_attached = false;
+ Transform m_attachedHand = null;
+ Transform m_attachTransform = null;
+ FixedJoint m_attachJoint = null;
+
// Unity events
void Awake()
{
@@ -72,6 +77,8 @@ namespace ml_prm
{
if(collider != null)
CVRParticlePointerManager.RemoveTrigger(collider);
+
+ Detach();
}
void FixedUpdate()
@@ -80,11 +87,26 @@ namespace ml_prm
{
m_rigidBody.useGravity = false;
- if(m_activeGravity && ((m_physicsInfluencer == null) || !m_physicsInfluencer.enableInfluence || !m_physicsInfluencer.GetSubmerged()))
+ if(!m_attached && m_activeGravity && ((m_physicsInfluencer == null) || !m_physicsInfluencer.enableInfluence || !m_physicsInfluencer.GetSubmerged()))
m_rigidBody.AddForce(BetterBetterCharacterController.Instance.GravityResult.AppliedGravity * m_rigidBody.mass);
}
}
+ void Update()
+ {
+ if(m_attached && !ReferenceEquals(m_attachTransform, null) && (m_attachTransform == null))
+ {
+ m_attachTransform = null;
+
+ if(m_attachJoint != null)
+ Object.Destroy(m_attachJoint);
+ m_attachJoint = null;
+
+ m_attachedHand = null;
+ m_attached = false;
+ }
+ }
+
void OnTriggerEnter(Collider p_col)
{
if(Settings.PointersReaction && (RagdollController.Instance != null))
@@ -178,6 +200,54 @@ namespace ml_prm
return (Settings.IgnoreLocal && (p_transform.root == PlayerSetup.Instance.transform));
}
+ public bool Attach(Transform p_hand, Vector3 p_pos)
+ {
+ bool l_result = false;
+
+ if(!m_attached && (collider != null))
+ {
+ if(Vector3.Distance(p_pos, collider.ClosestPoint(p_pos)) <= Settings.GrabDistance)
+ {
+ GameObject l_attachPoint = new GameObject("[AttachPoint]");
+ m_attachTransform = l_attachPoint.transform;
+ m_attachTransform.parent = p_hand;
+ m_attachTransform.position = p_pos;
+
+ Rigidbody l_body = l_attachPoint.AddComponent();
+ l_body.isKinematic = true;
+ l_body.detectCollisions = false;
+
+ m_attachJoint = this.gameObject.AddComponent();
+ m_attachJoint.connectedBody = l_body;
+ m_attachJoint.breakForce = Mathf.Infinity;
+ m_attachJoint.breakTorque = Mathf.Infinity;
+
+ m_attached = true;
+ m_attachedHand = p_hand;
+ l_result = true;
+ }
+ }
+ return l_result;
+ }
+
+ public void Detach() => Detach(m_attachedHand);
+ public void Detach(Transform p_hand)
+ {
+ if(m_attached && ReferenceEquals(m_attachedHand, p_hand))
+ {
+ if(m_attachTransform != null)
+ Object.Destroy(m_attachTransform.gameObject);
+ m_attachTransform = null;
+
+ if(m_attachJoint != null)
+ Object.Destroy(m_attachJoint);
+ m_attachJoint = null;
+
+ m_attachedHand = null;
+ m_attached = false;
+ }
+ }
+
// CVRTriggerVolume
public void TriggerEnter(CVRPointer pointer)
{
@@ -187,6 +257,8 @@ namespace ml_prm
RagdollController.Instance.SwitchRagdoll();
}
}
- public void TriggerExit(CVRPointer pointer) { }
+ public void TriggerExit(CVRPointer pointer)
+ {
+ }
}
}
diff --git a/ml_prm/RagdollController.cs b/ml_prm/RagdollController.cs
index feaf031..7fd72b3 100644
--- a/ml_prm/RagdollController.cs
+++ b/ml_prm/RagdollController.cs
@@ -88,6 +88,7 @@ namespace ml_prm
Settings.OnBouncinessChanged.AddHandler(this.OnPhysicsMaterialChanged);
Settings.OnBuoyancyChanged.AddHandler(this.OnBuoyancyChanged);
Settings.OnFallDamageChanged.AddHandler(this.OnFallDamageChanged);
+ Settings.OnGestureGrabChanged.AddHandler(this.OnGestureGrabChanged);
GameEvents.OnAvatarClear.AddHandler(this.OnAvatarClear);
GameEvents.OnAvatarSetup.AddHandler(this.OnAvatarSetup);
@@ -103,6 +104,7 @@ namespace ml_prm
BetterBetterCharacterController.OnTeleport.AddListener(this.OnPlayerTeleport);
ModUi.OnSwitchChanged.AddHandler(this.SwitchRagdoll);
+ RemoteGestureHandler.OnGestureState.AddHandler(this.OnRemotePlayerGestureStateChanged);
}
void OnDestroy()
@@ -135,6 +137,7 @@ namespace ml_prm
Settings.OnBouncinessChanged.RemoveHandler(this.OnPhysicsMaterialChanged);
Settings.OnBuoyancyChanged.RemoveHandler(this.OnBuoyancyChanged);
Settings.OnFallDamageChanged.RemoveHandler(this.OnFallDamageChanged);
+ Settings.OnGestureGrabChanged.RemoveHandler(this.OnGestureGrabChanged);
GameEvents.OnAvatarClear.RemoveHandler(this.OnAvatarClear);
GameEvents.OnAvatarSetup.RemoveHandler(this.OnAvatarSetup);
@@ -150,6 +153,7 @@ namespace ml_prm
BetterBetterCharacterController.OnTeleport.RemoveListener(this.OnPlayerTeleport);
ModUi.OnSwitchChanged.RemoveHandler(this.SwitchRagdoll);
+ RemoteGestureHandler.OnGestureState.RemoveHandler(this.OnRemotePlayerGestureStateChanged);
}
void Update()
@@ -485,6 +489,37 @@ namespace ml_prm
p_result.m_result |= (m_enabled && (m_vrIK != null));
}
+ // Custom game events
+ void OnRemotePlayerGestureStateChanged(ABI_RC.Core.Player.PuppetMaster p_master, bool p_left, bool p_state)
+ {
+ if(m_avatarReady && m_enabled && Settings.GestureGrab && (p_master.animatorManager.Animator != null))
+ {
+ Transform l_hand = p_master.animatorManager.Animator.GetBoneTransform(p_left ? HumanBodyBones.LeftHand : HumanBodyBones.RightHand);
+ Transform l_finger = p_master.animatorManager.Animator.GetBoneTransform(p_left ? HumanBodyBones.LeftMiddleProximal : HumanBodyBones.RightMiddleProximal);
+
+ if(l_hand != null)
+ {
+ Vector3 l_pos = l_hand.position;
+ if(l_finger != null)
+ {
+ l_pos += l_finger.position;
+ l_pos *= 0.5f;
+ }
+
+ foreach(var l_bodyHandler in m_ragdollBodyHandlers)
+ {
+ if(p_state)
+ {
+ if(l_bodyHandler.Attach(l_hand, l_pos))
+ break;
+ }
+ else
+ l_bodyHandler.Detach(l_hand);
+ }
+ }
+ }
+ }
+
// VRIK updates
void OnIKPostSolverUpdate()
{
@@ -505,6 +540,7 @@ namespace ml_prm
l_handler.SetDrag(l_drag);
}
}
+
void OnAngularDragChanged(float p_value)
{
if(m_avatarReady)
@@ -513,6 +549,7 @@ namespace ml_prm
l_handler.SetAngularDrag(p_value);
}
}
+
void OnGravityChanged(bool p_state)
{
if(m_avatarReady)
@@ -528,6 +565,7 @@ namespace ml_prm
}
}
}
+
void OnPhysicsMaterialChanged(bool p_state)
{
if(m_physicsMaterial != null)
@@ -541,6 +579,7 @@ namespace ml_prm
m_physicsMaterial.bounceCombine = (l_bounciness ? PhysicMaterialCombine.Maximum : PhysicMaterialCombine.Average);
}
}
+
void OnBuoyancyChanged(bool p_state)
{
if(m_avatarReady)
@@ -556,11 +595,21 @@ namespace ml_prm
}
}
}
+
void OnFallDamageChanged(bool p_state)
{
m_inAir = false;
}
+ void OnGestureGrabChanged(bool p_state)
+ {
+ if(m_avatarReady && m_enabled & !p_state)
+ {
+ foreach(var l_hanlder in m_ragdollBodyHandlers)
+ l_hanlder.Detach();
+ }
+ }
+
// Arbitrary
public void SwitchRagdoll()
{
@@ -644,8 +693,9 @@ namespace ml_prm
foreach(RagdollBodypartHandler l_handler in m_ragdollBodyHandlers)
{
- l_handler.SetAsKinematic(true);
+ l_handler.Detach();
l_handler.ClearFluidVolumes();
+ l_handler.SetAsKinematic(true);
}
m_lastPosition = PlayerSetup.Instance.transform.position;
diff --git a/ml_prm/RemoteGestureHandler.cs b/ml_prm/RemoteGestureHandler.cs
new file mode 100644
index 0000000..a946d8f
--- /dev/null
+++ b/ml_prm/RemoteGestureHandler.cs
@@ -0,0 +1,50 @@
+using ABI_RC.Core.Networking.IO.Social;
+using ABI_RC.Core.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+
+namespace ml_prm
+{
+ class RemoteGestureHandler : MonoBehaviour
+ {
+ internal class GestureEvent
+ {
+ event Action m_action;
+ public void AddHandler(Action p_listener) => m_action += p_listener;
+ public void RemoveHandler(Action p_listener) => m_action -= p_listener;
+ public void Invoke(T1 p_objA, T2 p_objB, T3 p_objC) => m_action?.Invoke(p_objA, p_objB, p_objC);
+ }
+
+ public static readonly GestureEvent OnGestureState = new GestureEvent();
+
+ PuppetMaster m_puppetMaster = null;
+ bool m_stateLeft = false;
+ bool m_stateRight = false;
+
+ void Start()
+ {
+ m_puppetMaster = this.GetComponent();
+ }
+
+ void Update()
+ {
+ bool l_state = Mathf.Approximately(m_puppetMaster.PlayerAvatarMovementDataInput.AnimatorGestureLeft, 1f);
+ if(m_stateLeft != l_state)
+ {
+ m_stateLeft = l_state;
+ if(!Settings.FriendsGrab || Friends.FriendsWith(m_puppetMaster.CVRPlayerEntity.PlayerDescriptor.ownerId))
+ OnGestureState.Invoke(m_puppetMaster, true, m_stateLeft);
+ }
+
+ l_state = Mathf.Approximately(m_puppetMaster.PlayerAvatarMovementDataInput.AnimatorGestureRight, 1f);
+ if(m_stateRight != l_state)
+ {
+ m_stateRight = l_state;
+ if(!Settings.FriendsGrab || Friends.FriendsWith(m_puppetMaster.CVRPlayerEntity.PlayerDescriptor.ownerId))
+ OnGestureState.Invoke(m_puppetMaster, false, m_stateRight);
+ }
+ }
+ }
+}
diff --git a/ml_prm/Settings.cs b/ml_prm/Settings.cs
index 571685e..e16d152 100644
--- a/ml_prm/Settings.cs
+++ b/ml_prm/Settings.cs
@@ -33,7 +33,10 @@ namespace ml_prm
JumpRecover,
Buoyancy,
FallDamage,
- FallLimit
+ FallLimit,
+ GestureGrab,
+ FriendsGrab,
+ GrabDistance
}
public static bool Hotkey { get; private set; } = true;
@@ -54,6 +57,9 @@ namespace ml_prm
public static bool Buoyancy { get; private set; } = true;
public static bool FallDamage { get; private set; } = true;
public static float FallLimit { get; private set; } = 9.899494f;
+ public static bool GestureGrab { get; private set; } = false;
+ public static bool FriendsGrab { get; private set; } = true;
+ public static float GrabDistance { get; private set; } = 0.1f;
public static readonly SettingEvent OnHotkeyChanged = new SettingEvent();
public static readonly SettingEvent OnHotkeyKeyChanged = new SettingEvent();
@@ -73,6 +79,9 @@ namespace ml_prm
public static readonly SettingEvent OnBuoyancyChanged = new SettingEvent();
public static readonly SettingEvent OnFallDamageChanged = new SettingEvent();
public static readonly SettingEvent OnFallLimitChanged = new SettingEvent();
+ public static readonly SettingEvent OnGestureGrabChanged = new SettingEvent();
+ public static readonly SettingEvent OnFriendsGrabChanged = new SettingEvent();
+ public static readonly SettingEvent OnGrabDistanceChanged = new SettingEvent();
static MelonLoader.MelonPreferences_Category ms_category = null;
static List ms_entries = null;
@@ -101,6 +110,9 @@ namespace ml_prm
ms_category.CreateEntry(ModSetting.Buoyancy.ToString(), Buoyancy, 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.GestureGrab.ToString(), GestureGrab, null, null, true),
+ ms_category.CreateEntry(ModSetting.FriendsGrab.ToString(), FriendsGrab, null, null, true),
+ ms_category.CreateEntry(ModSetting.GrabDistance.ToString(), GrabDistance, null, null, true),
};
ms_entries[(int)ModSetting.HotkeyKey].OnEntryValueChangedUntyped.Subscribe(OnMelonSettingSave_HotkeyKey);
@@ -123,6 +135,9 @@ namespace ml_prm
Buoyancy = (bool)ms_entries[(int)ModSetting.Buoyancy].BoxedValue;
FallDamage = (bool)ms_entries[(int)ModSetting.FallDamage].BoxedValue;
FallLimit = Mathf.Clamp((float)ms_entries[(int)ModSetting.FallLimit].BoxedValue, 4.5f, 44.5f);
+ GestureGrab = (bool)ms_entries[(int)ModSetting.GestureGrab].BoxedValue;
+ FriendsGrab = (bool)ms_entries[(int)ModSetting.FriendsGrab].BoxedValue;
+ GrabDistance = Mathf.Clamp01((float)ms_entries[(int)ModSetting.GrabDistance].BoxedValue);
}
static void OnMelonSettingSave_HotkeyKey(object p_oldValue, object p_newValue)
@@ -232,6 +247,20 @@ namespace ml_prm
}
break;
+ case ModSetting.GestureGrab:
+ {
+ GestureGrab = (bool)p_value;
+ OnGestureGrabChanged.Invoke(GestureGrab);
+ }
+ break;
+
+ case ModSetting.FriendsGrab:
+ {
+ FriendsGrab = (bool)p_value;
+ OnFriendsGrabChanged.Invoke(FriendsGrab);
+ }
+ break;
+
// Floats
case ModSetting.VelocityMultiplier:
{
@@ -267,6 +296,13 @@ namespace ml_prm
OnFallLimitChanged.Invoke(FallLimit);
}
break;
+
+ case ModSetting.GrabDistance:
+ {
+ GrabDistance = (float)p_value;
+ OnGrabDistanceChanged.Invoke(GrabDistance);
+ }
+ break;
}
if(ms_entries != null)
diff --git a/ml_prm/ml_prm.csproj b/ml_prm/ml_prm.csproj
index 3b7a96c..95f8b33 100644
--- a/ml_prm/ml_prm.csproj
+++ b/ml_prm/ml_prm.csproj
@@ -4,7 +4,7 @@
netstandard2.1
x64
PlayerRagdollMod
- 1.1.8
+ 1.1.9
SDraw
None
PlayerRagdollMod