diff --git a/EyeMovementFix/BetterEyeController.cs b/EyeMovementFix/BetterEyeController.cs index 361fd33..d812870 100644 --- a/EyeMovementFix/BetterEyeController.cs +++ b/EyeMovementFix/BetterEyeController.cs @@ -2,12 +2,14 @@ using ABI_RC.Core.Player.AvatarTracking.Local; using ABI_RC.Core.Player.AvatarTracking.Remote; using ABI.CCK.Components; +using EyeMovementFix.CCK; using HarmonyLib; using MelonLoader; using UnityEngine; #if DEBUG using CCK.Debugger.Components; +using CCKDebugger = CCK.Debugger; #endif namespace EyeMovementFix; @@ -20,8 +22,7 @@ public class BetterEyeController : MonoBehaviour { public static readonly Dictionary OriginalLeftEyeLocalRotation = new(); public static readonly Dictionary OriginalRightEyeLocalRotation = new(); - private const float MaxVerticalAngle = 25f; - private const float MaxHorizontalAngle = 25f; + private EyeRotationLimits _eyeRotationLimits; private CVRAvatar _avatar; @@ -148,7 +149,7 @@ private static void CreateFake(BetterEye eye, Transform viewpoint, Transform hea #endif } - private static void Initialize(CVRAvatar avatar, CVREyeController eyeController, Transform head, Transform leftRealEye, Transform rightRealEye) { + private static void Initialize(CVRAvatar avatar, Animator animator, CVREyeController eyeController, Transform head, Transform leftRealEye, Transform rightRealEye) { // Initialize our better eye controller var betterEyeController = eyeController.gameObject.AddComponent(); @@ -156,6 +157,9 @@ private static void Initialize(CVRAvatar avatar, CVREyeController eyeController, betterEyeController.cvrEyeController = eyeController; betterEyeController._avatar = avatar; + // Fetch and load the eye rotation limits + LoadRotationLimits(avatar, animator, betterEyeController); + // Todo: Improve this crap if (eyeController.isLocal) { var localHeadPoint = Traverse.Create(PlayerSetup.Instance).Field("_viewPoint").Value; @@ -208,6 +212,50 @@ private static void Initialize(CVRAvatar avatar, CVREyeController eyeController, } } + private static void LoadRotationLimits(CVRAvatar avatar, Animator animator, BetterEyeController betterEyeController) { + + // Look for the EyeRotationLimit Script + betterEyeController._eyeRotationLimits = avatar.GetComponent(); + + #if DEBUG + if (betterEyeController._eyeRotationLimits != null) MelonLogger.Msg($"Found avatar EyeRotationLimits in the avatar!"); + #endif + + if (betterEyeController._eyeRotationLimits != null) return; + + // If there is no script, created with defaults (25) + betterEyeController._eyeRotationLimits = avatar.gameObject.AddComponent(); + + // If there are bone muscle limits, get them and apply over the defaults! + var humanBones = animator.avatar.humanDescription.human; + foreach (var humanBone in humanBones) { + + // Ignore default muscle values values + if (humanBone.limit.useDefaultValues) continue; + + if (humanBone.humanName == HumanBodyBones.LeftEye.ToString()) { + betterEyeController._eyeRotationLimits.LeftEyeMinY = humanBone.limit.min.z; + betterEyeController._eyeRotationLimits.LeftEyeMaxY = humanBone.limit.max.z; + betterEyeController._eyeRotationLimits.LeftEyeMinX = -humanBone.limit.max.y; + betterEyeController._eyeRotationLimits.LeftEyeMaxX = -humanBone.limit.min.y; + + #if DEBUG + MelonLogger.Msg($"Found avatar Left Eye Muscle Limits on the avatar!"); + #endif + } + else if (humanBone.humanName == HumanBodyBones.RightEye.ToString()) { + betterEyeController._eyeRotationLimits.RightEyeMinY = humanBone.limit.min.z; + betterEyeController._eyeRotationLimits.RightEyeMaxY = humanBone.limit.max.z; + betterEyeController._eyeRotationLimits.RightEyeMinX = humanBone.limit.min.y; + betterEyeController._eyeRotationLimits.RightEyeMaxX = humanBone.limit.max.y; + + #if DEBUG + MelonLogger.Msg($"Found avatar Right Eye Muscle Limits on the avatar!"); + #endif + } + } + } + private void Start() { #if DEBUG @@ -227,7 +275,7 @@ private void Start() { } - CCK.Debugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent += OnCCKDebuggerAvatarChanged; + CCKDebugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent += OnCCKDebuggerAvatarChanged; #endif if (enabled) initialized = true; @@ -265,9 +313,13 @@ private void TargetHandler(CVREyeController controller) { } } - private static float NormalizeAngleToPercent(float angle, float maxAngle) { - var normal = Mathf.InverseLerp(-maxAngle, maxAngle, angle); - return Mathf.Lerp(-1f, 1f, normal); + private static float NormalizeAngleToPercent(float angle, float minAngle, float maxAngle) { + if (angle >= 0) { + return Mathf.Lerp(0, 1f, Mathf.InverseLerp(0, maxAngle, angle)); + } + else { + return Mathf.Lerp(-1f, 0, Mathf.InverseLerp(minAngle, 0, angle)); + } } private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) { @@ -277,8 +329,15 @@ private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) { var wrapperLocalRotation = eye.FakeEyeWrapper.localRotation.eulerAngles; if (wrapperLocalRotation.x > 180f) wrapperLocalRotation.x -= 360f; if (wrapperLocalRotation.y > 180f) wrapperLocalRotation.y -= 360f; - wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, -MaxVerticalAngle, MaxVerticalAngle); - wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, -MaxHorizontalAngle, MaxHorizontalAngle); + + if (eye.IsLeft) { + wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, _eyeRotationLimits.LeftEyeMinY, _eyeRotationLimits.LeftEyeMaxY); + wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, _eyeRotationLimits.LeftEyeMinX, _eyeRotationLimits.LeftEyeMaxX); + } + else { + wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, _eyeRotationLimits.RightEyeMinY, _eyeRotationLimits.RightEyeMaxY); + wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, _eyeRotationLimits.RightEyeMinX, _eyeRotationLimits.RightEyeMaxX); + } #if DEBUG var previousLocal = eye.FakeEyeWrapper.localRotation; @@ -296,7 +355,9 @@ private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) { // Set the eye angle (we're setting twice if we have 2 eyes, but the values should be the same anyway) // This will give values different than cvr. I've opted to have the looking forward angle to be 0 // And then goes between [-1;0] and [0;+1], instead of [335-360] and [0-25] (cvr default) - cvrEyeController.eyeAngle.Set(NormalizeAngleToPercent(wrapperLocalRotation.y, MaxHorizontalAngle), NormalizeAngleToPercent(wrapperLocalRotation.x, MaxVerticalAngle)); + cvrEyeController.eyeAngle.Set( + NormalizeAngleToPercent(wrapperLocalRotation.y, _eyeRotationLimits.LeftEyeMinX, _eyeRotationLimits.LeftEyeMaxX), + NormalizeAngleToPercent(wrapperLocalRotation.x, _eyeRotationLimits.LeftEyeMinY, _eyeRotationLimits.LeftEyeMaxY)); } private void UpdateEyeRotations() { @@ -375,7 +436,7 @@ private void OnDestroy() { } #if DEBUG - CCK.Debugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent -= OnCCKDebuggerAvatarChanged; + CCKDebugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent -= OnCCKDebuggerAvatarChanged; #endif } @@ -583,7 +644,7 @@ private static void After_CVREyeControllerManager_Start(ref CVREyeController __i if ((__instance.isLocal && head == null) || leftEye == null && rightEye == null) return; // Initialize the controller - Initialize(___avatar, __instance, head, leftEye, rightEye); + Initialize(___avatar, animator, __instance, head, leftEye, rightEye); } catch (Exception e) { diff --git a/EyeMovementFix/CCK/EyeRotationLimits.cs b/EyeMovementFix/CCK/EyeRotationLimits.cs new file mode 100644 index 0000000..a7e6900 --- /dev/null +++ b/EyeMovementFix/CCK/EyeRotationLimits.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace EyeMovementFix.CCK; + +public class EyeRotationLimits : MonoBehaviour { + + public float LeftEyeMinX = -25; + public float LeftEyeMaxX = 25; + public float LeftEyeMinY = -25; + public float LeftEyeMaxY = 25; + + public float RightEyeMinX = -25; + public float RightEyeMaxX = 25; + public float RightEyeMinY = -25; + public float RightEyeMaxY = 25; +} diff --git a/EyeMovementFix/Main.cs b/EyeMovementFix/Main.cs index 689bdd1..f6ab918 100644 --- a/EyeMovementFix/Main.cs +++ b/EyeMovementFix/Main.cs @@ -1,7 +1,9 @@ using System.Reflection; using System.Reflection.Emit; using ABI_RC.Core.Player; +using ABI_RC.Core.Util.AssetFiltering; using ABI.CCK.Components; +using EyeMovementFix.CCK; using HarmonyLib; using MelonLoader; using UnityEngine; @@ -30,6 +32,9 @@ public override void OnInitializeMelon() { "are active, they create targets for the eye movement. But since I use the mirror to mostly " + "see what I'm doing, makes no sense to target players in the portable mirror."); + // Add our CCK component to the whitelist + Traverse.Create(typeof(SharedFilter)).Field>("_avatarWhitelist").Value.Add(typeof(EyeRotationLimits)); + // Check for portable mirror _hasPortableMirror = RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PortableMirrorMod") != null; diff --git a/EyeMovementFix/Properties/AssemblyInfo.cs b/EyeMovementFix/Properties/AssemblyInfo.cs index 5a70767..30ea8e3 100644 --- a/EyeMovementFix/Properties/AssemblyInfo.cs +++ b/EyeMovementFix/Properties/AssemblyInfo.cs @@ -26,6 +26,6 @@ namespace EyeMovementFix.Properties; internal static class AssemblyInfoParams { - public const string Version = "2.0.2"; + public const string Version = "2.0.3"; public const string Author = "kafeijao"; } diff --git a/EyeMovementFix/README.md b/EyeMovementFix/README.md index 5282c3e..6bdc733 100644 --- a/EyeMovementFix/README.md +++ b/EyeMovementFix/README.md @@ -1,16 +1,29 @@ # EyeMovementFix Mod to attempt to fix some of the current issues with the eye movement. This will affect the local and remote players -eye movement but only for the local client. +eye movement but only for the local client. Over time I also implemented some new features. + +## Added Features +- Reworked the eye targeting system, now finding a target will require line of sight and gives priority to: + - Distance of the target and how close to the viewpoint center it is. + - How loud the target is talking (if the target is a player). + - Prefers the real players over their reflections on the mirror. + - Is the local player :) +- Allow to set the avatar eye rotation limits by: + - Using a Unity [Custom CCK Component](https://github.com/kafeijao/Kafe_CVR_CCKs/blob/master/EyeMovementFix) + - Using the Unity Muscle rig limits for the eyes ## Addressed Issues - The Local Player look at target (local and in mirror) doesn't follow the viewpoint Up/Down when in FBT. (results in - players looking higher/lower when looking at you). + players looking higher/lower when looking at you). This seems to be because of some janky coded that during the + Update Loop makes the viewpoint default to the avatar height position. In FixedUpdate and LateUpdate it is on the + correct place. - Some remote players don't have an instance of PuppetMaster attached to their CVRAvatar instance, which results in the viewpoint location to be completely wrong (results in crossed eyes a lot of times). - The math for rotating the eyes has some issues. Sometimes one of my eyes would go up, and the other to the left. I redid that part with simpler code, it might not be the best, but the eyes won't move in opposite ways/axis anymore. +- The parameter stream fetches the Y eye angle from the X axis. ## Configuration