Skip to content

Commit

Permalink
New mod: AvatarMotionTweaker
Browse files Browse the repository at this point in the history
  • Loading branch information
SDraw committed Aug 13, 2022
1 parent a6539f4 commit c4a92bf
Show file tree
Hide file tree
Showing 23 changed files with 609 additions and 23 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
Merged set of MelonLoader mods for ChilloutVR.

**State table for game build 2022r166:**
**State table for game build 2022r166p1:**
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | Current Status | Notes |
|-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------|
| Avatar Change Info | ml_aci | 1.0.1 | Pending approval | Working |
| Avatar Motion Tweaker | ml_amt | 1.0.0 | Pending approval | Working |
| Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working |
| Four Point Tracking | ml_fpt | 1.0.1 | Pending approval | Working |
| Leap Motion Extension | ml_lme | 1.1.2 | Pending approval | Working |
| Leap Motion Extension | ml_lme | 1.1.3 | Pending approval | Working |
Binary file added ml_amt/.github/img_02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_05.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_06.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_07.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ml_amt/.github/img_08.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions ml_amt/Main.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using ABI_RC.Core.Player;

namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static AvatarMotionTweaker ms_instance = null;

MotionTweaker m_localTweaker = null;

public override void OnApplicationStart()
{
ms_instance = this;

Settings.Init();
Settings.CrouchLimitChange += this.OnCrouchLimitChange;

HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAvatarClear_Postfix), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
);

HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnLocalAvatarSetup_Postfix), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
);

MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}

System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;

m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
}

void OnCrouchLimitChange(float p_value)
{
if(m_localTweaker != null)
m_localTweaker.SetCrouchLimit(p_value);
}

static void OnLocalAvatarSetup_Postfix() => ms_instance?.OnLocalAvatarSetup();
void OnLocalAvatarSetup()
{
if((m_localTweaker != null) && !PlayerSetup.Instance._inVr)
m_localTweaker.OnAvatarSetup();
}

static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
if(m_localTweaker != null)
m_localTweaker.OnAvatarClear();
}
}
}
139 changes: 139 additions & 0 deletions ml_amt/MotionTweaker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using ABI_RC.Core.Player;
using System.Collections.Generic;
using UnityEngine;

namespace ml_amt
{
class MotionTweaker : MonoBehaviour
{
enum ParameterType
{
Upright
}
enum ParameterSyncType
{
Local,
Synced
}

struct AdditionalParameterInfo
{
public ParameterType m_type;
public ParameterSyncType m_sync;
public string m_name;
public int m_hash; // For local only
}

static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);

RootMotion.FinalIK.VRIK m_vrIk = null;

bool m_standing = true;
float m_currentUpright = 1f;
float m_locomotionWeight = 1f;
float m_crouchLimit = 0.65f;
bool m_customCrouchLimit = false;

readonly List<AdditionalParameterInfo> m_parameters = null;

public MotionTweaker()
{
m_parameters = new List<AdditionalParameterInfo>();
}

void Start()
{
if(PlayerSetup.Instance._inVr)
PlayerSetup.Instance.avatarSetupCompleted.AddListener(this.OnAvatarSetup);
}

void Update()
{
// Update upright
Matrix4x4 l_hmdMatrix = PlayerSetup.Instance.transform.GetMatrix().inverse * (PlayerSetup.Instance._inVr ? PlayerSetup.Instance.vrHeadTracker.transform.GetMatrix() : PlayerSetup.Instance.desktopCameraRig.transform.GetMatrix());
float l_currentHeight = Mathf.Clamp((l_hmdMatrix * ms_pointVector).y, 0f, float.MaxValue);
float l_avatarViewHeight = Mathf.Clamp(PlayerSetup.Instance.GetViewPointHeight() * PlayerSetup.Instance._avatar.transform.localScale.y, 0f, float.MaxValue);
m_currentUpright = Mathf.Clamp((((l_currentHeight > 0f) && (l_avatarViewHeight > 0f)) ? (l_currentHeight / l_avatarViewHeight) : 0f), 0f, 1f);
m_standing = (m_currentUpright > m_crouchLimit);

if((m_vrIk != null) && m_vrIk.enabled && !PlayerSetup.Instance._movementSystem.sitting && (PlayerSetup.Instance._movementSystem.movementVector.magnitude <= Mathf.Epsilon) && !PlayerSetup.Instance.fullBodyActive)
{
m_locomotionWeight = Mathf.Lerp(m_locomotionWeight, m_standing ? 1f : 0f, 0.5f);
m_vrIk.solver.locomotion.weight = m_locomotionWeight;
}

if(m_parameters.Count > 0)
{
foreach(AdditionalParameterInfo l_param in m_parameters)
{
switch(l_param.m_type)
{
case ParameterType.Upright:
{
switch(l_param.m_sync)
{
case ParameterSyncType.Local:
PlayerSetup.Instance._animator.SetFloat(l_param.m_hash, m_currentUpright);
break;
case ParameterSyncType.Synced:
PlayerSetup.Instance.changeAnimatorParam(l_param.m_name, m_currentUpright);
break;
}
}
break;
}
}
}
}

public void OnAvatarClear()
{
m_vrIk = null;
m_standing = true;
m_parameters.Clear();
m_locomotionWeight = 1f;
m_crouchLimit = 0.65f;
m_customCrouchLimit = false;
}

public void OnAvatarSetup()
{
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<RootMotion.FinalIK.VRIK>();

// Parse animator parameters
AnimatorControllerParameter[] l_params = PlayerSetup.Instance._animator.parameters;
ParameterType[] l_enumParams = (ParameterType[])System.Enum.GetValues(typeof(ParameterType));

foreach(var l_param in l_params)
{
foreach(var l_enumParam in l_enumParams)
{
if(l_param.name.Contains(l_enumParam.ToString()) && (m_parameters.FindIndex(p => p.m_type == l_enumParam) == -1))
{
bool l_local = (l_param.name[0] == '#');

m_parameters.Add(new AdditionalParameterInfo
{
m_type = l_enumParam,
m_sync = (l_local ? ParameterSyncType.Local : ParameterSyncType.Synced),
m_name = l_param.name,
m_hash = (l_local ? l_param.nameHash : 0)
});

break;
}
}
}

Transform l_customLimit = PlayerSetup.Instance._avatar.transform.Find("CrouchLimit");
m_customCrouchLimit = (l_customLimit != null);
m_crouchLimit = m_customCrouchLimit ? Mathf.Clamp(l_customLimit.localPosition.y, 0f, 1f) : Settings.CrouchLimit;
}

public void SetCrouchLimit(float p_value)
{
if(!m_customCrouchLimit)
m_crouchLimit = Mathf.Clamp(p_value, 0f, 1f);
}
}
}
10 changes: 10 additions & 0 deletions ml_amt/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Reflection;

[assembly: AssemblyTitle("AvatarMotionTweaker")]
[assembly: AssemblyVersion("1.0.0")]
[assembly: AssemblyFileVersion("1.0.0")]

[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "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)]
38 changes: 38 additions & 0 deletions ml_amt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Avatar Motion Tweaker
This mod adds `Upright` parameter for usage in AAS animator and allows disabling legs autostep upon reaching specific `Upright` value.

![](.github/img_01.png)

# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_amt.dll` in `Mods` folder of game

# Usage
Available mod's settings in `Settings - Implementation - Avatar Motion Tweaker`:
* **Legs locomotion upright limit:** defines upright limit of legs autostep. If HMD tracking goes below set limit, legs autostep is disabled. Default value - 65.
* Limit can be overrided by avatar. For this avatar has to have child gameobject with name `CrouchLimit` and its Y-axis location will be used as limit, should be in range [0.0, 1.0].

Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height. Range - [0.0,1.0] (0.0 - floor, 1.0 - full standing).
* Note: can be set as local-only (not synced) if starts with `#` character.

## Example of usage in AAS animator for mixed desktop and VR
* To differentiate between desktop and VR players use `CVR Parameter Stream` component on avatar's root gameobject. As example, `InVR` and `InFBT` are boolean typed animator parameters:
![](.github/img_02.png)
* Add additional transitions between standing, crouching and proning blend trees:
![](.github/img_03.png)
* Add conditions for new VR transitions:
* Standing -> Crouching:
![](.github/img_04.png)
* Crouching -> Standing:
![](.github/img_05.png)
* Crouching -> Proning:
![](.github/img_06.png)
* Proning -> Crouching:
![](.github/img_07.png)
* Add condition check for all desktop transitions:
![](.github/img_08.png)

# Notes
* Sometimes after restoring legs autostep avatar's torso shakes, currently investigating solution.
26 changes: 26 additions & 0 deletions ml_amt/Scripts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;

namespace ml_amt
{
static class Scripts
{
public static string GetEmbeddedScript(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;

try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}
catch(Exception) { }

return l_result;
}
}
}
83 changes: 83 additions & 0 deletions ml_amt/Settings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace ml_amt
{
static class Settings
{
enum ModSetting
{
CrouchLimit = 0
};

static float ms_crouchLimit = 0.65f;

static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;

static public event Action<float> CrouchLimitChange;

public static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT");

ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
ms_entries.Add(ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), 65));

Load();

MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}

static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;

ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingAMT", l_entry.DisplayName, l_entry.GetValueAsString());
};
}

static void Load()
{
ms_crouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
}

static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.CrouchLimit:
{
ms_crouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
CrouchLimitChange?.Invoke(ms_crouchLimit);
} break;
}

ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}

public static float CrouchLimit
{
get => ms_crouchLimit;
}
}
}
13 changes: 13 additions & 0 deletions ml_amt/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using UnityEngine;

namespace ml_amt
{
static class Utils
{
// Extensions
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);
}
}
}
Loading

0 comments on commit c4a92bf

Please sign in to comment.