Decompiled source of ServalMod v1.1.0

Serval.dll

Decompiled 2 weeks ago
using 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)));
		}
	}
}