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)
{
}
}
}