Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ServalMod v1.1.0
Serval.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Serval")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Serval model replacement plugin for BepInEx")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Serval")] [assembly: AssemblyTitle("Serval")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServalModelReplaceMod { [BepInPlugin("com.kotokott.servalreplace", "Serval Model Replace", "1.0.1")] public class ServalPlugin : BaseUnityPlugin { private const string BundleFileName = "serval"; private const string PrefabName = "serval"; internal static readonly bool ReplaceThinMan = false; private static readonly bool EnableSpawnLogger = false; internal static readonly Vector3 ModelScale = new Vector3(1.5f, 1.5f, 1.5f); internal static ManualLogSource Log; internal static GameObject ReplacementModel; private readonly Harmony harmony = new Harmony("com.kotokott.servalreplace"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); string text = Path.Combine(directoryName, "serval"); AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { Log.LogError((object)"Failed to load asset bundle 'serval'."); return; } ReplacementModel = val.LoadAsset<GameObject>("serval"); if ((Object)(object)ReplacementModel == (Object)null) { Log.LogError((object)"Prefab 'serval' was not found in asset bundle 'serval'."); return; } harmony.PatchAll(); if (EnableSpawnLogger) { SpawnLoggerInstaller.Install(harmony); } Log.LogInfo((object)"Serval model replacement is active. Build=1.0.1 HunterAliasMapping=ON"); } } [HarmonyPatch(typeof(EnemyThinMan), "Awake")] internal static class EnemyThinManAwakePatch { [HarmonyPostfix] private static void Postfix(EnemyThinMan __instance) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (ServalPlugin.ReplaceThinMan && !((Object)(object)ServalPlugin.ReplacementModel == (Object)null)) { ThinManVisualReplacementController thinManVisualReplacementController = ((Component)__instance).GetComponent<ThinManVisualReplacementController>(); if ((Object)(object)thinManVisualReplacementController == (Object)null) { thinManVisualReplacementController = ((Component)__instance).gameObject.AddComponent<ThinManVisualReplacementController>(); } thinManVisualReplacementController.Initialize((Component)(object)__instance, ServalPlugin.ReplacementModel, ServalPlugin.ModelScale); } } } [HarmonyPatch(typeof(EnemyHunter), "Awake")] internal static class EnemyHunterAwakePatch { [HarmonyPostfix] private static void Postfix(EnemyHunter __instance) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ServalPlugin.ReplacementModel == (Object)null)) { ThinManVisualReplacementController thinManVisualReplacementController = ((Component)__instance).GetComponent<ThinManVisualReplacementController>(); if ((Object)(object)thinManVisualReplacementController == (Object)null) { thinManVisualReplacementController = ((Component)__instance).gameObject.AddComponent<ThinManVisualReplacementController>(); } thinManVisualReplacementController.Initialize((Component)(object)__instance, ServalPlugin.ReplacementModel, ServalPlugin.ModelScale); } } } internal static class SpawnLoggerInstaller { private static readonly HashSet<string> PatchedTypeNames = new HashSet<string>(StringComparer.Ordinal); private static readonly HashSet<int> LoggedInstanceIds = new HashSet<int>(); private static readonly Dictionary<Type, MemberInfo> EnemyTypeMemberCache = new Dictionary<Type, MemberInfo>(); private static readonly HashSet<Type> NoEnemyTypeMember = new HashSet<Type>(); internal static void Install(Harmony harmony) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown if (harmony == null) { return; } Type type = AccessTools.TypeByName("EnemyThinMan"); if (type == null) { ServalPlugin.Log.LogWarning((object)"Spawn logger was not installed: could not locate game assembly marker type."); return; } Type[] types = type.Assembly.GetTypes(); int num = 0; foreach (Type type2 in types) { if (!(type2 == null) && type2.IsClass && !type2.IsAbstract && typeof(MonoBehaviour).IsAssignableFrom(type2) && type2.Name.StartsWith("Enemy", StringComparison.Ordinal) && !type2.Name.Contains("State", StringComparison.Ordinal) && !type2.Name.Contains("Anim", StringComparison.Ordinal) && !type2.Name.Contains("Director", StringComparison.Ordinal) && PatchedTypeNames.Add(type2.FullName ?? type2.Name)) { MethodInfo method = type2.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); MethodInfo method2 = type2.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); MethodInfo methodInfo = method ?? method2; if (!(methodInfo == null)) { HarmonyMethod val = new HarmonyMethod(typeof(SpawnLoggerInstaller), "LogSpawnPostfix", (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } } ServalPlugin.Log.LogInfo((object)$"Spawn logger installed on {num} Enemy*.Start methods."); } private static void LogSpawnPostfix(object __instance) { if (__instance == null || ServalPlugin.Log == null) { return; } Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { int instanceID = ((Object)val).GetInstanceID(); if (LoggedInstanceIds.Add(instanceID)) { string name = __instance.GetType().Name; string text = (((Object)(object)val.gameObject != (Object)null) ? ((Object)val.gameObject).name : "<null>"); string enemyTypeName = GetEnemyTypeName(__instance); ServalPlugin.Log.LogInfo((object)("[SpawnDetect] class='" + name + "' enemyType='" + enemyTypeName + "' object='" + text + "'")); } } } private static string GetEnemyTypeName(object enemy) { object enemyTypeValue = GetEnemyTypeValue(enemy); if (enemyTypeValue == null) { return "<null>"; } string text = (AccessTools.Field(enemyTypeValue.GetType(), "enemyName")?.GetValue(enemyTypeValue) as string) ?? (AccessTools.Field(enemyTypeValue.GetType(), "name")?.GetValue(enemyTypeValue) as string); if (string.IsNullOrWhiteSpace(text)) { text = enemyTypeValue.ToString(); } return text; } private static object GetEnemyTypeValue(object enemy) { Type type = enemy.GetType(); if (NoEnemyTypeMember.Contains(type)) { return null; } if (!EnemyTypeMemberCache.TryGetValue(type, out var value)) { BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo fieldInfo = type.GetField("enemyType", bindingAttr) ?? type.GetField("thisEnemyType", bindingAttr); if (fieldInfo != null) { value = fieldInfo; } else { PropertyInfo propertyInfo = type.GetProperty("enemyType", bindingAttr) ?? type.GetProperty("thisEnemyType", bindingAttr); if (propertyInfo != null) { value = propertyInfo; } } if (value == null) { NoEnemyTypeMember.Add(type); return null; } EnemyTypeMemberCache[type] = value; } if (value is FieldInfo fieldInfo2) { return fieldInfo2.GetValue(enemy); } if (value is PropertyInfo propertyInfo2) { return propertyInfo2.GetValue(enemy, null); } return null; } } internal sealed class ThinManVisualReplacementController : MonoBehaviour { private const int ApplyDelayFrames = 5; private const int RecollectRootsEveryNFrames = 120; private const bool EnableHunterAliasMapping = true; private Component enemy; private GameObject replacementPrefab; private Vector3 replacementScale; private bool initialized; private bool replacementApplied; private bool rigDiagnosticsLogged; private bool sourceSkinnedMissingLogged; private bool hideRootsLogged; private bool hunterAnimLinkedLogged; private bool zeroMatchDumpLogged; private int framesAlive; private GameObject replacementRoot; private SkinnedMeshRenderer mainEnemySkinned; private readonly List<Transform> hideRoots = new List<Transform>(); private readonly HashSet<int> hideRootIds = new HashSet<int>(); private readonly List<Transform> boneRoots = new List<Transform>(); private readonly HashSet<int> boneRootIds = new HashSet<int>(); private readonly List<Renderer> forcedHiddenRenderers = new List<Renderer>(); private readonly HashSet<int> forcedHiddenRendererIds = new HashSet<int>(); internal void Initialize(Component targetEnemy, GameObject prefab, Vector3 scale) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!initialized) { enemy = targetEnemy; replacementPrefab = prefab; replacementScale = scale; initialized = true; CollectHideRoots(); CollectBoneRoots(); CollectHunterSceneLinks(); } } private void LateUpdate() { if (initialized && !((Object)(object)enemy == (Object)null) && !((Object)(object)replacementPrefab == (Object)null)) { framesAlive++; if (framesAlive % 120 == 0) { CollectHideRoots(); CollectBoneRoots(); CollectHunterSceneLinks(); } if (!replacementApplied && framesAlive >= 5) { TryApplyReplacement(); } KeepOriginalHidden(); } } private void TryApplyReplacement() { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) try { mainEnemySkinned = FindPrimaryEnemySkinned(); if ((Object)(object)mainEnemySkinned == (Object)null && enemy is EnemyHunter) { mainEnemySkinned = FindSceneHunterSkinned(((Object)(object)replacementRoot == (Object)null) ? null : replacementRoot.transform); if ((Object)(object)mainEnemySkinned != (Object)null) { AddBoneRoot(((Component)mainEnemySkinned).transform); AddForcedHiddenRenderers(((Component)mainEnemySkinned).transform); ServalPlugin.Log.LogInfo((object)"[EnemyHunter] Found source skinned mesh from scene fallback."); } } if ((Object)(object)mainEnemySkinned == (Object)null && !sourceSkinnedMissingLogged) { sourceSkinnedMissingLogged = true; ServalPlugin.Log.LogInfo((object)("[" + ((object)enemy).GetType().Name + "] No direct source SkinnedMeshRenderer found, using discovered roots only.")); } Dictionary<string, Transform> dictionary = BuildBoneMapFromRoots(normalized: false, ((Object)(object)replacementRoot == (Object)null) ? null : replacementRoot.transform); Dictionary<string, Transform> dictionary2 = BuildBoneMapFromRoots(normalized: true, ((Object)(object)replacementRoot == (Object)null) ? null : replacementRoot.transform); if ((Object)(object)replacementRoot == (Object)null) { replacementRoot = Object.Instantiate<GameObject>(replacementPrefab, enemy.transform); replacementRoot.transform.localPosition = Vector3.zero; replacementRoot.transform.localRotation = Quaternion.identity; replacementRoot.transform.localScale = replacementScale; DisableAnimators(replacementRoot); } dictionary = BuildBoneMapFromRoots(normalized: false, ((Object)(object)replacementRoot == (Object)null) ? null : replacementRoot.transform); dictionary2 = BuildBoneMapFromRoots(normalized: true, ((Object)(object)replacementRoot == (Object)null) ? null : replacementRoot.transform); SkinnedMeshRenderer[] componentsInChildren = replacementRoot.GetComponentsInChildren<SkinnedMeshRenderer>(true); int num = 0; int num2 = 0; int num3 = 0; bool isHunter = enemy is EnemyHunter; for (int i = 0; i < componentsInChildren.Length; i++) { if (TryRemapSkinnedMeshBones(componentsInChildren[i], dictionary, dictionary2, mainEnemySkinned, isHunter, out var mappedByName, out var _, out var sourceBoneCount)) { num++; } num2 += mappedByName; num3 += sourceBoneCount; } if (componentsInChildren.Length == 0) { ServalPlugin.Log.LogWarning((object)"Replacement prefab has no SkinnedMeshRenderer. It will stay static."); replacementApplied = true; return; } if (!rigDiagnosticsLogged) { rigDiagnosticsLogged = true; LogRigDiagnostics(((object)enemy).GetType().Name, componentsInChildren.Length, num2, num3, num); } ServalPlugin.Log.LogInfo((object)$"Replacement applied. Remapped {num}/{componentsInChildren.Length} skinned meshes."); if (num == 0) { ServalPlugin.Log.LogWarning((object)"No usable bone mapping was found. The replacement rig is likely incompatible."); if (!zeroMatchDumpLogged) { zeroMatchDumpLogged = true; LogBoneNameSamples(componentsInChildren, dictionary2); } } replacementApplied = true; KeepOriginalHidden(); } catch (Exception arg) { ServalPlugin.Log.LogError((object)$"Model replacement failed: {arg}"); replacementApplied = true; } } private SkinnedMeshRenderer FindPrimaryEnemySkinned() { for (int i = 0; i < boneRoots.Count; i++) { Transform val = boneRoots[i]; if (!((Object)(object)val == (Object)null)) { SkinnedMeshRenderer[] componentsInChildren = ((Component)val).GetComponentsInChildren<SkinnedMeshRenderer>(true); if (componentsInChildren.Length != 0) { return componentsInChildren[0]; } } } return null; } private void KeepOriginalHidden() { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < hideRoots.Count; i++) { Transform val = hideRoots[i]; if ((Object)(object)val == (Object)null) { continue; } Renderer[] componentsInChildren = ((Component)val).GetComponentsInChildren<Renderer>(true); foreach (Renderer val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { int instanceID = ((Object)val2).GetInstanceID(); if (hashSet.Add(instanceID) && (!((Object)(object)replacementRoot != (Object)null) || !((Component)val2).transform.IsChildOf(replacementRoot.transform))) { val2.enabled = false; } } } } for (int k = 0; k < forcedHiddenRenderers.Count; k++) { Renderer val3 = forcedHiddenRenderers[k]; if (!((Object)(object)val3 == (Object)null) && (!((Object)(object)replacementRoot != (Object)null) || !((Component)val3).transform.IsChildOf(replacementRoot.transform))) { val3.enabled = false; } } } private static void DisableAnimators(GameObject root) { Animator[] componentsInChildren = root.GetComponentsInChildren<Animator>(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((Behaviour)componentsInChildren[i]).enabled = false; } } private Dictionary<string, Transform> BuildBoneMapFromRoots(bool normalized, Transform excludedRoot) { Dictionary<string, Transform> dictionary = new Dictionary<string, Transform>(StringComparer.Ordinal); for (int i = 0; i < boneRoots.Count; i++) { Transform val = boneRoots[i]; if (!((Object)(object)val == (Object)null)) { AppendBoneMap(val, normalized, dictionary, excludedRoot); } } return dictionary; } private static void AppendBoneMap(Transform root, bool normalized, Dictionary<string, Transform> map, Transform excludedRoot) { if ((Object)(object)excludedRoot != (Object)null && root.IsChildOf(excludedRoot)) { return; } Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { if (!((Object)(object)excludedRoot != (Object)null) || !val.IsChildOf(excludedRoot)) { string text = (normalized ? NormalizeBoneName(((Object)val).name) : ((Object)val).name); if (!string.IsNullOrEmpty(text) && !map.ContainsKey(text)) { map.Add(text, val); } } } } private void CollectHideRoots() { if (!((Object)(object)enemy == (Object)null)) { AddHideRoot(enemy.transform); AddHunterSpecificHideRoots(); if (!hideRootsLogged) { hideRootsLogged = true; ServalPlugin.Log.LogInfo((object)$"[{((object)enemy).GetType().Name}] hideRoots collected: {hideRoots.Count}"); } } } private void CollectBoneRoots() { if (!((Object)(object)enemy == (Object)null)) { AddBoneRoot(enemy.transform); AddBoneRoot(enemy.transform.parent); AddBoneRoot(enemy.transform.root); AddBoneRootsFromMembers(enemy); AddHunterSpecificBoneRoots(); } } private void CollectHunterSceneLinks() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (!(enemy is EnemyHunter)) { return; } Component[] array = Object.FindObjectsOfType<Component>(true); Vector3 position = enemy.transform.position; foreach (Component val in array) { if (!((Object)(object)val == (Object)null) && IsLikelyHunterComponent(val) && !(Vector3.Distance(val.transform.position, position) > 20f)) { AddBoneRoot(val.transform); AddForcedHiddenRenderers(val.transform); } } } private void AddHunterSpecificHideRoots() { if (!(enemy is EnemyHunter)) { return; } Transform transform = enemy.transform; Component[] componentsInChildren = ((Component)transform).GetComponentsInChildren<Component>(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { string name = ((object)val).GetType().Name; if (name.IndexOf("Hunter", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Anim", StringComparison.OrdinalIgnoreCase) >= 0) { AddHideRoot(val.transform); } } } Transform val2 = FindNearestTransformByTypeName("EnemyHunterAnim", 40f); if ((Object)(object)val2 != (Object)null) { AddHideRoot(val2); AddHideRoot(val2.parent); AddForcedHiddenRenderers(val2); AddForcedHiddenRenderers(val2.parent); if (!hunterAnimLinkedLogged) { hunterAnimLinkedLogged = true; ServalPlugin.Log.LogInfo((object)("[EnemyHunter] Linked hide root from EnemyHunterAnim: '" + ((Object)val2).name + "'.")); } } } private void AddHunterSpecificBoneRoots() { if (!(enemy is EnemyHunter)) { return; } Transform val = (((Object)(object)enemy.transform.parent != (Object)null) ? enemy.transform.parent : enemy.transform); Component[] componentsInChildren = ((Component)val).GetComponentsInChildren<Component>(true); foreach (Component val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { string name = ((object)val2).GetType().Name; if (name.IndexOf("Hunter", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Anim", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Skinned", StringComparison.OrdinalIgnoreCase) >= 0) { AddBoneRoot(val2.transform); AddForcedHiddenRenderers(val2.transform); } } } Transform val3 = FindNearestTransformByTypeName("EnemyHunterAnim", 40f); if ((Object)(object)val3 != (Object)null) { AddBoneRoot(val3); AddBoneRoot(val3.parent); } } private void AddBoneRootsFromMembers(object target) { if (target == null) { return; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo[] fields = target.GetType().GetFields(bindingAttr); for (int i = 0; i < fields.Length; i++) { object value; try { value = fields[i].GetValue(target); } catch { continue; } AddBoneRootFromObject(value); } } private void AddBoneRootFromObject(object value) { if (value == null) { return; } Transform val = (Transform)((value is Transform) ? value : null); if (val != null) { AddBoneRoot(val); return; } GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 != null) { AddBoneRoot(val2.transform); return; } Component val3 = (Component)((value is Component) ? value : null); if (val3 != null) { AddBoneRoot(val3.transform); } } private void AddHideRoot(Transform root) { if (!((Object)(object)root == (Object)null)) { int instanceID = ((Object)root).GetInstanceID(); if (hideRootIds.Add(instanceID)) { hideRoots.Add(root); } } } private void AddBoneRoot(Transform root) { if (!((Object)(object)root == (Object)null)) { int instanceID = ((Object)root).GetInstanceID(); if (boneRootIds.Add(instanceID)) { boneRoots.Add(root); } } } private void AddForcedHiddenRenderers(Transform root) { if ((Object)(object)root == (Object)null) { return; } Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); if (forcedHiddenRendererIds.Add(instanceID)) { forcedHiddenRenderers.Add(val); } } } } private SkinnedMeshRenderer FindSceneHunterSkinned(Transform excludedRoot) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) SkinnedMeshRenderer[] array = Object.FindObjectsOfType<SkinnedMeshRenderer>(true); SkinnedMeshRenderer result = null; float num = float.MaxValue; Vector3 position = enemy.transform.position; foreach (SkinnedMeshRenderer val in array) { if (!((Object)(object)val == (Object)null) && (!((Object)(object)excludedRoot != (Object)null) || !((Component)val).transform.IsChildOf(excludedRoot)) && IsLikelyHunterRenderer((Renderer)(object)val)) { float num2 = Vector3.Distance(((Component)val).transform.position, position); if (!(num2 > 20f) && !(num2 >= num)) { num = num2; result = val; } } } return result; } private static bool IsLikelyHunterComponent(Component component) { string name = ((object)component).GetType().Name; if (name.IndexOf("Hunter", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("EnemyHunter", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("HunterAnim", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static bool IsLikelyHunterRenderer(Renderer renderer) { if ((Object)(object)renderer == (Object)null) { return false; } string text = ((Object)renderer).name ?? string.Empty; if (text.IndexOf("hunter", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } Component[] componentsInParent = ((Component)renderer).GetComponentsInParent<Component>(true); foreach (Component val in componentsInParent) { if (!((Object)(object)val == (Object)null) && IsLikelyHunterComponent(val)) { return true; } } return false; } private static string NormalizeBoneName(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } string text = name.ToLowerInvariant(); int num = text.LastIndexOf('|'); if (num >= 0 && num + 1 < text.Length) { text = text.Substring(num + 1); } text = text.Replace("mixamorig:", string.Empty); text = text.Replace(" ", string.Empty); text = text.Replace("_", string.Empty); return text.Replace("-", string.Empty); } private static bool TryRemapSkinnedMeshBones(SkinnedMeshRenderer skinned, Dictionary<string, Transform> enemyBonesExact, Dictionary<string, Transform> enemyBonesNormalized, SkinnedMeshRenderer enemySkinnedFallback, bool isHunter, out int mappedByName, out int mappedTotal, out int sourceBoneCount) { mappedByName = 0; mappedTotal = 0; sourceBoneCount = 0; if ((Object)(object)skinned == (Object)null || skinned.bones == null || skinned.bones.Length == 0) { return false; } Transform[] bones = skinned.bones; Transform[] array = (Transform[])(object)new Transform[bones.Length]; Array.Copy(bones, array, bones.Length); sourceBoneCount = skinned.bones.Length; for (int i = 0; i < skinned.bones.Length; i++) { Transform val = skinned.bones[i]; if ((Object)(object)val == (Object)null) { continue; } if (enemyBonesExact.TryGetValue(((Object)val).name, out var value)) { array[i] = value; mappedByName++; continue; } string text = NormalizeBoneName(((Object)val).name); if (!string.IsNullOrEmpty(text) && enemyBonesNormalized.TryGetValue(text, out var value2)) { array[i] = value2; mappedByName++; } else if (isHunter && !string.IsNullOrEmpty(text)) { Transform val2 = TryMapHunterBoneAlias(text, enemyBonesNormalized); if ((Object)(object)val2 != (Object)null) { array[i] = val2; mappedByName++; } } } if ((Object)(object)enemySkinnedFallback != (Object)null && enemySkinnedFallback.bones != null) { int num = Math.Min(array.Length, enemySkinnedFallback.bones.Length); for (int j = 0; j < num; j++) { if ((Object)(object)array[j] == (Object)null) { array[j] = enemySkinnedFallback.bones[j]; } } } if (mappedByName == 0) { return false; } Transform val3 = null; for (int k = 0; k < array.Length; k++) { if ((Object)(object)array[k] != (Object)null && (Object)(object)array[k] != (Object)(object)bones[k]) { mappedTotal++; if ((Object)(object)val3 == (Object)null) { val3 = array[k]; } } } skinned.bones = array; skinned.updateWhenOffscreen = true; if ((Object)(object)skinned.rootBone != (Object)null) { if (enemyBonesExact.TryGetValue(((Object)skinned.rootBone).name, out var value3)) { skinned.rootBone = value3; } else { string text2 = NormalizeBoneName(((Object)skinned.rootBone).name); if (!string.IsNullOrEmpty(text2) && enemyBonesNormalized.TryGetValue(text2, out var value4)) { skinned.rootBone = value4; } else if (isHunter) { Transform val4 = TryMapHunterBoneAlias(text2, enemyBonesNormalized); if ((Object)(object)val4 != (Object)null) { skinned.rootBone = val4; } else if ((Object)(object)enemySkinnedFallback != (Object)null && (Object)(object)enemySkinnedFallback.rootBone != (Object)null) { skinned.rootBone = enemySkinnedFallback.rootBone; } else { skinned.rootBone = skinned.rootBone; } } else if ((Object)(object)enemySkinnedFallback != (Object)null && (Object)(object)enemySkinnedFallback.rootBone != (Object)null) { skinned.rootBone = enemySkinnedFallback.rootBone; } } } else if ((Object)(object)enemySkinnedFallback != (Object)null && (Object)(object)enemySkinnedFallback.rootBone != (Object)null) { skinned.rootBone = enemySkinnedFallback.rootBone; } return mappedTotal > 0 || (Object)(object)val3 != (Object)null; } private static Transform TryMapHunterBoneAlias(string modelBone, Dictionary<string, Transform> hunterBones) { if (string.IsNullOrEmpty(modelBone) || hunterBones == null || hunterBones.Count == 0) { return null; } bool flag = modelBone.Contains("left", StringComparison.Ordinal); bool flag2 = modelBone.Contains("right", StringComparison.Ordinal); if (modelBone.Contains("hip", StringComparison.Ordinal) || modelBone.Contains("upleg", StringComparison.Ordinal) || modelBone.Contains("leg", StringComparison.Ordinal) || modelBone.Contains("foot", StringComparison.Ordinal) || modelBone.Contains("toe", StringComparison.Ordinal)) { return FindHunterBone(hunterBones, "animbottom", "animbody"); } if (modelBone.Contains("spine02", StringComparison.Ordinal) || modelBone.Contains("chest", StringComparison.Ordinal)) { return FindHunterBone(hunterBones, "animarms", "animbody"); } if (modelBone.Contains("spine", StringComparison.Ordinal)) { return FindHunterBone(hunterBones, "animbody", "animbottom"); } if (modelBone.Contains("neck", StringComparison.Ordinal) || modelBone.Contains("head", StringComparison.Ordinal)) { return FindHunterBone(hunterBones, "animhead", "codeaimvertical", "codegunaim", "animbody"); } if (modelBone.Contains("shoulder", StringComparison.Ordinal) || modelBone.Contains("arm", StringComparison.Ordinal)) { if (modelBone.Contains("forearm", StringComparison.Ordinal)) { if (flag2) { return FindHunterBone(hunterBones, "animarmright2", "armright2", "animarmright1"); } if (flag) { return FindHunterBone(hunterBones, "animarmleft2", "armleft2", "animarmleft1"); } } if (modelBone.Contains("hand", StringComparison.Ordinal)) { if (flag2) { return FindHunterBone(hunterBones, "animhandright", "handright"); } if (flag) { return FindHunterBone(hunterBones, "animhandleft", "handleft"); } } if (flag2) { return FindHunterBone(hunterBones, "animarmright1", "armright1", "animarmright2"); } if (flag) { return FindHunterBone(hunterBones, "animarmleft1", "armleft1", "animarmleft2"); } return FindHunterBone(hunterBones, "animarms", "animbody"); } return FindHunterBone(hunterBones, "animbody", "animbottom"); } private static Transform FindHunterBone(Dictionary<string, Transform> bones, params string[] aliases) { foreach (string value in aliases) { foreach (KeyValuePair<string, Transform> bone in bones) { if (bone.Key.IndexOf(value, StringComparison.Ordinal) >= 0) { return bone.Value; } } } return null; } private static void LogRigDiagnostics(string enemyClassName, int skinnedCount, int matchedByName, int sourceBones, int mappedMeshes) { float num = ((sourceBones > 0) ? (100f * (float)matchedByName / (float)sourceBones) : 0f); ServalPlugin.Log.LogInfo((object)$"[RigCheck] enemy='{enemyClassName}' skinnedMeshes={skinnedCount} bonesMatchedByName={matchedByName}/{sourceBones} ({num:0.0}%), mappedMeshes={mappedMeshes}"); if (sourceBones > 0 && matchedByName == 0) { ServalPlugin.Log.LogWarning((object)"[RigCheck] 0 bones matched by name. This strongly indicates incompatible armature naming in the replacement model."); } } private Transform FindNearestTransformByTypeName(string typeName, float maxDistance) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(typeName)) { return null; } Component[] array = Object.FindObjectsOfType<Component>(true); Transform result = null; float num = float.MaxValue; Vector3 position = enemy.transform.position; foreach (Component val in array) { if (!((Object)(object)val == (Object)null) && string.Equals(((object)val).GetType().Name, typeName, StringComparison.Ordinal)) { float num2 = Vector3.Distance(val.transform.position, position); if (!(num2 > maxDistance) && !(num2 >= num)) { result = val.transform; num = num2; } } } return result; } private static void LogBoneNameSamples(SkinnedMeshRenderer[] replacementSkinned, Dictionary<string, Transform> enemyBonesNormalized) { if (replacementSkinned == null || replacementSkinned.Length == 0) { return; } SkinnedMeshRenderer val = replacementSkinned[0]; if ((Object)(object)val == (Object)null || val.bones == null) { return; } List<string> list = new List<string>(); for (int i = 0; i < val.bones.Length; i++) { if (list.Count >= 24) { break; } Transform val2 = val.bones[i]; if (!((Object)(object)val2 == (Object)null)) { string item = NormalizeBoneName(((Object)val2).name); list.Add(item); } } List<string> list2 = new List<string>(); foreach (string key in enemyBonesNormalized.Keys) { if (list2.Count >= 32) { break; } list2.Add(key); } ServalPlugin.Log.LogInfo((object)("[RigCheck] Sample model bones: " + string.Join(",", list))); ServalPlugin.Log.LogInfo((object)("[RigCheck] Sample source bones: " + string.Join(",", list2))); } } }