Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PingHud v1.2.0
BepInEx\plugins\ValheimPingHud.dll
Decompiled a week agousing 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); } } }