Skip to content

Commit

Permalink
New mod: PlayerMovementCopycat
Browse files Browse the repository at this point in the history
Collider radius upon avatar scaling
  • Loading branch information
SDraw committed May 4, 2023
1 parent 3dceb85 commit 0f5e148
Show file tree
Hide file tree
Showing 16 changed files with 1,122 additions and 7 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ Merged set of MelonLoader mods for ChilloutVR.
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) | Current Status | Notes |
|-----------|------------|----------------|-----------------------------------------------------------------|----------------|-------|
| Avatar Change Info | ml_aci | 1.0.3 | Retired | Retired | Superseded by `Extended Game Notifications`
| Avatar Motion Tweaker | ml_amt | 1.2.7 | Yes | Working |
| Avatar Motion Tweaker | ml_amt | 1.2.8 | Yes, update review | Working |
| Desktop Head Tracking | ml_dht | 1.1.3 | Yes | Working |
| Desktop Reticle Switch | ml_drs | 1.0.0 | Yes | Working |
| Extended Game Notifications | ml_egn | 1.0.2 | Yes | Working
| Four Point Tracking | ml_fpt | 1.0.9 | Retired | Deprecated | In-game feature since 2022r170 update
| Leap Motion Extension | ml_lme | 1.3.6 | Yes, update review | Working |
| Leap Motion Extension | ml_lme | 1.3.6 | Yes | Working |
| Pickup Arm Movement | ml_pam | 1.0.4 | Yes | Working |
| Player Ragdoll Mod | ml_prm | 1.0.3 | Yes, update review | Working |
| Player Movement Copycat | ml_pmc | 1.0.0 | On review | Working |
| Player Ragdoll Mod | ml_prm | 1.0.4 | Yes, update review | Working |
| Server Connection Info | ml_sci | 1.0.2 | Retired | Retired | Superseded by `Extended Game Notifications`
25 changes: 24 additions & 1 deletion ml_amt/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,17 @@ public override void OnInitializeMelon()
);
}

// Alternative collider height
// Alternative collider height and radius
HarmonyInstance.Patch(
typeof(MovementSystem).GetMethod("UpdateCollider", BindingFlags.NonPublic | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnUpdateCollider_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
null
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);

// AAS overriding fix
HarmonyInstance.Patch(
Expand Down Expand Up @@ -252,7 +257,25 @@ Vector3 ____colliderCenter

return false;
}
static void OnSetupIKScaling_Postfix(
ref PlayerSetup __instance,
float ____avatarHeight
)
{
if(!Settings.CollisionScale)
return;

try
{
__instance._movementSystem.UpdateAvatarHeight(Mathf.Clamp(____avatarHeight, 0.05f, float.MaxValue), true);
}
catch(System.Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}

// AnimatorOverrideController runtime animation replacement fix
static void OnOverride_Prefix(ref CVRAnimatorManager __instance, out AnimatorAnalyzer __state)
{
__state = new AnimatorAnalyzer();
Expand Down
6 changes: 3 additions & 3 deletions ml_amt/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Reflection;

[assembly: AssemblyTitle("AvatarMotionTweaker")]
[assembly: AssemblyVersion("1.2.7")]
[assembly: AssemblyFileVersion("1.2.7")]
[assembly: AssemblyVersion("1.2.8")]
[assembly: AssemblyFileVersion("1.2.8")]

[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.7", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.8", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_prm", "ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
Expand Down
6 changes: 6 additions & 0 deletions ml_mods_cvr.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_pam", "ml_pam\ml_pam.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_prm", "ml_prm\ml_prm.csproj", "{ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ml_pmc", "ml_pmc\ml_pmc.csproj", "{758514D3-6E1A-4E05-A156-B4E5C74AB5C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Expand Down Expand Up @@ -51,6 +53,10 @@ Global
{ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Debug|x64.Build.0 = Debug|x64
{ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Release|x64.ActiveCfg = Release|x64
{ABD2A720-2DE8-4EB3-BFC2-8F1C3D2ADA15}.Release|x64.Build.0 = Release|x64
{758514D3-6E1A-4E05-A156-B4E5C74AB5C4}.Debug|x64.ActiveCfg = Debug|x64
{758514D3-6E1A-4E05-A156-B4E5C74AB5C4}.Debug|x64.Build.0 = Debug|x64
{758514D3-6E1A-4E05-A156-B4E5C74AB5C4}.Release|x64.ActiveCfg = Release|x64
{758514D3-6E1A-4E05-A156-B4E5C74AB5C4}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
130 changes: 130 additions & 0 deletions ml_pmc/Main.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using ABI_RC.Core.Networking.IO.Social;
using ABI_RC.Core.Player;
using ABI_RC.Systems.MovementSystem;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;

namespace ml_pmc
{
public class PlayerMovementCopycat : MelonLoader.MelonMod
{
static PlayerMovementCopycat ms_instance = null;

PoseCopycat m_localCopycat = null;

public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;

Settings.Init();
ModUi.Init();

HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(PlayerMovementCopycat).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(PlayerMovementCopycat).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);

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

public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;

m_localCopycat = null;
}

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

m_localCopycat = PlayerSetup.Instance.gameObject.AddComponent<PoseCopycat>();
ModUi.CopySwitch += this.OnTargetSelect;
}

void OnTargetSelect(string p_id)
{
if(m_localCopycat != null)
{
if(m_localCopycat.IsActive())
m_localCopycat.SetTarget(null);
else
{
if(Friends.FriendsWith(p_id))
{
if(CVRPlayerManager.Instance.GetPlayerPuppetMaster(p_id, out PuppetMaster l_puppetMaster))
{
if(IsInSight(MovementSystem.Instance.proxyCollider, l_puppetMaster.GetComponent<CapsuleCollider>(), Utils.GetWorldMovementLimit()))
m_localCopycat.SetTarget(l_puppetMaster.gameObject);
else
ModUi.ShowAlert("Selected player is too far away or obstructed");
}
else
ModUi.ShowAlert("Selected player isn't connected or ready yet");
}
else
ModUi.ShowAlert("Selected player isn't your friend");
}
}
}

static bool IsInSight(CapsuleCollider p_source, CapsuleCollider p_target, float p_limit)
{
bool l_result = false;
if((p_source != null) && (p_target != null))
{
Ray l_ray = new Ray();
l_ray.origin = (p_source.transform.position + p_source.transform.rotation * p_source.center);
l_ray.direction = (p_target.transform.position + p_target.transform.rotation * p_target.center) - l_ray.origin;
List<RaycastHit> l_hits = Physics.RaycastAll(l_ray, p_limit, LayerMask.NameToLayer("UI Internal")).ToList();
if(l_hits.Count > 0)
{
l_hits.Sort((a, b) => ((a.distance < b.distance) ? -1 : 1));
l_result = (l_hits.First().collider.gameObject.transform.root == p_target.transform.root);
}
}
return l_result;
}

// Patches
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localCopycat != null)
m_localCopycat.OnAvatarClear();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}

static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localCopycat != null)
m_localCopycat.OnAvatarSetup();
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}
136 changes: 136 additions & 0 deletions ml_pmc/ModUi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using BTKUILib.UIObjects;
using BTKUILib.UIObjects.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace ml_pmc
{
static class ModUi
{
enum UiIndex
{
Toggle,
Position,
Rotation,
Gestures,
LookAtMix,
MirrorPose,
MirrorPosition,
MirrorRotation,
Reset
}

internal static Action<string> CopySwitch;

static List<QMUIElement> ms_uiElements = null;
static string ms_selectedPlayer;

internal static void Init()
{
ms_uiElements = new List<QMUIElement>();

BTKUILib.QuickMenuAPI.PrepareIcon("PlayerMovementCopycat", "PMC-Dancing", GetIconStream("dancing.png"));
BTKUILib.QuickMenuAPI.PrepareIcon("PlayerMovementCopycat", "PMC-Dancing-On", GetIconStream("dancing_on.png"));

var l_category = BTKUILib.QuickMenuAPI.PlayerSelectPage.AddCategory("Player Movement Copycat", "PlayerMovementCopycat");

ms_uiElements.Add(l_category.AddButton("Copy movement", "PMC-Dancing", "Start/stop copy of player's movement"));
(ms_uiElements[(int)UiIndex.Toggle] as Button).OnPress += OnCopySwitch;

ms_uiElements.Add(l_category.AddToggle("Apply position", "Apply local position change of target player", Settings.Position));
(ms_uiElements[(int)UiIndex.Position] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.Position, value);

ms_uiElements.Add(l_category.AddToggle("Apply rotation", "Apply local rotation change of target player", Settings.Rotation));
(ms_uiElements[(int)UiIndex.Rotation] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.Rotation, value);

ms_uiElements.Add(l_category.AddToggle("Copy gestures", "Copy gestures of target player", Settings.Gestures));
(ms_uiElements[(int)UiIndex.Gestures] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.Gestures, value);

ms_uiElements.Add(l_category.AddToggle("Apply LookAtIK", "Mix target player pose and camera view direction (desktop only)", Settings.LookAtMix));
(ms_uiElements[(int)UiIndex.LookAtMix] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.LookAtMix, value);

ms_uiElements.Add(l_category.AddToggle("Mirror pose", "Mirror target player pose", Settings.MirrorPose));
(ms_uiElements[(int)UiIndex.MirrorPose] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.MirrorPose, value);

ms_uiElements.Add(l_category.AddToggle("Mirror position", "Mirror target player movement against 0YZ plane", Settings.MirrorPosition));
(ms_uiElements[(int)UiIndex.MirrorPosition] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.MirrorPosition, value);

ms_uiElements.Add(l_category.AddToggle("Mirror rotation", "Mirror target player rotation against 0YZ plane", Settings.MirrorRotation));
(ms_uiElements[(int)UiIndex.MirrorRotation] as ToggleButton).OnValueUpdated += (value) => OnToggleUpdate(UiIndex.MirrorRotation, value);

ms_uiElements.Add(l_category.AddButton("Reset settings", "", "Reset mod's settings to default"));
(ms_uiElements[(int)UiIndex.Reset] as Button).OnPress += Reset;

BTKUILib.QuickMenuAPI.OnPlayerSelected += (_, id) => ms_selectedPlayer = id;
PoseCopycat.OnActivityChange += UpdateToggleColor;
}

static void OnCopySwitch() => CopySwitch?.Invoke(ms_selectedPlayer);

static void OnToggleUpdate(UiIndex p_index, bool p_value, bool p_force = false)
{
switch(p_index)
{
case UiIndex.Position:
Settings.SetSetting(Settings.ModSetting.Position, p_value);
break;

case UiIndex.Rotation:
Settings.SetSetting(Settings.ModSetting.Rotation, p_value);
break;

case UiIndex.Gestures:
Settings.SetSetting(Settings.ModSetting.Gestures, p_value);
break;

case UiIndex.LookAtMix:
Settings.SetSetting(Settings.ModSetting.LookAtMix, p_value);
break;

case UiIndex.MirrorPose:
Settings.SetSetting(Settings.ModSetting.MirrorPose, p_value);
break;

case UiIndex.MirrorPosition:
Settings.SetSetting(Settings.ModSetting.MirrorPosition, p_value);
break;

case UiIndex.MirrorRotation:
Settings.SetSetting(Settings.ModSetting.MirrorRotation, p_value);
break;
}

if(p_force)
(ms_uiElements[(int)p_index] as ToggleButton).ToggleValue = p_value;
}

static void Reset()
{
OnToggleUpdate(UiIndex.Position, true, true);
OnToggleUpdate(UiIndex.Rotation, true, true);
OnToggleUpdate(UiIndex.Gestures, true, true);
OnToggleUpdate(UiIndex.LookAtMix, true, true);
OnToggleUpdate(UiIndex.MirrorPose, false, true);
OnToggleUpdate(UiIndex.MirrorPosition, false, true);
OnToggleUpdate(UiIndex.MirrorRotation, false, true);
}

internal static void ShowAlert(string p_text) => BTKUILib.QuickMenuAPI.ShowAlertToast(p_text, 2);

// Currently broken in BTKUILib, waiting for fix
static void UpdateToggleColor(bool p_state)
{
//(ms_uiElements[(int)UiIndex.Toggle] as Button).ButtonIcon = (p_state ? "PMC-Dancing-On" : "PMC-Dancing");
//(ms_uiElements[(int)UiIndex.Toggle] as Button).ButtonText = (p_state ? "PMC-Dancing-On" : "PMC-Dancing");
}

static Stream GetIconStream(string p_name)
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
return l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
}
}
}
Loading

0 comments on commit 0f5e148

Please sign in to comment.