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 NativeEnemyHealthBars v1.0.0
BepInEx/plugins/NativeEnemyHealthBars/NativeEnemyHealthUI.dll
Decompiled 6 hours agousing System; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace NativeEnemyHealthUI; [BepInPlugin("chatgpt.repo.nativeenemyhealthui", "Native Enemy Health UI", "1.0.8")] public sealed class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry<float> MaxDistance; internal static ConfigEntry<float> VisibleSeconds; internal static ConfigEntry<float> UiScale; internal static ConfigEntry<float> ExtraHeight; internal static ConfigEntry<float> ScreenXOffset; private Harmony _harmony; private void Awake() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Enemy Health UI", "Max Distance", 40f, new ConfigDescription("Maximum camera distance for the health display.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), new object[0])); VisibleSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Enemy Health UI", "Visible Seconds After Damage", 4f, new ConfigDescription("How long the native bar stays visible after a hit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 15f), new object[0])); UiScale = ((BaseUnityPlugin)this).Config.Bind<float>("Enemy Health UI", "UI Scale", 0.5f, new ConfigDescription("Base scale of the native enemy health UI.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), new object[0])); ExtraHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Enemy Health UI", "Extra Height Offset", 1.25f, new ConfigDescription("DamageShow-style extra height added above the enemy bounds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), new object[0])); ScreenXOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Enemy Health UI", "Screen X Offset", 10f, new ConfigDescription("Small horizontal correction in HUD units. Positive values move the enemy bar to the right.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-50f, 50f), new object[0])); _harmony = new Harmony("chatgpt.repo.nativeenemyhealthui.v108"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Native Enemy Health UI 1.0.8 loaded - final stabilized placement."); } } [HarmonyPatch] internal static class EnemyHealthPatches { [HarmonyPatch(typeof(EnemyHealth), "Hurt")] [HarmonyPostfix] private static void HurtPostfix(EnemyHealth __instance, int _damage) { if ((Object)(object)__instance == (Object)null || _damage <= 0) { return; } try { NativeEnemyBar nativeEnemyBar = ((Component)__instance).GetComponent<NativeEnemyBar>(); if ((Object)(object)nativeEnemyBar == (Object)null) { nativeEnemyBar = ((Component)__instance).gameObject.AddComponent<NativeEnemyBar>(); } nativeEnemyBar.ShowAfterDamage(); if (Plugin.Log != null) { Plugin.Log.LogDebug((object)"[NativeEnemyHealthUI] Hurt detected; requested native health bar."); } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogWarning((object)("[NativeEnemyHealthUI] Hurt hook failed: " + ex.Message)); } } } } public sealed class NativeEnemyBar : MonoBehaviour { private EnemyHealth _health; private Enemy _enemy; private EnemyParent _parent; private Transform _followTransform; private float _nextFollowResolveTime; private float _nextOffsetResolveTime; private float _worldOffsetY = 1.25f; private float _targetWorldOffsetY = 1.25f; private float _worldOffsetVelocity; private bool _hasOffsetSample; private Vector3 _currentWorldAnchor; private GameObject _healthClone; private RectTransform _cloneRect; private RectTransform _uiParentRect; private Canvas _hostCanvas; private Camera _worldCamera; private float _visibleUntil = -1000f; private int _lastCurrent = int.MinValue; private int _lastMax = int.MinValue; public void ShowAfterDamage() { EnsureReferences(); EnsureUi(); _visibleUntil = Time.realtimeSinceStartup + ((Plugin.VisibleSeconds != null) ? Plugin.VisibleSeconds.Value : 4f); ResolveFollowTarget(force: true); UpdateWorldOffsetFromBounds(force: true); UpdateCurrentWorldAnchor(); RefreshHealthText(force: true); UpdateScreenPose(log: true); if ((Object)(object)_healthClone != (Object)null) { _healthClone.SetActive(true); } } private void EnsureReferences() { if ((Object)(object)_health == (Object)null) { _health = ((Component)this).GetComponent<EnemyHealth>(); } if ((Object)(object)_health != (Object)null && (Object)(object)_enemy == (Object)null) { _enemy = EnemyAccess.GetEnemy(_health); } if ((Object)(object)_enemy == (Object)null) { _enemy = ((Component)this).GetComponentInParent<Enemy>(); } if ((Object)(object)_parent == (Object)null) { _parent = EnemyAccess.GetEnemyParent(_enemy); if ((Object)(object)_parent == (Object)null) { _parent = ((Component)this).GetComponentInParent<EnemyParent>(); } } if ((Object)(object)_worldCamera == (Object)null) { _worldCamera = Camera.main; } if ((Object)(object)_followTransform == (Object)null) { _followTransform = ResolveFollowTransform(); } } private Transform ResolveFollowTransform() { Transform damageShowFollowTransform = EnemyAccess.GetDamageShowFollowTransform(_enemy); if ((Object)(object)damageShowFollowTransform != (Object)null) { return damageShowFollowTransform; } if (!((Object)(object)_health != (Object)null)) { return ((Component)this).transform; } return ((Component)_health).transform; } private void ResolveFollowTarget(bool force) { if (!force && Time.time < _nextFollowResolveTime) { return; } _nextFollowResolveTime = Time.time + 0.5f; Transform val = ResolveFollowTransform(); if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)_followTransform) { _followTransform = val; UpdateWorldOffsetFromBounds(force: true); if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("[NativeEnemyHealthUI] DamageShow follow target rebound: " + HierarchyPath(_followTransform))); } } } private Transform ResolveBoundsRoot() { if ((Object)(object)_health != (Object)null && (Object)(object)((Component)_health).transform != (Object)null && (Object)(object)((Component)_health).transform.parent != (Object)null) { return ((Component)_health).transform.parent; } if ((Object)(object)_parent != (Object)null) { return ((Component)_parent).transform; } if ((Object)(object)_health != (Object)null) { return ((Component)_health).transform; } return null; } private void UpdateWorldOffsetFromBounds(bool force) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (!force && Time.time < _nextOffsetResolveTime) { return; } _nextOffsetResolveTime = Time.time + 0.25f; Transform val = ResolveBoundsRoot(); if (!((Object)(object)val == (Object)null) && !((Object)(object)_followTransform == (Object)null) && TryGetWorldBounds(val, out var bounds)) { float num = ((Bounds)(ref bounds)).max.y - _followTransform.position.y + 0.15f; num = Mathf.Clamp(num, 0.35f, 12f); float num2 = ((Plugin.ExtraHeight != null) ? Plugin.ExtraHeight.Value : 1.25f); if (num2 < 0f) { num2 = 0f; } float num3 = Mathf.Clamp(num + num2, 0.35f, 20f); if (!_hasOffsetSample || force) { _targetWorldOffsetY = num3; _worldOffsetY = num3; _worldOffsetVelocity = 0f; _hasOffsetSample = true; } else { num3 = Mathf.Clamp(num3, _targetWorldOffsetY - 0.55f, _targetWorldOffsetY + 0.55f); _targetWorldOffsetY = num3; } } } private static bool TryGetWorldBounds(Transform root, out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); bool flag = false; if ((Object)(object)root == (Object)null) { return false; } Collider[] componentsInChildren = ((Component)root).GetComponentsInChildren<Collider>(true); Collider[] array = componentsInChildren; foreach (Collider val in array) { if (!((Object)(object)val == (Object)null) && !val.isTrigger) { if (!flag) { bounds = val.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val.bounds); } } } if (flag) { return true; } Renderer[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<Renderer>(true); Renderer[] array2 = componentsInChildren2; foreach (Renderer val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { if (!flag) { bounds = val2.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val2.bounds); } } } return flag; } private void UpdateCurrentWorldAnchor() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_followTransform == (Object)null)) { if (_hasOffsetSample) { _worldOffsetY = Mathf.SmoothDamp(_worldOffsetY, _targetWorldOffsetY, ref _worldOffsetVelocity, 0.1f, 8f, Mathf.Max(Time.deltaTime, 0.001f)); } _currentWorldAnchor = _followTransform.position + Vector3.up * _worldOffsetY; } } private void EnsureUi() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_healthClone != (Object)null) { return; } GameObject template = NativeTemplateResolver.GetTemplate(); if ((Object)(object)template == (Object)null) { return; } try { Transform parent = template.transform.parent; if (!((Object)(object)parent == (Object)null)) { _healthClone = Object.Instantiate<GameObject>(template, parent, false); ((Object)_healthClone).name = "Native Enemy Health"; _healthClone.SetActive(true); RemoveNativeHealthControllerDirect(_healthClone); _cloneRect = _healthClone.GetComponent<RectTransform>(); _uiParentRect = (RectTransform)(object)((parent is RectTransform) ? parent : null); _hostCanvas = _healthClone.GetComponentInParent<Canvas>(); if ((Object)(object)_cloneRect != (Object)null) { _cloneRect.anchorMin = new Vector2(0.5f, 0.5f); _cloneRect.anchorMax = new Vector2(0.5f, 0.5f); _cloneRect.pivot = new Vector2(0.5f, 0.5f); _cloneRect.anchoredPosition = Vector2.zero; ((Transform)_cloneRect).localScale = Vector3.one * 0.5f; ((Transform)_cloneRect).localRotation = Quaternion.identity; } _healthClone.transform.SetAsLastSibling(); ForceCanvasGroupsVisible(_healthClone); if (Plugin.Log != null) { string text = (((Object)(object)_parent != (Object)null) ? (_parent.enemyName ?? ((Object)_parent).name) : ((Object)((Component)this).gameObject).name); Plugin.Log.LogInfo((object)("[NativeEnemyHealthUI] Native bar attached with DamageShow placement: " + text + " | follow=" + (((Object)(object)_followTransform != (Object)null) ? HierarchyPath(_followTransform) : "<pending>") + " | canvas=" + (((Object)(object)_hostCanvas != (Object)null) ? ((object)_hostCanvas.renderMode).ToString() : "<none>"))); } } } catch (Exception ex) { if ((Object)(object)_healthClone != (Object)null) { Object.Destroy((Object)(object)_healthClone); } _healthClone = null; _cloneRect = null; _uiParentRect = null; _hostCanvas = null; if (Plugin.Log != null) { Plugin.Log.LogWarning((object)("[NativeEnemyHealthUI] UI creation failed: " + ex)); } } } private void LateUpdate() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_health == (Object)null || (Object)(object)_healthClone == (Object)null) { return; } int currentHealth = EnemyAccess.GetCurrentHealth(_health); if (Time.realtimeSinceStartup > _visibleUntil || currentHealth <= 0) { if (_healthClone.activeSelf) { _healthClone.SetActive(false); } return; } EnsureReferences(); if ((Object)(object)_worldCamera == (Object)null) { return; } ResolveFollowTarget(force: false); UpdateWorldOffsetFromBounds(force: false); UpdateCurrentWorldAnchor(); float num = ((Plugin.MaxDistance != null) ? Plugin.MaxDistance.Value : 40f); if ((Object)(object)_followTransform == (Object)null || Vector3.Distance(((Component)_worldCamera).transform.position, _followTransform.position) > num) { if (_healthClone.activeSelf) { _healthClone.SetActive(false); } } else { UpdateScreenPose(log: false); RefreshHealthText(force: false); } } private void UpdateScreenPose(bool log) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_healthClone == (Object)null || (Object)(object)_cloneRect == (Object)null || (Object)(object)_worldCamera == (Object)null || (Object)(object)_followTransform == (Object)null) { return; } Vector3 val = _worldCamera.WorldToViewportPoint(_currentWorldAnchor); if (val.z <= 0f) { _healthClone.SetActive(false); return; } float num = Vector3.Distance(((Component)_worldCamera).transform.position, _currentWorldAnchor); if ((Object)(object)_uiParentRect != (Object)null) { Rect rect = _uiParentRect.rect; float num2 = ((Plugin.ScreenXOffset != null) ? Plugin.ScreenXOffset.Value : 10f); _cloneRect.anchoredPosition = new Vector2((val.x - 0.5f) * ((Rect)(ref rect)).width + num2, (val.y - 0.5f) * ((Rect)(ref rect)).height); } float num3 = ((Plugin.UiScale != null) ? Plugin.UiScale.Value : 0.5f); float num4 = Mathf.InverseLerp(5f, 40f, num); float num5 = Mathf.Lerp(1f, 0.45f, num4); float num6 = num3 * num5; ((Transform)_cloneRect).localScale = Vector3.one * num6; if (!_healthClone.activeSelf) { _healthClone.SetActive(true); } if (log && Plugin.Log != null) { Plugin.Log.LogInfo((object)string.Concat("[NativeEnemyHealthUI] DAMAGESHOW FOLLOW SHOW follow=", _followTransform.position, " anchor=", _currentWorldAnchor, " viewport=", val, " offsetY=", _worldOffsetY.ToString("0.00"), " targetOffsetY=", _targetWorldOffsetY.ToString("0.00"), " anchored=", _cloneRect.anchoredPosition, " scale=", num6.ToString("0.00"), " distance=", num.ToString("0.00"))); } } private void RefreshHealthText(bool force) { if ((Object)(object)_healthClone == (Object)null || (Object)(object)_health == (Object)null) { return; } int num = Mathf.Max(0, EnemyAccess.GetCurrentHealth(_health)); int num2 = Mathf.Max(1, EnemyAccess.GetMaxHealth(_health, num)); if (force || num != _lastCurrent || num2 != _lastMax) { _lastCurrent = num; _lastMax = num2; TextUtil.SetText(_healthClone, num.ToString()); GameObject val = TextUtil.FindChild(_healthClone.transform, "HealthMax"); if ((Object)(object)val != (Object)null) { TextUtil.SetText(val, num2.ToString()); } } } private static void RemoveNativeHealthControllerDirect(GameObject clone) { if ((Object)(object)clone == (Object)null) { return; } Component[] components = clone.GetComponents<Component>(); Component[] array = components; foreach (Component val in array) { if (!((Object)(object)val == (Object)null) && string.Equals(((object)val).GetType().Name, "HealthUI", StringComparison.OrdinalIgnoreCase)) { Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if ((Object)(object)val2 != (Object)null) { val2.enabled = false; } Object.Destroy((Object)(object)val); } } } private static void ForceCanvasGroupsVisible(GameObject root) { if ((Object)(object)root == (Object)null) { return; } CanvasGroup[] componentsInChildren = root.GetComponentsInChildren<CanvasGroup>(true); CanvasGroup[] array = componentsInChildren; foreach (CanvasGroup val in array) { if (!((Object)(object)val == (Object)null)) { val.alpha = 1f; val.interactable = false; val.blocksRaycasts = false; } } } private static string HierarchyPath(Transform t) { if ((Object)(object)t == (Object)null) { return "<null>"; } List<string> list = new List<string>(); Transform val = t; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list.ToArray()); } private void OnDestroy() { if ((Object)(object)_healthClone != (Object)null) { Object.Destroy((Object)(object)_healthClone); } _healthClone = null; _cloneRect = null; _uiParentRect = null; _hostCanvas = null; _followTransform = null; } } internal static class EnemyAccess { private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly FieldInfo HealthEnemyField = typeof(EnemyHealth).GetField("enemy", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo HealthCurrentField = typeof(EnemyHealth).GetField("healthCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo EnemyParentField = typeof(Enemy).GetField("EnemyParent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo EnemyHasRigidbodyField = typeof(Enemy).GetField("HasRigidbody", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo EnemyHasRigidbodyProperty = typeof(Enemy).GetProperty("HasRigidbody", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo EnemyRigidbodyField = typeof(Enemy).GetField("Rigidbody", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo EnemyRigidbodyProperty = typeof(Enemy).GetProperty("Rigidbody", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo EnemyCenterTransformField = typeof(Enemy).GetField("CenterTransform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo EnemyCenterTransformProperty = typeof(Enemy).GetProperty("CenterTransform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static bool _loggedLayout; public static Enemy GetEnemy(EnemyHealth health) { if ((Object)(object)health == (Object)null) { return null; } try { if (HealthEnemyField != null) { object? value = HealthEnemyField.GetValue(health); Enemy val = (Enemy)((value is Enemy) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } return ((Component)health).GetComponentInParent<Enemy>(); } public static Transform GetDamageShowFollowTransform(Enemy enemy) { if ((Object)(object)enemy == (Object)null) { return null; } try { bool flag = false; if (EnemyHasRigidbodyField != null) { flag = Convert.ToBoolean(EnemyHasRigidbodyField.GetValue(enemy)); } else if (EnemyHasRigidbodyProperty != null) { flag = Convert.ToBoolean(EnemyHasRigidbodyProperty.GetValue(enemy, null)); } if (flag) { object obj = null; if (EnemyRigidbodyField != null) { obj = EnemyRigidbodyField.GetValue(enemy); } else if (EnemyRigidbodyProperty != null) { obj = EnemyRigidbodyProperty.GetValue(enemy, null); } Rigidbody val = (Rigidbody)((obj is Rigidbody) ? obj : null); if ((Object)(object)val != (Object)null) { return ((Component)val).transform; } } object obj2 = null; if (EnemyCenterTransformField != null) { obj2 = EnemyCenterTransformField.GetValue(enemy); } else if (EnemyCenterTransformProperty != null) { obj2 = EnemyCenterTransformProperty.GetValue(enemy, null); } Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null); if ((Object)(object)val2 != (Object)null) { return val2; } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("[NativeEnemyHealthUI] DamageShow follow resolve fallback: " + ex.Message)); } } return ((Component)enemy).transform; } public static EnemyParent GetEnemyParent(Enemy enemy) { if ((Object)(object)enemy == (Object)null) { return null; } try { if (EnemyParentField != null) { object? value = EnemyParentField.GetValue(enemy); EnemyParent val = (EnemyParent)((value is EnemyParent) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } return ((Component)enemy).GetComponentInParent<EnemyParent>(); } public static int GetCurrentHealth(EnemyHealth health) { if ((Object)(object)health == (Object)null) { return 0; } LogLayoutOnce(); try { if (HealthCurrentField != null) { return Convert.ToInt32(HealthCurrentField.GetValue(health)); } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogWarning((object)("[NativeEnemyHealthUI] Could not read private healthCurrent: " + ex.Message)); } } return Mathf.Max(0, health.health); } public static int GetMaxHealth(EnemyHealth health, int current) { if ((Object)(object)health == (Object)null) { return Mathf.Max(1, current); } try { return Mathf.Max(current, health.health); } catch { return Mathf.Max(1, current); } } private static void LogLayoutOnce() { if (!_loggedLayout && Plugin.Log != null) { _loggedLayout = true; Plugin.Log.LogInfo((object)("[NativeEnemyHealthUI] Enemy layout: healthCurrent=" + ((HealthCurrentField != null) ? "found" : "missing") + ", EnemyHealth.enemy=" + ((HealthEnemyField != null) ? "found" : "missing") + ", Enemy.EnemyParent=" + ((EnemyParentField != null) ? "found" : "missing"))); } } } internal static class NativeTemplateResolver { private static GameObject _template; private static float _nextSearchAt; private static bool _pathLogged; public static GameObject GetTemplate() { if ((Object)(object)_template != (Object)null) { return _template; } if (Time.realtimeSinceStartup < _nextSearchAt) { return null; } _nextSearchAt = Time.realtimeSinceStartup + 2f; string[] array = new string[2] { "UI/HUD/HUD Canvas/HUD/Game Hud/Health", "HUD Canvas/HUD/Game Hud/Health" }; string[] array2 = array; foreach (string text in array2) { GameObject val = GameObject.Find(text); if (IsHealthTemplate(val)) { _template = val; LogPath(val.transform); return _template; } } Transform[] array3 = Object.FindObjectsOfType<Transform>(); Transform[] array4 = array3; foreach (Transform val2 in array4) { if (!((Object)(object)val2 == (Object)null) && string.Equals(((Object)val2).name, "Health", StringComparison.OrdinalIgnoreCase) && IsHealthTemplate(((Component)val2).gameObject)) { _template = ((Component)val2).gameObject; LogPath(val2); return _template; } } return null; } private static bool IsHealthTemplate(GameObject go) { if ((Object)(object)go == (Object)null) { return false; } Component[] components = go.GetComponents<Component>(); Component[] array = components; foreach (Component val in array) { if ((Object)(object)val != (Object)null && string.Equals(((object)val).GetType().Name, "HealthUI", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void LogPath(Transform t) { if (!_pathLogged && Plugin.Log != null && !((Object)(object)t == (Object)null)) { _pathLogged = true; Plugin.Log.LogInfo((object)("[NativeEnemyHealthUI] Native template resolved: " + HierarchyPath(t))); } } private static string HierarchyPath(Transform t) { List<string> list = new List<string>(); Transform val = t; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list.ToArray()); } } internal static class HealthUiGeometry { public static Vector3 GetAnchor(EnemyParent parent, Enemy enemy, Transform fallback, float extraHeight) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)parent != (Object)null) ? ((Component)parent).transform : (((Object)(object)enemy != (Object)null) ? ((Component)enemy).transform : fallback)); if ((Object)(object)val == (Object)null) { return Vector3.zero; } Collider[] colliders = (Collider[])(((Object)(object)parent != (Object)null) ? ((Component)parent).GetComponentsInChildren<Collider>(true) : (((Object)(object)enemy != (Object)null) ? ((Array)((Component)enemy).GetComponentsInChildren<Collider>(true)) : ((Array)new Collider[0]))); if (TryCompactBounds(colliders, val.position, includeTriggers: false, out var bounds) || TryCompactBounds(colliders, val.position, includeTriggers: true, out bounds)) { return new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y + extraHeight, ((Bounds)(ref bounds)).center.z); } Renderer[] array = (Renderer[])(((Object)(object)parent != (Object)null) ? ((Component)parent).GetComponentsInChildren<Renderer>(true) : (((Object)(object)enemy != (Object)null) ? ((Array)((Component)enemy).GetComponentsInChildren<Renderer>(true)) : ((Array)new Renderer[0]))); bool flag = false; Bounds val2 = default(Bounds); ((Bounds)(ref val2))..ctor(val.position, Vector3.zero); Renderer[] array2 = array; foreach (Renderer val3 in array2) { if ((Object)(object)val3 == (Object)null || !val3.enabled || !((Component)val3).gameObject.activeInHierarchy) { continue; } Bounds bounds2 = val3.bounds; Vector3 size = ((Bounds)(ref bounds2)).size; if (!(((Vector3)(ref size)).magnitude > 6f) && !(Vector3.Distance(((Bounds)(ref bounds2)).center, val.position) > 4f)) { if (!flag) { val2 = bounds2; flag = true; } else { ((Bounds)(ref val2)).Encapsulate(bounds2); } } } if (flag) { return new Vector3(((Bounds)(ref val2)).center.x, ((Bounds)(ref val2)).max.y + extraHeight, ((Bounds)(ref val2)).center.z); } return val.position + Vector3.up * (1.6f + extraHeight); } private static bool TryCompactBounds(Collider[] colliders, Vector3 origin, bool includeTriggers, out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); bool flag = false; if (colliders == null) { return false; } foreach (Collider val in colliders) { if ((Object)(object)val == (Object)null || !val.enabled || (!includeTriggers && val.isTrigger)) { continue; } Bounds bounds2 = val.bounds; Vector3 size = ((Bounds)(ref bounds2)).size; float magnitude = ((Vector3)(ref size)).magnitude; float num = Vector3.Distance(((Bounds)(ref bounds2)).center, origin); if (!(magnitude < 0.05f) && !(magnitude > 6f) && !(num > 4f)) { if (!flag) { bounds = bounds2; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(bounds2); } } } return flag; } } internal static class TextUtil { public static GameObject FindChild(Transform root, string name) { if ((Object)(object)root == (Object)null) { return null; } Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true); Transform[] array = componentsInChildren; foreach (Transform val in array) { if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, name, StringComparison.OrdinalIgnoreCase)) { return ((Component)val).gameObject; } } return null; } public static void SetText(GameObject obj, string text) { if ((Object)(object)obj == (Object)null) { return; } Component[] components = obj.GetComponents<Component>(); Component[] array = components; foreach (Component val in array) { if ((Object)(object)val == (Object)null) { continue; } PropertyInfo property = ((object)val).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property == null) && property.CanWrite && !(property.PropertyType != typeof(string))) { try { property.SetValue(val, text, null); break; } catch { } } } } }
BepInEx/plugins/NativeEnemyHealthBars/NativeEnemyHealthUIFix.dll
Decompiled 6 hours agousing 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<int> CleanedEnemyIds = new HashSet<int>(); 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<MonoBehaviour>(); 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<RectTransform>() != (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 { } } }