Decompiled source of Enemy Health Bars v0.2.0

EnemyHealthBars.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.UI;
using PerfectRandom.Sulfur.Core.Units;
using PerfectRandom.Sulfur.Gameplay;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Enemy Health Bars")]
[assembly: AssemblyProduct("Enemy Health Bars")]
[assembly: AssemblyCompany("Ryuka")]
[assembly: ComVisible(false)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
[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 EnemyHealthBars
{
	internal static class BarArt
	{
		public const float PixelsPerCanvasUnit = 4f;

		public const int RoundsCount = 10;

		public const float SulfurFillStart = 0.018656716f;

		public const float SulfurFillEnd = 0.98134327f;

		public const float RoundsFillStart = 3f / 134f;

		public const float RoundsFillEnd = 131f / 134f;

		public const float IchorFillStart = 0.045774646f;

		public const float IchorFillEnd = 0.95422536f;

		public const float BoneFillStart = 15f / 136f;

		public const float BoneFillEnd = 0.9669118f;

		public const float ClassicFillStart = 1f / 122f;

		public const float ClassicFillEnd = 121f / 122f;

		public const float SulfurMarkLeft = 90f;

		public const float SulfurMarkTop = 8f;

		public const float SulfurMarkPivotX = 122f;

		public const float SulfurMarkPivotY = 14f;

		public const float SulfurSkullX = 120.86f;

		public const float SulfurSkullY = 35.995f;

		public const float SulfurSkullHeight = 9.75f;

		public const float SulfurSkullAngle = -4f;

		public const bool SulfurMarkBehind = false;

		public const float RoundsMarkLeft = 137f;

		public const float RoundsMarkTop = -7f;

		public const float RoundsMarkPivotX = 134f;

		public const float RoundsMarkPivotY = 12.5f;

		public const float RoundsSkullX = 145.5f;

		public const float RoundsSkullY = 16.4f;

		public const float RoundsSkullHeight = 6f;

		public const float RoundsSkullAngle = 0f;

		public const bool RoundsMarkBehind = false;

		public const float IchorMarkLeft = 98f;

		public const float IchorMarkTop = 10f;

		public const float IchorMarkPivotX = 124f;

		public const float IchorMarkPivotY = 12f;

		public const float IchorSkullX = 128.2f;

		public const float IchorSkullY = 59.25f;

		public const float IchorSkullHeight = 19.5f;

		public const float IchorSkullAngle = 0f;

		public const bool IchorMarkBehind = true;

		public const float BoneMarkLeft = -17.5f;

		public const float BoneMarkTop = -14f;

		public const float BoneMarkPivotX = 7.5f;

		public const float BoneMarkPivotY = 11f;

		public const float BoneSkullX = 0f;

		public const float BoneSkullY = 0f;

		public const float BoneSkullHeight = 0f;

		public const float BoneSkullAngle = 0f;

		public const bool BoneMarkBehind = true;

		public const float ClassicMarkLeft = 124f;

		public const float ClassicMarkTop = -5.5f;

		public const float ClassicMarkPivotX = 122f;

		public const float ClassicMarkPivotY = 5.5f;

		public const float ClassicSkullX = 135f;

		public const float ClassicSkullY = 5.5f;

		public const float ClassicSkullHeight = 13.6f;

		public const float ClassicSkullAngle = 0f;

		public const bool ClassicMarkBehind = false;
	}
	public enum BarStyleId
	{
		Sulfur = 1,
		Rounds,
		Ichor,
		Bone,
		Classic
	}
	internal sealed class BarStyle
	{
		private struct MarkLayout
		{
			public float Left;

			public float Top;

			public float PivotX;

			public float PivotY;

			public float SkullX;

			public float SkullY;

			public float SkullHeight;

			public float SkullAngle;

			public bool Behind;

			public bool HasEyes;

			public Color SkullBone;

			public Color SkullHoles;
		}

		private static readonly Color HudRed = new Color(35f / 51f, 0.2980392f, 0.290196f, 1f);

		private static readonly Color PaleTrail = new Color(0.96f, 0.85f, 0.66f, 0.9f);

		private static readonly Color SkullColor = new Color(0.94f, 0.91f, 0.63f, 1f);

		private static readonly Dictionary<BarStyleId, BarStyle> Loaded = new Dictionary<BarStyleId, BarStyle>();

		private static readonly List<Object> Owned = new List<Object>();

		private static MethodInfo _decodeImage;

		private bool _marksTried;

		private static readonly Color SkullDark = new Color(0.11372549f, 0.23137255f, 0.2f, 1f);

		private static readonly Color SeeThrough = new Color(0f, 0f, 0f, 0f);

		private static readonly Dictionary<BarStyleId, MarkLayout> Layouts = new Dictionary<BarStyleId, MarkLayout>
		{
			{
				BarStyleId.Sulfur,
				new MarkLayout
				{
					Left = 90f,
					Top = 8f,
					PivotX = 122f,
					PivotY = 14f,
					SkullX = 120.86f,
					SkullY = 35.995f,
					SkullHeight = 9.75f,
					SkullAngle = -4f,
					Behind = false,
					SkullBone = SkullDark,
					SkullHoles = SeeThrough
				}
			},
			{
				BarStyleId.Rounds,
				new MarkLayout
				{
					Left = 137f,
					Top = -7f,
					PivotX = 134f,
					PivotY = 12.5f,
					SkullX = 145.5f,
					SkullY = 16.4f,
					SkullHeight = 6f,
					SkullAngle = 0f,
					Behind = false,
					SkullBone = SkullDark,
					SkullHoles = SeeThrough
				}
			},
			{
				BarStyleId.Ichor,
				new MarkLayout
				{
					Left = 98f,
					Top = 10f,
					PivotX = 124f,
					PivotY = 12f,
					SkullX = 128.2f,
					SkullY = 59.25f,
					SkullHeight = 19.5f,
					SkullAngle = 0f,
					Behind = true,
					SkullBone = SulfurConstants.COLOR_SulfurYellow,
					SkullHoles = SkullDark
				}
			},
			{
				BarStyleId.Bone,
				new MarkLayout
				{
					Left = -17.5f,
					Top = -14f,
					PivotX = 7.5f,
					PivotY = 11f,
					SkullX = 0f,
					SkullY = 0f,
					SkullHeight = 0f,
					SkullAngle = 0f,
					Behind = true,
					HasEyes = true,
					SkullBone = SkullDark,
					SkullHoles = SeeThrough
				}
			},
			{
				BarStyleId.Classic,
				new MarkLayout
				{
					Left = 124f,
					Top = -5.5f,
					PivotX = 122f,
					PivotY = 5.5f,
					SkullX = 135f,
					SkullY = 5.5f,
					SkullHeight = 13.6f,
					SkullAngle = 0f,
					Behind = false,
					SkullBone = SkullDark,
					SkullHoles = SeeThrough
				}
			}
		};

		public BarStyleId Id { get; private set; }

		public Vector2 Size { get; private set; }

		public float FillStart { get; private set; }

		public float FillEnd { get; private set; }

		public int Steps { get; private set; }

		public bool FollowsUiColor { get; private set; }

		public Color FillColor { get; private set; }

		public Color TrailColor { get; private set; }

		public Color NameColor { get; private set; }

		public Sprite Back { get; private set; }

		public Sprite Fill { get; private set; }

		public Sprite Frame { get; private set; }

		public Sprite[] Marks { get; private set; }

		public Sprite MarkEyes { get; private set; }

		public Rect MarkRect { get; private set; }

		public Vector2 MarkPivot { get; private set; }

		public bool MarkBehind { get; private set; }

		public Vector2 SkullCentre { get; private set; }

		public float SkullHeight { get; private set; }

		public float SkullAngle { get; private set; }

		public Color SkullBoneColor { get; private set; }

		public Color SkullHoleColor { get; private set; }

		public float FillAmount(float health)
		{
			if (health <= 0f)
			{
				return 0f;
			}
			float num = Mathf.Clamp01(health);
			if (Steps > 0)
			{
				num = Mathf.Ceil(num * (float)Steps) / (float)Steps;
			}
			return Mathf.Lerp(FillStart, FillEnd, num);
		}

		public static BarStyle Get(BarStyleId id)
		{
			if (Loaded.TryGetValue(id, out var value))
			{
				return value;
			}
			value = Load(id);
			if (value == null && id != BarStyleId.Classic)
			{
				value = Get(BarStyleId.Classic);
			}
			Loaded[id] = value;
			return value;
		}

		public static void Unload()
		{
			foreach (Object item in Owned)
			{
				Object.Destroy(item);
			}
			Owned.Clear();
			Loaded.Clear();
		}

		private static BarStyle Load(BarStyleId id)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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_0086: Unknown result type (might be due to invalid IL or missing references)
			BarStyle barStyle = Describe(id);
			if (barStyle == null)
			{
				Plugin.Log.LogError((object)("Unknown bar style " + id.ToString() + "."));
				return null;
			}
			try
			{
				barStyle.Id = id;
				barStyle.Back = LoadSprite(id, "back");
				barStyle.Fill = LoadSprite(id, "fill");
				barStyle.Frame = LoadSprite(id, "frame");
				Rect rect = barStyle.Back.rect;
				barStyle.Size = ((Rect)(ref rect)).size / 4f;
				return barStyle;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Bar style " + id.ToString() + " could not be loaded: " + ex.Message));
				return null;
			}
		}

		public bool EnsureMarks()
		{
			if (_marksTried)
			{
				return false;
			}
			_marksTried = true;
			LoadMarks(this);
			return Marks != null;
		}

		private static void LoadMarks(BarStyle style)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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_00b4: 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)
			//IL_00c0: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			if (!Layouts.TryGetValue(style.Id, out var value))
			{
				return;
			}
			try
			{
				Sprite[] array = (Sprite[])(object)new Sprite[3];
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = LoadSprite(style.Id, "mark" + (i + 1));
				}
				style.MarkEyes = (value.HasEyes ? LoadSprite(style.Id, "eyes") : null);
				float y = style.Size.y;
				Rect rect = array[0].rect;
				Vector2 val = ((Rect)(ref rect)).size / 4f;
				style.MarkRect = new Rect(value.Left, y - value.Top - val.y, val.x, val.y);
				style.MarkPivot = new Vector2(value.PivotX, y - value.PivotY);
				style.SkullCentre = new Vector2(value.SkullX, y - value.SkullY);
				style.SkullHeight = value.SkullHeight;
				style.SkullAngle = value.SkullAngle;
				style.MarkBehind = value.Behind;
				style.SkullBoneColor = value.SkullBone;
				style.SkullHoleColor = value.SkullHoles;
				style.Marks = array;
			}
			catch (Exception ex)
			{
				style.Marks = null;
				style.MarkEyes = null;
				Plugin.Log.LogError((object)("Team marks for bar style " + style.Id.ToString() + " could not be loaded: " + ex.Message));
			}
		}

		private static bool DecodeImage(Texture2D texture, byte[] bytes)
		{
			if (_decodeImage == null)
			{
				_decodeImage = AccessTools.TypeByName("UnityEngine.ImageConversion")?.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3]
				{
					typeof(Texture2D),
					typeof(byte[]),
					typeof(bool)
				}, null);
				if (_decodeImage == null)
				{
					throw new MissingMethodException("UnityEngine.ImageConversion", "LoadImage");
				}
			}
			return (bool)_decodeImage.Invoke(null, new object[3] { texture, bytes, true });
		}

		private static BarStyle Describe(BarStyleId id)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: 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_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: 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)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			return id switch
			{
				BarStyleId.Sulfur => new BarStyle
				{
					FillStart = 0.018656716f,
					FillEnd = 0.98134327f,
					FollowsUiColor = true,
					FillColor = HudRed,
					TrailColor = PaleTrail,
					NameColor = Color.white
				}, 
				BarStyleId.Rounds => new BarStyle
				{
					FillStart = 3f / 134f,
					FillEnd = 131f / 134f,
					Steps = 10,
					FollowsUiColor = true,
					FillColor = HudRed,
					TrailColor = PaleTrail,
					NameColor = Color.white
				}, 
				BarStyleId.Ichor => new BarStyle
				{
					FillStart = 0.045774646f,
					FillEnd = 0.95422536f,
					FillColor = HudRed,
					TrailColor = PaleTrail,
					NameColor = Color.white
				}, 
				BarStyleId.Bone => new BarStyle
				{
					FillStart = 15f / 136f,
					FillEnd = 0.9669118f,
					FillColor = HudRed,
					TrailColor = PaleTrail,
					NameColor = SkullColor
				}, 
				BarStyleId.Classic => new BarStyle
				{
					FillStart = 1f / 122f,
					FillEnd = 121f / 122f,
					FillColor = new Color(0.82f, 0.16f, 0.13f, 1f),
					TrailColor = new Color(0.95f, 0.85f, 0.75f, 0.9f),
					NameColor = Color.white
				}, 
				_ => null, 
			};
		}

		private static Sprite LoadSprite(BarStyleId id, string layer)
		{
			//IL_00a6: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			string text = "EnemyHealthBars.Styles." + id.ToString() + "." + layer + ".png";
			byte[] array;
			using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text))
			{
				if (stream == null)
				{
					throw new FileNotFoundException("missing resource", text);
				}
				array = new byte[stream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = stream.Read(array, i, array.Length - i);
					if (num <= 0)
					{
						throw new EndOfStreamException(text);
					}
				}
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true)
			{
				name = text,
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)2
			};
			((Object)val).hideFlags = (HideFlags)61;
			Owned.Add((Object)(object)val);
			if (!DecodeImage(val, array))
			{
				throw new InvalidDataException("not an image: " + text);
			}
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height);
			Sprite val3 = Sprite.Create(val, val2, new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
			((Object)val3).name = text;
			((Object)val3).hideFlags = (HideFlags)61;
			Owned.Add((Object)(object)val3);
			return val3;
		}
	}
	[HarmonyPatch(typeof(BossHealth))]
	internal static class BossBarTracker
	{
		private static readonly Dictionary<BossHealth, Unit> Attached = new Dictionary<BossHealth, Unit>();

		public static bool Shows(Unit unit)
		{
			foreach (KeyValuePair<BossHealth, Unit> item in Attached)
			{
				if ((Object)(object)item.Value == (Object)(object)unit)
				{
					return true;
				}
			}
			return false;
		}

		public static void Reset()
		{
			Attached.Clear();
		}

		[HarmonyPostfix]
		[HarmonyPatch("Attach")]
		private static void AfterAttach(BossHealth __instance, Unit unit)
		{
			Attached[__instance] = unit;
		}

		[HarmonyPostfix]
		[HarmonyPatch("Detach")]
		private static void AfterDetach(BossHealth __instance, Unit unit)
		{
			if (Attached.TryGetValue(__instance, out var value) && ((Object)(object)unit == (Object)null || (Object)(object)unit == (Object)(object)value))
			{
				Attached.Remove(__instance);
			}
		}
	}
	internal static class EnemyRules
	{
		private static readonly FieldRef<Npc, float> CurrentOpacity = ResolveOpacity("unitOpacity");

		private static readonly FieldRef<Npc, float> TargetOpacity = ResolveOpacity("unitTargetOpacity");

		private const float HiddenBelowOpacity = 0.5f;

		private static FieldRef<Npc, float> ResolveOpacity(string field)
		{
			try
			{
				return AccessTools.FieldRefAccess<Npc, float>(field);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Npc." + field + " was not found; ghosts will never show a bar: " + ex.Message));
				return null;
			}
		}

		public static bool CanEverShow(Npc npc)
		{
			if ((Object)(object)((Unit)npc).unitSO != (Object)null && ((Unit)npc).Stats != null && !((Unit)npc).IsProtectedNpc && !((Unit)npc).IsCivilian)
			{
				return !((Unit)npc).IsBoss;
			}
			return false;
		}

		public static bool CanShowNow(TrackedEnemy enemy, Unit player)
		{
			Npc npc = enemy.Npc;
			if (!((Component)npc).gameObject.activeInHierarchy || !((Unit)npc).IsAlive || ((Unit)npc).GetCurrentHealth() <= 0f)
			{
				return false;
			}
			if (!((Unit)npc).IsHostileTo(player))
			{
				return false;
			}
			if (((Unit)npc).IsCharmed || (Object)(object)npc.forcedCharmingOwner != (Object)null || npc.isFoulAlly)
			{
				return false;
			}
			if (!IsHidden(enemy) && (Object)(object)((Unit)npc).unitFrame == (Object)null)
			{
				return !BossBarTracker.Shows((Unit)(object)npc);
			}
			return false;
		}

		private static bool IsHidden(TrackedEnemy enemy)
		{
			Npc npc = enemy.Npc;
			if (((Unit)npc).isInvisible || npc.HiddenMode || !((Unit)npc).Targetable)
			{
				return true;
			}
			if ((Object)(object)enemy.Sniper != (Object)null && enemy.Sniper.IsHidden)
			{
				return true;
			}
			if ((Object)(object)npc.mainRenderer != (Object)null && !((Component)npc.mainRenderer).gameObject.activeInHierarchy)
			{
				return true;
			}
			if (CurrentOpacity == null || TargetOpacity == null)
			{
				return enemy.IsGhost;
			}
			if (!(CurrentOpacity.Invoke(npc) < 0.5f))
			{
				return TargetOpacity.Invoke(npc) < 0.5f;
			}
			return true;
		}
	}
	public static class HealthBarIntegration
	{
		public const int MaxMarks = 3;

		private static Func<object, int> _markCount;

		private static bool _marksChanged;

		internal static bool HasMarkSource => _markCount != null;

		public static void SetMarkSource(Func<object, int> markCount)
		{
			_markCount = markCount;
			_marksChanged = true;
		}

		public static void NotifyMarksChanged()
		{
			_marksChanged = true;
		}

		public static bool IsShowingMarks(object unit)
		{
			return HealthBarManager.IsShowingMarks(unit);
		}

		internal static bool TakeMarksChanged()
		{
			bool marksChanged = _marksChanged;
			_marksChanged = false;
			return marksChanged;
		}

		internal static int ReadMarks(object unit)
		{
			Func<object, int> markCount = _markCount;
			if (markCount == null)
			{
				return 0;
			}
			try
			{
				int num = markCount(unit);
				return (num >= 0) ? ((num > 3) ? 3 : num) : 0;
			}
			catch (Exception ex)
			{
				if ((object)_markCount == markCount)
				{
					_markCount = null;
					_marksChanged = true;
					Plugin.Log.LogError((object)("Team marks are off for this session: reading them failed. " + ex));
				}
				return 0;
			}
		}
	}
	internal sealed class HealthBarManager : MonoBehaviour
	{
		private const string BattleImprovementsGuid = "BattleImprove";

		private const int MaxBars = 32;

		private const float ScanInterval = 0.1f;

		private const float SightInterval = 0.15f;

		private const float ShowSeconds = 6f;

		private const float FadeSeconds = 0.75f;

		private const float AlphaPerSecond = 8f;

		private const float TrailDelayPerSecond = 0.6f;

		private const float HealthEpsilon = 0.0005f;

		private const float ScreenMargin = 0.03f;

		private const float BarHeightOnScreen = 0.05f;

		private const float MinDistanceScale = 0.65f;

		private const float StealMargin = 3f;

		private const float FontRetrySeconds = 1f;

		private const float ErrorLogSeconds = 10f;

		private const float MarkReadInterval = 0.5f;

		private const float MarkVisibleAlpha = 0.5f;

		private static HealthBarManager _instance;

		private readonly Dictionary<int, TrackedEnemy> _byId = new Dictionary<int, TrackedEnemy>();

		private readonly List<TrackedEnemy> _tracked = new List<TrackedEnemy>();

		private readonly Stack<HealthBarView> _freeViews = new Stack<HealthBarView>();

		private Canvas _canvas;

		private RectTransform _canvasRect;

		private int _viewsCreated;

		private int _scan;

		private float _nextScanTime;

		private TMP_FontAsset _font;

		private BarStyle _style;

		private Color _uiColor;

		private bool _languageDirty = true;

		private float _nextFontAttempt;

		private AsyncAssetLoading _languageSource;

		private float _nextErrorLog;

		private bool _overlapChecked;

		private int _shownFrame = int.MinValue;

		private bool _marksOn;

		private float _nextMarkRead;

		private Sprite _skull;

		public static void EnsureExists()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("Enemy Health Bars");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<HealthBarManager>();
			}
		}

		public static void Shutdown()
		{
			if (!((Object)(object)_instance == (Object)null))
			{
				HealthBarManager instance = _instance;
				_instance = null;
				Object.Destroy((Object)(object)((Component)instance).gameObject);
			}
		}

		private void Awake()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			GameObject val = new GameObject("Enemy Health Bars Canvas", new Type[2]
			{
				typeof(Canvas),
				typeof(CanvasScaler)
			});
			val.transform.SetParent(((Component)this).transform, false);
			_canvas = val.GetComponent<Canvas>();
			_canvas.renderMode = (RenderMode)0;
			_canvas.sortingOrder = -100;
			((Behaviour)_canvas).enabled = false;
			CanvasScaler component = val.GetComponent<CanvasScaler>();
			component.uiScaleMode = (ScaleMode)1;
			component.referenceResolution = new Vector2(1920f, 1080f);
			component.matchWidthOrHeight = 1f;
			_canvasRect = (RectTransform)val.transform;
		}

		private void OnDestroy()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			if ((Object)(object)_languageSource != (Object)null)
			{
				_languageSource.onLanguageChange = (OnLanguageChange)Delegate.Remove((Delegate?)(object)_languageSource.onLanguageChange, (Delegate?)new OnLanguageChange(OnLanguageChange));
			}
		}

		private void LateUpdate()
		{
			try
			{
				Tick();
				_shownFrame = (((Behaviour)_canvas).enabled ? Time.frameCount : int.MinValue);
			}
			catch (Exception ex)
			{
				((Behaviour)_canvas).enabled = false;
				_shownFrame = int.MinValue;
				if (Time.unscaledTime >= _nextErrorLog)
				{
					_nextErrorLog = Time.unscaledTime + 10f;
					Plugin.Log.LogError((object)("Health bar update failed: " + ex));
				}
			}
		}

		private void Tick()
		{
			WarnAboutOverlapOnce();
			FollowLanguageChanges();
			GameManager instance = StaticInstance<GameManager>.Instance;
			if (!TryGetLiveView(instance, out var player, out var camera))
			{
				((Behaviour)_canvas).enabled = false;
				return;
			}
			((Behaviour)_canvas).enabled = true;
			if (_languageDirty || (Object)(object)_font == (Object)null)
			{
				RefreshFont();
			}
			RefreshStyle();
			if (_style != null)
			{
				float time = Time.time;
				if (time >= _nextScanTime)
				{
					_nextScanTime = time + 0.1f;
					Scan(instance.aliveNpcs, time);
				}
				RefreshMarks(time);
				UpdateBars(instance, player, camera, time);
			}
		}

		private void RefreshMarks(float now)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			if (!HealthBarIntegration.HasMarkSource)
			{
				if (_marksOn)
				{
					_marksOn = false;
					for (int i = 0; i < _tracked.Count; i++)
					{
						_tracked[i].Marks = 0;
					}
				}
				return;
			}
			_marksOn = true;
			if (_style != null && _style.EnsureMarks())
			{
				foreach (HealthBarView freeView in _freeViews)
				{
					freeView.SetStyle(_style, _uiColor);
				}
				for (int j = 0; j < _tracked.Count; j++)
				{
					_tracked[j].View?.SetStyle(_style, _uiColor);
				}
			}
			try
			{
				SkullArt.Tick();
			}
			catch (Exception e)
			{
				SkullArt.GiveUp(e);
			}
			if ((Object)(object)SkullArt.Bone != (Object)(object)_skull)
			{
				_skull = SkullArt.Bone;
				foreach (HealthBarView freeView2 in _freeViews)
				{
					freeView2.SetSkull(SkullArt.Bone, SkullArt.Holes, SkullArt.Aspect);
				}
				for (int k = 0; k < _tracked.Count; k++)
				{
					_tracked[k].View?.SetSkull(SkullArt.Bone, SkullArt.Holes, SkullArt.Aspect);
				}
			}
			if (!HealthBarIntegration.TakeMarksChanged() && now < _nextMarkRead)
			{
				return;
			}
			_nextMarkRead = now + 0.5f;
			for (int l = 0; l < _tracked.Count; l++)
			{
				TrackedEnemy trackedEnemy = _tracked[l];
				if (trackedEnemy.View != null && (Object)(object)trackedEnemy.Npc != (Object)null)
				{
					trackedEnemy.Marks = HealthBarIntegration.ReadMarks(trackedEnemy.Npc);
				}
			}
		}

		internal static bool IsShowingMarks(object unit)
		{
			HealthBarManager instance = _instance;
			Object val = (Object)((unit is Object) ? unit : null);
			if ((Object)(object)instance == (Object)null || val == (Object)null)
			{
				return false;
			}
			int num = Time.frameCount - 1;
			if (instance._shownFrame >= num && instance._byId.TryGetValue(val.GetInstanceID(), out var value))
			{
				return value.MarkDrawnFrame == instance._shownFrame;
			}
			return false;
		}

		private static bool TryGetLiveView(GameManager game, out Unit player, out Camera camera)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Invalid comparison between Unknown and I4
			player = null;
			camera = null;
			if ((Object)(object)game == (Object)null || (int)game.gameState != 3 || game.awaitingStartLevel)
			{
				return false;
			}
			if (game.PlayerControllerLocked)
			{
				return false;
			}
			Player playerScript = game.PlayerScript;
			player = game.PlayerUnit;
			if ((Object)(object)playerScript == (Object)null || (Object)(object)player == (Object)null || (int)player.UnitState != 2)
			{
				return false;
			}
			if (playerScript.HudHiddenBy((LockStatePadlock)3) || playerScript.HudHiddenBy((LockStatePadlock)6))
			{
				return false;
			}
			camera = game.currentCamera;
			if ((Object)(object)camera != (Object)null && (Object)(object)camera == (Object)(object)playerScript.playerCamera)
			{
				return ((Behaviour)camera).isActiveAndEnabled;
			}
			return false;
		}

		private void RefreshStyle()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_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)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			BarStyle barStyle = BarStyle.Get(Plugin.Style.Value);
			if (barStyle == null)
			{
				return;
			}
			UIManager instance = StaticInstance<UIManager>.Instance;
			Color val = (((Object)(object)instance != (Object)null) ? instance.CurrentUiColor : SulfurConstants.COLOR_SulfurYellow);
			if (barStyle == _style && val == _uiColor)
			{
				return;
			}
			_style = barStyle;
			_uiColor = val;
			foreach (HealthBarView freeView in _freeViews)
			{
				freeView.SetStyle(barStyle, val);
			}
			for (int i = 0; i < _tracked.Count; i++)
			{
				_tracked[i].View?.SetStyle(barStyle, val);
			}
		}

		private void Scan(List<Npc> npcs, float now)
		{
			_scan++;
			for (int i = 0; i < npcs.Count; i++)
			{
				Npc val = npcs[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (!_byId.TryGetValue(((Object)val).GetInstanceID(), out var value))
				{
					if (!EnemyRules.CanEverShow(val))
					{
						continue;
					}
					value = new TrackedEnemy(val);
					_byId.Add(value.Id, value);
					_tracked.Add(value);
				}
				value.SeenInScan = _scan;
				float normalizedHealth = ((Unit)val).normalizedHealth;
				if (float.IsNaN(normalizedHealth))
				{
					continue;
				}
				if (normalizedHealth < value.LastHealth - 0.0005f)
				{
					value.LastHurtTime = now;
					if (Plugin.DebugLog.Value)
					{
						Plugin.Log.LogInfo((object)("[Bars] hurt " + ((Object)val).name + " " + value.LastHealth + " -> " + normalizedHealth));
					}
					value.LastHealth = normalizedHealth;
				}
				else if (normalizedHealth > value.LastHealth)
				{
					value.LastHealth = normalizedHealth;
				}
			}
			for (int num = _tracked.Count - 1; num >= 0; num--)
			{
				TrackedEnemy trackedEnemy = _tracked[num];
				if (trackedEnemy.SeenInScan != _scan || !((Object)(object)trackedEnemy.Npc != (Object)null))
				{
					ReleaseView(trackedEnemy);
					_byId.Remove(trackedEnemy.Id);
					_tracked[num] = _tracked[_tracked.Count - 1];
					_tracked.RemoveAt(_tracked.Count - 1);
				}
			}
		}

		private void UpdateBars(GameManager game, Unit player, Camera camera, float now)
		{
			//IL_0006: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: 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_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: 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_012a: 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_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)camera).transform.position;
			float num = Mathf.Clamp(Plugin.MaxDistance.Value, 5f, 100f);
			float num2 = Mathf.Clamp(Plugin.BarScale.Value, 0.5f, 2f);
			bool value = Plugin.ShowName.Value;
			bool flag = Plugin.Mode.Value == BarDisplayMode.Nearby;
			int mask = LayerMask.op_Implicit(game.enemyOcclusionLayers);
			TrackedEnemy trackedEnemy = null;
			Vector2 position2 = default(Vector2);
			for (int i = 0; i < _tracked.Count; i++)
			{
				TrackedEnemy trackedEnemy2 = _tracked[i];
				if ((Object)(object)trackedEnemy2.Npc == (Object)null)
				{
					ReleaseView(trackedEnemy2);
					continue;
				}
				float num3 = (flag ? 1f : DamagedAlpha(now - trackedEnemy2.LastHurtTime));
				if (num3 <= 0f)
				{
					ReleaseView(trackedEnemy2);
					continue;
				}
				Vector3 val = trackedEnemy2.Anchor();
				float num4 = (trackedEnemy2.Distance = Vector3.Distance(position, val));
				if (num4 > num || !EnemyRules.CanShowNow(trackedEnemy2, player))
				{
					ReleaseView(trackedEnemy2);
					continue;
				}
				Vector3 viewport = camera.WorldToViewportPoint(val);
				KeepBarOnScreen(ref viewport, camera, trackedEnemy2, num2);
				if (viewport.z <= 0f || !IsOnScreen(viewport))
				{
					ReleaseView(trackedEnemy2);
					continue;
				}
				if (now >= trackedEnemy2.NextSightCheck)
				{
					trackedEnemy2.NextSightCheck = now + 0.15f + (float)(trackedEnemy2.Id & 7) * 0.01f;
					trackedEnemy2.InSight = HasLineOfSight(position, trackedEnemy2, val, mask);
				}
				float num5 = (trackedEnemy2.InSight ? num3 : 0f);
				trackedEnemy2.Alpha = Mathf.MoveTowards(trackedEnemy2.Alpha, num5, 8f * Time.deltaTime);
				if (trackedEnemy2.Alpha <= 0f && num5 <= 0f)
				{
					ReleaseView(trackedEnemy2);
					continue;
				}
				if (trackedEnemy2.View == null && !TryAcquireView(trackedEnemy2))
				{
					if (trackedEnemy == null || num4 < trackedEnemy.Distance)
					{
						trackedEnemy = trackedEnemy2;
					}
					continue;
				}
				float normalizedHealth = ((Unit)trackedEnemy2.Npc).normalizedHealth;
				normalizedHealth = (float.IsNaN(normalizedHealth) ? 0f : Mathf.Clamp01(normalizedHealth));
				trackedEnemy2.Trail = Mathf.Max(normalizedHealth, Mathf.MoveTowards(trackedEnemy2.Trail, normalizedHealth, 0.6f * Time.deltaTime));
				float num6 = Mathf.Lerp(1f, 0.65f, num4 / num);
				float x = viewport.x;
				Rect rect = _canvasRect.rect;
				float num7 = x * ((Rect)(ref rect)).width;
				float y = viewport.y;
				rect = _canvasRect.rect;
				((Vector2)(ref position2))..ctor(num7, y * ((Rect)(ref rect)).height);
				string name = null;
				if (value)
				{
					if (trackedEnemy2.Name == null)
					{
						trackedEnemy2.Name = ((Unit)trackedEnemy2.Npc).unitSO.LocalizedIdentifier();
					}
					name = trackedEnemy2.Name;
				}
				if (trackedEnemy2.View.Show(position2, num2 * num6, trackedEnemy2.Alpha, normalizedHealth, trackedEnemy2.Trail, name, trackedEnemy2.Marks) && trackedEnemy2.Alpha >= 0.5f)
				{
					trackedEnemy2.MarkDrawnFrame = Time.frameCount;
				}
			}
			if (trackedEnemy != null && TryTakeFromFarther(trackedEnemy.Distance))
			{
				TryAcquireView(trackedEnemy);
			}
		}

		private static void KeepBarOnScreen(ref Vector3 viewport, Camera camera, TrackedEnemy enemy, float barScale)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			float num = 1f - 0.05f * barScale;
			if (!(viewport.z <= 0f) && !(viewport.y <= num) && (viewport.y <= 1.03f || IsInView(camera, ((Unit)enemy.Npc).GetPositionToAimAt()) || IsInView(camera, ((Component)enemy.Npc).transform.position)))
			{
				viewport.y = num;
			}
		}

		private static bool IsInView(Camera camera, Vector3 world)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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)
			Vector3 val = camera.WorldToViewportPoint(world);
			if (val.z > 0f)
			{
				return IsOnScreen(val);
			}
			return false;
		}

		private static float DamagedAlpha(float secondsSinceHurt)
		{
			if (secondsSinceHurt <= 6f)
			{
				return 1f;
			}
			return Mathf.Clamp01(1f - (secondsSinceHurt - 6f) / 0.75f);
		}

		private static bool IsOnScreen(Vector3 viewport)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (viewport.x >= -0.03f && viewport.x <= 1.03f && viewport.y >= -0.03f)
			{
				return viewport.y <= 1.03f;
			}
			return false;
		}

		private static bool HasLineOfSight(Vector3 eye, TrackedEnemy enemy, Vector3 anchor, int mask)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: 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_0010: 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)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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)
			Vector3 to = anchor - Vector3.up * 0.2f;
			if (!IsClear(eye, ((Unit)enemy.Npc).GetPositionToAimAt(), mask))
			{
				return IsClear(eye, to, mask);
			}
			return true;
		}

		private static bool IsClear(Vector3 from, Vector3 to, int mask)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = to - from;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude < 0.01f)
			{
				return true;
			}
			return !Physics.Raycast(from, val / magnitude, magnitude, mask, (QueryTriggerInteraction)1);
		}

		private bool TryAcquireView(TrackedEnemy enemy)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (_freeViews.Count == 0 && _viewsCreated < 32)
			{
				HealthBarView healthBarView = new HealthBarView((Transform)(object)_canvasRect);
				healthBarView.SetFont(_font);
				healthBarView.SetSkull(SkullArt.Bone, SkullArt.Holes, SkullArt.Aspect);
				healthBarView.SetStyle(_style, _uiColor);
				_viewsCreated++;
				_freeViews.Push(healthBarView);
			}
			if (_freeViews.Count == 0)
			{
				return false;
			}
			enemy.View = _freeViews.Pop();
			enemy.Marks = HealthBarIntegration.ReadMarks(enemy.Npc);
			if (Plugin.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)("[Bars] show " + ((Object)enemy.Npc).name + " at " + enemy.LastHealth));
			}
			return true;
		}

		private bool TryTakeFromFarther(float distance)
		{
			TrackedEnemy trackedEnemy = null;
			float num = distance + 3f;
			for (int i = 0; i < _tracked.Count; i++)
			{
				TrackedEnemy trackedEnemy2 = _tracked[i];
				if (trackedEnemy2.View != null && !(trackedEnemy2.Distance <= num))
				{
					trackedEnemy = trackedEnemy2;
					num = trackedEnemy2.Distance;
				}
			}
			if (trackedEnemy == null)
			{
				return false;
			}
			ReleaseView(trackedEnemy);
			return true;
		}

		private void ReleaseView(TrackedEnemy enemy)
		{
			enemy.Alpha = 0f;
			enemy.MarkDrawnFrame = int.MinValue;
			if (enemy.View != null)
			{
				enemy.View.Hide();
				_freeViews.Push(enemy.View);
				enemy.View = null;
			}
		}

		private void FollowLanguageChanges()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			if (!((Object)(object)_languageSource != (Object)null))
			{
				AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					_languageSource = instance;
					instance.onLanguageChange = (OnLanguageChange)Delegate.Combine((Delegate?)(object)instance.onLanguageChange, (Delegate?)new OnLanguageChange(OnLanguageChange));
				}
			}
		}

		private void OnLanguageChange()
		{
			_languageDirty = true;
		}

		private void RefreshFont()
		{
			if (Time.unscaledTime < _nextFontAttempt)
			{
				return;
			}
			_nextFontAttempt = Time.unscaledTime + 1f;
			AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			int num = 1;
			if ((Object)(object)instance.fontCache[num] == (Object)null)
			{
				FontLocalizer.LoadFontCache();
			}
			TMP_FontAsset val = instance.fontCache[num];
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			_font = val;
			_languageDirty = false;
			foreach (HealthBarView freeView in _freeViews)
			{
				freeView.SetFont(val);
			}
			for (int i = 0; i < _tracked.Count; i++)
			{
				_tracked[i].Name = null;
				_tracked[i].View?.SetFont(val);
			}
		}

		private void WarnAboutOverlapOnce()
		{
			if (!_overlapChecked)
			{
				_overlapChecked = true;
				if (Chainloader.PluginInfos.ContainsKey("BattleImprove"))
				{
					Plugin.Log.LogWarning((object)"Battle Improvements is installed and has its own enemy health bar. Turn off EnableHealthBar in its settings to avoid two bars on each enemy.");
				}
			}
		}
	}
	internal sealed class HealthBarView
	{
		private const float NameGap = 2f;

		private const float NameFontSize = 18f;

		private const float NameWidth = 360f;

		private static readonly Color NameShadowColor = new Color(0f, 0f, 0f, 0.9f);

		private static readonly Vector2 NameShadowOffset = new Vector2(1.5f, -1.5f);

		private readonly GameObject _root;

		private readonly RectTransform _rootRect;

		private readonly CanvasGroup _group;

		private readonly Image _back;

		private readonly Image _trail;

		private readonly Image _fill;

		private readonly Image _frame;

		private readonly GameObject _nameRoot;

		private readonly TextMeshProUGUI _name;

		private readonly TextMeshProUGUI _nameShadow;

		private const float MinMarkScale = 0.65f;

		private const float PopSeconds = 0.2f;

		private const float PopAmount = 0.3f;

		private readonly RectTransform _markRoot;

		private readonly Image _mark;

		private readonly Image _skullBone;

		private readonly Image _skullHoles;

		private readonly Image _eyes;

		private Sprite _skullBoneSprite;

		private Sprite _skullHolesSprite;

		private float _skullAspect = 1f;

		private int _shownMarks;

		private bool _marksFresh = true;

		private float _popStart = float.NegativeInfinity;

		private BarStyle _style;

		private string _shownName;

		public HealthBarView(Transform parent)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected O, but got Unknown
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: 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_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Expected O, but got Unknown
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Expected O, but got Unknown
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			_root = new GameObject("Enemy Health Bar", new Type[2]
			{
				typeof(RectTransform),
				typeof(CanvasGroup)
			});
			_root.transform.SetParent(parent, false);
			_rootRect = (RectTransform)_root.transform;
			_rootRect.anchorMin = Vector2.zero;
			_rootRect.anchorMax = Vector2.zero;
			_rootRect.pivot = new Vector2(0.5f, 0f);
			_group = _root.GetComponent<CanvasGroup>();
			_group.interactable = false;
			_group.blocksRaycasts = false;
			_back = CreateImage("Back", filled: false);
			_trail = CreateImage("Trail", filled: true);
			_fill = CreateImage("Fill", filled: true);
			_frame = CreateImage("Frame", filled: false);
			_eyes = CreateImage("Mark Eyes", filled: false);
			GameObject val = new GameObject("Mark", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent((Transform)(object)_rootRect, false);
			_markRoot = (RectTransform)val.transform;
			_markRoot.anchorMin = Vector2.zero;
			_markRoot.anchorMax = Vector2.zero;
			_markRoot.pivot = Vector2.zero;
			_markRoot.sizeDelta = Vector2.zero;
			_mark = CreateMarkImage("Mark Art");
			_skullBone = CreateMarkImage("Skull");
			_skullHoles = CreateMarkImage("Skull Holes");
			((Graphic)_skullBone).rectTransform.pivot = new Vector2(0.5f, 0.5f);
			((Graphic)_skullHoles).rectTransform.pivot = new Vector2(0.5f, 0.5f);
			val.SetActive(false);
			((Behaviour)_eyes).enabled = false;
			_nameRoot = new GameObject("Name", new Type[1] { typeof(RectTransform) });
			_nameRoot.transform.SetParent((Transform)(object)_rootRect, false);
			RectTransform val2 = (RectTransform)_nameRoot.transform;
			val2.anchorMin = new Vector2(0.5f, 1f);
			val2.anchorMax = new Vector2(0.5f, 1f);
			val2.pivot = new Vector2(0.5f, 0f);
			val2.anchoredPosition = new Vector2(0f, 2f);
			val2.sizeDelta = new Vector2(360f, 27f);
			_nameShadow = CreateText("Shadow", (Transform)(object)val2, NameShadowColor, NameShadowOffset);
			_name = CreateText("Text", (Transform)(object)val2, Color.white, Vector2.zero);
			Hide();
		}

		public void SetFont(TMP_FontAsset font)
		{
			if (!((Object)(object)font == (Object)null))
			{
				((TMP_Text)_name).font = font;
				((TMP_Text)_nameShadow).font = font;
			}
		}

		public void SetStyle(BarStyle style, Color uiColor)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			_style = style;
			_rootRect.sizeDelta = style.Size;
			_back.sprite = style.Back;
			_trail.sprite = style.Fill;
			_fill.sprite = style.Fill;
			_frame.sprite = style.Frame;
			((Graphic)_trail).color = style.TrailColor;
			((Graphic)_fill).color = style.FillColor;
			((Graphic)_frame).color = (style.FollowsUiColor ? uiColor : Color.white);
			((Graphic)_name).color = style.NameColor;
			LayOutMark(style);
		}

		public void SetSkull(Sprite bone, Sprite holes, float aspect)
		{
			_skullBoneSprite = bone;
			_skullHolesSprite = holes;
			_skullAspect = ((aspect > 0f) ? aspect : 1f);
			if (_style != null)
			{
				LayOutMark(_style);
			}
		}

		private void LayOutMark(BarStyle style)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: 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_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			_eyes.sprite = style.MarkEyes;
			if (style.Marks != null)
			{
				if (style.MarkBehind)
				{
					((Transform)_markRoot).SetAsFirstSibling();
				}
				else
				{
					((Transform)_markRoot).SetSiblingIndex(((Component)_frame).transform.GetSiblingIndex() + 1);
				}
				((Component)_eyes).transform.SetSiblingIndex(((Component)_frame).transform.GetSiblingIndex() + 1);
				_markRoot.anchoredPosition = style.MarkPivot;
				RectTransform rectTransform = ((Graphic)_mark).rectTransform;
				Rect markRect = style.MarkRect;
				Vector2 position = ((Rect)(ref markRect)).position - style.MarkPivot;
				markRect = style.MarkRect;
				Place(rectTransform, position, ((Rect)(ref markRect)).size);
				float skullHeight = style.SkullHeight;
				Vector2 size = default(Vector2);
				((Vector2)(ref size))..ctor(skullHeight * _skullAspect, skullHeight);
				Vector2 position2 = style.SkullCentre - style.MarkPivot;
				Quaternion localRotation = Quaternion.Euler(0f, 0f, style.SkullAngle);
				Place(((Graphic)_skullBone).rectTransform, position2, size);
				Place(((Graphic)_skullHoles).rectTransform, position2, size);
				((Transform)((Graphic)_skullBone).rectTransform).localRotation = localRotation;
				((Transform)((Graphic)_skullHoles).rectTransform).localRotation = localRotation;
				_skullBone.sprite = _skullBoneSprite;
				_skullHoles.sprite = _skullHolesSprite;
				((Graphic)_skullBone).color = style.SkullBoneColor;
				((Graphic)_skullHoles).color = style.SkullHoleColor;
				((Behaviour)_skullBone).enabled = skullHeight > 0f && (Object)(object)_skullBoneSprite != (Object)null;
				((Behaviour)_skullHoles).enabled = skullHeight > 0f && (Object)(object)_skullHolesSprite != (Object)null && style.SkullHoleColor.a > 0f;
			}
		}

		private static void Place(RectTransform rect, Vector2 position, Vector2 size)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			rect.anchoredPosition = position;
			rect.sizeDelta = size;
		}

		public bool Show(Vector2 position, float scale, float alpha, float health, float trail, string name, int marks)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (!_root.activeSelf)
			{
				_root.SetActive(true);
			}
			_rootRect.anchoredPosition = position;
			((Transform)_rootRect).localScale = new Vector3(scale, scale, 1f);
			_group.alpha = alpha;
			_fill.fillAmount = _style.FillAmount(health);
			_trail.fillAmount = _style.FillAmount(trail);
			bool flag = !string.IsNullOrEmpty(name);
			if (_nameRoot.activeSelf != flag)
			{
				_nameRoot.SetActive(flag);
			}
			if (flag && !string.Equals(_shownName, name, StringComparison.Ordinal))
			{
				_shownName = name;
				((TMP_Text)_name).text = name;
				((TMP_Text)_nameShadow).text = name;
			}
			return ShowMarks(marks, scale);
		}

		private bool ShowMarks(int marks, float scale)
		{
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			bool flag = marks > 0 && _style.Marks != null;
			if (((Component)_markRoot).gameObject.activeSelf != flag)
			{
				((Component)_markRoot).gameObject.SetActive(flag);
			}
			if (((Behaviour)_eyes).enabled != (flag && (Object)(object)_style.MarkEyes != (Object)null))
			{
				((Behaviour)_eyes).enabled = flag && (Object)(object)_style.MarkEyes != (Object)null;
			}
			if (_marksFresh)
			{
				_marksFresh = false;
			}
			else if (marks > _shownMarks)
			{
				_popStart = Time.unscaledTime;
			}
			_shownMarks = marks;
			if (!flag)
			{
				return false;
			}
			_mark.sprite = _style.Marks[Mathf.Min(marks, _style.Marks.Length) - 1];
			float num = Mathf.Max(1f, 0.65f / Mathf.Max(scale, 0.01f));
			float num2 = Time.unscaledTime - _popStart;
			if (num2 < 0.2f)
			{
				num *= 1f + 0.3f * (1f - num2 / 0.2f);
			}
			((Transform)_markRoot).localScale = new Vector3(num, num, 1f);
			return true;
		}

		public void Hide()
		{
			_marksFresh = true;
			_shownMarks = 0;
			_popStart = float.NegativeInfinity;
			if (_root.activeSelf)
			{
				_root.SetActive(false);
			}
		}

		private Image CreateMarkImage(string name)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent((Transform)(object)_markRoot, false);
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.zero;
			val2.pivot = Vector2.zero;
			Image component = val.GetComponent<Image>();
			((Graphic)component).raycastTarget = false;
			return component;
		}

		private Image CreateImage(string name, bool filled)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_0067: 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)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent((Transform)(object)_rootRect, false);
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.one;
			val2.offsetMin = Vector2.zero;
			val2.offsetMax = Vector2.zero;
			Image component = val.GetComponent<Image>();
			((Graphic)component).raycastTarget = false;
			if (filled)
			{
				component.type = (Type)3;
				component.fillMethod = (FillMethod)0;
				component.fillOrigin = 0;
			}
			return component;
		}

		private static TextMeshProUGUI CreateText(string name, Transform parent, Color color, Vector2 offset)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: 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)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.one;
			val2.offsetMin = offset;
			val2.offsetMax = offset;
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).alignment = (TextAlignmentOptions)1026;
			((TMP_Text)obj).fontSize = 18f;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)0;
			((Graphic)obj).raycastTarget = false;
			((Graphic)obj).color = color;
			return obj;
		}
	}
	[BepInPlugin("ryuka.sulfur.enemy_health_bars", "Enemy Health Bars", "0.2.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ryuka.sulfur.enemy_health_bars";

		public const string PluginName = "Enemy Health Bars";

		public const string PluginVersion = "0.2.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<BarDisplayMode> Mode;

		internal static ConfigEntry<BarStyleId> Style;

		internal static ConfigEntry<float> MaxDistance;

		internal static ConfigEntry<float> BarScale;

		internal static ConfigEntry<bool> ShowName;

		internal static ConfigEntry<bool> DebugLog;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: 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_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Mode = ((BaseUnityPlugin)this).Config.Bind<BarDisplayMode>("Display", "DisplayMode", BarDisplayMode.Damaged, "Damaged shows a bar for a few seconds after an enemy loses health. Nearby shows a bar on every enemy in range that you can see.");
			Style = ((BaseUnityPlugin)this).Config.Bind<BarStyleId>("Display", "BarStyle", BarStyleId.Sulfur, "Look of the bars. Sulfur matches the HUD health bar and follows its color. Rounds is a row of ten rings like the ammo gauge. Ichor is a bar of dripping ink. Bone has a skull at its left end. Classic is a plain bar.");
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "MaxDistance", 30f, new ConfigDescription("How far away a bar can still show, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), Array.Empty<object>()));
			BarScale = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BarScale", 1f, new ConfigDescription("Size of the bars and names.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>()));
			ShowName = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowName", true, "Show the enemy's name above its bar.");
			DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLog", false, "Write bar events to the log. Doesn't change what you see.");
			HealthBarManager.EnsureExists();
			_harmony = new Harmony("ryuka.sulfur.enemy_health_bars");
			try
			{
				_harmony.PatchAll(typeof(BossBarTracker));
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not follow the boss health bar; a boss that is not flagged as one may show two bars: " + ex));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Enemy Health Bars 0.2.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			BossBarTracker.Reset();
			HealthBarManager.Shutdown();
			BarStyle.Unload();
			SkullArt.Unload();
		}
	}
	public enum BarDisplayMode
	{
		Damaged = 1,
		Nearby
	}
	internal static class SkullArt
	{
		private enum State
		{
			NotStarted,
			Loading,
			TimedOut,
			Done
		}

		private const string SkullUnitName = "BlackGuildSkeleton";

		private const float LoadTimeoutSeconds = 20f;

		private static State _state;

		private static float _loadStartedAt;

		private static readonly List<Object> Owned = new List<Object>();

		private const int S = 2;

		private const float Tilt = 6f;

		private const float AlphaCut = 22f / 51f;

		public static Sprite Bone { get; private set; }

		public static Sprite Holes { get; private set; }

		public static float Aspect { get; private set; } = 1f;

		public static void Tick()
		{
			if (_state == State.Done || _state == State.TimedOut)
			{
				return;
			}
			if (_state == State.Loading)
			{
				if (Time.realtimeSinceStartup - _loadStartedAt > 20f)
				{
					_state = State.TimedOut;
					Plugin.Log.LogWarning((object)("The skull portrait did not load in " + 20f + " s; team marks show without the skull until it does."));
				}
				return;
			}
			AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
			UnitDatabase val = (((Object)(object)instance != (Object)null) ? instance.unitDatabase : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			UnitSO val2 = null;
			try
			{
				foreach (UnitSO raw in val.GetRawList())
				{
					if ((Object)(object)raw != (Object)null && string.Equals(((Object)raw).name, "BlackGuildSkeleton", StringComparison.Ordinal))
					{
						val2 = raw;
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Finish(null, "unit database: " + ex.Message);
				return;
			}
			if ((Object)(object)val2 == (Object)null)
			{
				Finish(null, "no unit named BlackGuildSkeleton");
				return;
			}
			_state = State.Loading;
			_loadStartedAt = Time.realtimeSinceStartup;
			try
			{
				val2.LoadAndSetArtwork((Action<Sprite>)OnPortrait);
			}
			catch (Exception ex2)
			{
				Finish(null, "LoadAndSetArtwork: " + ex2.Message);
			}
		}

		public static void Unload()
		{
			foreach (Object item in Owned)
			{
				if (item != (Object)null)
				{
					Object.Destroy(item);
				}
			}
			Owned.Clear();
			Bone = null;
			Holes = null;
			Aspect = 1f;
			_state = State.NotStarted;
		}

		public static void GiveUp(Exception e)
		{
			if (_state != State.Done)
			{
				_state = State.Done;
				Plugin.Log.LogWarning((object)("The skull portrait cannot be loaded (" + e.GetType().Name + ": " + e.Message + "); team marks show without the skull."));
			}
		}

		private static void OnPortrait(Sprite portrait)
		{
			if (_state == State.Loading || _state == State.TimedOut)
			{
				Finish(portrait, ((Object)(object)portrait == (Object)null) ? "the portrait loaded empty" : null);
			}
		}

		private static void Finish(Sprite portrait, string why)
		{
			if (_state == State.Done)
			{
				return;
			}
			_state = State.Done;
			if ((Object)(object)portrait != (Object)null)
			{
				try
				{
					CutSkull(portrait, out var bone, out var holes, out var w, out var h);
					Bone = MakeSprite(bone, w, h, "EnemyHealthBars.Skull.Bone");
					Holes = MakeSprite(holes, w, h, "EnemyHealthBars.Skull.Holes");
					Aspect = (float)w / (float)h;
					Plugin.Log.LogInfo((object)("Team mark skull cut from " + ((Object)portrait).name + ", " + w + "x" + h + "."));
					return;
				}
				catch (Exception ex)
				{
					why = ex.GetType().Name + ": " + ex.Message;
					Bone = null;
					Holes = null;
				}
			}
			Plugin.Log.LogWarning((object)("The skull portrait is unavailable (" + why + "); team marks show without the skull."));
		}

		private static Sprite MakeSprite(float[] coverage, int w, int h, string name)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			//IL_00bc: 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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			Color32[] array = (Color32[])(object)new Color32[w * h];
			for (int i = 0; i < h; i++)
			{
				for (int j = 0; j < w; j++)
				{
					byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(coverage[i * w + j] * 255f), 0, 255);
					array[(h - 1 - i) * w + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			Texture2D val = new Texture2D(w, h, (TextureFormat)4, true)
			{
				name = name,
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)2,
				hideFlags = (HideFlags)61
			};
			Owned.Add((Object)(object)val);
			val.SetPixels32(array);
			val.Apply(true, true);
			Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)w, (float)h), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
			((Object)val2).name = name;
			((Object)val2).hideFlags = (HideFlags)61;
			Owned.Add((Object)(object)val2);
			return val2;
		}

		private static void CutSkull(Sprite portrait, out float[] bone, out float[] holes, out int w, out int h)
		{
			//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_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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_0017: 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)
			if (portrait.packed && ((int)portrait.packingMode == 0 || (int)portrait.packingRotation != 0))
			{
				throw new InvalidOperationException("the portrait is tightly packed or rotated in an atlas");
			}
			int w2;
			int h2;
			float[] bottomUp = ReadAlpha(portrait, out w2, out h2);
			Rect rect = portrait.rect;
			float width = ((Rect)(ref rect)).width;
			rect = portrait.rect;
			float height = ((Rect)(ref rect)).height;
			Vector2 textureRectOffset = portrait.textureRectOffset;
			float num = width / 256f;
			float num2 = height / 256f;
			float num3 = height - (textureRectOffset.y + (float)h2);
			int W = 340;
			int num4 = 280;
			bool[] solid = new bool[W * num4];
			float num5 = Mathf.Cos((float)Math.PI / 30f);
			float num6 = Mathf.Sin((float)Math.PI / 30f);
			float num7 = (float)W / 2f;
			float num8 = (float)num4 / 2f;
			for (int i = 0; i < num4; i++)
			{
				for (int j = 0; j < W; j++)
				{
					float num9 = (float)j + 0.5f - num7;
					float num10 = (float)i + 0.5f - num8;
					float num11 = num9 * num5 + num10 * num6 + num7;
					float num12 = (0f - num9) * num6 + num10 * num5 + num8;
					float x = (30f + num11 / 2f) * num - textureRectOffset.x;
					float y = num12 / 2f * num2 - num3;
					solid[i * W + j] = SampleTopLeft(bottomUp, w2, h2, x, y) > 22f / 51f;
				}
			}
			int num13 = num4;
			bool flag = false;
			for (int k = 0; k < num4; k++)
			{
				int num14 = -1;
				int num15 = -1;
				for (int l = 0; l < W; l++)
				{
					if (solid[k * W + l])
					{
						if (num14 < 0)
						{
							num14 = l;
						}
						num15 = l;
					}
				}
				int num16 = ((num14 >= 0) ? (num15 - num14 + 1) : 0);
				if (num16 > 180)
				{
					flag = true;
				}
				if (flag && num16 < 110 && k > 140)
				{
					num13 = k;
					break;
				}
			}
			num4 = num13;
			bool[] outside = new bool[W * num4];
			Stack<int> stack = new Stack<int>();
			Action<int, int> action = delegate(int num30, int num29)
			{
				int num28 = num29 * W + num30;
				if (!outside[num28] && !solid[num28])
				{
					outside[num28] = true;
					stack.Push(num28);
				}
			};
			for (int num17 = 0; num17 < W; num17++)
			{
				action(num17, 0);
				action(num17, num4 - 1);
			}
			for (int num18 = 0; num18 < num4; num18++)
			{
				action(0, num18);
				action(W - 1, num18);
			}
			while (stack.Count > 0)
			{
				int num19 = stack.Pop();
				int num20 = num19 % W;
				int num21 = num19 / W;
				if (num20 > 0)
				{
					action(num20 - 1, num21);
				}
				if (num20 < W - 1)
				{
					action(num20 + 1, num21);
				}
				if (num21 > 0)
				{
					action(num20, num21 - 1);
				}
				if (num21 < num4 - 1)
				{
					action(num20, num21 + 1);
				}
			}
			Bounds(outside, W, num4, out var x2, out var y2, out var x3, out var y3);
			int num22 = Math.Min(x3 - x2 + 1, 332);
			int num23 = Math.Min(y3 - y2 + 1, 199);
			bool[] array = new bool[num22 * num23];
			for (int num24 = 0; num24 < num23; num24++)
			{
				for (int num25 = 0; num25 < num22; num25++)
				{
					array[num24 * num22 + num25] = !outside[(num24 + y2) * W + num25 + x2];
				}
			}
			bool[] array2 = MainPiece(array, num22, num23, num22 / 2, 20);
			Bounds(Invert(array2), num22, num23, out var x4, out var y4, out var x5, out var y5);
			w = x5 - x4 + 1;
			h = y5 - y4 + 1;
			bone = new float[w * h];
			holes = new float[w * h];
			for (int num26 = 0; num26 < h; num26++)
			{
				for (int num27 = 0; num27 < w; num27++)
				{
					if (array2[(num26 + y4) * num22 + num27 + x4])
					{
						if (solid[(num26 + y4 + y2) * W + num27 + x4 + x2])
						{
							bone[num26 * w + num27] = 1f;
						}
						else
						{
							holes[num26 * w + num27] = 1f;
						}
					}
				}
			}
		}

		private static float[] ReadAlpha(Sprite sprite, out int w, out int h)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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_0078: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			Texture2D texture = sprite.texture;
			Rect textureRect = sprite.textureRect;
			w = Mathf.RoundToInt(((Rect)(ref textureRect)).width);
			h = Mathf.RoundToInt(((Rect)(ref textureRect)).height);
			if (w <= 0 || h <= 0)
			{
				throw new InvalidOperationException("empty portrait");
			}
			RenderTexture temporary = RenderTexture.GetTemporary(((Texture)texture).width, ((Texture)texture).height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1);
			RenderTexture active = RenderTexture.active;
			Texture2D val = null;
			try
			{
				Graphics.Blit((Texture)(object)texture, temporary);
				RenderTexture.active = temporary;
				val = new Texture2D(w, h, (TextureFormat)4, false, true);
				val.ReadPixels(new Rect(((Rect)(ref textureRect)).x, ((Rect)(ref textureRect)).y, (float)w, (float)h), 0, 0, false);
				val.Apply(false);
				Color32[] pixels = val.GetPixels32();
				float[] array = new float[pixels.Length];
				for (int i = 0; i < pixels.Length; i++)
				{
					array[i] = (float)(int)pixels[i].a / 255f;
				}
				return array;
			}
			finally
			{
				RenderTexture.active = active;
				RenderTexture.ReleaseTemporary(temporary);
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private static float SampleTopLeft(float[] bottomUp, int w, int h, float x, float y)
		{
			float num = x - 0.5f;
			float num2 = (float)h - y - 0.5f;
			int num3 = Mathf.FloorToInt(num);
			int num4 = Mathf.FloorToInt(num2);
			float num5 = num - (float)num3;
			float num6 = num2 - (float)num4;
			return Mathf.Lerp(Mathf.Lerp(At(bottomUp, w, h, num3, num4), At(bottomUp, w, h, num3 + 1, num4), num5), Mathf.Lerp(At(bottomUp, w, h, num3, num4 + 1), At(bottomUp, w, h, num3 + 1, num4 + 1), num5), num6);
		}

		private static float At(float[] m, int w, int h, int x, int y)
		{
			if (x >= 0 && y >= 0 && x < w && y < h)
			{
				return m[y * w + x];
			}
			return 0f;
		}

		private static bool[] Invert(bool[] m)
		{
			bool[] array = new bool[m.Length];
			for (int i = 0; i < m.Length; i++)
			{
				array[i] = !m[i];
			}
			return array;
		}

		private static void Bounds(bool[] empty, int w, int h, out int x0, out int y0, out int x1, out int y1)
		{
			x0 = w;
			y0 = h;
			x1 = -1;
			y1 = -1;
			for (int i = 0; i < h; i++)
			{
				for (int j = 0; j < w; j++)
				{
					if (!empty[i * w + j])
					{
						if (j < x0)
						{
							x0 = j;
						}
						if (j > x1)
						{
							x1 = j;
						}
						if (i < y0)
						{
							y0 = i;
						}
						if (i > y1)
						{
							y1 = i;
						}
					}
				}
			}
			if (x1 < 0)
			{
				throw new InvalidOperationException("no skull in the portrait");
			}
		}

		private static bool[] MainPiece(bool[] m, int w, int h, int sx, int sy)
		{
			int[] array = new int[m.Length];
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			Stack<int> stack = new Stack<int>();
			for (int i = 0; i < m.Length; i++)
			{
				if (!m[i] || array[i] != 0)
				{
					continue;
				}
				int num4 = ++num3;
				int num5 = 0;
				array[i] = num4;
				stack.Push(i);
				while (stack.Count > 0)
				{
					int num6 = stack.Pop();
					int num7 = num6 % w;
					int num8 = num6 / w;
					num5++;
					if (num7 > 0)
					{
						Visit(m, array, stack, num6 - 1, num4);
					}
					if (num7 < w - 1)
					{
						Visit(m, array, stack, num6 + 1, num4);
					}
					if (num8 > 0)
					{
						Visit(m, array, stack, num6 - w, num4);
					}
					if (num8 < h - 1)
					{
						Visit(m, array, stack, num6 + w, num4);
					}
				}
				if (num5 > num2)
				{
					num2 = num5;
					num = num4;
				}
			}
			int num9 = ((sx >= 0 && sy >= 0 && sx < w && sy < h) ? array[sy * w + sx] : 0);
			int num10 = ((num9 != 0) ? num9 : num);
			bool[] array2 = new bool[m.Length];
			for (int j = 0; j < m.Length; j++)
			{
				array2[j] = array[j] == num10;
			}
			return array2;
		}

		private static void Visit(bool[] m, int[] label, Stack<int> stack, int k, int id)
		{
			if (m[k] && label[k] == 0)
			{
				label[k] = id;
				stack.Push(k);
			}
		}
	}
	internal sealed class TrackedEnemy
	{
		public readonly Npc Npc;

		public readonly int Id;

		public readonly HellShrewSnipa Sniper;

		public readonly bool IsGhost;

		public readonly CapsuleCollider Collider;

		public int SeenInScan;

		public float LastHealth;

		public float Distance;

		public float LastHurtTime = float.NegativeInfinity;

		public bool InSight;

		public float NextSightCheck;

		public float Alpha;

		public float Trail;

		public string Name;

		public HealthBarView View;

		public int Marks;

		public int MarkDrawnFrame = int.MinValue;

		public const float GapAboveHead = 0.2f;

		public TrackedEnemy(Npc npc)
		{
			Npc = npc;
			Id = ((Object)npc).GetInstanceID();
			Sniper = ((Component)npc).GetComponent<HellShrewSnipa>();
			IsGhost = (Object)(object)((Component)npc).GetComponent<GhostHelper>() != (Object)null;
			Collider = ((Component)npc).GetComponent<CapsuleCollider>();
			LastHealth = ((Unit)npc).normalizedHealth;
			Trail = Mathf.Clamp01(LastHealth);
		}

		public Vector3 Anchor()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: 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_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_006d: 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)
			if ((Object)(object)Npc.overhead != (Object)null)
			{
				return Npc.overhead.position + Vector3.up * 0.2f;
			}
			if ((Object)(object)Collider != (Object)null && ((Collider)Collider).enabled)
			{
				Bounds bounds = ((Collider)Collider).bounds;
				return ((Bounds)(ref bounds)).center + Vector3.up * (((Bounds)(ref bounds)).extents.y + 0.2f);
			}
			return ((Component)Npc).transform.position + Vector3.up * 2f;
		}
	}
}

EnemyHealthBars.SulfurTogether.dll

Decompiled 2 days ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using Microsoft.CodeAnalysis;
using SULFURTogether.Api;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("EnemyHealthBars.SulfurTogether")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+862dd6d3f1b315eceab9885e04f6340ca2c1a096")]
[assembly: AssemblyProduct("EnemyHealthBars.SulfurTogether")]
[assembly: AssemblyTitle("EnemyHealthBars.SulfurTogether")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 EnemyHealthBars.SulfurTogether
{
	[BepInPlugin("ryuka.sulfur.enemy_health_bars_sulfur_together", "Enemy Health Bars - SULFUR Together Adapter", "0.2.0")]
	[BepInDependency("ryuka.sulfur.enemy_health_bars", "0.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class SulfurTogetherAdapterPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ryuka.sulfur.enemy_health_bars_sulfur_together";

		public const string PluginName = "Enemy Health Bars - SULFUR Together Adapter";

		private const string SulfurTogetherGuid = "com.ryuka.sulfur.together";

		private const string NoMarks = "the bars show no team marks, and SULFUR Together keeps its own icons.";

		private IDisposable _bridge;

		private void Awake()
		{
			if (!Chainloader.PluginInfos.ContainsKey("com.ryuka.sulfur.together"))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SULFUR Together is not installed; team marks are off.");
				return;
			}
			int num;
			try
			{
				num = ReadApiVersion();
			}
			catch (Exception ex) when (IsMissingApi(ex))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("The installed SULFUR Together has no combat API; the bars show no team marks, and SULFUR Together keeps its own icons. (" + ex.GetType().Name + ": " + ex.Message + ")"));
				return;
			}
			if (num < 2)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("The installed SULFUR Together has combat API " + num + ", without team marks; the bars show no team marks, and SULFUR Together keeps its own icons."));
				return;
			}
			try
			{
				_bridge = Attach();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Showing SULFUR Together's team marks on the bars (combat API " + num + ")."));
			}
			catch (Exception ex2) when (IsMissingApi(ex2))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("The installed SULFUR Together reports combat API " + num + ", but its team marks are not what this version expects; the bars show no team marks, and SULFUR Together keeps its own icons. (" + ex2.GetType().Name + ": " + ex2.Message + ")"));
			}
		}

		private void OnDestroy()
		{
			if (_bridge != null)
			{
				_bridge.Dispose();
				_bridge = null;
			}
		}

		private static bool IsMissingApi(Exception e)
		{
			if (!(e is TypeLoadException) && !(e is MissingMethodException) && !(e is MissingFieldException) && !(e is TypeInitializationException))
			{
				return e is BadImageFormatException;
			}
			return true;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static int ReadApiVersion()
		{
			return TeamMarkBridge.ReadApiVersion();
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static IDisposable Attach()
		{
			return TeamMarkBridge.Create();
		}
	}
	internal sealed class TeamMarkBridge : IDisposable
	{
		public const int TeamMarksApiVersion = 2;

		private IDisposable _presenter;

		private bool _subscribed;

		public static TeamMarkBridge Create()
		{
			TeamMarkBridge teamMarkBridge = new TeamMarkBridge();
			try
			{
				teamMarkBridge.Attach();
				return teamMarkBridge;
			}
			catch
			{
				teamMarkBridge.Dispose();
				throw;
			}
		}

		public void Dispose()
		{
			HealthBarIntegration.SetMarkSource((Func<object, int>)null);
			if (_presenter != null)
			{
				_presenter.Dispose();
				_presenter = null;
			}
			if (_subscribed)
			{
				Unsubscribe();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static int ReadApiVersion()
		{
			return NetCombat.ApiVersion;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void Attach()
		{
			NetTeamMarks.MultiplierChanged += OnMarksChanged;
			_subscribed = true;
			HealthBarIntegration.SetMarkSource((Func<object, int>)CountMarks);
			_presenter = NetTeamMarks.RegisterMarkPresenter((Func<object, bool>)IsShowing);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void Unsubscribe()
		{
			NetTeamMarks.MultiplierChanged -= OnMarksChanged;
			_subscribed = false;
		}

		private void OnMarksChanged()
		{
			HealthBarIntegration.NotifyMarksChanged();
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private int CountMarks(object unit)
		{
			return NetTeamMarks.GetOtherMarkCount(unit);
		}

		private bool IsShowing(object unit)
		{
			return HealthBarIntegration.IsShowingMarks(unit);
		}
	}
}