Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TeammateHealthHUD v1.1.0
plugins/TeammateHealthHUD.dll
Decompiled 11 hours agousing 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(); } } }