Emotes custom detection

VRIK component checks for desktop mode
Disable head rotation when emote is active
This commit is contained in:
SDraw 2022-10-08 02:28:22 +03:00
parent 71c0068652
commit 4dafd9dcd7
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
9 changed files with 93 additions and 26 deletions

View file

@ -4,8 +4,8 @@ Merged set of MelonLoader mods for ChilloutVR.
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | Current Status | Notes | | Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | Current Status | Notes |
|-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------| |-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------|
| Avatar Change Info | ml_aci | 1.0.2 | Yes | Working | | Avatar Change Info | ml_aci | 1.0.2 | Yes | Working |
| Avatar Motion Tweaker | ml_amt | 1.1.1 | On review | Working | | Avatar Motion Tweaker | ml_amt | 1.1.2 | On review | Working |
| Desktop Head Tracking | ml_dht | 1.0.4 | On review | Working | | Desktop Head Tracking | ml_dht | 1.0.5 | On review | Working |
| Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working | | Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working |
| Four Point Tracking | ml_fpt | 1.0.7 | On review | Working | | Four Point Tracking | ml_fpt | 1.0.7 | On review | Working |
| Leap Motion Extension | ml_lme | 1.2.0 | On review | Working | | Leap Motion Extension | ml_lme | 1.2.0 | On review | Working |

View file

@ -21,6 +21,7 @@ namespace ml_amt
Settings.PoseTransitionsChange += this.OnPoseTransitonsChange; Settings.PoseTransitionsChange += this.OnPoseTransitonsChange;
Settings.AdjustedMovementChange += this.OnAdjustedMovementChange; Settings.AdjustedMovementChange += this.OnAdjustedMovementChange;
Settings.IKOverrideFlyChange += this.OnIKOverrideFlyChange; Settings.IKOverrideFlyChange += this.OnIKOverrideFlyChange;
Settings.DetectEmotesChange += this.OnDetectEmotesChange;
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
@ -49,6 +50,7 @@ namespace ml_amt
m_localTweaker.SetPoseTransitions(Settings.PoseTransitions); m_localTweaker.SetPoseTransitions(Settings.PoseTransitions);
m_localTweaker.SetAdjustedMovement(Settings.AdjustedMovement); m_localTweaker.SetAdjustedMovement(Settings.AdjustedMovement);
m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly); m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly);
m_localTweaker.SetDetectEmotes(Settings.DetectEmotes);
} }
void OnIKOverrideCrouchChange(bool p_state) void OnIKOverrideCrouchChange(bool p_state)
@ -86,6 +88,11 @@ namespace ml_amt
if(m_localTweaker != null) if(m_localTweaker != null)
m_localTweaker.SetIKOverrideFly(p_state); m_localTweaker.SetIKOverrideFly(p_state);
} }
void OnDetectEmotesChange(bool p_state)
{
if(m_localTweaker != null)
m_localTweaker.SetDetectEmotes(p_state);
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear(); static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear() void OnAvatarClear()

View file

@ -2,14 +2,17 @@
using ABI_RC.Systems.MovementSystem; using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK; using RootMotion.FinalIK;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using UnityEngine; using UnityEngine;
namespace ml_amt namespace ml_amt
{ {
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour class MotionTweaker : MonoBehaviour
{ {
static System.Reflection.FieldInfo ms_rootVelocity = typeof(IKSolverVR).GetField("rootVelocity", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); static readonly FieldInfo ms_rootVelocity = typeof(IKSolverVR).GetField("rootVelocity", BindingFlags.NonPublic | BindingFlags.Instance);
static System.Reflection.FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
enum ParameterType enum ParameterType
{ {
@ -41,6 +44,8 @@ namespace ml_amt
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f); static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
VRIK m_vrIk = null; VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_ikWeight = 1f; // Original weight
float m_locomotionWeight = 1f; // Original weight float m_locomotionWeight = 1f; // Original weight
float m_avatarScale = 1f; // Instantiated scale float m_avatarScale = 1f; // Instantiated scale
@ -64,6 +69,9 @@ namespace ml_amt
bool m_customLocomotionOffset = false; bool m_customLocomotionOffset = false;
Vector3 m_locomotionOffset = Vector3.zero; Vector3 m_locomotionOffset = Vector3.zero;
bool m_detectEmotes = true;
bool m_emoteActive = false;
readonly List<AdditionalParameterInfo> m_parameters = null; readonly List<AdditionalParameterInfo> m_parameters = null;
public MotionTweaker() public MotionTweaker()
@ -83,7 +91,7 @@ namespace ml_amt
m_upright = Mathf.Clamp(((l_avatarViewHeight > 0f) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f); m_upright = Mathf.Clamp(((l_avatarViewHeight > 0f) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f);
PoseState l_poseState = (m_upright <= m_proneLimit) ? PoseState.Proning : ((m_upright <= m_crouchLimit) ? PoseState.Crouching : PoseState.Standing); PoseState l_poseState = (m_upright <= m_proneLimit) ? PoseState.Proning : ((m_upright <= m_crouchLimit) ? PoseState.Crouching : PoseState.Standing);
if((m_vrIk != null) && m_vrIk.enabled) if(PlayerSetup.Instance._inVr && (m_vrIk != null) && m_vrIk.enabled)
{ {
if(m_poseState != l_poseState) if(m_poseState != l_poseState)
{ {
@ -115,6 +123,14 @@ namespace ml_amt
m_poseState = l_poseState; m_poseState = l_poseState;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
AnimatorStateInfo l_animState = PlayerSetup.Instance._animator.GetCurrentAnimatorStateInfo(m_locomotionLayer);
m_emoteActive = (l_animState.tagHash == ms_emoteHash);
}
else
m_emoteActive = false;
if(m_parameters.Count > 0) if(m_parameters.Count > 0)
{ {
foreach(AdditionalParameterInfo l_param in m_parameters) foreach(AdditionalParameterInfo l_param in m_parameters)
@ -129,7 +145,7 @@ namespace ml_amt
PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_upright); PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_upright);
break; break;
case ParameterSyncType.Synced: case ParameterSyncType.Synced:
PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, m_upright); PlayerSetup.Instance.animatorManager.SetAnimatorParameterFloat(l_param.m_name, m_upright);
break; break;
} }
} }
@ -143,7 +159,7 @@ namespace ml_amt
PlayerSetup.Instance._animator.SetBool(l_param.m_hash, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance)); PlayerSetup.Instance._animator.SetBool(l_param.m_hash, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance));
break; break;
case ParameterSyncType.Synced: case ParameterSyncType.Synced:
PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance) ? 1f : 0f); PlayerSetup.Instance.animatorManager.SetAnimatorParameterBool(l_param.m_name, (bool)ms_groundedRaw.GetValue(MovementSystem.Instance));
break; break;
} }
} }
@ -157,6 +173,7 @@ namespace ml_amt
public void OnAvatarClear() public void OnAvatarClear()
{ {
m_vrIk = null; m_vrIk = null;
m_locomotionLayer = -1;
m_avatarReady = false; m_avatarReady = false;
m_compatibleAvatar = false; m_compatibleAvatar = false;
m_poseState = PoseState.Standing; m_poseState = PoseState.Standing;
@ -165,12 +182,14 @@ namespace ml_amt
m_customLocomotionOffset = false; m_customLocomotionOffset = false;
m_locomotionOffset = Vector3.zero; m_locomotionOffset = Vector3.zero;
m_avatarScale = 1f; m_avatarScale = 1f;
m_emoteActive = false;
m_parameters.Clear(); m_parameters.Clear();
} }
public void OnCalibrateAvatar() public void OnCalibrateAvatar()
{ {
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>(); m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
// Parse animator parameters // Parse animator parameters
AnimatorControllerParameter[] l_params = PlayerSetup.Instance._animator.parameters; AnimatorControllerParameter[] l_params = PlayerSetup.Instance._animator.parameters;
@ -227,16 +246,24 @@ namespace ml_amt
void OnIKPreUpdate() void OnIKPreUpdate()
{ {
m_ikWeight = m_vrIk.solver.IKPositionWeight;
m_locomotionWeight = m_vrIk.solver.locomotion.weight; m_locomotionWeight = m_vrIk.solver.locomotion.weight;
if((m_ikOverrideCrouch && (m_poseState != PoseState.Standing)) || (m_ikOverrideProne && (m_poseState == PoseState.Proning))) if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.locomotion.weight = 0f; m_vrIk.solver.IKPositionWeight = 0f;
if(m_ikOverrideFly && MovementSystem.Instance.flying)
m_vrIk.solver.locomotion.weight = 0f; if(PlayerSetup.Instance._inVr)
{
if((m_ikOverrideCrouch && (m_poseState != PoseState.Standing)) || (m_ikOverrideProne && (m_poseState == PoseState.Proning)))
m_vrIk.solver.locomotion.weight = 0f;
if(m_ikOverrideFly && MovementSystem.Instance.flying)
m_vrIk.solver.locomotion.weight = 0f;
}
} }
void OnIKPostUpdate() void OnIKPostUpdate()
{ {
m_vrIk.solver.IKPositionWeight = m_ikWeight;
m_vrIk.solver.locomotion.weight = m_locomotionWeight; m_vrIk.solver.locomotion.weight = m_locomotionWeight;
} }
@ -282,5 +309,9 @@ namespace ml_amt
{ {
m_ikOverrideFly = p_state; m_ikOverrideFly = p_state;
} }
public void SetDetectEmotes(bool p_state)
{
m_detectEmotes = p_state;
}
} }
} }

View file

@ -1,10 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyTitle("AvatarMotionTweaker")] [assembly: AssemblyTitle("AvatarMotionTweaker")]
[assembly: AssemblyVersion("1.1.1")] [assembly: AssemblyVersion("1.1.2")]
[assembly: AssemblyFileVersion("1.1.1")] [assembly: AssemblyFileVersion("1.1.2")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.1.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.1.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

@ -15,7 +15,8 @@ namespace ml_amt
ProneLimit, ProneLimit,
PoseTransitions, PoseTransitions,
AdjustedMovement, AdjustedMovement,
IKOverrideFly IKOverrideFly,
DetectEmotes
}; };
static bool ms_ikOverrideCrouch = true; static bool ms_ikOverrideCrouch = true;
@ -25,6 +26,7 @@ namespace ml_amt
static bool ms_poseTransitions = true; static bool ms_poseTransitions = true;
static bool ms_adjustedMovement = true; static bool ms_adjustedMovement = true;
static bool ms_ikOverrideFly = true; static bool ms_ikOverrideFly = true;
static bool ms_detectEmotes = 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;
@ -36,6 +38,7 @@ namespace ml_amt
static public event Action<bool> PoseTransitionsChange; static public event Action<bool> PoseTransitionsChange;
static public event Action<bool> AdjustedMovementChange; static public event Action<bool> AdjustedMovementChange;
static public event Action<bool> IKOverrideFlyChange; static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> DetectEmotesChange;
public static void Init() public static void Init()
{ {
@ -49,6 +52,7 @@ namespace ml_amt
ms_entries.Add(ms_category.CreateEntry(ModSetting.PoseTransitions.ToString(), true)); ms_entries.Add(ms_category.CreateEntry(ModSetting.PoseTransitions.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.AdjustedMovement.ToString(), true)); ms_entries.Add(ms_category.CreateEntry(ModSetting.AdjustedMovement.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), true)); ms_entries.Add(ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), true));
ms_entries.Add(ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), true));
Load(); Load();
@ -86,6 +90,7 @@ namespace ml_amt
ms_poseTransitions = (bool)ms_entries[(int)ModSetting.PoseTransitions].BoxedValue; ms_poseTransitions = (bool)ms_entries[(int)ModSetting.PoseTransitions].BoxedValue;
ms_adjustedMovement = (bool)ms_entries[(int)ModSetting.AdjustedMovement].BoxedValue; ms_adjustedMovement = (bool)ms_entries[(int)ModSetting.AdjustedMovement].BoxedValue;
ms_ikOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue; ms_ikOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
ms_detectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
} }
static void OnSliderUpdate(string p_name, string p_value) static void OnSliderUpdate(string p_name, string p_value)
@ -152,6 +157,13 @@ namespace ml_amt
ms_ikOverrideFly = bool.Parse(p_value); ms_ikOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(ms_ikOverrideFly); IKOverrideFlyChange?.Invoke(ms_ikOverrideFly);
} break; } break;
case ModSetting.DetectEmotes:
{
ms_detectEmotes = bool.Parse(p_value);
DetectEmotesChange?.Invoke(ms_detectEmotes);
}
break;
} }
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value); ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
@ -186,5 +198,9 @@ namespace ml_amt
{ {
get => ms_ikOverrideFly; get => ms_ikOverrideFly;
} }
public static bool DetectEmotes
{
get => ms_detectEmotes;
}
} }
} }

View file

@ -228,6 +228,13 @@ function inp_toggle_mod_amt(_obj, _callbackName) {
<div id="AdjustedMovement" class ="inp_toggle no-scroll" data-current="true"></div> <div id="AdjustedMovement" class ="inp_toggle no-scroll" data-current="true"></div>
</div> </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>
`; `;
document.getElementById('settings-implementation').appendChild(l_block); document.getElementById('settings-implementation').appendChild(l_block);

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components; using ABI.CCK.Components;
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using System.Reflection;
using UnityEngine; using UnityEngine;
namespace ml_dht namespace ml_dht
@ -7,6 +8,8 @@ namespace ml_dht
[DisallowMultipleComponent] [DisallowMultipleComponent]
class HeadTracked : MonoBehaviour class HeadTracked : MonoBehaviour
{ {
static FieldInfo ms_emotePlaying = typeof(PlayerSetup).GetField("_emotePlaying", BindingFlags.NonPublic | BindingFlags.Instance);
bool m_enabled = false; bool m_enabled = false;
float m_smoothing = 0.5f; float m_smoothing = 0.5f;
bool m_mirrored = false; bool m_mirrored = false;
@ -47,7 +50,9 @@ namespace ml_dht
if(m_enabled && (m_headBone != null)) if(m_enabled && (m_headBone != null))
{ {
m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_avatarDescriptor.transform.rotation * (m_headRotation * m_bindRotation), m_smoothing); m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_avatarDescriptor.transform.rotation * (m_headRotation * m_bindRotation), m_smoothing);
m_headBone.rotation = m_lastHeadRotation;
if(!(bool)ms_emotePlaying.GetValue(PlayerSetup.Instance))
m_headBone.rotation = m_lastHeadRotation;
} }
} }
@ -71,7 +76,7 @@ namespace ml_dht
{ {
if(m_avatarDescriptor != null) if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false; m_avatarDescriptor.useVisemeLipsync = false;
float l_weight = Mathf.Clamp(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y)), 0f, 1f) * 100f; float l_weight = Mathf.Clamp(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y)), 0f, 1f) * 100f;
p_component.BlendShapeValues[(int)ViveSR.anipal.Lip.LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f; p_component.BlendShapeValues[(int)ViveSR.anipal.Lip.LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f;

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components; using ABI.CCK.Components;
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using System.Reflection;
namespace ml_dht namespace ml_dht
{ {
@ -33,22 +34,22 @@ namespace ml_dht
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)), typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnAvatarClear_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.CalibrateAvatar)), typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.CalibrateAvatar)),
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnCalibrateAvatar_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnCalibrateAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(CVREyeController).GetMethod("Update", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic), typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
HarmonyInstance.Patch( HarmonyInstance.Patch(
typeof(CVRFaceTracking).GetMethod("Update", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic), typeof(CVRFaceTracking).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null, null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)) new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
); );
MelonLoader.MelonCoroutines.Start(WaitForPlayer()); MelonLoader.MelonCoroutines.Start(WaitForPlayer());

View file

@ -1,10 +1,10 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyTitle("DesktopHeadTracking")] [assembly: AssemblyTitle("DesktopHeadTracking")]
[assembly: AssemblyVersion("1.0.4")] [assembly: AssemblyVersion("1.0.5")]
[assembly: AssemblyFileVersion("1.0.4")] [assembly: AssemblyFileVersion("1.0.5")]
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.0.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")] [assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.0.5", "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)]