using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ChatGPT.NativeEnemyHealthUI.DamageShowEventFix; [BepInPlugin("chatgpt.nativeenemyhealthui.damageshoweventfix", "Native Enemy Health UI - DamageShow Event Fix", "1.0.9.2")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "chatgpt.nativeenemyhealthui.damageshoweventfix"; public const string PluginName = "Native Enemy Health UI - DamageShow Event Fix"; public const string PluginVersion = "1.0.9.2"; internal static Plugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("chatgpt.nativeenemyhealthui.damageshoweventfix"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Native Enemy Health UI 1.0.9c loaded - direct NativeEnemyBar death cleanup, no periodic scan."); } internal static void QueueCleanup(EnemyHealth health) { if (!((Object)(object)Instance == (Object)null) && !((Object)(object)health == (Object)null)) { ((MonoBehaviour)Instance).StartCoroutine(DelayedCleanup(health)); } } private static IEnumerator DelayedCleanup(EnemyHealth health) { yield return null; yield return (object)new WaitForEndOfFrame(); if ((Object)(object)health != (Object)null) { NativeBarCleanup.CleanupFor(health, "DeathImpulseRPC"); } } } [HarmonyPatch(typeof(EnemyHealth), "DeathImpulseRPC")] internal static class EnemyHealthDeathImpulseRpcPatch { private static void Postfix(EnemyHealth __instance) { Plugin.QueueCleanup(__instance); } } internal static class NativeBarCleanup { private static readonly HashSet CleanedEnemyIds = new HashSet(); private static Assembly _nativeAssembly; internal static void CleanupFor(EnemyHealth health, string reason) { if ((Object)(object)health == (Object)null) { return; } int instanceID; try { instanceID = ((Object)health).GetInstanceID(); } catch { return; } if (CleanedEnemyIds.Contains(instanceID)) { return; } Assembly assembly = FindNativeAssembly(); if (assembly == null) { Plugin.Log.LogWarning((object)"[NativeEnemyHealthUI] DamageShow cleanup skipped: NativeEnemyHealthUI assembly not found."); return; } Transform val = FindEnemyRoot(health); int num = 0; int num2 = 0; int num3 = 0; try { MonoBehaviour[] array = Resources.FindObjectsOfTypeAll(); foreach (MonoBehaviour val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } Type type; try { type = ((object)val2).GetType(); } catch { continue; } if (type.Assembly != assembly || !ReferencesEnemy(val2, health, val)) { continue; } num++; GameObject val3 = null; try { val3 = ((Component)val2).gameObject; } catch { } if (type.FullName == "NativeEnemyHealthUI.NativeEnemyBar" && (Object)(object)val3 != (Object)null) { try { val3.SetActive(false); } catch { } Object.Destroy((Object)(object)val3); num2++; continue; } try { ((Behaviour)val2).enabled = false; } catch { } if ((Object)(object)val3 != (Object)null && IsIndividualUiObject(val3)) { try { val3.SetActive(false); } catch { } Object.Destroy((Object)(object)val3); num2++; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NativeEnemyHealthUI] DamageShow component cleanup error: " + ex.GetType().Name + ": " + ex.Message)); } try { num3 = RemoveNativeCacheEntries(assembly, health, val); } catch (Exception ex2) { Plugin.Log.LogDebug((object)("[NativeEnemyHealthUI] Cache cleanup skipped: " + ex2.GetType().Name + ": " + ex2.Message)); } CleanedEnemyIds.Add(instanceID); string text = "(unknown)"; try { text = (((Object)(object)val != (Object)null) ? ((Object)val).name : ((Object)health).name); } catch { } Plugin.Log.LogInfo((object)("[NativeEnemyHealthUI] DAMAGESHOW DEATH CLEANUP | enemy='" + text + "' | reason=" + reason + " | matched=" + num + " | destroyed=" + num2 + " | cacheEntries=" + num3)); } private static Assembly FindNativeAssembly() { if (_nativeAssembly != null) { return _nativeAssembly; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { string text = ""; try { text = assemblies[i].GetName().Name; } catch { } if (!string.IsNullOrEmpty(text)) { string text2 = text.ToLowerInvariant(); if (text2 == "nativeenemyhealthui" || text2.Contains("nativeenemyhealthui")) { _nativeAssembly = assemblies[i]; Plugin.Log.LogInfo((object)("[NativeEnemyHealthUI] 1.0.8 assembly detected: " + text)); return _nativeAssembly; } } } return null; } private static Transform FindEnemyRoot(EnemyHealth health) { Transform val; try { val = ((Component)health).transform; } catch { return null; } Transform result = val; while ((Object)(object)val != (Object)null) { string text = ""; try { text = ((Object)val).name; } catch { } if (!string.IsNullOrEmpty(text) && text.StartsWith("Enemy - ", StringComparison.OrdinalIgnoreCase)) { return val; } result = val; val = val.parent; } return result; } private static bool ReferencesEnemy(object instance, EnemyHealth health, Transform enemyRoot) { if (instance == null) { return false; } FieldInfo[] fields; try { fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { return false; } for (int i = 0; i < fields.Length; i++) { object value; try { value = fields[i].GetValue(instance); } catch { continue; } if (ValueReferencesEnemy(value, health, enemyRoot)) { return true; } } return false; } private static bool ValueReferencesEnemy(object value, EnemyHealth health, Transform enemyRoot) { if (value == null) { return false; } if (object.ReferenceEquals(value, health)) { return true; } Component val = (Component)((value is Component) ? value : null); if ((Object)(object)val != (Object)null && (Object)(object)enemyRoot != (Object)null) { try { Transform transform = val.transform; if ((Object)(object)transform == (Object)(object)enemyRoot || transform.IsChildOf(enemyRoot)) { return true; } } catch { } } GameObject val2 = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val2 != (Object)null && (Object)(object)enemyRoot != (Object)null) { try { Transform transform2 = val2.transform; if ((Object)(object)transform2 == (Object)(object)enemyRoot || transform2.IsChildOf(enemyRoot)) { return true; } } catch { } } return false; } private static bool IsIndividualUiObject(GameObject go) { if ((Object)(object)go == (Object)null) { return false; } string text = ""; try { text = (((Object)go).name ?? "").ToLowerInvariant(); } catch { } if (text.Contains("manager") || text.Contains("plugin") || text.Contains("controller") || text.Contains("canvas")) { return false; } try { if ((Object)(object)go.GetComponent() != (Object)null) { return true; } } catch { } return false; } private static int RemoveNativeCacheEntries(Assembly native, EnemyHealth health, Transform enemyRoot) { int num = 0; Type[] types; try { types = native.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } foreach (Type type in types) { if (type == null) { continue; } FieldInfo[] fields; try { fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } for (int j = 0; j < fields.Length; j++) { object value; try { value = fields[j].GetValue(null); } catch { continue; } if (!(value is IDictionary dictionary)) { continue; } ArrayList arrayList = new ArrayList(); foreach (DictionaryEntry item in dictionary) { if (!ValueReferencesEnemy(item.Key, health, enemyRoot) && !ValueReferencesEnemy(item.Value, health, enemyRoot)) { continue; } arrayList.Add(item.Key); object? value2 = item.Value; Object val = (Object)((value2 is Object) ? value2 : null); if (!(val != (Object)null)) { continue; } Component val2 = (Component)(object)((val is Component) ? val : null); if ((Object)(object)val2 != (Object)null) { try { GameObject gameObject = val2.gameObject; if (IsIndividualUiObject(gameObject)) { Object.Destroy((Object)(object)gameObject); } } catch { } } GameObject val3 = (GameObject)(object)((val is GameObject) ? val : null); if ((Object)(object)val3 != (Object)null && IsIndividualUiObject(val3)) { Object.Destroy((Object)(object)val3); } } for (int k = 0; k < arrayList.Count; k++) { try { dictionary.Remove(arrayList[k]); num++; } catch { } } } } return num; } private static void TryInvokeNoArg(object instance, string methodName) { try { MethodInfo methodInfo = AccessTools.Method(instance.GetType(), methodName, Type.EmptyTypes, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(instance, null); } } catch { } } }