Decompiled source of PingHud v1.2.0

BepInEx\plugins\ValheimPingHud.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ValheimPingHud;

internal enum HudLanguage
{
	English,
	Chinese,
	TraditionalChinese
}
internal sealed class HudStrings
{
	public const string AutoSetting = "auto";

	public readonly HudLanguage Language;

	public readonly string PingLabel;

	public readonly string LossLabel;

	public readonly string JitterLabel;

	public readonly string QualityLabel;

	public readonly string DownLabel;

	public readonly string UpLabel;

	public readonly string OfflineLabel;

	public readonly string UnknownValue;

	public readonly string NotAvailableValue;

	private static readonly HudStrings Chinese = new HudStrings(HudLanguage.Chinese);

	private static readonly HudStrings Traditional = new HudStrings(HudLanguage.TraditionalChinese);

	private static readonly HudStrings English = new HudStrings(HudLanguage.English);

	private HudStrings(HudLanguage language)
	{
		Language = language;
		UnknownValue = "--";
		NotAvailableValue = "N/A";
		switch (language)
		{
		case HudLanguage.Chinese:
			PingLabel = "延迟";
			LossLabel = "丢包";
			JitterLabel = "抖动";
			QualityLabel = "质量";
			DownLabel = "下行";
			UpLabel = "上行";
			OfflineLabel = "未连接";
			break;
		case HudLanguage.TraditionalChinese:
			PingLabel = "延遲";
			LossLabel = "丟包";
			JitterLabel = "抖動";
			QualityLabel = "品質";
			DownLabel = "下行";
			UpLabel = "上行";
			OfflineLabel = "未連線";
			break;
		default:
			PingLabel = "Ping";
			LossLabel = "Loss";
			JitterLabel = "Jitter";
			QualityLabel = "Quality";
			DownLabel = "Down";
			UpLabel = "Up";
			OfflineLabel = "Not connected";
			break;
		}
	}

	public static HudLanguage ResolveLanguage(string setting)
	{
		if (!Matches(setting, "traditional"))
		{
			switch (setting)
			{
			default:
				if (Matches(setting, "chinese_trad"))
				{
					break;
				}
				if (!Matches(setting, "chinese"))
				{
					switch (setting)
					{
					case "中文":
					case "简体中文":
					case "簡體中文":
						break;
					default:
						if (Matches(setting, "english") || setting == "英文")
						{
							return HudLanguage.English;
						}
						try
						{
							Localization instance = Localization.instance;
							if (instance != null)
							{
								string selectedLanguage = instance.GetSelectedLanguage();
								if (!string.IsNullOrEmpty(selectedLanguage))
								{
									if (selectedLanguage.IndexOf("trad", StringComparison.OrdinalIgnoreCase) >= 0)
									{
										return HudLanguage.TraditionalChinese;
									}
									if (selectedLanguage.IndexOf("chin", StringComparison.OrdinalIgnoreCase) >= 0)
									{
										return HudLanguage.Chinese;
									}
								}
							}
						}
						catch (Exception)
						{
						}
						return HudLanguage.English;
					}
				}
				return HudLanguage.Chinese;
			case "繁體中文":
			case "繁体中文":
			case "繁體":
			case "繁体":
			case "正體中文":
				break;
			}
		}
		return HudLanguage.TraditionalChinese;
	}

	public static HudStrings For(HudLanguage language)
	{
		return language switch
		{
			HudLanguage.Chinese => Chinese, 
			HudLanguage.TraditionalChinese => Traditional, 
			_ => English, 
		};
	}

	private static bool Matches(string value, string expected)
	{
		if (!string.IsNullOrEmpty(value))
		{
			return value.Trim().Equals(expected, StringComparison.OrdinalIgnoreCase);
		}
		return false;
	}

	public string FormatPing(float milliseconds)
	{
		return Mathf.RoundToInt(milliseconds).ToString(CultureInfo.InvariantCulture) + " ms";
	}

	public string FormatLoss(float percent)
	{
		return percent.ToString("0.0", CultureInfo.InvariantCulture) + "%";
	}

	public string FormatJitter(float milliseconds)
	{
		return milliseconds.ToString("0.0", CultureInfo.InvariantCulture) + " ms";
	}

	public string FormatQuality(float quality)
	{
		return Mathf.RoundToInt(Mathf.Clamp01(quality) * 100f).ToString(CultureInfo.InvariantCulture) + "%";
	}

	public string FormatRate(float bytesPerSecond)
	{
		float num = Mathf.Max(0f, bytesPerSecond);
		if (num >= 1048576f)
		{
			return (num / 1048576f).ToString("0.00", CultureInfo.InvariantCulture) + " MB/s";
		}
		if (num >= 1024f)
		{
			return (num / 1024f).ToString("0.0", CultureInfo.InvariantCulture) + " kB/s";
		}
		return Mathf.RoundToInt(num).ToString(CultureInfo.InvariantCulture) + " B/s";
	}

	public string Labeled(string label, string value, bool showLabels)
	{
		if (!showLabels)
		{
			return value;
		}
		return label + " " + value;
	}
}
internal sealed class NetStatsSampler
{
	private const int HistorySize = 32;

	private const float EmaAlpha = 0.3f;

	private readonly float[] _pingHistory = new float[32];

	private int _historyCount;

	private int _historyNext;

	private int _lastProbeVersion = -1;

	private readonly RpcPingProbe _probe = new RpcPingProbe();

	private ZRpc _probeTarget;

	public bool HasConnection { get; private set; }

	public bool IsServer { get; private set; }

	public bool IsSinglePlayer { get; private set; }

	public int PeerCount { get; private set; }

	public bool LossSupported { get; private set; }

	public bool HasPing { get; private set; }

	public bool HasLoss { get; private set; }

	public bool HasBandwidth { get; private set; }

	public bool PingFromRpc { get; private set; }

	public string TransportName { get; private set; }

	public float PingRaw { get; private set; }

	public float PingSmooth { get; private set; }

	public float PingMin { get; private set; }

	public float PingMax { get; private set; }

	public float JitterMs { get; private set; }

	public float QualityLocal { get; private set; }

	public float QualityRemote { get; private set; }

	public float LossPercent { get; private set; }

	public float LossSmooth { get; private set; }

	public float OutBytesPerSec { get; private set; }

	public float InBytesPerSec { get; private set; }

	public void PollTransport()
	{
		if (_probeTarget != null)
		{
			_probe.Poll(_probeTarget);
		}
	}

	public void Sample()
	{
		HasConnection = false;
		HasPing = false;
		HasLoss = false;
		HasBandwidth = false;
		LossSupported = false;
		PingFromRpc = false;
		PeerCount = 0;
		IsServer = false;
		IsSinglePlayer = false;
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			SetProbeTarget(null);
			ResetHistory();
			return;
		}
		IsServer = instance.IsServer();
		IsSinglePlayer = ZNet.IsSinglePlayer;
		int num = 0;
		bool flag = false;
		ZRpc val = null;
		string transportName = null;
		try
		{
			List<ZNetPeer> connectedPeers = instance.GetConnectedPeers();
			if (connectedPeers != null)
			{
				for (int i = 0; i < connectedPeers.Count; i++)
				{
					ZNetPeer val2 = connectedPeers[i];
					if (val2 == null || !val2.IsReady())
					{
						continue;
					}
					num++;
					if (val == null)
					{
						val = val2.m_rpc;
					}
					if (val2.m_socket != null)
					{
						transportName = ((object)val2.m_socket).GetType().Name;
						if (val2.m_socket is ZSteamSocket)
						{
							flag = true;
						}
					}
				}
			}
		}
		catch (Exception)
		{
		}
		PeerCount = num;
		TransportName = transportName;
		SetProbeTarget(val);
		if (num <= 0)
		{
			ResetHistory();
			return;
		}
		float num2 = 0f;
		float num3 = 0f;
		float num4 = 0f;
		float num5 = 0f;
		int num6 = 0;
		instance.GetNetStats(ref num2, ref num3, ref num6, ref num4, ref num5);
		HasConnection = true;
		LossSupported = flag;
		OutBytesPerSec = num4;
		InBytesPerSec = num5;
		HasBandwidth = num4 > 0f || num5 > 0f;
		if (flag)
		{
			if (num6 > 0 || num2 > 0f || num4 > 0f || num5 > 0f)
			{
				HasPing = true;
				HasLoss = true;
				PingFromRpc = false;
				QualityLocal = Mathf.Clamp01(num2);
				QualityRemote = Mathf.Clamp01(num3);
				PingRaw = num6;
				LossPercent = Mathf.Clamp01(1f - QualityLocal) * 100f;
				LossSmooth = ((LossPercent <= 0f) ? 0f : Mathf.Lerp(LossSmooth, LossPercent, 0.3f));
				PushPingHistory(num6);
				PingSmooth = ((PingSmooth <= 0f) ? ((float)num6) : Mathf.Lerp(PingSmooth, (float)num6, 0.3f));
			}
			return;
		}
		HasLoss = false;
		LossPercent = 0f;
		LossSmooth = 0f;
		if (_probe.Fresh)
		{
			HasPing = true;
			PingFromRpc = true;
			PingRaw = _probe.RttMs;
			if (_lastProbeVersion != _probe.Version)
			{
				_lastProbeVersion = _probe.Version;
				PushPingHistory(_probe.RttMs);
				PingSmooth = ((PingSmooth <= 0f) ? _probe.RttMs : Mathf.Lerp(PingSmooth, _probe.RttMs, 0.3f));
			}
		}
	}

	private void SetProbeTarget(ZRpc rpc)
	{
		if (rpc != _probeTarget)
		{
			_probeTarget = rpc;
			_lastProbeVersion = -1;
			_probe.Reset();
		}
	}

	private void PushPingHistory(float ping)
	{
		_pingHistory[_historyNext] = ping;
		_historyNext = (_historyNext + 1) % 32;
		if (_historyCount < 32)
		{
			_historyCount++;
		}
		float num = 0f;
		float num2 = float.MaxValue;
		float num3 = float.MinValue;
		for (int i = 0; i < _historyCount; i++)
		{
			float num4 = _pingHistory[i];
			num += num4;
			if (num4 < num2)
			{
				num2 = num4;
			}
			if (num4 > num3)
			{
				num3 = num4;
			}
		}
		PingMin = ((_historyCount > 0) ? num2 : 0f);
		PingMax = ((_historyCount > 0) ? num3 : 0f);
		if (_historyCount <= 1)
		{
			JitterMs = 0f;
			return;
		}
		float num5 = num / (float)_historyCount;
		float num6 = 0f;
		for (int j = 0; j < _historyCount; j++)
		{
			float num7 = _pingHistory[j] - num5;
			num6 += num7 * num7;
		}
		JitterMs = Mathf.Sqrt(num6 / (float)_historyCount);
	}

	private void ResetHistory()
	{
		_historyCount = 0;
		_historyNext = 0;
		HasPing = false;
		HasLoss = false;
		HasBandwidth = false;
		LossSupported = false;
		PingSmooth = 0f;
		PingRaw = 0f;
		PingMin = 0f;
		PingMax = 0f;
		JitterMs = 0f;
		LossPercent = 0f;
		LossSmooth = 0f;
		QualityLocal = 0f;
		QualityRemote = 0f;
		OutBytesPerSec = 0f;
		InBytesPerSec = 0f;
	}
}
internal enum HudAnchorMode
{
	BelowMinimap,
	AboveMinimap,
	TopRight,
	TopLeft,
	BottomRight,
	BottomLeft
}
internal sealed class PingHudPanel
{
	private const float RowHeight = 22f;

	private const float VerticalPadding = 4f;

	private const float CornerMargin = 8f;

	private const float MinimapGap = 6f;

	private const float FallbackMinimapCenterX = -140f;

	private const float FallbackMinimapTop = -40f;

	private const float FallbackMinimapBottom = -240f;

	private const string PanelObjectName = "PingHudPanel";

	private static readonly Color GoodColor = new Color(0.45f, 1f, 0.45f, 1f);

	private static readonly Color WarnColor = new Color(1f, 0.86f, 0.35f, 1f);

	private static readonly Color BadColor = new Color(1f, 0.36f, 0.32f, 1f);

	private readonly GameObject _root;

	private readonly RectTransform _rect;

	private readonly Image _background;

	private readonly Text[] _cells = (Text[])(object)new Text[6];

	private readonly Outline[] _outlines = (Outline[])(object)new Outline[6];

	private readonly Vector3[] _cornerBuffer = (Vector3[])(object)new Vector3[4];

	private readonly List<RectTransform> _avoidTargets = new List<RectTransform>();

	private static FieldInfo _smallRootField;

	private static FieldInfo _mapSmallField;

	private static bool _minimapFieldsResolved;

	public bool Alive => (Object)(object)_root != (Object)null;

	public PingHudPanel(Transform parent)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		_root = new GameObject("PingHudPanel");
		_root.layer = 5;
		_root.transform.SetParent(parent, false);
		_rect = _root.AddComponent<RectTransform>();
		_rect.anchorMin = new Vector2(1f, 1f);
		_rect.anchorMax = new Vector2(1f, 1f);
		_rect.pivot = new Vector2(0.5f, 0.5f);
		_rect.anchoredPosition = new Vector2(-140f, -260f);
		_background = _root.AddComponent<Image>();
		_background.sprite = PanelSprite.Get();
		_background.type = (Type)0;
		((Graphic)_background).raycastTarget = false;
		for (int i = 0; i < _cells.Length; i++)
		{
			_cells[i] = CreateText(_root.transform, "Cell" + i, out _outlines[i]);
		}
	}

	public void SetVisible(bool visible)
	{
		if ((Object)(object)_root != (Object)null && _root.activeSelf != visible)
		{
			_root.SetActive(visible);
		}
	}

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

	public void Refresh(PingHudPlugin plugin, NetStatsSampler stats)
	{
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_root == (Object)null || !_root.activeInHierarchy)
		{
			return;
		}
		HudStrings strings = plugin.Strings;
		bool hasConnection = stats.HasConnection;
		int num;
		int num2;
		if (hasConnection)
		{
			num = (plugin.CfgShowDetails.Value ? 1 : 0);
			if (num != 0)
			{
				num2 = ((stats.HasPing || stats.HasLoss) ? 1 : 0);
				goto IL_0052;
			}
		}
		else
		{
			num = 0;
		}
		num2 = 0;
		goto IL_0052;
		IL_0052:
		bool flag = (byte)num2 != 0;
		bool flag2 = num != 0 && stats.HasBandwidth;
		int num3 = 1 + (flag ? 1 : 0) + (flag2 ? 1 : 0);
		float num4 = Mathf.Max(80f, plugin.CfgPanelWidth.Value);
		float num5 = Mathf.Max((float)num3 * 22f + 8f, plugin.CfgPanelHeight.Value);
		float padding = Mathf.Clamp(plugin.CfgPadding.Value, 0f, num4 * 0.25f);
		float firstRowY = (float)(num3 - 1) * 22f * 0.5f;
		_rect.sizeDelta = new Vector2(num4, num5);
		((Behaviour)_background).enabled = plugin.CfgBackgroundEnabled.Value;
		((Graphic)_background).color = plugin.CfgBackgroundColor.Value;
		ApplyTypography(plugin, strings);
		FillRows(plugin, stats, strings, hasConnection, flag, flag2, firstRowY, num4, padding);
		ApplyPosition(plugin, num4, num5);
	}

	private void ApplyTypography(PingHudPlugin plugin, HudStrings strings)
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		Font val = FontProvider.Get(plugin.CfgFontName.Value, plugin.CfgFontSize.Value, strings.Language == HudLanguage.Chinese);
		int num = Mathf.Clamp(plugin.CfgFontSize.Value, 6, 72);
		bool value = plugin.CfgOutlineEnabled.Value;
		Color value2 = plugin.CfgOutlineColor.Value;
		for (int i = 0; i < _cells.Length; i++)
		{
			Text val2 = _cells[i];
			if (!((Object)(object)val2 == (Object)null))
			{
				if ((Object)(object)val2.font != (Object)(object)val)
				{
					val2.font = val;
				}
				if (val2.fontSize != num)
				{
					val2.fontSize = num;
				}
				Outline val3 = _outlines[i];
				if ((Object)(object)val3 != (Object)null)
				{
					((Behaviour)val3).enabled = value;
					((Shadow)val3).effectColor = value2;
				}
			}
		}
	}

	private void FillRows(PingHudPlugin plugin, NetStatsSampler stats, HudStrings strings, bool online, bool jitterRow, bool rateRow, float firstRowY, float width, float padding)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: 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_01e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0206: Unknown result type (might be due to invalid IL or missing references)
		//IL_0207: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_027a: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0257: Unknown result type (might be due to invalid IL or missing references)
		//IL_0267: Unknown result type (might be due to invalid IL or missing references)
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_041d: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0330: Unknown result type (might be due to invalid IL or missing references)
		//IL_032c: Unknown result type (might be due to invalid IL or missing references)
		//IL_034b: Unknown result type (might be due to invalid IL or missing references)
		//IL_034c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0351: Unknown result type (might be due to invalid IL or missing references)
		//IL_0384: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
		Color value = plugin.CfgFontColor.Value;
		bool value2 = plugin.CfgShowLabels.Value;
		float width2 = width * 0.5f - padding;
		float num = width * 0.25f - padding * 0.5f;
		float width3 = width - padding * 2f;
		Color val = default(Color);
		((Color)(ref val))..ctor(value.r, value.g, value.b, value.a * 0.6f);
		string unknownValue = strings.UnknownValue;
		string text = (stats.LossSupported ? strings.UnknownValue : strings.NotAvailableValue);
		if (!online)
		{
			ShowCell(0, strings.OfflineLabel, (TextAnchor)4, width3, 0f, 0f, val);
			HideCell(1);
			HideCell(2);
			HideCell(3);
			HideCell(4);
			HideCell(5);
			return;
		}
		bool flag = plugin.CfgShowPing.Value;
		bool value3 = plugin.CfgShowLoss.Value;
		if (!flag && !value3)
		{
			flag = true;
		}
		bool flag2 = !plugin.CfgReverseText.Value;
		bool num2 = flag && value3;
		float num3 = (plugin.CfgSmooth.Value ? stats.PingSmooth : stats.PingRaw);
		float num4 = (plugin.CfgSmooth.Value ? stats.LossSmooth : stats.LossPercent);
		string text2 = strings.Labeled(strings.PingLabel, stats.HasPing ? strings.FormatPing(num3) : unknownValue, value2);
		string text3 = strings.Labeled(strings.LossLabel, stats.HasLoss ? strings.FormatLoss(num4) : text, value2);
		Color color = ((stats.HasPing && plugin.CfgColorize.Value) ? Grade(num3, plugin.CfgPingGood.Value, plugin.CfgPingBad.Value, value) : (stats.HasPing ? value : val));
		Color color2 = ((stats.HasLoss && plugin.CfgColorize.Value) ? Grade(num4, plugin.CfgLossGood.Value, plugin.CfgLossBad.Value, value) : (stats.HasLoss ? value : val));
		if (!num2)
		{
			if (flag)
			{
				ShowCell(0, text2, (TextAnchor)4, width3, 0f, firstRowY, color);
			}
			else
			{
				ShowCell(0, text3, (TextAnchor)4, width3, 0f, firstRowY, color2);
			}
			HideCell(1);
		}
		else if (flag2)
		{
			ShowCell(0, text2, (TextAnchor)3, width2, 0f - num, firstRowY, color);
			ShowCell(1, text3, (TextAnchor)5, width2, num, firstRowY, color2);
		}
		else
		{
			ShowCell(0, text3, (TextAnchor)3, width2, 0f - num, firstRowY, color2);
			ShowCell(1, text2, (TextAnchor)5, width2, num, firstRowY, color);
		}
		float num5 = firstRowY - 22f;
		if (jitterRow)
		{
			Color color3 = ((stats.HasPing && plugin.CfgColorize.Value) ? Grade(stats.JitterMs, plugin.CfgPingGood.Value * 0.5f, plugin.CfgPingBad.Value * 0.5f, value) : (stats.HasPing ? value : val));
			float value4 = Mathf.Clamp01(1f - stats.QualityLocal) * 100f;
			Color color4 = ((stats.HasLoss && plugin.CfgColorize.Value) ? Grade(value4, plugin.CfgLossGood.Value, plugin.CfgLossBad.Value, value) : (stats.HasLoss ? value : val));
			ShowCell(2, strings.Labeled(strings.JitterLabel, stats.HasPing ? strings.FormatJitter(stats.JitterMs) : strings.UnknownValue, value2), (TextAnchor)3, width2, 0f - num, num5, color3);
			ShowCell(3, strings.Labeled(strings.QualityLabel, stats.HasLoss ? strings.FormatQuality(stats.QualityLocal) : text, value2), (TextAnchor)5, width2, num, num5, color4);
			num5 -= 22f;
		}
		else
		{
			HideCell(2);
			HideCell(3);
		}
		if (rateRow)
		{
			ShowCell(4, "↓ " + strings.FormatRate(stats.OutBytesPerSec), (TextAnchor)3, width2, 0f - num, num5, value);
			ShowCell(5, "↑ " + strings.FormatRate(stats.InBytesPerSec), (TextAnchor)5, width2, num, num5, value);
		}
		else
		{
			HideCell(4);
			HideCell(5);
		}
	}

	private void ShowCell(int index, string text, TextAnchor anchor, float width, float x, float y, Color color)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: 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_0094: Unknown result type (might be due to invalid IL or missing references)
		Text val = _cells[index];
		if (!((Object)(object)val == (Object)null))
		{
			if (!((Component)val).gameObject.activeSelf)
			{
				((Component)val).gameObject.SetActive(true);
			}
			if (val.text != text)
			{
				val.text = text;
			}
			val.alignment = anchor;
			((Graphic)val).color = color;
			RectTransform rectTransform = ((Graphic)val).rectTransform;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(width, 22f);
			if (rectTransform.sizeDelta != val2)
			{
				rectTransform.sizeDelta = val2;
			}
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(x, y);
			if (rectTransform.anchoredPosition != val3)
			{
				rectTransform.anchoredPosition = val3;
			}
		}
	}

	private void HideCell(int index)
	{
		Text val = _cells[index];
		if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf)
		{
			((Component)val).gameObject.SetActive(false);
		}
	}

	private void ApplyPosition(PingHudPlugin plugin, float width, float height)
	{
		//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01db: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_020b: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_024c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0257: Unknown result type (might be due to invalid IL or missing references)
		//IL_0298: 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)
		HudAnchorMode hudAnchorMode = ParseMode(plugin.CfgPosition.Value);
		float value = plugin.CfgOffsetX.Value;
		float value2 = plugin.CfgOffsetY.Value;
		float centerX = -140f;
		float top = -40f;
		float bottom = -240f;
		TryGetMinimapBounds(((Transform)_rect).parent, out centerX, out top, out bottom);
		Vector2 val = default(Vector2);
		Vector2 val2 = default(Vector2);
		int pushDirection;
		switch (hudAnchorMode)
		{
		case HudAnchorMode.AboveMinimap:
			((Vector2)(ref val))..ctor(1f, 1f);
			((Vector2)(ref val2))..ctor(centerX + value, top + 6f + height * 0.5f + value2);
			pushDirection = 1;
			break;
		case HudAnchorMode.TopRight:
			((Vector2)(ref val))..ctor(1f, 1f);
			((Vector2)(ref val2))..ctor(0f - (width * 0.5f + 8f) + value, 0f - (height * 0.5f + 8f) + value2);
			pushDirection = 1;
			break;
		case HudAnchorMode.TopLeft:
			((Vector2)(ref val))..ctor(0f, 1f);
			((Vector2)(ref val2))..ctor(width * 0.5f + 8f + value, 0f - (height * 0.5f + 8f) + value2);
			pushDirection = 1;
			break;
		case HudAnchorMode.BottomRight:
			((Vector2)(ref val))..ctor(1f, 0f);
			((Vector2)(ref val2))..ctor(0f - (width * 0.5f + 8f) + value, height * 0.5f + 8f + value2);
			pushDirection = -1;
			break;
		case HudAnchorMode.BottomLeft:
			((Vector2)(ref val))..ctor(0f, 0f);
			((Vector2)(ref val2))..ctor(width * 0.5f + 8f + value, height * 0.5f + 8f + value2);
			pushDirection = -1;
			break;
		default:
			((Vector2)(ref val))..ctor(1f, 1f);
			((Vector2)(ref val2))..ctor(centerX + value, bottom - 6f - height * 0.5f + value2);
			pushDirection = -1;
			break;
		}
		if (_rect.anchorMin != val || _rect.anchorMax != val)
		{
			_rect.anchorMin = val;
			_rect.anchorMax = val;
		}
		if (hudAnchorMode == HudAnchorMode.AboveMinimap && top < 0f && height + 6f > 0f - top)
		{
			pushDirection = -1;
			((Vector2)(ref val2))..ctor(centerX + value, bottom - 6f - height * 0.5f + value2);
		}
		float num = ResolveOverlapY(plugin, val2, pushDirection);
		num = ((val.y >= 0.5f) ? Mathf.Min(num, 0f - (height * 0.5f + 2f)) : Mathf.Max(num, height * 0.5f + 2f));
		_rect.anchoredPosition = new Vector2(val2.x, num);
	}

	private float ResolveOverlapY(PingHudPlugin plugin, Vector2 basePosition, int pushDirection)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Unknown result type (might be due to invalid IL or missing references)
		float num = basePosition.y;
		if (!plugin.CfgAutoAvoid.Value)
		{
			_rect.anchoredPosition = new Vector2(basePosition.x, num);
			return num;
		}
		Transform parent = ((Transform)_rect).parent;
		CollectAvoidTargets(plugin, parent);
		if ((Object)(object)parent == (Object)null || _avoidTargets.Count == 0)
		{
			_rect.anchoredPosition = new Vector2(basePosition.x, num);
			return num;
		}
		float num2 = Mathf.Max(0f, plugin.CfgAvoidGap.Value);
		for (int i = 0; i < 3; i++)
		{
			_rect.anchoredPosition = new Vector2(basePosition.x, num);
			if (!TryGetParentLocalRect(_rect, parent, _cornerBuffer, out var minX, out var maxX, out var minY, out var maxY))
			{
				break;
			}
			float num3 = 0f;
			for (int j = 0; j < _avoidTargets.Count; j++)
			{
				if (!TryGetParentLocalRect(_avoidTargets[j], parent, _cornerBuffer, out var minX2, out var maxX2, out var minY2, out var maxY2) || minX >= maxX2 || maxX <= minX2)
				{
					continue;
				}
				if (pushDirection < 0)
				{
					float num4 = minY2 - num2;
					if (maxY > num4)
					{
						num3 = Mathf.Max(num3, maxY - num4);
					}
				}
				else
				{
					float num5 = maxY2 + num2;
					if (minY < num5)
					{
						num3 = Mathf.Max(num3, num5 - minY);
					}
				}
			}
			if (num3 <= 0.01f)
			{
				break;
			}
			num += (float)pushDirection * num3;
		}
		_rect.anchoredPosition = new Vector2(basePosition.x, num);
		return num;
	}

	private void CollectAvoidTargets(PingHudPlugin plugin, Transform parent)
	{
		_avoidTargets.Clear();
		if ((Object)(object)parent == (Object)null)
		{
			return;
		}
		string value = plugin.CfgAvoidPanelNames.Value;
		if (string.IsNullOrEmpty(value))
		{
			return;
		}
		string[] array = value.Split(',');
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (text.Length == 0)
			{
				continue;
			}
			Transform val = parent.Find(text);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)_root.transform))
			{
				RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null);
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = ((Component)val).GetComponent<RectTransform>();
				}
				if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy)
				{
					_avoidTargets.Add(val2);
				}
			}
		}
	}

	private static bool TryGetParentLocalRect(RectTransform rect, Transform parent, Vector3[] buffer, out float minX, out float maxX, out float minY, out float maxY)
	{
		//IL_004d: 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_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: 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)
		minX = 0f;
		maxX = 0f;
		minY = 0f;
		maxY = 0f;
		if ((Object)(object)rect == (Object)null || (Object)(object)parent == (Object)null || buffer == null || !((Component)rect).gameObject.activeInHierarchy)
		{
			return false;
		}
		rect.GetWorldCorners(buffer);
		Vector3 val = parent.InverseTransformPoint(buffer[0]);
		Vector3 val2 = parent.InverseTransformPoint(buffer[2]);
		minX = Mathf.Min(val.x, val2.x);
		maxX = Mathf.Max(val.x, val2.x);
		minY = Mathf.Min(val.y, val2.y);
		maxY = Mathf.Max(val.y, val2.y);
		return true;
	}

	private void TryGetMinimapBounds(Transform parent, out float centerX, out float top, out float bottom)
	{
		centerX = -140f;
		top = -40f;
		bottom = -240f;
		if ((Object)(object)parent == (Object)null)
		{
			return;
		}
		try
		{
			Minimap instance = Minimap.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			ResolveMinimapFields();
			Transform val = null;
			if (_smallRootField != null)
			{
				object? value = _smallRootField.GetValue(instance);
				GameObject val2 = (GameObject)((value is GameObject) ? value : null);
				if ((Object)(object)val2 != (Object)null)
				{
					val = val2.transform;
				}
			}
			if ((Object)(object)val == (Object)null && _mapSmallField != null)
			{
				object? value2 = _mapSmallField.GetValue(instance);
				GameObject val3 = (GameObject)((value2 is GameObject) ? value2 : null);
				if ((Object)(object)val3 != (Object)null)
				{
					val = val3.transform;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			RectTransform val4 = (RectTransform)(object)((val is RectTransform) ? val : null);
			if ((Object)(object)val4 == (Object)null)
			{
				val4 = ((Component)val).GetComponent<RectTransform>();
			}
			if (!((Object)(object)val4 == (Object)null) && TryGetParentLocalRect(val4, parent, _cornerBuffer, out var minX, out var maxX, out var minY, out var maxY))
			{
				float num = maxX - minX;
				float num2 = maxY - minY;
				if (num >= 80f && num <= 420f && num2 >= 80f && num2 <= 420f && maxX < 0f && maxY < 0f)
				{
					centerX = (minX + maxX) * 0.5f;
					top = maxY;
					bottom = minY;
				}
			}
		}
		catch (Exception)
		{
		}
	}

	private static void ResolveMinimapFields()
	{
		if (!_minimapFieldsResolved)
		{
			_minimapFieldsResolved = true;
			_smallRootField = typeof(Minimap).GetField("m_smallRoot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_mapSmallField = typeof(Minimap).GetField("m_mapSmall", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}
	}

	public static HudAnchorMode ParseMode(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return HudAnchorMode.BelowMinimap;
		}
		switch (value.Trim().Replace(" ", string.Empty).Replace("_", string.Empty)
			.ToLowerInvariant())
		{
		case "aboveminimap":
		case "above":
		case "top":
			return HudAnchorMode.AboveMinimap;
		case "topright":
			return HudAnchorMode.TopRight;
		case "topleft":
			return HudAnchorMode.TopLeft;
		case "bottomright":
			return HudAnchorMode.BottomRight;
		case "bottomleft":
			return HudAnchorMode.BottomLeft;
		case "belowminimap":
		case "below":
			return HudAnchorMode.BelowMinimap;
		default:
			return HudAnchorMode.BelowMinimap;
		}
	}

	private static Color Grade(float value, float good, float bad, Color baseColor)
	{
		//IL_000e: 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_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		if (bad < good)
		{
			float num = good;
			good = bad;
			bad = num;
		}
		Color result = ((value <= good) ? GoodColor : ((!(value <= bad)) ? BadColor : WarnColor));
		result.a = baseColor.a;
		return result;
	}

	private static Text CreateText(Transform parent, string name, out Outline outline)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Expected O, but got Unknown
		//IL_002c: 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_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.layer = 5;
		val.transform.SetParent(parent, false);
		RectTransform obj = val.AddComponent<RectTransform>();
		obj.anchorMin = new Vector2(0.5f, 0.5f);
		obj.anchorMax = new Vector2(0.5f, 0.5f);
		obj.pivot = new Vector2(0.5f, 0.5f);
		Text obj2 = val.AddComponent<Text>();
		obj2.horizontalOverflow = (HorizontalWrapMode)1;
		obj2.verticalOverflow = (VerticalWrapMode)1;
		((Graphic)obj2).raycastTarget = false;
		obj2.supportRichText = false;
		outline = val.AddComponent<Outline>();
		((Shadow)outline).effectColor = Color.black;
		((Shadow)outline).effectDistance = new Vector2(1f, -1f);
		((Shadow)outline).useGraphicAlpha = true;
		return obj2;
	}
}
internal static class FontProvider
{
	private static readonly string[] ChineseFontCandidates = new string[10] { "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", "SimHei", "黑体", "SimSun", "宋体", "Noto Sans CJK SC", "Source Han Sans SC", "Arial Unicode MS" };

	private static readonly string[] LatinFontCandidates = new string[3] { "AveriaSansLibre-Bold", "AveriaSansLibre", "Arial" };

	private static Font _cached;

	private static string _cachedSetting;

	private static int _cachedSize;

	private static bool _cachedChinese;

	public static Font Get(string setting, int size, bool chinese)
	{
		if ((Object)(object)_cached != (Object)null && _cachedSetting == setting && _cachedSize == size && _cachedChinese == chinese)
		{
			return _cached;
		}
		Font result = (_cached = Resolve(setting, size, chinese));
		_cachedSetting = setting;
		_cachedSize = size;
		_cachedChinese = chinese;
		return result;
	}

	private static Font Resolve(string setting, int size, bool chinese)
	{
		int size2 = Mathf.Clamp(size, 6, 72);
		if (!string.IsNullOrEmpty(setting) && !setting.Trim().Equals("auto", StringComparison.OrdinalIgnoreCase))
		{
			Font val = FindInResources(setting.Trim()) ?? CreateFromOs(setting.Trim(), size2);
			if ((Object)(object)val != (Object)null && (!chinese || HasChineseGlyphs(val)))
			{
				return val;
			}
		}
		if (chinese)
		{
			Font val2 = CreateFromOs(ChineseFontCandidates, size2);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2;
			}
		}
		Font val3 = FindInResources("AveriaSansLibre-Bold") ?? FindAnyInResources();
		if ((Object)(object)val3 != (Object)null)
		{
			return val3;
		}
		return CreateFromOs(LatinFontCandidates, size2);
	}

	private static Font FindInResources(string name)
	{
		Font[] array = Resources.FindObjectsOfTypeAll<Font>();
		foreach (Font val in array)
		{
			if ((Object)(object)val != (Object)null && ((Object)val).name == name)
			{
				return val;
			}
		}
		return null;
	}

	private static Font FindAnyInResources()
	{
		Font[] array = Resources.FindObjectsOfTypeAll<Font>();
		for (int i = 0; i < array.Length; i++)
		{
			if ((Object)(object)array[i] != (Object)null)
			{
				return array[i];
			}
		}
		return null;
	}

	private static Font CreateFromOs(string name, int size)
	{
		try
		{
			Font val = Font.CreateDynamicFontFromOSFont(name, size);
			return ((Object)(object)val != (Object)null && ((Object)val).name == name) ? val : null;
		}
		catch (Exception)
		{
			return null;
		}
	}

	private static Font CreateFromOs(string[] candidates, int size)
	{
		string[] array;
		try
		{
			array = Font.GetOSInstalledFontNames();
		}
		catch (Exception)
		{
			array = null;
		}
		if (array == null || array.Length == 0)
		{
			return null;
		}
		foreach (string text in candidates)
		{
			if (Array.IndexOf(array, text) < 0)
			{
				continue;
			}
			try
			{
				Font val = Font.CreateDynamicFontFromOSFont(text, size);
				if ((Object)(object)val != (Object)null && HasChineseGlyphs(val))
				{
					return val;
				}
			}
			catch (Exception)
			{
			}
		}
		return null;
	}

	private static bool HasChineseGlyphs(Font font)
	{
		try
		{
			return font.HasCharacter('延') && font.HasCharacter('迟');
		}
		catch (Exception)
		{
			return false;
		}
	}
}
internal static class PanelSprite
{
	private const string PreferredSprite = "InputFieldBackground";

	private static Sprite _cached;

	private static bool _searched;

	public static Sprite Get()
	{
		if (_searched)
		{
			return _cached;
		}
		_searched = true;
		Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
		foreach (Sprite val in array)
		{
			if ((Object)(object)val != (Object)null && ((Object)val).name == "InputFieldBackground")
			{
				_cached = val;
				return _cached;
			}
		}
		for (int j = 0; j < array.Length; j++)
		{
			if ((Object)(object)array[j] != (Object)null)
			{
				_cached = array[j];
				break;
			}
		}
		return _cached;
	}
}
[BepInPlugin("kagegawa.valheim.pinghud", "PingHud - Server Latency & Packet Loss", "1.1.0")]
public sealed class PingHudPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "kagegawa.valheim.pinghud";

	public const string PluginName = "PingHud - Server Latency & Packet Loss";

	public const string PluginVersion = "1.1.0";

	internal static ManualLogSource Log;

	internal ConfigEntry<bool> CfgEnabled;

	internal ConfigEntry<KeyboardShortcut> CfgToggleKey;

	internal ConfigEntry<float> CfgUpdateInterval;

	internal ConfigEntry<bool> CfgHideWhenOffline;

	internal ConfigEntry<bool> CfgHideWithHud;

	internal ConfigEntry<string> CfgPosition;

	internal ConfigEntry<float> CfgOffsetX;

	internal ConfigEntry<float> CfgOffsetY;

	internal ConfigEntry<bool> CfgAutoAvoid;

	internal ConfigEntry<float> CfgAvoidGap;

	internal ConfigEntry<string> CfgAvoidPanelNames;

	internal ConfigEntry<float> CfgPanelWidth;

	internal ConfigEntry<float> CfgPanelHeight;

	internal ConfigEntry<string> CfgFontName;

	internal ConfigEntry<int> CfgFontSize;

	internal ConfigEntry<Color> CfgFontColor;

	internal ConfigEntry<bool> CfgOutlineEnabled;

	internal ConfigEntry<Color> CfgOutlineColor;

	internal ConfigEntry<bool> CfgBackgroundEnabled;

	internal ConfigEntry<Color> CfgBackgroundColor;

	internal ConfigEntry<float> CfgPadding;

	internal ConfigEntry<bool> CfgShowPing;

	internal ConfigEntry<bool> CfgShowLoss;

	internal ConfigEntry<bool> CfgShowDetails;

	internal ConfigEntry<bool> CfgSmooth;

	internal ConfigEntry<bool> CfgShowLabels;

	internal ConfigEntry<bool> CfgReverseText;

	internal ConfigEntry<bool> CfgColorize;

	internal ConfigEntry<float> CfgPingGood;

	internal ConfigEntry<float> CfgPingBad;

	internal ConfigEntry<float> CfgLossGood;

	internal ConfigEntry<float> CfgLossBad;

	internal ConfigEntry<string> CfgLanguage;

	private readonly NetStatsSampler _sampler = new NetStatsSampler();

	private PingHudPanel _panel;

	private float _sampleTimer;

	private bool _userVisible = true;

	private bool _errorLogged;

	private string _languageSetting;

	private string _loggedTransport;

	private HudStrings _strings = HudStrings.For(HudLanguage.English);

	private static MethodInfo _hudIsVisible;

	internal HudStrings Strings => _strings;

	private void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfiguration();
		RefreshStrings(force: true);
		Log.LogInfo((object)("PingHud - Server Latency & Packet Loss 1.1.0 loaded (anchor: " + PingHudPanel.ParseMode(CfgPosition.Value).ToString() + ", language: " + _strings.Language.ToString() + ")."));
	}

	private void OnDestroy()
	{
		if (_panel != null)
		{
			_panel.Destroy();
			_panel = null;
		}
	}

	private void BindConfiguration()
	{
		//IL_003c: 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_0089: Expected O, but got Unknown
		//IL_023e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0248: Expected O, but got Unknown
		//IL_0272: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0312: Unknown result type (might be due to invalid IL or missing references)
		CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General 常规", "Enabled 启用", true, "是否启用本插件。\nEnable or disable the HUD.");
		CfgToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1. General 常规", "Toggle key 切换热键", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "游戏中按下该按键可临时显示/隐藏面板。\nPress in game to show/hide the panel temporarily.");
		CfgUpdateInterval = ((BaseUnityPlugin)this).Config.Bind<float>("1. General 常规", "Update interval 刷新间隔", 0.25f, new ConfigDescription("采样网络数据的间隔(秒)。\nHow often the network data is sampled, in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>()));
		CfgHideWhenOffline = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General 常规", "Hide when not connected 未联网时隐藏", true, "单人游戏或未连接服务器时隐藏面板。\nHide the panel in single player / when no server connection exists.");
		CfgHideWithHud = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General 常规", "Hide with HUD 随HUD隐藏", true, "游戏隐藏 HUD 时同时隐藏本面板。\nHide the panel together with the game HUD.");
		CfgPosition = ((BaseUnityPlugin)this).Config.Bind<string>("2. Position 位置", "Anchor 位置", "BelowMinimap", "面板位置。可选值:\n  BelowMinimap  - 小地图正下方(默认)\n  AboveMinimap  - 小地图正上方\n  TopRight / TopLeft / BottomRight / BottomLeft - 屏幕四角\nPanel anchor: BelowMinimap (default), AboveMinimap, TopRight, TopLeft, BottomRight, BottomLeft.");
		CfgOffsetX = ((BaseUnityPlugin)this).Config.Bind<float>("2. Position 位置", "Extra X offset 额外X偏移", 0f, "在所选位置基础上再水平移动(像素)。\nExtra horizontal offset in HUD pixels.");
		CfgOffsetY = ((BaseUnityPlugin)this).Config.Bind<float>("2. Position 位置", "Extra Y offset 额外Y偏移", 0f, "在所选位置基础上再垂直移动(像素,正数向上)。\nExtra vertical offset in HUD pixels (positive = up).");
		CfgAutoAvoid = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Position 位置", "Auto avoid other panels 自动避让其他面板", true, "开启后会自动检测下列面板,若位置重叠则自动错开,保证 UI 不重叠。\nAutomatically slides this panel out of the way of the panels listed below.");
		CfgAvoidGap = ((BaseUnityPlugin)this).Config.Bind<float>("2. Position 位置", "Avoid gap 避让间距", 8f, "避让时两个面板之间保留的像素间距。\nGap kept between this panel and the avoided panel.");
		CfgAvoidPanelNames = ((BaseUnityPlugin)this).Config.Bind<string>("2. Position 位置", "Avoid panel names 避让面板名称", "DayTimePanel", "需要避让的 HUD 子物体名称(逗号分隔)。\n填 HUD 里已存在的面板名即可,避免和本面板重叠。\nComma separated list of HUD child object names to avoid. If a panel with one of these names is already in the way, PingHud moves aside.");
		CfgPanelWidth = ((BaseUnityPlugin)this).Config.Bind<float>("3. Appearance 外观", "Panel width 面板宽度", 200f, "面板宽度。\nPanel width.");
		CfgPanelHeight = ((BaseUnityPlugin)this).Config.Bind<float>("3. Appearance 外观", "Panel height 面板高度", 30f, "面板高度(内容需要更高时会自动扩展)。\nPanel height; grows automatically when extra rows are shown.");
		CfgFontName = ((BaseUnityPlugin)this).Config.Bind<string>("3. Appearance 外观", "Font name 字体名称", "auto", "字体名称。auto = 中文使用系统中文字体、英文使用游戏字体(AveriaSansLibre-Bold)。\nFont name, or 'auto'.");
		CfgFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("3. Appearance 外观", "Font size 字号", 16, new ConfigDescription("字号。\nFont size.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(6, 72), Array.Empty<object>()));
		CfgFontColor = ((BaseUnityPlugin)this).Config.Bind<Color>("3. Appearance 外观", "Font color 字体颜色", new Color(1f, 1f, 1f, 0.791f), "字体颜色(RGBA)。\nText colour.");
		CfgOutlineEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Appearance 外观", "Text outline enabled 文字描边", true, "是否给文字加描边,保证任何背景下都能看清。\nDraw an outline around the text.");
		CfgOutlineColor = ((BaseUnityPlugin)this).Config.Bind<Color>("3. Appearance 外观", "Text outline color 描边颜色", Color.black, "描边颜色。\nOutline colour.");
		CfgBackgroundEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Appearance 外观", "Display background 显示背景", true, "是否显示半透明背景。\nDraw the dark background panel.");
		CfgBackgroundColor = ((BaseUnityPlugin)this).Config.Bind<Color>("3. Appearance 外观", "Background color 背景颜色", new Color(0f, 0f, 0f, 0.3921569f), "背景颜色(RGBA)。\nBackground colour.");
		CfgPadding = ((BaseUnityPlugin)this).Config.Bind<float>("3. Appearance 外观", "Padding left and right 左右内边距", 10f, "文字与面板左右边缘的间距。\nHorizontal padding between the text and the panel edge.");
		CfgShowPing = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Show ping 显示延迟", true, "显示延迟 (ms)。\nShow latency in ms.");
		CfgShowLoss = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Show packet loss 显示丢包", true, "显示丢包率 (%)。\n只有 Steam P2P 连接(ZSteamSocket)才有丢包数据:Steam 的“连接质量”定义是\n“端到端按序送达的数据包比例”,所以丢包率 = 100% − 连接质量(0% = 不丢包)。\n跨平台联机(ZPlayFabSocket)和直连 IP / 局域网(ZSocket2,TCP)这两种传输层\n在游戏内部就把延迟与质量硬编码为 0,没有丢包数据源,因此显示 N/A。\n想要真实丢包率请用 Steam 邀请 / 服务器浏览器进服(关闭 Crossplay)。\n-- = 暂时还没数据;N/A = 该连接方式不提供。\nPacket loss is only available on Steam P2P connections; crossplay and direct-IP\nconnections report none and show N/A.");
		CfgShowDetails = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Show extra details 显示详细信息", false, "额外显示抖动、连接质量与上下行带宽(面板会自动变高)。\nAlso show jitter, connection quality and up/down bandwidth.");
		CfgSmooth = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Smooth values 数值平滑", true, "使用平滑后的数值,避免数字剧烈跳动。\nSmooth the displayed numbers.");
		CfgShowLabels = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Show text labels 显示文字标签", true, "显示“延迟/丢包”等文字标签。\nShow the text labels next to the numbers.");
		CfgReverseText = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Ping on the right 延迟显示在右侧", false, "勾选后延迟显示在右、丢包显示在左。\nPut ping on the right and loss on the left.");
		CfgColorize = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display 显示", "Color by quality 按质量着色", true, "根据下面的阈值把数值染成绿/黄/红。\nColour the values green/yellow/red using the thresholds below.");
		CfgPingGood = ((BaseUnityPlugin)this).Config.Bind<float>("4. Display 显示", "Ping good (ms) 延迟良好阈值", 80f, "低于该值显示绿色。\nBelow this ping the value is green.");
		CfgPingBad = ((BaseUnityPlugin)this).Config.Bind<float>("4. Display 显示", "Ping bad (ms) 延迟较差阈值", 150f, "高于该值显示红色。\nAbove this ping the value is red.");
		CfgLossGood = ((BaseUnityPlugin)this).Config.Bind<float>("4. Display 显示", "Loss good (%) 丢包良好阈值", 1f, "低于该值显示绿色。\nBelow this loss the value is green.");
		CfgLossBad = ((BaseUnityPlugin)this).Config.Bind<float>("4. Display 显示", "Loss bad (%) 丢包较差阈值", 5f, "高于该值显示红色。\nAbove this loss the value is red.");
		CfgLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("4. Display 显示", "Language 语言", "auto", "面板语言:auto(跟随游戏语言)/ Chinese / English。\nPanel language: auto (follow the game), Chinese or English.");
	}

	private void Update()
	{
		try
		{
			Tick();
			_errorLogged = false;
		}
		catch (Exception ex)
		{
			if (!_errorLogged)
			{
				_errorLogged = true;
				if (Log != null)
				{
					Log.LogError((object)("PingHud update failed: " + ex));
				}
			}
		}
	}

	private void Tick()
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		KeyboardShortcut value = CfgToggleKey.Value;
		if (((KeyboardShortcut)(ref value)).IsDown())
		{
			_userVisible = !_userVisible;
		}
		_sampler.PollTransport();
		_sampleTimer += Time.unscaledDeltaTime;
		float num = Mathf.Max(0.05f, CfgUpdateInterval.Value);
		if (_sampleTimer >= num)
		{
			_sampleTimer = 0f;
			_sampler.Sample();
			RefreshStrings(force: false);
			LogTransportOnce();
		}
		if (_panel != null && !_panel.Alive)
		{
			_panel = null;
		}
		if (!ShouldShow())
		{
			if (_panel != null)
			{
				_panel.SetVisible(visible: false);
			}
			return;
		}
		if (_panel == null)
		{
			Hud instance = Hud.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_rootObject == (Object)null)
			{
				return;
			}
			_panel = new PingHudPanel(instance.m_rootObject.transform);
			if (Log != null)
			{
				Log.LogInfo((object)("HUD panel created (" + PingHudPanel.ParseMode(CfgPosition.Value).ToString() + ")."));
			}
		}
		_panel.SetVisible(visible: true);
		_panel.Refresh(this, _sampler);
	}

	private void LogTransportOnce()
	{
		string transportName = _sampler.TransportName;
		if (transportName == null || transportName == _loggedTransport)
		{
			return;
		}
		_loggedTransport = transportName;
		if (Log != null)
		{
			if (_sampler.LossSupported)
			{
				Log.LogInfo((object)("Connection is " + transportName + " (Steam P2P): latency and packet loss come from Steam."));
			}
			else
			{
				Log.LogInfo((object)("Connection is " + transportName + " (not Steam P2P): Steam provides no latency/loss for this transport, so packet loss shows N/A and latency is measured from the game's own RPC ping/pong."));
			}
		}
	}

	private bool ShouldShow()
	{
		if (!CfgEnabled.Value || !_userVisible)
		{
			return false;
		}
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			return false;
		}
		Hud instance = Hud.instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_rootObject == (Object)null)
		{
			return false;
		}
		if (CfgHideWithHud.Value && !IsHudVisible(instance))
		{
			return false;
		}
		if (!_sampler.HasConnection && CfgHideWhenOffline.Value)
		{
			return false;
		}
		return true;
	}

	private void RefreshStrings(bool force)
	{
		string text = ((CfgLanguage == null) ? "auto" : CfgLanguage.Value);
		if (force || !(text == _languageSetting))
		{
			_languageSetting = text;
			_strings = HudStrings.For(HudStrings.ResolveLanguage(text));
		}
	}

	private static bool IsHudVisible(Hud hud)
	{
		if (_hudIsVisible == null)
		{
			_hudIsVisible = typeof(Hud).GetMethod("IsVisible", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}
		if (_hudIsVisible == null)
		{
			return true;
		}
		object obj = _hudIsVisible.Invoke(hud, null);
		if (obj is bool)
		{
			return (bool)obj;
		}
		return true;
	}
}
internal sealed class RpcPingProbe
{
	private const float NewerThanSeconds = 5f;

	private static FieldInfo _pingTimerField;

	private static FieldInfo _ageField;

	private static bool _fieldsResolved;

	private float _lastAge = -1f;

	private float _lastSampleTime = -1000f;

	public float RttMs { get; private set; }

	public int Version { get; private set; }

	public bool HasSample { get; private set; }

	public bool Fresh
	{
		get
		{
			if (HasSample)
			{
				return Time.time - _lastSampleTime < 5f;
			}
			return false;
		}
	}

	public void Reset()
	{
		_lastAge = -1f;
		HasSample = false;
		RttMs = 0f;
	}

	public void Poll(ZRpc rpc)
	{
		if (rpc == null)
		{
			return;
		}
		ResolveFields();
		if (_pingTimerField == null || _ageField == null)
		{
			return;
		}
		float num;
		float num2;
		try
		{
			num = (float)_pingTimerField.GetValue(rpc);
			num2 = (float)_ageField.GetValue(rpc);
		}
		catch (Exception)
		{
			return;
		}
		if (_lastAge >= 0f && num2 < _lastAge - 0.0001f)
		{
			float num3 = Mathf.Max(0f, num - Time.deltaTime * 0.5f) * 1000f;
			if (num3 >= 0f && num3 < 30000f)
			{
				RttMs = num3;
				_lastSampleTime = Time.time;
				HasSample = true;
				Version++;
			}
		}
		_lastAge = num2;
	}

	private static void ResolveFields()
	{
		if (!_fieldsResolved)
		{
			_fieldsResolved = true;
			_pingTimerField = typeof(ZRpc).GetField("m_pingTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_ageField = typeof(ZRpc).GetField("m_timeSinceLastPing", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}
	}
}