Decompiled source of Player Status Bars v0.3.0

BepInEx/plugins/PlayerStatusBars/PlayerStatusBars.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using LethalConfig;
using LethalConfig.ConfigItems;
using LethalConfig.ConfigItems.Options;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
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: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.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 OtherPlayerStatusBars
{
	internal static class ChineseProjectDetection
	{
		public const string PluginGuid = "Aueser.LCChineseProject";

		public const string LegacyPluginGuid = "cn.codex.v81testchn";

		public const string ThunderstoreDirectoryName = "Aueser-LC_Chinese_Project";

		public const string PluginFileName = "V81TestChn.dll";

		public static bool Matches(string? guid, string? location)
		{
			if (!string.Equals(guid, "Aueser.LCChineseProject", StringComparison.OrdinalIgnoreCase) && !string.Equals(guid, "cn.codex.v81testchn", StringComparison.OrdinalIgnoreCase) && !Contains(location, "Aueser-LC_Chinese_Project"))
			{
				return Contains(location, "V81TestChn.dll");
			}
			return true;
		}

		private static bool Contains(string? value, string term)
		{
			if (value != null)
			{
				return value.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}
	}
	internal static class LegacyConfigCleaner
	{
		private static readonly string[] ObsoleteKeys = new string[9] { "Bar Spacing", "Health Bar Y Offset", "Infection Bar Y Offset", "Infection Bar Display Mode", "Critical Health Sync Mode", "Show Health Text", "Show Infection Text", "Enable Ten Health Fallback", "Ten Health Fallback Delay Seconds" };

		public static bool TryRemoveObsoleteKeys(string[] lines, out string[] cleanedLines)
		{
			List<string> list = new List<string>(lines.Length);
			foreach (string text in lines)
			{
				if (!IsObsoleteEntry(text))
				{
					list.Add(text);
				}
			}
			cleanedLines = list.ToArray();
			return cleanedLines.Length != lines.Length;
		}

		private static bool IsObsoleteEntry(string line)
		{
			int num = line.IndexOf('=');
			if (num <= 0 || line.TrimStart().StartsWith("#", StringComparison.Ordinal))
			{
				return false;
			}
			string value = line.Substring(0, num).Trim();
			return Array.IndexOf(ObsoleteKeys, value) >= 0;
		}
	}
	internal static class LethalConfigIntegration
	{
		public const string PluginGuid = "ainavt.lc.lethalconfig";

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Register(StatusBarConfig settings, bool useChinese)
		{
			LethalConfigManager.SkipAutoGen();
			LethalConfigManager.SetModDescription(useChinese ? "在其他玩家头顶显示血条和感染条。" : "Shows health and infection bars above other players.");
			AddBool(settings.EnabledEntry, in StatusBarConfigText.Enabled, useChinese);
			AddBool(settings.HideInOrbitEntry, in StatusBarConfigText.HideInOrbit, useChinese);
			AddFloat(settings.MaxDistanceEntry, in StatusBarConfigText.MaxDisplayDistance, useChinese);
			AddBool(settings.ShowInfectionBarsEntry, in StatusBarConfigText.ShowInfectionBars, useChinese);
			AddBarStyle(settings.BarStyleEntry, in StatusBarConfigText.BarStyle, useChinese);
			AddFloat(settings.HeadOffsetEntry, in StatusBarConfigText.HeadOffset, useChinese);
			AddFloat(settings.UiScaleEntry, in StatusBarConfigText.UiScale, useChinese);
			AddBool(settings.ShowTextEntry, in StatusBarConfigText.ShowText, useChinese);
			AddColor(settings.HealthColorEntry, in StatusBarConfigText.HealthBarColor, useChinese);
			AddColor(settings.InfectionColorEntry, in StatusBarConfigText.InfectionBarColor, useChinese);
			AddColor(settings.BackgroundColorEntry, in StatusBarConfigText.BackgroundColor, useChinese);
			AddBool(settings.DebugLoggingEntry, in StatusBarConfigText.DebugLogging, useChinese);
		}

		private static void AddBool(ConfigEntry<bool> entry, in ConfigText text, bool useChinese)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(entry, new BoolCheckBoxOptions
			{
				Section = text.DisplaySection(useChinese),
				Name = text.DisplayName(useChinese),
				Description = text.DisplayDescription(useChinese),
				RequiresRestart = false
			}));
		}

		private static void AddFloat(ConfigEntry<float> entry, in ConfigText text, bool useChinese)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(entry, new FloatSliderOptions
			{
				Section = text.DisplaySection(useChinese),
				Name = text.DisplayName(useChinese),
				Description = text.DisplayDescription(useChinese),
				RequiresRestart = false
			}));
		}

		private static void AddBarStyle(ConfigEntry<StatusBarConfig.BarStyle> entry, in ConfigText text, bool useChinese)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<StatusBarConfig.BarStyle>(entry, new EnumDropDownOptions
			{
				Section = text.DisplaySection(useChinese),
				Name = text.DisplayName(useChinese),
				Description = text.DisplayDescription(useChinese),
				RequiresRestart = false
			}));
		}

		private static void AddColor(ConfigEntry<StatusBarConfig.ColorPreset> entry, in ConfigText text, bool useChinese)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<StatusBarConfig.ColorPreset>(entry, new EnumDropDownOptions
			{
				Section = text.DisplaySection(useChinese),
				Name = text.DisplayName(useChinese),
				Description = text.DisplayDescription(useChinese),
				RequiresRestart = false
			}));
		}
	}
	internal sealed class StatusBarConfig
	{
		public enum BarStyle
		{
			Classic,
			Segmented
		}

		public enum ColorPreset
		{
			Green,
			Red,
			Orange,
			Yellow,
			Blue,
			Cyan,
			White,
			Black,
			Slate
		}

		public static readonly Color SegmentedCriticalColor = new Color(0.74f, 0.32f, 0.3f, 1f);

		private const float AnchorSpacingUnits = 14f;

		private readonly bool useChineseDescriptions;

		private Color cachedHealthFillColor;

		private Color cachedInfectionFillColor;

		private Color cachedSegmentedHealthFillColor;

		private Color cachedSegmentedInfectionFillColor;

		private Color cachedBackgroundColor;

		private Color cachedBorderColor;

		private float maxDistanceSqr;

		private float anchorYOffset;

		public ConfigEntry<bool> EnabledEntry { get; }

		public ConfigEntry<bool> HideInOrbitEntry { get; }

		public ConfigEntry<float> MaxDistanceEntry { get; }

		public ConfigEntry<bool> ShowInfectionBarsEntry { get; }

		public ConfigEntry<BarStyle> BarStyleEntry { get; }

		public ConfigEntry<float> HeadOffsetEntry { get; }

		public ConfigEntry<float> UiScaleEntry { get; }

		public ConfigEntry<bool> ShowTextEntry { get; }

		public ConfigEntry<ColorPreset> HealthColorEntry { get; }

		public ConfigEntry<ColorPreset> InfectionColorEntry { get; }

		public ConfigEntry<ColorPreset> BackgroundColorEntry { get; }

		public ConfigEntry<bool> DebugLoggingEntry { get; }

		public bool Enabled => EnabledEntry.Value;

		public bool HideInOrbit => HideInOrbitEntry.Value;

		public float MaxDistance => MaxDistanceEntry.Value;

		public float MaxDistanceSqr => maxDistanceSqr;

		public float AnchorYOffset => anchorYOffset;

		public float UiScale => UiScaleEntry.Value;

		public bool ShowText => ShowTextEntry.Value;

		public BarStyle Style => BarStyleEntry.Value;

		public bool ShowInfectionBars => ShowInfectionBarsEntry.Value;

		public bool DebugLogging => DebugLoggingEntry.Value;

		public int Revision { get; private set; }

		public event Action? SettingsChanged;

		private StatusBarConfig(ConfigFile configFile, bool useChineseDescriptions)
		{
			this.useChineseDescriptions = useChineseDescriptions;
			EnabledEntry = Bind(configFile, in StatusBarConfigText.Enabled, defaultValue: true);
			HideInOrbitEntry = Bind(configFile, in StatusBarConfigText.HideInOrbit, defaultValue: true);
			MaxDistanceEntry = Bind(configFile, in StatusBarConfigText.MaxDisplayDistance, 18f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(2f, 80f));
			ShowInfectionBarsEntry = Bind(configFile, in StatusBarConfigText.ShowInfectionBars, defaultValue: true);
			BarStyleEntry = Bind(configFile, in StatusBarConfigText.BarStyle, BarStyle.Segmented);
			HeadOffsetEntry = Bind(configFile, in StatusBarConfigText.HeadOffset, 0.65f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 2f));
			UiScaleEntry = Bind(configFile, in StatusBarConfigText.UiScale, 0.0085f, (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.003f, 0.05f));
			ShowTextEntry = Bind(configFile, in StatusBarConfigText.ShowText, defaultValue: true);
			HealthColorEntry = Bind(configFile, in StatusBarConfigText.HealthBarColor, ColorPreset.Green);
			InfectionColorEntry = Bind(configFile, in StatusBarConfigText.InfectionBarColor, ColorPreset.Orange);
			BackgroundColorEntry = Bind(configFile, in StatusBarConfigText.BackgroundColor, ColorPreset.Slate);
			DebugLoggingEntry = Bind(configFile, in StatusBarConfigText.DebugLogging, defaultValue: false);
			UpdateCachedValues();
		}

		public static StatusBarConfig Create(ConfigFile configFile, bool useChineseDescriptions)
		{
			return new StatusBarConfig(configFile, useChineseDescriptions);
		}

		public Color GetHealthFillColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedHealthFillColor;
		}

		public Color GetInfectionFillColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedInfectionFillColor;
		}

		public Color GetSegmentedHealthFillColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedSegmentedHealthFillColor;
		}

		public Color GetSegmentedInfectionFillColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedSegmentedInfectionFillColor;
		}

		public Color GetBackgroundColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedBackgroundColor;
		}

		public Color GetBorderColor()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return cachedBorderColor;
		}

		private ConfigEntry<T> Bind<T>(ConfigFile configFile, in ConfigText text, T defaultValue, AcceptableValueBase? acceptableValues = null)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			ConfigDescription val = new ConfigDescription(text.DisplayDescription(useChineseDescriptions), acceptableValues, Array.Empty<object>());
			ConfigEntry<T> obj = configFile.Bind<T>(text.Section, text.Key, defaultValue, val);
			obj.SettingChanged += delegate
			{
				NotifySettingsChanged();
			};
			return obj;
		}

		private void NotifySettingsChanged()
		{
			UpdateCachedValues();
			Revision++;
			this.SettingsChanged?.Invoke();
		}

		private void UpdateCachedValues()
		{
			//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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			cachedHealthFillColor = ResolveColor(HealthColorEntry.Value, 0.95f);
			cachedInfectionFillColor = ResolveColor(InfectionColorEntry.Value, 0.95f);
			cachedSegmentedHealthFillColor = ResolveSegmentedColor(HealthColorEntry.Value);
			cachedSegmentedInfectionFillColor = ResolveSegmentedColor(InfectionColorEntry.Value);
			cachedBackgroundColor = ResolveColor(BackgroundColorEntry.Value, 0.78f);
			cachedBorderColor = cachedBackgroundColor;
			cachedBorderColor.a = 1f;
			cachedBorderColor = Color.Lerp(cachedBorderColor, Color.white, 0.35f);
			float value = MaxDistanceEntry.Value;
			maxDistanceSqr = value * value;
			anchorYOffset = HeadOffsetEntry.Value + 14f * UiScaleEntry.Value;
		}

		private static Color ResolveSegmentedColor(ColorPreset preset)
		{
			//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_004a: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			return (Color)(preset switch
			{
				ColorPreset.Green => new Color(0.44f, 0.64f, 0.46f, 1f), 
				ColorPreset.Red => SegmentedCriticalColor, 
				ColorPreset.Orange => new Color(0.755f, 0.512f, 0.305f, 1f), 
				ColorPreset.Yellow => new Color(0.78f, 0.7f, 0.36f, 1f), 
				ColorPreset.Blue => new Color(0.38f, 0.54f, 0.76f, 1f), 
				ColorPreset.Cyan => new Color(0.36f, 0.68f, 0.7f, 1f), 
				ColorPreset.White => new Color(0.85f, 0.85f, 0.85f, 1f), 
				ColorPreset.Black => new Color(0.12f, 0.12f, 0.12f, 1f), 
				_ => new Color(0.22f, 0.25f, 0.3f, 1f), 
			});
		}

		private static Color ResolveColor(ColorPreset preset, float alpha)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: 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_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			Color result = (Color)(preset switch
			{
				ColorPreset.Green => new Color(0.18f, 0.86f, 0.32f, alpha), 
				ColorPreset.Red => new Color(0.88f, 0.25f, 0.22f, alpha), 
				ColorPreset.Orange => new Color(0.93f, 0.47f, 0.16f, alpha), 
				ColorPreset.Yellow => new Color(0.95f, 0.82f, 0.18f, alpha), 
				ColorPreset.Blue => new Color(0.28f, 0.58f, 0.92f, alpha), 
				ColorPreset.Cyan => new Color(0.18f, 0.83f, 0.88f, alpha), 
				ColorPreset.White => new Color(0.95f, 0.95f, 0.95f, alpha), 
				ColorPreset.Black => new Color(0.08f, 0.08f, 0.08f, alpha), 
				_ => new Color(0.12f, 0.15f, 0.2f, alpha), 
			});
			result.a = alpha;
			return result;
		}
	}
	internal readonly struct ConfigText
	{
		public string Section { get; }

		public string Key { get; }

		public string Description { get; }

		public string ChineseSection { get; }

		public string ChineseName { get; }

		public string ChineseDescription { get; }

		public ConfigText(string section, string key, string description, string chineseSection, string chineseName, string chineseDescription)
		{
			Section = section;
			Key = key;
			Description = description;
			ChineseSection = chineseSection;
			ChineseName = chineseName;
			ChineseDescription = chineseDescription;
		}

		public string DisplaySection(bool useChinese)
		{
			if (!useChinese)
			{
				return Section;
			}
			return ChineseSection;
		}

		public string DisplayName(bool useChinese)
		{
			if (!useChinese)
			{
				return Key;
			}
			return ChineseName;
		}

		public string DisplayDescription(bool useChinese)
		{
			if (!useChinese)
			{
				return Description;
			}
			return ChineseDescription;
		}
	}
	internal static class StatusBarConfigText
	{
		public const string ModDescription = "Shows health and infection bars above other players.";

		public const string ChineseModDescription = "在其他玩家头顶显示血条和感染条。";

		private const string General = "General";

		private const string Layout = "Layout";

		private const string Text = "Text";

		private const string Colors = "Colors";

		private const string Debug = "Debug";

		private const string ChineseGeneral = "常规";

		private const string ChineseLayout = "布局";

		private const string ChineseText = "文字";

		private const string ChineseColors = "颜色";

		private const string ChineseDebug = "调试";

		private const string ChineseColorOptions = "可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。";

		public static readonly ConfigText Enabled = new ConfigText("General", "Enabled", "Show status bars above other players. Status sync keeps running while this is off, so other players still receive your data.", "常规", "启用状态条", "在其他玩家头顶显示状态条。关闭后状态同步仍会运行,其他玩家依然能收到你的数据。");

		public static readonly ConfigText HideInOrbit = new ConfigText("General", "Hide In Orbit", "Hide status bars while the ship is in orbit.", "常规", "轨道中隐藏", "飞船处于轨道时隐藏状态条。");

		public static readonly ConfigText MaxDisplayDistance = new ConfigText("General", "Max Display Distance", "Hide status bars beyond this distance.", "常规", "最大显示距离", "超过此距离时隐藏状态条。");

		public static readonly ConfigText ShowInfectionBars = new ConfigText("General", "Show Infection Bars", "Host setting for the whole room. Infection bars only appear when the host runs this version of the mod with this turned on; the value on other players has no effect.", "常规", "显示感染条", "主机设置,对整个房间生效。只有主机安装了此版本模组并开启此项时才显示感染条,其他玩家的设置不起作用。");

		public static readonly ConfigText BarStyle = new ConfigText("Layout", "Bar Style", "Look of the status bars. Segmented shows one compact panel with ten-cell bars and animated damage and healing. Classic is the original style.", "布局", "状态条样式", "状态条的外观。Segmented 为十格样式,使用合并面板,并带有掉血和回血动画;Classic 为经典样式。");

		public static readonly ConfigText HeadOffset = new ConfigText("Layout", "Head Offset", "Height of the status bars above the player's head.", "布局", "头顶高度", "状态条距离玩家头顶的高度。");

		public static readonly ConfigText UiScale = new ConfigText("Layout", "UI Scale", "Size of the status bars.", "布局", "界面缩放", "状态条的大小。");

		public static readonly ConfigText ShowText = new ConfigText("Text", "Show Text", "Show health numbers and infection percentage on the bars.", "文字", "显示文字", "在状态条上显示血量数字和感染百分比。");

		public static readonly ConfigText HealthBarColor = new ConfigText("Colors", "Health Bar Color", "Fill color of the health bar.", "颜色", "血条颜色", "血条的填充颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。");

		public static readonly ConfigText InfectionBarColor = new ConfigText("Colors", "Infection Bar Color", "Fill color of the infection bar.", "颜色", "感染条颜色", "感染条的填充颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。");

		public static readonly ConfigText BackgroundColor = new ConfigText("Colors", "Background Color", "Background color of both bars.", "颜色", "背景颜色", "血条和感染条的背景颜色。可选值:Green 绿色、Red 红色、Orange 橙色、Yellow 黄色、Blue 蓝色、Cyan 青色、White 白色、Black 黑色、Slate 石板灰。");

		public static readonly ConfigText DebugLogging = new ConfigText("Debug", "Debug Logging", "Write throttled diagnostic logs for bar creation, filtering, visibility, camera state and health display decisions.", "调试", "调试日志", "写入节流诊断日志,用于排查状态条的创建、过滤、可见性、相机状态和血量显示决策。");

		public static readonly ConfigText[] All = new ConfigText[12]
		{
			Enabled, HideInOrbit, MaxDisplayDistance, ShowInfectionBars, BarStyle, HeadOffset, UiScale, ShowText, HealthBarColor, InfectionBarColor,
			BackgroundColor, DebugLogging
		};
	}
	internal static class MyPluginInfo
	{
		public const string PluginGuid = "playerstatusbars";

		public const string PluginName = "PlayerStatusBars";

		public const string PluginVersion = "0.3.0";
	}
	internal sealed class PlayerStatusBarManager : MonoBehaviour
	{
		private const float DebugSummaryInterval = 5f;

		private const float ActiveRefreshInterval = 0.5f;

		private const float IdleRefreshInterval = 2.5f;

		private const int ScanSlotsPerRefresh = 4;

		private const int MinimumPlayerSlotCapacity = 16;

		private PlayerStatusBarView?[] trackedBarsBySlot = Array.Empty<PlayerStatusBarView>();

		private PlayerStatusBarView?[] activeBars = Array.Empty<PlayerStatusBarView>();

		private int activeBarCount;

		private int activePlayerSlotCount;

		private readonly Stack<PlayerStatusBarView> pooledBars = new Stack<PlayerStatusBarView>();

		private float nextRefreshTime;

		private float nextDebugSummaryTime;

		private int refreshScanCursor;

		private bool displaySuspended;

		private void Start()
		{
			Plugin.LogDebug($"Runtime manager started active={((Component)this).gameObject.activeInHierarchy}.");
		}

		private void Update()
		{
			StatusBarConfig settings = Plugin.Settings;
			StartOfRound instance = StartOfRound.Instance;
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : instance?.localPlayerController);
			PlayerControllerB[] array = instance?.allPlayerScripts;
			PlayerStatusSnapshotSync.Tick(instance, array);
			if (!settings.Enabled)
			{
				LogDebugBlocked("disabled");
				SuspendDisplay();
				return;
			}
			if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null || array == null)
			{
				if (settings.DebugLogging)
				{
					LogDebugBlocked($"not-ready startOfRound={(Object)(object)instance != (Object)null} localPlayer={(Object)(object)val != (Object)null} allPlayers={array != null}");
				}
				SuspendDisplay();
				return;
			}
			EnsureSlotCapacity(array.Length);
			if (ShouldSkipBarsForShipState(instance))
			{
				if (settings.DebugLogging)
				{
					LogDebugBlocked($"orbit-hidden inShipPhase={instance.inShipPhase} shipHasLanded={instance.shipHasLanded} shipDoorsEnabled={instance.shipDoorsEnabled} shipIsLeaving={instance.shipIsLeaving} dayStarted={TimeOfDay.Instance?.currentDayTimeStarted}");
				}
				SuspendDisplay();
				return;
			}
			ResumeDisplay();
			if (activeBarCount != 0 || !(Time.unscaledTime < nextRefreshTime))
			{
				if (Time.unscaledTime >= nextRefreshTime)
				{
					RefreshPlayerSlice(instance, val, array);
				}
				if (activeBarCount != 0)
				{
					TickTrackedBars(CreateFrameContext(settings, val));
				}
			}
		}

		private void SuspendDisplay()
		{
			if (!displaySuspended)
			{
				displaySuspended = true;
				ClearBars();
				PlayerStatusSnapshotSync.ClearSnapshots();
				CadaverGrowthInfectionProvider.InvalidateAll();
			}
		}

		private void ResumeDisplay()
		{
			if (displaySuspended)
			{
				displaySuspended = false;
				nextRefreshTime = 0f;
				CadaverGrowthInfectionProvider.InvalidateAll();
			}
		}

		private void RefreshPlayerSlice(StartOfRound startOfRound, PlayerControllerB localPlayer, PlayerControllerB[] allPlayers)
		{
			bool flag = Plugin.Settings.DebugLogging && Time.unscaledTime >= nextDebugSummaryTime;
			int num = 0;
			int num2 = activeBarCount;
			int num3 = 0;
			if (allPlayers.Length == 0)
			{
				ClearBars();
				LogDebugSummary(startOfRound, localPlayer, 0, num, num2, num3);
				ScheduleNextRefresh(num > 0 || num2 > 0 || allPlayers.Length > 4);
				return;
			}
			int num4 = Mathf.Min(4, allPlayers.Length);
			for (int i = 0; i < num4; i++)
			{
				int num5 = refreshScanCursor;
				refreshScanCursor = (refreshScanCursor + 1) % allPlayers.Length;
				PlayerControllerB val = allPlayers[num5];
				if (!ShouldTrackPlayer(startOfRound, val, localPlayer, num5, allPlayers, out string skipReason))
				{
					num3++;
					if (flag)
					{
						LogDebugPlayerSkip(num5, val, skipReason);
					}
					RemoveBar(num5);
					continue;
				}
				int num6 = ResolvePlayerKey(val, num5, allPlayers);
				PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[num5];
				if (!((Object)(object)playerStatusBarView != (Object)null) || !playerStatusBarView.MatchesPlayer(val, localPlayer, num6))
				{
					if ((Object)(object)playerStatusBarView != (Object)null)
					{
						RemoveBar(num5);
					}
					PlayerStatusBarView playerStatusBarView2 = RentBarView(num6, val);
					if ((Object)(object)playerStatusBarView2 == (Object)null)
					{
						Plugin.Log.LogWarning((object)$"Failed to create player status bar for playerKey={num6}.");
						continue;
					}
					trackedBarsBySlot[num5] = playerStatusBarView2;
					AddActiveBar(playerStatusBarView2);
					num++;
					Plugin.LogDebug($"Created bar playerKey={num6} slot={num5} clientId={val.playerClientId} actualClientId={val.actualClientId} name='{val.playerUsername}' health={val.health} active={((Component)val).gameObject.activeInHierarchy}.");
				}
			}
			LogDebugSummary(startOfRound, localPlayer, allPlayers.Length, num, num2, num3);
			ScheduleNextRefresh(num > 0 || num2 > 0 || allPlayers.Length > 4);
		}

		private void EnsureSlotCapacity(int slotCount)
		{
			if (slotCount < activePlayerSlotCount)
			{
				ReleaseBarsFrom(slotCount);
			}
			activePlayerSlotCount = slotCount;
			if (slotCount == 0 || refreshScanCursor >= slotCount)
			{
				refreshScanCursor = 0;
			}
			if (trackedBarsBySlot.Length < slotCount)
			{
				int num = trackedBarsBySlot.Length;
				PlayerStatusBarView[] destinationArray = new PlayerStatusBarView[GrowSlotCapacity(slotCount)];
				if (num > 0)
				{
					Array.Copy(trackedBarsBySlot, destinationArray, num);
				}
				trackedBarsBySlot = destinationArray;
			}
		}

		private static int GrowSlotCapacity(int requiredCapacity)
		{
			int num;
			for (num = 16; num < requiredCapacity; num *= 2)
			{
			}
			return num;
		}

		private void ScheduleNextRefresh(bool hasActiveBars)
		{
			nextRefreshTime = Time.unscaledTime + (hasActiveBars ? 0.5f : 2.5f);
		}

		private PlayerStatusBarView? RentBarView(int playerKey, PlayerControllerB player)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			PlayerStatusBarView playerStatusBarView;
			if (pooledBars.Count > 0)
			{
				playerStatusBarView = pooledBars.Pop();
				((Component)playerStatusBarView).transform.SetParent(((Component)this).transform, false);
				((Object)playerStatusBarView).name = $"OtherPlayerStatusBar_{playerKey}";
				playerStatusBarView.Bind(player, playerKey);
				return playerStatusBarView;
			}
			GameObject val = new GameObject($"OtherPlayerStatusBar_{playerKey}");
			val.transform.SetParent(((Component)this).transform, false);
			playerStatusBarView = val.AddComponent<PlayerStatusBarView>();
			if (playerStatusBarView.Initialize(player, playerKey))
			{
				return playerStatusBarView;
			}
			Object.Destroy((Object)(object)val);
			return null;
		}

		private void ReleaseBarView(PlayerStatusBarView view)
		{
			view.Release();
			((Component)view).transform.SetParent(((Component)this).transform, false);
			pooledBars.Push(view);
		}

		private void TickTrackedBars(in PlayerStatusFrameContext frameContext)
		{
			for (int i = 0; i < activeBarCount; i++)
			{
				PlayerStatusBarView playerStatusBarView = activeBars[i];
				if ((Object)(object)playerStatusBarView != (Object)null)
				{
					playerStatusBarView.Tick(in frameContext);
				}
			}
		}

		private void AddActiveBar(PlayerStatusBarView view)
		{
			EnsureActiveBarCapacity(activeBarCount + 1);
			activeBars[activeBarCount] = view;
			activeBarCount++;
		}

		private void RemoveActiveBar(PlayerStatusBarView view)
		{
			for (int i = 0; i < activeBarCount; i++)
			{
				if (!((Object)(object)activeBars[i] != (Object)(object)view))
				{
					int num = activeBarCount - 1;
					activeBars[i] = activeBars[num];
					activeBars[num] = null;
					activeBarCount = num;
					break;
				}
			}
		}

		private void EnsureActiveBarCapacity(int requiredCapacity)
		{
			if (activeBars.Length < requiredCapacity)
			{
				PlayerStatusBarView[] destinationArray = new PlayerStatusBarView[GrowSlotCapacity(requiredCapacity)];
				if (activeBarCount > 0)
				{
					Array.Copy(activeBars, destinationArray, activeBarCount);
				}
				activeBars = destinationArray;
			}
		}

		private static PlayerStatusFrameContext CreateFrameContext(StatusBarConfig settings, PlayerControllerB localPlayer)
		{
			//IL_0013: 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)
			Camera viewCamera = StatusBarBillboard.ResolveViewCamera();
			Quaternion rotation;
			bool hasBillboardRotation = StatusBarBillboard.TryResolveBillboardRotation(viewCamera, out rotation);
			return new PlayerStatusFrameContext(settings, localPlayer, viewCamera, hasBillboardRotation, rotation, ResolveObserverPosition(localPlayer, viewCamera));
		}

		private static Vector3 ResolveObserverPosition(PlayerControllerB localPlayer, Camera? viewCamera)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)viewCamera != (Object)null)
			{
				return ((Component)viewCamera).transform.position;
			}
			PlayerControllerB spectatedPlayerScript = localPlayer.spectatedPlayerScript;
			if (localPlayer.isPlayerDead && (Object)(object)spectatedPlayerScript != (Object)null && !spectatedPlayerScript.isPlayerDead)
			{
				return ((Component)spectatedPlayerScript).transform.position;
			}
			return ((Component)localPlayer).transform.position;
		}

		private static bool ShouldTrackPlayer(StartOfRound startOfRound, PlayerControllerB player, PlayerControllerB localPlayer, int slot, PlayerControllerB[] allPlayers, out string skipReason)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)localPlayer == (Object)null)
			{
				skipReason = "null-player-or-local";
				return false;
			}
			if ((Object)(object)player == (Object)(object)localPlayer)
			{
				skipReason = "local-player";
				return false;
			}
			if (!((Component)player).gameObject.activeInHierarchy)
			{
				skipReason = "inactive-gameobject";
				return false;
			}
			int playerKey = ResolvePlayerKey(player, slot, allPlayers);
			if (!IsConnectedPlayerSlot(startOfRound, player, playerKey))
			{
				skipReason = "not-connected";
				return false;
			}
			skipReason = string.Empty;
			return true;
		}

		private static bool IsConnectedPlayerSlot(StartOfRound startOfRound, PlayerControllerB player, int playerKey)
		{
			if (startOfRound.ClientPlayerList.TryGetValue(player.actualClientId, out var value))
			{
				return value == playerKey;
			}
			if (player.isPlayerControlled)
			{
				return !player.disconnectedMidGame;
			}
			return false;
		}

		private static int ResolvePlayerKey(PlayerControllerB player, int slotIndex, PlayerControllerB[] allPlayers)
		{
			int num = (int)player.playerClientId;
			if (num >= 0 && num < allPlayers.Length && (Object)(object)allPlayers[num] == (Object)(object)player)
			{
				return num;
			}
			return slotIndex;
		}

		internal static bool ShouldSkipBarsForShipState(StartOfRound startOfRound)
		{
			if (!Plugin.Settings.HideInOrbit)
			{
				return false;
			}
			if (startOfRound.shipIsLeaving)
			{
				return true;
			}
			if ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.currentDayTimeStarted)
			{
				return false;
			}
			if (startOfRound.inShipPhase)
			{
				return !startOfRound.shipHasLanded;
			}
			return false;
		}

		private void RemoveBar(int playerId)
		{
			if (playerId < 0 || playerId >= trackedBarsBySlot.Length)
			{
				return;
			}
			PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[playerId];
			if (!((Object)(object)playerStatusBarView == (Object)null))
			{
				trackedBarsBySlot[playerId] = null;
				RemoveActiveBar(playerStatusBarView);
				if ((Object)(object)playerStatusBarView != (Object)null)
				{
					ReleaseBarView(playerStatusBarView);
				}
				Plugin.LogDebug($"Removed bar playerKey={playerId}.");
			}
		}

		private void ClearBars()
		{
			int num = activeBarCount;
			for (int i = 0; i < trackedBarsBySlot.Length; i++)
			{
				PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[i];
				if ((Object)(object)playerStatusBarView != (Object)null)
				{
					trackedBarsBySlot[i] = null;
					ReleaseBarView(playerStatusBarView);
				}
			}
			Array.Clear(activeBars, 0, activeBarCount);
			activeBarCount = 0;
			if (num > 0)
			{
				CadaverGrowthInfectionProvider.InvalidateAll();
				Plugin.LogDebug($"Cleared all bars count={num}.");
			}
		}

		private void ReleaseBarsFrom(int firstSlot)
		{
			for (int i = firstSlot; i < trackedBarsBySlot.Length; i++)
			{
				RemoveBar(i);
			}
		}

		private void OnDestroy()
		{
			Plugin.LogDebug($"Runtime manager destroyed tracked={activeBarCount}.");
			PlayerStatusSnapshotSync.ClearSnapshots();
			CadaverGrowthInfectionProvider.InvalidateAll();
			DestroyAllBars();
		}

		private void DestroyAllBars()
		{
			for (int i = 0; i < trackedBarsBySlot.Length; i++)
			{
				PlayerStatusBarView playerStatusBarView = trackedBarsBySlot[i];
				if ((Object)(object)playerStatusBarView != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)playerStatusBarView).gameObject);
				}
			}
			trackedBarsBySlot = Array.Empty<PlayerStatusBarView>();
			activeBars = Array.Empty<PlayerStatusBarView>();
			activeBarCount = 0;
			while (pooledBars.Count > 0)
			{
				PlayerStatusBarView playerStatusBarView2 = pooledBars.Pop();
				if ((Object)(object)playerStatusBarView2 != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)playerStatusBarView2).gameObject);
				}
			}
		}

		private void OnEnable()
		{
			Plugin.LogDebug("Runtime manager enabled.");
			Plugin.Settings.SettingsChanged += HandleSettingsChanged;
		}

		private void OnDisable()
		{
			Plugin.LogDebug($"Runtime manager disabled tracked={activeBarCount}.");
			Plugin.Settings.SettingsChanged -= HandleSettingsChanged;
		}

		private void HandleSettingsChanged()
		{
			nextRefreshTime = 0f;
		}

		private void LogDebugBlocked(string reason)
		{
			if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime))
			{
				nextDebugSummaryTime = Time.unscaledTime + 5f;
				Plugin.LogDebug($"Refresh blocked reason={reason} tracked={activeBarCount}.");
			}
		}

		private static void LogDebugPlayerSkip(int slot, PlayerControllerB player, string reason)
		{
			if (Plugin.Settings.DebugLogging && !(reason == "local-player"))
			{
				Plugin.LogDebug(string.Format("Skipped slot={0} reason={1} clientId={2} name='{3}' controlled={4} dead={5} health={6} active={7}.", slot, reason, ((Object)(object)player != (Object)null) ? player.playerClientId.ToString() : "null", ((Object)(object)player != (Object)null) ? player.playerUsername : "null", (Object)(object)player != (Object)null && player.isPlayerControlled, (Object)(object)player != (Object)null && player.isPlayerDead, ((Object)(object)player != (Object)null) ? player.health : (-1), (Object)(object)player != (Object)null && ((Component)player).gameObject.activeInHierarchy));
			}
		}

		private void LogDebugSummary(StartOfRound startOfRound, PlayerControllerB localPlayer, int playerSlots, int createdCount, int existingCount, int skippedCount)
		{
			if (Plugin.Settings.DebugLogging && !(Time.unscaledTime < nextDebugSummaryTime))
			{
				nextDebugSummaryTime = Time.unscaledTime + 5f;
				Camera val = StatusBarBillboard.ResolveViewCamera();
				Plugin.LogDebug(string.Format("Refresh summary slots={0} tracked={1} created={2} existing={3} skipped={4} localClient={5} inShipPhase={6} shipHasLanded={7} shipDoorsEnabled={8} shipIsLeaving={9} dayStarted={10} camera='{11}'.", playerSlots, activeBarCount, createdCount, existingCount, skippedCount, localPlayer.playerClientId, startOfRound.inShipPhase, startOfRound.shipHasLanded, startOfRound.shipDoorsEnabled, startOfRound.shipIsLeaving, TimeOfDay.Instance?.currentDayTimeStarted, ((Object)(object)val != (Object)null) ? ((Object)val).name : "none"));
			}
		}
	}
	internal readonly struct PlayerStatusFrameContext
	{
		public readonly StatusBarConfig Settings;

		public readonly PlayerControllerB LocalPlayer;

		public readonly Camera? ViewCamera;

		public readonly bool HasBillboardRotation;

		public readonly Quaternion BillboardRotation;

		public readonly Vector3 ObserverPosition;

		public readonly float MaxDistanceSqr;

		public readonly float AnchorYOffset;

		public readonly float UiScale;

		public PlayerStatusFrameContext(StatusBarConfig settings, PlayerControllerB localPlayer, Camera? viewCamera, bool hasBillboardRotation, Quaternion billboardRotation, Vector3 observerPosition)
		{
			//IL_001e: 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_0026: 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)
			Settings = settings;
			LocalPlayer = localPlayer;
			ViewCamera = viewCamera;
			HasBillboardRotation = hasBillboardRotation;
			BillboardRotation = billboardRotation;
			ObserverPosition = observerPosition;
			MaxDistanceSqr = settings.MaxDistanceSqr;
			AnchorYOffset = settings.AnchorYOffset;
			UiScale = settings.UiScale;
		}
	}
	[BepInPlugin("playerstatusbars", "PlayerStatusBars", "0.3.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private const string RuntimeObjectName = "PlayerStatusBars.Runtime";

		private const string LegacyConfigFileName = "Codex.OtherPlayerStatusBars.cfg";

		private static GameObject? runtimeObject;

		private static PlayerStatusBarManager? runtimeManager;

		internal static ManualLogSource Log { get; private set; }

		internal static StatusBarConfig Settings { get; private set; }

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			MigrateLegacyConfigFile();
			RemoveObsoleteConfigKeys();
			bool flag = DetectChineseProject();
			Settings = StatusBarConfig.Create(((BaseUnityPlugin)this).Config, flag);
			RegisterLethalConfig(flag);
			EnsureRuntimeManager();
			((BaseUnityPlugin)this).Config.Save();
			Log.LogInfo((object)"PlayerStatusBars loaded.");
			LogDebug("Debug logging is enabled.");
		}

		private void OnDestroy()
		{
			LogDebug("Plugin component destroyed; runtime manager remains active.");
		}

		internal static void LogDebug(string message)
		{
			StatusBarConfig settings = Settings;
			if (settings != null && settings.DebugLogging)
			{
				Log.LogInfo((object)("[Debug] " + message));
			}
		}

		private void MigrateLegacyConfigFile()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			string directoryName = Path.GetDirectoryName(configFilePath);
			if (!string.IsNullOrEmpty(directoryName))
			{
				string text = Path.Combine(directoryName, "Codex.OtherPlayerStatusBars.cfg");
				if (File.Exists(text) && !File.Exists(configFilePath))
				{
					File.Copy(text, configFilePath);
					((BaseUnityPlugin)this).Config.Reload();
					Log.LogInfo((object)("Migrated legacy config from Codex.OtherPlayerStatusBars.cfg to " + Path.GetFileName(configFilePath) + "."));
				}
			}
		}

		private void RemoveObsoleteConfigKeys()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			if (!File.Exists(configFilePath))
			{
				return;
			}
			try
			{
				if (LegacyConfigCleaner.TryRemoveObsoleteKeys(File.ReadAllLines(configFilePath), out string[] cleanedLines))
				{
					File.WriteAllLines(configFilePath, cleanedLines);
					((BaseUnityPlugin)this).Config.Reload();
					Log.LogInfo((object)"Removed obsolete settings from the config file.");
				}
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
			{
				Log.LogWarning((object)("Could not remove obsolete settings: " + ex.Message));
			}
		}

		private static void RegisterLethalConfig(bool useChinese)
		{
			if (!Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig"))
			{
				return;
			}
			try
			{
				LethalConfigIntegration.Register(Settings, useChinese);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("LethalConfig registration failed: " + ex.Message));
			}
		}

		private static void EnsureRuntimeManager()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			if ((Object)(object)runtimeManager != (Object)null)
			{
				GameObject gameObject = ((Component)runtimeManager).gameObject;
				if ((Object)(object)gameObject != (Object)null && !gameObject.activeSelf)
				{
					gameObject.SetActive(true);
				}
				LogDebug("Runtime manager already active.");
				return;
			}
			if ((Object)(object)runtimeObject == (Object)null)
			{
				runtimeObject = new GameObject("PlayerStatusBars.Runtime");
				((Object)runtimeObject).hideFlags = (HideFlags)61;
				Object.DontDestroyOnLoad((Object)(object)runtimeObject);
			}
			else if (!runtimeObject.activeSelf)
			{
				runtimeObject.SetActive(true);
			}
			runtimeManager = runtimeObject.GetComponent<PlayerStatusBarManager>();
			if ((Object)(object)runtimeManager == (Object)null)
			{
				runtimeManager = runtimeObject.AddComponent<PlayerStatusBarManager>();
			}
			LogDebug("Runtime manager created.");
		}

		private static bool DetectChineseProject()
		{
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				if (ChineseProjectDetection.Matches(value.Metadata.GUID, value.Location))
				{
					return true;
				}
			}
			return HasChineseProjectFiles();
		}

		private static bool HasChineseProjectFiles()
		{
			try
			{
				string path = Path.Combine(Paths.PluginPath, "Aueser-LC_Chinese_Project");
				if (Directory.Exists(path) && Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Length != 0)
				{
					return true;
				}
				return Directory.GetFiles(Paths.PluginPath, "V81TestChn.dll", SearchOption.AllDirectories).Length != 0;
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
			{
				Log.LogWarning((object)("Could not check for LC Chinese Project: " + ex.Message));
				return false;
			}
		}
	}
	internal sealed class BarPositionStabilizer
	{
		private const float DeadZoneMeters = 0.04f;

		private bool hasPosition;

		public float X { get; private set; }

		public float Y { get; private set; }

		public float Z { get; private set; }

		public void Update(float targetX, float targetY, float targetZ)
		{
			if (!hasPosition)
			{
				hasPosition = true;
				X = targetX;
				Y = targetY;
				Z = targetZ;
				return;
			}
			float num = targetX - X;
			float num2 = targetY - Y;
			float num3 = targetZ - Z;
			float num4 = (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3);
			if (!(num4 <= 0.04f))
			{
				float num5 = (num4 - 0.04f) / num4;
				X += num * num5;
				Y += num2 * num5;
				Z += num3 * num5;
			}
		}

		public void Reset()
		{
			hasPosition = false;
		}
	}
	internal sealed class BarValueAnimation
	{
		private const float TrailHoldSeconds = 0.4f;

		private const float TrailPointsPerSecond = 66f;

		private const float RisePointsPerSecond = 54f;

		private bool hasValue;

		private float lastUpdateTime;

		private float trailHoldEndTime;

		public float Fill { get; private set; }

		public float Trail { get; private set; }

		public int Target { get; private set; }

		public void Update(int targetValue, float now)
		{
			if (!hasValue)
			{
				hasValue = true;
				Fill = targetValue;
				Trail = targetValue;
				Target = targetValue;
				lastUpdateTime = now;
				return;
			}
			float num = Math.Max(0f, now - lastUpdateTime);
			lastUpdateTime = now;
			Target = targetValue;
			if ((float)targetValue < Fill)
			{
				Trail = Math.Max(Trail, Fill);
				Fill = targetValue;
				trailHoldEndTime = now + 0.4f;
			}
			else if (Fill < (float)targetValue)
			{
				Fill = Math.Min(targetValue, Fill + 54f * num);
			}
			if (now >= trailHoldEndTime)
			{
				Trail -= 66f * num;
			}
			Trail = Math.Max(Trail, Fill);
		}

		public void Reset()
		{
			hasValue = false;
			Fill = 0f;
			Trail = 0f;
			Target = 0;
			lastUpdateTime = 0f;
			trailHoldEndTime = 0f;
		}
	}
	internal sealed class HealthDisplayModel
	{
		public const int LowHealthRegenerationTarget = 20;

		private const int CriticalHealthThreshold = 10;

		private const int MaxCriticalHealth = 9;

		private const int LethalHitProtectionDamage = 50;

		private const int LethalHitProtectedHealth = 5;

		private const float LowHealthRegenerationInterval = 1f;

		private const float DisplayStepInterval = 0.05f;

		private const float DisplayMaxDuration = 0.4f;

		private static readonly (int Damage, int Weight)[] CommonHits = new(int, int)[14]
		{
			(7, 1),
			(8, 1),
			(10, 7),
			(20, 4),
			(25, 1),
			(30, 7),
			(35, 1),
			(40, 3),
			(50, 3),
			(70, 1),
			(80, 1),
			(85, 1),
			(90, 2),
			(100, 1)
		};

		private bool hasObservedRawHealth;

		private int lastObservedRawHealth;

		private bool hasObservedDamageTimestamp;

		private float lastObservedDamageTimestamp;

		private bool hasObservedCriticalState;

		private bool lastObservedCriticalState;

		private int predictionStartHealth;

		private int predictionUpperStartHealth;

		private float predictionStartTime;

		private bool hasDisplayedHealth;

		private int animationTarget;

		private int animationStartValue;

		private float animationStartTime;

		private float animationDuration;

		public int TargetHealth { get; private set; }

		public int DisplayedHealth { get; private set; }

		public bool IsPredictionActive { get; private set; }

		public bool IsHoldingRecoveredHealth { get; private set; }

		public bool DamageSignalChanged { get; private set; }

		public bool CriticalSignalChanged { get; private set; }

		public bool IsLowHealth
		{
			get
			{
				if (TargetHealth >= 20)
				{
					return DisplayedHealth < 20;
				}
				return true;
			}
		}

		public HealthDisplayModel()
		{
			Reset();
		}

		public static int EstimateHealthAfterCriticalHit(int healthBeforeHit)
		{
			int num = 0;
			int num2 = 0;
			(int, int)[] commonHits = CommonHits;
			for (int i = 0; i < commonHits.Length; i++)
			{
				(int, int) tuple = commonHits[i];
				int item = tuple.Item1;
				int item2 = tuple.Item2;
				int num3 = ((healthBeforeHit - item <= 0 && item < 50) ? 5 : (healthBeforeHit - item));
				if (num3 >= 1 && num3 <= 9)
				{
					num += num3 * item2;
					num2 += item2;
				}
			}
			if (num2 != 0)
			{
				return (int)Math.Round((double)num / (double)num2);
			}
			return 5;
		}

		public int Update(int rawHealth, bool isExact, bool isCritical, float damageTimestamp, float now)
		{
			bool isFirstCriticalObservation = !hasObservedCriticalState && isCritical;
			DamageSignalChanged = ObserveDamageTimestamp(damageTimestamp);
			CriticalSignalChanged = ObserveCriticalState(isCritical);
			TargetHealth = ResolveTarget(rawHealth, isExact, isCritical, isFirstCriticalObservation, now);
			DisplayedHealth = ResolveAnimatedDisplay(TargetHealth, now);
			return DisplayedHealth;
		}

		public void Reset()
		{
			hasObservedRawHealth = false;
			lastObservedRawHealth = int.MinValue;
			hasObservedDamageTimestamp = false;
			lastObservedDamageTimestamp = 0f;
			hasObservedCriticalState = false;
			lastObservedCriticalState = false;
			DamageSignalChanged = false;
			CriticalSignalChanged = false;
			ResetPrediction();
			IsHoldingRecoveredHealth = false;
			ResetDisplay();
		}

		public void ResetDisplay()
		{
			hasDisplayedHealth = false;
			TargetHealth = int.MinValue;
			DisplayedHealth = int.MinValue;
			animationTarget = int.MinValue;
			animationStartValue = 0;
			animationStartTime = 0f;
			animationDuration = 0f;
		}

		private bool ObserveDamageTimestamp(float damageTimestamp)
		{
			if (!hasObservedDamageTimestamp)
			{
				hasObservedDamageTimestamp = true;
				lastObservedDamageTimestamp = damageTimestamp;
				return false;
			}
			if (damageTimestamp == lastObservedDamageTimestamp)
			{
				return false;
			}
			lastObservedDamageTimestamp = damageTimestamp;
			return true;
		}

		private bool ObserveCriticalState(bool isCritical)
		{
			if (!hasObservedCriticalState)
			{
				hasObservedCriticalState = true;
				lastObservedCriticalState = isCritical;
				return false;
			}
			if (isCritical == lastObservedCriticalState)
			{
				return false;
			}
			lastObservedCriticalState = isCritical;
			return true;
		}

		private int ResolveTarget(int rawHealth, bool isExact, bool isCritical, bool isFirstCriticalObservation, float now)
		{
			bool flag = !hasObservedRawHealth || rawHealth != lastObservedRawHealth;
			hasObservedRawHealth = true;
			lastObservedRawHealth = rawHealth;
			if (isExact || rawHealth <= 0)
			{
				ResetPrediction();
				IsHoldingRecoveredHealth = false;
				return rawHealth;
			}
			bool num = CriticalSignalChanged && isCritical;
			bool flag2 = CriticalSignalChanged && !isCritical;
			if (num || (isFirstCriticalObservation && rawHealth > 9))
			{
				BeginCriticalEstimate(flag ? rawHealth : ResolveCurrentEstimate(rawHealth, now), now);
			}
			else if (flag || DamageSignalChanged)
			{
				ResetPrediction();
				IsHoldingRecoveredHealth = false;
			}
			else if (flag2)
			{
				IsHoldingRecoveredHealth = HasHadTimeToRecover(now);
				ResetPrediction();
			}
			if (IsHoldingRecoveredHealth)
			{
				return 20;
			}
			if (!IsPredictionActive)
			{
				if (rawHealth >= 20)
				{
					return rawHealth;
				}
				BeginPrediction(rawHealth, rawHealth, now);
			}
			int num2 = PredictHealth(predictionStartHealth, now);
			if (isCritical)
			{
				return Math.Min(num2, 19);
			}
			if (num2 >= 20)
			{
				ResetPrediction();
				IsHoldingRecoveredHealth = true;
				return 20;
			}
			return num2;
		}

		private int ResolveCurrentEstimate(int rawHealth, float now)
		{
			if (IsHoldingRecoveredHealth)
			{
				return 20;
			}
			if (!IsPredictionActive)
			{
				return rawHealth;
			}
			return PredictHealth(predictionStartHealth, now);
		}

		private void BeginCriticalEstimate(int healthBeforeHit, float now)
		{
			IsHoldingRecoveredHealth = false;
			BeginPrediction(EstimateHealthAfterCriticalHit(healthBeforeHit), 9, now);
		}

		private bool HasHadTimeToRecover(float now)
		{
			if (IsPredictionActive)
			{
				return PredictHealth(predictionUpperStartHealth, now) >= 19;
			}
			return false;
		}

		private int PredictHealth(int startHealth, float now)
		{
			int num = Math.Max(0, (int)Math.Floor((now - predictionStartTime) / 1f));
			return Math.Min(20, startHealth + num);
		}

		private void BeginPrediction(int startHealth, int upperStartHealth, float now)
		{
			IsPredictionActive = true;
			predictionStartHealth = startHealth;
			predictionUpperStartHealth = upperStartHealth;
			predictionStartTime = now;
		}

		private void ResetPrediction()
		{
			IsPredictionActive = false;
			predictionStartHealth = int.MinValue;
			predictionUpperStartHealth = int.MinValue;
			predictionStartTime = 0f;
		}

		private int ResolveAnimatedDisplay(int targetHealth, float now)
		{
			if (!hasDisplayedHealth)
			{
				hasDisplayedHealth = true;
				animationTarget = targetHealth;
				return targetHealth;
			}
			if (targetHealth == DisplayedHealth)
			{
				animationTarget = targetHealth;
				return targetHealth;
			}
			if (targetHealth != animationTarget)
			{
				animationTarget = targetHealth;
				animationStartValue = DisplayedHealth;
				animationStartTime = now;
				animationDuration = Math.Min(0.4f, (float)Math.Abs(targetHealth - DisplayedHealth) * 0.05f);
			}
			float num = Math.Min(1f, Math.Max(0f, (now - animationStartTime) / animationDuration));
			return (int)Math.Round((float)animationStartValue + (float)(targetHealth - animationStartValue) * num);
		}
	}
	internal sealed class InfectionDisplayModel
	{
		private const int MinimumPercent = 1;

		private const int MaximumPercent = 99;

		private const float ExactPredictionWindow = 0.5f;

		private const float MaxSmoothedPredictionWindow = 60f;

		private const float DisplayStepInterval = 0.1f;

		private bool isPredictionActive;

		private float lastObservedMeter;

		private float lastObservedSampleTime;

		private float predictionStartMeter;

		private float predictionStartTime;

		private float predictionRate;

		private float predictionWindow;

		private bool hasDisplayedPercent;

		private int displayedPercent;

		private float nextDisplayStepTime;

		public int Update(float rawMeter, bool isExact, float now)
		{
			rawMeter = Clamp01(rawMeter);
			if (!isPredictionActive)
			{
				BeginPrediction(rawMeter, 0f, 0.5f, now);
				lastObservedMeter = rawMeter;
				lastObservedSampleTime = now;
				return ResolveSteppedPercent(ToPercent(rawMeter), canDrop: true, now);
			}
			bool flag = rawMeter < lastObservedMeter;
			if (flag)
			{
				BeginPrediction(rawMeter, 0f, 0.5f, now);
				lastObservedSampleTime = now;
			}
			else if (rawMeter > lastObservedMeter)
			{
				float num = Math.Max(0.5f, now - lastObservedSampleTime);
				float window = (isExact ? 0.5f : Math.Min(60f, num));
				BeginPrediction(rawMeter, (rawMeter - lastObservedMeter) / num, window, now);
				lastObservedSampleTime = now;
			}
			lastObservedMeter = rawMeter;
			return ResolveSteppedPercent(ToPercent(PredictMeter(now)), isExact || flag, now);
		}

		public void Reset()
		{
			isPredictionActive = false;
			lastObservedMeter = 0f;
			lastObservedSampleTime = 0f;
			predictionStartMeter = 0f;
			predictionStartTime = 0f;
			predictionRate = 0f;
			predictionWindow = 0f;
			hasDisplayedPercent = false;
			displayedPercent = int.MinValue;
			nextDisplayStepTime = 0f;
		}

		private void BeginPrediction(float rawMeter, float rate, float window, float now)
		{
			isPredictionActive = true;
			predictionStartMeter = rawMeter;
			predictionStartTime = now;
			predictionRate = rate;
			predictionWindow = window;
		}

		private float PredictMeter(float now)
		{
			float num = Math.Min(now - predictionStartTime, predictionWindow);
			return Clamp01(predictionStartMeter + predictionRate * num);
		}

		private int ResolveSteppedPercent(int targetPercent, bool canDrop, float now)
		{
			if (!hasDisplayedPercent)
			{
				hasDisplayedPercent = true;
				displayedPercent = targetPercent;
				nextDisplayStepTime = now + 0.1f;
				return displayedPercent;
			}
			if (targetPercent < displayedPercent)
			{
				if (canDrop)
				{
					displayedPercent = targetPercent;
					nextDisplayStepTime = now + 0.1f;
				}
				return displayedPercent;
			}
			if (now < nextDisplayStepTime)
			{
				return displayedPercent;
			}
			displayedPercent = Math.Min(targetPercent, displayedPercent + 1);
			nextDisplayStepTime = now + 0.1f;
			return displayedPercent;
		}

		private static int ToPercent(float meter)
		{
			int val = (int)Math.Floor(Clamp01(meter) * 100f);
			return Math.Min(99, Math.Max(1, val));
		}

		private static float Clamp01(float value)
		{
			return Math.Min(1f, Math.Max(0f, value));
		}
	}
	internal sealed class PlayerStatusBarStrip : MonoBehaviour
	{
		internal enum StripType
		{
			Health,
			Infection
		}

		private const float Width = 172f;

		private const float Height = 20f;

		private bool visible = true;

		private float currentValue;

		private float maxValue = 1f;

		private bool renderAsPercent;

		private StripType stripType;

		private RectTransform rectTransform;

		private RectTransform fillRect;

		private Image backgroundImage;

		private Image fillImage;

		private TextMeshProUGUI text;

		private Outline border;

		private bool dirty = true;

		private bool lastAppliedVisible = true;

		private float lastAppliedNormalized = -1f;

		private string lastAppliedText = string.Empty;

		private bool lastAppliedTextEnabled = true;

		private Color lastAppliedBackgroundColor = Color.clear;

		private Color lastAppliedFillColor = Color.clear;

		private Color lastAppliedBorderColor = Color.clear;

		private int lastAppliedSettingsRevision = -1;

		private bool hasFillColorOverride;

		private Color fillColorOverride;

		public void Initialize(StripType type)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Expected O, but got Unknown
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: 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_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Expected O, but got Unknown
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			stripType = type;
			rectTransform = ((Component)this).gameObject.GetComponent<RectTransform>();
			if ((Object)(object)rectTransform == (Object)null)
			{
				rectTransform = ((Component)this).gameObject.AddComponent<RectTransform>();
			}
			rectTransform.sizeDelta = new Vector2(172f, 20f);
			rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
			rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
			rectTransform.pivot = new Vector2(0.5f, 0.5f);
			GameObject val = new GameObject("Background", new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(Outline)
			});
			val.transform.SetParent(((Component)this).transform, false);
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			component.offsetMin = Vector2.zero;
			component.offsetMax = Vector2.zero;
			backgroundImage = val.GetComponent<Image>();
			((Graphic)backgroundImage).raycastTarget = false;
			border = val.GetComponent<Outline>();
			GameObject val2 = new GameObject("Fill", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			val2.transform.SetParent(val.transform, false);
			fillRect = val2.GetComponent<RectTransform>();
			fillRect.anchorMin = Vector2.zero;
			fillRect.anchorMax = Vector2.one;
			fillRect.pivot = new Vector2(0f, 0.5f);
			fillRect.offsetMin = new Vector2(1f, 1f);
			fillRect.offsetMax = new Vector2(-1f, -1f);
			((Transform)fillRect).localScale = Vector3.one;
			fillImage = val2.GetComponent<Image>();
			((Graphic)fillImage).raycastTarget = false;
			fillImage.type = (Type)0;
			GameObject val3 = new GameObject("Text", new Type[2]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI)
			});
			val3.transform.SetParent(val.transform, false);
			RectTransform component2 = val3.GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			component2.offsetMin = new Vector2(6f, 1f);
			component2.offsetMax = new Vector2(-6f, -1f);
			text = val3.GetComponent<TextMeshProUGUI>();
			((TMP_Text)text).alignment = (TextAlignmentOptions)514;
			((TMP_Text)text).fontSize = 12f;
			((TMP_Text)text).enableWordWrapping = false;
			((Graphic)text).color = Color.white;
			((Graphic)text).raycastTarget = false;
		}

		public void SetDisplay(float value, float max, bool showPercent)
		{
			float num = Mathf.Clamp(value, 0f, (max <= 0f) ? 1f : max);
			float num2 = Mathf.Max(max, 0.0001f);
			if (Mathf.Approximately(currentValue, num) && Mathf.Approximately(maxValue, num2) && renderAsPercent == showPercent)
			{
				visible = true;
				return;
			}
			currentValue = num;
			maxValue = num2;
			renderAsPercent = showPercent;
			visible = true;
			dirty = true;
		}

		public void SetVisible(bool isVisible)
		{
			if (visible != isVisible || ((Component)this).gameObject.activeSelf != isVisible)
			{
				visible = isVisible;
				((Component)this).gameObject.SetActive(isVisible);
				dirty = true;
			}
		}

		public void SetFillColorOverride(bool enabled, Color color)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (hasFillColorOverride != enabled || (enabled && !(fillColorOverride == color)))
			{
				hasFillColorOverride = enabled;
				fillColorOverride = color;
				dirty = true;
			}
		}

		public void ApplyIfDirty(StatusBarConfig settings)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: 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_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: 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_01dc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)backgroundImage == (Object)null || (Object)(object)fillImage == (Object)null || (Object)(object)this.text == (Object)null || !visible || (!dirty && settings.Revision == lastAppliedSettingsRevision))
			{
				return;
			}
			float num = Mathf.Clamp01(currentValue / maxValue);
			Color val = ((stripType == StripType.Health) ? settings.GetHealthFillColor() : settings.GetInfectionFillColor());
			if (hasFillColorOverride)
			{
				val = fillColorOverride;
			}
			Color backgroundColor = settings.GetBackgroundColor();
			Color borderColor = settings.GetBorderColor();
			bool showText = settings.ShowText;
			string text = string.Empty;
			if (showText)
			{
				text = (renderAsPercent ? StatusBarTextCache.GetInfectionLabel(Mathf.RoundToInt(num * 100f)) : StatusBarTextCache.GetHealthLabel(Mathf.RoundToInt(currentValue), Mathf.RoundToInt(maxValue)));
			}
			bool flag = backgroundColor != lastAppliedBackgroundColor || val != lastAppliedFillColor || borderColor != lastAppliedBorderColor;
			bool flag2 = !Mathf.Approximately(num, lastAppliedNormalized);
			bool flag3 = showText != lastAppliedTextEnabled;
			bool flag4 = text != lastAppliedText;
			bool flag5 = visible != lastAppliedVisible;
			if (dirty || flag || flag2 || flag3 || flag4 || flag5)
			{
				if (flag)
				{
					((Graphic)backgroundImage).color = backgroundColor;
					((Shadow)border).effectColor = borderColor;
					((Shadow)border).effectDistance = new Vector2(1f, -1f);
					((Graphic)fillImage).color = val;
					lastAppliedBackgroundColor = backgroundColor;
					lastAppliedFillColor = val;
					lastAppliedBorderColor = borderColor;
				}
				if (dirty || flag2)
				{
					((Transform)fillRect).localScale = new Vector3(num, 1f, 1f);
					lastAppliedNormalized = num;
				}
				if (dirty || flag3)
				{
					((Behaviour)this.text).enabled = showText;
					lastAppliedTextEnabled = showText;
				}
				if (showText && (dirty || flag4))
				{
					((TMP_Text)this.text).text = text;
					lastAppliedText = text;
				}
				lastAppliedVisible = visible;
				lastAppliedSettingsRevision = settings.Revision;
				dirty = false;
			}
		}
	}
	internal sealed class PlayerStatusBarView : MonoBehaviour
	{
		private static readonly Color CriticalHealthBarColor = new Color(0.88f, 0.15f, 0.12f, 0.95f);

		private const float InfectionStripYOffset = 25f;

		private const float SegmentedStripGap = 1.25f;

		private const float SegmentedPanelPadding = 1.67f;

		private const float SegmentedPanelBrightness = 1.25f;

		private const float DebugVisibilityLogInterval = 5f;

		private readonly HealthDisplayModel healthDisplay = new HealthDisplayModel();

		private readonly InfectionDisplayModel infectionDisplay = new InfectionDisplayModel();

		private readonly BarValueAnimation healthBarAnimation = new BarValueAnimation();

		private readonly BarValueAnimation infectionBarAnimation = new BarValueAnimation();

		private readonly BarPositionStabilizer positionStabilizer = new BarPositionStabilizer();

		private PlayerControllerB targetPlayer;

		private Transform? anchor;

		private ulong boundActualClientId = ulong.MaxValue;

		private int boundPlayerKey = -1;

		private PlayerStatusBarStrip healthStrip;

		private PlayerStatusBarStrip infectionStrip;

		private StatusBarConfig.BarStyle appliedBarStyle;

		private SegmentedBarStrip? segmentedHealthStrip;

		private SegmentedBarStrip? segmentedInfectionStrip;

		private Image? segmentedPanel;

		private bool isSegmentedPanelLaidOut;

		private bool lastSegmentedPanelShowedInfection;

		private int lastSegmentedPanelSettingsRevision = -1;

		private Canvas worldCanvas;

		private RectTransform canvasRect;

		private int lastDisplayedHealth = int.MinValue;

		private int lastDisplayedInfectionPercent = int.MinValue;

		private bool wasHealthVisible;

		private bool wasInfectionVisible;

		private bool lastCanvasEnabled = true;

		private Camera? lastAppliedCanvasCamera;

		private float lastAppliedUiScale = -1f;

		private bool wasTargetDead;

		private bool wasUsingSnapshot;

		private string lastDebugVisibilityState = string.Empty;

		private float nextDebugVisibilityLogTime;

		private int lastDebugRawHealth = int.MinValue;

		private bool lastDebugRawHealthExact;

		private bool lastDebugRawCriticalState;

		public int PlayerId
		{
			get
			{
				if (boundPlayerKey < 0)
				{
					if (!((Object)(object)targetPlayer != (Object)null))
					{
						return -1;
					}
					return (int)targetPlayer.playerClientId;
				}
				return boundPlayerKey;
			}
		}

		private bool IsSegmented => appliedBarStyle == StatusBarConfig.BarStyle.Segmented;

		public bool Initialize(PlayerControllerB player, int playerKey)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			worldCanvas = ((Component)this).gameObject.AddComponent<Canvas>();
			worldCanvas.renderMode = (RenderMode)2;
			worldCanvas.overrideSorting = true;
			worldCanvas.sortingOrder = 50;
			canvasRect = ((Component)worldCanvas).GetComponent<RectTransform>();
			canvasRect.sizeDelta = new Vector2(220f, 84f);
			healthStrip = CreateStrip("HealthStrip", PlayerStatusBarStrip.StripType.Health, 0f);
			infectionStrip = CreateStrip("InfectionStrip", PlayerStatusBarStrip.StripType.Infection, 25f);
			Bind(player, playerKey);
			return true;
		}

		internal void Bind(PlayerControllerB player, int playerKey)
		{
			targetPlayer = player;
			boundPlayerKey = playerKey;
			boundActualClientId = player.actualClientId;
			anchor = ResolveAnchor(player);
			ResetRuntimeState();
			((Object)this).name = $"PlayerStatusBar_{player.playerClientId}";
			((Component)this).gameObject.SetActive(true);
		}

		internal void Release()
		{
			if ((Object)(object)worldCanvas != (Object)null)
			{
				SetCanvasEnabled(enabled: false);
			}
			if ((Object)(object)healthStrip != (Object)null && (Object)(object)infectionStrip != (Object)null)
			{
				HideStrips();
			}
			targetPlayer = null;
			anchor = null;
			boundPlayerKey = -1;
			boundActualClientId = ulong.MaxValue;
			ResetRuntimeState();
			((Component)this).gameObject.SetActive(false);
		}

		public bool IsStillValid(PlayerControllerB localPlayer)
		{
			if ((Object)(object)targetPlayer == (Object)null || (Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			if ((Object)(object)targetPlayer == (Object)(object)localPlayer)
			{
				return false;
			}
			if (!targetPlayer.disconnectedMidGame)
			{
				return ((Component)targetPlayer).gameObject.activeInHierarchy;
			}
			return false;
		}

		public bool MatchesPlayer(PlayerControllerB player, PlayerControllerB localPlayer, int playerKey)
		{
			if (IsStillValid(localPlayer) && (Object)(object)targetPlayer == (Object)(object)player && boundPlayerKey == playerKey)
			{
				return boundActualClientId == player.actualClientId;
			}
			return false;
		}

		internal void Tick(in PlayerStatusFrameContext context)
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)targetPlayer == (Object)null)
			{
				return;
			}
			StatusBarConfig settings = context.Settings;
			ApplyBarStyle(settings.Style);
			PlayerStatusSnapshot snapshot;
			bool flag = PlayerStatusSnapshotSync.TryGetSnapshot(targetPlayer, out snapshot);
			HandleSnapshotSourceTransition(flag);
			bool targetIsDead = ((!flag) ? (targetPlayer.isPlayerDead || PlayerStatusSnapshotSync.SlotInheritance.ResolveHealth(boundPlayerKey, targetPlayer.health) <= 0) : (snapshot.IsDead || snapshot.Health <= 0));
			HandleDeathStateTransition(targetIsDead);
			if (!IsTargetReadyForDisplay(targetIsDead))
			{
				HideAll(settings, context.ViewCamera);
				return;
			}
			float unscaledTime = Time.unscaledTime;
			OwnerStatusReport report;
			bool hasOwnerReport = PlayerStatusSnapshotSync.TryGetOwnerReport(targetPlayer, boundPlayerKey, out report);
			int displayHealth = ResolveDisplayHealth(flag, in snapshot, hasOwnerReport, in report, unscaledTime);
			if (anchor == null)
			{
				anchor = ResolveAnchor(targetPlayer);
			}
			Vector3 val = (((Object)(object)anchor != (Object)null) ? anchor.position : ((Component)targetPlayer).transform.position);
			((Component)this).transform.position = ResolveStablePosition(val + Vector3.up * context.AnchorYOffset, context.ViewCamera);
			if (context.HasBillboardRotation)
			{
				((Component)this).transform.rotation = context.BillboardRotation;
			}
			ApplyCanvasCamera(context.ViewCamera);
			ApplyCanvasScale(context.UiScale);
			if (!ShouldShowForDistance(in context, out var distanceSqr))
			{
				HideForDistance(context.ViewCamera, distanceSqr);
				return;
			}
			UpdateHealthStrip(displayHealth, unscaledTime);
			LogDebugVisibility("visible", context.ViewCamera, distanceSqr);
			bool showInfection = UpdateInfectionStrip(flag, in snapshot, hasOwnerReport, in report, unscaledTime);
			UpdateSegmentedPanel(settings, showInfection);
			SetCanvasEnabled(enabled: true);
			ApplyStripsIfDirty(settings);
			wasHealthVisible = true;
			wasInfectionVisible = showInfection;
		}

		private Vector3 ResolveStablePosition(Vector3 targetPosition, Camera? viewCamera)
		{
			//IL_0006: 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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: 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)
			positionStabilizer.Update(targetPosition.x, targetPosition.y, targetPosition.z);
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(positionStabilizer.X, positionStabilizer.Y, positionStabilizer.Z);
			if ((Object)(object)viewCamera == (Object)null)
			{
				return val;
			}
			Vector3 val2 = viewCamera.WorldToScreenPoint(val);
			if (val2.z <= 0f)
			{
				return val;
			}
			val2.x = Mathf.Round(val2.x);
			val2.y = Mathf.Round(val2.y);
			return viewCamera.ScreenToWorldPoint(val2);
		}

		private int ResolveDisplayHealth(bool hasSyncedSnapshot, in PlayerStatusSnapshot syncedSnapshot, bool hasOwnerReport, in OwnerStatusReport ownerReport, float now)
		{
			bool isExact;
			bool isCritical;
			int num;
			if (hasSyncedSnapshot)
			{
				num = syncedSnapshot.Health;
				isExact = syncedSnapshot.IsHealthExact;
				isCritical = syncedSnapshot.IsCritical;
			}
			else
			{
				num = (hasOwnerReport ? ownerReport.Health : PlayerStatusSnapshotSync.SlotInheritance.ResolveHealth(boundPlayerKey, targetPlayer.health));
				isExact = hasOwnerReport;
				isCritical = targetPlayer.criticallyInjured;
			}
			num = (lastDebugRawHealth = Mathf.Clamp(num, 0, 100));
			lastDebugRawHealthExact = isExact;
			lastDebugRawCriticalState = isCritical;
			return healthDisplay.Update(num, isExact, isCritical, targetPlayer.timeSinceTakingDamage, now);
		}

		private void UpdateHealthStrip(int displayHealth, float now)
		{
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (IsSegmented && (Object)(object)segmentedHealthStrip != (Object)null)
			{
				healthBarAnimation.Update(healthDisplay.TargetHealth, now);
				string numberLabel = StatusBarTextCache.GetNumberLabel(Mathf.RoundToInt(healthBarAnimation.Fill));
				segmentedHealthStrip.SetFillColorOverride(healthDisplay.IsLowHealth, StatusBarConfig.SegmentedCriticalColor);
				segmentedHealthStrip.SetValues(healthBarAnimation.Fill, healthBarAnimation.Trail, healthBarAnimation.Target, numberLabel);
				segmentedHealthStrip.SetVisible(isVisible: true);
				return;
			}
			healthStrip.SetFillColorOverride(healthDisplay.IsLowHealth, CriticalHealthBarColor);
			if (!wasHealthVisible || displayHealth != lastDisplayedHealth)
			{
				healthStrip.SetDisplay(displayHealth, 100f, showPercent: false);
				lastDisplayedHealth = displayHealth;
			}
			healthStrip.SetVisible(isVisible: true);
		}

		private bool UpdateInfectionStrip(bool hasSyncedSnapshot, in PlayerStatusSnapshot syncedSnapshot, bool hasOwnerReport, in OwnerStatusReport ownerReport, float now)
		{
			if (!PlayerStatusSnapshotSync.IsInfectionDisplayAllowed)
			{
				HideInfectionStrip();
				return false;
			}
			bool flag;
			bool isExact;
			float infectionMeter;
			if (hasSyncedSnapshot && syncedSnapshot.IsInfectionKnown)
			{
				flag = syncedSnapshot.HasInfection;
				isExact = syncedSnapshot.IsInfectionExact;
				infectionMeter = syncedSnapshot.InfectionMeter;
			}
			else if (hasOwnerReport && ownerReport.IsInfectionKnown)
			{
				flag = ownerReport.HasInfection;
				isExact = true;
				infectionMeter = ownerReport.InfectionMeter;
			}
			else
			{
				flag = CadaverGrowthInfectionProvider.TryGetNormalizedInfection(targetPlayer, boundPlayerKey, out infectionMeter);
				isExact = false;
			}
			if (!flag)
			{
				if (TryShowInfectionClearing(now))
				{
					return true;
				}
				HideInfectionStrip();
				return false;
			}
			int num = infectionDisplay.Update(infectionMeter, isExact, now);
			if (IsSegmented && (Object)(object)segmentedInfectionStrip != (Object)null)
			{
				infectionBarAnimation.Update(num, now);
				float fill = infectionBarAnimation.Fill;
				segmentedInfectionStrip.SetValues(fill, infectionBarAnimation.Trail, fill, StatusBarTextCache.GetPercentLabel(Mathf.RoundToInt(fill)));
				segmentedInfectionStrip.SetVisible(isVisible: true);
				return true;
			}
			if (!wasInfectionVisible || num != lastDisplayedInfectionPercent)
			{
				infectionStrip.SetDisplay(num, 100f, showPercent: true);
				lastDisplayedInfectionPercent = num;
			}
			infectionStrip.SetVisible(isVisible: true);
			return true;
		}

		private bool TryShowInfectionClearing(float now)
		{
			if (!IsSegmented || (Object)(object)segmentedInfectionStrip == (Object)null || !wasInfectionVisible)
			{
				return false;
			}
			infectionBarAnimation.Update(0, now);
			if (infectionBarAnimation.Trail <= 0f)
			{
				return false;
			}
			segmentedInfectionStrip.SetValues(0f, infectionBarAnimation.Trail, 0f, StatusBarTextCache.GetPercentLabel(0));
			return true;
		}

		private void HideInfectionStrip()
		{
			infectionStrip.SetVisible(isVisible: false);
			segmentedInfectionStrip?.SetVisible(isVisible: false);
			ResetInfectionDisplay();
		}

		private void HideForDistance(Camera? viewCamera, float distanceSqr)
		{
			LogDebugVisibility("distance-hidden", viewCamera, distanceSqr);
			SetCanvasEnabled(enabled: false);
			HideStrips();
			if (wasHealthVisible)
			{
				healthDisplay.ResetDisplay();
				healthBarAnimation.Reset();
			}
			if (wasInfectionVisible)
			{
				ResetInfectionDisplay();
			}
			wasHealthVisible = false;
			wasInfectionVisible = false;
		}

		private void HideAll(StatusBarConfig settings, Camera? viewCamera)
		{
			LogDebugVisibility("target-not-ready", viewCamera, -1f);
			SetCanvasEnabled(enabled: false);
			HideStrips();
			wasHealthVisible = false;
			wasInfectionVisible = false;
			ResetInfectionDisplay();
			ApplyStripsIfDirty(settings);
		}

		private void HideStrips()
		{
			healthStrip.SetVisible(isVisible: false);
			infectionStrip.SetVisible(isVisible: false);
			segmentedHealthStrip?.SetVisible(isVisible: false);
			segmentedInfectionStrip?.SetVisible(isVisible: false);
		}

		private void ApplyBarStyle(StatusBarConfig.BarStyle style)
		{
			if (style != appliedBarStyle)
			{
				appliedBarStyle = style;
				if (IsSegmented && (Object)(object)segmentedPanel == (Object)null)
				{
					CreateSegmentedBars();
				}
				HideStrips();
				Image? obj = segmentedPanel;
				if (obj != null)
				{
					((Component)obj).gameObject.SetActive(IsSegmented);
				}
				wasHealthVisible = false;
				wasInfectionVisible = false;
				lastDisplayedHealth = int.MinValue;
				lastDisplayedInfectionPercent = int.MinValue;
				healthBarAnimation.Reset();
				infectionBarAnimation.Reset();
			}
		}

		private void CreateSegmentedBars()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			GameObject val = new GameObject("SegmentedPanel", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			val.transform.SetParent(((Component)worldCanvas).transform, false);
			val.transform.SetAsFirstSibling();
			segmentedPanel = val.GetComponent<Image>();
			((Graphic)segmentedPanel).raycastTarget = false;
			segmentedHealthStrip = CreateSegmentedStrip("SegmentedHealthStrip", "HP", isInfection: false, 0f);
			segmentedInfectionStrip = CreateSegmentedStrip("SegmentedInfectionStrip", "INF", isInfection: true, 14.25f);
		}

		private SegmentedBarStrip CreateSegmentedStrip(string stripName, string label, bool isInfection, float yOffset)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(stripName, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(((Component)worldCanvas).transform, false);
			SegmentedBarStrip segmentedBarStrip = val.AddComponent<SegmentedBarStrip>();
			segmentedBarStrip.Initialize(label, isInfection);
			((Component)segmentedBarStrip).GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, yOffset);
			return segmentedBarStrip;
		}

		private void UpdateSegmentedPanel(StatusBarConfig settings, bool showInfection)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			if (IsSegmented && !((Object)(object)segmentedPanel == (Object)null))
			{
				if (settings.Revision != lastSegmentedPanelSettingsRevision)
				{
					lastSegmentedPanelSettingsRevision = settings.Revision;
					Color color = settings.GetBackgroundColor() * 1.25f;
					color.a = 1f;
					((Graphic)segmentedPanel).color = color;
				}
				if (!isSegmentedPanelLaidOut || showInfection != lastSegmentedPanelShowedInfection)
				{
					isSegmentedPanelLaidOut = true;
					lastSegmentedPanelShowedInfection = showInfection;
					float num = (showInfection ? 14.25f : 0f);
					RectTransform rectTransform = ((Graphic)segmentedPanel).rectTransform;
					rectTransform.sizeDelta = new Vector2(175.34f, 13f + num + 3.34f);
					rectTransform.anchoredPosition = new Vector2(0f, num * 0.5f);
				}
			}
		}

		private bool IsTargetReadyForDisplay(bool targetIsDead)
		{
			if ((Object)(object)targetPlayer != (Object)null && !targetPlayer.disconnectedMidGame && !targetIsDead)
			{
				return ((Component)targetPlayer).gameObject.activeInHierarchy;
			}
			return false;
		}

		private void ResetRuntimeState()
		{
			wasHealthVisible = false;
			wasInfectionVisible = false;
			wasTargetDead = false;
			wasUsingSnapshot = false;
			lastAppliedCanvasCamera = null;
			lastAppliedUiScale = -1f;
			lastDebugVisibilityState = string.Empty;
			nextDebugVisibilityLogTime = 0f;
			lastDebugRawHealth = int.MinValue;
			lastDebugRawHealthExact = false;
			lastDebugRawCriticalState = false;
			positionStabilizer.Reset();
			ResetStatusDisplay();
		}

		private void ResetStatusDisplay()
		{
			lastDisplayedHealth = int.MinValue;
			healthDisplay.Reset();
			healthBarAnimation.Reset();
			ResetInfectionDisplay();
		}

		private void ResetInfectionDisplay()
		{
			lastDisplayedInfectionPercent = int.MinValue;
			infectionDisplay.Reset();
			infectionBarAnimation.Reset();
		}

		private void HandleSnapshotSourceTransition(bool hasSyncedSnapshot)
		{
			if (wasUsingSnapshot != hasSyncedSnapshot)
			{
				wasUsingSnapshot = hasSyncedSnapshot;
				ResetStatusDisplay();
			}
		}

		private void HandleDeathStateTransition(bool targetIsDead)
		{
			if (wasTargetDead != targetIsDead)
			{
				wasTargetDead = targetIsDead;
				ResetStatusDisplay();
			}
		}

		private void ApplyCanvasScale(float uiScale)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (!Mathf.Approximately(lastAppliedUiScale, uiScale))
			{
				((Transform)canvasRect).localScale = Vector3.one * uiScale;
				lastAppliedUiScale = uiScale;
			}
		}

		private void ApplyStripsIfDirty(StatusBarConfig settings)
		{
			healthStrip.ApplyIfDirty(settings);
			infectionStrip.ApplyIfDirty(settings);
			segmentedHealthStrip?.ApplyIfDirty(settings);
			segmentedInfectionStrip?.ApplyIfDirty(settings);
		}

		private void SetCanvasEnabled(bool enabled)
		{
			if (lastCanvasEnabled != enabled || ((Behaviour)worldCanvas).enabled != enabled)
			{
				((Behaviour)worldCanvas).enabled = enabled;
				lastCanvasEnabled = enabled;
			}
		}

		private void ApplyCanvasCamera(Camera? viewCamera)
		{
			if (!((Object)(object)lastAppliedCanvasCamera == (Object)(object)viewCamera) || !((Object)(object)worldCanvas.worldCamera == (Object)(object)viewCamera))
			{
				worldCanvas.worldCamera = viewCamera;
				lastAppliedCanvasCamera = viewCamera;
			}
		}

		private PlayerStatusBarStrip CreateStrip(string stripName, PlayerStatusBarStrip.StripType stripType, float yOffset)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(stripName, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(((Component)worldCanvas).transform, false);
			val.transform.localPosition = Vector3.zero;
			val.transform.localRotation = Quaternion.identity;
			PlayerStatusBarStrip playerStatusBarStrip = val.AddComponent<PlayerStatusBarStrip>();
			playerStatusBarStrip.Initialize(stripType);
			((Component)playerStatusBarStrip).GetComponent<RectTransform>().anchoredPosition = new Vector2(0f, yOffset);
			return playerStatusBarStrip;
		}

		private bool ShouldShowForDistance(in PlayerStatusFrameContext context, out float distanceSqr)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//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_001b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = context.ObserverPosition - ((Component)targetPlayer).transform.position;
			distanceSqr = ((Vector3)(ref val)).sqrMagnitude;
			return distanceSqr <= context.MaxDistanceSqr;
		}

		private static Transform? ResolveAnchor(PlayerControllerB player)
		{
			if ((Object)(object)player.playerGlobalHead != (Object)null)
			{
				return player.playerGlobalHead;
			}
			if (player.bodyParts != null && player.bodyParts.Length != 0 && (Object)(object)player.bodyParts[0] != (Object)null)
			{
				return player.bodyParts[0];
			}
			return ((Component)player).transform;
		}

		private void LogDebugVisibility(string state, Camera? viewCamera, float distanceSqr)
		{
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings.DebugLogging && (!(state == lastDebugVisibilityState) || !(Time.unscaledTime < nextDebugVisibilityLogTime)))
			{
				lastDebugVisibilityState = state;
				nextDebugVisibilityLogTime = Time.unscaledTime + 5f;
				string text = FormatDebugDistance(distanceSqr);
				Plugin.LogDebug(string.Format("View playerId={0} clientId={1} actualClientId={2} boundActualClientId={3} snapshot={4} name='{5}' state={6} health={7} rawHealth={8} rawHealthExact={9} displayHealthTarget={10} displayHealth={11} rawCritical={12} damageSignalChanged={13} criticalSignalChanged={14} predictionActive={15} naturalHold={16} controlled={17} dead={18} distance={19} canvasEnabled={20} camera='{21}' anchor='{22}' position={23} target={24}.", PlayerId, targetPlayer.playerClientId, targetPlayer.actualClientId, boundActualClientId, wasUsingSnapshot, targetPlayer.playerUsername, state, targetPlayer.health, lastDebugRawHealth, lastDebugRawHealthExact, healthDisplay.TargetHealth, healthDisplay.DisplayedHealth, lastDebugRawCriticalState, healthDisplay.DamageSignalChanged, healthDisplay.CriticalSignalChanged, healthDisplay.IsPredictionActive, healthDisplay.IsHoldingRecoveredHealth, targetPlayer.isPlayerControlled, targetPlayer.isPlayerDead, text, ((Behaviour)worldCanvas).enabled, ((Object)(object)viewCamera != (Object)null) ? ((Object)viewCamera).name : "none", ((Object)(object)anchor != (Object)null) ? ((Object)anchor).name : "none", FormatVector(((Component)this).transform.position), FormatVector(((Component)targetPlayer).transform.position)));
			}
		}

		private static string FormatDebugDistance(float distanceSqr)
		{
			if (!(distanceSqr >= 0f))
			{
				return "n/a";
			}
			return $"{Mathf.Sqrt(distanceSqr):0.0}/{Plugin.Settings.MaxDistance:0.0}";
		}

		private static string FormatVector(Vector3 value)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			return $"({value.x:0.0},{value.y:0.0},{value.z:0.0})";
		}
	}
	internal sealed class SegmentedBarStrip : MonoBehaviour
	{
		public const float Width = 172f;

		public const float Height = 13f;

		private const int TextureScale = 12;

		private const int FrameTexels = 5;

		private const int BevelTexels = 10;

		private const float FrameThickness = 5f / 12f;

		private const int CellCount = 10;

		private const float EndCellWidth = 0.1f;

		private const float FontSize = 8f;

		private const string TextShaderName = "TextMeshPro/Distance Field";

		private const float TextOutlineWidth = 0.3f;

		private const float TextOutlineSoftness = 0.12f;

		private const float LightenBlend = 0.55f;

		private static readonly Color TrackColor = new Color(1f, 1f, 1f, 0.035f);

		private static readonly Color HealthTrailColor = new Color(0.85f, 0.83f, 0.77f, 1f);

		private static readonly Color FrameColor = new Color(0.34f, 0.36f, 0.4f, 1f);

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

		private static readonly Color AnimatedBevelBottomRight = new Color(1f, 1f, 1f, 0.06f);

		private static readonly Color EmptyBevelTopLeft = new Color(1f, 1f, 1f, 0.07f);

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

		private static readonly Color TextOutlineColor = new Color(0.03f, 0.04f, 0.05f, 1f);

		private static Texture2D? frameTexture;

		private static readonly Dictionary<Color, (Texture2D Bevel, Texture2D EndEdge)> filledBevelTextures = new Dictionary<Color, (Texture2D, Texture2D)>();

		private static Texture2D? emptyLightEdgesTexture;

		private static Texture2D? emptyDarkEdgesTexture;

		private static Texture2D? animatedDarkEdgesTexture;

		private static Texture2D? animatedLightEdgesTexture;

		private static Material? outlinedTextMaterial;

		private static Material? outlinedTextBaseMaterial;

		private bool usesInfectionColor;

		private RectTransform fillRect;

		private RectTransform trailRect;

		private RectTransform previewRect;

		private Image fillImage;

		private Image trailImage;

		private Image previewImage;

		private RawImage filledBevel;

		private RawImage animatedDarkEdges;

		private RawImage animatedLightEdges;

		private RawImage fillEndEdge;

		private TextMeshProUGUI labelText;

		private TextMeshProUGUI valueText;

		private float fill = -1f;

		private float trail = -1f;

		private float preview = -1f;

		private string value = string.Empty;

		private bool hasFillColorOverride;

		private Color fillColorOverride;

		private bool dirty = true;

		private int lastAppliedSettingsRevision