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(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 _sprites = new List(); 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("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(); component.preferredWidth = 292f; component.preferredHeight = 64f; component.minWidth = 292f; component.minHeight = 64f; _panel = _root.GetComponent(); _panel.sprite = styles.PanelSprite; ((Graphic)_panel).raycastTarget = false; _panelOutline = _root.GetComponent(); ((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(); _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(); _avatarMaskImage.sprite = styles.CircleSprite; ((Graphic)_avatarMaskImage).raycastTarget = false; _avatarRoot.GetComponent().showMaskGraphic = true; GameObject val2 = CreateUiObject("Portrait", _avatarRoot.transform, typeof(CanvasRenderer), typeof(RawImage)); Stretch((RectTransform)val2.transform); _avatarImage = val2.GetComponent(); ((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(); ConfigureText(_initialText, 19, (FontStyle)1, (TextAnchor)4); GameObject val4 = CreateUiObject("Name", _root.transform, typeof(CanvasRenderer), typeof(Text), typeof(Shadow)); _nameText = val4.GetComponent(); ConfigureText(_nameText, 15, (FontStyle)1, (TextAnchor)3); _nameText.resizeTextForBestFit = true; _nameText.resizeTextMinSize = 10; _nameText.resizeTextMaxSize = 15; Shadow component2 = val4.GetComponent(); 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(); ConfigureText(_healthText, 20, (FontStyle)1, (TextAnchor)3); _healthGlow = val5.GetComponent(); _healthGlow.effectDistance = new Vector2(1f, 0f); _healthGlow.useGraphicAlpha = true; GameObject val6 = CreateUiObject("HealthMaximum", _root.transform, typeof(CanvasRenderer), typeof(Text)); _maxHealthText = val6.GetComponent(); ConfigureText(_maxHealthText, 12, (FontStyle)1, (TextAnchor)3); _barRoot = CreateUiObject("HealthBar", _root.transform, typeof(CanvasRenderer), typeof(Image)); Image component3 = _barRoot.GetComponent(); 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(); _barForeground.sprite = styles.BarSprite; ((Graphic)_barForeground).raycastTarget = false; _barGlow = val7.GetComponent(); _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(); ((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(); component5.sprite = styles.StampBorderSprite; ((Graphic)component5).raycastTarget = false; _deadCanvasGroup = _deadStamp.GetComponent(); GameObject val8 = CreateUiObject("Label", _deadStamp.transform, typeof(CanvasRenderer), typeof(Text)); Stretch((RectTransform)val8.transform); _deadText = val8.GetComponent(); 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()).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 _entries = new Dictionary(StringComparer.Ordinal); private readonly List _removeBuffer = new List(); private readonly HashSet _liveIds = new HashSet(StringComparer.Ordinal); private readonly List _orderedStates = new List(); 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(); component.renderMode = (RenderMode)0; component.overrideSorting = true; component.sortingOrder = 32700; CanvasScaler component2 = _canvasObject.GetComponent(); 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(); _container.sizeDelta = new Vector2(292f, 0f); _layout = val2.GetComponent(); ((HorizontalOrVerticalLayoutGroup)_layout).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)_layout).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)_layout).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)_layout).childForceExpandHeight = false; ContentSizeFitter component3 = val2.GetComponent(); 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) ?? ""; 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 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() : 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 EmptyPlayers = Array.Empty(); private readonly ManualLogSource _logger; private readonly List _singlePlayerFallback = new List(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(_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 GetPlayers(bool debugLogging) { try { List 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(_isLocalField, player, out value) && value; bool value2; bool flag = ReflectionUtils.TryRead(_deadSetField, player, out value2) && value2; int value3 = 0; int value4 = 0; bool flag2 = (Object)(object)player.playerHealth != (Object)null && ReflectionUtils.TryRead(_healthField, player.playerHealth, out value3) && ReflectionUtils.TryRead(_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 _states = new Dictionary(StringComparer.Ordinal); private readonly HashSet _seenIds = new HashSet(StringComparer.Ordinal); private readonly HashSet _sampleFailuresLogged = new HashSet(StringComparer.Ordinal); private readonly List _removeBuffer = new List(); private float _rosterTimer; private float _stateTimer; private float _debugTimer; private long _nextJoinOrder; public IReadOnlyDictionary 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 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 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 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 _unsubscribeActions = new List(); private readonly ConfigFile _configFile; private DateTime _lastWriteTimeUtc; private long _lastFileLength; private float _externalReloadTimer; public ConfigEntry Enabled { get; } public ConfigEntry ShowSelf { get; } public ConfigEntry ShowHealthNumbers { get; } public ConfigEntry ShowHealthBar { get; } public ConfigEntry ShowAvatar { get; } public ConfigEntry ShowDeadPlayers { get; } public ConfigEntry DeadText { get; } public ConfigEntry ToggleKey { get; } public ConfigEntry Anchor { get; } public ConfigEntry OffsetX { get; } public ConfigEntry OffsetY { get; } public ConfigEntry Scale { get; } public ConfigEntry Spacing { get; } public ConfigEntry PanelOpacity { get; } public ConfigEntry HealthyColor { get; } public ConfigEntry MediumColor { get; } public ConfigEntry LowColor { get; } public ConfigEntry DeadColor { get; } public ConfigEntry NameColor { get; } public ConfigEntry SmoothHealthAnimation { get; } public ConfigEntry HealthLerpSpeed { get; } public ConfigEntry 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(config, "General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "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 Bind(ConfigFile config, string section, string key, T value, string description) { ConfigEntry entry = config.Bind(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(); } } }