Merge branch 'master' into experimental

This commit is contained in:
SDraw 2024-01-26 19:18:36 +03:00
commit c169c7336a
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
155 changed files with 16128 additions and 11723 deletions

View file

@ -1,20 +1,24 @@
Merged set of MelonLoader mods for ChilloutVR. Merged set of MelonLoader mods for ChilloutVR.
**Table for game build 2022r171:** **Table for game build 2023r173:**
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | | Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) |
|:---------:|:----------:|:--------------:| :----------------------------------------------------------------| |:---------:|:----------:|:--------------:| :----------------------------------------------------------------|
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | - | ✔ Yes<br>:warning:Broken | | [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | 1.3.6 [:arrow_down:](../../releases/latest/download/ml_amt.dll)| ✔ Yes<br>:hourglass: Update review |
| [Desktop Head Tracking](/ml_dht/README.md)| ml_dht | - | ✔ Yes<br>:warning:Broken | | [Avatar Synced Look](/ml_asl/README.md) | ml_asl | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_asl.dll)| ✔ Yes |
| [Desktop Reticle Switch](/ml_drs/README.md)| ml_drs | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_drs.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Desktop Head Tracking](/ml_dht/README.md) | ml_dht | 1.2.0 [:arrow_down:](../../releases/latest/download/ml_dht.dll) | ✔ Yes (`Retired` group)<br>:hourglass: Update review |
| [Extended Game Notifications](/ml_egn/README.md) | ml_egn | 1.0.3 [:arrow_down:](../../releases/latest/download/ml_egn.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.5 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes |
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.0 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.9 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes |
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes |
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Player Ragdoll Mod](/ml_prm/README.md) | ml_prm | 1.1.2 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass: Update review |
| [Player Ragdoll Mod](/ml_prm/README.md)| ml_prm | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review | | [Players Instance Notifier](/ml_pin/README.md) | ml_pin | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_ml_pin.dll)| ✔ Yes<br>:hourglass: Update review |
| [Vive Extended Input](/ml_vei/README.md) | ml_vei | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_vei.dll)| ✔ Yes |
**Archived mods:** **Archived mods:**
| Full name | Short name | Notes | | Full name | Short name | Notes |
|:---------:|:----------:|-------| |:---------:|:----------:|-------|
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications` | Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications` |
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update | Desktop Reticle Switch | ml_drs | Boring functionality |
| Extended Game Notifications | ml_egn | In-game feature sine 2023r172 update |
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update |
| Game Main Fixes | ml_gmf | In-game feature sine 2023r172 update |
| Server Connection Info | ml_sci | Superseded by `Extended Game Notifications` | Server Connection Info | ml_sci | Superseded by `Extended Game Notifications`

BIN
archived/ml_aci/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

58
archived/ml_aci/Main.cs Normal file
View file

@ -0,0 +1,58 @@
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Networking;
using ABI_RC.Core.Util;
using DarkRift;
using System.Reflection;
namespace ml_aci
{
public class AvatarChangeInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(AssetManagement).GetMethod(nameof(AssetManagement.LoadLocalAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnLocalAvatarLoad), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnPropSpawned), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnLocalAvatarLoad()
{
try
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerPushNotification("Avatar changed", 1f);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPropSpawned()
{
try
{
if(ViewManager.Instance != null)
{
if((NetworkManager.Instance != null) && (NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected))
ViewManager.Instance.TriggerPushNotification("Prop spawned", 1f);
else
ViewManager.Instance.TriggerAlert("Prop Error", "Not connected to live instance", -1, true);
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.3", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: AssemblyTitle("AvatarChangeInfo")]
[assembly: AssemblyVersion("1.0.3")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: MelonLoader.MelonInfo(typeof(ml_aci.AvatarChangeInfo), "AvatarChangeInfo", "1.0.3", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -0,0 +1,9 @@
# Avatar Change Info
This mod shows simple main menu popup upon local player avatar change and prop spawn.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_aci.dll` in `Mods` folder of game

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_aci</RootNamespace>
<AssemblyName>ml_aci</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DarkRift">
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "1.0.1", "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)]

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.3", "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)]

313
archived/ml_fpt/Main.cs Normal file
View file

@ -0,0 +1,313 @@
using ABI.CCK.Scripts;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace ml_fpt
{
public class FourPointTracking : MelonLoader.MelonMod
{
static FourPointTracking ms_instance = null;
bool m_ready = false;
IndexIK m_indexIK = null;
RootMotion.FinalIK.VRIK m_vrIK = null;
RuntimeAnimatorController m_runtimeAnimator = null;
List<CVRAdvancedSettingsFileProfileValue> m_aasParameters = null;
bool m_calibrationActive = false;
object m_calibrationTask = null;
int m_hipsTrackerIndex = -1;
Transform m_hips = null;
Dictionary<string, Tuple<Vector3, Quaternion>> m_avatarCalibrations = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
m_avatarCalibrations = new Dictionary<string, Tuple<Vector3, Quaternion>>();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForMainMenuView());
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
System.Collections.IEnumerator WaitForMainMenuView()
{
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.RegisterForEvent("MelonMod_FPT_Action_Calibrate", new Action(this.StartCalibration));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
};
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_indexIK = PlayerSetup.Instance.gameObject.GetComponent<IndexIK>();
m_ready = true;
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_ready = false;
m_aasParameters?.Clear();
m_aasParameters = null;
m_avatarCalibrations?.Clear();
m_avatarCalibrations = null;
m_hipsTrackerIndex = -1;
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_calibrationTask = null;
}
void StartCalibration()
{
if(m_ready && !m_calibrationActive && PlayerSetup.Instance._inVr && !PlayerSetup.Instance.avatarIsLoading && PlayerSetup.Instance._animator.isHuman && !BodySystem.isCalibrating && !BodySystem.isCalibratedAsFullBody)
{
m_hipsTrackerIndex = GetHipsTracker();
if(m_hipsTrackerIndex != -1)
{
m_avatarCalibrations.Remove(MetaPort.Instance.currentAvatarGuid);
m_runtimeAnimator = PlayerSetup.Instance._animator.runtimeAnimatorController;
m_aasParameters = PlayerSetup.Instance.animatorManager.GetAdditionalSettingsCurrent();
PlayerSetup.Instance._animator.runtimeAnimatorController = PlayerSetup.Instance.tPoseAnimatorController;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, PlayerSetup.Instance.tPoseAnimatorController);
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(m_vrIK != null)
m_vrIK.solver.OnPreUpdate += this.OverrideIKWeight;
IKSystem.Instance.leftHandModel.SetActive(true);
IKSystem.Instance.rightHandModel.SetActive(true);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(true);
CVR_InteractableManager.enableInteractions = false;
m_calibrationActive = true;
m_calibrationTask = MelonLoader.MelonCoroutines.Start(CalibrationTask());
ViewManager.Instance.ForceUiStatus(false);
ShowHudNotification("Calibration started");
}
else
ShowMenuAlert("No hips tracker detected. Check if tracker has waist role in SteamVR settings.");
}
else
ShowMenuAlert("Calibraton requirements aren't met: be in VR, be not in FBT or avatar calibration, humanoid avatar");
}
System.Collections.IEnumerator CalibrationTask()
{
while(m_calibrationActive)
{
if(m_vrIK != null)
m_vrIK.enabled = false;
m_indexIK.enabled = false;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(true, m_hips);
if((CVRInputManager.Instance.interactLeftValue > 0.9f) && (CVRInputManager.Instance.interactRightValue > 0.9f))
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.position = m_hips.position;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.rotation = m_hips.rotation;
m_avatarCalibrations.Add(
MetaPort.Instance.currentAvatarGuid,
new Tuple<Vector3, Quaternion>(
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localPosition,
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localRotation
)
);
if(m_vrIK != null)
{
m_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target;
m_vrIK.solver.spine.pelvisPositionWeight = 1f;
m_vrIK.solver.spine.pelvisRotationWeight = 1f;
m_vrIK.solver.OnPreUpdate -= this.OverrideIKWeight;
m_vrIK.solver.IKPositionWeight = 1f;
m_vrIK.enabled = true;
}
m_indexIK.enabled = true;
PlayerSetup.Instance._animator.runtimeAnimatorController = m_runtimeAnimator;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, m_runtimeAnimator);
if(m_aasParameters != null)
{
foreach(var l_param in m_aasParameters)
{
PlayerSetup.Instance.animatorManager.SetAnimatorParameter(l_param.name, l_param.value);
}
}
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration completed");
}
yield return null;
}
m_calibrationTask = null; // Idk if it's safe or not
}
void OverrideIKWeight()
{
if(m_calibrationActive)
{
m_vrIK.solver.IKPositionWeight = 0f;
}
}
void Reset()
{
m_vrIK = null;
m_runtimeAnimator = null;
m_aasParameters = null;
m_calibrationActive = false;
m_calibrationTask = null;
m_hipsTrackerIndex = -1;
m_hips = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_calibrationActive)
{
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_indexIK.enabled = true;
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
if(m_hipsTrackerIndex != -1)
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
}
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration canceled");
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_ready && PlayerSetup.Instance._inVr && PlayerSetup.Instance._animator.isHuman && !VRTrackerManager.Instance.CheckFullBody())
{
int l_hipsTracker = GetHipsTracker();
if((l_hipsTracker != -1) && m_avatarCalibrations.TryGetValue(MetaPort.Instance.currentAvatarGuid, out var l_stored))
{
var l_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(l_vrIK != null)
{
l_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[l_hipsTracker].target;
l_vrIK.solver.spine.pelvisPositionWeight = 1f;
l_vrIK.solver.spine.pelvisRotationWeight = 1f;
l_vrIK.solver.spine.pelvisTarget.localPosition = l_stored.Item1;
l_vrIK.solver.spine.pelvisTarget.localRotation = l_stored.Item2;
ShowHudNotification("Applied saved calibration");
}
}
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void ShowHudNotification(string p_message)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("4-Point Tracking", p_message);
}
static void ShowMenuAlert(string p_message)
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerAlert("4-Point Tracking", p_message, 0, false);
}
static int GetHipsTracker()
{
int l_result = -1;
for(int i = 0; i < PlayerSetup.Instance._trackerManager.trackerNames.Length; i++)
{
if((PlayerSetup.Instance._trackerManager.trackerNames[i] == "vive_tracker_waist") && PlayerSetup.Instance._trackerManager.trackers[i].active)
{
l_result = i;
break;
}
}
return l_result;
}
}
}

View file

@ -0,0 +1,10 @@
using System.Reflection;
[assembly: AssemblyTitle("FourPointTracking")]
[assembly: AssemblyVersion("1.0.9")]
[assembly: AssemblyFileVersion("1.0.9")]
[assembly: MelonLoader.MelonInfo(typeof(ml_fpt.FourPointTracking), "FourPointTracking", "1.0.9", "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)]

18
archived/ml_fpt/README.md Normal file
View file

@ -0,0 +1,18 @@
# Four Point Tracking
This mod adds ability to use 4-point tracking.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_fpt.dll` in `Mods` folder of game
# Usage
* Be sure that your tracker role is set to `Hips` in SteamVR
* Go to `Settings - Implementation - 4-Point Tracking` and press `Calibrate` button
* Adjust your tracker in a similar way as in FBT calibration
* Press trigger on both controllers
# Notes
* Will be deprecated soon
* Calibration is saved per avatar for game session.
* AAS parameters are restored after calibration.

View file

@ -2,7 +2,7 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
namespace ml_amt namespace ml_fpt
{ {
static class Scripts static class Scripts
{ {

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_fpt</RootNamespace>
<AssemblyName>ml_fpt</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>C:\Games\Steam\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>
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>C:\Games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine.AnimationModule">
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,12 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">4-Point Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="action-btn button" onclick="engine.trigger('MelonMod_FPT_Action_Calibrate');"><img src="gfx/recalibrate.svg">Calibrate</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
}

View file

@ -0,0 +1,34 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AnimationOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
static void OnOverride_Prefix(ref CVRAnimatorManager __instance)
{
try
{
if(__instance.animator != null)
__instance.animator.WriteDefaultValues();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -0,0 +1,48 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AvatarOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupAvatarGeneral", BindingFlags.NonPublic | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnSetupAvatarGeneral_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
p_instance.Patch(
typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnPuppetAvatarInstantiated_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnSetupAvatarGeneral_Prefix(CVRAvatar ____avatarDescriptor)
{
try
{
if(____avatarDescriptor.overrides != null)
____avatarDescriptor.overrides = UnityEngine.Object.Instantiate(____avatarDescriptor.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPuppetAvatarInstantiated_Prefix(ref PuppetMaster __instance)
{
try
{
CVRAvatar l_avatar = __instance.avatarObject.GetComponent<CVRAvatar>();
if((l_avatar != null) && (l_avatar.overrides != null))
l_avatar.overrides = UnityEngine.Object.Instantiate(l_avatar.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,31 @@
using ABI_RC.Core.Player;
using System.Collections;
namespace ml_gmf.Fixes
{
static class PostProccesVolumes
{
internal static void Init()
{
MelonLoader.MelonCoroutines.Start(FixVRCameraVolumeTarget());
}
static IEnumerator FixVRCameraVolumeTarget()
{
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.vrCamera == null)
yield return null;
UnityEngine.Rendering.PostProcessing.PostProcessLayer l_layer = null;
while(l_layer == null)
{
l_layer = PlayerSetup.Instance.vrCamera.GetComponent<UnityEngine.Rendering.PostProcessing.PostProcessLayer>();
yield return null;
}
l_layer.volumeTrigger = PlayerSetup.Instance.vrCamera.transform;
}
}
}

View file

@ -0,0 +1,60 @@
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.InputManagement.XR;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class ViveControls
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRXRModule).GetMethod("Update_Gestures_Vive", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnViveGesturesUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRXRModule).GetMethod(nameof(CVRXRModule.Reset), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Prefix), BindingFlags.NonPublic | BindingFlags.Static)),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnViveGesturesUpdate_Postfix(ref CVRXRModule __instance)
{
try
{
float l_mag = ((!__instance.HasEmoteOverride) ? __instance.Primary2DAxis : __instance.EmoteOverride).magnitude;
if(__instance.ViveDirectionPressed && (l_mag >= CVRInputManager.VrViveGestureDeadZone))
{
if(__instance.Grip > 0.5f)
{
__instance.GestureRaw = -1f;
__instance.Gesture = -1f;
}
else
{
__instance.GestureRaw = __instance.Trigger;
__instance.Gesture = __instance.Trigger;
}
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCVRXRModuleReset_Prefix(ref CVRXRModule __instance, out bool __state)
{
__state = __instance.ViveDirectionPressed;
}
static void OnCVRXRModuleReset_Postfix(ref CVRXRModule __instance, bool __state)
{
if((__instance.Type == EXRControllerType.Vive) && CVRInputManager._moduleXR.ViveAdvancedControls)
__instance.ViveDirectionPressed = __state;
}
}
}

13
archived/ml_gmf/Main.cs Normal file
View file

@ -0,0 +1,13 @@
namespace ml_gmf
{
public class GameMainFixes : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Fixes.ViveControls.Init(HarmonyInstance);
Fixes.AvatarOverrides.Init(HarmonyInstance);
Fixes.PostProccesVolumes.Init();
Fixes.AnimationOverrides.Init(HarmonyInstance);
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_gmf.GameMainFixes), "GameMainFixes", "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)]

17
archived/ml_gmf/README.md Normal file
View file

@ -0,0 +1,17 @@
# Game Main Fixes
This mod fixes some issues that are present in game
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_gmf.dll` in `Mods` folder of game
# Implemented fixes
* Fix of broken `Vive Advanced Controls` game input option
* Additional feature: Disables gestures when moving with Vive controllers
* Fix of post-processing layer volume trigger for VR camera ([feedback post](https://feedback.abinteractive.net/p/2023r171ex1-post-process-volume-effects-are-applied-based-on-playspace-center-instead-of-camera-s-in-vr-mode))
* Fix of shared `AnimatorOverrideController` between same avatars that leads to broken avatar animator
* Fix of animation replacement (chairs, etc.) that leads to broken avatar animator ([feedback post](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs))
# Notes
All these fixes are implemented natively in 2023r172ex4 build.

View file

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>GameMainFixes</PackageId>
<Version>1.0.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>GameMainFixes</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="PlayerRagdollMod.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Unity.Postprocessing.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

BIN
archived/ml_sci/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

31
archived/ml_sci/Main.cs Normal file
View file

@ -0,0 +1,31 @@
using ABI_RC.Core.UI;
using DarkRift.Client;
using System.Reflection;
namespace ml_sci
{
public class ServerConnectionInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(ABI_RC.Core.Networking.NetworkManager).GetMethod("OnGameNetworkConnectionClosed", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ServerConnectionInfo).GetMethod(nameof(OnGameNetworkConnectionClosed), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnGameNetworkConnectionClosed(object __0, DisconnectedEventArgs __1)
{
try
{
if((CohtmlHud.Instance != null) && (__1 != null) && (!__1.LocalDisconnect))
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client", "Connection lost", (__1.Error != System.Net.Sockets.SocketError.Success) ? ("Reason: " + __1.Error.ToString()) : "");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: AssemblyTitle("ServerConnectionInfo")]
[assembly: AssemblyVersion("1.0.2")]
[assembly: AssemblyFileVersion("1.0.2")]
[assembly: MelonLoader.MelonInfo(typeof(ml_sci.ServerConnectionInfo), "ServerConnectionInfo", "1.0.2", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -0,0 +1,9 @@
# Server Connection Info
This mod shows HUD notification upon server disconnection.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_sci.dll` in `Mods` folder of game

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E5481D41-196C-4241-AF26-6595EF1863C1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_sci</RootNamespace>
<AssemblyName>ml_sci</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DarkRift.Client, Version=2.4.5.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\DarkRift.Client.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ReferencePath>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
</PropertyGroup>
</Project>

283
js/mods_extension.js Normal file
View file

@ -0,0 +1,283 @@
if (typeof modsExtension === 'undefined') {
window.modsExtension = []
// UI elements, modified from original `inp` types, because I have no js knowledge to hook stuff
modsExtension.createToggle = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
var self = uiElement;
uiElement.mouseDown = function (_e) {
self.value = self.value == "True" ? "False" : "True";
self.updateState();
}
uiElement.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', uiElement.mouseDown);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
}
uiElement.updateValue(uiElement.value);
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createSlider = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.minValue = parseFloat(_obj.getAttribute('data-min'));
uiElement.maxValue = parseFloat(_obj.getAttribute('data-max'));
uiElement.percent = 0;
uiElement.value = parseFloat(_obj.getAttribute('data-current'));
uiElement.dragActive = false;
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
uiElement.stepSize = _obj.getAttribute('data-stepSize') || 0;
uiElement.format = _obj.getAttribute('data-format') || '{value}';
var self = uiElement;
if (uiElement.stepSize != 0)
uiElement.value = Math.round(uiElement.value / uiElement.stepSize) * uiElement.stepSize;
else
uiElement.value = Math.round(uiElement.value);
uiElement.valueLabelBackground = document.createElement('div');
uiElement.valueLabelBackground.className = 'valueLabel background';
uiElement.valueLabelBackground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.obj.appendChild(uiElement.valueLabelBackground);
uiElement.valueBar = document.createElement('div');
uiElement.valueBar.className = 'valueBar';
uiElement.valueBar.setAttribute('style', 'width: ' + (((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.obj.appendChild(uiElement.valueBar);
uiElement.valueLabelForeground = document.createElement('div');
uiElement.valueLabelForeground.className = 'valueLabel foreground';
uiElement.valueLabelForeground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.valueBar.appendChild(uiElement.valueLabelForeground);
uiElement.mouseDown = function (_e) {
self.dragActive = true;
self.mouseMove(_e, false);
}
uiElement.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();
}
}
uiElement.mouseUp = function (_e) {
self.mouseMove(_e, true);
self.dragActive = false;
}
_obj.addEventListener('mousedown', uiElement.mouseDown);
document.addEventListener('mousemove', uiElement.mouseMove);
document.addEventListener('mouseup', uiElement.mouseUp);
uiElement.getValue = function () {
return self.value;
}
uiElement.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();
}
uiElement.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: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createDropdown = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.options = _obj.getAttribute('data-options').split(',');
uiElement.name = _obj.id;
uiElement.opened = false;
uiElement.keyValue = [];
uiElement.type = _obj.getAttribute('data-type');
uiElement.optionElements = [];
var self = uiElement;
uiElement.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
uiElement.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
uiElement.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
uiElement.list = document.createElement('div');
uiElement.list.className = 'valueList';
uiElement.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
uiElement.valueElement = document.createElement('div');
uiElement.valueElement.className = 'dropdown-value';
uiElement.updateOptions();
uiElement.obj.appendChild(uiElement.valueElement);
uiElement.obj.appendChild(uiElement.list);
uiElement.valueElement.addEventListener('mousedown', uiElement.openClick);
document.addEventListener('mousedown', uiElement.globalClose);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
uiElement.setOptions = function (options) {
self.options = options;
}
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue,
updateOptions: uiElement.updateOptions,
setOptions: uiElement.setOptions
}
};
modsExtension.settings = []
modsExtension.settings.data = []; // [category] -> [entry]
modsExtension.addSetting = function (_category, _entry, _obj) {
if (modsExtension.settings.data[_category] === undefined)
modsExtension.settings.data[_category] = []
modsExtension.settings.data[_category][_entry] = _obj
};
modsExtension.updateSetting = function (_category, _entry, _value) {
if ((modsExtension.settings.data[_category] !== undefined) && (modsExtension.settings.data[_category][_entry] !== undefined))
modsExtension.settings.data[_category][_entry].updateValue(_value);
};
engine.on('updateModSetting', modsExtension.updateSetting);
}

View file

@ -8,7 +8,6 @@ namespace ml_amt
{ {
public enum ParameterType public enum ParameterType
{ {
Upright,
GroundedRaw, GroundedRaw,
Moving Moving
} }
@ -32,7 +31,7 @@ namespace ml_amt
if(l_regex.IsMatch(l_param.name)) if(l_regex.IsMatch(l_param.name))
{ {
m_hash = l_param.nameHash; m_hash = l_param.nameHash;
m_sync = (l_param.name[0] != '#'); m_sync = !l_param.name.StartsWith('#');
m_innerType = l_param.type; m_innerType = l_param.type;
break; break;
} }
@ -43,10 +42,6 @@ namespace ml_amt
{ {
switch(m_type) switch(m_type)
{ {
case ParameterType.Upright:
SetFloat(p_tweaker.GetUpright());
break;
case ParameterType.GroundedRaw: case ParameterType.GroundedRaw:
SetBoolean(p_tweaker.GetGroundedRaw()); SetBoolean(p_tweaker.GetGroundedRaw());
break; break;

View file

@ -1,71 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ml_amt.Fixes
{
class AnimatorAnalyzer
{
bool m_enabled = true;
List<AnimatorControllerParameter> m_parameters = null;
public void AnalyzeFrom(Animator p_animator)
{
m_enabled = p_animator.enabled;
m_parameters = p_animator.parameters?.ToList();
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
l_param.defaultBool = p_animator.GetBool(l_param.nameHash);
break;
case AnimatorControllerParameterType.Float:
l_param.defaultFloat = p_animator.GetFloat(l_param.nameHash);
break;
case AnimatorControllerParameterType.Int:
l_param.defaultInt = p_animator.GetInteger(l_param.nameHash);
break;
}
}
}
}
public void ApplyTo(Animator p_animator)
{
p_animator.enabled = m_enabled;
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
p_animator.SetBool(l_param.nameHash, l_param.defaultBool);
break;
case AnimatorControllerParameterType.Float:
p_animator.SetFloat(l_param.nameHash, l_param.defaultFloat);
break;
case AnimatorControllerParameterType.Int:
p_animator.SetInteger(l_param.nameHash, l_param.defaultInt);
break;
case AnimatorControllerParameterType.Trigger:
{
if(l_param.defaultBool)
p_animator.SetTrigger(l_param.nameHash);
}
break;
}
}
}
}
public bool IsEnabled() => m_enabled;
}
}

View file

@ -1,60 +0,0 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_amt.Fixes
{
static class AnimatorOverrideControllerFix
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
// AAS overriding fix
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
// AnimatorOverrideController runtime animation replacement fix
static void OnOverride_Prefix(ref CVRAnimatorManager __instance, out AnimatorAnalyzer __state)
{
__state = new AnimatorAnalyzer();
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.AnalyzeFrom(__instance.animator);
if(__state.IsEnabled())
__instance.animator.enabled = false;
__instance.animator.WriteDefaultValues();
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnOverride_Postfix(ref CVRAnimatorManager __instance, AnimatorAnalyzer __state)
{
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.ApplyTo(__instance.animator);
if(__state.IsEnabled())
__instance.animator.Update(0f);
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,114 +0,0 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.MovementSystem;
using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace ml_amt.Fixes
{
static class MovementJumpFix
{
static FieldInfo ms_avatarHeight = typeof(PlayerSetup).GetField("_avatarHeight", BindingFlags.NonPublic | BindingFlags.Instance);
static float ms_playerHeight = 1f;
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRWorld).GetMethod("SetupWorldRules", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnWorldRulesSetup_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
Settings.ScaledJumpChange += OnScaledJumpChange;
MelonLoader.MelonCoroutines.Start(WaitForGameSettings());
}
static IEnumerator WaitForGameSettings()
{
while(MetaPort.Instance == null)
yield return null;
while(MetaPort.Instance.settings == null)
yield return null;
ms_playerHeight = MetaPort.Instance.settings.GetSettingInt("GeneralPlayerHeight") * 0.01f;
MetaPort.Instance.settings.settingIntChanged.AddListener(OnGameSettingIntChange);
}
// Patches
static void OnSetupAvatar_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnWorldRulesSetup_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupIKScaling_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
// Mod settings
static void OnScaledJumpChange(bool p_state)
{
SetScaledJump(p_state);
}
// Game settings
static void OnGameSettingIntChange(string p_name, int p_value)
{
if(p_name == "GeneralPlayerHeight")
{
ms_playerHeight = p_value * 0.01f;
}
}
// Arbitrary
static void SetScaledJump(bool p_state)
{
if(Utils.IsWorldSafe())
{
if(p_state)
MovementSystem.Instance.jumpHeight = Mathf.Clamp(Utils.GetWorldJumpHeight() * ((float)ms_avatarHeight.GetValue(PlayerSetup.Instance) / ms_playerHeight), float.MinValue, Utils.GetWorldMovementLimit());
else
MovementSystem.Instance.jumpHeight = Utils.GetWorldJumpHeight();
}
}
}
}

View file

@ -1,5 +1,4 @@
using ABI.CCK.Components; using ABI_RC.Core.Player;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.IK.SubSystems;
using System; using System;
using System.Collections; using System.Collections;
@ -35,12 +34,12 @@ namespace ml_amt
null, null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
// Fixes
Fixes.AnimatorOverrideControllerFix.Init(HarmonyInstance);
Fixes.MovementJumpFix.Init(HarmonyInstance);
ModSupporter.Init();
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
} }
@ -50,12 +49,6 @@ namespace ml_amt
yield return null; yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>(); m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
m_localTweaker.SetCrouchLimit(Settings.CrouchLimit);
m_localTweaker.SetProneLimit(Settings.ProneLimit);
m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly);
m_localTweaker.SetIKOverrideJump(Settings.IKOverrideJump);
m_localTweaker.SetDetectEmotes(Settings.DetectEmotes);
m_localTweaker.SetFollowHips(Settings.FollowHips);
} }
public override void OnDeinitializeMelon() public override void OnDeinitializeMelon()
@ -63,6 +56,8 @@ namespace ml_amt
if(ms_instance == this) if(ms_instance == this)
ms_instance = null; ms_instance = null;
if(m_localTweaker != null)
UnityEngine.Object.Destroy(m_localTweaker);
m_localTweaker = null; m_localTweaker = null;
} }
@ -107,5 +102,19 @@ namespace ml_amt
MelonLoader.MelonLogger.Error(l_exception); MelonLoader.MelonLogger.Error(l_exception);
} }
} }
static void OnPlayspaceScale_Postfix() => ms_instance?.OnPlayspaceScale();
void OnPlayspaceScale()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnPlayspaceScale();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
} }
} }

View file

@ -1,49 +0,0 @@
using System.Collections;
using System.Linq;
namespace ml_amt
{
static class ModSupporter
{
static bool ms_ragdollMod = false;
static bool ms_copycatMod = false;
public static void Init()
{
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerRagdollMod") != null)
MelonLoader.MelonCoroutines.Start(WaitForRagdollInstance());
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerMovementCopycat") != null)
MelonLoader.MelonCoroutines.Start(WaitForCopycatInstance());
}
// PlayerRagdollMod support
static IEnumerator WaitForRagdollInstance()
{
while(ml_prm.RagdollController.Instance == null)
yield return null;
ms_ragdollMod = true;
}
static bool IsRagdolled() => ml_prm.RagdollController.Instance.IsRagdolled();
// PlayerMovementCopycat support
static IEnumerator WaitForCopycatInstance()
{
while(ml_pmc.PoseCopycat.Instance == null)
yield return null;
ms_copycatMod = true;
}
static bool IsCopycating() => ml_pmc.PoseCopycat.Instance.IsActive();
public static bool SkipHipsOverride()
{
bool l_result = false;
l_result |= (ms_ragdollMod && IsRagdolled());
l_result |= (ms_copycatMod && IsCopycating());
return l_result;
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK; using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem; using ABI_RC.Systems.MovementSystem;
@ -29,7 +28,6 @@ namespace ml_amt
int m_locomotionLayer = 0; int m_locomotionLayer = 0;
float m_avatarScale = 1f; float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
Transform m_avatarHips = null;
bool m_inVR = false; bool m_inVR = false;
bool m_avatarReady = false; bool m_avatarReady = false;
@ -44,11 +42,10 @@ namespace ml_amt
bool m_detectEmotes = true; bool m_detectEmotes = true;
bool m_emoteActive = false; bool m_emoteActive = false;
bool m_followHips = true;
Vector3 m_hipsToPlayer = Vector3.zero;
Vector3 m_massCenter = Vector3.zero; Vector3 m_massCenter = Vector3.zero;
Transform m_ikLimits = null;
readonly List<AvatarParameter> m_parameters = null; readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker() internal MotionTweaker()
@ -61,26 +58,31 @@ namespace ml_amt
{ {
m_inVR = Utils.IsInVR(); m_inVR = Utils.IsInVR();
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
SetIKOverrideFly(Settings.IKOverrideFly);
SetIKOverrideJump(Settings.IKOverrideJump);
SetDetectEmotes(Settings.DetectEmotes);
Settings.CrouchLimitChange += this.SetCrouchLimit; Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit; Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly; Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump; Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes; Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.FollowHipsChange += this.SetFollowHips;
Settings.MassCenterChange += this.OnMassCenterChange; Settings.MassCenterChange += this.OnMassCenterChange;
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
} }
void OnDestroy() void OnDestroy()
{ {
m_vrIk = null;
m_ikLimits = null;
m_parameters.Clear();
Settings.CrouchLimitChange -= this.SetCrouchLimit; Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit; Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly; Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump; Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes; Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.FollowHipsChange -= this.SetFollowHips;
Settings.MassCenterChange -= this.OnMassCenterChange; Settings.MassCenterChange -= this.OnMassCenterChange;
} }
@ -92,11 +94,7 @@ namespace ml_amt
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw(); m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f); m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
if(m_avatarHips != null) UpdateIKLimits();
{
Vector4 l_hipsToPoint = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_avatarHips.GetMatrix()) * ms_pointVector;
m_hipsToPlayer.Set(l_hipsToPoint.x, 0f, l_hipsToPoint.z);
}
m_emoteActive = false; m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0)) if(m_detectEmotes && (m_locomotionLayer >= 0))
@ -126,10 +124,12 @@ namespace ml_amt
m_emoteActive = false; m_emoteActive = false;
m_moving = false; m_moving = false;
m_locomotionOverride = false; m_locomotionOverride = false;
m_hipsToPlayer = Vector3.zero;
m_avatarHips = null;
m_massCenter = Vector3.zero; m_massCenter = Vector3.zero;
m_ikLimits = null;
m_parameters.Clear(); m_parameters.Clear();
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(Settings.CrouchLimit);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(Settings.ProneLimit);
} }
internal void OnSetupAvatar() internal void OnSetupAvatar()
@ -137,15 +137,17 @@ namespace ml_amt
m_inVR = Utils.IsInVR(); m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>(); m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes"); m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarHips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y); m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters // Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Upright, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager)); m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager)); m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid()); m_parameters.RemoveAll(p => !p.IsValid());
// Avatar custom IK limits
m_ikLimits = PlayerSetup.Instance._avatar.transform.Find("[IKLimits]");
UpdateIKLimits();
// Apply VRIK tweaks // Apply VRIK tweaks
if(m_vrIk != null) if(m_vrIk != null)
{ {
@ -196,9 +198,17 @@ namespace ml_amt
BodySystem.TrackingLeftLegEnabled = false; BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false; BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true; BodySystem.TrackingLocomotionEnabled = true;
IKSystem.Instance.applyOriginalHipRotation = true;
} }
} }
internal void OnPlayspaceScale()
{
if((m_vrIk != null) && Settings.MassCenter)
m_vrIk.solver.locomotion.offset = m_massCenter * GetRelativeScale();
}
// IK events // IK events
void OnIKPreUpdate() void OnIKPreUpdate()
{ {
@ -238,14 +248,6 @@ namespace ml_amt
} }
} }
bool l_solverActive = !Mathf.Approximately(m_vrIk.solver.IKPositionWeight, 0f);
if(l_locomotionOverride && l_solverActive && m_followHips && (!m_moving || (PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarProneLimit)) && m_inVR && !BodySystem.isCalibratedAsFullBody && !ModSupporter.SkipHipsOverride())
{
m_vrIk.solver.plantFeet = false;
IKSystem.VrikRootController.enabled = false;
PlayerSetup.Instance._avatar.transform.localPosition = m_hipsToPlayer;
}
if(m_locomotionOverride && !l_locomotionOverride) if(m_locomotionOverride && !l_locomotionOverride)
m_vrIk.solver.Reset(); m_vrIk.solver.Reset();
m_locomotionOverride = l_locomotionOverride; m_locomotionOverride = l_locomotionOverride;
@ -263,11 +265,13 @@ namespace ml_amt
// Settings // Settings
internal void SetCrouchLimit(float p_value) internal void SetCrouchLimit(float p_value)
{ {
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Max(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarProneLimit); if(m_ikLimits == null)
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(p_value);
} }
internal void SetProneLimit(float p_value) internal void SetProneLimit(float p_value)
{ {
PlayerSetup.Instance.avatarProneLimit = Mathf.Min(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarCrouchLimit); if(m_ikLimits == null)
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(p_value);
} }
internal void SetIKOverrideFly(bool p_state) internal void SetIKOverrideFly(bool p_state)
{ {
@ -281,10 +285,6 @@ namespace ml_amt
{ {
m_detectEmotes = p_state; m_detectEmotes = p_state;
} }
internal void SetFollowHips(bool p_state)
{
m_followHips = p_state;
}
void OnMassCenterChange(bool p_state) void OnMassCenterChange(bool p_state)
{ {
if(m_vrIk != null) if(m_vrIk != null)
@ -297,6 +297,16 @@ namespace ml_amt
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f); return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
} }
void UpdateIKLimits()
{
if(m_ikLimits != null)
{
Vector3 l_values = m_ikLimits.localPosition;
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(l_values.x);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(l_values.y);
}
}
// Parameters access // Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright; public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw; public bool GetGroundedRaw() => m_groundedRaw;

View file

@ -1,7 +1,4 @@
using System.Reflection; [assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.3.6", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_prm", "ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -14,18 +14,10 @@ Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Prone limit:** defines prone limit; default value - `40`. * **Prone limit:** defines prone limit; default value - `40`.
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`. * **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`. * **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Follow hips on IK override:** adjusts avatar position to overcome animation snapping on IK override; default value - `true`.
* Note: Works best with animations that have root transform position (XZ) based on center of mass.
* Note: Made for four point tracking (head, hands and hips) in mind.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`. * **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2). * Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`. * **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes). * Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
#### Fixes/overhauls options
* **Scaled locomotion jump:** scales locomotion jump according to relation between your player settings height and current avatar height (includes avatar scale); default value - `false`.
* Note: Disabled in worlds that don't allow flight.
* **Fix animation overrides (chairs, etc.):** fixes animations overriding for avatars with AAS; default value - `true`.
* Note: This option is made to address [broken animator in chairs and combat worlds issue](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs).
Available additional parameters for AAS animator: Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0]. * **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0].
@ -38,6 +30,4 @@ Available additional parameters for AAS animator:
Additional mod's behaviour: Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips). * Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
* Avatars can have controlled IK crouch and prone limits. For that create `[IKLimits]` GameObject parented to avatar's root. Its local X and Y positions will be used as crouch and prone limits respectively and can be changed via animations. Values should be in range of [0;1].
# NOTE
This is testing update for game build r171, not ready for massive usage yet!

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_amt
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.InteractionSystem;
using cohtml;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -14,10 +13,7 @@ namespace ml_amt
IKOverrideFly, IKOverrideFly,
IKOverrideJump, IKOverrideJump,
DetectEmotes, DetectEmotes,
FollowHips, MassCenter
ScaledJump,
MassCenter,
OverrideFix
}; };
public static float CrouchLimit { get; private set; } = 0.75f; public static float CrouchLimit { get; private set; } = 0.75f;
@ -25,10 +21,7 @@ namespace ml_amt
public static bool IKOverrideFly { get; private set; } = true; public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true; public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true; public static bool DetectEmotes { get; private set; } = true;
public static bool FollowHips { get; private set; } = true;
public static bool MassCenter { get; private set; } = true; public static bool MassCenter { get; private set; } = true;
public static bool ScaledJump { get; private set; } = false;
public static bool OverrideFix { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null; static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null; static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -38,10 +31,7 @@ namespace ml_amt
static public event Action<bool> IKOverrideFlyChange; static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange; static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange; static public event Action<bool> DetectEmotesChange;
static public event Action<bool> FollowHipsChange;
static public event Action<bool> MassCenterChange; static public event Action<bool> MassCenterChange;
static public event Action<bool> ScaledJumpChange;
static public event Action<bool> OverrideFixChange;
internal static void Init() internal static void Init()
{ {
@ -54,10 +44,7 @@ namespace ml_amt
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly), ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump), ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes), ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.FollowHips.ToString(), FollowHips), ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter)
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter),
ms_category.CreateEntry(ModSetting.ScaledJump.ToString(), ScaledJump),
ms_category.CreateEntry(ModSetting.OverrideFix.ToString(), OverrideFix)
}; };
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f; CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
@ -65,10 +52,7 @@ namespace ml_amt
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue; IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue; IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue; DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
FollowHips = (bool)ms_entries[(int)ModSetting.FollowHips].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue; MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue;
ScaledJump = (bool)ms_entries[(int)ModSetting.ScaledJump].BoxedValue;
OverrideFix = (bool)ms_entries[(int)ModSetting.OverrideFix].BoxedValue;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi()); MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
} }
@ -84,14 +68,15 @@ namespace ml_amt
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () => ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{ {
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_Call_InpSlider", new Action<string, string>(OnSliderUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_Call_InpToggle", new Action<string, string>(OnToggleUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
}; };
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) => ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{ {
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js")); ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries) foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingAMT", l_entry.DisplayName, l_entry.GetValueAsString()); ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
}; };
} }
@ -147,33 +132,12 @@ namespace ml_amt
} }
break; break;
case ModSetting.FollowHips:
{
FollowHips = bool.Parse(p_value);
FollowHipsChange?.Invoke(FollowHips);
}
break;
case ModSetting.MassCenter: case ModSetting.MassCenter:
{ {
MassCenter = bool.Parse(p_value); MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter); MassCenterChange?.Invoke(MassCenter);
} }
break; break;
case ModSetting.ScaledJump:
{
ScaledJump = bool.Parse(p_value);
ScaledJumpChange?.Invoke(ScaledJump);
}
break;
case ModSetting.OverrideFix:
{
OverrideFix = bool.Parse(p_value);
OverrideFixChange?.Invoke(OverrideFix);
}
break;
} }
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value); ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);

View file

@ -12,28 +12,15 @@ namespace ml_amt
static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance); static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance); static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance); static readonly FieldInfo ms_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance);
static MethodInfo ms_getSineKeyframes = typeof(IKSolverVR).GetMethod("GetSineKeyframes", BindingFlags.NonPublic | BindingFlags.Static); static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded); public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsGrounded(this MovementSystem p_instance) => (bool)ms_grounded.GetValue(MovementSystem.Instance);
public static bool IsGroundedRaw(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(MovementSystem.Instance); public static bool IsGroundedRaw(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(MovementSystem.Instance);
public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance); public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance);
public static Keyframe[] GetSineKeyframes(float p_mag)
{
return (Keyframe[])ms_getSineKeyframes.Invoke(null, new object[] { p_mag });
}
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying); public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldJumpHeight()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
l_result = CVRWorld.Instance.jumpHeight;
return l_result;
}
public static float GetWorldMovementLimit() public static float GetWorldMovementLimit()
{ {
float l_result = 1f; float l_result = 1f;
@ -47,7 +34,7 @@ namespace ml_amt
return l_result; return l_result;
} }
public static void ExecuteScript(this CohtmlControlledViewDisposable p_instance, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_instance))?.ExecuteScript(p_script); static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
// Engine extensions // Engine extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false) public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
@ -6,7 +6,7 @@
<Company>None</Company> <Company>None</Company>
<Product>AvatarMotionTweaker</Product> <Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId> <PackageId>AvatarMotionTweaker</PackageId>
<Version>1.2.9</Version> <Version>1.3.6</Version>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName> <AssemblyName>ml_amt</AssemblyName>
</PropertyGroup> </PropertyGroup>
@ -16,6 +16,8 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -24,61 +26,67 @@
<ItemGroup> <ItemGroup>
<None Remove="AvatarMotionTweaker.json" /> <None Remove="AvatarMotionTweaker.json" />
<None Remove="resources\menu.js" /> <None Remove="resources\mod_menu.js" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="resources\menu.js" /> <EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="0Harmony"> <Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Assembly-CSharp-firstpass"> <Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="cohtml.Net"> <Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Cohtml.Runtime"> <Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="MelonLoader"> <Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private> <Private>false</Private>
</Reference> <SpecificVersion>false</SpecificVersion>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_prm">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_prm.dll</HintPath>
<Private>false</Private>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.AnimationModule"> <Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.PhysicsModule"> <Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View file

@ -1,261 +0,0 @@
// Add settings
var g_modSettingsAMT = [];
engine.on('updateModSettingAMT', function (_name, _value) {
for (var i = 0; i < g_modSettingsAMT.length; i++) {
if (g_modSettingsAMT[i].name == _name) {
g_modSettingsAMT[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_slider_mod_amt(_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
}
}
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_amt(_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 = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while jumping: </div>
<div class ="option-input">
<div id="IKOverrideJump" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Follow hips on IK override: </div>
<div class ="option-input">
<div id="FollowHips" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<h4><p style="color: #7F7F7F">Avatar independent game fixes/overhauls</p></h4><br>
<div class ="row-wrapper">
<div class ="option-caption">Scaled locomotion jump: </div>
<div class ="option-input">
<div id="ScaledJump" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fix animator overrides (chairs, etc.): </div>
<div class ="option-input">
<div id="OverrideFix" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').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_modSettingsAMT[g_modSettingsAMT.length] = new inp_slider_mod_amt(l_sliders[i], 'MelonMod_AMT_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_modSettingsAMT[g_modSettingsAMT.length] = new inp_toggle_mod_amt(l_toggles[i], 'MelonMod_AMT_Call_InpToggle');
}
}

View file

@ -0,0 +1,54 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('AMT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_AMT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('AMT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_AMT'));
}

25
ml_asl/Main.cs Normal file
View file

@ -0,0 +1,25 @@
using ABI_RC.Core.Player;
using System.Reflection;
namespace ml_asl
{
public class AvatarSyncedLook : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("UpdatePlayerAvatarMovementData", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarSyncedLook).GetMethod(nameof(OnPlayerAvatarMovementDataUpdate_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnPlayerAvatarMovementDataUpdate_Postfix(ref PlayerSetup __instance, PlayerAvatarMovementData ____playerAvatarMovementData)
{
if(Settings.Enabled && (__instance.eyeMovement != null))
____playerAvatarMovementData.EyeTrackingOverride = true;
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_asl.AvatarSyncedLook), "AvatarSyncedLook", "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)]

14
ml_asl/README.md Normal file
View file

@ -0,0 +1,14 @@
# Avatar Synced Look
This mod Forces local player's eyes look direction to be synced for remote players.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_asl.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions - Avatar Synced Look`:
* **Enabled:** sets eyes look direction to be synced or locally generated on remote users side; `true` by default.
# Notes
* Remote users with [EyeMovementFix](https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/EyeMovementFix) installed can't see synced look direction.

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_asl
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

75
ml_asl/Settings.cs Normal file
View file

@ -0,0 +1,75 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_asl
{
static class Settings
{
public enum ModSetting
{
Enabled = 0
}
public static bool Enabled { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("ASL", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled)
};
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
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("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

12
ml_asl/Utils.cs Normal file
View file

@ -0,0 +1,12 @@
using ABI_RC.Core.UI;
using System.Reflection;
namespace ml_asl
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
}
}

63
ml_asl/ml_asl.csproj Normal file
View file

@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>AvatarSyncedLook</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarSyncedLook</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -0,0 +1,22 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Synced Look</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('ASL', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_ASL'));
}

View file

@ -14,13 +14,10 @@ namespace ml_dht
bool m_enabled = false; bool m_enabled = false;
bool m_headTracking = true; bool m_headTracking = true;
bool m_blinking = true;
bool m_eyeTracking = true;
float m_smoothing = 0.5f; float m_smoothing = 0.5f;
bool m_mirrored = false;
bool m_faceOverride = true;
CVRAvatar m_avatarDescriptor = null; CVRAvatar m_avatarDescriptor = null;
Transform m_camera = null;
LookAtIK m_lookIK = null; LookAtIK m_lookIK = null;
Transform m_headBone = null; Transform m_headBone = null;
@ -28,8 +25,6 @@ namespace ml_dht
Quaternion m_headRotation; Quaternion m_headRotation;
Vector2 m_gazeDirection; Vector2 m_gazeDirection;
float m_blinkProgress = 0f; float m_blinkProgress = 0f;
Vector2 m_mouthShapes;
float m_eyebrowsProgress = 0f;
Quaternion m_bindRotation; Quaternion m_bindRotation;
Quaternion m_lastHeadRotation; Quaternion m_lastHeadRotation;
@ -37,35 +32,29 @@ namespace ml_dht
// Unity events // Unity events
void Start() void Start()
{ {
SetEnabled(Settings.Enabled);
SetHeadTracking(Settings.HeadTracking);
SetSmoothing(Settings.Smoothing);
Settings.EnabledChange += this.SetEnabled; Settings.EnabledChange += this.SetEnabled;
Settings.HeadTrackingChange += this.SetHeadTracking; Settings.HeadTrackingChange += this.SetHeadTracking;
Settings.EyeTrackingChange += this.SetEyeTracking;
Settings.BlinkingChange += this.SetBlinking;
Settings.SmoothingChange += this.SetSmoothing; Settings.SmoothingChange += this.SetSmoothing;
Settings.MirroredChange += this.SetMirrored;
Settings.FaceOverrideChange += this.SetFaceOverride;
} }
void OnDestroy() void OnDestroy()
{ {
Settings.EnabledChange -= this.SetEnabled; Settings.EnabledChange -= this.SetEnabled;
Settings.HeadTrackingChange -= this.SetHeadTracking; Settings.HeadTrackingChange -= this.SetHeadTracking;
Settings.EyeTrackingChange -= this.SetEyeTracking;
Settings.BlinkingChange -= this.SetBlinking;
Settings.SmoothingChange -= this.SetSmoothing; Settings.SmoothingChange -= this.SetSmoothing;
Settings.MirroredChange -= this.SetMirrored;
Settings.FaceOverrideChange -= this.SetFaceOverride;
} }
// Tracking updates // Tracking updates
public void UpdateTrackingData(ref TrackingData p_data) 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_headPosition.Set(p_data.m_headPositionX * (Settings.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_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (Settings.Mirrored ? -1f : 1f), p_data.m_headRotationZ * (Settings.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_gazeDirection.Set(Settings.Mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
m_blinkProgress = p_data.m_blink; m_blinkProgress = p_data.m_blink;
m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape);
m_eyebrowsProgress = p_data.m_brows;
} }
void OnLookIKPostUpdate() void OnLookIKPostUpdate()
@ -80,48 +69,9 @@ namespace ml_dht
} }
// Game events // Game events
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(m_eyeTracking)
{
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
p_component.manualViewTarget = true;
p_component.targetViewPosition = l_camera.position + l_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(m_blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
internal void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
if(m_enabled && m_faceOverride)
{
if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false;
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y))) * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Pout] = ((m_mouthShapes.y > 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Left] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Right] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.LipSyncWasUpdated = true;
p_component.UpdateLipShapes();
}
}
internal void OnSetupAvatar() internal void OnSetupAvatar()
{ {
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>(); m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>();
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head); m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
m_lookIK = PlayerSetup.Instance._avatar.GetComponent<LookAtIK>(); m_lookIK = PlayerSetup.Instance._avatar.GetComponent<LookAtIK>();
@ -142,44 +92,53 @@ namespace ml_dht
m_bindRotation = Quaternion.identity; m_bindRotation = Quaternion.identity;
} }
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(Settings.EyeTracking && (m_camera != null))
{
p_component.manualViewTarget = true;
p_component.targetViewPosition = m_camera.position + m_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(Settings.Blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
// Settings // Settings
internal void SetEnabled(bool p_state) void SetEnabled(bool p_state)
{ {
if(m_enabled != p_state) if(m_enabled != p_state)
{ {
m_enabled = p_state; m_enabled = p_state;
if(m_enabled && m_headTracking) TryRestoreHeadRotation();
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
} }
} }
internal void SetHeadTracking(bool p_state) void SetHeadTracking(bool p_state)
{ {
if(m_headTracking != p_state) if(m_headTracking != p_state)
{ {
m_headTracking = p_state; m_headTracking = p_state;
TryRestoreHeadRotation();
}
}
void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
// Arbitrary
void TryRestoreHeadRotation()
{
if(m_enabled && m_headTracking) if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation); m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
} }
} }
internal void SetEyeTracking(bool p_state)
{
m_eyeTracking = p_state;
}
internal void SetBlinking(bool p_state)
{
m_blinking = p_state;
}
internal void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
internal void SetMirrored(bool p_state)
{
m_mirrored = p_state;
}
internal void SetFaceOverride(bool p_state)
{
m_faceOverride = p_state;
}
}
} }

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.FaceTracking;
using System.Reflection; using System.Reflection;
namespace ml_dht namespace ml_dht
@ -8,10 +9,7 @@ namespace ml_dht
{ {
static DesktopHeadTracking ms_instance = null; static DesktopHeadTracking ms_instance = null;
MemoryMapReader m_mapReader = null; TrackingModule m_trackingModule = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
HeadTracked m_localTracked = null; HeadTracked m_localTracked = null;
public override void OnInitializeMelon() public override void OnInitializeMelon()
@ -21,10 +19,7 @@ namespace ml_dht
Settings.Init(); Settings.Init();
m_mapReader = new MemoryMapReader(); m_trackingModule = new TrackingModule();
m_buffer = new byte[1024];
m_mapReader.Open("head/data");
// Patches // Patches
HarmonyInstance.Patch( HarmonyInstance.Patch(
@ -37,33 +32,27 @@ namespace ml_dht
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
MelonLoader.MelonCoroutines.Start(WaitForInstances());
}
System.Collections.IEnumerator WaitForInstances()
{
while(MetaPort.Instance == null)
yield return null;
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
FaceTrackingManager.Instance.RegisterModule(m_trackingModule);
// If you think it's a joke to put patch here, go on, try to put it in OnInitializeMelon, you melon :>
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic), typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch(
typeof(CVRFaceTracking).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForPlayer());
}
System.Collections.IEnumerator WaitForPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
m_localTracked.SetEnabled(Settings.Enabled);
m_localTracked.SetHeadTracking(Settings.HeadTracking);
m_localTracked.SetEyeTracking(Settings.EyeTracking);
m_localTracked.SetBlinking(Settings.Blinking);
m_localTracked.SetMirrored(Settings.Mirrored);
m_localTracked.SetSmoothing(Settings.Smoothing);
m_localTracked.SetFaceOverride(Settings.FaceOverride);
} }
public override void OnDeinitializeMelon() public override void OnDeinitializeMelon()
@ -71,19 +60,17 @@ namespace ml_dht
if(ms_instance == this) if(ms_instance == this)
ms_instance = null; ms_instance = null;
m_mapReader?.Close(); m_trackingModule = null;
m_mapReader = null;
m_buffer = null;
m_localTracked = null; m_localTracked = null;
} }
public override void OnUpdate() public override void OnUpdate()
{ {
if(Settings.Enabled && m_mapReader.Read(ref m_buffer)) if(Settings.Enabled && (m_trackingModule != null))
{ {
m_trackingData = TrackingData.ToObject(m_buffer); m_trackingModule.Update();
if(m_localTracked != null) if(m_localTracked != null)
m_localTracked.UpdateTrackingData(ref m_trackingData); m_localTracked.UpdateTrackingData(ref m_trackingModule.GetLatestTrackingData());
} }
} }
@ -128,19 +115,5 @@ namespace ml_dht
MelonLoader.MelonLogger.Error(e); MelonLoader.MelonLogger.Error(e);
} }
} }
static void OnFaceTrackingUpdate_Postfix(ref CVRFaceTracking __instance) => ms_instance?.OnFaceTrackingUpdate(__instance);
void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
try
{
if(p_component.isLocal && (m_localTracked != null))
m_localTracked.OnFaceTrackingUpdate(p_component);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
} }
} }

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.1.2-ex", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.2.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -7,8 +7,8 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
# Features # Features
* Head rotation * Head rotation
* Eyes gaze direction * Eyes gaze direction
* Basic mouth shapes: open, smile and pout
* Blinking * Blinking
* Basic mouth shapes
# Installation # Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader) * Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
@ -19,11 +19,13 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`: Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:
* **Enabled:** enables mod's activity; default value - `false`. * **Enabled:** enables mod's activity; default value - `false`.
* **Use head tracking:** enables head tracking; default value - `true`. * **Use head tracking:** enables head tracking; default value - `true`.
* **Use eyes tracking:** uses eyes tracking from data; default value - `true`. * **Use eyes tracking:** enables eyes tracking; default value - `true`.
* **Use face tracking:** enables mouth shapes tracking; default value - `true`.
* **Note:** You need to enable desktop tracking of `Vive Face tracking` in `Settings - Implementation` menu page.
* **Note:** Your avatar should have configured `CVR Face Tracking` component.
* **Use blinking:** uses blinking from data; default value - `true`. * **Use blinking:** uses blinking from data; default value - `true`.
* **Mirrored movement:** mirrors movement and gaze along 0YZ plane; 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`. * **Movement smoothing:** smoothing factor between new and old movement data; default value - `50`.
* **Override face tracking:** overrides and activates avatar's `VRC Face Tracking` components. List of used shapes: `Jaw_Open`, `Mouth_Pout`, `Mouth_Smile_Left`, `Mouth_Smile_Right`; default value - `true`.
# Known compatible tracking software # Known compatible tracking software
* [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf) * [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_dht namespace ml_dht
{ {
static class Scripts static class ResourcesHandler
{ {
public static string GetEmbeddedScript(string p_name) public static string GetEmbeddedResource(string p_name)
{ {
string l_result = ""; string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly(); Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -11,19 +11,19 @@ namespace ml_dht
Enabled = 0, Enabled = 0,
HeadTracking, HeadTracking,
EyeTracking, EyeTracking,
FaceTracking,
Blinking, Blinking,
Mirrored, Mirrored,
Smoothing, Smoothing,
FaceOverride
} }
public static bool Enabled { get; private set; } = false; public static bool Enabled { get; private set; } = false;
public static bool HeadTracking { get; private set; } = true; public static bool HeadTracking { get; private set; } = true;
public static bool EyeTracking { get; private set; } = true; public static bool EyeTracking { get; private set; } = true;
public static bool FaceTracking { get; private set; } = true;
public static bool Blinking { get; private set; } = true; public static bool Blinking { get; private set; } = true;
public static bool Mirrored { get; private set; } = false; public static bool Mirrored { get; private set; } = false;
public static float Smoothing { get; private set; } = 0.5f; public static float Smoothing { get; private set; } = 0.5f;
public static bool FaceOverride { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null; static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null; static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -31,10 +31,11 @@ namespace ml_dht
static public event Action<bool> EnabledChange; static public event Action<bool> EnabledChange;
static public event Action<bool> HeadTrackingChange; static public event Action<bool> HeadTrackingChange;
static public event Action<bool> EyeTrackingChange; static public event Action<bool> EyeTrackingChange;
static public event Action<bool> FaceTrackingChange;
static public event Action<bool> BlinkingChange; static public event Action<bool> BlinkingChange;
static public event Action<bool> MirroredChange; static public event Action<bool> MirroredChange;
static public event Action<float> SmoothingChange; static public event Action<float> SmoothingChange;
static public event Action<bool> FaceOverrideChange;
internal static void Init() internal static void Init()
{ {
@ -45,13 +46,19 @@ namespace ml_dht
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled), ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), HeadTracking), ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), HeadTracking),
ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), EyeTracking), ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), EyeTracking),
ms_category.CreateEntry(ModSetting.FaceTracking.ToString(), FaceTracking),
ms_category.CreateEntry(ModSetting.Blinking.ToString(), Blinking), ms_category.CreateEntry(ModSetting.Blinking.ToString(), Blinking),
ms_category.CreateEntry(ModSetting.Mirrored.ToString(), Mirrored), ms_category.CreateEntry(ModSetting.Mirrored.ToString(), Mirrored),
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 50f)), ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 50f)),
ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), FaceOverride)
}; };
Load(); Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
FaceTracking = (bool)ms_entries[(int)ModSetting.FaceTracking].BoxedValue;
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi()); MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
} }
@ -67,28 +74,18 @@ namespace ml_dht
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () => ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{ {
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpSlider", new Action<string, string>(OnSliderUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpToggle", new Action<string, string>(OnToggleUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
}; };
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) => ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{ {
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js")); ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries) foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString()); ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
}; };
} }
static void Load()
{
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
FaceOverride = (bool)ms_entries[(int)ModSetting.FaceOverride].BoxedValue;
}
static void OnSliderUpdate(string p_name, string p_value) static void OnSliderUpdate(string p_name, string p_value)
{ {
if(Enum.TryParse(p_name, out ModSetting l_setting)) if(Enum.TryParse(p_name, out ModSetting l_setting))
@ -134,6 +131,13 @@ namespace ml_dht
} }
break; break;
case ModSetting.FaceTracking:
{
FaceTracking = bool.Parse(p_value);
FaceTrackingChange?.Invoke(FaceTracking);
}
break;
case ModSetting.Blinking: case ModSetting.Blinking:
{ {
Blinking = bool.Parse(p_value); Blinking = bool.Parse(p_value);
@ -147,13 +151,6 @@ namespace ml_dht
MirroredChange?.Invoke(Mirrored); MirroredChange?.Invoke(Mirrored);
} }
break; break;
case ModSetting.FaceOverride:
{
FaceOverride = bool.Parse(p_value);
FaceOverrideChange?.Invoke(FaceOverride);
}
break;
} }
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value); ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);

70
ml_dht/TrackingModule.cs Normal file
View file

@ -0,0 +1,70 @@
using ABI_RC.Systems.FaceTracking;
using System;
using UnityEngine;
using ViveSR.anipal.Lip;
namespace ml_dht
{
class TrackingModule : ITrackingModule
{
bool m_registered = false;
bool m_activeAsModule = false;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
LipData_v2 m_lipData;
public TrackingModule()
{
m_lipData = new LipData_v2();
m_lipData.frame = 0;
m_lipData.time = 0;
m_lipData.image = IntPtr.Zero;
m_lipData.prediction_data = new PredictionData_v2();
m_lipData.prediction_data.blend_shape_weight = new float[(int)LipShape_v2.Max];
m_buffer = new byte[1024];
m_mapReader = new MemoryMapReader();
m_mapReader.Open("head/data");
}
~TrackingModule()
{
m_mapReader.Close();
m_mapReader = null;
}
public (bool, bool) Initialize(bool useEye, bool useLip)
{
m_registered = true;
m_activeAsModule = true;
return (false, true);
}
public void Shutdown()
{
m_activeAsModule = false;
}
public bool IsEyeDataAvailable() => false;
public bool IsLipDataAvailable() => true;
internal void Update()
{
if(m_mapReader.Read(ref m_buffer))
{
m_trackingData = TrackingData.ToObject(m_buffer);
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_trackingData.m_mouthShape)));
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Jaw_Open] = m_trackingData.m_mouthOpen;
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Pout] = ((m_trackingData.m_mouthShape > 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Left] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Right] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
if(m_registered && m_activeAsModule && Settings.FaceTracking)
FaceTrackingManager.Instance.SubmitNewFacialData(m_lipData);
}
}
internal ref TrackingData GetLatestTrackingData() => ref m_trackingData;
}
}

View file

@ -6,13 +6,13 @@ namespace ml_dht
{ {
static class Utils static class Utils
{ {
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance); static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false) 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.localScale : Vector3.one); return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
} }
public static void ExecuteScript(this CohtmlControlledViewDisposable p_viewDisposable, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_viewDisposable))?.ExecuteScript(p_script);
} }
} }

View file

@ -6,7 +6,7 @@
<Authors>SDraw</Authors> <Authors>SDraw</Authors>
<Company>None</Company> <Company>None</Company>
<Product>DesktopHeadTracking</Product> <Product>DesktopHeadTracking</Product>
<Version>1.1.2</Version> <Version>1.2.0</Version>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
</PropertyGroup> </PropertyGroup>
@ -18,49 +18,62 @@
<ItemGroup> <ItemGroup>
<None Remove="DesktopHeadTracking.json" /> <None Remove="DesktopHeadTracking.json" />
<None Remove="resources\menu.js" /> <None Remove="resources\mod_menu.js" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="resources\menu.js" /> <EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="0Harmony"> <Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Assembly-CSharp-firstpass"> <Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="cohtml.Net"> <Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Cohtml.Runtime"> <Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="MelonLoader"> <Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<SpecificVersion>false</SpecificVersion>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.AnimationModule"> <Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View file

@ -1,245 +0,0 @@
// 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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
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 = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Desktop Head Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use head tracking: </div>
<div class ="option-input">
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use eyes tracking: </div>
<div class ="option-input">
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use blinking: </div>
<div class ="option-input">
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mirrored movement: </div>
<div class ="option-input">
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Movement smoothing: </div>
<div class ="option-input">
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Override face tracking: </div>
<div class ="option-input">
<div id="FaceOverride" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
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');
}
}

View file

@ -0,0 +1,70 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Desktop Head Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enabled: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use head tracking: </div>
<div class ="option-input">
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use eyes tracking: </div>
<div class ="option-input">
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use face tracking: </div>
<div class ="option-input">
<div id="FaceTracking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Use blinking: </div>
<div class ="option-input">
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mirrored movement: </div>
<div class ="option-input">
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Movement smoothing: </div>
<div class ="option-input">
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('DHT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_DHT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('DHT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_DHT'));
}

View file

@ -13,8 +13,8 @@ namespace ml_lme
"leapmotion_hands.asset" "leapmotion_hands.asset"
}; };
static Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>(); static readonly Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>(); static readonly Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
public static void Load() public static void Load()
{ {

View file

@ -20,7 +20,7 @@ namespace ml_lme
foreach(string l_library in ms_libraries) foreach(string l_library in ms_libraries)
{ {
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + "." + l_library); Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_library);
if(!File.Exists(l_library)) if(!File.Exists(l_library))
{ {

View file

@ -24,20 +24,11 @@ namespace ml_lme
bool m_gripLeft = false; bool m_gripLeft = false;
bool m_gripRight = false; bool m_gripRight = false;
~LeapInput()
{
Settings.EnabledChange -= this.OnEnableChange;
Settings.InputChange -= this.OnInputChange;
MetaPort.Instance.settings.settingBoolChanged.RemoveListener(this.OnGameSettingBoolChange);
}
public override void ModuleAdded() public override void ModuleAdded()
{ {
base.ModuleAdded(); base.ModuleAdded();
base.InputEnabled = Settings.Enabled;
InputEnabled = Settings.Enabled; base.HapticFeedback = false;
HapticFeedback = false;
m_inVR = Utils.IsInVR(); m_inVR = Utils.IsInVR();
@ -47,13 +38,6 @@ namespace ml_lme
m_handRayLeft.isInteractionRay = true; m_handRayLeft.isInteractionRay = true;
m_handRayLeft.triggerGazeEvents = false; m_handRayLeft.triggerGazeEvents = false;
m_handRayLeft.holderRoot = m_handRayLeft.gameObject; m_handRayLeft.holderRoot = m_handRayLeft.gameObject;
m_handRayRight = LeapTracking.Instance.GetRightHand().gameObject.AddComponent<ControllerRay>();
m_handRayRight.hand = false;
m_handRayRight.generalMask = -1485;
m_handRayRight.isInteractionRay = true;
m_handRayRight.triggerGazeEvents = false;
m_handRayRight.holderRoot = m_handRayRight.gameObject;
m_handRayLeft.attachmentDistance = 0f; m_handRayLeft.attachmentDistance = 0f;
m_lineLeft = m_handRayLeft.gameObject.AddComponent<LineRenderer>(); m_lineLeft = m_handRayLeft.gameObject.AddComponent<LineRenderer>();
@ -67,6 +51,13 @@ namespace ml_lme
m_lineLeft.enabled = false; m_lineLeft.enabled = false;
m_lineLeft.receiveShadows = false; m_lineLeft.receiveShadows = false;
m_handRayLeft.lineRenderer = m_lineLeft; m_handRayLeft.lineRenderer = m_lineLeft;
m_handRayRight = LeapTracking.Instance.GetRightHand().gameObject.AddComponent<ControllerRay>();
m_handRayRight.hand = false;
m_handRayRight.generalMask = -1485;
m_handRayRight.isInteractionRay = true;
m_handRayRight.triggerGazeEvents = false;
m_handRayRight.holderRoot = m_handRayRight.gameObject;
m_handRayRight.attachmentDistance = 0f; m_handRayRight.attachmentDistance = 0f;
m_lineRight = m_handRayRight.gameObject.AddComponent<LineRenderer>(); m_lineRight = m_handRayRight.gameObject.AddComponent<LineRenderer>();
@ -82,12 +73,14 @@ namespace ml_lme
m_handRayRight.lineRenderer = m_lineRight; m_handRayRight.lineRenderer = m_lineRight;
Settings.EnabledChange += this.OnEnableChange; Settings.EnabledChange += this.OnEnableChange;
Settings.InputChange += this.OnInputChange; Settings.InteractionChange += this.OnInteractionChange;
Settings.GesturesChange += this.OnGesturesChange; Settings.GesturesChange += this.OnGesturesChange;
Settings.FingersOnlyChange += this.OnFingersOnlyChange;
OnEnableChange(Settings.Enabled); OnEnableChange(Settings.Enabled);
OnInputChange(Settings.Input); OnInteractionChange(Settings.Interaction);
OnGesturesChange(Settings.Gestures); OnGesturesChange(Settings.Gestures);
OnFingersOnlyChange(Settings.FingersOnly);
MelonLoader.MelonCoroutines.Start(WaitForSettings()); MelonLoader.MelonCoroutines.Start(WaitForSettings());
MelonLoader.MelonCoroutines.Start(WaitForMaterial()); MelonLoader.MelonCoroutines.Start(WaitForMaterial());
@ -108,69 +101,173 @@ namespace ml_lme
{ {
while(PlayerSetup.Instance == null) while(PlayerSetup.Instance == null)
yield return null; yield return null;
while(PlayerSetup.Instance.leftRay == null) while(PlayerSetup.Instance.vrRayLeft == null)
yield return null; yield return null;
while(PlayerSetup.Instance.leftRay.lineRenderer == null) while(PlayerSetup.Instance.vrRayLeft.lineRenderer == null)
yield return null; yield return null;
m_lineLeft.material = PlayerSetup.Instance.leftRay.lineRenderer.material; m_lineLeft.material = PlayerSetup.Instance.vrRayLeft.lineRenderer.material;
m_lineLeft.gameObject.layer = PlayerSetup.Instance.leftRay.gameObject.layer; m_lineLeft.gameObject.layer = PlayerSetup.Instance.vrRayLeft.gameObject.layer;
m_lineRight.material = PlayerSetup.Instance.leftRay.lineRenderer.material; m_lineRight.material = PlayerSetup.Instance.vrRayLeft.lineRenderer.material;
m_lineRight.gameObject.layer = PlayerSetup.Instance.leftRay.gameObject.layer; m_lineRight.gameObject.layer = PlayerSetup.Instance.vrRayLeft.gameObject.layer;
}
public override void ModuleDestroyed()
{
base.ModuleDestroyed();
if(m_handRayLeft != null)
Object.Destroy(m_handRayLeft);
m_handRayLeft = null;
if(m_handRayRight != null)
Object.Destroy(m_handRayRight);
m_handRayRight = null;
if(m_lineLeft != null)
Object.Destroy(m_lineLeft);
m_lineLeft = null;
if(m_lineRight != null)
Object.Destroy(m_lineRight);
m_lineRight = null;
Settings.EnabledChange -= this.OnEnableChange;
Settings.InteractionChange -= this.OnInteractionChange;
Settings.GesturesChange -= this.OnGesturesChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
MetaPort.Instance.settings.settingBoolChanged.RemoveListener(this.OnGameSettingBoolChange);
} }
public override void UpdateInput() public override void UpdateInput()
{ {
if(InputEnabled) if(base.InputEnabled)
{ {
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData(); LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present) if(l_data.m_leftHand.m_present)
{ {
SetFingersInput(l_data.m_leftHand, true);
m_handVisibleLeft = true; m_handVisibleLeft = true;
SetFingersInput(l_data.m_leftHand, true);
if(Settings.Gestures)
{
base._inputManager.gestureLeftRaw = 0f;
// Finger Point & Finger Gun
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle > 0.75f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky > 0.75f))
{
base._inputManager.gestureLeftRaw = (base._inputManager.fingerCurlLeftThumb >= 0.5f) ? 4f : 3f;
}
// Peace Sign
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle < 0.2f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky > 0.75f))
{
base._inputManager.gestureLeftRaw = 5f;
}
// Rock and Roll
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle > 0.75f) &&
(base._inputManager.fingerCurlLeftRing > 0.75f) && (base._inputManager.fingerCurlLeftPinky < 0.5f))
{
base._inputManager.gestureLeftRaw = 6f;
}
// Fist & Thumbs Up
if((base._inputManager.fingerCurlLeftIndex > 0.5f) && (base._inputManager.fingerCurlLeftMiddle > 0.5f) &&
(base._inputManager.fingerCurlLeftRing > 0.5f) && (base._inputManager.fingerCurlLeftPinky > 0.5f))
{
base._inputManager.gestureLeftRaw = (base._inputManager.fingerCurlLeftThumb >= 0.5f) ? ((l_data.m_leftHand.m_grabStrength - 0.5f) * 2f) : 2f;
}
// Open Hand
if((base._inputManager.fingerCurlLeftIndex < 0.2f) && (base._inputManager.fingerCurlLeftMiddle < 0.2f) &&
(base._inputManager.fingerCurlLeftRing < 0.2f) && (base._inputManager.fingerCurlLeftPinky < 0.2f))
{
base._inputManager.gestureLeftRaw = -1f;
}
base._inputManager.gestureLeft = base._inputManager.gestureLeftRaw;
}
} }
else else
{ {
if(m_handVisibleLeft) if(m_handVisibleLeft)
{ {
ResetFingers(true); ResetFingers(true);
m_handVisibleLeft = false;
if(Settings.Gestures) if(Settings.Gestures)
ResetGestures(true); ResetGestures(true);
} }
m_handVisibleLeft = false;
} }
if(l_data.m_rightHand.m_present) if(l_data.m_rightHand.m_present)
{ {
SetFingersInput(l_data.m_rightHand, false);
m_handVisibleRight = true; m_handVisibleRight = true;
SetFingersInput(l_data.m_rightHand, false);
if(Settings.Gestures)
{
base._inputManager.gestureRightRaw = 0f;
// Finger Point & Finger Gun
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle > 0.75f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky > 0.75f))
{
base._inputManager.gestureRightRaw = (base._inputManager.fingerCurlRightThumb >= 0.5f) ? 4f : 3f;
}
// Peace Sign
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle < 0.2f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky > 0.75f))
{
base._inputManager.gestureRightRaw = 5f;
}
// Rock and Roll
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle > 0.75f) &&
(base._inputManager.fingerCurlRightRing > 0.75f) && (base._inputManager.fingerCurlRightPinky < 0.5f))
{
base._inputManager.gestureRightRaw = 6f;
}
// Fist & Thumbs Up
if((base._inputManager.fingerCurlRightIndex > 0.5f) && (base._inputManager.fingerCurlRightMiddle > 0.5f) &&
(base._inputManager.fingerCurlRightRing > 0.5f) && (base._inputManager.fingerCurlRightPinky > 0.5f))
{
base._inputManager.gestureRightRaw = (base._inputManager.fingerCurlRightThumb >= 0.5f) ? ((l_data.m_rightHand.m_grabStrength - 0.5f) * 2f) : 2f;
}
// Open Hand
if((base._inputManager.fingerCurlRightIndex < 0.2f) && (base._inputManager.fingerCurlRightMiddle < 0.2f) &&
(base._inputManager.fingerCurlRightRing < 0.2f) && (base._inputManager.fingerCurlRightPinky < 0.2f))
{
base._inputManager.gestureRightRaw = -1f;
}
base._inputManager.gestureRight = base._inputManager.gestureRightRaw;
}
} }
else else
{ {
if(m_handVisibleRight) if(m_handVisibleRight)
{ {
ResetFingers(false); ResetFingers(false);
m_handVisibleRight = false;
if(Settings.Gestures) if(Settings.Gestures)
ResetGestures(false); ResetGestures(false);
} }
m_handVisibleRight = false;
} }
if(!ModSupporter.SkipFingersOverride()) if(!ModSupporter.SkipFingersOverride() && (!m_inVR || !Utils.AreKnucklesInUse()))
{ SetGameFingersTracking(m_handVisibleRight || m_handVisibleLeft);
if(m_inVR)
{
_inputManager.individualFingerTracking = !CVRInputManager._moduleXR.GestureToggleValue;
_inputManager.individualFingerTracking |= (l_data.m_leftHand.m_present || l_data.m_rightHand.m_present);
}
else
_inputManager.individualFingerTracking = (l_data.m_leftHand.m_present || l_data.m_rightHand.m_present);
IKSystem.Instance.FingerSystem.controlActive = _inputManager.individualFingerTracking;
}
m_handRayLeft.enabled = (l_data.m_leftHand.m_present && (!m_inVR || !Utils.IsLeftHandTracked() || !Settings.FingersOnly));
m_handRayRight.enabled = (l_data.m_rightHand.m_present && (!m_inVR || !Utils.IsRightHandTracked() || !Settings.FingersOnly));
base.UpdateInput(); base.UpdateInput();
} }
@ -178,180 +275,105 @@ namespace ml_lme
public override void Update_Interaction() public override void Update_Interaction()
{ {
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData(); if(Settings.Interaction)
if(Settings.Input)
{ {
if(l_data.m_leftHand.m_present && (!m_inVR || !Utils.IsLeftHandTracked() || !Settings.FingersOnly)) LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(m_handVisibleLeft && (!m_inVR || !Utils.IsLeftHandTracked()) && !Settings.FingersOnly)
{ {
float l_strength = l_data.m_leftHand.m_grabStrength; float l_strength = l_data.m_leftHand.m_grabStrength;
float l_interactValue = 0f; float l_interactValue;
if(m_gripToGrab) if(m_gripToGrab)
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength)); l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength));
else else
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength)); l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength));
_inputManager.interactLeftValue = Mathf.Max(l_interactValue, _inputManager.interactLeftValue); base._inputManager.interactLeftValue = Mathf.Max(l_interactValue, base._inputManager.interactLeftValue);
if(m_interactLeft != (l_strength > Settings.InteractThreadhold)) if(m_interactLeft != (l_strength > Settings.InteractThreadhold))
{ {
m_interactLeft = (l_strength > Settings.InteractThreadhold); m_interactLeft = (l_strength > Settings.InteractThreadhold);
_inputManager.interactLeftDown |= m_interactLeft; base._inputManager.interactLeftDown |= m_interactLeft;
_inputManager.interactLeftUp |= !m_interactLeft; base._inputManager.interactLeftUp |= !m_interactLeft;
} }
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength)); float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
_inputManager.gripLeftValue = Mathf.Max(l_gripValue, _inputManager.gripLeftValue); base._inputManager.gripLeftValue = Mathf.Max(l_gripValue, base._inputManager.gripLeftValue);
if(m_gripLeft != (l_strength > Settings.GripThreadhold)) if(m_gripLeft != (l_strength > Settings.GripThreadhold))
{ {
m_gripLeft = (l_strength > Settings.GripThreadhold); m_gripLeft = (l_strength > Settings.GripThreadhold);
_inputManager.gripLeftDown |= m_gripLeft; base._inputManager.gripLeftDown |= m_gripLeft;
_inputManager.gripLeftUp |= !m_gripLeft; base._inputManager.gripLeftUp |= !m_gripLeft;
} }
} }
if(l_data.m_rightHand.m_present && (!m_inVR || !Utils.IsRightHandTracked() || !Settings.FingersOnly)) if(m_handVisibleRight && (!m_inVR || !Utils.IsRightHandTracked()) && !Settings.FingersOnly)
{ {
float l_strength = l_data.m_rightHand.m_grabStrength; float l_strength = l_data.m_rightHand.m_grabStrength;
float l_interactValue = 0f; float l_interactValue;
if(m_gripToGrab) if(m_gripToGrab)
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength)); l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(Mathf.Min(Settings.GripThreadhold, Settings.InteractThreadhold), Mathf.Max(Settings.GripThreadhold, Settings.InteractThreadhold), l_strength));
else else
l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength)); l_interactValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.InteractThreadhold, l_strength));
_inputManager.interactRightValue = Mathf.Max(l_interactValue, _inputManager.interactRightValue); base._inputManager.interactRightValue = Mathf.Max(l_interactValue, base._inputManager.interactRightValue);
if(m_interactRight != (l_strength > Settings.InteractThreadhold)) if(m_interactRight != (l_strength > Settings.InteractThreadhold))
{ {
m_interactRight = (l_strength > Settings.InteractThreadhold); m_interactRight = (l_strength > Settings.InteractThreadhold);
_inputManager.interactRightDown |= m_interactRight; base._inputManager.interactRightDown |= m_interactRight;
_inputManager.interactRightUp |= !m_interactRight; base._inputManager.interactRightUp |= !m_interactRight;
} }
float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength)); float l_gripValue = Mathf.Clamp01(Mathf.InverseLerp(0f, Settings.GripThreadhold, l_strength));
_inputManager.gripRightValue = Mathf.Max(l_gripValue, _inputManager.gripRightValue); base._inputManager.gripRightValue = Mathf.Max(l_gripValue, base._inputManager.gripRightValue);
if(m_gripRight != (l_strength > Settings.GripThreadhold)) if(m_gripRight != (l_strength > Settings.GripThreadhold))
{ {
m_gripRight = (l_strength > Settings.GripThreadhold); m_gripRight = (l_strength > Settings.GripThreadhold);
_inputManager.gripRightDown |= m_gripRight; base._inputManager.gripRightDown |= m_gripRight;
_inputManager.gripRightUp |= !m_gripRight; base._inputManager.gripRightUp |= !m_gripRight;
}
} }
} }
if(Settings.Gestures) ToggleHandRay(m_handVisibleLeft && (!m_inVR || !Utils.IsLeftHandTracked()) && !Settings.FingersOnly, true);
{ ToggleHandRay(m_handVisibleRight && (!m_inVR || !Utils.IsRightHandTracked()) && !Settings.FingersOnly, false);
// Left hand gestures
if(l_data.m_leftHand.m_present)
{
_inputManager.gestureLeftRaw = 0f;
// Finger Point & Finger Gun
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle > 0.75f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky > 0.75f)
{
_inputManager.gestureLeftRaw = _inputManager.fingerCurlLeftThumb >= 0.5f ? 4f : 3f;
}
// Peace Sign
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle < 0.2f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky > 0.75f)
{
_inputManager.gestureLeftRaw = 5f;
}
// Rock and Roll
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle > 0.75f &&
_inputManager.fingerCurlLeftRing > 0.75f && _inputManager.fingerCurlLeftPinky < 0.5f)
{
_inputManager.gestureLeftRaw = 6f;
}
// Fist & Thumbs Up
if(_inputManager.fingerCurlLeftIndex > 0.5f && _inputManager.fingerCurlLeftMiddle > 0.5f &&
_inputManager.fingerCurlLeftRing > 0.5f && _inputManager.fingerCurlLeftPinky > 0.5f)
{
_inputManager.gestureLeftRaw = _inputManager.fingerCurlLeftThumb >= 0.5f
? (l_data.m_rightHand.m_grabStrength - 0.5f) * 2f
: 2f;
}
// Open Hand
if(_inputManager.fingerCurlLeftIndex < 0.2f && _inputManager.fingerCurlLeftMiddle < 0.2f &&
_inputManager.fingerCurlLeftRing < 0.2f && _inputManager.fingerCurlLeftPinky < 0.2f)
{
_inputManager.gestureLeftRaw = -1f;
}
_inputManager.gestureLeft = _inputManager.gestureLeftRaw;
}
// Right hand gestures
if(l_data.m_rightHand.m_present)
{
_inputManager.gestureRightRaw = 0f;
// Finger Point & Finger Gun
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle > 0.75f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky > 0.75f)
{
_inputManager.gestureRightRaw = _inputManager.fingerCurlRightThumb >= 0.5f ? 4f : 3f;
}
// Peace Sign
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle < 0.2f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky > 0.75f)
{
_inputManager.gestureRightRaw = 5f;
}
// Rock and Roll
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle > 0.75f &&
_inputManager.fingerCurlRightRing > 0.75f && _inputManager.fingerCurlRightPinky < 0.5f)
{
_inputManager.gestureRightRaw = 6f;
}
// Fist & Thumbs Up
if(_inputManager.fingerCurlRightIndex > 0.5f && _inputManager.fingerCurlRightMiddle > 0.5f &&
_inputManager.fingerCurlRightRing > 0.5f && _inputManager.fingerCurlRightPinky > 0.5f)
{
_inputManager.gestureRightRaw = _inputManager.fingerCurlRightThumb >= 0.5f
? (l_data.m_rightHand.m_grabStrength - 0.5f) * 2f
: 2f;
}
// Open Hand
if(_inputManager.fingerCurlRightIndex < 0.2f && _inputManager.fingerCurlRightMiddle < 0.2f &&
_inputManager.fingerCurlRightRing < 0.2f && _inputManager.fingerCurlRightPinky < 0.2f)
{
_inputManager.gestureRightRaw = -1f;
}
_inputManager.gestureRight = _inputManager.gestureRightRaw;
}
} }
} }
// Settings changes // Settings changes
void OnEnableChange(bool p_state) void OnEnableChange(bool p_state)
{ {
InputEnabled = p_state; base.InputEnabled = p_state;
OnInputChange(p_state && Settings.Input);
UpdateFingerTracking();
m_handVisibleLeft &= p_state; m_handVisibleLeft &= p_state;
m_handVisibleRight &= p_state; m_handVisibleRight &= p_state;
}
void OnInputChange(bool p_state)
{
((MonoBehaviour)m_handRayLeft).enabled = (p_state && Settings.Enabled);
((MonoBehaviour)m_handRayRight).enabled = (p_state && Settings.Enabled);
m_lineLeft.enabled = (p_state && Settings.Enabled);
m_lineRight.enabled = (p_state && Settings.Enabled);
if(!p_state) if(!p_state)
{
ResetFingers(true);
ResetFingers(false);
if(Settings.Gestures)
{
ResetGestures(true);
ResetGestures(false);
}
// Reset to default, FreedomFingers can go brrr, player should press funny controller button two times
SetGameFingersTracking(m_inVR && Utils.AreKnucklesInUse() && !CVRInputManager._moduleXR.GestureToggleValue);
}
OnInteractionChange(Settings.Interaction);
}
void OnInteractionChange(bool p_state)
{
bool l_state = (p_state && Settings.Enabled && !Settings.FingersOnly);
ToggleHandRay(l_state, true);
ToggleHandRay(l_state, false);
if(!l_state)
{ {
m_handRayLeft.DropObject(true); m_handRayLeft.DropObject(true);
m_handRayLeft.ClearGrabbedObject(); m_handRayLeft.ClearGrabbedObject();
@ -368,17 +390,21 @@ namespace ml_lme
void OnGesturesChange(bool p_state) void OnGesturesChange(bool p_state)
{ {
_inputManager.gestureLeft = 0f; base._inputManager.gestureLeft = 0f;
_inputManager.gestureLeftRaw = 0f; base._inputManager.gestureLeftRaw = 0f;
_inputManager.gestureRight = 0f; base._inputManager.gestureRight = 0f;
_inputManager.gestureRightRaw = 0f; base._inputManager.gestureRightRaw = 0f;
}
void OnFingersOnlyChange(bool p_state)
{
OnInteractionChange(Settings.Interaction);
} }
// Game events // Game events
internal void OnAvatarSetup() internal void OnAvatarSetup()
{ {
m_inVR = Utils.IsInVR(); m_inVR = Utils.IsInVR();
UpdateFingerTracking();
} }
internal void OnRayScale(float p_scale) internal void OnRayScale(float p_scale)
@ -388,45 +414,35 @@ namespace ml_lme
} }
// Arbitrary // Arbitrary
void UpdateFingerTracking() void SetFingersInput(LeapParser.HandData p_hand, bool p_left)
{
_inputManager.individualFingerTracking = (Settings.Enabled || (m_inVR && Utils.AreKnucklesInUse() && !CVRInputManager._moduleXR.GestureToggleValue));
IKSystem.Instance.FingerSystem.controlActive = _inputManager.individualFingerTracking;
if(!Settings.Enabled)
{
ResetFingers(true);
ResetFingers(false);
}
}
void SetFingersInput(GestureMatcher.HandData p_hand, bool p_left)
{ {
// Game has spreads in range of [0;1], but mod now operates in range of [-1;1]
// So spreads will be normalized towards game's range
if(p_left) if(p_left)
{ {
_inputManager.fingerCurlLeftThumb = p_hand.m_bends[0]; base._inputManager.fingerCurlLeftThumb = p_hand.m_bends[0];
_inputManager.fingerCurlLeftIndex = p_hand.m_bends[1]; base._inputManager.fingerCurlLeftIndex = p_hand.m_bends[1];
_inputManager.fingerCurlLeftMiddle = p_hand.m_bends[2]; base._inputManager.fingerCurlLeftMiddle = p_hand.m_bends[2];
_inputManager.fingerCurlLeftRing = p_hand.m_bends[3]; base._inputManager.fingerCurlLeftRing = p_hand.m_bends[3];
_inputManager.fingerCurlLeftPinky = p_hand.m_bends[4]; base._inputManager.fingerCurlLeftPinky = p_hand.m_bends[4];
_inputManager.fingerSpreadLeftThumb = p_hand.m_spreads[0]; base._inputManager.fingerSpreadLeftThumb = 1f - (p_hand.m_spreads[0] * 0.5f + 0.5f);
_inputManager.fingerSpreadLeftIndex = p_hand.m_spreads[1]; base._inputManager.fingerSpreadLeftIndex = 1f - (p_hand.m_spreads[1] * 0.5f + 0.5f);
_inputManager.fingerSpreadLeftMiddle = p_hand.m_spreads[2]; base._inputManager.fingerSpreadLeftMiddle = 1f - (p_hand.m_spreads[2] * 0.5f + 0.5f);
_inputManager.fingerSpreadLeftRing = p_hand.m_spreads[3]; base._inputManager.fingerSpreadLeftRing = 1f - (p_hand.m_spreads[3] * 0.5f + 0.5f);
_inputManager.fingerSpreadLeftPinky = p_hand.m_spreads[4]; base._inputManager.fingerSpreadLeftPinky = 1f - (p_hand.m_spreads[4] * 0.5f + 0.5f);
} }
else else
{ {
_inputManager.fingerCurlRightThumb = p_hand.m_bends[0]; base._inputManager.fingerCurlRightThumb = p_hand.m_bends[0];
_inputManager.fingerCurlRightIndex = p_hand.m_bends[1]; base._inputManager.fingerCurlRightIndex = p_hand.m_bends[1];
_inputManager.fingerCurlRightMiddle = p_hand.m_bends[2]; base._inputManager.fingerCurlRightMiddle = p_hand.m_bends[2];
_inputManager.fingerCurlRightRing = p_hand.m_bends[3]; base._inputManager.fingerCurlRightRing = p_hand.m_bends[3];
_inputManager.fingerCurlRightPinky = p_hand.m_bends[4]; base._inputManager.fingerCurlRightPinky = p_hand.m_bends[4];
_inputManager.fingerSpreadRightThumb = p_hand.m_spreads[0]; base._inputManager.fingerSpreadRightThumb = 1f - (p_hand.m_spreads[0] * 0.5f + 0.5f);
_inputManager.fingerSpreadRightIndex = p_hand.m_spreads[1]; base._inputManager.fingerSpreadRightIndex = 1f - (p_hand.m_spreads[1] * 0.5f + 0.5f);
_inputManager.fingerSpreadRightMiddle = p_hand.m_spreads[2]; base._inputManager.fingerSpreadRightMiddle = 1f - (p_hand.m_spreads[2] * 0.5f + 0.5f);
_inputManager.fingerSpreadRightRing = p_hand.m_spreads[3]; base._inputManager.fingerSpreadRightRing = 1f - (p_hand.m_spreads[3] * 0.5f + 0.5f);
_inputManager.fingerSpreadRightPinky = p_hand.m_spreads[4]; base._inputManager.fingerSpreadRightPinky = 1f - (p_hand.m_spreads[4] * 0.5f + 0.5f);
} }
} }
@ -434,29 +450,29 @@ namespace ml_lme
{ {
if(p_left) if(p_left)
{ {
_inputManager.fingerCurlLeftThumb = 0f; base._inputManager.fingerCurlLeftThumb = 0f;
_inputManager.fingerCurlLeftIndex = 0f; base._inputManager.fingerCurlLeftIndex = 0f;
_inputManager.fingerCurlLeftMiddle = 0f; base._inputManager.fingerCurlLeftMiddle = 0f;
_inputManager.fingerCurlLeftRing = 0f; base._inputManager.fingerCurlLeftRing = 0f;
_inputManager.fingerCurlLeftPinky = 0f; base._inputManager.fingerCurlLeftPinky = 0f;
_inputManager.fingerSpreadLeftThumb = 0f; base._inputManager.fingerSpreadLeftThumb = 0.5f;
_inputManager.fingerSpreadLeftIndex = 0f; base._inputManager.fingerSpreadLeftIndex = 0.5f;
_inputManager.fingerSpreadLeftMiddle = 0f; base._inputManager.fingerSpreadLeftMiddle = 0.5f;
_inputManager.fingerSpreadLeftRing = 0f; base._inputManager.fingerSpreadLeftRing = 0.5f;
_inputManager.fingerSpreadLeftPinky = 0f; base._inputManager.fingerSpreadLeftPinky = 0.5f;
} }
else else
{ {
_inputManager.fingerCurlRightThumb = 0f; base._inputManager.fingerCurlRightThumb = 0f;
_inputManager.fingerCurlRightIndex = 0f; base._inputManager.fingerCurlRightIndex = 0f;
_inputManager.fingerCurlRightMiddle = 0f; base._inputManager.fingerCurlRightMiddle = 0f;
_inputManager.fingerCurlRightRing = 0f; base._inputManager.fingerCurlRightRing = 0f;
_inputManager.fingerCurlRightPinky = 0f; base._inputManager.fingerCurlRightPinky = 0f;
_inputManager.fingerSpreadRightThumb = 0f; base._inputManager.fingerSpreadRightThumb = 0.5f;
_inputManager.fingerSpreadRightIndex = 0f; base._inputManager.fingerSpreadRightIndex = 0.5f;
_inputManager.fingerSpreadRightMiddle = 0f; base._inputManager.fingerSpreadRightMiddle = 0.5f;
_inputManager.fingerSpreadRightRing = 0f; base._inputManager.fingerSpreadRightRing = 0.5f;
_inputManager.fingerSpreadRightPinky = 0f; base._inputManager.fingerSpreadRightPinky = 0.5f;
} }
} }
@ -464,13 +480,31 @@ namespace ml_lme
{ {
if(p_left) if(p_left)
{ {
_inputManager.gestureLeft = 0f; base._inputManager.gestureLeft = 0f;
_inputManager.gestureLeftRaw = 0f; base._inputManager.gestureLeftRaw = 0f;
} }
else else
{ {
_inputManager.gestureRight = 0f; base._inputManager.gestureRight = 0f;
_inputManager.gestureRightRaw = 0f; base._inputManager.gestureRightRaw = 0f;
}
}
void ToggleHandRay(bool p_state, bool p_left)
{
if(p_left)
{
m_handRayLeft.enabled = p_state;
((MonoBehaviour)m_handRayLeft).enabled = p_state;
m_lineLeft.enabled = p_state;
m_lineLeft.forceRenderingOff = !p_state;
}
else
{
m_handRayRight.enabled = p_state;
((MonoBehaviour)m_handRayRight).enabled = p_state;
m_lineRight.enabled = p_state;
m_lineRight.forceRenderingOff = !p_state;
} }
} }
@ -480,5 +514,11 @@ namespace ml_lme
if(p_name == "ControlUseGripToGrab") if(p_name == "ControlUseGripToGrab")
m_gripToGrab = p_state; m_gripToGrab = p_state;
} }
void SetGameFingersTracking(bool p_state)
{
base._inputManager.individualFingerTracking = p_state;
IKSystem.Instance.FingerSystem.controlActive = base._inputManager.individualFingerTracking;
}
} }
} }

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.InputManagement;
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
@ -12,7 +11,7 @@ namespace ml_lme
public static LeapManager Instance { get; private set; } = null; public static LeapManager Instance { get; private set; } = null;
Leap.Controller m_leapController = null; Leap.Controller m_leapController = null;
GestureMatcher.LeapData m_leapData = null; LeapParser.LeapData m_leapData = null;
LeapTracking m_leapTracking = null; LeapTracking m_leapTracking = null;
LeapTracked m_leapTracked = null; LeapTracked m_leapTracked = null;
@ -24,7 +23,7 @@ namespace ml_lme
Instance = this; Instance = this;
m_leapController = new Leap.Controller(); m_leapController = new Leap.Controller();
m_leapData = new GestureMatcher.LeapData(); m_leapData = new LeapParser.LeapData();
DontDestroyOnLoad(this); DontDestroyOnLoad(this);
@ -60,6 +59,23 @@ namespace ml_lme
m_leapController.Dispose(); m_leapController.Dispose();
m_leapController = null; m_leapController = null;
if(m_leapTracking != null)
Object.Destroy(m_leapTracking);
m_leapTracking = null;
if(m_leapTracked != null)
Object.Destroy(m_leapTracked);
m_leapTracked = null;
if(m_leapInput != null)
{
if(CVRInputManager.Instance != null)
CVRInputManager.Instance.DestroyInputModule(m_leapInput);
else
m_leapInput.ModuleDestroyed();
}
m_leapInput = null;
Settings.EnabledChange -= this.OnEnableChange; Settings.EnabledChange -= this.OnEnableChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange; Settings.TrackingModeChange -= this.OnTrackingModeChange;
} }
@ -90,12 +106,12 @@ namespace ml_lme
if(m_leapController.IsConnected) if(m_leapController.IsConnected)
{ {
Leap.Frame l_frame = m_leapController.Frame(); Leap.Frame l_frame = m_leapController.Frame();
GestureMatcher.GetFrameData(l_frame, m_leapData); LeapParser.ParseFrame(l_frame, m_leapData);
} }
} }
} }
public GestureMatcher.LeapData GetLatestData() => m_leapData; public LeapParser.LeapData GetLatestData() => m_leapData;
// Device events // Device events
void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args) void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args)
@ -169,12 +185,6 @@ namespace ml_lme
m_leapTracked.OnAvatarSetup(); m_leapTracked.OnAvatarSetup();
} }
internal void OnCalibrate()
{
if(m_leapTracked != null)
m_leapTracked.OnCalibrate();
}
internal void OnRayScale(float p_scale) internal void OnRayScale(float p_scale)
{ {
m_leapInput?.OnRayScale(p_scale); m_leapInput?.OnRayScale(p_scale);

View file

@ -2,15 +2,24 @@
namespace ml_lme namespace ml_lme
{ {
static class GestureMatcher static class LeapParser
{ {
readonly static Vector2[] ms_fingerLimits = readonly static Vector2[] ms_bendLimits =
{ {
new Vector2(-50f, 0f), new Vector2(0f, 90f),
new Vector2(-20f, 30f), new Vector2(0f, 180f),
new Vector2(-15f, 15f), new Vector2(0f, 180f),
new Vector2(-10f, 20f), new Vector2(0f, 180f),
new Vector2(-10f, 25f) new Vector2(0f, 180f)
};
readonly static Vector2[] ms_spreadLimits =
{
new Vector2(-25f, 25f), // Unity's default limits
new Vector2(-20f, 20f),
new Vector2(-7.5f, 7.5f),
new Vector2(-7.5f, 7.5f),
new Vector2(-20f, 20f)
}; };
public class HandData public class HandData
@ -68,7 +77,7 @@ namespace ml_lme
} }
} }
public static void GetFrameData(Leap.Frame p_frame, LeapData p_data) public static void ParseFrame(Leap.Frame p_frame, LeapData p_data)
{ {
p_data.Reset(); p_data.Reset();
@ -93,30 +102,31 @@ namespace ml_lme
// Bends // Bends
foreach(Leap.Finger l_finger in p_hand.Fingers) foreach(Leap.Finger l_finger in p_hand.Fingers)
{ {
Quaternion l_prevSegment = Quaternion.identity; Quaternion l_parentRot = Quaternion.identity;
float l_angle = 0f; float l_angle = 0f;
foreach(Leap.Bone l_bone in l_finger.bones) foreach(Leap.Bone l_bone in l_finger.bones)
{ {
p_data.m_fingerPosition[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.PrevJoint; int l_index = (int)l_finger.Type * 4 + (int)l_bone.Type;
p_data.m_fingerRotation[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.Rotation; p_data.m_fingerPosition[l_index] = l_bone.PrevJoint;
p_data.m_fingerRotation[l_index] = l_bone.Rotation;
if(l_bone.Type == Leap.Bone.BoneType.TYPE_METACARPAL) if(l_bone.Type == Leap.Bone.BoneType.TYPE_METACARPAL)
{ {
l_prevSegment = l_bone.Rotation; l_parentRot = l_bone.Rotation;
continue; continue;
} }
Quaternion l_diff = Quaternion.Inverse(l_prevSegment) * l_bone.Rotation; Quaternion l_localRot = Quaternion.Inverse(l_parentRot) * l_bone.Rotation;
l_prevSegment = l_bone.Rotation; float l_angleDiff = l_localRot.eulerAngles.x;
float l_angleDiff = l_diff.eulerAngles.x;
if(l_angleDiff > 180f) if(l_angleDiff > 180f)
l_angleDiff -= 360f; l_angleDiff -= 360f;
l_angle += l_angleDiff; l_angle += l_angleDiff;
l_parentRot = l_bone.Rotation;
} }
p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(0f, (l_finger.Type == Leap.Finger.FingerType.TYPE_THUMB) ? 90f : 180f, l_angle); p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_bendLimits[(int)l_finger.Type].x, ms_bendLimits[(int)l_finger.Type].y, l_angle);
} }
// Spreads // Spreads
@ -126,24 +136,17 @@ namespace ml_lme
Leap.Bone l_child = l_finger.Bone(Leap.Bone.BoneType.TYPE_PROXIMAL); Leap.Bone l_child = l_finger.Bone(Leap.Bone.BoneType.TYPE_PROXIMAL);
Quaternion l_diff = Quaternion.Inverse(l_parent.Rotation) * l_child.Rotation; Quaternion l_diff = Quaternion.Inverse(l_parent.Rotation) * l_child.Rotation;
// Spread - local Y rotation, but thumb is obnoxious // Spread - local Y rotation
float l_angle = 360f - l_diff.eulerAngles.y; float l_angle = l_diff.eulerAngles.y;
if(l_angle > 180f) if(l_angle > 180f)
l_angle -= 360f; l_angle -= 360f;
// Pain
if(p_hand.IsRight) if(p_hand.IsRight)
l_angle *= -1f; l_angle *= -1f;
if(l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB) p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_spreadLimits[(int)l_finger.Type].x, ms_spreadLimits[(int)l_finger.Type].y, l_angle) * 2f - 1f;
{ if((l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB) && (p_data.m_bends[(int)l_finger.Type] >= 0.8f))
if(l_angle < 0f) p_data.m_spreads[(int)l_finger.Type] = Mathf.Lerp(p_data.m_spreads[(int)l_finger.Type], 0f, (p_data.m_bends[(int)l_finger.Type] - 0.8f) * 5f);
p_data.m_spreads[(int)l_finger.Type] = 0.5f * Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, 0f, l_angle);
else
p_data.m_spreads[(int)l_finger.Type] = 0.5f + 0.5f * Utils.InverseLerpUnclamped(0f, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
}
else
p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
} }
p_data.m_grabStrength = Mathf.Clamp01((p_data.m_bends[1] + p_data.m_bends[2] + p_data.m_bends[3] + p_data.m_bends[4]) * 0.25f); p_data.m_grabStrength = Mathf.Clamp01((p_data.m_bends[1] + p_data.m_bends[2] + p_data.m_bends[3] + p_data.m_bends[4]) * 0.25f);

View file

@ -9,33 +9,37 @@ namespace ml_lme
[DisallowMultipleComponent] [DisallowMultipleComponent]
class LeapTracked : MonoBehaviour class LeapTracked : MonoBehaviour
{ {
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[]; struct IKInfo
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 0f, 270f); {
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f); public Vector4 m_armsWeights;
static readonly Quaternion ms_offsetLeftDesktop = Quaternion.Euler(0f, 90f, 0f); public Vector2 m_elbowsWeights;
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f); public Transform m_leftHandTarget;
public Transform m_rightHandTarget;
public Transform m_leftElbowTarget;
public Transform m_rightElbowTarget;
}
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 90f, 0f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 270f, 0f);
VRIK m_vrIK = null;
Vector4 m_armsWeights = Vector2.zero;
bool m_inVR = false; bool m_inVR = false;
VRIK m_vrIK = null;
Transform m_hips = null; Transform m_hips = null;
Transform m_origLeftHand = null;
Transform m_origRightHand = null;
Transform m_origLeftElbow = null;
Transform m_origRightElbow = null;
bool m_enabled = true; bool m_enabled = true;
bool m_fingersOnly = false; bool m_fingersOnly = false;
bool m_trackElbows = true; bool m_trackElbows = true;
IKInfo m_vrIKInfo;
ArmIK m_leftArmIK = null; ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null; ArmIK m_rightArmIK = null;
HumanPoseHandler m_poseHandler = null; HumanPoseHandler m_poseHandler = null;
HumanPose m_pose; HumanPose m_pose;
Transform m_leftHandTarget = null; Transform m_leftHandTarget = null;
Transform m_rightHandTarget = null; Transform m_rightHandTarget = null;
bool m_leftTargetActive = false; bool m_leftTargetActive = false; // VRIK only
bool m_rightTargetActive = false; bool m_rightTargetActive = false; // VRIK only
// Unity events // Unity events
void Start() void Start()
@ -63,6 +67,27 @@ namespace ml_lme
void OnDestroy() void OnDestroy()
{ {
if(m_leftArmIK != null)
Destroy(m_leftArmIK);
m_leftArmIK = null;
if(m_rightArmIK != null)
Destroy(m_rightArmIK);
m_rightArmIK = null;
if(m_leftHandTarget != null)
Destroy(m_leftHandTarget);
m_leftHandTarget = null;
if(m_rightHandTarget != null)
Destroy(m_rightHandTarget);
m_rightHandTarget = null;
m_poseHandler?.Dispose();
m_poseHandler = null;
m_vrIK = null;
Settings.EnabledChange -= this.OnEnabledChange; Settings.EnabledChange -= this.OnEnabledChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange; Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
Settings.TrackElbowsChange -= this.OnTrackElbowsChange; Settings.TrackElbowsChange -= this.OnTrackElbowsChange;
@ -72,7 +97,7 @@ namespace ml_lme
{ {
if(m_enabled) if(m_enabled)
{ {
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData(); LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if((m_leftArmIK != null) && (m_rightArmIK != null)) if((m_leftArmIK != null) && (m_rightArmIK != null))
{ {
@ -89,35 +114,8 @@ namespace ml_lme
if((m_vrIK != null) && !m_fingersOnly) if((m_vrIK != null) && !m_fingersOnly)
{ {
if(l_data.m_leftHand.m_present && !m_leftTargetActive) m_leftTargetActive = l_data.m_leftHand.m_present;
{ m_rightTargetActive = l_data.m_rightHand.m_present;
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftTargetActive = true;
}
if(!l_data.m_leftHand.m_present && m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false;
}
if(l_data.m_rightHand.m_present && !m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightTargetActive = true;
}
if(!l_data.m_rightHand.m_present && m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
} }
} }
} }
@ -126,7 +124,7 @@ namespace ml_lme
{ {
if(m_enabled && !m_inVR && (m_poseHandler != null)) if(m_enabled && !m_inVR && (m_poseHandler != null))
{ {
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData(); LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
Vector3 l_hipsLocalPos = m_hips.localPosition; Vector3 l_hipsLocalPos = m_hips.localPosition;
Quaternion l_hipsLocalRot = m_hips.localRotation; Quaternion l_hipsLocalRot = m_hips.localRotation;
@ -140,76 +138,11 @@ namespace ml_lme
} }
} }
// Tracking update
void UpdateFingers(GestureMatcher.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_leftHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_leftHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_leftHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_leftHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_leftHand.m_spreads[4]));
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_rightHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_rightHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_rightHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_rightHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_rightHand.m_spreads[4]));
}
}
// Game events // Game events
internal void OnAvatarClear() internal void OnAvatarClear()
{ {
m_vrIK = null; m_vrIK = null;
m_origLeftHand = null;
m_origRightHand = null;
m_origLeftElbow = null;
m_origRightElbow = null;
m_hips = null; m_hips = null;
m_armsWeights = Vector2.zero;
m_leftArmIK = null; m_leftArmIK = null;
m_rightArmIK = null; m_rightArmIK = null;
m_leftTargetActive = false; m_leftTargetActive = false;
@ -256,11 +189,11 @@ namespace ml_lme
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand); Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_hand != null) if(l_hand != null)
m_leftHandTarget.localRotation = (m_inVR ? ms_offsetLeft : ms_offsetLeftDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation; m_leftHandTarget.localRotation = ms_offsetLeft * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand); l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null) if(l_hand != null)
m_rightHandTarget.localRotation = (m_inVR ? ms_offsetRight : ms_offsetRightDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation; m_rightHandTarget.localRotation = ms_offsetRight * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null) if(m_vrIK == null)
{ {
@ -304,10 +237,6 @@ namespace ml_lme
} }
else else
{ {
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate; m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate; m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
} }
@ -317,44 +246,56 @@ namespace ml_lme
} }
} }
internal void OnCalibrate() // VRIK updates
{
if(m_vrIK != null)
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
}
}
// IK updates
void OnIKPreUpdate() void OnIKPreUpdate()
{ {
m_armsWeights.Set( if(m_leftTargetActive)
m_vrIK.solver.leftArm.positionWeight,
m_vrIK.solver.leftArm.rotationWeight,
m_vrIK.solver.rightArm.positionWeight,
m_vrIK.solver.rightArm.rotationWeight
);
if(m_leftTargetActive && (Mathf.Approximately(m_armsWeights.x, 0f) || Mathf.Approximately(m_armsWeights.y, 0f)))
{ {
m_vrIKInfo.m_leftHandTarget = m_vrIK.solver.leftArm.target;
m_vrIKInfo.m_armsWeights.x = m_vrIK.solver.leftArm.positionWeight;
m_vrIKInfo.m_armsWeights.y = m_vrIK.solver.leftArm.rotationWeight;
m_vrIKInfo.m_leftElbowTarget = m_vrIK.solver.leftArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.x = m_vrIK.solver.leftArm.bendGoalWeight;
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = 1f; m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f; m_vrIK.solver.leftArm.rotationWeight = 1f;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
} }
if(m_rightTargetActive && (Mathf.Approximately(m_armsWeights.z, 0f) || Mathf.Approximately(m_armsWeights.w, 0f))) if(m_rightTargetActive)
{ {
m_vrIKInfo.m_rightHandTarget = m_vrIK.solver.rightArm.target;
m_vrIKInfo.m_armsWeights.z = m_vrIK.solver.rightArm.positionWeight;
m_vrIKInfo.m_armsWeights.w = m_vrIK.solver.rightArm.rotationWeight;
m_vrIKInfo.m_rightElbowTarget = m_vrIK.solver.rightArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.y = m_vrIK.solver.rightArm.bendGoalWeight;
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = 1f; m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f; m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
} }
} }
void OnIKPostUpdate() void OnIKPostUpdate()
{ {
m_vrIK.solver.leftArm.positionWeight = m_armsWeights.x; if(m_leftTargetActive)
m_vrIK.solver.leftArm.rotationWeight = m_armsWeights.y; {
m_vrIK.solver.rightArm.positionWeight = m_armsWeights.z; m_vrIK.solver.leftArm.target = m_vrIKInfo.m_leftHandTarget;
m_vrIK.solver.rightArm.rotationWeight = m_armsWeights.w; m_vrIK.solver.leftArm.positionWeight = m_vrIKInfo.m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_vrIKInfo.m_armsWeights.y;
m_vrIK.solver.leftArm.bendGoal = m_vrIKInfo.m_leftElbowTarget;
m_vrIK.solver.leftArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.x;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_vrIKInfo.m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = m_vrIKInfo.m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_vrIKInfo.m_armsWeights.w;
m_vrIK.solver.rightArm.bendGoal = m_vrIKInfo.m_rightElbowTarget;
m_vrIK.solver.rightArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.y;
}
} }
// Settings // Settings
@ -363,8 +304,7 @@ namespace ml_lme
m_enabled = p_state; m_enabled = p_state;
RefreshArmIK(); RefreshArmIK();
if(!m_enabled || m_fingersOnly) ResetTargetsStates();
RestoreVRIK();
} }
void OnFingersOnlyChange(bool p_state) void OnFingersOnlyChange(bool p_state)
@ -372,8 +312,7 @@ namespace ml_lme
m_fingersOnly = p_state; m_fingersOnly = p_state;
RefreshArmIK(); RefreshArmIK();
if(!m_enabled || m_fingersOnly) ResetTargetsStates();
RestoreVRIK();
} }
void OnTrackElbowsChange(bool p_state) void OnTrackElbowsChange(bool p_state)
@ -386,30 +325,15 @@ namespace ml_lme
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f); m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
} }
RestoreVRIK(); ResetTargetsStates();
} }
// Arbitrary // Arbitrary
void RestoreVRIK() void ResetTargetsStates()
{ {
if(m_vrIK != null)
{
if(m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false; m_leftTargetActive = false;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false; m_rightTargetActive = false;
} }
}
}
void RefreshArmIK() void RefreshArmIK()
{ {
@ -420,6 +344,65 @@ namespace ml_lme
} }
} }
void UpdateFingers(LeapParser.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, -0.5f - p_data.m_leftHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, -p_data.m_leftHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, 0.5f - p_data.m_leftHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, p_data.m_leftHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, 0.5f - p_data.m_leftHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, p_data.m_leftHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, 0.5f - p_data.m_leftHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, -p_data.m_leftHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, 0.5f - p_data.m_leftHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, -p_data.m_leftHand.m_spreads[4]);
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, -0.5f - p_data.m_rightHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, -p_data.m_rightHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, 0.5f - p_data.m_rightHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, p_data.m_rightHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, 0.5f - p_data.m_rightHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, p_data.m_rightHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, 0.5f - p_data.m_rightHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, -p_data.m_rightHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, 0.5f - p_data.m_rightHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, -p_data.m_rightHand.m_spreads[4]);
}
}
static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value) static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value)
{ {
if(p_pose.muscles.Length > p_index) if(p_pose.muscles.Length > p_index)

View file

@ -102,8 +102,36 @@ namespace ml_lme
if(Instance == this) if(Instance == this)
Instance = null; Instance = null;
if(m_leapHandLeft != null)
Object.Destroy(m_leapHandLeft);
m_leapHandLeft = null;
if(m_leapHandRight != null)
Object.Destroy(m_leapHandRight);
m_leapHandRight = null;
if(m_leapElbowLeft != null)
Object.Destroy(m_leapElbowLeft);
m_leapElbowLeft = null;
if(m_leapElbowRight != null)
Object.Destroy(m_leapElbowRight);
m_leapElbowRight = null;
if(m_leapControllerModel != null)
Object.Destroy(m_leapControllerModel);
m_leapControllerModel = null;
if(m_visualHands != null)
Object.Destroy(m_visualHands);
m_visualHands = null;
m_visualHandLeft = null;
m_visualHandRight = null;
Settings.DesktopOffsetChange -= this.OnDesktopOffsetChange; Settings.DesktopOffsetChange -= this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange -= this.OnModelVisibilityChange; Settings.ModelVisibilityChange -= this.OnModelVisibilityChange;
Settings.VisualHandsChange -= this.OnVisualHandsChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange; Settings.TrackingModeChange -= this.OnTrackingModeChange;
Settings.RootAngleChange -= this.OnRootAngleChange; Settings.RootAngleChange -= this.OnRootAngleChange;
Settings.HeadAttachChange -= this.OnHeadAttachChange; Settings.HeadAttachChange -= this.OnHeadAttachChange;
@ -114,7 +142,7 @@ namespace ml_lme
{ {
if(Settings.Enabled) if(Settings.Enabled)
{ {
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData(); LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present) if(l_data.m_leftHand.m_present)
{ {

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System.Collections; using System.Collections;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
@ -33,11 +32,6 @@ namespace ml_lme
null, null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetControllerRayScale)), typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetControllerRayScale)),
null, null,
@ -57,6 +51,10 @@ namespace ml_lme
{ {
if(ms_instance == this) if(ms_instance == this)
ms_instance = null; ms_instance = null;
if(m_leapManager != null)
Object.Destroy(m_leapManager);
m_leapManager = null;
} }
IEnumerator WaitForRootLogic() IEnumerator WaitForRootLogic()
@ -96,20 +94,6 @@ namespace ml_lme
} }
} }
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_leapManager != null)
m_leapManager.OnCalibrate();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnRayScale_Postfix(float __0) => ms_instance?.OnRayScale(__0); static void OnRayScale_Postfix(float __0) => ms_instance?.OnRayScale(__0);
void OnRayScale(float p_scale) void OnRayScale(float p_scale)
{ {

View file

@ -1,9 +1,5 @@
using System; using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ml_lme namespace ml_lme
{ {

View file

@ -1,7 +1,6 @@
using System.Reflection; [assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")] [assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")] [assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)] [assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonAdditionalCredits("NotAKidOnSteam")]

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_lme namespace ml_lme
{ {
static class Scripts static class ResourcesHandler
{ {
public static string GetEmbeddedScript(string p_name) public static string GetEmbeddedResource(string p_name)
{ {
string l_result = ""; string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly(); Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.InteractionSystem; using ABI_RC.Core.InteractionSystem;
using cohtml;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@ -32,7 +31,7 @@ namespace ml_lme
HeadY, HeadY,
HeadZ, HeadZ,
TrackElbows, TrackElbows,
Input, Interaction,
Gestures, Gestures,
InteractThreadhold, InteractThreadhold,
GripThreadhold, GripThreadhold,
@ -48,7 +47,7 @@ namespace ml_lme
public static bool HeadAttach { get; private set; } = false; public static bool HeadAttach { get; private set; } = false;
public static Vector3 HeadOffset { get; private set; } = new Vector3(0f, -0.3f, 0.15f); public static Vector3 HeadOffset { get; private set; } = new Vector3(0f, -0.3f, 0.15f);
public static bool TrackElbows { get; private set; } = true; public static bool TrackElbows { get; private set; } = true;
public static bool Input { get; private set; } = true; public static bool Interaction { get; private set; } = true;
public static bool Gestures { get; private set; } = false; public static bool Gestures { get; private set; } = false;
public static float InteractThreadhold { get; private set; } = 0.8f; public static float InteractThreadhold { get; private set; } = 0.8f;
public static float GripThreadhold { get; private set; } = 0.4f; public static float GripThreadhold { get; private set; } = 0.4f;
@ -66,7 +65,7 @@ namespace ml_lme
static public event Action<bool> HeadAttachChange; static public event Action<bool> HeadAttachChange;
static public event Action<Vector3> HeadOffsetChange; static public event Action<Vector3> HeadOffsetChange;
static public event Action<bool> TrackElbowsChange; static public event Action<bool> TrackElbowsChange;
static public event Action<bool> InputChange; static public event Action<bool> InteractionChange;
static public event Action<bool> GesturesChange; static public event Action<bool> GesturesChange;
static public event Action<float> InteractThreadholdChange; static public event Action<float> InteractThreadholdChange;
static public event Action<float> GripThreadholdChange; static public event Action<float> GripThreadholdChange;
@ -93,7 +92,7 @@ namespace ml_lme
ms_category.CreateEntry(ModSetting.HeadY.ToString(), (int)(HeadOffset.y * 100f)), ms_category.CreateEntry(ModSetting.HeadY.ToString(), (int)(HeadOffset.y * 100f)),
ms_category.CreateEntry(ModSetting.HeadZ.ToString(), (int)(HeadOffset.z * 100f)), ms_category.CreateEntry(ModSetting.HeadZ.ToString(), (int)(HeadOffset.z * 100f)),
ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), TrackElbows), ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), TrackElbows),
ms_category.CreateEntry(ModSetting.Input.ToString(), Input), ms_category.CreateEntry(ModSetting.Interaction.ToString(), Interaction),
ms_category.CreateEntry(ModSetting.Gestures.ToString(), Gestures), ms_category.CreateEntry(ModSetting.Gestures.ToString(), Gestures),
ms_category.CreateEntry(ModSetting.InteractThreadhold.ToString(), (int)(InteractThreadhold * 100f)), ms_category.CreateEntry(ModSetting.InteractThreadhold.ToString(), (int)(InteractThreadhold * 100f)),
ms_category.CreateEntry(ModSetting.GripThreadhold.ToString(), (int)(GripThreadhold * 100f)), ms_category.CreateEntry(ModSetting.GripThreadhold.ToString(), (int)(GripThreadhold * 100f)),
@ -116,15 +115,16 @@ namespace ml_lme
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () => ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{ {
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpToggle", new Action<string, string>(OnToggleUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpSlider", new Action<string, string>(OnSliderUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpDropdown", new Action<string, string>(OnDropdownUpdate)); ViewManager.Instance.gameMenuView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
}; };
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) => ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{ {
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js")); ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries) foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingLME", l_entry.DisplayName, l_entry.GetValueAsString()); ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
}; };
} }
@ -151,7 +151,7 @@ namespace ml_lme
(int)ms_entries[(int)ModSetting.HeadZ].BoxedValue (int)ms_entries[(int)ModSetting.HeadZ].BoxedValue
) * 0.01f; ) * 0.01f;
TrackElbows = (bool)ms_entries[(int)ModSetting.TrackElbows].BoxedValue; TrackElbows = (bool)ms_entries[(int)ModSetting.TrackElbows].BoxedValue;
Input = (bool)ms_entries[(int)ModSetting.Input].BoxedValue; Interaction = (bool)ms_entries[(int)ModSetting.Interaction].BoxedValue;
Gestures = (bool)ms_entries[(int)ModSetting.Gestures].BoxedValue; Gestures = (bool)ms_entries[(int)ModSetting.Gestures].BoxedValue;
InteractThreadhold = (int)ms_entries[(int)ModSetting.InteractThreadhold].BoxedValue * 0.01f; InteractThreadhold = (int)ms_entries[(int)ModSetting.InteractThreadhold].BoxedValue * 0.01f;
GripThreadhold = (int)ms_entries[(int)ModSetting.GripThreadhold].BoxedValue * 0.01f; GripThreadhold = (int)ms_entries[(int)ModSetting.GripThreadhold].BoxedValue * 0.01f;
@ -199,10 +199,10 @@ namespace ml_lme
} }
break; break;
case ModSetting.Input: case ModSetting.Interaction:
{ {
Input = bool.Parse(p_value); Interaction = bool.Parse(p_value);
InputChange?.Invoke(Input); InteractionChange?.Invoke(Interaction);
} }
break; break;

View file

@ -1,8 +1,6 @@
using ABI_RC.Core.Player; using ABI_RC.Core.Savior;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI; using ABI_RC.Core.UI;
using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.InputManagement;
using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
@ -10,10 +8,12 @@ namespace ml_lme
{ {
static class Utils static class Utils
{ {
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded); public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index)); public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index));
public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None); public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None); public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false) public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{ {
@ -31,6 +31,8 @@ namespace ml_lme
} }
} }
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static void Swap<T>(ref T lhs, ref T rhs) public static void Swap<T>(ref T lhs, ref T rhs)
{ {
T temp = lhs; T temp = lhs;

View file

@ -4,17 +4,14 @@ namespace ml_lme
{ {
class VisualHand class VisualHand
{ {
Transform m_root = null; readonly Transform m_wrist = null;
Transform m_wrist = null; readonly Transform[] m_fingers = null;
Transform[] m_fingers = null;
public VisualHand(Transform p_root, bool p_left) public VisualHand(Transform p_root, bool p_left)
{ {
m_root = p_root; if(p_root != null)
if(m_root != null)
{ {
m_wrist = m_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist"); m_wrist = p_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist");
if(m_wrist != null) if(m_wrist != null)
{ {
m_fingers = new Transform[20]; m_fingers = new Transform[20];
@ -47,7 +44,7 @@ namespace ml_lme
} }
} }
public void Update(GestureMatcher.HandData p_data) public void Update(LeapParser.HandData p_data)
{ {
if(m_wrist != null) if(m_wrist != null)
{ {

View file

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<PackageId>LeapMotionExtension</PackageId> <PackageId>LeapMotionExtension</PackageId>
<Version>1.4.0</Version> <Version>1.4.5</Version>
<Authors>SDraw</Authors> <Authors>SDraw</Authors>
<Company>None</Company> <Company>None</Company>
<Product>LeapMotionExtension</Product> <Product>LeapMotionExtension</Product>
@ -25,15 +25,15 @@
<None Remove="LeapMotionExtension.json" /> <None Remove="LeapMotionExtension.json" />
<None Remove="resources\leapmotion_controller.asset" /> <None Remove="resources\leapmotion_controller.asset" />
<None Remove="resources\leapmotion_hands.asset" /> <None Remove="resources\leapmotion_hands.asset" />
<None Remove="resources\menu.js" /> <None Remove="resources\mod_menu.js" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="resources\leapmotion_controller.asset" /> <EmbeddedResource Include="resources\leapmotion_controller.asset" />
<EmbeddedResource Include="resources\leapmotion_hands.asset" /> <EmbeddedResource Include="resources\leapmotion_hands.asset" />
<EmbeddedResource Include="resources\menu.js" /> <EmbeddedResource Include="resources\mod_menu.js" />
<EmbeddedResource Include="vendor\LeapSDK\lib\x64\LeapC.dll"> <EmbeddedResource Include="vendor\LeapSDK\lib\x64\LeapC.dll">
<Link>LeapC.dll</Link> <Link>resources/LeapC.dll</Link>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
@ -41,50 +41,62 @@
<Reference Include="0Harmony"> <Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Assembly-CSharp-firstpass"> <Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="cohtml.Net"> <Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="Cohtml.Runtime"> <Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="MelonLoader"> <Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath> <HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="ml_pmc"> <Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.AnimationModule"> <Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.AssetBundleModule"> <Reference Include="UnityEngine.AssetBundleModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
<Reference Include="UnityEngine.XRModule"> <Reference Include="UnityEngine.XRModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.XRModule.dll</HintPath> <HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.XRModule.dll</HintPath>
<Private>false</Private> <Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
@ -92,6 +104,10 @@
<Folder Include="vendor\LeapCSharp\" /> <Folder Include="vendor\LeapCSharp\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" /> <Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target> </Target>

View file

@ -1,442 +0,0 @@
// Add settings
var g_modSettingsLME = [];
engine.on('updateModSettingLME', function (_name, _value) {
for (var i = 0; i < g_modSettingsLME.length; i++) {
if (g_modSettingsLME[i].name == _name) {
g_modSettingsLME[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_lme(_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_lme(_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
}
}
function inp_dropdown_mod_lme(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.value = _obj.getAttribute('data-current');
this.options = _obj.getAttribute('data-options').split(',');
this.name = _obj.id;
this.opened = false;
this.keyValue = [];
this.type = _obj.getAttribute('data-type');
this.optionElements = [];
var self = this;
this.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
this.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
this.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
this.list = document.createElement('div');
this.list.className = 'valueList';
this.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
this.valueElement = document.createElement('div');
this.valueElement.className = 'dropdown-value';
this.updateOptions();
this.obj.appendChild(this.valueElement);
this.obj.appendChild(this.list);
this.valueElement.addEventListener('mousedown', this.openClick);
document.addEventListener('mousedown', this.globalClose);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
this.setOptions = function (options) {
self.options = options;
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue,
updateOptions: this.updateOptions,
setOptions: this.setOptions
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interaction input: </div>
<div class ="option-input">
<div id="Input" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Recognize Gestures: </div>
<div class ="option-input">
<div id="Gestures" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interact gesture threadhold: </div>
<div class ="option-input">
<div id="InteractThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="80"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grip gesture threadhold: </div>
<div class ="option-input">
<div id="GripThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').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_modSettingsLME[g_modSettingsLME.length] = new inp_toggle_mod_lme(l_toggles[i], 'MelonMod_LME_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_modSettingsLME[g_modSettingsLME.length] = new inp_slider_mod_lme(l_sliders[i], 'MelonMod_LME_Call_InpSlider');
}
//Update dropdowns in new menu block
let l_dropdowns = l_block.querySelectorAll('.inp_dropdown');
for (var i = 0; i < l_dropdowns.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_dropdown_mod_lme(l_dropdowns[i], 'MelonMod_LME_Call_InpDropdown');
}
}

View file

@ -0,0 +1,162 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </div>
<div class ="option-input">
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interaction input: </div>
<div class ="option-input">
<div id="Interaction" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Recognize gestures: </div>
<div class ="option-input">
<div id="Gestures" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interact gesture threadhold: </div>
<div class ="option-input">
<div id="InteractThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="80"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grip gesture threadhold: </div>
<div class ="option-input">
<div id="GripThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('LME', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_LME'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('LME', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_LME'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('LME', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_LME'));
}

View file

@ -18,6 +18,7 @@ namespace Leap
/// ///
/// @since 1.0 /// @since 1.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class Config public class Config
{ {
private Connection _connection; private Connection _connection;
@ -29,12 +30,15 @@ namespace Leap
/// Note that the Controller.Config provides a properly initialized Config object already. /// Note that the Controller.Config provides a properly initialized Config object already.
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(Connection.Key connectionKey) public Config(Connection.Key connectionKey)
{ {
_connection = Connection.GetConnection(connectionKey); _connection = Connection.GetConnection(connectionKey);
_connection.LeapConfigChange += handleConfigChange; _connection.LeapConfigChange += handleConfigChange;
_connection.LeapConfigResponse += handleConfigResponse; _connection.LeapConfigResponse += handleConfigResponse;
} }
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(int connectionId) : this(new Connection.Key(connectionId)) { } public Config(int connectionId) : this(new Connection.Key(connectionId)) { }
private void handleConfigChange(object sender, ConfigChangeEventArgs eventArgs) private void handleConfigChange(object sender, ConfigChangeEventArgs eventArgs)
@ -87,6 +91,7 @@ namespace Leap
/// ///
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Get<T>(string key, Action<T> onResult) public bool Get<T>(string key, Action<T> onResult)
{ {
uint requestId = _connection.GetConfigValue(key); uint requestId = _connection.GetConfigValue(key);
@ -107,6 +112,7 @@ namespace Leap
/// ///
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Set<T>(string key, T value, Action<bool> onResult) where T : IConvertible public bool Set<T>(string key, T value, Action<bool> onResult) where T : IConvertible
{ {
uint requestId = _connection.SetConfigValue<T>(key, value); uint requestId = _connection.SetConfigValue<T>(key, value);
@ -123,6 +129,7 @@ namespace Leap
/// Enumerates the possible data types for configuration values. /// Enumerates the possible data types for configuration values.
/// @since 1.0 /// @since 1.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public enum ValueType public enum ValueType
{ {
TYPE_UNKNOWN = 0, TYPE_UNKNOWN = 0,

View file

@ -359,9 +359,6 @@ namespace LeapInternal
StructMarshal<LEAP_CONFIG_CHANGE_EVENT>.PtrToStruct(_msg.eventStructPtr, out config_change_evt); StructMarshal<LEAP_CONFIG_CHANGE_EVENT>.PtrToStruct(_msg.eventStructPtr, out config_change_evt);
handleConfigChange(ref config_change_evt); handleConfigChange(ref config_change_evt);
break; break;
case eLeapEventType.eLeapEventType_ConfigResponse:
handleConfigResponse(ref _msg);
break;
case eLeapEventType.eLeapEventType_DroppedFrame: case eLeapEventType.eLeapEventType_DroppedFrame:
LEAP_DROPPED_FRAME_EVENT dropped_frame_evt; LEAP_DROPPED_FRAME_EVENT dropped_frame_evt;
StructMarshal<LEAP_DROPPED_FRAME_EVENT>.PtrToStruct(_msg.eventStructPtr, out dropped_frame_evt); StructMarshal<LEAP_DROPPED_FRAME_EVENT>.PtrToStruct(_msg.eventStructPtr, out dropped_frame_evt);
@ -749,55 +746,6 @@ namespace LeapInternal
} }
} }
private void handleConfigResponse(ref LEAP_CONNECTION_MESSAGE configMsg)
{
LEAP_CONFIG_RESPONSE_EVENT config_response_evt;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT>.PtrToStruct(configMsg.eventStructPtr, out config_response_evt);
string config_key = "";
_configRequests.TryGetValue(config_response_evt.requestId, out config_key);
if (config_key != null)
_configRequests.Remove(config_response_evt.requestId);
Config.ValueType dataType;
object value;
uint requestId = config_response_evt.requestId;
if (config_response_evt.value.type != eLeapValueType.eLeapValueType_String)
{
switch (config_response_evt.value.type)
{
case eLeapValueType.eLeapValueType_Boolean:
dataType = Config.ValueType.TYPE_BOOLEAN;
value = config_response_evt.value.boolValue;
break;
case eLeapValueType.eLeapValueType_Int32:
dataType = Config.ValueType.TYPE_INT32;
value = config_response_evt.value.intValue;
break;
case eLeapValueType.eLeapValueType_Float:
dataType = Config.ValueType.TYPE_FLOAT;
value = config_response_evt.value.floatValue;
break;
default:
dataType = Config.ValueType.TYPE_UNKNOWN;
value = new object();
break;
}
}
else
{
LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE config_ref_value;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE>.PtrToStruct(configMsg.eventStructPtr, out config_ref_value);
dataType = Config.ValueType.TYPE_STRING;
value = config_ref_value.value.stringValue;
}
SetConfigResponseEventArgs args = new SetConfigResponseEventArgs(config_key, dataType, value, requestId);
if (LeapConfigResponse != null)
{
LeapConfigResponse.DispatchOnContext(this, EventContext, args);
}
}
private void reportLogMessage(ref LEAP_LOG_EVENT logMsg) private void reportLogMessage(ref LEAP_LOG_EVENT logMsg)
{ {
if (LeapLogEvent != null) if (LeapLogEvent != null)

View file

@ -43,7 +43,6 @@ namespace Leap
bool _disposed = false; bool _disposed = false;
bool _supportsMultipleDevices = true; bool _supportsMultipleDevices = true;
string _serverNamespace = "Leap Service"; string _serverNamespace = "Leap Service";
Config _config;
/// <summary> /// <summary>
/// The SynchronizationContext used for dispatching events. /// The SynchronizationContext used for dispatching events.
@ -871,13 +870,12 @@ namespace Leap
/// ///
/// @since 1.0 /// @since 1.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config Config public Config Config
{ {
get get
{ {
if (_config == null) return null;
_config = new Config(this._connection.ConnectionKey);
return _config;
} }
} }

View file

@ -121,14 +121,6 @@ namespace Leap
IsStreaming = true; IsStreaming = true;
else else
IsStreaming = false; IsStreaming = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Smudged) == eLeapDeviceStatus.eLeapDeviceStatus_Smudged)
IsSmudged = true;
else
IsSmudged = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Robust) == eLeapDeviceStatus.eLeapDeviceStatus_Robust)
IsLightingBad = true;
else
IsLightingBad = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_LowResource) == eLeapDeviceStatus.eLeapDeviceStatus_LowResource) if ((status & eLeapDeviceStatus.eLeapDeviceStatus_LowResource) == eLeapDeviceStatus.eLeapDeviceStatus_LowResource)
IsLowResource = true; IsLowResource = true;
else else
@ -263,33 +255,61 @@ namespace Leap
return devicePose; return devicePose;
} }
//float[] data = new float[16]; bool deviceTransformAvailable = LeapC.GetDeviceTransformAvailable(Handle);
//eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
//if (result != eLeapRS.eLeapRS_Success || data == null) if (!deviceTransformAvailable)
//{ {
// devicePose = Pose.identity; devicePose = Pose.identity;
// return devicePose; poseSet = true;
//} return Pose.identity;
}
//// Get transform matrix and convert to unity space by inverting Z. float[] data = new float[16];
//Matrix4x4 transformMatrix = new Matrix4x4( eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
// new Vector4(data[0], data[1], data[2], data[3]),
// new Vector4(data[4], data[5], data[6], data[7]),
// new Vector4(data[8], data[9], data[10], data[11]),
// new Vector4(data[12], data[13], data[14], data[15]));
//Matrix4x4 toUnity = Matrix4x4.Scale(new Vector3(1, 1, -1));
//transformMatrix = toUnity * transformMatrix;
//// Identity matrix here means we have no device transform, also check validity. if (result != eLeapRS.eLeapRS_Success || data == null)
//if (transformMatrix.isIdentity || !transformMatrix.ValidTRS()) {
//{ devicePose = Pose.identity;
// devicePose = Pose.identity; poseSet = true;
// return devicePose; return Pose.identity;
//} }
//// Return the valid pose // Using the LEAP->OPENXR device transform matrix
//devicePose = new Pose(transformMatrix.GetColumn(3), transformMatrix.rotation); // Unitys matrices are generated as 4 columns:
Matrix4x4 deviceTransform = new Matrix4x4(
new Vector4(data[0], data[1], data[2], data[3]),
new Vector4(data[4], data[5], data[6], data[7]),
new Vector4(data[8], data[9], data[10], data[11]),
new Vector4(data[12], data[13], data[14], data[15]));
// An example of the expected matrix if it were 8cm forward from the head origin
// Unitys matrices are generated as 4 columns:
//Matrix4x4 deviceTransform = new Matrix4x4(
// new Vector4(-0.001f, 0, 0, 0),
// new Vector4(0, 0, -0.001f, 0),
// new Vector4(0, -0.001f, 0, 0),
// new Vector4(0, 0, -0.08f, 1));
if (deviceTransform == Matrix4x4.identity)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
Matrix4x4 openXRToUnity = new Matrix4x4(
new Vector4(1f, 0, 0, 0),
new Vector4(0, 1f, 0, 0),
new Vector4(0, 0, -1f, 0),
new Vector4(0, 0, 0, 1));
deviceTransform = openXRToUnity * deviceTransform;
Vector3 outputPos = deviceTransform.GetPosition();
//Quaternion outputRot = deviceTransform.rotation; // Note: the matrices we receive are not rotatrion matrices. This produces unexpected results
devicePose = new Pose(outputPos, Quaternion.identity);
poseSet = true; poseSet = true;
return devicePose; return devicePose;
@ -321,6 +341,7 @@ namespace Leap
/// over the Leap Motion cameras. /// over the Leap Motion cameras.
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("IsSmudged is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsSmudged { get; internal set; } public bool IsSmudged { get; internal set; }
/// <summary> /// <summary>
@ -335,6 +356,7 @@ namespace Leap
/// isLightingBad() is true. /// isLightingBad() is true.
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("IsLightingBad is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsLightingBad { get; internal set; } public bool IsLightingBad { get; internal set; }
/// <summary> /// <summary>

View file

@ -179,6 +179,7 @@ namespace Leap
/// Provides the configuration key, whether the change was successful, and the id of the original change request. /// Provides the configuration key, whether the change was successful, and the id of the original change request.
/// @since 3.0 /// @since 3.0
/// </summary> /// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class SetConfigResponseEventArgs : LeapEventArgs public class SetConfigResponseEventArgs : LeapEventArgs
{ {
public SetConfigResponseEventArgs(string config_key, Config.ValueType dataType, object value, uint requestId) : base(LeapEvent.EVENT_CONFIG_RESPONSE) public SetConfigResponseEventArgs(string config_key, Config.ValueType dataType, object value, uint requestId) : base(LeapEvent.EVENT_CONFIG_RESPONSE)

View file

@ -24,7 +24,7 @@ namespace Leap
long Now(); long Now();
bool IsConnected { get; } bool IsConnected { get; }
Config Config { get; }
DeviceList Devices { get; } DeviceList Devices { get; }
event EventHandler<ConnectionEventArgs> Connect; event EventHandler<ConnectionEventArgs> Connect;

View file

@ -1052,6 +1052,9 @@ namespace LeapInternal
[DllImport("LeapC", EntryPoint = "LeapGetDeviceInfo", CharSet = CharSet.Ansi)] [DllImport("LeapC", EntryPoint = "LeapGetDeviceInfo", CharSet = CharSet.Ansi)]
public static extern eLeapRS GetDeviceInfo(IntPtr hDevice, ref LEAP_DEVICE_INFO info); public static extern eLeapRS GetDeviceInfo(IntPtr hDevice, ref LEAP_DEVICE_INFO info);
[DllImport("LeapC", EntryPoint = "LeapDeviceTransformAvailable")]
public static extern bool GetDeviceTransformAvailable(IntPtr hDevice);
[DllImport("LeapC", EntryPoint = "LeapGetDeviceTransform")] [DllImport("LeapC", EntryPoint = "LeapGetDeviceTransform")]
public static extern eLeapRS GetDeviceTransform(IntPtr hDevice, [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)] float[] transform); public static extern eLeapRS GetDeviceTransform(IntPtr hDevice, [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)] float[] transform);

View file

@ -41,6 +41,17 @@ namespace Leap
return new Frame().CopyFrom(frame).Transform(transform); return new Frame().CopyFrom(frame).Transform(transform);
} }
/**
* Returns a new frame that is a copy of a frame, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied frame.
*/
public static Frame TransformedCopy(this Frame frame, Vector3 position, Quaternion rotation)
{
return new Frame().CopyFrom(frame).Transform(new LeapTransform(position, rotation));
}
/** /**
* Does an in-place rigid transformation of a Hand. * Does an in-place rigid transformation of a Hand.
* *
@ -79,6 +90,17 @@ namespace Leap
return new Hand().CopyFrom(hand).Transform(transform); return new Hand().CopyFrom(hand).Transform(transform);
} }
/**
* Returns a new hand that is a copy of a hand, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied hand.
*/
public static Hand TransformedCopy(this Hand hand, Vector3 position, Quaternion rotation)
{
return new Hand().CopyFrom(hand).Transform(new LeapTransform(position, rotation));
}
/** /**
* Does an in-place rigid transformation of a Finger. * Does an in-place rigid transformation of a Finger.
* *

View file

@ -88,7 +88,6 @@ SRC_DIR=/usr/share/doc/ultraleap-hand-tracking-service/samples
BUILD_TYPE='Release' BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=/usr/bin/ultraleap-tracking-samples REPOS_INSTALL_ROOT=/usr/bin/ultraleap-tracking-samples
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example ` cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" ` -DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
@ -96,9 +95,32 @@ cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE} cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
``` ```
### ARM64 Linux
1. Open the top level directory of the untarred release and select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
2. Set the CMAKE_PREFIX_PATH directory to the absolute location of LeapSDK
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin
PREFIX_PATH=$(pwd)/LeapSDK
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example"`
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DCMAKE_PREFIX_PATH="${PREFIX_PATH}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### MacOS ### MacOS
1. Open CMake using /Library/Application Support/Ultraleap/LeapSDK/samples as the source directory 1. Open CMake using /Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use 2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
@ -107,7 +129,7 @@ cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --confi
4. Open and build the CMake generated project files. For more help, see the CMake documentation. 4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be : * An example script would be :
```bash ```bash
SRC_DIR='/Library/Application Support/Ultraleap/LeapSDK/samples' SRC_DIR='/Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples'
BUILD_TYPE='Release' BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more