diff --git a/README.md b/README.md index 158a34d..c4eb45e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Merged set of MelonLoader mods for ChilloutVR. |-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------| | Avatar Change Info | ml_aci | 1.0.2 | Yes | Working | | Avatar Motion Tweaker | ml_amt | 1.1.0 | On review | Working | +| Desktop Head Tracking | ml_dht | 1.0.0. | No | Working | | Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working | | Four Point Tracking | ml_fpt | 1.0.6 | On review | Working | | Leap Motion Extension | ml_lme | 1.1.8 | Yes | Working | diff --git a/ml_dht/.github/img_01.png b/ml_dht/.github/img_01.png new file mode 100644 index 0000000..89399a9 Binary files /dev/null and b/ml_dht/.github/img_01.png differ diff --git a/ml_dht/FaceTracked.cs b/ml_dht/FaceTracked.cs new file mode 100644 index 0000000..df0cfae --- /dev/null +++ b/ml_dht/FaceTracked.cs @@ -0,0 +1,101 @@ +using ABI_RC.Core.Player; +using UnityEngine; + +namespace ml_dht +{ + class FaceTracked : MonoBehaviour + { + bool m_enabled = true; + float m_smoothing = 0.5f; + bool m_mirrored = false; + + RootMotion.FinalIK.LookAtIK m_lookIK = null; + Transform m_camera = null; + Transform m_headBone = null; + + Vector3 m_headPosition; + Quaternion m_headRotation; + Vector2 m_gazeDirection; + float m_blinkProgress = 0f; + Vector2 m_mouthShapes; + float m_eyebrowsProgress = 0f; + + Quaternion m_bindRotation; + Quaternion m_lastHeadRotation; + + void Start() + { + m_camera = PlayerSetup.Instance.desktopCamera.transform; + } + + public void UpdateTrackingData(ref TrackingData p_data) + { + m_headPosition.Set(p_data.m_headPositionX * (m_mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ); + m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (m_mirrored ? -1f : 1f), p_data.m_headRotationZ * (m_mirrored ? -1f : 1f), p_data.m_headRotationW); + m_gazeDirection.Set(m_mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY); + m_blinkProgress = p_data.m_blink; + m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape); + m_eyebrowsProgress = p_data.m_brows; + } + + public void OnEyeControllerUpdate() + { + if(m_enabled) + { + // Gaze + PlayerSetup.Instance.eyeMovement.manualViewTarget = true; + PlayerSetup.Instance.eyeMovement.targetViewPosition = m_camera.position + m_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * -2f, (m_gazeDirection.y - 0.5f) * 2f, 1f); + + // Blink + PlayerSetup.Instance.eyeMovement.manualBlinking = true; + PlayerSetup.Instance.eyeMovement.blinkProgress = m_blinkProgress; + } + } + + void OnLookIKPostUpdate() + { + if(m_enabled && (m_headBone != null)) + { + m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_headRotation * m_bindRotation, m_smoothing); + m_headBone.localRotation = m_lastHeadRotation; + } + } + + public void OnSetupAvatarGeneral() + { + m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head); + m_lookIK = PlayerSetup.Instance._avatar.GetComponent(); + + if(m_headBone != null) + m_bindRotation = m_headBone.localRotation; + + if(m_lookIK != null) + m_lookIK.solver.OnPostUpdate += this.OnLookIKPostUpdate; + } + public void OnAvatarClear() + { + m_lookIK = null; + m_headBone = null; + m_lastHeadRotation = Quaternion.identity; + m_bindRotation = Quaternion.identity; + } + + public void SetEnabled(bool p_state) + { + if(m_enabled != p_state) + { + m_enabled = p_state; + if(m_enabled) + m_lastHeadRotation = m_bindRotation; + } + } + public void SetSmoothing(float p_value) + { + m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f); + } + public void SetMirrored(bool p_state) + { + m_mirrored = p_state; + } + } +} diff --git a/ml_dht/Main.cs b/ml_dht/Main.cs new file mode 100644 index 0000000..07a833c --- /dev/null +++ b/ml_dht/Main.cs @@ -0,0 +1,133 @@ +using ABI_RC.Core.Player; + +namespace ml_dht +{ + public class DesktopHeadTracking : MelonLoader.MelonMod + { + static DesktopHeadTracking ms_instance = null; + + MemoryMapReader m_mapReader = null; + byte[] m_buffer = null; + TrackingData m_trackingData; + + FaceTracked m_localTracked = null; + + public override void OnApplicationStart() + { + if(ms_instance == null) + ms_instance = this; + + Settings.Init(); + Settings.EnabledChange += this.OnEnabledChanged; + Settings.MirroredChange += this.OnMirroredChanged; + Settings.SmoothingChange += this.OnSmoothingChanged; + + m_mapReader = new MemoryMapReader(); + m_buffer = new byte[1024]; + + m_mapReader.Open("head/data"); + + // Patches + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), + null, + new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnAvatarClear_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) + ); + HarmonyInstance.Patch( + typeof(PlayerSetup).GetMethod("SetupAvatarGeneral", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic), + null, + new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatarGeneral_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) + ); + HarmonyInstance.Patch( + typeof(CVREyeController).GetMethod("Update", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic), + null, + new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) + ); + + MelonLoader.MelonCoroutines.Start(WaitForPlayer()); + } + + System.Collections.IEnumerator WaitForPlayer() + { + while(PlayerSetup.Instance == null) + yield return null; + + m_localTracked = PlayerSetup.Instance.gameObject.AddComponent(); + m_localTracked.SetEnabled(Settings.Enabled); + m_localTracked.SetMirrored(Settings.Mirrored); + m_localTracked.SetSmoothing(Settings.Smoothing); + } + + public override void OnUpdate() + { + if(Settings.Enabled && m_mapReader.Read(ref m_buffer)) + { + m_trackingData = TrackingData.ToObject(m_buffer); + if(m_localTracked != null) + m_localTracked.UpdateTrackingData(ref m_trackingData); + } + } + + void OnEnabledChanged(bool p_state) + { + if(m_localTracked != null) + m_localTracked.SetEnabled(p_state); + } + void OnMirroredChanged(bool p_state) + { + if(m_localTracked != null) + m_localTracked.SetMirrored(p_state); + } + void OnSmoothingChanged(float p_value) + { + if(m_localTracked != null) + m_localTracked.SetSmoothing(p_value); + } + + static void OnSetupAvatarGeneral_Postfix() => ms_instance?.OnSetupAvatarGeneral(); + void OnSetupAvatarGeneral() + { + try + { + if(m_localTracked != null) + m_localTracked.OnSetupAvatarGeneral(); + } + catch(System.Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear(); + void OnAvatarClear() + { + try + { + if(m_localTracked != null) + m_localTracked.OnAvatarClear(); + } + catch(System.Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + + static void OnEyeControllerUpdate_Postfix(ref CVREyeController __instance) + { + try + { + if(__instance == PlayerSetup.Instance.eyeMovement) + ms_instance?.OnEyeControllerUpdate(); + } + catch(System.Exception e) + { + MelonLoader.MelonLogger.Error(e); + } + } + void OnEyeControllerUpdate() + { + if(m_localTracked != null) + m_localTracked.OnEyeControllerUpdate(); + } + } +} \ No newline at end of file diff --git a/ml_dht/MemoryMapReader.cs b/ml_dht/MemoryMapReader.cs new file mode 100644 index 0000000..eb5f71c --- /dev/null +++ b/ml_dht/MemoryMapReader.cs @@ -0,0 +1,55 @@ +using System.IO; +using System.IO.MemoryMappedFiles; + +namespace ml_dht +{ + class MemoryMapReader + { + MemoryMappedFile m_file = null; + MemoryMappedViewStream m_stream = null; + int m_dataSize = 0; + + public bool Open(string p_path, int p_dataSize = 1024) + { + if(m_file == null) + { + m_dataSize = p_dataSize; + + m_file = MemoryMappedFile.CreateOrOpen(p_path, m_dataSize, MemoryMappedFileAccess.ReadWrite); + m_stream = m_file.CreateViewStream(0, m_dataSize, MemoryMappedFileAccess.Read); + } + return (m_file != null); + } + + public bool Read(ref byte[] p_data) + { + bool l_result = false; + if((m_stream != null) && m_stream.CanRead) + { + try + { + m_stream.Seek(0, SeekOrigin.Begin); + m_stream.Read(p_data, 0, (p_data.Length > m_dataSize) ? m_dataSize : p_data.Length); + l_result = true; + } + catch(System.Exception) { } + } + return l_result; + } + + public void Close() + { + if(m_file != null) + { + m_stream.Close(); + m_stream.Dispose(); + m_stream = null; + + m_file.Dispose(); + m_file = null; + + m_dataSize = 0; + } + } + } +} diff --git a/ml_dht/Properties/AssemblyInfo.cs b/ml_dht/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3833945 --- /dev/null +++ b/ml_dht/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Reflection; + +[assembly: AssemblyTitle("DesktopHeadTracking")] +[assembly: AssemblyVersion("1.0.0")] +[assembly: AssemblyFileVersion("1.0.0")] + +[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.0.0", "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/README.md b/ml_dht/README.md new file mode 100644 index 0000000..d43a63e --- /dev/null +++ b/ml_dht/README.md @@ -0,0 +1,27 @@ +# Desktop Head Tracking +This mod adds desktop head tracking based on data from memory-mapped file `head/data`. +Refer to `TrackingData.cs` for reference in case of implementing own software. + +[![](.github/img_01.png)](https://youtu.be/jgcFhSNi9DM) + +# Features +* Head rotation +* Eyes gaze direction +* Blinking + +# Installation +* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader) +* Get [latest release DLL](../../../releases/latest): + * Put `ml_dht.dll` in `Mods` folder of game + +# Usage +Available mod's settings in `Settings - Implementation - Desktop Head Tracking`: +* **Enabled:** enabled head tracking; default value - `false`. +* **Mirrored movement:** mirrors movement and gaze along 0YZ plane; default value - `false`. +* **Movement smoothing:** smoothing factor between new and old movement data; default value - `50`; + +# Known compatible tracking software +* VSeeFace with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf) + +# Notes +* Blinking doesn't work for remote players to due [game's bug](https://feedback.abinteractive.net/p/overrided-blinking-state-isn-t-copied-to-movement-data-from-network). diff --git a/ml_dht/Scripts.cs b/ml_dht/Scripts.cs new file mode 100644 index 0000000..2445a5b --- /dev/null +++ b/ml_dht/Scripts.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Reflection; + +namespace ml_dht +{ + 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; + } + } +} diff --git a/ml_dht/Settings.cs b/ml_dht/Settings.cs new file mode 100644 index 0000000..8662e5d --- /dev/null +++ b/ml_dht/Settings.cs @@ -0,0 +1,127 @@ +using ABI_RC.Core.InteractionSystem; +using cohtml; +using System; +using System.Collections.Generic; + +namespace ml_dht +{ + static class Settings + { + enum ModSetting + { + Enabled = 0, + Mirrored, + Smoothing + } + + static bool ms_enabled = false; + static bool ms_mirrored = false; + static float ms_smoothing = 0.5f; + + static MelonLoader.MelonPreferences_Category ms_category = null; + static List ms_entries = null; + + static public event Action EnabledChange; + static public event Action MirroredChange; + static public event Action SmoothingChange; + + public static void Init() + { + ms_category = MelonLoader.MelonPreferences.CreateCategory("DHT"); + + ms_entries = new List(); + ms_entries.Add(ms_category.CreateEntry(ModSetting.Enabled.ToString(), false)); + ms_entries.Add(ms_category.CreateEntry(ModSetting.Mirrored.ToString(), false)); + ms_entries.Add(ms_category.CreateEntry(ModSetting.Smoothing.ToString(), 50)); + + 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_DHT_Call_InpSlider", new Action(OnSliderUpdate)); + ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpToggle", new Action(OnToggleUpdate)); + }; + 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("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString()); + }; + } + + static void Load() + { + ms_enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue; + ms_mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue; + ms_smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f; + } + + static void OnSliderUpdate(string p_name, string p_value) + { + if(Enum.TryParse(p_name, out ModSetting l_setting)) + { + switch(l_setting) + { + case ModSetting.Smoothing: + { + ms_smoothing = int.Parse(p_value) * 0.01f; + SmoothingChange?.Invoke(ms_smoothing); + } + break; + } + + ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value); + } + } + + 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; + + case ModSetting.Mirrored: + { + ms_mirrored = bool.Parse(p_value); + MirroredChange?.Invoke(ms_mirrored); + } + break; + } + + ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value); + } + } + + public static bool Enabled + { + get => ms_enabled; + } + public static bool Mirrored + { + get => ms_mirrored; + } + public static float Smoothing + { + get => ms_smoothing; + } + } +} diff --git a/ml_dht/TrackingData.cs b/ml_dht/TrackingData.cs new file mode 100644 index 0000000..d8e2e6a --- /dev/null +++ b/ml_dht/TrackingData.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.InteropServices; + +struct TrackingData +{ + public float m_headPositionX; // Not used yet + public float m_headPositionY; // Not used yet + public float m_headPositionZ; // Not used yet + public float m_headRotationX; + public float m_headRotationY; + public float m_headRotationZ; + public float m_headRotationW; + public float m_gazeX; // Range - [0;1], 0.5 - center + public float m_gazeY; // Range - [0;1], 0.5 - center + public float m_blink; // Range - [0;1], 1.0 - closed + public float m_mouthOpen; // Range - [0;1], not used yet + public float m_mouthShape; // Range - [0;1], not used yet + public float m_brows; // Range - [0;1], not used yet + + static public byte[] ToBytes(TrackingData p_faceData) + { + int l_size = Marshal.SizeOf(p_faceData); + byte[] l_arr = new byte[l_size]; + + IntPtr ptr = Marshal.AllocHGlobal(l_size); + Marshal.StructureToPtr(p_faceData, ptr, true); + Marshal.Copy(ptr, l_arr, 0, l_size); + Marshal.FreeHGlobal(ptr); + return l_arr; + } + + static public TrackingData ToObject(byte[] p_buffer) + { + TrackingData l_faceData = new TrackingData(); + + int l_size = Marshal.SizeOf(l_faceData); + IntPtr l_ptr = Marshal.AllocHGlobal(l_size); + + Marshal.Copy(p_buffer, 0, l_ptr, l_size); + + l_faceData = (TrackingData)Marshal.PtrToStructure(l_ptr, l_faceData.GetType()); + Marshal.FreeHGlobal(l_ptr); + + return l_faceData; + } +} diff --git a/ml_dht/ml_dht.csproj b/ml_dht/ml_dht.csproj new file mode 100644 index 0000000..8572b09 --- /dev/null +++ b/ml_dht/ml_dht.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {6DD89FC3-A974-4C39-A3EE-F60C24B17B5B} + Library + Properties + ml_dht + ml_dht + v4.7.2 + 512 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + False + False + + + False + False + + + False + C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + False + + + C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll + False + + + C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll + False + + + C:\Games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll + False + + + + + + + + + + False + False + + + False + + + + + + + + + + + + + + + + + + copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\" + + \ No newline at end of file diff --git a/ml_dht/ml_dht.csproj.user b/ml_dht/ml_dht.csproj.user new file mode 100644 index 0000000..2539084 --- /dev/null +++ b/ml_dht/ml_dht.csproj.user @@ -0,0 +1,6 @@ + + + + C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\ + + \ No newline at end of file diff --git a/ml_dht/resources/menu.js b/ml_dht/resources/menu.js new file mode 100644 index 0000000..84b5ce2 --- /dev/null +++ b/ml_dht/resources/menu.js @@ -0,0 +1,217 @@ +// Add settings +var g_modSettingsDHT = []; + +engine.on('updateModSettingDHT', function (_name, _value) { + for (var i = 0; i < g_modSettingsDHT.length; i++) { + if (g_modSettingsDHT[i].name == _name) { + g_modSettingsDHT[i].updateValue(_value); + break; + } + } +}); + +// Modified from original `inp` types, because I have no js knowledge to hook stuff +function inp_slider_mod_dht(_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 + } +} + +// Modified from original `inp` types, because I have no js knowledge to hook stuff +function inp_toggle_mod_dht(_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 + } +} + +// Add own menu +{ + let l_block = document.createElement('div'); + l_block.innerHTML = ` +
+
Desktop Head Tracking
+
+
+ +
+
Enabled:
+
+
+
+
+ +
+
Mirrored movement:
+
+
+
+
+ +
+
Movement smoothing:
+
+
+
+
+ `; + document.getElementById('settings-implementation').appendChild(l_block); + + // Update sliders in new menu block + let l_sliders = l_block.querySelectorAll('.inp_slider'); + for (var i = 0; i < l_sliders.length; i++) { + g_modSettingsDHT[g_modSettingsDHT.length] = new inp_slider_mod_dht(l_sliders[i], 'MelonMod_DHT_Call_InpSlider'); + } + + // Update toggles in new menu block + let l_toggles = l_block.querySelectorAll('.inp_toggle'); + for (var i = 0; i < l_toggles.length; i++) { + g_modSettingsDHT[g_modSettingsDHT.length] = new inp_toggle_mod_dht(l_toggles[i], 'MelonMod_DHT_Call_InpToggle'); + } +} diff --git a/ml_mods_cvr.sln b/ml_mods_cvr.sln index dd2eeb3..e7ec63f 100644 --- a/ml_mods_cvr.sln +++ b/ml_mods_cvr.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_amt", "ml_amt\ml_amt.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_sci", "ml_sci\ml_sci.csproj", "{E5481D41-196C-4241-AF26-6595EF1863C1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_dht", "ml_dht\ml_dht.csproj", "{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -45,6 +47,10 @@ Global {E5481D41-196C-4241-AF26-6595EF1863C1}.Debug|x64.Build.0 = Debug|x64 {E5481D41-196C-4241-AF26-6595EF1863C1}.Release|x64.ActiveCfg = Release|x64 {E5481D41-196C-4241-AF26-6595EF1863C1}.Release|x64.Build.0 = Release|x64 + {6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Debug|x64.ActiveCfg = Debug|x64 + {6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Debug|x64.Build.0 = Debug|x64 + {6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Release|x64.ActiveCfg = Release|x64 + {6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE