Decompiled source of TeammateHealthHUD v1.1.0

plugins/TeammateHealthHUD.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TeammateHealthHUD.Config;
using TeammateHealthHUD.Core;
using TeammateHealthHUD.UI;
using TeammateHealthHUD.Utils;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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("TeammateHealthHUD")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("TeammateHealthHUD")]
[assembly: AssemblyTitle("TeammateHealthHUD")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 TeammateHealthHUD
{
	[BepInPlugin("com.juanma.teammatehealthhud", "TeammateHealthHUD", "1.1.0")]
	[BepInProcess("REPO.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.juanma.teammatehealthhud";

		public const string PluginName = "TeammateHealthHUD";

		public const string PluginVersion = "1.1.0";

		private ModConfig? _modConfig;

		private GameBindings? _bindings;

		private TeammateHudController? _hudController;

		private Harmony? _harmony;

		private static Plugin? _activeInstance;

		private void Awake()
		{
			//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_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Expected O, but got Unknown
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeammateHealthHUD] Loaded v1.1.0");
			Scene activeScene = SceneManager.GetActiveScene();
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Lifecycle] Awake: processId={1}, unity={2}, gameVersion={3}, frame={4}, activeScene={5}, buildIndex={6}, loadedScenes={7}", "TeammateHealthHUD", Process.GetCurrentProcess().Id, Application.unityVersion, Application.version, Time.frameCount, ((Scene)(ref activeScene)).name, ((Scene)(ref activeScene)).buildIndex, SceneManager.sceneCount));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[TeammateHealthHUD/Lifecycle] Config path: " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			_modConfig = new ModConfig(((BaseUnityPlugin)this).Config);
			_bindings = new GameBindings(((BaseUnityPlugin)this).Logger);
			if (_bindings.Resolve())
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeammateHealthHUD] Game bindings resolved");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"[TeammateHealthHUD] Some game bindings are unavailable; the HUD will show only data that can be read safely.");
			}
			_hudController = new TeammateHudController();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeammateHealthHUD/Lifecycle] Persistent managed HUD controller created; no scene-owned controller GameObject is used.");
			_hudController.Initialize(_modConfig, _bindings, ((BaseUnityPlugin)this).Logger);
			_activeInstance = this;
			_harmony = new Harmony("com.juanma.teammatehealthhud");
			MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "GameDirectorUpdatePostfix", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"[TeammateHealthHUD] Could not resolve the safe HUD update hook.");
			}
			else
			{
				int num = 0;
				num += (InstallHudUpdateHook(typeof(GameDirector), methodInfo) ? 1 : 0);
				num += (InstallHudUpdateHook(typeof(RunManager), methodInfo) ? 1 : 0);
				((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}] HUD update hooks installed: {1}/2", "TeammateHealthHUD", num));
			}
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.activeSceneChanged += OnActiveSceneChanged;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeammateHealthHUD] HUD initialized");
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Scene] sceneLoaded: name={1}, path={2}, buildIndex={3}, mode={4}, isLoaded={5}, loadedScenes={6}, frame={7}", "TeammateHealthHUD", ((Scene)(ref scene)).name, ((Scene)(ref scene)).path, ((Scene)(ref scene)).buildIndex, mode, ((Scene)(ref scene)).isLoaded, SceneManager.sceneCount, Time.frameCount));
			_hudController?.HandleSceneChanged();
		}

		private void OnActiveSceneChanged(Scene previous, Scene current)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Scene] activeSceneChanged: previous={1}[{2}], current={3}[{4}], currentLoaded={5}, loadedScenes={6}, frame={7}", "TeammateHealthHUD", ((Scene)(ref previous)).name, ((Scene)(ref previous)).buildIndex, ((Scene)(ref current)).name, ((Scene)(ref current)).buildIndex, ((Scene)(ref current)).isLoaded, SceneManager.sceneCount, Time.frameCount));
		}

		private static void GameDirectorUpdatePostfix()
		{
			_activeInstance?._hudController?.Tick(Time.unscaledDeltaTime);
		}

		private bool InstallHudUpdateHook(Type gameType, MethodInfo postfix)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(gameType, "Update", (Type[])null, (Type[])null);
			if (methodInfo == null || _harmony == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[TeammateHealthHUD] Update hook unavailable for " + gameType.Name + "."));
				return false;
			}
			_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			return true;
		}

		private void OnDestroy()
		{
			//IL_0028: 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)
			ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
			object[] obj = new object[4]
			{
				"TeammateHealthHUD",
				Time.frameCount,
				null,
				null
			};
			Scene activeScene = SceneManager.GetActiveScene();
			obj[2] = ((Scene)(ref activeScene)).name;
			obj[3] = _hudController != null;
			logger.LogInfo((object)string.Format("[{0}/Lifecycle] OnDestroy started: frame={1}, scene={2}, hudControllerPresent={3}", obj));
			SceneManager.sceneLoaded -= OnSceneLoaded;
			SceneManager.activeSceneChanged -= OnActiveSceneChanged;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)_activeInstance == (Object)(object)this)
			{
				_activeInstance = null;
			}
			_hudController?.Shutdown();
			_modConfig?.Dispose();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[TeammateHealthHUD/Lifecycle] OnDestroy complete.");
		}
	}
}
namespace TeammateHealthHUD.Utils
{
	internal static class ReflectionUtils
	{
		private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		public static FieldInfo? ResolveField(Type declaringType, string name, Type expectedType, ManualLogSource logger)
		{
			FieldInfo field = declaringType.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null)
			{
				logger.LogWarning((object)("[TeammateHealthHUD] Binding missing: " + declaringType.Name + "." + name));
				return null;
			}
			if (field.FieldType != expectedType)
			{
				logger.LogWarning((object)("[TeammateHealthHUD] Binding type changed: " + declaringType.Name + "." + name + " is " + field.FieldType.FullName + ", expected " + expectedType.FullName));
				return null;
			}
			return field;
		}

		public static bool TryRead<T>(FieldInfo? field, object target, out T value)
		{
			value = default(T);
			if (field == null)
			{
				return false;
			}
			try
			{
				if (field.GetValue(target) is T val)
				{
					value = val;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}
	}
	internal static class TextureUtils
	{
		public static Sprite CreateRoundedRectSprite(string name, int width, int height, int radius, int borderThickness = 0)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: 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_00b7: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(width, height, (TextureFormat)4, false)
			{
				name = name + "_Texture",
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1,
				hideFlags = (HideFlags)61
			};
			Color32[] array = (Color32[])(object)new Color32[width * height];
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					bool num = InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, radius, 0);
					bool flag = borderThickness > 0 && InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, Mathf.Max(0, radius - borderThickness), borderThickness);
					byte b = (byte)((num && !flag) ? byte.MaxValue : 0);
					array[i * width + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			val.SetPixels32(array);
			val.Apply(false, true);
			Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)width, (float)height), new Vector2(0.5f, 0.5f), 100f);
			((Object)obj).name = name;
			((Object)obj).hideFlags = (HideFlags)61;
			return obj;
		}

		private static bool InsideRoundedRect(float x, float y, int width, int height, int radius, int inset)
		{
			int num = width - inset;
			int num2 = height - inset;
			if (x < (float)inset || x > (float)num || y < (float)inset || y > (float)num2)
			{
				return false;
			}
			if (radius <= 0)
			{
				return true;
			}
			float num3 = Mathf.Clamp(x, (float)(inset + radius), (float)(num - radius));
			float num4 = Mathf.Clamp(y, (float)(inset + radius), (float)(num2 - radius));
			float num5 = x - num3;
			float num6 = y - num4;
			return num5 * num5 + num6 * num6 <= (float)(radius * radius);
		}
	}
}
namespace TeammateHealthHUD.UI
{
	public sealed class HudStyles
	{
		private readonly ModConfig _config;

		private readonly List<Sprite> _sprites = new List<Sprite>();

		private readonly bool _ownsFont;

		public Font Font { get; }

		public Sprite PanelSprite { get; }

		public Sprite CircleSprite { get; }

		public Sprite BarSprite { get; }

		public Sprite StampBorderSprite { get; }

		public Color PanelColor { get; private set; }

		public Color NameColor { get; private set; }

		public Color HealthyColor { get; private set; }

		public Color MediumColor { get; private set; }

		public Color LowColor { get; private set; }

		public Color DeadColor { get; private set; }

		public Color MutedTextColor { get; } = new Color(0.7f, 0.78f, 0.75f, 1f);

		public Color BarBackgroundColor { get; } = new Color(0.015f, 0.028f, 0.03f, 0.96f);

		public Color AvatarBackgroundColor { get; } = new Color(0.055f, 0.085f, 0.08f, 1f);

		public Color DividerColor { get; } = new Color(0.015f, 0.025f, 0.025f, 0.9f);

		public HudStyles(ModConfig config)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_005e: 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_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)
			_config = config;
			Font = Font.CreateDynamicFontFromOSFont(new string[4] { "Bahnschrift SemiCondensed", "Arial Narrow", "Segoe UI Semibold", "Arial" }, 18);
			if ((Object)(object)Font != (Object)null)
			{
				_ownsFont = true;
			}
			else
			{
				Font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			}
			PanelSprite = Add(TextureUtils.CreateRoundedRectSprite("TeammateHUD_Panel", 292, 64, 4));
			CircleSprite = Add(TextureUtils.CreateRoundedRectSprite("TeammateHUD_Avatar", 64, 64, 32));
			BarSprite = Add(TextureUtils.CreateRoundedRectSprite("TeammateHUD_Bar", 160, 10, 2));
			StampBorderSprite = Add(TextureUtils.CreateRoundedRectSprite("TeammateHUD_Stamp", 138, 28, 2, 2));
			Refresh();
		}

		public void Refresh()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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)
			//IL_007e: 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_00ad: 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_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: 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)
			HealthyColor = Parse(_config.HealthyColor.Value, new Color(0.3f, 1f, 0.36f));
			MediumColor = Parse(_config.MediumColor.Value, new Color(0.96f, 0.79f, 0.36f));
			LowColor = Parse(_config.LowColor.Value, new Color(1f, 0.35f, 0.37f));
			DeadColor = Parse(_config.DeadColor.Value, new Color(0.9f, 0.04f, 0.08f));
			NameColor = Parse(_config.NameColor.Value, new Color(0.94f, 1f, 0.95f, 1f));
			PanelColor = new Color(0.012f, 0.018f, 0.025f, Mathf.Clamp01(_config.PanelOpacity.Value));
		}

		public Color HealthColor(float fraction)
		{
			//IL_0011: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: 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)
			fraction = Mathf.Clamp01(fraction);
			if (fraction >= 0.7f)
			{
				return Color.Lerp(MediumColor, HealthyColor, (fraction - 0.7f) / 0.3f);
			}
			if (fraction >= 0.35f)
			{
				return Color.Lerp(LowColor, MediumColor, (fraction - 0.35f) / 0.35f);
			}
			return LowColor;
		}

		public void Dispose()
		{
			foreach (Sprite sprite in _sprites)
			{
				if (!((Object)(object)sprite == (Object)null))
				{
					Texture2D texture = sprite.texture;
					Object.Destroy((Object)(object)sprite);
					if ((Object)(object)texture != (Object)null)
					{
						Object.Destroy((Object)(object)texture);
					}
				}
			}
			_sprites.Clear();
			if (_ownsFont && (Object)(object)Font != (Object)null)
			{
				Object.Destroy((Object)(object)Font);
			}
		}

		private Sprite Add(Sprite sprite)
		{
			_sprites.Add(sprite);
			return sprite;
		}

		private static Color Parse(string value, Color fallback)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			Color result = default(Color);
			if (!ColorUtility.TryParseHtmlString(value, ref result))
			{
				return fallback;
			}
			return result;
		}
	}
	public sealed class PlayerHudEntry
	{
		private readonly ModConfig _config;

		private readonly HudStyles _styles;

		private readonly GameObject _root;

		private readonly Image _panel;

		private readonly Outline _panelOutline;

		private readonly Image _accentLine;

		private readonly GameObject _avatarRoot;

		private readonly Image _avatarMaskImage;

		private readonly RawImage _avatarImage;

		private readonly Text _initialText;

		private readonly Text _nameText;

		private readonly GameObject _barRoot;

		private readonly Image _barForeground;

		private readonly Shadow _barGlow;

		private readonly Text _healthText;

		private readonly Text _maxHealthText;

		private readonly Shadow _healthGlow;

		private readonly GameObject _deadStamp;

		private readonly Text _deadText;

		private readonly CanvasGroup _deadCanvasGroup;

		private float _displayedHealth;

		private bool _healthInitialized;

		private bool _wasDead;

		private float _deadAnimation = 1f;

		private string _cachedName = string.Empty;

		private int _cachedHealth = int.MinValue;

		private int _cachedMaxHealth = int.MinValue;

		private Texture? _cachedAvatar;

		public string Id { get; }

		public PlayerHudEntry(string id, Transform parent, ModConfig config, HudStyles styles)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Expected O, but got Unknown
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Expected O, but got Unknown
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_0343: Expected O, but got Unknown
			//IL_03ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0414: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0562: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0675: Unknown result type (might be due to invalid IL or missing references)
			//IL_069e: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0750: Unknown result type (might be due to invalid IL or missing references)
			//IL_076e: Expected O, but got Unknown
			//IL_0788: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0809: Expected O, but got Unknown
			Id = id;
			_config = config;
			_styles = styles;
			_root = CreateUiObject("Player_" + id, parent, typeof(CanvasRenderer), typeof(Image), typeof(LayoutElement), typeof(Outline));
			((RectTransform)_root.transform).sizeDelta = new Vector2(292f, 64f);
			LayoutElement component = _root.GetComponent<LayoutElement>();
			component.preferredWidth = 292f;
			component.preferredHeight = 64f;
			component.minWidth = 292f;
			component.minHeight = 64f;
			_panel = _root.GetComponent<Image>();
			_panel.sprite = styles.PanelSprite;
			((Graphic)_panel).raycastTarget = false;
			_panelOutline = _root.GetComponent<Outline>();
			((Shadow)_panelOutline).effectDistance = new Vector2(1f, -1f);
			((Shadow)_panelOutline).useGraphicAlpha = true;
			GameObject val = CreateUiObject("HealthAccent", _root.transform, typeof(CanvasRenderer), typeof(Image));
			SetTopLeft((RectTransform)val.transform, 0f, 5f, 3f, 54f);
			_accentLine = val.GetComponent<Image>();
			_accentLine.sprite = styles.BarSprite;
			((Graphic)_accentLine).raycastTarget = false;
			_avatarRoot = CreateUiObject("Avatar", _root.transform, typeof(CanvasRenderer), typeof(Image), typeof(Mask));
			SetTopLeft((RectTransform)_avatarRoot.transform, 12f, 10f, 44f, 44f);
			_avatarMaskImage = _avatarRoot.GetComponent<Image>();
			_avatarMaskImage.sprite = styles.CircleSprite;
			((Graphic)_avatarMaskImage).raycastTarget = false;
			_avatarRoot.GetComponent<Mask>().showMaskGraphic = true;
			GameObject val2 = CreateUiObject("Portrait", _avatarRoot.transform, typeof(CanvasRenderer), typeof(RawImage));
			Stretch((RectTransform)val2.transform);
			_avatarImage = val2.GetComponent<RawImage>();
			((Graphic)_avatarImage).raycastTarget = false;
			((Component)_avatarImage).gameObject.SetActive(false);
			GameObject val3 = CreateUiObject("Initial", _avatarRoot.transform, typeof(CanvasRenderer), typeof(Text));
			Stretch((RectTransform)val3.transform);
			_initialText = val3.GetComponent<Text>();
			ConfigureText(_initialText, 19, (FontStyle)1, (TextAnchor)4);
			GameObject val4 = CreateUiObject("Name", _root.transform, typeof(CanvasRenderer), typeof(Text), typeof(Shadow));
			_nameText = val4.GetComponent<Text>();
			ConfigureText(_nameText, 15, (FontStyle)1, (TextAnchor)3);
			_nameText.resizeTextForBestFit = true;
			_nameText.resizeTextMinSize = 10;
			_nameText.resizeTextMaxSize = 15;
			Shadow component2 = val4.GetComponent<Shadow>();
			component2.effectColor = new Color(0f, 0f, 0f, 0.9f);
			component2.effectDistance = new Vector2(1f, -1f);
			component2.useGraphicAlpha = true;
			GameObject val5 = CreateUiObject("HealthCurrent", _root.transform, typeof(CanvasRenderer), typeof(Text), typeof(Shadow));
			_healthText = val5.GetComponent<Text>();
			ConfigureText(_healthText, 20, (FontStyle)1, (TextAnchor)3);
			_healthGlow = val5.GetComponent<Shadow>();
			_healthGlow.effectDistance = new Vector2(1f, 0f);
			_healthGlow.useGraphicAlpha = true;
			GameObject val6 = CreateUiObject("HealthMaximum", _root.transform, typeof(CanvasRenderer), typeof(Text));
			_maxHealthText = val6.GetComponent<Text>();
			ConfigureText(_maxHealthText, 12, (FontStyle)1, (TextAnchor)3);
			_barRoot = CreateUiObject("HealthBar", _root.transform, typeof(CanvasRenderer), typeof(Image));
			Image component3 = _barRoot.GetComponent<Image>();
			component3.sprite = styles.BarSprite;
			((Graphic)component3).color = styles.BarBackgroundColor;
			((Graphic)component3).raycastTarget = false;
			GameObject val7 = CreateUiObject("Fill", _barRoot.transform, typeof(CanvasRenderer), typeof(Image), typeof(Shadow));
			_barForeground = val7.GetComponent<Image>();
			_barForeground.sprite = styles.BarSprite;
			((Graphic)_barForeground).raycastTarget = false;
			_barGlow = val7.GetComponent<Shadow>();
			_barGlow.effectDistance = new Vector2(0f, -1f);
			_barGlow.useGraphicAlpha = true;
			Stretch(((Graphic)_barForeground).rectTransform);
			for (int i = 1; i < 10; i++)
			{
				Image component4 = CreateUiObject("Segment_" + i, _barRoot.transform, typeof(CanvasRenderer), typeof(Image)).GetComponent<Image>();
				((Graphic)component4).color = styles.DividerColor;
				((Graphic)component4).raycastTarget = false;
				RectTransform rectTransform = ((Graphic)component4).rectTransform;
				float num = (float)i / 10f;
				rectTransform.anchorMin = new Vector2(num, 0f);
				rectTransform.anchorMax = new Vector2(num, 1f);
				rectTransform.pivot = new Vector2(0.5f, 0.5f);
				rectTransform.anchoredPosition = Vector2.zero;
				rectTransform.sizeDelta = new Vector2(1f, 0f);
			}
			_deadStamp = CreateUiObject("DeadStamp", _root.transform, typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup));
			SetTopLeft((RectTransform)_deadStamp.transform, 140f, 28f, 138f, 28f);
			_deadStamp.transform.localRotation = Quaternion.Euler(0f, 0f, -6f);
			Image component5 = _deadStamp.GetComponent<Image>();
			component5.sprite = styles.StampBorderSprite;
			((Graphic)component5).raycastTarget = false;
			_deadCanvasGroup = _deadStamp.GetComponent<CanvasGroup>();
			GameObject val8 = CreateUiObject("Label", _deadStamp.transform, typeof(CanvasRenderer), typeof(Text));
			Stretch((RectTransform)val8.transform);
			_deadText = val8.GetComponent<Text>();
			ConfigureText(_deadText, 16, (FontStyle)1, (TextAnchor)4);
			ApplyConfig();
		}

		public void ApplyConfig()
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Expected O, but got Unknown
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Expected O, but got Unknown
			float num = (_config.ShowAvatar.Value ? 68f : 14f);
			float width = 278f - num;
			_avatarRoot.SetActive(_config.ShowAvatar.Value);
			SetTopLeft(((Graphic)_nameText).rectTransform, num, 5f, width, 21f);
			SetTopLeft(((Graphic)_healthText).rectTransform, num, 26f, 72f, 28f);
			SetTopLeft(((Graphic)_maxHealthText).rectTransform, num + 68f, 34f, 54f, 18f);
			float num2 = (_config.ShowHealthNumbers.Value ? (num + 124f) : num);
			SetTopLeft((RectTransform)_barRoot.transform, num2, 42f, Mathf.Max(48f, 278f - num2), 8f);
			SetTopLeft((RectTransform)_deadStamp.transform, 140f, 28f, 138f, 28f);
			((Component)_healthText).gameObject.SetActive(_config.ShowHealthNumbers.Value);
			((Component)_maxHealthText).gameObject.SetActive(_config.ShowHealthNumbers.Value);
			_barRoot.SetActive(_config.ShowHealthBar.Value);
			_deadText.text = (string.IsNullOrWhiteSpace(_config.DeadText.Value) ? "DEAD" : _config.DeadText.Value.Trim().ToUpperInvariant());
		}

		public void Update(PlayerState state, float unscaledDeltaTime)
		{
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: 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_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_0266: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: 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_02b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_052c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0550: Unknown result type (might be due to invalid IL or missing references)
			//IL_0574: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0618: Unknown result type (might be due to invalid IL or missing references)
			//IL_062e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0644: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0404: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Unknown result type (might be due to invalid IL or missing references)
			//IL_0420: Unknown result type (might be due to invalid IL or missing references)
			//IL_042d: Unknown result type (might be due to invalid IL or missing references)
			//IL_043a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0441: Unknown result type (might be due to invalid IL or missing references)
			//IL_0448: Unknown result type (might be due to invalid IL or missing references)
			//IL_0454: Unknown result type (might be due to invalid IL or missing references)
			//IL_046a: Unknown result type (might be due to invalid IL or missing references)
			if (_cachedName != state.DisplayName)
			{
				_cachedName = state.DisplayName;
				_nameText.text = state.DisplayName.ToUpperInvariant();
				_initialText.text = InitialFor(state.DisplayName);
			}
			if ((Object)(object)_cachedAvatar != (Object)(object)state.Avatar)
			{
				_cachedAvatar = state.Avatar;
				_avatarImage.texture = state.Avatar;
				((Component)_avatarImage).gameObject.SetActive((Object)(object)state.Avatar != (Object)null);
				((Component)_initialText).gameObject.SetActive((Object)(object)state.Avatar == (Object)null);
			}
			if (!_healthInitialized)
			{
				_displayedHealth = state.CurrentHealth;
				_healthInitialized = true;
			}
			else if (_config.SmoothHealthAnimation.Value)
			{
				float num = Mathf.Max(0.1f, _config.HealthLerpSpeed.Value);
				float num2 = 1f - Mathf.Exp((0f - num) * unscaledDeltaTime);
				_displayedHealth = Mathf.Lerp(_displayedHealth, state.CurrentHealth, num2);
			}
			else
			{
				_displayedHealth = state.CurrentHealth;
			}
			bool isDead = state.IsDead;
			if (isDead && !_wasDead)
			{
				_deadAnimation = 0f;
			}
			_wasDead = isDead;
			((Graphic)_panel).color = (isDead ? Color.Lerp(_styles.PanelColor, Color.black, 0.35f) : _styles.PanelColor);
			((Shadow)_panelOutline).effectColor = (isDead ? new Color(_styles.DeadColor.r, _styles.DeadColor.g, _styles.DeadColor.b, 0.65f) : new Color(_styles.HealthyColor.r, _styles.HealthyColor.g, _styles.HealthyColor.b, 0.18f));
			((Graphic)_nameText).color = (isDead ? Color.Lerp(_styles.DeadColor, Color.gray, 0.35f) : _styles.NameColor);
			((Graphic)_initialText).color = (isDead ? Color.Lerp(_styles.DeadColor, Color.gray, 0.45f) : _styles.NameColor);
			((Graphic)_avatarMaskImage).color = (Color)(isDead ? new Color(0.035f, 0.045f, 0.045f, 1f) : _styles.AvatarBackgroundColor);
			((Graphic)_avatarImage).color = (Color)(isDead ? new Color(0.45f, 0.32f, 0.32f, 0.75f) : Color.white);
			bool flag = !isDead && state.HealthAvailable;
			_barRoot.SetActive(_config.ShowHealthBar.Value && flag);
			((Component)_healthText).gameObject.SetActive(_config.ShowHealthNumbers.Value && !isDead);
			((Component)_maxHealthText).gameObject.SetActive(_config.ShowHealthNumbers.Value && !isDead);
			_deadStamp.SetActive(isDead);
			if (state.HealthAvailable)
			{
				float num3 = Mathf.Clamp01((state.MaxHealth > 0f) ? (_displayedHealth / state.MaxHealth) : 0f);
				RectTransform rectTransform = ((Graphic)_barForeground).rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = new Vector2(num3, 1f);
				rectTransform.offsetMin = Vector2.zero;
				rectTransform.offsetMax = Vector2.zero;
				Color val = _styles.HealthColor(num3);
				((Graphic)_barForeground).color = val;
				_barGlow.effectColor = new Color(val.r, val.g, val.b, 0.38f);
				((Graphic)_accentLine).color = val;
				((Graphic)_healthText).color = val;
				_healthGlow.effectColor = new Color(val.r, val.g, val.b, 0.42f);
				((Graphic)_maxHealthText).color = _styles.MutedTextColor;
				int num4 = Mathf.RoundToInt(state.CurrentHealth);
				int num5 = Mathf.RoundToInt(state.MaxHealth);
				if (_cachedHealth != num4 || _cachedMaxHealth != num5)
				{
					_cachedHealth = num4;
					_cachedMaxHealth = num5;
					_healthText.text = $"+{num4}";
					_maxHealthText.text = $"/{num5}";
				}
			}
			else
			{
				_healthText.text = "+?";
				_maxHealthText.text = "/?";
				((Graphic)_healthText).color = new Color(0.65f, 0.7f, 0.68f, 1f);
				((Graphic)_maxHealthText).color = new Color(0.55f, 0.6f, 0.58f, 1f);
				((Graphic)_accentLine).color = new Color(0.45f, 0.52f, 0.49f, 0.8f);
			}
			if (isDead)
			{
				_deadAnimation = Mathf.Min(1f, _deadAnimation + unscaledDeltaTime * 7f);
				float num6 = 1f - Mathf.Pow(1f - _deadAnimation, 3f);
				_deadStamp.transform.localScale = Vector3.one * Mathf.Lerp(1.15f, 1f, num6);
				_deadCanvasGroup.alpha = Mathf.Lerp(0.15f, 1f, num6);
				((Graphic)_deadStamp.GetComponent<Image>()).color = _styles.DeadColor;
				((Graphic)_deadText).color = _styles.DeadColor;
				((Graphic)_accentLine).color = _styles.DeadColor;
			}
		}

		public void SetVisible(bool visible)
		{
			_root.SetActive(visible);
		}

		public void SetSiblingIndex(int index)
		{
			_root.transform.SetSiblingIndex(index);
		}

		public void Dispose()
		{
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
		}

		private void ConfigureText(Text text, int size, FontStyle style, TextAnchor alignment)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			text.font = _styles.Font;
			text.fontSize = size;
			text.fontStyle = style;
			text.alignment = alignment;
			text.horizontalOverflow = (HorizontalWrapMode)1;
			text.verticalOverflow = (VerticalWrapMode)0;
			text.supportRichText = false;
			((Graphic)text).raycastTarget = false;
			((Graphic)text).color = _styles.NameColor;
		}

		private static string InitialFor(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return "?";
			}
			return char.ToUpperInvariant(name.Trim()[0]).ToString();
		}

		private static GameObject CreateUiObject(string name, Transform parent, params Type[] components)
		{
			//IL_0022: 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)
			//IL_0035: Expected O, but got Unknown
			Type[] array = new Type[components.Length + 1];
			array[0] = typeof(RectTransform);
			components.CopyTo(array, 1);
			GameObject val = new GameObject(name, array);
			val.transform.SetParent(parent, false);
			return val;
		}

		private static void SetTopLeft(RectTransform rect, float x, float y, float width, float height)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: 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)
			rect.anchorMin = new Vector2(0f, 1f);
			rect.anchorMax = new Vector2(0f, 1f);
			rect.pivot = new Vector2(0f, 1f);
			rect.anchoredPosition = new Vector2(x, 0f - y);
			rect.sizeDelta = new Vector2(width, height);
		}

		private static void Stretch(RectTransform rect)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			rect.anchorMin = Vector2.zero;
			rect.anchorMax = Vector2.one;
			rect.offsetMin = Vector2.zero;
			rect.offsetMax = Vector2.zero;
		}
	}
	public sealed class TeammateHudController
	{
		private const float DiagnosticHeartbeatInterval = 2f;

		private readonly Dictionary<string, PlayerHudEntry> _entries = new Dictionary<string, PlayerHudEntry>(StringComparer.Ordinal);

		private readonly List<string> _removeBuffer = new List<string>();

		private readonly HashSet<string> _liveIds = new HashSet<string>(StringComparer.Ordinal);

		private readonly List<PlayerState> _orderedStates = new List<PlayerState>();

		private ModConfig? _config;

		private GameBindings? _bindings;

		private ManualLogSource? _logger;

		private PlayerTracker? _tracker;

		private HudStyles? _styles;

		private GameObject? _canvasObject;

		private RectTransform? _container;

		private VerticalLayoutGroup? _layout;

		private int _knownRosterVersion = -1;

		private bool _configurationDirty;

		private bool _userVisible = true;

		private bool _shuttingDown;

		private bool _updateLoopLogged;

		private bool _invalidStateLogged;

		private int _lastTickFrame = -1;

		private int _canvasRestoreCount;

		private float _diagnosticHeartbeatTimer;

		private bool? _lastCanvasActive;

		private int _lastCanvasRestoreAttemptFrame = -10000;

		private bool _gameplayActive;

		private bool? _lastGameplayActive;

		private string _gameplayPhase = "not evaluated";

		public void Initialize(ModConfig config, GameBindings bindings, ManualLogSource logger)
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			logger.LogInfo((object)$"[TeammateHealthHUD/Controller] Managed controller initialize started: scene={((Scene)(ref activeScene)).name}, frame={Time.frameCount}");
			_config = config;
			_bindings = bindings;
			_logger = logger;
			_tracker = new PlayerTracker(bindings, logger);
			_styles = new HudStyles(config);
			config.Changed += OnConfigurationChanged;
			_configurationDirty = true;
			logger.LogInfo((object)$"[TeammateHealthHUD] Config loaded: Enabled={config.Enabled.Value}, ShowSelf={config.ShowSelf.Value}, Anchor={config.Anchor.Value}, DebugLogging={config.DebugLogging.Value}");
			logger.LogInfo((object)$"[TeammateHealthHUD/Controller] Initialize complete: deferredCanvasUntilRun=True, stylesAlive={_styles != null}");
		}

		public void Tick(float unscaledDeltaTime)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_0119: Unknown result type (might be due to invalid IL or missing references)
			int frameCount = Time.frameCount;
			if (_lastTickFrame == frameCount)
			{
				return;
			}
			_lastTickFrame = frameCount;
			if (!_updateLoopLogged)
			{
				_updateLoopLogged = true;
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					object arg = frameCount;
					Scene activeScene = SceneManager.GetActiveScene();
					logger.LogInfo((object)$"[TeammateHealthHUD] HUD update loop active: frame={arg}, scene={((Scene)(ref activeScene)).name}, overlaySortingOrder=32700");
				}
			}
			if (_shuttingDown || _config == null || _bindings == null || _tracker == null)
			{
				if (!_invalidStateLogged)
				{
					_invalidStateLogged = true;
					ManualLogSource? logger2 = _logger;
					if (logger2 != null)
					{
						logger2.LogWarning((object)$"[TeammateHealthHUD] HUD cannot update: shuttingDown={_shuttingDown}, configMissing={_config == null}, bindingsMissing={_bindings == null}, trackerMissing={_tracker == null}");
					}
				}
				return;
			}
			if (_config.PollForExternalChanges(unscaledDeltaTime))
			{
				ManualLogSource? logger3 = _logger;
				if (logger3 != null)
				{
					logger3.LogInfo((object)"[TeammateHealthHUD] Configuration reloaded and applied without restarting the game.");
				}
			}
			KeyboardShortcut value = _config.ToggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_userVisible = !_userVisible;
				UpdateCanvasVisibility();
				ManualLogSource? logger4 = _logger;
				if (logger4 != null)
				{
					logger4.LogInfo((object)$"[TeammateHealthHUD] HUD visibility toggled: {_userVisible}");
				}
			}
			RefreshGameplayState();
			if (!_gameplayActive)
			{
				TickDiagnosticHeartbeat(unscaledDeltaTime);
			}
			else
			{
				if (!EnsureCanvas())
				{
					return;
				}
				if (_configurationDirty)
				{
					ApplyConfiguration();
				}
				_tracker.Tick(unscaledDeltaTime, _config.DebugLogging.Value);
				if (_knownRosterVersion != _tracker.RosterVersion)
				{
					SynchronizeEntries();
				}
				foreach (PlayerState orderedState in _orderedStates)
				{
					if (_entries.TryGetValue(orderedState.Id, out PlayerHudEntry value2))
					{
						bool flag = (_config.ShowSelf.Value || !orderedState.IsLocalPlayer) && (_config.ShowDeadPlayers.Value || !orderedState.IsDead);
						value2.SetVisible(flag);
						if (flag)
						{
							value2.Update(orderedState, unscaledDeltaTime);
						}
					}
				}
				TickDiagnosticHeartbeat(unscaledDeltaTime);
			}
		}

		public void HandleSceneChanged()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			ManualLogSource? logger = _logger;
			if (logger != null)
			{
				logger.LogInfo((object)$"[TeammateHealthHUD/Controller] Scene reset received: activeScene={((Scene)(ref activeScene)).name}, buildIndex={((Scene)(ref activeScene)).buildIndex}, canvasAliveBefore={(Object)(object)_canvasObject != (Object)null}, entriesBefore={_entries.Count}, frame={Time.frameCount}");
			}
			_tracker?.Reset();
			ClearEntries();
			_knownRosterVersion = -1;
			_diagnosticHeartbeatTimer = 0f;
			_lastGameplayActive = null;
		}

		public void Shutdown()
		{
			if (!_shuttingDown)
			{
				_shuttingDown = true;
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[TeammateHealthHUD/Controller] Shutdown started: canvasAlive={(Object)(object)_canvasObject != (Object)null}, entries={_entries.Count}, frame={Time.frameCount}");
				}
				if (_config != null)
				{
					_config.Changed -= OnConfigurationChanged;
				}
				ClearEntries();
				_styles?.Dispose();
				if ((Object)(object)_canvasObject != (Object)null)
				{
					Object.Destroy((Object)(object)_canvasObject);
				}
				_canvasObject = null;
				ManualLogSource? logger2 = _logger;
				if (logger2 != null)
				{
					logger2.LogInfo((object)"[TeammateHealthHUD/Controller] Shutdown complete.");
				}
			}
		}

		private void BuildCanvas()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			ManualLogSource? logger = _logger;
			Scene val;
			if (logger != null)
			{
				val = SceneManager.GetActiveScene();
				logger.LogInfo((object)$"[TeammateHealthHUD/Canvas] Build started: scene={((Scene)(ref val)).name}, managedControllerAlive=True, frame={Time.frameCount}");
			}
			_canvasObject = new GameObject("TeammateHealthHUD_Canvas", new Type[3]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler)
			});
			Object.DontDestroyOnLoad((Object)(object)_canvasObject);
			Canvas component = _canvasObject.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.overrideSorting = true;
			component.sortingOrder = 32700;
			CanvasScaler component2 = _canvasObject.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)1;
			component2.referenceResolution = new Vector2(1920f, 1080f);
			component2.screenMatchMode = (ScreenMatchMode)0;
			component2.matchWidthOrHeight = 0.5f;
			GameObject val2 = new GameObject("PlayerCards", new Type[3]
			{
				typeof(RectTransform),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter)
			});
			val2.transform.SetParent(_canvasObject.transform, false);
			_container = val2.GetComponent<RectTransform>();
			_container.sizeDelta = new Vector2(292f, 0f);
			_layout = val2.GetComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)_layout).childControlWidth = false;
			((HorizontalOrVerticalLayoutGroup)_layout).childControlHeight = false;
			((HorizontalOrVerticalLayoutGroup)_layout).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)_layout).childForceExpandHeight = false;
			ContentSizeFitter component3 = val2.GetComponent<ContentSizeFitter>();
			component3.horizontalFit = (FitMode)0;
			component3.verticalFit = (FitMode)2;
			ManualLogSource? logger2 = _logger;
			if (logger2 != null)
			{
				object[] obj = new object[8]
				{
					((Object)_canvasObject).GetInstanceID(),
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				Transform parent = _canvasObject.transform.parent;
				obj[1] = ((parent != null) ? ((Object)parent).name : null) ?? "<root>";
				val = _canvasObject.scene;
				obj[2] = ((Scene)(ref val)).name;
				obj[3] = component.renderMode;
				obj[4] = component.overrideSorting;
				obj[5] = component.sortingOrder;
				obj[6] = component2.referenceResolution;
				obj[7] = ((Object)val2).GetInstanceID();
				logger2.LogInfo((object)string.Format("[TeammateHealthHUD/Canvas] Build complete: canvasUnityId={0}, parent={1}, scene={2}, renderMode={3}, overrideSorting={4}, sortingOrder={5}, referenceResolution={6}, containerUnityId={7}", obj));
			}
		}

		private bool EnsureCanvas()
		{
			//IL_009c: 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_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_canvasObject != (Object)null && (Object)(object)_container != (Object)null && (Object)(object)_layout != (Object)null)
			{
				return true;
			}
			int frameCount = Time.frameCount;
			if (frameCount - _lastCanvasRestoreAttemptFrame < 120)
			{
				return false;
			}
			_lastCanvasRestoreAttemptFrame = frameCount;
			try
			{
				ManualLogSource? logger = _logger;
				Scene activeScene;
				if (logger != null)
				{
					object[] obj = new object[5]
					{
						(Object)(object)_canvasObject != (Object)null,
						(Object)(object)_container != (Object)null,
						(Object)(object)_layout != (Object)null,
						null,
						null
					};
					activeScene = SceneManager.GetActiveScene();
					obj[3] = ((Scene)(ref activeScene)).name;
					obj[4] = frameCount;
					logger.LogWarning((object)string.Format("[TeammateHealthHUD/Canvas] Missing UI detected: canvasAlive={0}, containerAlive={1}, layoutAlive={2}, scene={3}, frame={4}. Rebuilding from the managed controller now.", obj));
				}
				ClearEntries();
				_knownRosterVersion = -1;
				_canvasObject = null;
				_container = null;
				_layout = null;
				BuildCanvas();
				ApplyConfiguration();
				_canvasRestoreCount++;
				_invalidStateLogged = false;
				ManualLogSource? logger2 = _logger;
				if (logger2 != null)
				{
					object[] obj2 = new object[4] { _canvasRestoreCount, null, null, null };
					GameObject? canvasObject = _canvasObject;
					obj2[1] = ((canvasObject != null) ? new int?(((Object)canvasObject).GetInstanceID()) : ((int?)null));
					GameObject? canvasObject2 = _canvasObject;
					obj2[2] = ((canvasObject2 != null) ? new bool?(canvasObject2.activeSelf) : ((bool?)null));
					activeScene = SceneManager.GetActiveScene();
					obj2[3] = ((Scene)(ref activeScene)).name;
					logger2.LogInfo((object)string.Format("[TeammateHealthHUD/Canvas] HUD canvas restored after scene cleanup: restore={0}, canvasUnityId={1}, active={2}, scene={3}", obj2));
				}
				return (Object)(object)_canvasObject != (Object)null;
			}
			catch (Exception arg)
			{
				if (!_invalidStateLogged)
				{
					_invalidStateLogged = true;
					ManualLogSource? logger3 = _logger;
					if (logger3 != null)
					{
						logger3.LogError((object)$"[TeammateHealthHUD] Failed to restore the HUD canvas: {arg}");
					}
				}
				return false;
			}
		}

		private void SynchronizeEntries()
		{
			if (_tracker == null || (Object)(object)_container == (Object)null || _config == null || _styles == null)
			{
				return;
			}
			RectTransform container = _container;
			if (_config.DebugLogging.Value)
			{
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[TeammateHealthHUD/UI] Synchronizing cards: trackerStates={_tracker.States.Count}, existingCards={_entries.Count}, trackerRosterVersion={_tracker.RosterVersion}, knownRosterVersion={_knownRosterVersion}, containerAlive={(Object)(object)_container != (Object)null}");
				}
			}
			_liveIds.Clear();
			_orderedStates.Clear();
			foreach (KeyValuePair<string, PlayerState> state in _tracker.States)
			{
				_liveIds.Add(state.Key);
				_orderedStates.Add(state.Value);
				if (_entries.ContainsKey(state.Key))
				{
					continue;
				}
				try
				{
					_entries.Add(state.Key, new PlayerHudEntry(state.Key, (Transform)(object)container, _config, _styles));
					ManualLogSource? logger2 = _logger;
					if (logger2 != null)
					{
						logger2.LogInfo((object)$"[TeammateHealthHUD/UI] Card created: id={state.Key}, name={state.Value.DisplayName}, local={state.Value.IsLocalPlayer}, parentUnityId={((Object)((Component)container).gameObject).GetInstanceID()}");
					}
				}
				catch (Exception arg)
				{
					ManualLogSource? logger3 = _logger;
					if (logger3 != null)
					{
						logger3.LogError((object)$"[TeammateHealthHUD/UI] Card creation failed: id={state.Key}, name={state.Value.DisplayName}, exception={arg}");
					}
				}
			}
			_removeBuffer.Clear();
			foreach (string key in _entries.Keys)
			{
				if (!_liveIds.Contains(key))
				{
					_removeBuffer.Add(key);
				}
			}
			foreach (string item in _removeBuffer)
			{
				ManualLogSource? logger4 = _logger;
				if (logger4 != null)
				{
					logger4.LogInfo((object)("[TeammateHealthHUD/UI] Card removed: id=" + item));
				}
				_entries[item].Dispose();
				_entries.Remove(item);
			}
			_orderedStates.Sort((PlayerState left, PlayerState right) => left.JoinOrder.CompareTo(right.JoinOrder));
			for (int num = 0; num < _orderedStates.Count; num++)
			{
				if (_entries.TryGetValue(_orderedStates[num].Id, out PlayerHudEntry value))
				{
					value.SetSiblingIndex(num);
				}
			}
			_knownRosterVersion = _tracker.RosterVersion;
			ManualLogSource? logger5 = _logger;
			if (logger5 != null)
			{
				logger5.LogInfo((object)$"[TeammateHealthHUD/UI] Card synchronization complete: cards={_entries.Count}, orderedStates={_orderedStates.Count}, rosterVersion={_knownRosterVersion}");
			}
		}

		private void ApplyConfiguration()
		{
			//IL_00de: 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_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: 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_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: 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_0311: Unknown result type (might be due to invalid IL or missing references)
			if (_config == null || _styles == null || (Object)(object)_container == (Object)null || (Object)(object)_layout == (Object)null)
			{
				return;
			}
			_configurationDirty = false;
			_styles.Refresh();
			((HorizontalOrVerticalLayoutGroup)_layout).spacing = Mathf.Clamp(_config.Spacing.Value, 0f, 40f);
			string text = (_config.Anchor.Value ?? "TopLeft").Trim();
			bool flag = text.EndsWith("Right", StringComparison.OrdinalIgnoreCase);
			bool flag2 = text.StartsWith("Bottom", StringComparison.OrdinalIgnoreCase);
			bool flag3 = text.StartsWith("Middle", StringComparison.OrdinalIgnoreCase);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(flag ? 1f : 0f, flag2 ? 0f : (flag3 ? 0.5f : 1f));
			Vector2 pivot = val;
			_container.anchorMin = val;
			_container.anchorMax = val;
			_container.pivot = pivot;
			((Transform)_container).localScale = Vector3.one * Mathf.Clamp(_config.Scale.Value, 0.4f, 3f);
			float num = Mathf.Abs(_config.OffsetX.Value) * (flag ? (-1f) : 1f);
			float num2 = Mathf.Abs(_config.OffsetY.Value);
			float num3 = (flag2 ? num2 : (0f - num2));
			_container.anchoredPosition = new Vector2(num, num3);
			((LayoutGroup)_layout).childAlignment = (TextAnchor)((!flag) ? (flag2 ? 6 : (flag3 ? 3 : 0)) : (flag2 ? 8 : (flag3 ? 5 : 2)));
			foreach (PlayerHudEntry value in _entries.Values)
			{
				value.ApplyConfig();
			}
			UpdateCanvasVisibility();
			if (_config.DebugLogging.Value)
			{
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					object[] obj = new object[14]
					{
						_config.Enabled.Value,
						_config.ShowSelf.Value,
						_config.ShowDeadPlayers.Value,
						_config.ShowAvatar.Value,
						_config.ShowHealthBar.Value,
						_config.ShowHealthNumbers.Value,
						text,
						_config.OffsetX.Value,
						_config.OffsetY.Value,
						_container.anchoredPosition,
						((Transform)_container).localScale.x,
						((HorizontalOrVerticalLayoutGroup)_layout).spacing,
						_config.PanelOpacity.Value,
						null
					};
					GameObject? canvasObject = _canvasObject;
					obj[13] = ((canvasObject != null) ? new bool?(canvasObject.activeSelf) : ((bool?)null));
					logger.LogInfo((object)string.Format("[TeammateHealthHUD/Config] Applied: Enabled={0}, ShowSelf={1}, ShowDead={2}, ShowAvatar={3}, ShowBar={4}, ShowNumbers={5}, Anchor={6}, Offset=({7},{8}), anchoredPosition={9}, scale={10}, spacing={11}, panelOpacity={12}, canvasActive={13}", obj));
				}
			}
		}

		private void UpdateCanvasVisibility()
		{
			//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)
			if (!((Object)(object)_canvasObject != (Object)null) || _config == null)
			{
				return;
			}
			bool flag = _config.Enabled.Value && _userVisible && _gameplayActive;
			_canvasObject.SetActive(flag);
			if (_lastCanvasActive != flag)
			{
				_lastCanvasActive = flag;
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					object[] obj = new object[6]
					{
						flag,
						_config.Enabled.Value,
						_userVisible,
						_gameplayActive,
						_gameplayPhase,
						null
					};
					Scene activeScene = SceneManager.GetActiveScene();
					obj[5] = ((Scene)(ref activeScene)).name;
					logger.LogInfo((object)string.Format("[TeammateHealthHUD/Canvas] Visibility applied: active={0}, configEnabled={1}, userVisible={2}, gameplayActive={3}, phase={4}, scene={5}", obj));
				}
			}
		}

		private void OnConfigurationChanged()
		{
			_configurationDirty = true;
			ModConfig? config = _config;
			if (config != null && config.DebugLogging.Value)
			{
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[TeammateHealthHUD/Config] Change event received: frame={Time.frameCount}; configuration will be applied on the current HUD tick.");
				}
			}
		}

		private void ClearEntries()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if (_entries.Count > 0)
			{
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					object arg = _entries.Count;
					Scene activeScene = SceneManager.GetActiveScene();
					logger.LogInfo((object)$"[TeammateHealthHUD/UI] Clearing all cards: count={arg}, scene={((Scene)(ref activeScene)).name}");
				}
			}
			foreach (PlayerHudEntry value in _entries.Values)
			{
				value.Dispose();
			}
			_entries.Clear();
			_orderedStates.Clear();
		}

		private void LogDiagnosticHeartbeat()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			if (_config == null || _tracker == null)
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			bool flag = (Object)(object)_canvasObject != (Object)null;
			Canvas val = (flag ? _canvasObject.GetComponent<Canvas>() : null);
			RectTransform container = _container;
			int num = 0;
			foreach (PlayerState orderedState in _orderedStates)
			{
				if ((_config.ShowSelf.Value || !orderedState.IsLocalPlayer) && (_config.ShowDeadPlayers.Value || !orderedState.IsDead))
				{
					num++;
				}
			}
			ManualLogSource? logger = _logger;
			if (logger != null)
			{
				object[] obj = new object[25]
				{
					Time.frameCount,
					((Scene)(ref activeScene)).name,
					((Scene)(ref activeScene)).buildIndex,
					((Scene)(ref activeScene)).isLoaded,
					_gameplayActive,
					_gameplayPhase,
					Screen.width,
					Screen.height,
					flag,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				GameObject? canvasObject = _canvasObject;
				obj[9] = ((canvasObject != null) ? new bool?(canvasObject.activeSelf) : ((bool?)null));
				GameObject? canvasObject2 = _canvasObject;
				obj[10] = ((canvasObject2 != null) ? new bool?(canvasObject2.activeInHierarchy) : ((bool?)null));
				obj[11] = ((val != null) ? new int?(val.sortingOrder) : ((int?)null));
				obj[12] = (Object)(object)container != (Object)null;
				obj[13] = ((container != null) ? new Vector2?(container.anchoredPosition) : ((Vector2?)null));
				obj[14] = ((container != null) ? new Vector3?(((Transform)container).localScale) : ((Vector3?)null));
				obj[15] = _config.Enabled.Value;
				obj[16] = _userVisible;
				obj[17] = _config.ShowSelf.Value;
				obj[18] = _tracker.States.Count;
				obj[19] = _orderedStates.Count;
				obj[20] = num;
				obj[21] = _entries.Count;
				obj[22] = _tracker.RosterVersion;
				obj[23] = _knownRosterVersion;
				obj[24] = _canvasRestoreCount;
				logger.LogInfo((object)string.Format("[TeammateHealthHUD/Heartbeat] frame={0}, scene={1}, buildIndex={2}, sceneLoaded={3}, gameplayActive={4}, phase={5}, screen={6}x{7}, managedControllerAlive=True, canvasAlive={8}, canvasActiveSelf={9}, canvasActiveHierarchy={10}, sortingOrder={11}, containerAlive={12}, containerPosition={13}, containerScale={14}, configEnabled={15}, userVisible={16}, ShowSelf={17}, trackerStates={18}, orderedStates={19}, visibleStates={20}, cards={21}, trackerRosterVersion={22}, knownRosterVersion={23}, restores={24}", obj));
			}
		}

		private void RefreshGameplayState()
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if (_bindings == null || _tracker == null)
			{
				return;
			}
			string phase;
			bool flag = (_gameplayActive = _bindings.IsGameplayActive(out phase));
			_gameplayPhase = phase;
			if (_lastGameplayActive != flag)
			{
				_lastGameplayActive = flag;
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					object[] obj = new object[4]
					{
						flag,
						phase,
						Time.frameCount,
						null
					};
					Scene activeScene = SceneManager.GetActiveScene();
					obj[3] = ((Scene)(ref activeScene)).name;
					logger.LogInfo((object)string.Format("[TeammateHealthHUD/GameState] Gameplay visibility changed: active={0}, phase={1}, frame={2}, scene={3}", obj));
				}
				_tracker.Reset();
				ClearEntries();
				_knownRosterVersion = -1;
				UpdateCanvasVisibility();
			}
		}

		private void TickDiagnosticHeartbeat(float unscaledDeltaTime)
		{
			if (_config != null)
			{
				_diagnosticHeartbeatTimer -= unscaledDeltaTime;
				if (_config.DebugLogging.Value && !(_diagnosticHeartbeatTimer > 0f))
				{
					_diagnosticHeartbeatTimer = 2f;
					LogDiagnosticHeartbeat();
				}
			}
		}
	}
}
namespace TeammateHealthHUD.Core
{
	public sealed class GameBindings
	{
		private static readonly IReadOnlyList<PlayerAvatar> EmptyPlayers = Array.Empty<PlayerAvatar>();

		private readonly ManualLogSource _logger;

		private readonly List<PlayerAvatar> _singlePlayerFallback = new List<PlayerAvatar>(1);

		private FieldInfo? _healthField;

		private FieldInfo? _maxHealthField;

		private FieldInfo? _deadSetField;

		private FieldInfo? _isLocalField;

		private FieldInfo? _runStartedField;

		private string _lastReportedRosterSource = string.Empty;

		private int _lastReportedRosterCount = -1;

		private string _lastBindingException = string.Empty;

		public bool HealthAvailable
		{
			get
			{
				if (_healthField != null)
				{
					return _maxHealthField != null;
				}
				return false;
			}
		}

		public bool DeathStateAvailable => _deadSetField != null;

		public string LastRosterSource { get; private set; } = "not-scanned";

		public int LastRosterCount { get; private set; }

		public GameBindings(ManualLogSource logger)
		{
			_logger = logger;
		}

		public bool Resolve()
		{
			_healthField = ReflectionUtils.ResolveField(typeof(PlayerHealth), "health", typeof(int), _logger);
			_maxHealthField = ReflectionUtils.ResolveField(typeof(PlayerHealth), "maxHealth", typeof(int), _logger);
			_deadSetField = ReflectionUtils.ResolveField(typeof(PlayerAvatar), "deadSet", typeof(bool), _logger);
			_isLocalField = ReflectionUtils.ResolveField(typeof(PlayerAvatar), "isLocal", typeof(bool), _logger);
			_runStartedField = ReflectionUtils.ResolveField(typeof(RunManager), "runStarted", typeof(bool), _logger);
			if (HealthAvailable && DeathStateAvailable && _isLocalField != null)
			{
				return _runStartedField != null;
			}
			return false;
		}

		public bool IsGameplayActive(out string phase)
		{
			phase = "unavailable";
			try
			{
				RunManager instance = RunManager.instance;
				if ((Object)(object)instance == (Object)null)
				{
					phase = "RunManager missing";
					return false;
				}
				if (SemiFunc.IsMainMenu())
				{
					phase = "main menu";
					return false;
				}
				if (!(ReflectionUtils.TryRead<bool>(_runStartedField, instance, out var value) && value))
				{
					phase = "run not started";
					return false;
				}
				if (SemiFunc.RunIsLevel())
				{
					phase = "mission level";
				}
				else if (SemiFunc.RunIsLobby())
				{
					phase = "run lobby";
				}
				else if (SemiFunc.RunIsShop())
				{
					phase = "shop";
				}
				else if (SemiFunc.RunIsTutorial())
				{
					phase = "tutorial";
				}
				else if (SemiFunc.RunIsArena())
				{
					phase = "arena";
				}
				else
				{
					phase = "active run";
				}
				return true;
			}
			catch (Exception ex)
			{
				phase = "state error: " + ex.GetType().Name;
				return false;
			}
		}

		public IReadOnlyList<PlayerAvatar> GetPlayers(bool debugLogging)
		{
			try
			{
				List<PlayerAvatar> list = SemiFunc.PlayerGetList();
				if (list != null && list.Count > 0)
				{
					ReportRosterSource("SemiFunc.PlayerGetList", list.Count, debugLogging);
					return list;
				}
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				if ((Object)(object)val != (Object)null)
				{
					_singlePlayerFallback.Clear();
					_singlePlayerFallback.Add(val);
					ReportRosterSource("SemiFunc.PlayerAvatarLocal fallback", 1, debugLogging);
					return _singlePlayerFallback;
				}
				ReportRosterSource("no game roster/local avatar", 0, debugLogging);
				return EmptyPlayers;
			}
			catch (Exception ex)
			{
				ReportRosterSource("roster exception", 0, debugLogging);
				string text = ex.GetType().Name + ": " + ex.Message;
				if (_lastBindingException != text)
				{
					_lastBindingException = text;
					_logger.LogWarning((object)("[TeammateHealthHUD/Bindings] Player roster read failed: " + text));
				}
				return EmptyPlayers;
			}
		}

		private void ReportRosterSource(string source, int count, bool debugLogging)
		{
			LastRosterSource = source;
			LastRosterCount = count;
			if (debugLogging && (!(_lastReportedRosterSource == source) || _lastReportedRosterCount != count))
			{
				_lastReportedRosterSource = source;
				_lastReportedRosterCount = count;
				_logger.LogInfo((object)$"[TeammateHealthHUD/Bindings] Roster source changed: source={source}, count={count}");
			}
		}

		public string GetStableId(PlayerAvatar player)
		{
			try
			{
				if ((Object)(object)player.photonView != (Object)null && player.photonView.ViewID > 0)
				{
					return "photon:" + player.photonView.ViewID;
				}
			}
			catch
			{
			}
			return "unity:" + ((Object)player).GetInstanceID();
		}

		public string GetDisplayName(PlayerAvatar player)
		{
			try
			{
				string text = SemiFunc.PlayerGetName(player);
				return string.IsNullOrWhiteSpace(text) ? "Player" : text.Trim();
			}
			catch
			{
				return "Player";
			}
		}

		public bool TrySample(PlayerAvatar player, out PlayerSample sample, out string failure)
		{
			sample = default(PlayerSample);
			failure = string.Empty;
			if ((Object)(object)player == (Object)null)
			{
				failure = "PlayerAvatar is null or destroyed";
				return false;
			}
			try
			{
				bool value;
				bool isLocal = ReflectionUtils.TryRead<bool>(_isLocalField, player, out value) && value;
				bool value2;
				bool flag = ReflectionUtils.TryRead<bool>(_deadSetField, player, out value2) && value2;
				int value3 = 0;
				int value4 = 0;
				bool flag2 = (Object)(object)player.playerHealth != (Object)null && ReflectionUtils.TryRead<int>(_healthField, player.playerHealth, out value3) && ReflectionUtils.TryRead<int>(_maxHealthField, player.playerHealth, out value4) && value4 > 0;
				sample = new PlayerSample(GetDisplayName(player), value3, value4, flag2, flag || (flag2 && value3 <= 0), isLocal, null);
				return true;
			}
			catch (MissingReferenceException)
			{
				failure = "MissingReferenceException while reading PlayerAvatar";
				return false;
			}
			catch (NullReferenceException)
			{
				failure = "NullReferenceException while reading PlayerAvatar";
				return false;
			}
			catch (InvalidCastException)
			{
				failure = "InvalidCastException while reading reflected player state";
				return false;
			}
			catch (Exception ex4)
			{
				failure = ex4.GetType().Name + ": " + ex4.Message;
				return false;
			}
		}
	}
	public readonly struct PlayerSample
	{
		public string DisplayName { get; }

		public int CurrentHealth { get; }

		public int MaxHealth { get; }

		public bool HealthAvailable { get; }

		public bool IsDead { get; }

		public bool IsLocal { get; }

		public Texture? Avatar { get; }

		public PlayerSample(string displayName, int currentHealth, int maxHealth, bool healthAvailable, bool isDead, bool isLocal, Texture? avatar)
		{
			DisplayName = displayName;
			CurrentHealth = currentHealth;
			MaxHealth = maxHealth;
			HealthAvailable = healthAvailable;
			IsDead = isDead;
			IsLocal = isLocal;
			Avatar = avatar;
		}
	}
	public sealed class PlayerState
	{
		public string Id { get; }

		public long JoinOrder { get; }

		public string DisplayName { get; internal set; } = "Player";

		public float CurrentHealth { get; internal set; }

		public float MaxHealth { get; internal set; }

		public bool HealthAvailable { get; internal set; }

		public bool IsDead { get; internal set; }

		public bool IsLocalPlayer { get; internal set; }

		public Texture? Avatar { get; internal set; }

		public PlayerAvatar? GamePlayerReference { get; internal set; }

		internal PlayerState(string id, long joinOrder, PlayerAvatar player)
		{
			Id = id;
			JoinOrder = joinOrder;
			GamePlayerReference = player;
		}
	}
	public sealed class PlayerTracker
	{
		private const float RosterInterval = 0.75f;

		private const float StateInterval = 0.1f;

		private const float DebugInterval = 2f;

		private readonly GameBindings _bindings;

		private readonly ManualLogSource _logger;

		private readonly Dictionary<string, PlayerState> _states = new Dictionary<string, PlayerState>(StringComparer.Ordinal);

		private readonly HashSet<string> _seenIds = new HashSet<string>(StringComparer.Ordinal);

		private readonly HashSet<string> _sampleFailuresLogged = new HashSet<string>(StringComparer.Ordinal);

		private readonly List<string> _removeBuffer = new List<string>();

		private float _rosterTimer;

		private float _stateTimer;

		private float _debugTimer;

		private long _nextJoinOrder;

		public IReadOnlyDictionary<string, PlayerState> States => _states;

		public int RosterVersion { get; private set; }

		public PlayerTracker(GameBindings bindings, ManualLogSource logger)
		{
			_bindings = bindings;
			_logger = logger;
		}

		public void Tick(float unscaledDeltaTime, bool debugLogging)
		{
			_rosterTimer -= unscaledDeltaTime;
			_stateTimer -= unscaledDeltaTime;
			_debugTimer -= unscaledDeltaTime;
			if (_rosterTimer <= 0f)
			{
				_rosterTimer = 0.75f;
				RefreshRoster(debugLogging);
			}
			if (_stateTimer <= 0f)
			{
				_stateTimer = 0.1f;
				RefreshStates(debugLogging);
			}
			if (debugLogging && _debugTimer <= 0f)
			{
				_debugTimer = 2f;
				LogSnapshot();
			}
		}

		public void Reset()
		{
			_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Reset requested: cachedPlayers={_states.Count}, rosterVersion={RosterVersion}");
			if (_states.Count > 0)
			{
				_states.Clear();
				RosterVersion++;
			}
			_rosterTimer = 0f;
			_stateTimer = 0f;
			_debugTimer = 0f;
			_nextJoinOrder = 0L;
			_sampleFailuresLogged.Clear();
		}

		private void RefreshRoster(bool debugLogging)
		{
			_seenIds.Clear();
			IReadOnlyList<PlayerAvatar> players = _bindings.GetPlayers(debugLogging);
			if (debugLogging)
			{
				_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Roster scan: source={_bindings.LastRosterSource}, returned={players.Count}, cachedBefore={_states.Count}, rosterVersion={RosterVersion}");
			}
			for (int i = 0; i < players.Count; i++)
			{
				PlayerAvatar val;
				try
				{
					val = players[i];
				}
				catch (ArgumentOutOfRangeException)
				{
					break;
				}
				if (!((Object)(object)val == (Object)null))
				{
					string stableId = _bindings.GetStableId(val);
					_seenIds.Add(stableId);
					if (!_states.TryGetValue(stableId, out PlayerState value))
					{
						value = new PlayerState(stableId, _nextJoinOrder++, val);
						_states.Add(stableId, value);
						RosterVersion++;
						SampleState(value, debugLogging);
						_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Player added: name={value.DisplayName}, id={value.Id}, unityId={((Object)val).GetInstanceID()}, local={value.IsLocalPlayer}, healthAvailable={value.HealthAvailable}, health={value.CurrentHealth}, max={value.MaxHealth}, dead={value.IsDead}, joinOrder={value.JoinOrder}");
					}
					else
					{
						value.GamePlayerReference = val;
					}
				}
			}
			_removeBuffer.Clear();
			foreach (KeyValuePair<string, PlayerState> state in _states)
			{
				if (!_seenIds.Contains(state.Key) || (Object)(object)state.Value.GamePlayerReference == (Object)null)
				{
					_removeBuffer.Add(state.Key);
				}
			}
			foreach (string item in _removeBuffer)
			{
				PlayerState playerState = _states[item];
				_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Player removed: name={playerState.DisplayName}, id={item}, unityObjectAlive={(Object)(object)playerState.GamePlayerReference != (Object)null}");
				_states.Remove(item);
				_sampleFailuresLogged.Remove(item);
				RosterVersion++;
			}
			if (debugLogging)
			{
				_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Roster scan complete: cachedAfter={_states.Count}, rosterVersion={RosterVersion}");
			}
		}

		private void RefreshStates(bool debugLogging)
		{
			foreach (PlayerState value in _states.Values)
			{
				SampleState(value, debugLogging);
			}
		}

		private void SampleState(PlayerState state, bool debugLogging)
		{
			PlayerAvatar gamePlayerReference = state.GamePlayerReference;
			if ((Object)(object)gamePlayerReference == (Object)null)
			{
				if (_sampleFailuresLogged.Add(state.Id))
				{
					_logger.LogWarning((object)("[TeammateHealthHUD/Tracker] Sample failed for " + state.Id + ": PlayerAvatar reference is null or destroyed"));
				}
				return;
			}
			if (!_bindings.TrySample(gamePlayerReference, out PlayerSample sample, out string failure))
			{
				if (_sampleFailuresLogged.Add(state.Id))
				{
					_logger.LogWarning((object)("[TeammateHealthHUD/Tracker] Sample failed for " + state.Id + ": " + failure));
				}
				return;
			}
			_sampleFailuresLogged.Remove(state.Id);
			string displayName = state.DisplayName;
			float currentHealth = state.CurrentHealth;
			float maxHealth = state.MaxHealth;
			bool healthAvailable = state.HealthAvailable;
			bool isDead = state.IsDead;
			bool isLocalPlayer = state.IsLocalPlayer;
			state.DisplayName = sample.DisplayName;
			state.CurrentHealth = sample.CurrentHealth;
			state.MaxHealth = sample.MaxHealth;
			state.HealthAvailable = sample.HealthAvailable;
			state.IsDead = sample.IsDead;
			state.IsLocalPlayer = sample.IsLocal;
			state.Avatar = sample.Avatar;
			if (debugLogging && (displayName != state.DisplayName || currentHealth != state.CurrentHealth || maxHealth != state.MaxHealth || healthAvailable != state.HealthAvailable || isDead != state.IsDead || isLocalPlayer != state.IsLocalPlayer))
			{
				_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] State changed: id={state.Id}, name={displayName}->{state.DisplayName}, local={isLocalPlayer}->{state.IsLocalPlayer}, hp={currentHealth}/{maxHealth}->{state.CurrentHealth}/{state.MaxHealth}, healthAvailable={healthAvailable}->{state.HealthAvailable}, dead={isDead}->{state.IsDead}");
			}
		}

		private void LogSnapshot()
		{
			if (_states.Count == 0)
			{
				_logger.LogInfo((object)$"[TeammateHealthHUD/Tracker] Player snapshot: no players detected; source={_bindings.LastRosterSource}, returned={_bindings.LastRosterCount}, rosterVersion={RosterVersion}");
				return;
			}
			IEnumerable<string> values = from state in _states.Values
				orderby state.JoinOrder
				select string.Format("{0} [{1}] local={2} hp={3} dead={4}", state.DisplayName, state.Id, state.IsLocalPlayer, state.HealthAvailable ? $"{state.CurrentHealth}/{state.MaxHealth}" : "unavailable", state.IsDead);
			_logger.LogInfo((object)($"[TeammateHealthHUD/Tracker] Player snapshot: source={_bindings.LastRosterSource}, cached={_states.Count}, rosterVersion={RosterVersion}; " + string.Join(" | ", values)));
		}
	}
}
namespace TeammateHealthHUD.Config
{
	public sealed class ModConfig : IDisposable
	{
		private const float ExternalReloadInterval = 0.5f;

		private readonly List<Action> _unsubscribeActions = new List<Action>();

		private readonly ConfigFile _configFile;

		private DateTime _lastWriteTimeUtc;

		private long _lastFileLength;

		private float _externalReloadTimer;

		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<bool> ShowSelf { get; }

		public ConfigEntry<bool> ShowHealthNumbers { get; }

		public ConfigEntry<bool> ShowHealthBar { get; }

		public ConfigEntry<bool> ShowAvatar { get; }

		public ConfigEntry<bool> ShowDeadPlayers { get; }

		public ConfigEntry<string> DeadText { get; }

		public ConfigEntry<KeyboardShortcut> ToggleKey { get; }

		public ConfigEntry<string> Anchor { get; }

		public ConfigEntry<float> OffsetX { get; }

		public ConfigEntry<float> OffsetY { get; }

		public ConfigEntry<float> Scale { get; }

		public ConfigEntry<float> Spacing { get; }

		public ConfigEntry<float> PanelOpacity { get; }

		public ConfigEntry<string> HealthyColor { get; }

		public ConfigEntry<string> MediumColor { get; }

		public ConfigEntry<string> LowColor { get; }

		public ConfigEntry<string> DeadColor { get; }

		public ConfigEntry<string> NameColor { get; }

		public ConfigEntry<bool> SmoothHealthAnimation { get; }

		public ConfigEntry<float> HealthLerpSpeed { get; }

		public ConfigEntry<bool> DebugLogging { get; }

		public event Action? Changed;

		public ModConfig(ConfigFile config)
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			_configFile = config;
			Enabled = Bind(config, "General", "Enabled", value: true, "Enable the teammate HUD.");
			ShowSelf = Bind(config, "General", "ShowSelf", value: false, "Include the local player in the HUD.");
			ShowHealthNumbers = Bind(config, "General", "ShowHealthNumbers", value: true, "Show current and maximum HP.");
			ShowHealthBar = Bind(config, "General", "ShowHealthBar", value: true, "Show the graphical health bar.");
			ShowAvatar = Bind(config, "General", "ShowAvatar", value: false, "Show the player portrait or fallback initial.");
			ShowDeadPlayers = Bind(config, "General", "ShowDeadPlayers", value: true, "Keep dead players visible with a stamp.");
			DeadText = Bind(config, "General", "DeadText", "DEAD", "Text displayed over dead teammates.");
			ToggleKey = Bind<KeyboardShortcut>(config, "General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Local show/hide shortcut.");
			Anchor = Bind(config, "Position", "Anchor", "TopLeft", "TopLeft, MiddleLeft, BottomLeft, TopRight, MiddleRight, or BottomRight.");
			OffsetX = Bind(config, "Position", "OffsetX", 20f, "Horizontal distance from the selected screen edge.");
			OffsetY = Bind(config, "Position", "OffsetY", 150f, "Vertical distance from the selected screen anchor.");
			Scale = Bind(config, "Position", "Scale", 1f, "HUD scale multiplier.");
			Spacing = Bind(config, "Position", "Spacing", 8f, "Vertical space between player cards.");
			PanelOpacity = Bind(config, "Appearance", "PanelOpacity", 0.58f, "Player-card background opacity from 0 to 1.");
			HealthyColor = Bind(config, "Appearance", "HealthyColor", "#4DFF5C", "Healthy bar color in HTML hex format.");
			MediumColor = Bind(config, "Appearance", "MediumColor", "#F4C95D", "Medium-health bar color in HTML hex format.");
			LowColor = Bind(config, "Appearance", "LowColor", "#FF5A5F", "Low-health bar color in HTML hex format.");
			DeadColor = Bind(config, "Appearance", "DeadColor", "#E50914", "Dead-state color in HTML hex format.");
			NameColor = Bind(config, "Appearance", "NameColor", "#F0FFF2", "Player-name color in HTML hex format.");
			SmoothHealthAnimation = Bind(config, "Appearance", "SmoothHealthAnimation", value: true, "Smooth bar movement between health samples.");
			HealthLerpSpeed = Bind(config, "Appearance", "HealthLerpSpeed", 8f, "Health-bar smoothing speed.");
			DebugLogging = Bind(config, "Debug", "DebugLogging", value: false, "Log a player snapshot at most once every two seconds.");
			CaptureFileState();
		}

		public bool PollForExternalChanges(float unscaledDeltaTime)
		{
			_externalReloadTimer -= ((unscaledDeltaTime > 0f) ? unscaledDeltaTime : 0f);
			if (_externalReloadTimer > 0f)
			{
				return false;
			}
			_externalReloadTimer = 0.5f;
			try
			{
				string configFilePath = _configFile.ConfigFilePath;
				if (!File.Exists(configFilePath))
				{
					return false;
				}
				FileInfo fileInfo = new FileInfo(configFilePath);
				if (fileInfo.LastWriteTimeUtc == _lastWriteTimeUtc && fileInfo.Length == _lastFileLength)
				{
					return false;
				}
				bool saveOnConfigSet = _configFile.SaveOnConfigSet;
				try
				{
					_configFile.SaveOnConfigSet = false;
					_configFile.Reload();
				}
				finally
				{
					_configFile.SaveOnConfigSet = saveOnConfigSet;
				}
				CaptureFileState();
				this.Changed?.Invoke();
				return true;
			}
			catch (IOException)
			{
				return false;
			}
			catch (UnauthorizedAccessException)
			{
				return false;
			}
		}

		private ConfigEntry<T> Bind<T>(ConfigFile config, string section, string key, T value, string description)
		{
			ConfigEntry<T> entry = config.Bind<T>(section, key, value, description);
			entry.SettingChanged += OnSettingChanged;
			_unsubscribeActions.Add(delegate
			{
				entry.SettingChanged -= OnSettingChanged;
			});
			return entry;
		}

		private void OnSettingChanged(object sender, EventArgs args)
		{
			this.Changed?.Invoke();
		}

		private void CaptureFileState()
		{
			string configFilePath = _configFile.ConfigFilePath;
			if (File.Exists(configFilePath))
			{
				FileInfo fileInfo = new FileInfo(configFilePath);
				_lastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
				_lastFileLength = fileInfo.Length;
			}
		}

		public void Dispose()
		{
			foreach (Action unsubscribeAction in _unsubscribeActions)
			{
				unsubscribeAction();
			}
			_unsubscribeActions.Clear();
		}
	}
}