diff --git a/README.md b/README.md index 07df240..0152fa5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Merged set of MelonLoader mods for ChilloutVR. | Avatar Motion Tweaker | ml_amt | 1.2.5 | Yes, update review | Working | | Desktop Head Tracking | ml_dht | 1.1.2 | Yes | Working | | Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working | -| Extended Game Notifications | ml_egn | 1.0.1 | Yes | Working +| Extended Game Notifications | ml_egn | 1.0.2 | Yes, update review | Working | Four Point Tracking | ml_fpt | 1.0.9 | Retired | Deprecated | In-game feature since 2022r170 update | Leap Motion Extension | ml_lme | 1.3.3 | Yes | Working | | Pickup Arm Movement | ml_pam | 1.0.2 | Yes| Working | diff --git a/ml_egn/Main.cs b/ml_egn/Main.cs index c77bf82..90d54a2 100644 --- a/ml_egn/Main.cs +++ b/ml_egn/Main.cs @@ -1,5 +1,4 @@ using ABI_RC.Core.EventSystem; -using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.IO; using ABI_RC.Core.Networking; using ABI_RC.Core.Util; @@ -57,6 +56,7 @@ namespace ml_egn Utils.ShowMenuNotification("Avatar changed", 1f); else Utils.ShowHUDNotification("(Synced) Client", "Avatar changed"); + } catch(System.Exception e) { @@ -68,19 +68,37 @@ namespace ml_egn { try { - if(Utils.IsConnected()) + if(Utils.ArePropsEnabled()) { - if(Utils.IsMenuOpened()) - Utils.ShowMenuNotification("Prop spawned", 1f); + if(Utils.ArePropsAllowed()) + { + if(Utils.IsConnected()) + { + if(Utils.IsMenuOpened()) + Utils.ShowMenuNotification("Prop spawned", 1f); + else + Utils.ShowHUDNotification("(Synced) Client", "Prop spawned"); + } + else + { + if(Utils.IsMenuOpened()) + Utils.ShowMenuAlert("Prop Error", "Not connected to live instance"); + else + Utils.ShowHUDNotification("(Local) Client", "Unable to spawn prop", "Not connected to live instance"); + } + } else - Utils.ShowHUDNotification("(Synced) Client", "Prop spawned"); + { + if(Utils.IsMenuOpened()) + Utils.ShowMenuAlert("Prop Error", "Props are not allowed in this world"); + } } else { if(Utils.IsMenuOpened()) - Utils.ShowMenuAlert("Prop Error", "Not connected to live instance"); + Utils.ShowMenuAlert("Prop Error", "Props are disabled in game settings"); else - Utils.ShowHUDNotification("(Local) Client", "Unable to spawn prop", "Not connected to live instance"); + Utils.ShowHUDNotification("(Local) Client", "Unable to spawn prop", "Props are disabled in game settings"); } } catch(System.Exception e) diff --git a/ml_egn/Properties/AssemblyInfo.cs b/ml_egn/Properties/AssemblyInfo.cs index f1610b5..2bd2627 100644 --- a/ml_egn/Properties/AssemblyInfo.cs +++ b/ml_egn/Properties/AssemblyInfo.cs @@ -1,10 +1,10 @@ using System.Reflection; [assembly: AssemblyTitle("ExtendedGameNotifications")] -[assembly: AssemblyVersion("1.0.1")] -[assembly: AssemblyFileVersion("1.0.1")] +[assembly: AssemblyVersion("1.0.2")] +[assembly: AssemblyFileVersion("1.0.2")] -[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] +[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.2", "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)] \ No newline at end of file diff --git a/ml_egn/Utils.cs b/ml_egn/Utils.cs index f4935b9..319e7ac 100644 --- a/ml_egn/Utils.cs +++ b/ml_egn/Utils.cs @@ -1,5 +1,6 @@ using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.Networking; +using ABI_RC.Core.Savior; using ABI_RC.Core.UI; using DarkRift; @@ -42,5 +43,8 @@ namespace ml_egn l_result = (NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected); return l_result; } + + public static bool ArePropsAllowed() => ((MetaPort.Instance != null) && MetaPort.Instance.worldAllowProps); + public static bool ArePropsEnabled() => ((MetaPort.Instance != null) && MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled")); } } diff --git a/ml_prm/Main.cs b/ml_prm/Main.cs index 8a53961..a8abb8f 100644 --- a/ml_prm/Main.cs +++ b/ml_prm/Main.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using System.Reflection; using ABI_RC.Core.Util.AssetFiltering; +using ABI.CCK.Components; +using System.Linq; namespace ml_prm { @@ -47,6 +49,11 @@ namespace ml_prm 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 + ); // Whitelist the toggle script (typeof(SharedFilter).GetField("_localComponentWhitelist", BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) as HashSet)?.Add(typeof(RagdollToggle)); @@ -140,5 +147,22 @@ namespace ml_prm } } + 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); + } + } } } diff --git a/ml_prm/README.md b/ml_prm/README.md index b233754..fb8142a 100644 --- a/ml_prm/README.md +++ b/ml_prm/README.md @@ -16,6 +16,7 @@ Optional mod's settings with [BTKUILib](https://github.com/BTK-Development/BTKUI * **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 CVRPointer colliders of `ragdoll` type (avatars, props and world included); `true` by default. +* **Combat reaction:** enables ragdoll state upon death in worlds with combat system; `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. diff --git a/ml_prm/RagdollController.cs b/ml_prm/RagdollController.cs index 4b91dca..c66e8bd 100644 --- a/ml_prm/RagdollController.cs +++ b/ml_prm/RagdollController.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.InteractionSystem; +using ABI.CCK.Components; +using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.Player; using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.MovementSystem; @@ -33,6 +34,8 @@ namespace ml_prm RagdollToggle m_avatarRagdollToggle = null; RagdollTrigger m_customTrigger = null; + bool m_reachedGround = true; + internal RagdollController() { if(Instance == null) @@ -84,15 +87,18 @@ namespace ml_prm m_velocity = (m_velocity + (l_pos - m_lastPosition) / Time.deltaTime) * 0.5f; m_lastPosition = l_pos; + if(m_avatarReady && !m_reachedGround && MovementSystem.Instance.IsGrounded()) + m_reachedGround = true; + + if(m_enabled && m_avatarReady && BodySystem.isCalibratedAsFullBody) + BodySystem.TrackingPositionWeight = 0f; + if(Settings.Hotkey && Input.GetKeyDown(KeyCode.R) && !ViewManager.Instance.isGameMenuOpen()) SwitchRagdoll(); if((m_avatarRagdollToggle != null) && m_avatarRagdollToggle.isActiveAndEnabled && m_avatarRagdollToggle.shouldOverride && (m_enabled != m_avatarRagdollToggle.isOn)) SwitchRagdoll(); - if(m_enabled && m_avatarReady && BodySystem.isCalibratedAsFullBody) - BodySystem.TrackingPositionWeight = 0f; - if((m_customTrigger != null) && m_customTrigger.GetStateWithReset() && !m_enabled && m_avatarReady && Settings.PointersReaction) SwitchRagdoll(); } @@ -127,6 +133,7 @@ namespace ml_prm m_colliders.Clear(); m_puppetReferences = new BipedRagdollReferences(); m_boneLinks.Clear(); + m_reachedGround = true; } internal void OnAvatarSetup() @@ -253,6 +260,12 @@ namespace ml_prm } } + internal void OnCombatDown() + { + if(!m_enabled && m_avatarReady && Settings.CombatReaction) + SwitchRagdoll(); + } + // IK updates void OnIKPreUpdate() { @@ -305,60 +318,88 @@ namespace ml_prm // Arbitrary public void SwitchRagdoll() { - if(m_avatarReady && (MovementSystem.Instance.lastSeat == null) && !BodySystem.isCalibrating) + if(m_avatarReady) { - m_enabled = !m_enabled; - - MovementSystem.Instance.SetImmobilized(m_enabled); - - if(m_enabled) + if(!m_enabled) { - PlayerSetup.Instance.animatorManager.SetAnimatorParameterTrigger("CancelEmote"); - if(BodySystem.isCalibratedAsFullBody) - BodySystem.TrackingPositionWeight = 0f; - - // Copy before set to non-kinematic to reduce stacked forces - foreach(var l_link in m_boneLinks) - l_link.Item2.CopyGlobal(l_link.Item1); - - foreach(Rigidbody l_body in m_rigidBodies) - l_body.isKinematic = false; - - Vector3 l_velocity = m_velocity * Mathf.Clamp(Settings.VelocityMultiplier, 1f, (Utils.IsWorldSafe() ? Utils.GetWorldFlyMultiplier() : 1f)); - foreach(Rigidbody l_body in m_rigidBodies) + if(IsSafeToRagdoll() && m_reachedGround) { - l_body.velocity = l_velocity; - l_body.angularVelocity = Vector3.zero; + // Eject player from seat + if(MovementSystem.Instance.lastSeat != null) + { + Vector3 l_pos = PlayerSetup.Instance.transform.position; + Quaternion l_rot = PlayerSetup.Instance.transform.rotation; + MovementSystem.Instance.lastSeat.ExitSeat(); + PlayerSetup.Instance.transform.position = l_pos; + PlayerSetup.Instance.transform.rotation = Quaternion.Euler(0f, l_rot.eulerAngles.y, 0f); + } + + MovementSystem.Instance.SetImmobilized(true); + PlayerSetup.Instance.animatorManager.SetAnimatorParameterTrigger("CancelEmote"); + if(BodySystem.isCalibratedAsFullBody) + BodySystem.TrackingPositionWeight = 0f; + + if(!Utils.IsWorldSafe()) + m_reachedGround = false; // Force player to unragdoll and reach ground first + + // Copy before set to non-kinematic to reduce stacked forces + foreach(var l_link in m_boneLinks) + l_link.Item2.CopyGlobal(l_link.Item1); + + foreach(Rigidbody l_body in m_rigidBodies) + l_body.isKinematic = false; + + Vector3 l_velocity = m_velocity * Mathf.Clamp(Settings.VelocityMultiplier, 1f, (Utils.IsWorldSafe() ? Utils.GetWorldFlyMultiplier() : 1f)); + + 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_enabled = true; } } else { - if(BodySystem.isCalibratedAsFullBody) - BodySystem.TrackingPositionWeight = 1f; - - foreach(Rigidbody l_body in m_rigidBodies) - l_body.isKinematic = true; - - if(m_puppetReferences.hips != null) + if(IsSafeToUnragdoll()) { - Vector3 l_hipsPos = m_puppetReferences.hips.position; + MovementSystem.Instance.SetImmobilized(false); + if(BodySystem.isCalibratedAsFullBody) + BodySystem.TrackingPositionWeight = 1f; - if(!Settings.RestorePosition) + foreach(Rigidbody l_body in m_rigidBodies) + l_body.isKinematic = true; + + if(m_puppetReferences.hips != null) { - if(Utils.IsInVR()) + Vector3 l_hipsPos = m_puppetReferences.hips.position; + + if(!Settings.RestorePosition) { - Vector3 l_diff = l_hipsPos - PlayerSetup.Instance._avatar.transform.position; - Vector3 l_playerPos = PlayerSetup.Instance.transform.position; - PlayerSetup.Instance.transform.position = l_playerPos + l_diff; + if(Utils.IsInVR()) + { + Vector3 l_diff = l_hipsPos - PlayerSetup.Instance._avatar.transform.position; + Vector3 l_playerPos = PlayerSetup.Instance.transform.position; + PlayerSetup.Instance.transform.position = l_playerPos + l_diff; + } + else + PlayerSetup.Instance.transform.position = l_hipsPos; } - else - PlayerSetup.Instance.transform.position = l_hipsPos; } + + foreach(Collider l_collider in m_colliders) + l_collider.enabled = false; + + m_lastPosition = PlayerSetup.Instance.transform.position; + m_velocity = Vector3.zero; + + m_enabled = false; } } - - foreach(Collider l_collider in m_colliders) - l_collider.enabled = m_enabled; } } @@ -371,5 +412,20 @@ namespace ml_prm 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; + } } } diff --git a/ml_prm/Settings.cs b/ml_prm/Settings.cs index bd44fd3..79e2187 100644 --- a/ml_prm/Settings.cs +++ b/ml_prm/Settings.cs @@ -14,7 +14,8 @@ namespace ml_prm MovementDrag, AngularDrag, Gravity, - PointersReaction + PointersReaction, + CombatReaction } enum UiElementIndex @@ -23,6 +24,7 @@ namespace ml_prm RestorePosition, Gravity, PointersReaction, + CombatReaction, VelocityMultiplier, MovementDrag, AngularDrag @@ -35,6 +37,7 @@ namespace ml_prm 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 CombatReaction { get; private set; } = true; static public event Action SwitchChange; static public event Action HotkeyChange; @@ -44,6 +47,7 @@ namespace ml_prm static public event Action AngularDragChange; static public event Action GravityChange; static public event Action PointersReactionChange; + static public event Action CombatReactionChange; static MelonLoader.MelonPreferences_Category ms_category = null; static List ms_entries = null; @@ -61,7 +65,8 @@ namespace ml_prm ms_category.CreateEntry(ModSetting.MovementDrag.ToString(), MovementDrag), ms_category.CreateEntry(ModSetting.AngularDrag.ToString(), AngularDrag), ms_category.CreateEntry(ModSetting.Gravity.ToString(), Gravity), - ms_category.CreateEntry(ModSetting.PointersReaction.ToString(), PointersReaction) + ms_category.CreateEntry(ModSetting.PointersReaction.ToString(), PointersReaction), + ms_category.CreateEntry(ModSetting.CombatReaction.ToString(), CombatReaction) }; Hotkey = (bool)ms_entries[(int)ModSetting.Hotkey].BoxedValue; @@ -71,6 +76,7 @@ namespace ml_prm AngularDrag = UnityEngine.Mathf.Clamp((float)ms_entries[(int)ModSetting.MovementDrag].BoxedValue, 0f, 50f); Gravity = (bool)ms_entries[(int)ModSetting.Gravity].BoxedValue; PointersReaction = (bool)ms_entries[(int)ModSetting.PointersReaction].BoxedValue; + CombatReaction = (bool)ms_entries[(int)ModSetting.CombatReaction].BoxedValue; if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "BTKUILib") != null) { @@ -122,6 +128,14 @@ namespace ml_prm PointersReactionChange?.Invoke(state); }; + ms_uiElements.Add(l_categoryMod.AddToggle("Combat reaction", "Ragdoll upon combat system death", CombatReaction)); + (ms_uiElements[(int)UiElementIndex.CombatReaction] as BTKUILib.UIObjects.Components.ToggleButton).OnValueUpdated += (state) => + { + CombatReaction = state; + ms_entries[(int)ModSetting.CombatReaction].BoxedValue = state; + CombatReactionChange?.Invoke(state); + }; + ms_uiElements.Add(l_page.AddSlider("Velocity multiplier", "Velocity multiplier upon entering ragdoll state", VelocityMultiplier, 1f, 50f)); (ms_uiElements[(int)UiElementIndex.VelocityMultiplier] as BTKUILib.UIObjects.Components.SliderFloat).OnValueUpdated += (value) => { @@ -168,6 +182,11 @@ namespace ml_prm (ms_uiElements[(int)UiElementIndex.PointersReaction] as BTKUILib.UIObjects.Components.ToggleButton).ToggleValue = true; PointersReactionChange?.Invoke(true); + CombatReaction = true; + ms_entries[(int)ModSetting.CombatReaction].BoxedValue = true; + (ms_uiElements[(int)UiElementIndex.CombatReaction] as BTKUILib.UIObjects.Components.ToggleButton).ToggleValue = true; + CombatReactionChange?.Invoke(true); + VelocityMultiplier = 2f; ms_entries[(int)ModSetting.VelocityMultiplier].BoxedValue = 2f; (ms_uiElements[(int)UiElementIndex.VelocityMultiplier] as BTKUILib.UIObjects.Components.SliderFloat).SetSliderValue(2f); diff --git a/ml_prm/Utils.cs b/ml_prm/Utils.cs index 36a8c74..c6eb321 100644 --- a/ml_prm/Utils.cs +++ b/ml_prm/Utils.cs @@ -1,11 +1,16 @@ 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 { - public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded); + static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", 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 GetWorldFlyMultiplier() { @@ -15,6 +20,8 @@ namespace ml_prm return l_result; } + public static bool IsGrounded(this MovementSystem p_instance) => (bool)ms_grounded.GetValue(p_instance); + public static void CopyGlobal(this Transform p_source, Transform p_target) { p_target.position = p_source.position;