using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EnemyHealthBar.Config; using EnemyHealthBar.Services; using EnemyHealthBar.UI; using EnemyHealthBar.Utilities; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [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: AssemblyVersion("0.0.0.0")] [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 EnemyHealthBar { [BepInPlugin("DarkSpider90.EnemyHealthBar", "EnemyHealthBar", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string ModGuid = "DarkSpider90.EnemyHealthBar"; public const string ModName = "EnemyHealthBar"; public const string ModVersion = "1.0.0"; private readonly Harmony _harmony = new Harmony("DarkSpider90.EnemyHealthBar"); internal static ManualLogSource Log { get; private set; } internal static ModConfig Settings { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Settings = new ModConfig(((BaseUnityPlugin)this).Config); _harmony.PatchAll(); Log.LogInfo((object)"EnemyHealthBar by DarkSpider90 loaded. v1.0.0"); } private void OnDestroy() { _harmony.UnpatchSelf(); } } } namespace EnemyHealthBar.Utilities { internal static class ColorUtils { internal static Color Parse(string htmlColor, Color fallback) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) Color val = default(Color); return ColorUtility.TryParseHtmlString(htmlColor, ref val) ? val : fallback; } internal static Color WithAlpha(Color color, float alpha) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) color.a = Mathf.Clamp01(alpha); return color; } } } namespace EnemyHealthBar.UI { internal sealed class EnemyHealthBarDisplay : MonoBehaviour { private const float BoundsRefreshInterval = 0.65f; private EnemyHealth _enemyHealth; private GameObject _root; private RectTransform _canvasRect; private Image _foreground; private Image _background; private TextMeshProUGUI _healthText; private int _displayedCurrent; private int _displayedMax; private bool _hasHealthState; private float _targetFill = 1f; private float _smoothedFill = 1f; private float _lastRevealTime = -999f; private float _verticalBarOffset = 1.7f; private float _nextBoundsRefreshTime; private static Sprite _whiteSprite; private void Awake() { _enemyHealth = ((Component)this).GetComponent(); } private void Start() { if (Plugin.Settings.HealthBarEnabled.Value && !((Object)(object)_enemyHealth == (Object)null)) { SetHealth(_enemyHealth.health, _enemyHealth.health, silent: true); } } private void LateUpdate() { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_enemyHealth == (Object)null) { DestroyRoot(); Object.Destroy((Object)(object)this); return; } if (!Plugin.Settings.HealthBarEnabled.Value || !_hasHealthState || _displayedCurrent <= 0) { HideRoot(); return; } if (!Plugin.Settings.AlwaysVisible.Value && Time.time - _lastRevealTime > Plugin.Settings.HideAfterSeconds.Value) { HideRoot(); return; } EnsureCanvas(); if ((Object)(object)_root == (Object)null || (Object)(object)_canvasRect == (Object)null) { return; } Vector3 val = CalculateTargetPosition(); _root.transform.position = val; Camera main = Camera.main; if ((Object)(object)main != (Object)null) { _root.transform.rotation = ((Component)main).transform.rotation; } float num = CalculateVisibilityAlpha(val, main); if (num <= 0f) { HideRoot(); return; } if (!_root.activeSelf) { _root.SetActive(true); } ((Transform)_canvasRect).localScale = Vector3.one * Mathf.Max(0.001f, Plugin.Settings.BarScale.Value); RefreshVisuals(num); } internal void SetHealth(int current, int max, bool silent) { _displayedMax = Mathf.Max(1, max); _displayedCurrent = Mathf.Clamp(current, 0, _displayedMax); _hasHealthState = true; _targetFill = Mathf.Clamp01((float)_displayedCurrent / (float)_displayedMax); if (silent) { _smoothedFill = _targetFill; } else { Reveal(); } } internal void ApplyClientDamage(int damage, int max) { int num = Mathf.Max(1, max); if (!_hasHealthState || _displayedMax != num) { _displayedMax = num; _displayedCurrent = num; _hasHealthState = true; _smoothedFill = 1f; } _displayedCurrent = Mathf.Clamp(_displayedCurrent - Mathf.Max(0, damage), 0, _displayedMax); _targetFill = Mathf.Clamp01((float)_displayedCurrent / (float)_displayedMax); Reveal(); } internal void Reveal() { _lastRevealTime = Time.time; if ((Object)(object)_root != (Object)null && !_root.activeSelf) { _root.SetActive(true); } } internal void KillImmediately() { DestroyRoot(); Object.Destroy((Object)(object)this); } private float CalculateVisibilityAlpha(Vector3 barPosition, Camera camera) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) float num = 1f; if (!Plugin.Settings.AlwaysVisible.Value) { float num2 = _lastRevealTime + Mathf.Max(0.1f, Plugin.Settings.HideAfterSeconds.Value); float num3 = num2 - Time.time; if (num3 <= 0f) { return 0f; } float num4 = Mathf.Max(0.01f, Plugin.Settings.HideFadeDuration.Value); if (num3 < num4) { num *= Mathf.Clamp01(num3 / num4); } } if ((Object)(object)camera != (Object)null) { float num5 = Mathf.Max(1f, Plugin.Settings.FadeDistance.Value); float num6 = Vector3.Distance(((Component)camera).transform.position, barPosition); num *= Mathf.Clamp01(1f - num6 / num5); } return num; } private Vector3 CalculateTargetPosition() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0048: Unknown result type (might be due to invalid IL or missing references) Transform val = ResolveFollowAnchor(); RefreshVerticalOffset(val); return val.position + Vector3.up * (_verticalBarOffset + Mathf.Max(0f, Plugin.Settings.ExtraHeightOffset.Value)); } private Transform ResolveFollowAnchor() { Transform val = ResolveEnemyRootForBounds(); if ((Object)(object)val != (Object)null) { EnemyRigidbody componentInChildren = ((Component)val).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { return ((Component)componentInChildren).transform; } Transform val2 = FindNamedDescendant(val, "Center"); if ((Object)(object)val2 != (Object)null) { return val2; } } return ((Component)this).transform; } private void RefreshVerticalOffset(Transform anchor) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)anchor == (Object)null) && !(Time.time < _nextBoundsRefreshTime)) { _nextBoundsRefreshTime = Time.time + 0.65f; Transform val = ResolveEnemyRootForBounds(); if (!((Object)(object)val == (Object)null) && TryResolveTopY(val, out var topY)) { _verticalBarOffset = Mathf.Clamp(topY - anchor.position.y + 0.2f, 0.4f, 16f); } } } private Transform ResolveEnemyRootForBounds() { if ((Object)(object)_enemyHealth != (Object)null && (Object)(object)((Component)_enemyHealth).transform != (Object)null && (Object)(object)((Component)_enemyHealth).transform.parent != (Object)null) { return ((Component)_enemyHealth).transform.parent; } return ((Component)this).transform; } private void EnsureCanvas() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_006a: 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) //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("EnemyHealthBar_View"); Canvas val = _root.AddComponent(); val.renderMode = (RenderMode)2; _canvasRect = ((Component)val).GetComponent(); _canvasRect.sizeDelta = new Vector2(Plugin.Settings.BarWidth.Value, Plugin.Settings.BarHeight.Value); ((Transform)_canvasRect).localScale = Vector3.one * Mathf.Max(0.001f, Plugin.Settings.BarScale.Value); _background = CreateImage((Transform)(object)_canvasRect, "Background"); Stretch(((Graphic)_background).rectTransform, 0f); _foreground = CreateImage((Transform)(object)_canvasRect, "Foreground"); Stretch(((Graphic)_foreground).rectTransform, 3f); _foreground.type = (Type)3; _foreground.fillMethod = (FillMethod)0; _foreground.fillOrigin = 0; _foreground.fillAmount = _smoothedFill; CreateHealthText((Transform)(object)_canvasRect); } } private static Image CreateImage(Transform parent, string name) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Image val2 = val.AddComponent(); val2.sprite = GetWhiteSprite(); ((Graphic)val2).raycastTarget = false; return val2; } private void CreateHealthText(Transform parent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown GameObject val = new GameObject("HealthText"); val.transform.SetParent(parent, false); _healthText = val.AddComponent(); ((TMP_Text)_healthText).alignment = (TextAlignmentOptions)514; ((TMP_Text)_healthText).enableAutoSizing = false; ((Graphic)_healthText).raycastTarget = false; RectTransform rectTransform = ((TMP_Text)_healthText).rectTransform; Stretch(rectTransform, 0f); } private static void Stretch(RectTransform rect, float inset) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(inset, inset); rect.offsetMax = new Vector2(0f - inset, 0f - inset); } private void RefreshVisuals(float alpha) { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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_013f: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0153: 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_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_foreground == (Object)null) && !((Object)(object)_background == (Object)null)) { _smoothedFill = Mathf.Lerp(_smoothedFill, _targetFill, Time.deltaTime * Mathf.Max(0.01f, Plugin.Settings.BarLerpSpeed.Value)); _foreground.fillAmount = _smoothedFill; float num = ((_displayedMax > 0) ? Mathf.Clamp01((float)_displayedCurrent / (float)_displayedMax) : 0f); bool flag = num <= Plugin.Settings.LowHealthThreshold.Value; Color color = (flag ? ColorUtils.Parse(Plugin.Settings.LowHealthBarColor.Value, Color.yellow) : ColorUtils.Parse(Plugin.Settings.BarColor.Value, Color.red)); Color val = ColorUtils.Parse(Plugin.Settings.BackgroundColor.Value, new Color(0f, 0f, 0f, 0.65f)); Color val2 = (flag ? ColorUtils.Parse(Plugin.Settings.LowHealthTextColor.Value, Color.yellow) : ColorUtils.Parse(Plugin.Settings.TextColor.Value, Color.white)); ((Graphic)_foreground).color = ColorUtils.WithAlpha(color, alpha); ((Graphic)_background).color = ColorUtils.WithAlpha(val, val.a * alpha); if (!((Object)(object)_healthText == (Object)null)) { ((Component)_healthText).gameObject.SetActive(Plugin.Settings.ShowHealthText.Value); ((TMP_Text)_healthText).fontSize = Plugin.Settings.TextSize.Value; ((Graphic)_healthText).color = ColorUtils.WithAlpha(val2, val2.a * alpha); ((TMP_Text)_healthText).rectTransform.anchoredPosition = new Vector2(0f, Plugin.Settings.TextOffsetY.Value); ((TMP_Text)_healthText).text = (Plugin.Settings.ShowMaxHealth.Value ? $"{_displayedCurrent} / {_displayedMax}" : _displayedCurrent.ToString()); } } } private static Sprite GetWhiteSprite() { //IL_0037: 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) if ((Object)(object)_whiteSprite != (Object)null) { return _whiteSprite; } Texture2D whiteTexture = Texture2D.whiteTexture; _whiteSprite = Sprite.Create(whiteTexture, new Rect(0f, 0f, (float)((Texture)whiteTexture).width, (float)((Texture)whiteTexture).height), new Vector2(0.5f, 0.5f), 100f); return _whiteSprite; } private static bool TryResolveTopY(Transform root, out float topY) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: 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) topY = 0f; bool flag = false; Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(false); Renderer[] array = componentsInChildren; Bounds bounds; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name == "ParticleSystemRenderer")) { bounds = val.bounds; float y = ((Bounds)(ref bounds)).max.y; if (!flag || y > topY) { topY = y; flag = true; } } } if (flag) { return true; } Collider[] componentsInChildren2 = ((Component)root).GetComponentsInChildren(false); Collider[] array2 = componentsInChildren2; foreach (Collider val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !val2.isTrigger) { bounds = val2.bounds; float y2 = ((Bounds)(ref bounds)).max.y; if (!flag || y2 > topY) { topY = y2; flag = true; } } } return flag; } private static Transform FindNamedDescendant(Transform root, string namePart) { if ((Object)(object)root == (Object)null) { return null; } string value = namePart.ToLowerInvariant(); Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if ((Object)(object)val != (Object)null && ((Object)val).name.ToLowerInvariant().Contains(value)) { return val; } } return null; } private void HideRoot() { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } } private void DestroyRoot() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } } private void OnDisable() { DestroyRoot(); } private void OnDestroy() { DestroyRoot(); } } internal sealed class FloatingDamageText : MonoBehaviour { private TextMeshProUGUI _text; private Vector3 _startWorldPosition; private float _age; private float _lifetime; private float _riseDistance; private float _fadeMultiplier; internal void Initialize(int damage, Vector3 position, DamageTextStyle style) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0158: 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_0170: 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) _startWorldPosition = position + Random.insideUnitSphere * Plugin.Settings.DamageTextRandomOffset.Value; _lifetime = Mathf.Max(0.05f, style.Lifetime); _riseDistance = style.FloatSpeed; _fadeMultiplier = Mathf.Max(0.01f, style.FadeMultiplier); Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)2; RectTransform component = ((Component)val).GetComponent(); component.sizeDelta = new Vector2(150f, 72f); ((Transform)component).localScale = Vector3.one * style.CanvasScale; GameObject val2 = new GameObject("Text"); val2.transform.SetParent((Transform)(object)component, false); _text = val2.AddComponent(); ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((TMP_Text)_text).enableAutoSizing = false; ((TMP_Text)_text).fontStyle = (FontStyles)1; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).text = FormatDamage(style.Format, damage); ((Graphic)_text).color = style.Color; ((TMP_Text)_text).fontSize = style.Size; RectTransform rectTransform = ((TMP_Text)_text).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((Component)this).transform.position = _startWorldPosition; FaceMainCamera(); } private void LateUpdate() { //IL_004b: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) _age += Time.deltaTime; float num = Mathf.Clamp01(_age / _lifetime); if (num >= 1f) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } ((Component)this).transform.position = _startWorldPosition + Vector3.up * (num * _riseDistance); FaceMainCamera(); if ((Object)(object)_text != (Object)null) { ((TMP_Text)_text).alpha = Mathf.Clamp01((1f - num * num) * _fadeMultiplier); } } private static string FormatDamage(string format, int damage) { if (string.IsNullOrWhiteSpace(format)) { format = "-{damage}"; } return format.Replace("{damage}", damage.ToString()); } private void FaceMainCamera() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) Camera main = Camera.main; if ((Object)(object)main != (Object)null) { ((Component)this).transform.rotation = ((Component)main).transform.rotation; } } } } namespace EnemyHealthBar.Services { internal static class DamageTextSpawner { internal static void Spawn(Vector3 worldPosition, int damage) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Settings.DamageTextEnabled.Value) { DamageTextStyle style = DamageTextStyle.FromDamage(damage); GameObject val = new GameObject("EnemyHealthBar_DamageText"); val.AddComponent().Initialize(damage, worldPosition, style); } } } internal readonly struct DamageTextStyle { internal readonly Color Color; internal readonly float Size; internal readonly float Lifetime; internal readonly float FloatSpeed; internal readonly float FadeMultiplier; internal readonly float CanvasScale; internal readonly string Format; private DamageTextStyle(Color color, float size, float lifetime, float floatSpeed, float fadeMultiplier, float canvasScale, string format) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) Color = color; Size = size; Lifetime = lifetime; FloatSpeed = floatSpeed; FadeMultiplier = fadeMultiplier; CanvasScale = canvasScale; Format = format; } internal static DamageTextStyle FromDamage(int damage) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_007d: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) ModConfig settings = Plugin.Settings; Color color = ColorUtils.Parse(settings.DamageTextColor.Value, new Color(1f, 0.6f, 0.18f)); float value = settings.DamageTextSize.Value; if (settings.UseDamageTiers.Value) { if (damage >= settings.CriticalDamageThreshold.Value) { color = ColorUtils.Parse(settings.CriticalDamageTextColor.Value, new Color(1f, 0.31f, 0.27f)); value = settings.CriticalDamageTextSize.Value; } else if (damage >= settings.MediumDamageThreshold.Value) { color = ColorUtils.Parse(settings.MediumDamageTextColor.Value, new Color(1f, 0.83f, 0.35f)); value = settings.MediumDamageTextSize.Value; } } return new DamageTextStyle(color, value, Mathf.Max(0.05f, settings.DamageTextLifetime.Value), settings.DamageTextFloatSpeed.Value, settings.DamageTextFadeMultiplier.Value, Mathf.Max(0.001f, settings.DamageTextScale.Value), settings.DamageTextFormat.Value); } } } namespace EnemyHealthBar.Patches { [HarmonyPatch] internal static class EnemyHealthPatches { private static readonly Dictionary LastDamageTextTimeByEnemy = new Dictionary(); private static readonly FieldRef HealthCurrentRef = AccessTools.FieldRefAccess("healthCurrent"); [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "Awake")] private static void EnemyHealthAwakePostfix(EnemyHealth __instance) { try { EnsureHealthBar(__instance); if (Plugin.Settings.VerboseLogging.Value) { Plugin.Log.LogInfo((object)("Initialized health bar for " + ((Object)__instance).name)); } } catch (Exception arg) { Plugin.Log.LogError((object)$"EnemyHealthBar Awake patch failed: {arg}"); } } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "OnSpawn")] private static void EnemyHealthOnSpawnPostfix(EnemyHealth __instance) { EnemyHealthBarDisplay enemyHealthBarDisplay = EnsureHealthBar(__instance); enemyHealthBarDisplay.SetHealth(__instance.health, __instance.health, silent: true); } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "HurtRPC")] private static void EnemyHealthHurtRpcPostfix(EnemyHealth __instance, int _damage, Vector3 _hurtDirection) { if ((Object)(object)__instance == (Object)null || _damage <= 0) { return; } try { EnemyHealthBarDisplay enemyHealthBarDisplay = EnsureHealthBar(__instance); if (SemiFunc.IsMasterClientOrSingleplayer()) { enemyHealthBarDisplay.SetHealth(HealthCurrentRef.Invoke(__instance), __instance.health, silent: false); } else { enemyHealthBarDisplay.ApplyClientDamage(_damage, __instance.health); } SpawnDamageText(__instance, _damage); } catch (Exception arg) { Plugin.Log.LogError((object)$"EnemyHealthBar HurtRPC patch failed: {arg}"); } } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "DeathRPC")] private static void EnemyHealthDeathRpcPostfix(EnemyHealth __instance) { DestroyHealthBar(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "Death")] private static void EnemyHealthDeathPostfix(EnemyHealth __instance) { DestroyHealthBar(__instance); } private static EnemyHealthBarDisplay EnsureHealthBar(EnemyHealth enemyHealth) { EnemyHealthBarDisplay enemyHealthBarDisplay = ((Component)enemyHealth).GetComponent(); if ((Object)(object)enemyHealthBarDisplay == (Object)null) { enemyHealthBarDisplay = ((Component)enemyHealth).gameObject.AddComponent(); } return enemyHealthBarDisplay; } private static void DestroyHealthBar(EnemyHealth enemyHealth) { if (!((Object)(object)enemyHealth == (Object)null)) { LastDamageTextTimeByEnemy.Remove(((Object)enemyHealth).GetInstanceID()); EnemyHealthBarDisplay component = ((Component)enemyHealth).GetComponent(); if ((Object)(object)component != (Object)null) { component.KillImmediately(); } } } private static void SpawnDamageText(EnemyHealth enemyHealth, int damage) { //IL_005f: 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_0065: 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_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) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Settings.DamageTextEnabled.Value) { int instanceID = ((Object)enemyHealth).GetInstanceID(); float time = Time.time; if (!LastDamageTextTimeByEnemy.TryGetValue(instanceID, out var value) || !(time - value < 0.05f)) { LastDamageTextTimeByEnemy[instanceID] = time; Vector3 position = ((Component)enemyHealth).transform.position; Vector3 worldPosition = position + Vector3.up * Plugin.Settings.DamageTextHeightOffset.Value; DamageTextSpawner.Spawn(worldPosition, Mathf.Clamp(damage, 1, 9999)); } } } } } namespace EnemyHealthBar.Config { internal sealed class ModConfig { internal readonly ConfigEntry HealthBarEnabled; internal readonly ConfigEntry AlwaysVisible; internal readonly ConfigEntry ShowHealthText; internal readonly ConfigEntry ShowMaxHealth; internal readonly ConfigEntry BarWidth; internal readonly ConfigEntry BarHeight; internal readonly ConfigEntry BarScale; internal readonly ConfigEntry BarLerpSpeed; internal readonly ConfigEntry ExtraHeightOffset; internal readonly ConfigEntry HideAfterSeconds; internal readonly ConfigEntry HideFadeDuration; internal readonly ConfigEntry FadeDistance; internal readonly ConfigEntry BarColor; internal readonly ConfigEntry LowHealthBarColor; internal readonly ConfigEntry BackgroundColor; internal readonly ConfigEntry TextColor; internal readonly ConfigEntry LowHealthTextColor; internal readonly ConfigEntry LowHealthThreshold; internal readonly ConfigEntry TextSize; internal readonly ConfigEntry TextOffsetY; internal readonly ConfigEntry DamageTextEnabled; internal readonly ConfigEntry DamageTextFormat; internal readonly ConfigEntry UseDamageTiers; internal readonly ConfigEntry DamageTextHeightOffset; internal readonly ConfigEntry DamageTextRandomOffset; internal readonly ConfigEntry DamageTextLifetime; internal readonly ConfigEntry DamageTextFloatSpeed; internal readonly ConfigEntry DamageTextFadeMultiplier; internal readonly ConfigEntry DamageTextScale; internal readonly ConfigEntry DamageTextColor; internal readonly ConfigEntry DamageTextSize; internal readonly ConfigEntry MediumDamageThreshold; internal readonly ConfigEntry MediumDamageTextColor; internal readonly ConfigEntry MediumDamageTextSize; internal readonly ConfigEntry CriticalDamageThreshold; internal readonly ConfigEntry CriticalDamageTextColor; internal readonly ConfigEntry CriticalDamageTextSize; internal readonly ConfigEntry VerboseLogging; internal ModConfig(ConfigFile config) { HealthBarEnabled = config.Bind("Health Bar", "Enabled", true, "Show enemy health bars."); AlwaysVisible = config.Bind("Health Bar", "AlwaysVisible", false, "Always show health bars, even without recent damage."); ShowHealthText = config.Bind("Health Bar", "ShowHealthText", true, "Show health numbers above the bar."); ShowMaxHealth = config.Bind("Health Bar", "ShowMaxHealth", true, "Show current/max health instead of current health only."); BarWidth = config.Bind("Health Bar", "BarWidth", 210f, "World-space health bar canvas width."); BarHeight = config.Bind("Health Bar", "BarHeight", 28f, "World-space health bar canvas height."); BarScale = config.Bind("Health Bar", "BarScale", 0.0068f, "World-space health bar scale."); BarLerpSpeed = config.Bind("Health Bar", "BarLerpSpeed", 11f, "Health bar fill smoothing speed."); ExtraHeightOffset = config.Bind("Health Bar", "ExtraHeightOffset", 0.62f, "Extra height above enemy after automatic height calculation."); HideAfterSeconds = config.Bind("Health Bar", "HideAfterSeconds", 8f, "Hide health bar after this many seconds without damage."); HideFadeDuration = config.Bind("Health Bar", "HideFadeDuration", 0.45f, "Fade-out duration before the bar disappears."); FadeDistance = config.Bind("Health Bar", "FadeDistance", 48f, "Distance where health bar fully fades out."); BarColor = config.Bind("Health Bar", "BarColor", "#E2372F", "Health bar color in HTML format."); LowHealthBarColor = config.Bind("Health Bar", "LowHealthBarColor", "#FFD447", "Health bar color below low health threshold."); BackgroundColor = config.Bind("Health Bar", "BackgroundColor", "#16191ECC", "Health bar background color in HTML format."); TextColor = config.Bind("Health Bar", "TextColor", "#F7F8FA", "Health text color in HTML format."); LowHealthTextColor = config.Bind("Health Bar", "LowHealthTextColor", "#FFD447", "Health text color below low health threshold."); LowHealthThreshold = config.Bind("Health Bar", "LowHealthThreshold", 0.3f, "Low health threshold from 0 to 1."); TextSize = config.Bind("Health Bar", "TextSize", 14f, "Health text size."); TextOffsetY = config.Bind("Health Bar", "TextOffsetY", 0f, "Health text vertical offset."); DamageTextEnabled = config.Bind("Damage Text", "Enabled", true, "Show floating damage numbers."); DamageTextFormat = config.Bind("Damage Text", "TextFormat", "-{damage}", "Damage text format. Use {damage} as placeholder."); UseDamageTiers = config.Bind("Damage Text", "UseDamageTiers", true, "Use separate colors/sizes for medium and critical damage."); DamageTextHeightOffset = config.Bind("Damage Text", "HeightOffset", 1.65f, "Damage text height above enemy transform."); DamageTextRandomOffset = config.Bind("Damage Text", "RandomOffsetRadius", 0.28f, "Random offset radius for damage text."); DamageTextLifetime = config.Bind("Damage Text", "Lifetime", 1.05f, "Damage text lifetime."); DamageTextFloatSpeed = config.Bind("Damage Text", "FloatSpeed", 1.25f, "Damage text upward movement distance."); DamageTextFadeMultiplier = config.Bind("Damage Text", "FadeMultiplier", 1f, "Damage text fade multiplier."); DamageTextScale = config.Bind("Damage Text", "Scale", 0.0105f, "World-space damage text canvas scale."); DamageTextColor = config.Bind("Damage Text", "Color", "#FF9A2F", "Default damage text color."); DamageTextSize = config.Bind("Damage Text", "Size", 30f, "Default damage text size."); MediumDamageThreshold = config.Bind("Damage Text", "MediumDamageThreshold", 30, "Minimum damage for medium damage text style."); MediumDamageTextColor = config.Bind("Damage Text", "MediumColor", "#FFD35A", "Medium damage text color."); MediumDamageTextSize = config.Bind("Damage Text", "MediumSize", 35f, "Medium damage text size."); CriticalDamageThreshold = config.Bind("Damage Text", "CriticalDamageThreshold", 60, "Minimum damage for critical damage text style."); CriticalDamageTextColor = config.Bind("Damage Text", "CriticalColor", "#FF4E45", "Critical damage text color."); CriticalDamageTextSize = config.Bind("Damage Text", "CriticalSize", 42f, "Critical damage text size."); VerboseLogging = config.Bind("Debug", "VerboseLogging", false, "Enable verbose logs."); } } }