mirror of
https://github.com/hanetzer/sdraw_mods_cvr.git
synced 2025-09-03 18:39:23 +00:00
Automatic locomotion mass center
Animated bend normal while jumping/flying Arm weights fix Mod remake
This commit is contained in:
parent
f2b63303eb
commit
cb26ab1e6c
16 changed files with 773 additions and 96 deletions
|
@ -1,5 +1,8 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using RootMotion.FinalIK;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ml_pam
|
||||
|
@ -7,57 +10,275 @@ namespace ml_pam
|
|||
[DisallowMultipleComponent]
|
||||
class ArmMover : MonoBehaviour
|
||||
{
|
||||
const float c_offsetLimit = 0.5f;
|
||||
|
||||
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
|
||||
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
|
||||
static readonly Quaternion ms_rotationOffset = Quaternion.Euler(0f, 0f, -90f);
|
||||
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f);
|
||||
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f);
|
||||
static readonly Quaternion ms_palmToLeft = Quaternion.Euler(0f, 0f, -90f);
|
||||
|
||||
Animator m_animator = null;
|
||||
bool m_inVR = false;
|
||||
VRIK m_vrIK = null;
|
||||
Vector2 m_armWeight = Vector2.zero;
|
||||
Transform m_origRightHand = null;
|
||||
float m_playspaceScale = 1f;
|
||||
|
||||
int m_mainLayer = -1;
|
||||
CVRPickupObject m_target = null;
|
||||
bool m_enabled = true;
|
||||
ArmIK m_armIK = null;
|
||||
Transform m_target = null;
|
||||
Transform m_rotationTarget = null;
|
||||
CVRPickupObject m_pickup = null;
|
||||
Matrix4x4 m_offset = Matrix4x4.identity;
|
||||
bool m_targetActive = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_animator = PlayerSetup.Instance._animator;
|
||||
m_mainLayer = m_animator.GetLayerIndex("Locomotion/Emotes");
|
||||
m_inVR = Utils.IsInVR();
|
||||
|
||||
m_target = new GameObject("ArmPickupTarget").transform;
|
||||
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
|
||||
m_target.localPosition = Vector3.zero;
|
||||
m_target.localRotation = Quaternion.identity;
|
||||
|
||||
m_rotationTarget = new GameObject("RotationTarget").transform;
|
||||
m_rotationTarget.parent = m_target;
|
||||
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * Settings.GrabOffset, 0f, 0f);
|
||||
m_rotationTarget.localRotation = Quaternion.identity;
|
||||
|
||||
m_enabled = Settings.Enabled;
|
||||
|
||||
Settings.EnabledChange += this.SetEnabled;
|
||||
Settings.GrabOffsetChange += this.SetGrabOffset;
|
||||
}
|
||||
|
||||
void OnAnimatorIK(int p_layerIndex)
|
||||
void OnDestroy()
|
||||
{
|
||||
if((p_layerIndex == m_mainLayer) && (m_target != null)) // Only main Locomotion/Emotes layer
|
||||
Settings.EnabledChange -= this.SetEnabled;
|
||||
Settings.GrabOffsetChange -= this.SetGrabOffset;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if(m_enabled && (m_pickup != null))
|
||||
{
|
||||
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
|
||||
Matrix4x4 l_result = m_pickup.transform.GetMatrix() * m_offset;
|
||||
m_target.position = l_result * ms_pointVector;
|
||||
}
|
||||
}
|
||||
|
||||
m_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f);
|
||||
m_animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f);
|
||||
void OnIKPreUpdate()
|
||||
{
|
||||
m_armWeight.Set(m_vrIK.solver.rightArm.positionWeight, m_vrIK.solver.rightArm.rotationWeight);
|
||||
|
||||
switch(m_target.gripType)
|
||||
if(m_targetActive && (Mathf.Approximately(m_armWeight.x, 0f) || Mathf.Approximately(m_armWeight.y, 0f)))
|
||||
{
|
||||
m_vrIK.solver.rightArm.positionWeight = 1f;
|
||||
m_vrIK.solver.rightArm.rotationWeight = 1f;
|
||||
}
|
||||
}
|
||||
void OnIKPostUpdate()
|
||||
{
|
||||
m_vrIK.solver.rightArm.positionWeight = m_armWeight.x;
|
||||
m_vrIK.solver.rightArm.rotationWeight = m_armWeight.y;
|
||||
}
|
||||
|
||||
// Settings
|
||||
void SetEnabled(bool p_state)
|
||||
{
|
||||
m_enabled = p_state;
|
||||
|
||||
RefreshArmIK();
|
||||
if(m_enabled)
|
||||
RestorePickup();
|
||||
else
|
||||
RestoreVRIK();
|
||||
}
|
||||
|
||||
void SetGrabOffset(float p_value)
|
||||
{
|
||||
if(m_rotationTarget != null)
|
||||
m_rotationTarget.localPosition = new Vector3(c_offsetLimit * m_playspaceScale * p_value, 0f, 0f);
|
||||
}
|
||||
|
||||
// Game events
|
||||
internal void OnAvatarClear()
|
||||
{
|
||||
m_vrIK = null;
|
||||
m_origRightHand = null;
|
||||
m_armIK = null;
|
||||
m_targetActive = false;
|
||||
}
|
||||
|
||||
internal void OnAvatarSetup()
|
||||
{
|
||||
// Recheck if user could switch to VR
|
||||
if(m_inVR != Utils.IsInVR())
|
||||
{
|
||||
m_target.parent = PlayerSetup.Instance.GetActiveCamera().transform;
|
||||
m_target.localPosition = Vector3.zero;
|
||||
m_target.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
m_inVR = Utils.IsInVR();
|
||||
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
|
||||
|
||||
if(PlayerSetup.Instance._animator.isHuman)
|
||||
{
|
||||
HumanPose l_currentPose = new HumanPose();
|
||||
HumanPoseHandler l_poseHandler = null;
|
||||
|
||||
if(!m_inVR)
|
||||
{
|
||||
case CVRPickupObject.GripType.Origin:
|
||||
{
|
||||
if(m_target.gripOrigin != null)
|
||||
{
|
||||
m_animator.SetIKPosition(AvatarIKGoal.RightHand, m_target.gripOrigin.position);
|
||||
m_animator.SetIKRotation(AvatarIKGoal.RightHand, l_camera.rotation * ms_rotationOffset);
|
||||
}
|
||||
}
|
||||
break;
|
||||
l_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
|
||||
l_poseHandler.GetHumanPose(ref l_currentPose);
|
||||
|
||||
case CVRPickupObject.GripType.Free:
|
||||
HumanPose l_tPose = new HumanPose
|
||||
{
|
||||
Matrix4x4 l_result = m_target.transform.GetMatrix() * m_offset;
|
||||
m_animator.SetIKPosition(AvatarIKGoal.RightHand, l_result * ms_pointVector);
|
||||
m_animator.SetIKRotation(AvatarIKGoal.RightHand, l_camera.rotation * ms_rotationOffset);
|
||||
bodyPosition = l_currentPose.bodyPosition,
|
||||
bodyRotation = l_currentPose.bodyRotation,
|
||||
muscles = new float[l_currentPose.muscles.Length]
|
||||
};
|
||||
for(int i = 0; i < l_tPose.muscles.Length; i++)
|
||||
l_tPose.muscles[i] = ms_tposeMuscles[i];
|
||||
|
||||
l_poseHandler.SetHumanPose(ref l_tPose);
|
||||
}
|
||||
|
||||
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
|
||||
if(l_hand != null)
|
||||
m_rotationTarget.localRotation = (ms_palmToLeft * (m_inVR ? ms_offsetRight : ms_offsetRightDesktop)) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
|
||||
|
||||
if(m_vrIK == null)
|
||||
{
|
||||
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
|
||||
if(l_chest == null)
|
||||
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
|
||||
if(l_chest == null)
|
||||
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
|
||||
|
||||
m_armIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
|
||||
m_armIK.solver.isLeft = false;
|
||||
m_armIK.solver.SetChain(
|
||||
l_chest,
|
||||
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
|
||||
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
|
||||
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
|
||||
l_hand,
|
||||
PlayerSetup.Instance._animator.transform
|
||||
);
|
||||
m_armIK.solver.arm.target = m_rotationTarget;
|
||||
m_armIK.solver.arm.positionWeight = 1f;
|
||||
m_armIK.solver.arm.rotationWeight = 1f;
|
||||
m_armIK.solver.IKPositionWeight = 0f;
|
||||
m_armIK.solver.IKRotationWeight = 0f;
|
||||
m_armIK.enabled = m_enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_origRightHand = m_vrIK.solver.rightArm.target;
|
||||
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
|
||||
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
|
||||
}
|
||||
|
||||
l_poseHandler?.SetHumanPose(ref l_currentPose);
|
||||
l_poseHandler?.Dispose();
|
||||
}
|
||||
|
||||
if(m_enabled)
|
||||
RestorePickup();
|
||||
}
|
||||
|
||||
internal void OnPickupGrab(CVRPickupObject p_pickup, ControllerRay p_ray, Vector3 p_hit)
|
||||
{
|
||||
if(p_ray == ViewManager.Instance.desktopControllerRay)
|
||||
{
|
||||
m_pickup = p_pickup;
|
||||
|
||||
// Set offsets
|
||||
if(m_pickup.gripType == CVRPickupObject.GripType.Origin)
|
||||
{
|
||||
if(m_pickup.ikReference != null)
|
||||
m_offset = (m_pickup.transform.GetMatrix().inverse * m_pickup.ikReference.GetMatrix());
|
||||
else
|
||||
{
|
||||
if(m_pickup.gripOrigin != null)
|
||||
m_offset = m_pickup.transform.GetMatrix().inverse * m_pickup.gripOrigin.GetMatrix();
|
||||
}
|
||||
}
|
||||
else
|
||||
m_offset = m_pickup.transform.GetMatrix().inverse * Matrix4x4.Translate(p_hit);
|
||||
|
||||
if(m_enabled)
|
||||
{
|
||||
if((m_vrIK != null) && !m_targetActive)
|
||||
{
|
||||
m_vrIK.solver.rightArm.target = m_rotationTarget;
|
||||
m_targetActive = true;
|
||||
}
|
||||
|
||||
if(m_armIK != null)
|
||||
{
|
||||
m_armIK.solver.IKPositionWeight = 1f;
|
||||
m_armIK.solver.IKRotationWeight = 1f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTarget(CVRPickupObject p_target, Vector3 p_hit)
|
||||
internal void OnPickupDrop(CVRPickupObject p_pickup)
|
||||
{
|
||||
m_target = p_target;
|
||||
m_offset = (m_target != null) ? (p_target.transform.GetMatrix().inverse * Matrix4x4.Translate(p_hit)): Matrix4x4.identity;
|
||||
if(m_pickup == p_pickup)
|
||||
{
|
||||
m_pickup = null;
|
||||
|
||||
if(m_enabled)
|
||||
{
|
||||
RestoreVRIK();
|
||||
|
||||
if(m_armIK != null)
|
||||
{
|
||||
m_armIK.solver.IKPositionWeight = 0f;
|
||||
m_armIK.solver.IKRotationWeight = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnPlayspaceScale(float p_relation)
|
||||
{
|
||||
m_playspaceScale = p_relation;
|
||||
SetGrabOffset(Settings.GrabOffset);
|
||||
}
|
||||
|
||||
// Arbitrary
|
||||
void RestorePickup()
|
||||
{
|
||||
if((m_vrIK != null) && (m_pickup != null))
|
||||
{
|
||||
m_vrIK.solver.rightArm.target = m_rotationTarget;
|
||||
m_targetActive = true;
|
||||
}
|
||||
if((m_armIK != null) && (m_pickup != null))
|
||||
{
|
||||
m_armIK.solver.IKPositionWeight = 1f;
|
||||
m_armIK.solver.IKRotationWeight = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
void RestoreVRIK()
|
||||
{
|
||||
if((m_vrIK != null) && m_targetActive)
|
||||
{
|
||||
m_vrIK.solver.rightArm.target = m_origRightHand;
|
||||
m_targetActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshArmIK()
|
||||
{
|
||||
if(m_armIK != null)
|
||||
m_armIK.enabled = m_enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
@ -10,13 +11,15 @@ namespace ml_pam
|
|||
{
|
||||
static PickupArmMovement ms_instance = null;
|
||||
|
||||
ArmMover m_localPuller = null;
|
||||
ArmMover m_localMover = null;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
if(ms_instance == null)
|
||||
ms_instance = this;
|
||||
|
||||
Settings.Init();
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
|
||||
null,
|
||||
|
@ -37,6 +40,21 @@ namespace ml_pam
|
|||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnCVRPickupObjectDrop_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(PickupArmMovement).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator WaitForLocalPlayer()
|
||||
{
|
||||
while(PlayerSetup.Instance == null)
|
||||
yield return null;
|
||||
|
||||
m_localMover = PlayerSetup.Instance.gameObject.AddComponent<ArmMover>();
|
||||
}
|
||||
|
||||
public override void OnDeinitializeMelon()
|
||||
|
@ -50,7 +68,8 @@ namespace ml_pam
|
|||
{
|
||||
try
|
||||
{
|
||||
m_localPuller = null;
|
||||
if(m_localMover != null)
|
||||
m_localMover.OnAvatarClear();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -63,8 +82,8 @@ namespace ml_pam
|
|||
{
|
||||
try
|
||||
{
|
||||
if(!Utils.IsInVR())
|
||||
m_localPuller = PlayerSetup.Instance._avatar.AddComponent<ArmMover>();
|
||||
if(m_localMover != null)
|
||||
m_localMover.OnAvatarSetup();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -72,15 +91,13 @@ namespace ml_pam
|
|||
}
|
||||
}
|
||||
|
||||
static void OnCVRPickupObjectGrab_Postfix(ref CVRPickupObject __instance, Vector3 __2) => ms_instance?.OnCVRPickupObjectGrab(__instance, __2);
|
||||
void OnCVRPickupObjectGrab(CVRPickupObject p_pickup, Vector3 p_hit)
|
||||
static void OnCVRPickupObjectGrab_Postfix(ref CVRPickupObject __instance, ControllerRay __1, Vector3 __2) => ms_instance?.OnCVRPickupObjectGrab(__instance, __1, __2);
|
||||
void OnCVRPickupObjectGrab(CVRPickupObject p_pickup, ControllerRay p_ray, Vector3 p_hit)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(p_pickup.IsGrabbedByMe() && (m_localPuller != null))
|
||||
{
|
||||
m_localPuller.SetTarget(p_pickup, p_hit);
|
||||
}
|
||||
if(p_pickup.IsGrabbedByMe() && (m_localMover != null))
|
||||
m_localMover.OnPickupGrab(p_pickup, p_ray, p_hit);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -88,15 +105,27 @@ namespace ml_pam
|
|||
}
|
||||
}
|
||||
|
||||
static void OnCVRPickupObjectDrop_Postfix() => ms_instance?.OnCVRPickupObjectDrop();
|
||||
void OnCVRPickupObjectDrop()
|
||||
static void OnCVRPickupObjectDrop_Postfix(ref CVRPickupObject __instance) => ms_instance?.OnCVRPickupObjectDrop(__instance);
|
||||
void OnCVRPickupObjectDrop(CVRPickupObject p_pickup)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(m_localPuller != null)
|
||||
{
|
||||
m_localPuller.SetTarget(null, Vector3.zero);
|
||||
}
|
||||
if(m_localMover != null)
|
||||
m_localMover.OnPickupDrop(p_pickup);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnPlayspaceScale_Postfix(float ____avatarScaleRelation) => ms_instance?.OnPlayspaceScale(____avatarScaleRelation);
|
||||
void OnPlayspaceScale(float p_relation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(m_localMover != null)
|
||||
m_localMover.OnPlayspaceScale(p_relation);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyTitle("PickupArmMovement")]
|
||||
[assembly: AssemblyVersion("1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0")]
|
||||
[assembly: AssemblyVersion("1.0.1")]
|
||||
[assembly: AssemblyFileVersion("1.0.1")]
|
||||
|
||||
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.0.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
|
||||
[assembly: MelonLoader.MelonInfo(typeof(ml_pam.PickupArmMovement), "PickupArmMovement", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
|
||||
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
|
||||
[assembly: MelonLoader.MelonPriority(1)]
|
||||
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
|
@ -1,7 +1,12 @@
|
|||
# Pickup Arm Movement
|
||||
This mod adds arm tracking upon holding pickup.
|
||||
This mod adds arm tracking upon holding pickup in desktop mode.
|
||||
|
||||
# Installation
|
||||
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
|
||||
* Get [latest release DLL](../../../releases/latest):
|
||||
* Put `ml_pam.dll` in `Mods` folder of game
|
||||
|
||||
# Usage
|
||||
Available mod's settings in `Settings - Interactions`:
|
||||
* **Enable hand movement:** enables/disables arm tracking; default value - `true`.
|
||||
* **Grab offset:** offset from pickup grab point; defalut value - `25`.
|
||||
|
|
26
ml_pam/Scripts.cs
Normal file
26
ml_pam/Scripts.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_pam
|
||||
{
|
||||
static class Scripts
|
||||
{
|
||||
public static string GetEmbeddedScript(string p_name)
|
||||
{
|
||||
string l_result = "";
|
||||
Assembly l_assembly = Assembly.GetExecutingAssembly();
|
||||
string l_assemblyName = l_assembly.GetName().Name;
|
||||
|
||||
try
|
||||
{
|
||||
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
|
||||
StreamReader l_streadReader = new StreamReader(l_libraryStream);
|
||||
l_result = l_streadReader.ReadToEnd();
|
||||
}
|
||||
catch(Exception) { }
|
||||
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
}
|
113
ml_pam/Settings.cs
Normal file
113
ml_pam/Settings.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using cohtml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ml_pam
|
||||
{
|
||||
static class Settings
|
||||
{
|
||||
public enum ModSetting
|
||||
{
|
||||
Enabled = 0,
|
||||
GrabOffset
|
||||
}
|
||||
|
||||
static bool ms_enabled = true;
|
||||
static float ms_grabOffset = 0.25f;
|
||||
|
||||
static MelonLoader.MelonPreferences_Category ms_category = null;
|
||||
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
|
||||
|
||||
static public event Action<bool> EnabledChange;
|
||||
static public event Action<float> GrabOffsetChange;
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
ms_category = MelonLoader.MelonPreferences.CreateCategory("PAM");
|
||||
|
||||
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
|
||||
{
|
||||
ms_category.CreateEntry(ModSetting.Enabled.ToString(), ms_enabled),
|
||||
ms_category.CreateEntry(ModSetting.GrabOffset.ToString(), 25),
|
||||
};
|
||||
|
||||
Load();
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator WaitMainMenuUi()
|
||||
{
|
||||
while(ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.gameMenuView == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.gameMenuView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_PAM_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_PAM_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
|
||||
};
|
||||
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
|
||||
foreach(var l_entry in ms_entries)
|
||||
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingPAM", l_entry.DisplayName, l_entry.GetValueAsString());
|
||||
};
|
||||
}
|
||||
|
||||
static void Load()
|
||||
{
|
||||
ms_enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
|
||||
ms_grabOffset = (int)ms_entries[(int)ModSetting.GrabOffset].BoxedValue * 0.01f;
|
||||
}
|
||||
|
||||
static void OnToggleUpdate(string p_name, string p_value)
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Enabled:
|
||||
{
|
||||
ms_enabled = bool.Parse(p_value);
|
||||
EnabledChange?.Invoke(ms_enabled);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnSliderUpdate(string p_name, string p_value)
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.GrabOffset:
|
||||
{
|
||||
ms_grabOffset = int.Parse(p_value) * 0.01f;
|
||||
GrabOffsetChange?.Invoke(ms_grabOffset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Enabled
|
||||
{
|
||||
get => ms_enabled;
|
||||
}
|
||||
public static float GrabOffset
|
||||
{
|
||||
get => ms_grabOffset;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,16 @@
|
|||
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="cohtml.Net">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Cohtml.Runtime">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader, Version=0.5.7.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
|
||||
|
@ -66,8 +76,13 @@
|
|||
<Compile Include="ArmMover.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Scripts.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="resources\menu.js" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"</PostBuildEvent>
|
||||
|
|
209
ml_pam/resources/menu.js
Normal file
209
ml_pam/resources/menu.js
Normal file
|
@ -0,0 +1,209 @@
|
|||
// Add settings
|
||||
var g_modSettingsPAM = [];
|
||||
|
||||
engine.on('updateModSettingPAM', function (_name, _value) {
|
||||
for (var i = 0; i < g_modSettingsPAM.length; i++) {
|
||||
if (g_modSettingsPAM[i].name == _name) {
|
||||
g_modSettingsPAM[i].updateValue(_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Modified from original `inp` types, because I have no js knowledge to hook stuff
|
||||
function inp_toggle_mod_pam(_obj, _callbackName) {
|
||||
this.obj = _obj;
|
||||
this.callbackName = _callbackName;
|
||||
this.value = _obj.getAttribute('data-current');
|
||||
this.name = _obj.id;
|
||||
this.type = _obj.getAttribute('data-type');
|
||||
|
||||
var self = this;
|
||||
|
||||
this.mouseDown = function (_e) {
|
||||
self.value = self.value == "True" ? "False" : "True";
|
||||
self.updateState();
|
||||
}
|
||||
|
||||
this.updateState = function () {
|
||||
self.obj.classList.remove("checked");
|
||||
if (self.value == "True") {
|
||||
self.obj.classList.add("checked");
|
||||
}
|
||||
|
||||
engine.call(self.callbackName, self.name, self.value);
|
||||
}
|
||||
|
||||
_obj.addEventListener('mousedown', this.mouseDown);
|
||||
|
||||
this.getValue = function () {
|
||||
return self.value;
|
||||
}
|
||||
|
||||
this.updateValue = function (value) {
|
||||
self.value = value;
|
||||
|
||||
self.obj.classList.remove("checked");
|
||||
if (self.value == "True") {
|
||||
self.obj.classList.add("checked");
|
||||
}
|
||||
}
|
||||
|
||||
this.updateValue(this.value);
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
value: this.getValue,
|
||||
updateValue: this.updateValue
|
||||
}
|
||||
}
|
||||
|
||||
function inp_slider_mod_pam(_obj, _callbackName) {
|
||||
this.obj = _obj;
|
||||
this.callbackName = _callbackName;
|
||||
this.minValue = parseFloat(_obj.getAttribute('data-min'));
|
||||
this.maxValue = parseFloat(_obj.getAttribute('data-max'));
|
||||
this.percent = 0;
|
||||
this.value = parseFloat(_obj.getAttribute('data-current'));
|
||||
this.dragActive = false;
|
||||
this.name = _obj.id;
|
||||
this.type = _obj.getAttribute('data-type');
|
||||
this.stepSize = _obj.getAttribute('data-stepSize') || 0;
|
||||
this.format = _obj.getAttribute('data-format') || '{value}';
|
||||
|
||||
var self = this;
|
||||
|
||||
if (this.stepSize != 0)
|
||||
this.value = Math.round(this.value / this.stepSize) * this.stepSize;
|
||||
else
|
||||
this.value = Math.round(this.value);
|
||||
|
||||
this.valueLabelBackground = document.createElement('div');
|
||||
this.valueLabelBackground.className = 'valueLabel background';
|
||||
this.valueLabelBackground.innerHTML = this.format.replace('{value}', this.value);
|
||||
this.obj.appendChild(this.valueLabelBackground);
|
||||
|
||||
this.valueBar = document.createElement('div');
|
||||
this.valueBar.className = 'valueBar';
|
||||
this.valueBar.setAttribute('style', 'width: ' + (((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
|
||||
this.obj.appendChild(this.valueBar);
|
||||
|
||||
this.valueLabelForeground = document.createElement('div');
|
||||
this.valueLabelForeground.className = 'valueLabel foreground';
|
||||
this.valueLabelForeground.innerHTML = this.format.replace('{value}', this.value);
|
||||
this.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
|
||||
this.valueBar.appendChild(this.valueLabelForeground);
|
||||
|
||||
this.mouseDown = function (_e) {
|
||||
self.dragActive = true;
|
||||
self.mouseMove(_e, false);
|
||||
}
|
||||
|
||||
this.mouseMove = function (_e, _write) {
|
||||
if (self.dragActive) {
|
||||
var rect = _obj.getBoundingClientRect();
|
||||
var start = rect.left;
|
||||
var end = rect.right;
|
||||
self.percent = Math.min(Math.max((_e.clientX - start) / rect.width, 0), 1);
|
||||
var value = self.percent;
|
||||
value *= (self.maxValue - self.minValue);
|
||||
value += self.minValue;
|
||||
if (self.stepSize != 0) {
|
||||
value = Math.round(value / self.stepSize);
|
||||
self.value = value * self.stepSize;
|
||||
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
|
||||
}
|
||||
else
|
||||
self.value = Math.round(value);
|
||||
|
||||
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
|
||||
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
|
||||
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
|
||||
|
||||
engine.call(self.callbackName, self.name, "" + self.value);
|
||||
self.displayImperial();
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseUp = function (_e) {
|
||||
self.mouseMove(_e, true);
|
||||
self.dragActive = false;
|
||||
}
|
||||
|
||||
_obj.addEventListener('mousedown', this.mouseDown);
|
||||
document.addEventListener('mousemove', this.mouseMove);
|
||||
document.addEventListener('mouseup', this.mouseUp);
|
||||
|
||||
this.getValue = function () {
|
||||
return self.value;
|
||||
}
|
||||
|
||||
this.updateValue = function (value) {
|
||||
if (self.stepSize != 0)
|
||||
self.value = Math.round(value * self.stepSize) / self.stepSize;
|
||||
else
|
||||
self.value = Math.round(value);
|
||||
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
|
||||
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
|
||||
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
|
||||
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
|
||||
self.displayImperial();
|
||||
}
|
||||
|
||||
this.displayImperial = function () {
|
||||
var displays = document.querySelectorAll('.imperialDisplay');
|
||||
for (var i = 0; i < displays.length; i++) {
|
||||
var binding = displays[i].getAttribute('data-binding');
|
||||
if (binding == self.name) {
|
||||
var realFeet = ((self.value * 0.393700) / 12);
|
||||
var feet = Math.floor(realFeet);
|
||||
var inches = Math.floor((realFeet - feet) * 12);
|
||||
displays[i].innerHTML = feet + "'" + inches + '''';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
value: this.getValue,
|
||||
updateValue: this.updateValue
|
||||
}
|
||||
}
|
||||
|
||||
// Add own menu
|
||||
{
|
||||
let l_block = document.createElement('div');
|
||||
l_block.innerHTML = `
|
||||
<div class ="settings-subcategory">
|
||||
<div class ="subcategory-name">Pickup Arm Mover</div>
|
||||
<div class ="subcategory-description"></div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Enable hand movement: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Grab offset: </div>
|
||||
<div class ="option-input">
|
||||
<div id="GrabOffset" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="25"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('settings-interaction').appendChild(l_block);
|
||||
|
||||
// Update toggles in new menu block
|
||||
let l_toggles = l_block.querySelectorAll('.inp_toggle');
|
||||
for (var i = 0; i < l_toggles.length; i++) {
|
||||
g_modSettingsPAM[g_modSettingsPAM.length] = new inp_toggle_mod_pam(l_toggles[i], 'MelonMod_PAM_Call_InpToggle');
|
||||
}
|
||||
|
||||
// Update sliders in new menu block
|
||||
let l_sliders = l_block.querySelectorAll('.inp_slider');
|
||||
for (var i = 0; i < l_sliders.length; i++) {
|
||||
g_modSettingsPAM[g_modSettingsPAM.length] = new inp_slider_mod_pam(l_sliders[i], 'MelonMod_PAM_Call_InpSlider');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue