diff --git a/README.md b/README.md index 1233447..9cbe8e5 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ Merged set of MelonLoader mods for ChilloutVR. |[Avatar Motion Tweaker](/ml_amt/README.md)|1.5.1 [:arrow_down:](../../releases/latest/download/AvatarMotionTweaker.dll)| |[Avatar Synced Look](/ml_asl/README.md)|1.1.1 [:arrow_down:](../../releases/latest/download/AvatarSyncedLook.dll)| |[Better Fingers Tracking](/ml_bft/README.md)|1.1.2 [:arrow_down:](../../releases/latest/download/BetterFingersTracking.dll)| -|[Desktop Head Tracking](/ml_dht/README.md)|1.3.1 [:arrow_down:](../../releases/latest/download/DesktopHeadTracking.dll)| +|[Desktop Head Tracking](/ml_dht/README.md)|1.3.2 [:arrow_down:](../../releases/latest/download/DesktopHeadTracking.dll)| |[Leap Motion Extension](/ml_lme/README.md)| 1.6.1 [:arrow_down:](../../releases/latest/download/LeapMotionExtension.dll)| |[Pickup Arm Movement](/ml_pam/README.md)|1.2.2 [:arrow_down:](../../releases/latest/download/PickupArmMovement.dll)| |[Players Instance Notifier](/ml_pin/README.md)|1.1.1 [:arrow_down:](../../releases/latest/download/PlayersInstanceNotifier.dll)| |[Player Movement Copycat](/ml_pmc/README.md)|1.1.1 [:arrow_down:](../../releases/latest/download/PlayerMovementCopycat.dll)| +|[Player Pick Up](/ml_ppu/README.md)|1.0.0 [:arrow_down:](../../releases/latest/download/PlayerPickUp.dll)| |[Player Ragdoll Mod](/ml_prm/README.md)|1.2.3 [:arrow_down:](../../releases/latest/download/PlayerRagdollMod.dll)| |[Video Player Cookies](/ml_vpc/README.md)|1.0.1 [:arrow_down:](../../releases/latest/download/VideoPlayerCookies.dll)| |[Vive Extended Input](/ml_vei/README.md)|1.1.1 [:arrow_down:](../../releases/latest/download/ViveExtendedInput.dll)| diff --git a/ml_dht/HeadTracked.cs b/ml_dht/HeadTracked.cs index 0f6cf0f..03a4bbb 100644 --- a/ml_dht/HeadTracked.cs +++ b/ml_dht/HeadTracked.cs @@ -2,6 +2,7 @@ using ABI_RC.Core.Player; using ABI_RC.Core.Player.EyeMovement; using ABI_RC.Systems.FaceTracking; +using ABI_RC.Systems.IK; using ABI_RC.Systems.VRModeSwitch; using RootMotion.FinalIK; using System; @@ -54,7 +55,7 @@ namespace ml_dht ms_instance = this; DontDestroyOnLoad(this); - + m_dataParser = new DataParser(); } @@ -138,19 +139,23 @@ namespace ml_dht // Game events internal void OnAvatarSetup() { - Utils.SetAvatarTPose(); - m_camera = PlayerSetup.Instance.GetActiveCamera().transform; m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent(); - m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head); - m_lookIK = PlayerSetup.Instance._avatar.GetComponent(); - if(m_headBone != null) - m_bindRotation = Quaternion.Inverse(m_avatarDescriptor.transform.rotation) * m_headBone.rotation; + if(PlayerSetup.Instance._animator.isHuman) + { + IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.TPose); + PlayerSetup.Instance._avatar.transform.localPosition = Vector3.zero; + PlayerSetup.Instance._avatar.transform.localRotation = Quaternion.identity; - if(m_lookIK != null) - m_lookIK.onPostSolverUpdate.AddListener(this.OnLookIKPostUpdate); + m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head); + if(m_headBone != null) + m_bindRotation = Quaternion.Inverse(m_avatarDescriptor.transform.rotation) * m_headBone.rotation; + m_lookIK = PlayerSetup.Instance._avatar.GetComponent(); + if(m_lookIK != null) + m_lookIK.onPostSolverUpdate.AddListener(this.OnLookIKPostUpdate); + } } void OnAvatarClear() { @@ -163,6 +168,7 @@ namespace ml_dht void OnAvatarReuse() { m_camera = PlayerSetup.Instance.GetActiveCamera().transform; + m_lookIK = PlayerSetup.Instance._avatar.GetComponent(); if(m_lookIK != null) m_lookIK.onPostSolverUpdate.AddListener(this.OnLookIKPostUpdate); @@ -190,7 +196,7 @@ namespace ml_dht void UpdateFaceTracking(CVRFaceTracking p_component, GameEvents.EventResult p_result) { - if(this.enabled && Settings.Enabled && Settings.FaceTracking && p_component.isLocal && p_component.UseFacialTracking ) + if(this.enabled && Settings.Enabled && Settings.FaceTracking && p_component.isLocal && p_component.UseFacialTracking) { if(!m_lipDataSent) { diff --git a/ml_dht/Properties/AssemblyInfo.cs b/ml_dht/Properties/AssemblyInfo.cs index a990bd0..fb8f7b9 100644 --- a/ml_dht/Properties/AssemblyInfo.cs +++ b/ml_dht/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.3.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] +[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.3.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_dht/Utils.cs b/ml_dht/Utils.cs index ec35c6c..26853ee 100644 --- a/ml_dht/Utils.cs +++ b/ml_dht/Utils.cs @@ -19,15 +19,5 @@ namespace ml_dht public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => (ms_view?.GetValue(p_instance) as cohtml.Net.View)?.ExecuteScript(p_script); public static void UpdateShapesLocal_Private(this CVRFaceTracking p_instance) => ms_updateShapesLocal?.Invoke(p_instance, ms_emptyArray); - - public static void SetAvatarTPose() - { - if(PlayerSetup.Instance._animator.isHuman) - { - IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.TPose); - PlayerSetup.Instance._avatar.transform.localPosition = Vector3.zero; - PlayerSetup.Instance._avatar.transform.localRotation = Quaternion.identity; - } - } } } diff --git a/ml_dht/ml_dht.csproj b/ml_dht/ml_dht.csproj index e336875..a2538d1 100644 --- a/ml_dht/ml_dht.csproj +++ b/ml_dht/ml_dht.csproj @@ -6,7 +6,7 @@ SDraw SDraw DesktopHeadTracking - 1.3.1 + 1.3.2 x64 DesktopHeadTracking diff --git a/ml_mods_cvr.sln b/ml_mods_cvr.sln index fa53b8e..782d4e6 100644 --- a/ml_mods_cvr.sln +++ b/ml_mods_cvr.sln @@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ml_bft", "ml_bft\ml_bft.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ml_vpc", "ml_vpc\ml_vpc.csproj", "{7CF37B93-9341-422D-845C-9AB96DB4D0A1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ml_ppu", "ml_ppu\ml_ppu.csproj", "{F16DF16B-D127-4A2A-81FF-2FD80F320E64}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -71,6 +73,10 @@ Global {7CF37B93-9341-422D-845C-9AB96DB4D0A1}.Debug|x64.Build.0 = Debug|x64 {7CF37B93-9341-422D-845C-9AB96DB4D0A1}.Release|x64.ActiveCfg = Release|x64 {7CF37B93-9341-422D-845C-9AB96DB4D0A1}.Release|x64.Build.0 = Release|x64 + {F16DF16B-D127-4A2A-81FF-2FD80F320E64}.Debug|x64.ActiveCfg = Debug|x64 + {F16DF16B-D127-4A2A-81FF-2FD80F320E64}.Debug|x64.Build.0 = Debug|x64 + {F16DF16B-D127-4A2A-81FF-2FD80F320E64}.Release|x64.ActiveCfg = Release|x64 + {F16DF16B-D127-4A2A-81FF-2FD80F320E64}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ml_ppu/GameEvents.cs b/ml_ppu/GameEvents.cs new file mode 100644 index 0000000..3ab7057 --- /dev/null +++ b/ml_ppu/GameEvents.cs @@ -0,0 +1,132 @@ +using ABI_RC.Core; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using System; +using System.Reflection; + +namespace ml_ppu +{ + static class GameEvents + { + internal class GameEvent + { + event Action m_action; + public void AddListener(Action p_listener) => m_action += p_listener; + public void RemoveListener(Action p_listener) => m_action -= p_listener; + public void Invoke() => m_action?.Invoke(); + } + internal class GameEvent + { + event Action m_action; + public void AddListener(Action p_listener) => m_action += p_listener; + public void RemoveListener(Action p_listener) => m_action -= p_listener; + public void Invoke(T1 p_obj) => m_action?.Invoke(p_obj); + } + + public static readonly GameEvent OnAvatarSetup = new GameEvent(); + public static readonly GameEvent OnAvatarClear = new GameEvent(); + public static readonly GameEvent OnIKScaling = new GameEvent(); + public static readonly GameEvent OnWorldPreSpawn = new GameEvent(); + public static readonly GameEvent OnSeatPreSit = new GameEvent(); + + internal static void Init(HarmonyLib.Harmony p_instance) + { + try + { + p_instance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar), BindingFlags.Instance | BindingFlags.Public), + null, + new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.Static |BindingFlags.NonPublic)) + ); + + p_instance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar), BindingFlags.Instance | BindingFlags.Public), + null, + new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) + ); + + p_instance.Patch( + typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.Instance | BindingFlags.NonPublic), + null, + new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) + ); + + p_instance.Patch( + typeof(RootLogic).GetMethod(nameof(RootLogic.SpawnOnWorldInstance), BindingFlags.Instance | BindingFlags.Public), + new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnWorldSpawn_Prefix), BindingFlags.Static | BindingFlags.NonPublic)), + null + ); + + p_instance.Patch( + typeof(CVRSeat).GetMethod(nameof(CVRSeat.SitDown), BindingFlags.Instance | BindingFlags.Public), + new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnCVRSeatSitDown_Prefix), BindingFlags.Static | BindingFlags.NonPublic)), + null + ); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnAvatarClear_Postfix() + { + try + { + OnAvatarClear.Invoke(); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnSetupAvatar_Postfix() + { + try + { + OnAvatarSetup.Invoke(); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnSetupIKScaling_Postfix(ref UnityEngine.Vector3 ___scaleDifference) + { + try + { + OnIKScaling.Invoke(1f + ___scaleDifference.y); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnWorldSpawn_Prefix() + { + try + { + OnWorldPreSpawn.Invoke(); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnCVRSeatSitDown_Prefix(ref CVRSeat __instance) + { + try + { + OnSeatPreSit.Invoke(__instance); + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + } +} diff --git a/ml_ppu/GrabDetector.cs b/ml_ppu/GrabDetector.cs new file mode 100644 index 0000000..2af8e3c --- /dev/null +++ b/ml_ppu/GrabDetector.cs @@ -0,0 +1,32 @@ +using ABI.CCK.Components; +using ABI_RC.Core.Networking.IO.Social; +using ABI_RC.Core.Player; +using UnityEngine; + +namespace ml_ppu +{ + class GrabDetector : MonoBehaviour + { + void OnTriggerEnter(Collider p_collider) + { + if(!Settings.Enabled) + return; + + CVRPointer l_pointer = p_collider.GetComponent(); + if((l_pointer != null) && (l_pointer.type == "grab") && RestrictionsCheck(p_collider.transform.root)) + PickUpManager.Instance?.OnGrabDetected(p_collider, l_pointer); + } + + static bool RestrictionsCheck(Transform p_transform) + { + if(p_transform == PlayerSetup.Instance.transform) + return false; + + PlayerDescriptor l_playerDescriptor = p_transform.GetComponent(); + if(l_playerDescriptor != null) + return (!Settings.FriendsOnly || Friends.FriendsWith(l_playerDescriptor.ownerId)); + + return false; + } + } +} diff --git a/ml_ppu/Main.cs b/ml_ppu/Main.cs new file mode 100644 index 0000000..1a98d3d --- /dev/null +++ b/ml_ppu/Main.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using UnityEngine; + +namespace ml_ppu +{ + public class PlayerPickUp : MelonLoader.MelonMod + { + PickUpManager m_manager = null; + + public override void OnInitializeMelon() + { + Settings.Init(); + GameEvents.Init(HarmonyInstance); + ModUi.Init(); + ModSupport.Init(); + + MelonLoader.MelonCoroutines.Start(WaitForRootLogic()); + } + + IEnumerator WaitForRootLogic() + { + while(ABI_RC.Core.RootLogic.Instance == null) + yield return null; + + m_manager = new GameObject("[PlayerPickUp]").AddComponent(); + } + + public override void OnDeinitializeMelon() + { + if(m_manager != null) + { + UnityEngine.Object.Destroy(m_manager.gameObject); + m_manager = null; + } + } + } +} diff --git a/ml_ppu/ModSupport.cs b/ml_ppu/ModSupport.cs new file mode 100644 index 0000000..c815210 --- /dev/null +++ b/ml_ppu/ModSupport.cs @@ -0,0 +1,24 @@ +using System.Linq; + +namespace ml_ppu +{ + static class ModSupport + { + static bool ms_ragdollPresent = false; + + internal static void Init() + { + ms_ragdollPresent = (MelonLoader.MelonBase.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerRagdollMod") != null); + } + + public static bool IsRagdolled() => (ms_ragdollPresent && IsRagdollInternal()); + static bool IsRagdollInternal() => ml_prm.RagdollController.Instance.IsRagdolled(); + + public static void TryToUnragdoll() + { + if(ms_ragdollPresent) + TryToUngradollInternal(); + } + static void TryToUngradollInternal() => ml_prm.RagdollController.Instance.Unragdoll(); + } +} diff --git a/ml_ppu/ModUi.cs b/ml_ppu/ModUi.cs new file mode 100644 index 0000000..600b2e7 --- /dev/null +++ b/ml_ppu/ModUi.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; +using System.Reflection; +using BTKUILib.UIObjects; +using BTKUILib.UIObjects.Components; + +namespace ml_ppu +{ + static class ModUi + { + enum UiIndex + { + Enabled = 0, + FriendsOnly, + VelocityMultiplier + } + readonly static string ms_namespace = typeof(ModUi).Namespace; + + static Page ms_page = null; + static Category ms_category = null; + + static ToggleButton ms_enabledToggle = null; + static ToggleButton ms_friendsOnlyToggle = null; + static SliderFloat ms_velocityMultiplierSlider = null; + + internal static void Init() + { + BTKUILib.QuickMenuAPI.PrepareIcon("PlayerPickUp", "PPU-Person", GetIconStream("person.png")); + + ms_page = new Page("PlayerPickUp", "MainPage", true, "PPU-Person"); + ms_page.MenuTitle = "Player Pick Up"; + ms_page.MenuSubtitle = "Let people pick you up and carry you around"; + + ms_category = ms_page.AddCategory("Settings"); + + ms_enabledToggle = ms_category.AddToggle("Enabled", "Set mod's activity as enabled or disabled", Settings.Enabled); + ms_enabledToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.Enabled, state); + + ms_friendsOnlyToggle = ms_category.AddToggle("Friends only", "Allow only friends to pick you up", Settings.FriendsOnly); + ms_friendsOnlyToggle.OnValueUpdated += (state) => OnToggleUpdate(UiIndex.FriendsOnly, state); + + ms_velocityMultiplierSlider = ms_category.AddSlider("Velocity multiplier", "Velocity multiplier upon drop", Settings.VelocityMultiplier, 0f, 50f); + ms_velocityMultiplierSlider.OnValueUpdated += (value) => OnSliderUpdate(UiIndex.VelocityMultiplier, value); + } + + static void OnToggleUpdate(UiIndex p_index, bool p_state) + { + try + { + switch(p_index) + { + case UiIndex.Enabled: + Settings.SetSetting(Settings.ModSetting.Enabled, p_state); + break; + + case UiIndex.FriendsOnly: + Settings.SetSetting(Settings.ModSetting.FriendsOnly, p_state); + break; + } + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnSliderUpdate(UiIndex p_index, float p_value) + { + try + { + switch(p_index) + { + case UiIndex.VelocityMultiplier: + Settings.SetSetting(Settings.ModSetting.VelocityMultiplier, p_value); + break; + } + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static Stream GetIconStream(string p_name) => Assembly.GetExecutingAssembly().GetManifestResourceStream(ms_namespace + ".resources." + p_name); + } +} diff --git a/ml_ppu/PickUpManager.cs b/ml_ppu/PickUpManager.cs new file mode 100644 index 0000000..bac293a --- /dev/null +++ b/ml_ppu/PickUpManager.cs @@ -0,0 +1,246 @@ +using ABI.CCK.Components; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.Player; +using ABI_RC.Systems.IK; +using ABI_RC.Systems.Movement; +using UnityEngine; + +namespace ml_ppu +{ + class PickUpManager : MonoBehaviour + { + public static PickUpManager Instance { get; private set; } = null; + + Collider m_holderPointA = null; + CVRPointer m_holderPointerA = null; + Collider m_holderPointB = null; + CVRPointer m_holderPointerB = null; + + CapsuleCollider m_collider = null; + Matrix4x4 m_colliderOffSet; + Matrix4x4 m_avatarOffSet; + + Transform m_hips = null; + Transform m_armLeft = null; + Transform m_armRight = null; + bool m_ready = false; + bool m_held = false; + + Vector3 m_lastPosition; + Vector3 m_velocity; + + void Awake() + { + if(Instance != null) + { + DestroyImmediate(this); + return; + } + + Instance = this; + DontDestroyOnLoad(this); + } + + void Start() + { + GameEvents.OnAvatarSetup.AddListener(this.OnAvatarSetup); + GameEvents.OnAvatarClear.AddListener(this.OnAvatarClear); + GameEvents.OnIKScaling.AddListener(this.OnIKScaling); + GameEvents.OnWorldPreSpawn.AddListener(this.OnWorldPreSpawn); + GameEvents.OnSeatPreSit.AddListener(this.OnSeatPreSit); + + Settings.OnEnabledChanged.AddListener(this.OnEnabledChanged); + } + + void OnDestroy() + { + if(Instance == this) + Instance = null; + + GameEvents.OnAvatarSetup.RemoveListener(this.OnAvatarSetup); + GameEvents.OnAvatarClear.RemoveListener(this.OnAvatarClear); + GameEvents.OnIKScaling.RemoveListener(this.OnIKScaling); + GameEvents.OnWorldPreSpawn.RemoveListener(this.OnWorldPreSpawn); + GameEvents.OnSeatPreSit.RemoveListener(this.OnSeatPreSit); + + Settings.OnEnabledChanged.RemoveListener(this.OnEnabledChanged); + } + + void Update() + { + if(m_ready) + { + if(!m_held) + { + if((m_holderPointA != null) && !m_collider.bounds.Intersects(m_holderPointA.bounds)) + { + m_holderPointA = null; + m_holderPointerA = null; + } + + Vector3 l_armsMidPoint = (m_armLeft.position + m_armRight.position) * 0.5f; + Quaternion l_avatarRot = PlayerSetup.Instance._avatar.transform.rotation; + + m_collider.transform.position = Vector3.zero; + m_collider.transform.rotation = Quaternion.identity; + m_collider.transform.up = Quaternion.Inverse(l_avatarRot) * (l_armsMidPoint - m_hips.position).normalized; + + m_collider.transform.position = m_hips.position; + m_collider.transform.rotation = l_avatarRot * m_collider.transform.rotation; + } + else + { + // Check if our points are still valid + if((m_holderPointA != null) && m_holderPointerA.isActiveAndEnabled && (m_holderPointB != null) && m_holderPointerB.isActiveAndEnabled && !ModSupport.IsRagdolled()) + { + Matrix4x4 l_midPoint = Matrix4x4.TRS( + Vector3.Lerp(m_holderPointA.transform.position, m_holderPointB.transform.position, 0.5f), + Quaternion.Slerp(m_holderPointA.transform.rotation, m_holderPointB.transform.rotation, 0.5f), + Vector3.one + ); + Matrix4x4 l_colliderMat = l_midPoint * m_colliderOffSet; + m_collider.transform.position = l_colliderMat.GetPosition(); + m_collider.transform.rotation = l_colliderMat.rotation; + + Matrix4x4 l_avatarMat = l_colliderMat * m_avatarOffSet; + BetterBetterCharacterController.Instance.TeleportPlayerTo(l_avatarMat.GetPosition(), l_avatarMat.rotation.eulerAngles, true, false); + + Vector3 l_position = l_avatarMat.GetPosition(); + m_velocity = (l_position - m_lastPosition) / Time.deltaTime; + m_lastPosition = l_position; + } + else + { + m_holderPointA = null; + m_holderPointerA = null; + m_holderPointB = null; + m_holderPointerB = null; + m_held = false; + + BetterBetterCharacterController.Instance.SetVelocity(m_velocity * Settings.VelocityMultiplier); + } + } + } + } + + void OnAvatarSetup() + { + Animator l_animator = PlayerSetup.Instance._animator; + if((l_animator != null) && l_animator.isHuman) + { + IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.TPose); + PlayerSetup.Instance._avatar.transform.localPosition = Vector3.zero; + PlayerSetup.Instance._avatar.transform.localRotation = Quaternion.identity; + + m_hips = l_animator.GetBoneTransform(HumanBodyBones.Hips); + m_armLeft = l_animator.GetBoneTransform(HumanBodyBones.LeftUpperArm); + m_armRight = l_animator.GetBoneTransform(HumanBodyBones.RightUpperArm); + + if((m_hips != null) && (m_armLeft != null) && (m_armRight != null)) + { + Vector3 l_hipsPos = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_hips.GetMatrix()).GetPosition(); + Vector3 l_armPos = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_armLeft.GetMatrix()).GetPosition(); + + m_collider = new GameObject("[Collider]").AddComponent(); + m_collider.transform.parent = this.transform; + m_collider.isTrigger = true; + m_collider.height = Vector3.Distance(l_hipsPos, new Vector3(0f, l_armPos.y, l_armPos.z)); + m_collider.radius = new Vector2(l_armPos.x, l_armPos.z).magnitude; + m_collider.center = new Vector3(0f, m_collider.height * 0.5f, 0f); + m_collider.gameObject.AddComponent(); + + m_ready = true; + } + } + } + + void OnAvatarClear() + { + m_ready = false; + m_held = false; + + if(m_collider != null) + { + UnityEngine.Object.Destroy(m_collider.gameObject); + m_collider = null; + } + m_holderPointA = null; + m_holderPointerA = null; + m_holderPointB = null; + m_holderPointerB = null; + } + + void OnIKScaling(float p_scale) + { + if(m_ready) + m_collider.transform.localScale = Vector3.one * p_scale; + } + + void OnWorldPreSpawn() + { + if(m_ready && m_held) + { + m_held = false; + m_holderPointA = null; + m_holderPointerA = null; + m_holderPointB = null; + m_holderPointerB = null; + } + } + + void OnSeatPreSit(CVRSeat p_seat) + { + if(!p_seat.occupied && m_ready && m_held) + { + m_held = false; + m_holderPointA = null; + m_holderPointerA = null; + m_holderPointB = null; + m_holderPointerB = null; + } + } + + void OnEnabledChanged(bool p_state) + { + if(!p_state && m_ready && m_held) + { + m_held = false; + m_holderPointA = null; + m_holderPointerA = null; + m_holderPointB = null; + m_holderPointerB = null; + } + } + + internal void OnGrabDetected(Collider p_collider, CVRPointer p_pointer) + { + if(m_ready && !m_held && CVRWorld.Instance.allowFlying && !ModSupport.IsRagdolled()) + { + if(m_holderPointA == null) + { + m_holderPointA = p_collider; + m_holderPointerA = p_pointer; + } + else + { + if((m_holderPointB == null) && (m_holderPointA != p_collider) && (m_holderPointA.transform.root == p_collider.transform.root)) + { + m_holderPointB = p_collider; + m_holderPointerB = p_pointer; + + // Remember offsets + Matrix4x4 l_midPoint = Matrix4x4.TRS( + Vector3.Lerp(m_holderPointA.transform.position, m_holderPointB.transform.position, 0.5f), + Quaternion.Slerp(m_holderPointA.transform.rotation, m_holderPointB.transform.rotation, 0.5f), + Vector3.one + ); + m_colliderOffSet = l_midPoint.inverse * m_collider.transform.GetMatrix(); + m_avatarOffSet = m_collider.transform.GetMatrix().inverse * PlayerSetup.Instance._avatar.transform.GetMatrix(); + m_lastPosition = PlayerSetup.Instance._avatar.transform.position; + m_held = true; + } + } + } + } + } +} diff --git a/ml_ppu/Properties/AssemblyInfo.cs b/ml_ppu/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a7bf6ae --- /dev/null +++ b/ml_ppu/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +[assembly: MelonLoader.MelonInfo(typeof(ml_ppu.PlayerPickUp), "PlayerPickUp", "1.0.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] +[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] +[assembly: MelonLoader.MelonOptionalDependencies("PlayerRagdollMod")] +[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] diff --git a/ml_ppu/README.md b/ml_ppu/README.md new file mode 100644 index 0000000..a9f4400 --- /dev/null +++ b/ml_ppu/README.md @@ -0,0 +1,21 @@ +# Player Pick Up +This mod allow you to be picked up and carried around. + +# Installation +* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader) +* Install [BTKUILib](https://github.com/BTK-Development/BTKUILib) +* Get [latest release DLL](../../../releases/latest): + * Put `PlayerPickUp.dll` in `Mods` folder of game + +# Usage +Available mod's settings in BTKUILib's page: +* **Enabled:** sets mod's activity as enabled or disabled; `true` by default; +* **Friends only:** allow only friends to pick you up; `true` by default; +* **Velocity multiplier:** velocity multiplier upon drop/throw; `1.0` by default. + +To pick you up remote player should: +* Make hands `grab` pointers to appear on your side (usually, press controller grip trigger button or fist gesture, depends on remote player controllers type); +* Touch your avatar's torso with both pointers; + +# Notes +* Compatible with PlayerRagdolMod. diff --git a/ml_ppu/Settings.cs b/ml_ppu/Settings.cs new file mode 100644 index 0000000..24b9215 --- /dev/null +++ b/ml_ppu/Settings.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace ml_ppu +{ + static class Settings + { + internal class SettingEvent + { + event Action m_action; + public void AddListener(Action p_listener) => m_action += p_listener; + public void RemoveListener(Action p_listener) => m_action -= p_listener; + public void Invoke(T p_value) => m_action?.Invoke(p_value); + } + + public enum ModSetting + { + Enabled = 0, + FriendsOnly, + VelocityMultiplier + } + + public static bool Enabled { get; private set; } = true; + public static bool FriendsOnly { get; private set; } = true; + public static float VelocityMultiplier { get; private set; } = 1f; + + public static readonly SettingEvent OnEnabledChanged = new SettingEvent(); + public static readonly SettingEvent OnFriendsOnlyChanged = new SettingEvent(); + public static readonly SettingEvent OnVelocityMultiplierChanged = new SettingEvent(); + + static MelonLoader.MelonPreferences_Category ms_category = null; + static List ms_entries = null; + + internal static void Init() + { + ms_category = MelonLoader.MelonPreferences.CreateCategory("PPU", "Player Pick Up", true); + + ms_entries = new List() + { + ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled), + ms_category.CreateEntry(ModSetting.FriendsOnly.ToString(), FriendsOnly), + ms_category.CreateEntry(ModSetting.VelocityMultiplier.ToString(), VelocityMultiplier) + }; + + Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue; + FriendsOnly = (bool)ms_entries[(int)ModSetting.FriendsOnly].BoxedValue; + VelocityMultiplier = Mathf.Clamp((float)ms_entries[(int)ModSetting.VelocityMultiplier].BoxedValue, 0f, 50f); + } + + public static void SetSetting(ModSetting p_settings, object p_value) + { + try + { + switch(p_settings) + { + // Booleans + case ModSetting.Enabled: + { + Enabled = (bool)p_value; + OnEnabledChanged.Invoke(Enabled); + } + break; + + case ModSetting.FriendsOnly: + { + FriendsOnly = (bool)p_value; + OnFriendsOnlyChanged.Invoke(FriendsOnly); + } + break; + + // Floats + case ModSetting.VelocityMultiplier: + { + VelocityMultiplier = (float)p_value; + OnVelocityMultiplierChanged.Invoke(VelocityMultiplier); + } + break; + } + + if(ms_entries != null) + ms_entries[(int)p_settings].BoxedValue = p_value; + } + catch(Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + } +} diff --git a/ml_ppu/Utils.cs b/ml_ppu/Utils.cs new file mode 100644 index 0000000..54686b1 --- /dev/null +++ b/ml_ppu/Utils.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace ml_ppu +{ + static class Utils + { + 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.lossyScale : Vector3.one); + } + } +} diff --git a/ml_ppu/ml_ppu.csproj b/ml_ppu/ml_ppu.csproj new file mode 100644 index 0000000..4bbe04c --- /dev/null +++ b/ml_ppu/ml_ppu.csproj @@ -0,0 +1,80 @@ + + + + netstandard2.1 + x64 + PlayerPickUp + SDraw + + + + embedded + true + + + + + + + + + + + + + D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\ECM2.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\Mods\PlayerRagdollMod.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll + false + false + + + D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll + false + false + + + + + + + + diff --git a/ml_ppu/resources/person.png b/ml_ppu/resources/person.png new file mode 100644 index 0000000..917f468 Binary files /dev/null and b/ml_ppu/resources/person.png differ diff --git a/ml_prm/README.md b/ml_prm/README.md index 28b3c3f..9af7f2a 100644 --- a/ml_prm/README.md +++ b/ml_prm/README.md @@ -3,6 +3,7 @@ This mod turns player's avatar into ragdoll puppet. # Installation * Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader) +* Install [BTKUILib](https://github.com/BTK-Development/BTKUILib) * Get [latest release DLL](../../../releases/latest): * Put `PlayerRagdollMod.dll` in `Mods` folder of game