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)));
}
}
}