Automatic locomotion mass center

Animated bend normal while jumping/flying
Arm weights fix
Mod remake
This commit is contained in:
SDraw 2023-02-11 13:49:50 +03:00
parent f2b63303eb
commit cb26ab1e6c
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
16 changed files with 773 additions and 96 deletions

View file

@ -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;
}
}
}

View file

@ -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)
{

View file

@ -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)]

View file

@ -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
View 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
View 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;
}
}
}

View file

@ -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
View 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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
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');
}
}