Decompiled source of ShowBossHealth v1.0.2

BepInEx/plugins/ShowBossHealth.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Sparroh.UI;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Sparroh")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyProduct("ShowBossHealth")]
[assembly: AssemblyTitle("ShowBossHealth")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 ShowBossHealth
{
	internal static class BossHealthbarExtensions
	{
		private static readonly FieldInfo TargetField = AccessTools.Field(typeof(BossHealthbar), "target");

		public static bool targetMatches(this BossHealthbar bar, EnemyBrain brain)
		{
			if ((Object)(object)bar == (Object)null || (Object)(object)brain == (Object)null || TargetField == null)
			{
				return false;
			}
			return TargetField.GetValue(bar) == brain;
		}

		public static void DestroySafe(this UIText text)
		{
			if (text != null)
			{
				UIHelpers.DestroySafe((Object)(object)text.GameObject);
			}
		}
	}
	internal static class BossHealthbarPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(BossHealthbar), "Initialize")]
		private static void PostfixInitialize(BossHealthbar prefab, EnemyBrain target)
		{
			if (BossHealthDisplay.Instance == null)
			{
				return;
			}
			try
			{
				List<BossHealthbar> instances = BossHealthbar.instances;
				if (instances == null || instances.Count == 0)
				{
					return;
				}
				BossHealthbar val = instances[instances.Count - 1];
				if ((Object)(object)val != (Object)null && val.targetMatches(target))
				{
					BossHealthDisplay.Instance.AttachToBar(val);
					return;
				}
				for (int num = instances.Count - 1; num >= 0; num--)
				{
					BossHealthbar val2 = instances[num];
					if ((Object)(object)val2 != (Object)null && val2.targetMatches(target))
					{
						BossHealthDisplay.Instance.AttachToBar(val2);
						break;
					}
				}
			}
			catch (Exception ex)
			{
				SparrohPlugin.Logger.LogError((object)("Failed to attach boss health label: " + ex.Message));
			}
		}
	}
	public class BossHealthDisplay
	{
		private readonly FieldInfo healthbarParentField;

		private readonly Dictionary<BossHealthbar, UIText> labels = new Dictionary<BossHealthbar, UIText>();

		private readonly List<BossHealthbar> removeBuffer = new List<BossHealthbar>();

		private readonly FieldInfo targetField;

		public static BossHealthDisplay Instance { get; private set; }

		public BossHealthDisplay()
		{
			Instance = this;
			targetField = AccessTools.Field(typeof(BossHealthbar), "target");
			healthbarParentField = AccessTools.Field(typeof(BossHealthbar), "healthbarParent");
			if (targetField == null)
			{
				SparrohPlugin.Logger.LogError((object)"Could not find BossHealthbar.target field.");
			}
			if (healthbarParentField == null)
			{
				SparrohPlugin.Logger.LogWarning((object)"Could not find BossHealthbar.healthbarParent field; using bar root for layout.");
			}
		}

		public void AttachToBar(BossHealthbar bar)
		{
			if (!((Object)(object)bar == (Object)null) && !labels.ContainsKey(bar) && ConfigManager.ShowBossHealth.Value)
			{
				CreateLabel(bar);
			}
		}

		private void CreateLabel(BossHealthbar bar)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: 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_0116: 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_0131: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)bar == (Object)null || labels.ContainsKey(bar))
			{
				return;
			}
			Transform transform = ((Component)bar).transform;
			object? obj = healthbarParentField?.GetValue(bar);
			RectTransform val = (RectTransform)((obj is RectTransform) ? obj : null);
			float num = UITheme.S(ConfigManager.FontSize.Value);
			UIText val2 = UIText.Create(transform, "BossHealthValue", "", num, (Color?)ConfigManager.TextColor.Value, (TextAlignmentOptions)514, false);
			if (val2 == null)
			{
				return;
			}
			RectTransform rect = val2.Rect;
			rect.anchorMin = new Vector2(0.5f, 0.5f);
			rect.anchorMax = new Vector2(0.5f, 0.5f);
			rect.pivot = new Vector2(0.5f, 1f);
			float num2 = 400f;
			float num3 = UITheme.S(ConfigManager.YOffset.Value);
			if ((Object)(object)val != (Object)null)
			{
				float[] array = new float[3];
				Rect rect2 = val.rect;
				array[0] = ((Rect)(ref rect2)).width;
				array[1] = val.sizeDelta.x;
				array[2] = 200f;
				num2 = Mathf.Max(array);
				rect2 = val.rect;
				float num4 = ((Rect)(ref rect2)).height * 0.5f;
				if (num4 <= 0f)
				{
					num4 = val.sizeDelta.y * 0.5f;
				}
				num3 = val.anchoredPosition.y - num4 + UITheme.S(ConfigManager.YOffset.Value);
			}
			rect.sizeDelta = new Vector2(num2, UITheme.S(22f));
			rect.anchoredPosition = new Vector2(0f, num3);
			labels[bar] = val2;
			UpdateLabel(bar, val2);
		}

		public void Update()
		{
			if (!ConfigManager.ShowBossHealth.Value)
			{
				if (labels.Count > 0)
				{
					ClearAllLabels();
				}
				return;
			}
			List<BossHealthbar> instances = BossHealthbar.instances;
			if (instances != null)
			{
				for (int i = 0; i < instances.Count; i++)
				{
					BossHealthbar val = instances[i];
					if ((Object)(object)val != (Object)null && !labels.ContainsKey(val))
					{
						CreateLabel(val);
					}
				}
			}
			removeBuffer.Clear();
			foreach (KeyValuePair<BossHealthbar, UIText> label in labels)
			{
				BossHealthbar key = label.Key;
				UIText value = label.Value;
				if ((Object)(object)key == (Object)null || value == null || !value.IsAlive)
				{
					removeBuffer.Add(key);
				}
				else
				{
					UpdateLabel(key, value);
				}
			}
			for (int j = 0; j < removeBuffer.Count; j++)
			{
				BossHealthbar val2 = removeBuffer[j];
				if (labels.TryGetValue(val2, out var value2))
				{
					value2?.DestroySafe();
					labels.Remove(val2);
				}
				else if ((Object)(object)val2 == (Object)null)
				{
					labels.Remove(val2);
				}
			}
		}

		private void UpdateLabel(BossHealthbar bar, UIText text)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			object? obj = targetField?.GetValue(bar);
			EnemyBrain val = (EnemyBrain)((obj is EnemyBrain) ? obj : null);
			if (!((Object)(object)val == (Object)null) && TryGetHealthValues(val, out var current, out var max))
			{
				Color value = ConfigManager.TextColor.Value;
				text.Color = value;
				text.FontSize = UITheme.S(ConfigManager.FontSize.Value);
				int num = Mathf.Max(0, Mathf.RoundToInt(current));
				int num2 = Mathf.Max(0, Mathf.RoundToInt(max));
				text.Text = $"{num} / {num2}";
			}
		}

		internal static bool TryGetHealthValues(EnemyBrain brain, out float current, out float max)
		{
			current = 0f;
			max = 0f;
			if ((Object)(object)brain == (Object)null || (Object)(object)brain.Core == (Object)null)
			{
				return false;
			}
			bool flag = false;
			try
			{
				if ((Object)(object)brain.EnemyClass != (Object)null)
				{
					flag = brain.EnemyClass.config.onlyUseCoreHealthForHealthbar;
				}
			}
			catch
			{
				flag = false;
			}
			if (flag)
			{
				current = ((EnemyPart)brain.Core).Health;
				max = ((EnemyPart)brain.Core).MaxHealth;
				return max > 0f;
			}
			SumShellHealth((IEnemyComponent)(object)brain.Core, ref current, ref max);
			if (max <= 0f)
			{
				current = ((EnemyPart)brain.Core).Health;
				max = ((EnemyPart)brain.Core).MaxHealth;
			}
			return max > 0f;
		}

		private static void SumShellHealth(IEnemyComponent component, ref float current, ref float max)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (component == null)
			{
				return;
			}
			EnemyPart val = (EnemyPart)(object)((component is EnemyPart) ? component : null);
			if (val != null && (val.ComponentType & 4) != 0)
			{
				max += val.MaxHealth;
				if (val.IsAlive)
				{
					current += val.Health;
				}
			}
			List<IEnemyComponent> childComponents = component.ChildComponents;
			if (childComponents != null)
			{
				for (int i = 0; i < childComponents.Count; i++)
				{
					SumShellHealth(childComponents[i], ref current, ref max);
				}
			}
		}

		private void ClearAllLabels()
		{
			foreach (KeyValuePair<BossHealthbar, UIText> label in labels)
			{
				label.Value?.DestroySafe();
			}
			labels.Clear();
		}

		public void OnDestroy()
		{
			ClearAllLabels();
			Instance = null;
		}
	}
	public static class ConfigManager
	{
		private const float DebounceSeconds = 0.25f;

		private static ConfigFile config;

		private static ManualLogSource logger;

		private static FileSystemWatcher configWatcher;

		private static volatile bool reloadPending;

		private static float lastReloadTime;

		public static ConfigEntry<bool> ShowBossHealth { get; private set; }

		public static ConfigEntry<float> FontSize { get; private set; }

		public static ConfigEntry<float> YOffset { get; private set; }

		public static ConfigColor TextColor { get; private set; }

		public static void Initialize(ConfigFile configFile, ManualLogSource log)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			config = configFile;
			logger = log;
			ShowBossHealth = config.Bind<bool>("General", "Show Boss Health", true, "Show current / max health under enemy HUD health bars.");
			FontSize = config.Bind<float>("Display", "Font Size", 14f, "Font size for health value text (reference 1080p pixels).");
			YOffset = config.Bind<float>("Display", "Y Offset", -4f, "Vertical offset below the health bar (reference pixels; negative is further down).");
			TextColor = ConfigColor.Bind(config, "Colors", "Text Color", UIColors.TextPrimary, "Health value text color (hex RRGGBB or #RRGGBB).");
			try
			{
				SetupFileWatcher();
			}
			catch (Exception ex)
			{
				logger.LogError((object)("Error setting up config file watcher: " + ex.Message));
			}
		}

		public static void Tick()
		{
			if (!reloadPending || Time.unscaledTime - lastReloadTime < 0.25f)
			{
				return;
			}
			reloadPending = false;
			lastReloadTime = Time.unscaledTime;
			try
			{
				config.Reload();
				logger.LogInfo((object)"Config reloaded from disk.");
			}
			catch (Exception ex)
			{
				logger.LogError((object)("Error reloading config: " + ex.Message));
			}
		}

		public static void Dispose()
		{
			if (configWatcher != null)
			{
				configWatcher.EnableRaisingEvents = false;
				configWatcher.Changed -= OnConfigFileChanged;
				configWatcher.Created -= OnConfigFileChanged;
				configWatcher.Renamed -= OnConfigFileChanged;
				configWatcher.Dispose();
				configWatcher = null;
			}
		}

		private static void SetupFileWatcher()
		{
			configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.showbosshealth.cfg");
			configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
			configWatcher.Changed += OnConfigFileChanged;
			configWatcher.Created += OnConfigFileChanged;
			configWatcher.Renamed += OnConfigFileChanged;
			configWatcher.EnableRaisingEvents = true;
		}

		private static void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			reloadPending = true;
		}
	}
	[BepInPlugin("sparroh.showbosshealth", "ShowBossHealth", "1.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[MycoMod(/*Could not decode attribute arguments.*/)]
	public class SparrohPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "sparroh.showbosshealth";

		public const string PluginName = "ShowBossHealth";

		public const string PluginVersion = "1.0.2";

		internal static ManualLogSource Logger;

		private Harmony harmony;

		private BossHealthDisplay mod;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Logger);
			try
			{
				harmony = new Harmony("sparroh.showbosshealth");
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Failed to create Harmony instance: " + ex.Message));
				return;
			}
			try
			{
				mod = new BossHealthDisplay();
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)("Failed to initialize ShowBossHealth: " + ex2.Message));
			}
			try
			{
				harmony.PatchAll(typeof(BossHealthbarPatches));
			}
			catch (Exception ex3)
			{
				Logger.LogError((object)("Failed to apply Harmony patches: " + ex3.Message));
			}
			Logger.LogInfo((object)"ShowBossHealth loaded successfully.");
		}

		private void Update()
		{
			ConfigManager.Tick();
			try
			{
				if (mod != null)
				{
					mod.Update();
				}
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Error in ShowBossHealth.Update(): " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			try
			{
				if (mod != null)
				{
					mod.OnDestroy();
				}
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Error in ShowBossHealth.OnDestroy(): " + ex.Message));
			}
			ConfigManager.Dispose();
			try
			{
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)("Error unpatching Harmony: " + ex2.Message));
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "ShowBossHealth";

		public const string PLUGIN_NAME = "ShowBossHealth";

		public const string PLUGIN_VERSION = "1.0.2";
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}