diff --git a/VisualCloneFix/Main.cs b/VisualCloneFix/Main.cs index 9790d2f..2787b55 100644 --- a/VisualCloneFix/Main.cs +++ b/VisualCloneFix/Main.cs @@ -1,10 +1,4 @@ -using System.Reflection; -using ABI_RC.Core.Player.LocalClone; -using ABI_RC.Core.Player.TransformHider; -using ABI.CCK.Components; -using HarmonyLib; -using MelonLoader; -using UnityEngine; +using MelonLoader; namespace NAK.VisualCloneFix; @@ -15,7 +9,7 @@ public class VisualCloneFixMod : MelonMod private static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(VisualCloneFix)); - private static readonly MelonPreferences_Entry EntryUseVisualClone = + internal static readonly MelonPreferences_Entry EntryUseVisualClone = Category.CreateEntry("use_visual_clone", true, "Use Visual Clone", description: "Uses the potentially faster Visual Clone setup for the local avatar."); @@ -25,150 +19,25 @@ public class VisualCloneFixMod : MelonMod public override void OnInitializeMelon() { - HarmonyInstance.Patch( // add option back as melonpref - typeof(TransformHiderUtils).GetMethod(nameof(TransformHiderUtils.SetupAvatar), - BindingFlags.Public | BindingFlags.Static), - prefix: new HarmonyMethod(typeof(VisualCloneFixMod).GetMethod(nameof(OnSetupAvatar), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( // fix binding of ids to exclusions - typeof(LocalCloneHelper).GetMethod(nameof(LocalCloneHelper.CollectTransformToExclusionMap), - BindingFlags.NonPublic | BindingFlags.Static), - prefix: new HarmonyMethod(typeof(VisualCloneFixMod).GetMethod(nameof(CollectTransformToExclusionMap), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( // fix binding of exclusion ids to compute buffer - typeof(SkinnedLocalClone).GetMethod(nameof(SkinnedLocalClone.FindExclusionVertList), - BindingFlags.Public | BindingFlags.Static), - prefix: new HarmonyMethod(typeof(VisualCloneFixMod).GetMethod(nameof(FindExclusionVertList), - BindingFlags.NonPublic | BindingFlags.Static)) - ); + ApplyPatches(typeof(Patches)); // slapped together a fix cause HarmonyInstance.Patch was null ref for no reason? } #endregion Melon Events - #region Transform Hider Setup + #region Melon Mod Utilities - private static bool OnSetupAvatar(GameObject avatar) + private void ApplyPatches(Type type) { - if (!EntryUseVisualClone.Value) - return true; - - LocalCloneHelper.SetupAvatar(avatar); - return false; + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } } - #endregion Transform Hider Setup - - #region FPR Exclusion Processing - - private static bool CollectTransformToExclusionMap( - Component root, Transform headBone, - ref Dictionary __result) - { - // add an fpr exclusion to the head bone - FPRExclusion headExclusion = headBone.gameObject.AddComponent(); - MeshHiderExclusion headExclusionBehaviour = new(); - headExclusion.target = headBone; - headExclusion.behaviour = headExclusionBehaviour; - headExclusionBehaviour.id = 1; // head bone is always 1 - - // body is 0, ensure it is not masked away - //LocalCloneManager.cullingMask &= ~(1 << 0); - - // get all FPRExclusions - var fprExclusions = root.GetComponentsInChildren(true).ToList(); - - // get all valid exclusion targets, and destroy invalid exclusions - Dictionary exclusionTargets = new(); - - int nextId = 2; - for (int i = fprExclusions.Count - 1; i >= 0; i--) - { - FPRExclusion exclusion = fprExclusions[i]; - if (exclusion.target == null - || exclusionTargets.ContainsKey(exclusion.target) - || !exclusion.target.gameObject.scene.IsValid()) - { - UnityEngine.Object.Destroy(exclusion); - fprExclusions.RemoveAt(i); - continue; - } - - if (exclusion.behaviour == null) // head exclusion is already created - { - MeshHiderExclusion meshHiderExclusion = new(); - exclusion.behaviour = meshHiderExclusion; - meshHiderExclusion.id = nextId++; - } - - // first to add wins - exclusionTargets.TryAdd(exclusion.target, exclusion); - } - - // process each FPRExclusion (recursive) - foreach (FPRExclusion exclusion in fprExclusions) - { - ProcessExclusion(exclusion, exclusion.target); - exclusion.UpdateExclusions(); // initial state - } - - // log totals - //LocalCloneMod.Logger.Msg($"Exclusions: {fprExclusions.Count}"); - __result = exclusionTargets; - return false; - - void ProcessExclusion(FPRExclusion exclusion, Transform transform) - { - if (exclusionTargets.ContainsKey(transform) - && exclusionTargets[transform] != exclusion) return; // found other exclusion root - - //exclusion.affectedChildren.Add(transform); // associate with the exclusion - exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) - - foreach (Transform child in transform) - ProcessExclusion(exclusion, child); // process children - } - } - - #endregion - - #region Head Hiding Methods - - public static bool FindExclusionVertList( - SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, - ref int[] __result) - { - Mesh sharedMesh = renderer.sharedMesh; - var boneWeights = sharedMesh.boneWeights; - int[] vertexIndices = new int[sharedMesh.vertexCount]; - - // Pre-map bone transforms to their exclusion ids if applicable - int[] boneIndexToExclusionId = new int[renderer.bones.Length]; - for (int i = 0; i < renderer.bones.Length; i++) - if (exclusions.TryGetValue(renderer.bones[i], out FPRExclusion exclusion)) - boneIndexToExclusionId[i] = ((MeshHiderExclusion)exclusion.behaviour).id; - - const float minWeightThreshold = 0.2f; - for (int i = 0; i < boneWeights.Length; i++) - { - BoneWeight weight = boneWeights[i]; - - if (weight.weight0 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex0]; - else if (weight.weight1 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex1]; - else if (weight.weight2 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex2]; - else if (weight.weight3 > minWeightThreshold) - vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex3]; - } - - __result = vertexIndices; - return false; - } - - #endregion Head Hiding Methods + #endregion Melon Mod Utilities } \ No newline at end of file diff --git a/VisualCloneFix/Patches.cs b/VisualCloneFix/Patches.cs new file mode 100644 index 0000000..608763a --- /dev/null +++ b/VisualCloneFix/Patches.cs @@ -0,0 +1,127 @@ +using ABI_RC.Core.Player.LocalClone; +using ABI_RC.Core.Player.TransformHider; +using ABI.CCK.Components; +using HarmonyLib; +using UnityEngine; + +namespace NAK.VisualCloneFix; + +public static class Patches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(TransformHiderUtils), nameof(TransformHiderUtils.SetupAvatar))] + private static bool OnSetupAvatar(GameObject avatar) + { + if (!VisualCloneFixMod.EntryUseVisualClone.Value) + return true; + + LocalCloneHelper.SetupAvatar(avatar); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(LocalCloneHelper), nameof(LocalCloneHelper.CollectTransformToExclusionMap))] + private static bool CollectTransformToExclusionMap( + Component root, Transform headBone, + ref Dictionary __result) + { + // add an fpr exclusion to the head bone + FPRExclusion headExclusion = headBone.gameObject.AddComponent(); + MeshHiderExclusion headExclusionBehaviour = new(); + headExclusion.target = headBone; + headExclusion.behaviour = headExclusionBehaviour; + headExclusionBehaviour.id = 1; // head bone is always 1 + + // body is 0, ensure it is not masked away + //LocalCloneManager.cullingMask &= ~(1 << 0); + + // get all FPRExclusions + var fprExclusions = root.GetComponentsInChildren(true).ToList(); + + // get all valid exclusion targets, and destroy invalid exclusions + Dictionary exclusionTargets = new(); + + int nextId = 2; + for (int i = fprExclusions.Count - 1; i >= 0; i--) + { + FPRExclusion exclusion = fprExclusions[i]; + if (exclusion.target == null + || exclusionTargets.ContainsKey(exclusion.target) + || !exclusion.target.gameObject.scene.IsValid()) + { + UnityEngine.Object.Destroy(exclusion); + fprExclusions.RemoveAt(i); + continue; + } + + if (exclusion.behaviour == null) // head exclusion is already created + { + MeshHiderExclusion meshHiderExclusion = new(); + exclusion.behaviour = meshHiderExclusion; + meshHiderExclusion.id = nextId++; + } + + // first to add wins + exclusionTargets.TryAdd(exclusion.target, exclusion); + } + + // process each FPRExclusion (recursive) + foreach (FPRExclusion exclusion in fprExclusions) + { + ProcessExclusion(exclusion, exclusion.target); + exclusion.UpdateExclusions(); // initial state + } + + // log totals + //LocalCloneMod.Logger.Msg($"Exclusions: {fprExclusions.Count}"); + __result = exclusionTargets; + return false; + + void ProcessExclusion(FPRExclusion exclusion, Transform transform) + { + if (exclusionTargets.ContainsKey(transform) + && exclusionTargets[transform] != exclusion) return; // found other exclusion root + + //exclusion.affectedChildren.Add(transform); // associate with the exclusion + exclusionTargets.TryAdd(transform, exclusion); // add to the dictionary (yes its wasteful) + + foreach (Transform child in transform) + ProcessExclusion(exclusion, child); // process children + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SkinnedLocalClone), nameof(SkinnedLocalClone.FindExclusionVertList))] + public static bool FindExclusionVertList( + SkinnedMeshRenderer renderer, IReadOnlyDictionary exclusions, + ref int[] __result) + { + Mesh sharedMesh = renderer.sharedMesh; + var boneWeights = sharedMesh.boneWeights; + int[] vertexIndices = new int[sharedMesh.vertexCount]; + + // Pre-map bone transforms to their exclusion ids if applicable + int[] boneIndexToExclusionId = new int[renderer.bones.Length]; + for (int i = 0; i < renderer.bones.Length; i++) + if (exclusions.TryGetValue(renderer.bones[i], out FPRExclusion exclusion)) + boneIndexToExclusionId[i] = ((MeshHiderExclusion)exclusion.behaviour).id; + + const float minWeightThreshold = 0.2f; + for (int i = 0; i < boneWeights.Length; i++) + { + BoneWeight weight = boneWeights[i]; + + if (weight.weight0 > minWeightThreshold) + vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex0]; + else if (weight.weight1 > minWeightThreshold) + vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex1]; + else if (weight.weight2 > minWeightThreshold) + vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex2]; + else if (weight.weight3 > minWeightThreshold) + vertexIndices[i] = boneIndexToExclusionId[weight.boneIndex3]; + } + + __result = vertexIndices; + return false; + } +} \ No newline at end of file diff --git a/VisualCloneFix/format.json b/VisualCloneFix/format.json index fcd6381..40de166 100644 --- a/VisualCloneFix/format.json +++ b/VisualCloneFix/format.json @@ -1,5 +1,5 @@ { - "_id": -1, + "_id": 221, "name": "VisualCloneFix", "modversion": "1.0.0", "gameversion": "2024r175",