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 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 labels = new Dictionary(); private readonly List removeBuffer = new List(); 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 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 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 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 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 ShowBossHealth { get; private set; } public static ConfigEntry FontSize { get; private set; } public static ConfigEntry 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("General", "Show Boss Health", true, "Show current / max health under enemy HUD health bars."); FontSize = config.Bind("Display", "Font Size", 14f, "Font size for health value text (reference 1080p pixels)."); YOffset = config.Bind("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) { } } }