Decompiled source of CheckMyStats v1.0.1

BepInEx/plugins/CheckMyStats/CheckMyStats.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing.Client;
using FishNet.Managing.Server;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using UnityEngine;

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

internal enum DeduplicationResult
{
	NewEvent,
	Duplicate,
	PromoteToExact
}
internal sealed class CrossSourceDeduplicator
{
	private sealed class RecentObservation
	{
		internal float Time;

		internal Confidence Confidence;

		internal readonly HashSet<string> Sources = new HashSet<string>(StringComparer.Ordinal);
	}

	private readonly float _windowSeconds;

	private readonly Dictionary<string, List<RecentObservation>> _recent = new Dictionary<string, List<RecentObservation>>(StringComparer.Ordinal);

	internal CrossSourceDeduplicator(float windowSeconds)
	{
		_windowSeconds = windowSeconds;
	}

	internal DeduplicationResult Observe(string signature, string source, Confidence confidence, float now)
	{
		if (!_recent.TryGetValue(signature, out var value))
		{
			value = new List<RecentObservation>();
			_recent.Add(signature, value);
		}
		value.RemoveAll((RecentObservation observation) => now - observation.Time > _windowSeconds);
		foreach (RecentObservation item in value)
		{
			if (!item.Sources.Contains(source) && CanRepresentSameEvent(item.Sources, source))
			{
				item.Sources.Add(source);
				if (item.Confidence == Confidence.Inferred && confidence == Confidence.Exact)
				{
					item.Confidence = confidence;
					return DeduplicationResult.PromoteToExact;
				}
				return DeduplicationResult.Duplicate;
			}
		}
		RecentObservation recentObservation = new RecentObservation
		{
			Time = now,
			Confidence = confidence
		};
		recentObservation.Sources.Add(source);
		value.Add(recentObservation);
		if (_recent.Count > 1024)
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, List<RecentObservation>> item2 in _recent)
			{
				item2.Value.RemoveAll((RecentObservation observation) => now - observation.Time > 10f);
				if (item2.Value.Count == 0)
				{
					list.Add(item2.Key);
				}
			}
			foreach (string item3 in list)
			{
				_recent.Remove(item3);
			}
		}
		return DeduplicationResult.NewEvent;
	}

	private static bool CanRepresentSameEvent(IEnumerable<string> existingSources, string incomingSource)
	{
		string text = Channel(incomingSource);
		foreach (string existingSource in existingSources)
		{
			string text2 = Channel(existingSource);
			if ((text2 == "server-hit" && text == "observer-hit") || (text2 == "observer-hit" && text == "server-hit") || (text2 == "money-authority" && text == "money-observer") || (text2 == "money-observer" && text == "money-authority") || (text2 == "bait" && text == "rod-state") || (text2 == "rod-state" && text == "bait"))
			{
				return true;
			}
		}
		return false;
	}

	private static string Channel(string source)
	{
		switch (source)
		{
		case "Server.HitCreature":
		case "Server.HitPlayer":
			return "server-hit";
		case "Creature.ObserverHit":
		case "Creature.ObserverExplosionHit":
		case "PlayerVitals.ObserverHit":
			return "observer-hit";
		case "MoneyManager.AddMoney":
		case "MoneyManager.RemoveMoney":
		case "MoneyManager.SellItem":
			return "money-authority";
		case "money_correlation":
			return "money-observer";
		case "Bait.AddItemOnBait":
			return "bait";
		case "Item.OnAttachedRodChange":
			return "rod-state";
		default:
			return source ?? string.Empty;
		}
	}
}
internal sealed class PendingFishingCatch
{
	internal object Player;

	internal object Item;

	internal bool Authoritative;
}
internal sealed class FishingCatchTracker
{
	private readonly Dictionary<string, PendingFishingCatch> _pending = new Dictionary<string, PendingFishingCatch>(StringComparer.Ordinal);

	internal void Begin(string rodKey, object player, object item, bool authoritative)
	{
		if (!string.IsNullOrEmpty(rodKey))
		{
			_pending[rodKey] = new PendingFishingCatch
			{
				Player = player,
				Item = item,
				Authoritative = authoritative
			};
		}
	}

	internal bool TryConfirm(string rodKey, out PendingFishingCatch pending)
	{
		pending = null;
		if (string.IsNullOrEmpty(rodKey) || !_pending.TryGetValue(rodKey, out pending))
		{
			return false;
		}
		_pending.Remove(rodKey);
		return true;
	}

	internal bool Cancel(string rodKey)
	{
		if (!string.IsNullOrEmpty(rodKey))
		{
			return _pending.Remove(rodKey);
		}
		return false;
	}
}
internal static class FishingLandingRule
{
	internal const float RequiredSecondsAboveWater = 0.2f;

	internal static bool IsReached(float currentSeconds, float fixedDeltaSeconds)
	{
		return currentSeconds + fixedDeltaSeconds >= 0.2f;
	}
}
internal sealed class GameIntrospection
{
	private readonly Type _playerType = AccessTools.TypeByName("Player");

	internal bool HasPlayerType => _playerType != null;

	internal IList<object> FindPlayers()
	{
		List<object> list = new List<object>();
		if (_playerType == null)
		{
			return list;
		}
		try
		{
			Object[] array = Object.FindObjectsOfType(_playerType);
			foreach (Object val in array)
			{
				if (val != (Object)null)
				{
					list.Add(val);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.LogWarning("Player discovery failed: " + ex.Message);
		}
		return list;
	}

	internal PlayerIdentity GetPlayerIdentity(object player)
	{
		if (IsNull(player))
		{
			return null;
		}
		ulong num = ToUInt64(GetMember(player, "SteamID"));
		string text = Convert.ToString(GetMember(player, "SteamName"), CultureInfo.InvariantCulture);
		string objectId = GetObjectId(player);
		if (string.IsNullOrWhiteSpace(text))
		{
			text = ((num != 0L) ? ("Steam " + num.ToString(CultureInfo.InvariantCulture)) : ("Player " + objectId));
		}
		return new PlayerIdentity
		{
			Key = ((num != 0L) ? ("steam:" + num.ToString(CultureInfo.InvariantCulture)) : ("network:" + objectId)),
			SteamId = num,
			Name = text
		};
	}

	internal object GetPlayerFromVitals(object playerVitals)
	{
		return GetMember(playerVitals, "_player") ?? GetMember(playerVitals, "Player");
	}

	internal object GetPlayerFromDying(object playerDying)
	{
		return GetMember(playerDying, "_player") ?? GetMember(playerDying, "Player");
	}

	internal object GetFishingRodFromBait(object bait)
	{
		return GetMember(bait, "FishingRod") ?? GetMember(bait, "_fishingRod");
	}

	internal object GetPlayerFromBait(object bait)
	{
		return GetPlayerFromRod(GetFishingRodFromBait(bait));
	}

	internal object GetPlayerFromRod(object rod)
	{
		return GetMember(rod, "Holder") ?? GetMember(rod, "SyncedHolder") ?? GetMember(rod, "LastHolder") ?? GetMember(rod, "LastPlayer");
	}

	internal object GetAttachedRod(object item)
	{
		return GetMember(item, "AttachedRod") ?? GetMember(item, "_rodAttachedTo");
	}

	internal object GetItemFromBait(object bait)
	{
		return GetMember(bait, "ServerItemOnBait") ?? GetMember(bait, "ItemOnBait");
	}

	internal object GetItemFromRod(object rod)
	{
		return GetItemFromBait(GetMember(rod, "Bait") ?? GetMember(rod, "_bait"));
	}

	internal bool IsBaitAtLandingThreshold(object bait)
	{
		object member = GetMember(bait, "_timeAboveWater");
		float currentSeconds;
		try
		{
			currentSeconds = ((member == null) ? 0f : Convert.ToSingle(member, CultureInfo.InvariantCulture));
		}
		catch
		{
			return false;
		}
		return FishingLandingRule.IsReached(currentSeconds, Time.fixedDeltaTime);
	}

	internal bool IsSameObject(object first, object second)
	{
		if (IsNull(first) || IsNull(second))
		{
			return false;
		}
		if (first != second)
		{
			return string.Equals(GetObjectId(first), GetObjectId(second), StringComparison.Ordinal);
		}
		return true;
	}

	internal bool IsFishItem(object item)
	{
		if (IsNull(item))
		{
			return false;
		}
		return !IsNull(GetMember(item, "Fish"));
	}

	internal int GetItemWorth(object item)
	{
		object member = GetMember(item, "TotalWorth");
		try
		{
			return (member != null) ? Convert.ToInt32(member, CultureInfo.InvariantCulture) : 0;
		}
		catch
		{
			return 0;
		}
	}

	internal object GetItemLastHolder(object item)
	{
		return GetMember(item, "LastHolder") ?? GetMember(item, "LastPlayer") ?? GetMember(item, "Holder");
	}

	internal object GetDeadPlayerHolder(object deadPlayer)
	{
		return GetMember(deadPlayer, "Holder") ?? GetMember(deadPlayer, "_holder") ?? GetMember(deadPlayer, "SyncedHolder");
	}

	internal bool IsPlayerDead(object player)
	{
		if (IsNull(player))
		{
			return false;
		}
		object target = GetMember(player, "Vitals") ?? GetMember(player, "_vitals");
		object obj = GetMember(target, "Health") ?? GetMember(target, "_health") ?? GetMember(target, "_syncedHealth");
		try
		{
			return obj != null && Convert.ToInt32(obj, CultureInfo.InvariantCulture) <= 0;
		}
		catch
		{
			return false;
		}
	}

	internal bool IsPlayerAlive(object player)
	{
		if (IsNull(player))
		{
			return false;
		}
		object target = GetMember(player, "Vitals") ?? GetMember(player, "_vitals");
		object obj = GetMember(target, "Health") ?? GetMember(target, "_health") ?? GetMember(target, "_syncedHealth");
		try
		{
			return obj != null && Convert.ToInt32(obj, CultureInfo.InvariantCulture) > 0;
		}
		catch
		{
			return false;
		}
	}

	internal string GetItemName(object item)
	{
		if (IsNull(item))
		{
			return string.Empty;
		}
		try
		{
			MethodInfo methodInfo = AccessTools.Method(item.GetType(), "GetName", Type.EmptyTypes, (Type[])null);
			if (methodInfo != null)
			{
				string text = Convert.ToString(methodInfo.Invoke(item, null), CultureInfo.InvariantCulture);
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
		}
		catch
		{
		}
		Object val = (Object)((item is Object) ? item : null);
		if (val != (Object)null && !string.IsNullOrWhiteSpace(val.name))
		{
			return val.name;
		}
		return item.GetType().Name;
	}

	internal string GetObjectId(object value)
	{
		if (IsNull(value))
		{
			return string.Empty;
		}
		object member = GetMember(value, "ObjectId");
		if (member != null)
		{
			return "net:" + Convert.ToString(member, CultureInfo.InvariantCulture);
		}
		Object val = (Object)((value is Object) ? value : null);
		if (val != (Object)null)
		{
			return "unity:" + val.GetInstanceID().ToString(CultureInfo.InvariantCulture);
		}
		return "runtime:" + value.GetHashCode().ToString(CultureInfo.InvariantCulture);
	}

	internal bool IsServerObject(object value)
	{
		object obj = GetMember(value, "IsServerStarted") ?? GetMember(value, "IsServerInitialized");
		if (obj is bool)
		{
			return (bool)obj;
		}
		Type type = AccessTools.TypeByName("FishNet.InstanceFinder");
		object obj2 = GetStaticMember(type, "IsServerStarted") ?? GetStaticMember(type, "IsServer");
		if (obj2 is bool)
		{
			return (bool)obj2;
		}
		return false;
	}

	internal bool IsOwnedByLocalClient(object player)
	{
		object member = GetMember(GetMember(player, "Owner"), "IsLocalClient");
		if (member is bool)
		{
			return (bool)member;
		}
		return false;
	}

	internal static bool IsNull(object value)
	{
		if (value == null)
		{
			return true;
		}
		Object val = (Object)((value is Object) ? value : null);
		if (val != null)
		{
			return val == (Object)null;
		}
		return false;
	}

	private static object GetMember(object target, string name)
	{
		return ReflectionMemberAccessor.GetValue(target, name);
	}

	private static object GetStaticMember(Type type, string name)
	{
		return ReflectionMemberAccessor.GetStaticValue(type, name);
	}

	private static ulong ToUInt64(object value)
	{
		if (value == null)
		{
			return 0uL;
		}
		try
		{
			return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
		}
		catch
		{
			return 0uL;
		}
	}
}
internal sealed class HudPanelState
{
	internal MetricKind Metric;

	internal float X;

	internal float Y;

	internal float Width;

	internal float Height;
}
internal sealed class HudPanelCollection
{
	internal const int MaximumPanels = 5;

	internal const float CompactDefaultWidthPixels = 320f;

	internal const float CompactDefaultHeightPixels = 158f;

	internal const float CompactDefaultMarginPixels = 12f;

	private const float ScreenEdge = 0.002f;

	private const float MinimumPanelSize = 0.07f;

	private readonly List<HudPanelState> _panels = new List<HudPanelState>();

	internal IList<HudPanelState> Panels => _panels;

	internal int SelectedIndex { get; private set; }

	internal bool NeedsCompactDefault { get; private set; }

	internal HudPanelCollection(string serialized)
	{
		Parse(serialized);
		if (_panels.Count == 0)
		{
			_panels.Add(DefaultPanel());
			NeedsCompactDefault = true;
			return;
		}
		NeedsCompactDefault = IsLegacyDefault(_panels) || IsBrokenStartupDefault(_panels);
		if (_panels.Count > 1)
		{
			PackAttached();
		}
	}

	internal bool ApplyCompactDefault(float screenWidth, float screenHeight)
	{
		if (!NeedsCompactDefault || _panels.Count != 1)
		{
			return false;
		}
		if (screenWidth < 320f || screenHeight < 158f)
		{
			return false;
		}
		HudPanelState hudPanelState = _panels[0];
		hudPanelState.Width = Math.Max(0.07f, Math.Min(0.8f, 320f / screenWidth));
		hudPanelState.Height = Math.Max(0.07f, Math.Min(0.8f, 158f / screenHeight));
		hudPanelState.X = Math.Max(0.002f, 1f - hudPanelState.Width - 12f / screenWidth);
		hudPanelState.Y = Math.Max(0.002f, 12f / screenHeight);
		NeedsCompactDefault = false;
		return true;
	}

	internal bool Select(int index)
	{
		if (index < 0 || index >= _panels.Count)
		{
			return false;
		}
		SelectedIndex = index;
		return true;
	}

	internal bool SetMetric(int index, MetricKind metric)
	{
		if (!Select(index))
		{
			return false;
		}
		_panels[index].Metric = MetricCatalog.NormalizeVisible(metric);
		return true;
	}

	internal bool MoveAll(float deltaX, float deltaY)
	{
		if (_panels.Count == 0)
		{
			return false;
		}
		float num = _panels.Min((HudPanelState panel) => panel.X);
		float num2 = _panels.Min((HudPanelState panel) => panel.Y);
		float num3 = _panels.Max((HudPanelState panel) => panel.X + panel.Width);
		float num4 = _panels.Max((HudPanelState panel) => panel.Y + panel.Height);
		deltaX = Math.Max(0.002f - num, Math.Min(0.998f - num3, deltaX));
		deltaY = Math.Max(0.002f - num2, Math.Min(0.998f - num4, deltaY));
		if (Math.Abs(deltaX) < 1E-06f && Math.Abs(deltaY) < 1E-06f)
		{
			return false;
		}
		foreach (HudPanelState panel in _panels)
		{
			panel.X += deltaX;
			panel.Y += deltaY;
		}
		return true;
	}

	internal bool ResizeAll(float requestedWidth, float requestedHeight)
	{
		if (_panels.Count == 0)
		{
			return false;
		}
		HudPanelState hudPanelState = _panels[0];
		float val = 0.998f - hudPanelState.X;
		float val2 = (0.998f - hudPanelState.Y) / (float)_panels.Count;
		float width = Math.Max(0.07f, Math.Min(Math.Min(0.8f, val), requestedWidth));
		float height = Math.Max(0.07f, Math.Min(Math.Min(0.8f, val2), requestedHeight));
		foreach (HudPanelState panel in _panels)
		{
			panel.Width = width;
			panel.Height = height;
		}
		PackAttached();
		return true;
	}

	internal bool AddAfterSelected()
	{
		return AddAfter(SelectedIndex);
	}

	internal bool AddAfter(int index)
	{
		if (_panels.Count >= 5 || index < 0 || index >= _panels.Count)
		{
			return false;
		}
		HudPanelState hudPanelState = _panels[index];
		HudPanelState item = new HudPanelState
		{
			Metric = MetricCatalog.Cycle(hudPanelState.Metric, 1),
			X = hudPanelState.X,
			Y = hudPanelState.Y + hudPanelState.Height,
			Width = hudPanelState.Width,
			Height = hudPanelState.Height
		};
		_panels.Insert(index + 1, item);
		SelectedIndex = index + 1;
		FitHeightAndPackAttached();
		return true;
	}

	internal bool RemoveSelected()
	{
		return RemoveNewest();
	}

	internal bool RemoveNewest()
	{
		if (_panels.Count <= 1)
		{
			return false;
		}
		_panels.RemoveAt(_panels.Count - 1);
		SelectedIndex = Math.Min(SelectedIndex, _panels.Count - 1);
		PackAttached();
		return true;
	}

	internal bool Remove(int index)
	{
		if (_panels.Count <= 1 || index < 0 || index >= _panels.Count)
		{
			return false;
		}
		_panels.RemoveAt(index);
		SelectedIndex = Math.Min(index, _panels.Count - 1);
		PackAttached();
		return true;
	}

	internal string Serialize()
	{
		return string.Join(";", _panels.Select(delegate(HudPanelState panel)
		{
			string[] array = new string[5];
			int metric = (int)panel.Metric;
			array[0] = metric.ToString(CultureInfo.InvariantCulture);
			array[1] = panel.X.ToString("0.#####", CultureInfo.InvariantCulture);
			array[2] = panel.Y.ToString("0.#####", CultureInfo.InvariantCulture);
			array[3] = panel.Width.ToString("0.#####", CultureInfo.InvariantCulture);
			array[4] = panel.Height.ToString("0.#####", CultureInfo.InvariantCulture);
			return string.Join(",", array);
		}).ToArray());
	}

	private void Parse(string serialized)
	{
		if (string.IsNullOrWhiteSpace(serialized))
		{
			return;
		}
		string[] array = serialized.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
		for (int i = 0; i < array.Length; i++)
		{
			string[] array2 = array[i].Split(new char[1] { ',' });
			if ((array2.Length == 5 || array2.Length == 7) && int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) && float.TryParse(array2[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) && float.TryParse(array2[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result5) && MetricCatalog.IsDefined(result))
			{
				MetricKind metric = MetricCatalog.NormalizeVisible((MetricKind)result);
				_panels.Add(new HudPanelState
				{
					Metric = metric,
					X = Clamp01(result2),
					Y = Clamp01(result3),
					Width = Math.Max(0.07f, Math.Min(0.8f, result4)),
					Height = Math.Max(0.07f, Math.Min(0.8f, result5))
				});
				if (_panels.Count == 5)
				{
					break;
				}
			}
		}
	}

	private static HudPanelState DefaultPanel()
	{
		return new HudPanelState
		{
			Metric = MetricKind.CreatureDamage,
			X = 0.765f,
			Y = 0.02f,
			Width = 0.22f,
			Height = 0.3f
		};
	}

	private void FitHeightAndPackAttached()
	{
		if (_panels.Count == 0)
		{
			return;
		}
		float val = (0.998f - _panels[0].Y) / (float)_panels.Count;
		float height = Math.Max(0.07f, Math.Min(_panels[0].Height, val));
		foreach (HudPanelState panel in _panels)
		{
			panel.Height = height;
		}
		PackAttached();
	}

	private void PackAttached()
	{
		if (_panels.Count != 0)
		{
			float x = _panels[0].X;
			float num = _panels.Sum((HudPanelState panel) => panel.Height);
			float num2 = Math.Max(0.002f, Math.Min(_panels[0].Y, 0.998f - num));
			for (int num3 = 0; num3 < _panels.Count; num3++)
			{
				_panels[num3].X = x;
				_panels[num3].Y = num2;
				num2 += _panels[num3].Height;
			}
		}
	}

	private static bool IsLegacyDefault(IList<HudPanelState> panels)
	{
		if (panels.Count != 1)
		{
			return false;
		}
		HudPanelState hudPanelState = panels[0];
		if (hudPanelState.Metric == MetricKind.CreatureDamage && Approximately(hudPanelState.X, 0.765f) && Approximately(hudPanelState.Y, 0.02f) && Approximately(hudPanelState.Width, 0.22f))
		{
			return Approximately(hudPanelState.Height, 0.3f);
		}
		return false;
	}

	private static bool IsBrokenStartupDefault(IList<HudPanelState> panels)
	{
		if (panels.Count != 1)
		{
			return false;
		}
		HudPanelState hudPanelState = panels[0];
		if (hudPanelState.Metric == MetricKind.CreatureDamage && Approximately(hudPanelState.X, 0.002f) && Approximately(hudPanelState.Y, 1f) && Approximately(hudPanelState.Width, 0.8f))
		{
			return Approximately(hudPanelState.Height, 0.8f);
		}
		return false;
	}

	private static bool Approximately(float first, float second)
	{
		return Math.Abs(first - second) < 1E-05f;
	}

	private static float Clamp01(float value)
	{
		return Math.Max(0f, Math.Min(1f, value));
	}
}
internal static class MetricCatalog
{
	internal static readonly MetricKind[] Visible = new MetricKind[8]
	{
		MetricKind.CreatureDamage,
		MetricKind.PlayerDamage,
		MetricKind.MoneyEarned,
		MetricKind.MoneySpent,
		MetricKind.FishCaught,
		MetricKind.TeammatesRevived,
		MetricKind.PlayerKills,
		MetricKind.PlayerDeaths
	};

	internal static MetricKind NormalizeVisible(MetricKind metric)
	{
		if (IndexOf(metric) < 0)
		{
			return MetricKind.PlayerDeaths;
		}
		return metric;
	}

	internal static MetricKind Cycle(MetricKind current, int delta)
	{
		int num = IndexOf(NormalizeVisible(current));
		int num2 = Visible.Length;
		return Visible[(num + delta % num2 + num2) % num2];
	}

	internal static bool IsDefined(int value)
	{
		return Enum.IsDefined(typeof(MetricKind), value);
	}

	private static int IndexOf(MetricKind metric)
	{
		for (int i = 0; i < Visible.Length; i++)
		{
			if (Visible[i] == metric)
			{
				return i;
			}
		}
		return -1;
	}
}
internal enum MetricKind
{
	CreatureDamage,
	PlayerDamage,
	MoneyEarned,
	MoneySpent,
	FishCaught,
	TimesRevived,
	TeammatesRevived,
	PlayerKills,
	PlayerDeaths
}
internal enum Confidence
{
	Exact,
	Inferred,
	Corrected,
	Unknown
}
internal sealed class PlayerIdentity
{
	internal string Key;

	internal ulong SteamId;

	internal string Name;
}
internal sealed class MetricValue
{
	internal long Exact;

	internal long Inferred;

	internal long Total => Exact + Inferred;
}
internal sealed class PlayerStats
{
	internal string Key;

	internal ulong SteamId;

	internal string Name;

	internal bool IsPresent;

	internal DateTime LastSeenUtc;

	internal readonly Dictionary<MetricKind, MetricValue> Metrics = new Dictionary<MetricKind, MetricValue>();

	internal PlayerStats()
	{
		foreach (MetricKind value in Enum.GetValues(typeof(MetricKind)))
		{
			Metrics[value] = new MetricValue();
		}
	}
}
internal sealed class PlayerMetricRow
{
	internal string Key;

	internal ulong SteamId;

	internal string Name;

	internal bool IsPresent;

	internal long Exact;

	internal long Inferred;

	internal long Total => Exact + Inferred;
}
internal sealed class PatchRegistrar
{
	private readonly Harmony _harmony;

	private readonly List<string> _missing = new List<string>();

	private int _patched;

	internal PatchRegistrar(Harmony harmony)
	{
		_harmony = harmony;
	}

	internal string ApplyAll()
	{
		PatchPrefix("Creature", "RpcLogic___ObserverHit___", "CreatureObserverHit", required: true);
		PatchPrefix("Creature", "RpcLogic___ObserverExplosionHit___", "CreatureObserverExplosionHit", required: true);
		PatchPrefix("PlayerVitals", "RpcLogic___ObserverHit___", "PlayerObserverHit", required: true);
		PatchPrefix("Server", "RpcLogic___HitCreature___", "ServerHitCreature", required: false);
		PatchNamePrefixWithState("Server", "RpcLogic___HitPlayer___", "ServerHitPlayerPrefix", "ServerHitPlayerPostfix", required: false);
		PatchExact("MoneyManager", "AddMoney", 2, "AddMoney", required: false);
		PatchExact("MoneyManager", "RemoveMoney", 2, "RemoveMoney", required: false);
		PatchExact("MoneyManager", "SellItem", 1, "SellItem", required: true);
		PatchExact("MoneyManager", "OnChangeMoney", 3, "MoneyChanged", required: false);
		PatchPrefix("MoneyManager", "RpcLogic___ObserverMoneySound___", "MoneyCue", required: false);
		PatchExact("CreatureManager", "HookItem", 2, "CreatureManagerHookItem", required: true);
		PatchExact("Item", "InitializeBait", 1, "ItemInitializeBait", required: false);
		PatchExactPrefix("Bait", "IncreaseAboveWaterTime", 0, "BaitIncreaseAboveWaterTime", required: true);
		PatchExactPrefix("Item", "PickUp", 3, "ItemPickUp", required: false);
		PatchExactPrefix("FishingRod", "ReleaseItem", 1, "FishingRodReleaseItem", required: true);
		PatchNamePrefixAsPrefix("Server", "RpcLogic___ResurrectPlayer___", "ServerResurrectPlayer", required: true);
		PatchExact("PlayerDying", "ServerDie", 1, "PlayerDied", required: true);
		if (_missing.Count != 0)
		{
			return _patched + " collectors active; missing: " + string.Join(", ", _missing.ToArray());
		}
		return "all " + _patched + " collectors active";
	}

	private void PatchPrefix(string typeName, string methodPrefix, string callback, bool required)
	{
		Type type = AccessTools.TypeByName(typeName);
		MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
			where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
			select method).ToArray());
		if (array.Length == 0)
		{
			Missing(typeName + "." + methodPrefix, required);
			return;
		}
		MethodInfo[] array2 = array;
		foreach (MethodInfo target in array2)
		{
			Patch(target, callback);
		}
	}

	private void PatchExact(string typeName, string methodName, int parameterCount, string callback, bool required)
	{
		Type type = AccessTools.TypeByName(typeName);
		MethodInfo methodInfo = ((type == null) ? null : type.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo candidate) => candidate.Name == methodName && candidate.GetParameters().Length == parameterCount));
		if (methodInfo == null)
		{
			Missing(typeName + "." + methodName, required);
		}
		else
		{
			Patch(methodInfo, callback);
		}
	}

	private void PatchExactPrefix(string typeName, string methodName, int parameterCount, string callback, bool required)
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Expected O, but got Unknown
		Type type = AccessTools.TypeByName(typeName);
		MethodInfo methodInfo = ((type == null) ? null : type.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo candidate) => candidate.Name == methodName && candidate.GetParameters().Length == parameterCount));
		if (methodInfo == null)
		{
			Missing(typeName + "." + methodName, required);
			return;
		}
		try
		{
			MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
			_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_patched++;
			Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (prefix)");
		}
		catch (Exception ex)
		{
			_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
			Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
		}
	}

	private void PatchNamePrefixAsPrefix(string typeName, string methodPrefix, string callback, bool required)
	{
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Expected O, but got Unknown
		Type type = AccessTools.TypeByName(typeName);
		MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
			where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
			select method).ToArray());
		if (array.Length == 0)
		{
			Missing(typeName + "." + methodPrefix, required);
			return;
		}
		MethodInfo[] array2 = array;
		foreach (MethodInfo methodInfo in array2)
		{
			try
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched++;
				Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (prefix)");
			}
			catch (Exception ex)
			{
				_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
				Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
			}
		}
	}

	private void PatchNamePrefixWithState(string typeName, string methodPrefix, string prefixCallback, string postfixCallback, bool required)
	{
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Expected O, but got Unknown
		//IL_00bc: Expected O, but got Unknown
		Type type = AccessTools.TypeByName(typeName);
		MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
			where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
			select method).ToArray());
		if (array.Length == 0)
		{
			Missing(typeName + "." + methodPrefix, required);
			return;
		}
		MethodInfo[] array2 = array;
		foreach (MethodInfo methodInfo in array2)
		{
			try
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), prefixCallback, (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(typeof(PatchCallbacks), postfixCallback, (Type[])null, (Type[])null);
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched++;
				Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (state transition)");
			}
			catch (Exception ex)
			{
				_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
				Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
			}
		}
	}

	private void Patch(MethodInfo target, string callback)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
			_harmony.Patch((MethodBase)target, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_patched++;
			Plugin.LogDebug("Patched " + target.DeclaringType.FullName + "." + target.Name);
		}
		catch (Exception ex)
		{
			_missing.Add(target.DeclaringType.FullName + "." + target.Name);
			Plugin.LogWarning("Patch failed for " + target.Name + ": " + ex.Message);
		}
	}

	private void Missing(string name, bool required)
	{
		_missing.Add(name + (required ? " (required)" : string.Empty));
		Plugin.LogWarning("Collector entry not found: " + name);
	}
}
internal static class PatchCallbacks
{
	internal static void CreatureObserverHit(object __instance, object[] __args)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 0), __instance, IntArg(__args, 3), "Creature.ObserverHit", Confidence.Exact);
		});
	}

	internal static void CreatureObserverExplosionHit(object __instance, object[] __args)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 0), __instance, IntArg(__args, 2), "Creature.ObserverExplosionHit", Confidence.Exact);
		});
	}

	internal static void PlayerObserverHit(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object playerFromVitals = Plugin.Current.Game.GetPlayerFromVitals(__instance);
			Plugin.Current.Ledger.RecordPlayerDamage(Arg(__args, 0), playerFromVitals, IntArg(__args, 3), "PlayerVitals.ObserverHit", Confidence.Exact, Convert.ToString(Arg(__args, 4)));
		});
	}

	internal static void ServerHitCreature(object __instance, object[] __args)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 1), Arg(__args, 0), IntArg(__args, 2), "Server.HitCreature", Confidence.Exact);
		});
	}

	internal static void ServerHitPlayerPrefix(object[] __args, ref PlayerHitState __state)
	{
		try
		{
			object obj = Arg(__args, 0);
			__state = new PlayerHitState
			{
				Victim = obj,
				Attacker = Arg(__args, 5),
				WasAlive = ((Object)(object)Plugin.Current != (Object)null && Plugin.Current.Game != null && Plugin.Current.Game.IsPlayerAlive(obj))
			};
		}
		catch (Exception ex)
		{
			__state = null;
			Plugin.LogWarning("Player kill transition prefix failed: " + ex.Message);
		}
	}

	internal static void ServerHitPlayerPostfix(object __instance, object[] __args, PlayerHitState __state)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.RecordPlayerDamage(Arg(__args, 5), Arg(__args, 0), IntArg(__args, 1), "Server.HitPlayer", Confidence.Exact, Convert.ToString(Arg(__args, 4)));
			bool hasAttacker = __state != null && !GameIntrospection.IsNull(__state.Attacker);
			bool isDeadAfter = __state != null && Plugin.Current.Game.IsPlayerDead(__state.Victim);
			if (__state != null && PlayerKillTransitionRule.IsPlayerKill(__state.WasAlive, isDeadAfter, hasAttacker))
			{
				Plugin.Current.Ledger.RecordPlayerKill(__state.Attacker, __state.Victim, "Server.HitPlayerLethalTransition");
			}
		});
	}

	internal static void AddMoney(object __instance, object[] __args)
	{
		Safe(delegate
		{
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				Plugin.Current.Ledger.RecordAuthoritativeMoney(Arg(__args, 1), Math.Abs(IntArg(__args, 0)), increase: true, "MoneyManager.AddMoney");
			}
		});
	}

	internal static void RemoveMoney(object __instance, object[] __args)
	{
		Safe(delegate
		{
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				Plugin.Current.Ledger.RecordAuthoritativeMoney(Arg(__args, 1), Math.Abs(IntArg(__args, 0)), increase: false, "MoneyManager.RemoveMoney");
			}
		});
	}

	internal static void SellItem(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object item = Arg(__args, 0);
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				Plugin.Current.Ledger.RecordAuthoritativeMoney(Plugin.Current.Game.GetItemLastHolder(item), Math.Abs(Plugin.Current.Game.GetItemWorth(item)), increase: true, "MoneyManager.SellItem");
			}
		});
	}

	internal static void MoneyChanged(object __instance, object[] __args)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.ObserveBalanceChange(IntArg(__args, 0), IntArg(__args, 1), BoolArg(__args, 2));
		});
	}

	internal static void MoneyCue(object __instance, object[] __args)
	{
		Safe(delegate
		{
			Plugin.Current.Ledger.ObserveMoneyCue(BoolArg(__args, 0), Arg(__args, 1));
		});
	}

	internal static void CreatureManagerHookItem(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object bait = Arg(__args, 1);
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				object fishingRodFromBait = Plugin.Current.Game.GetFishingRodFromBait(bait);
				object item = Plugin.Current.Game.GetItemFromBait(bait) ?? Arg(__args, 0);
				Plugin.Current.Ledger.BeginFishingCatch(Plugin.Current.Game.GetPlayerFromRod(fishingRodFromBait), item, fishingRodFromBait, authoritative: true);
			}
		});
	}

	internal static void ItemInitializeBait(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object rod = Arg(__args, 0);
			object playerFromRod = Plugin.Current.Game.GetPlayerFromRod(rod);
			if (!Plugin.Current.Game.IsServerObject(__instance) && Plugin.Current.Game.IsOwnedByLocalClient(playerFromRod))
			{
				Plugin.Current.Ledger.BeginFishingCatch(playerFromRod, __instance, rod, authoritative: false);
			}
		});
	}

	internal static void BaitIncreaseAboveWaterTime(object __instance)
	{
		Safe(delegate
		{
			if (Plugin.Current.Game.IsBaitAtLandingThreshold(__instance))
			{
				object fishingRodFromBait = Plugin.Current.Game.GetFishingRodFromBait(__instance);
				Plugin.Current.Ledger.ConfirmFishingCatch(fishingRodFromBait, Plugin.Current.Game.GetItemFromBait(__instance), "Bait.AboveWaterLanding");
			}
		});
	}

	internal static void ItemPickUp(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object attachedRod = Plugin.Current.Game.GetAttachedRod(__instance);
			if (!GameIntrospection.IsNull(attachedRod))
			{
				Plugin.Current.Ledger.ConfirmFishingCatch(attachedRod, __instance, "Item.PickUpWhileHooked");
			}
		});
	}

	internal static void FishingRodReleaseItem(object __instance, object[] __args)
	{
		Safe(delegate
		{
			object first = Arg(__args, 0);
			object itemFromRod = Plugin.Current.Game.GetItemFromRod(__instance);
			if (Plugin.Current.Game.IsSameObject(first, itemFromRod))
			{
				Plugin.Current.Ledger.CancelFishingCatch(__instance);
			}
		});
	}

	internal static void ServerResurrectPlayer(object __instance, object[] __args)
	{
		Safe(delegate
		{
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				Plugin.Current.Ledger.RecordResurrection(Arg(__args, 0), Arg(__args, 1), "Server.ResurrectPlayer");
			}
		});
	}

	internal static void PlayerDied(object __instance)
	{
		Safe(delegate
		{
			if (Plugin.Current.Game.IsServerObject(__instance))
			{
				Plugin.Current.Ledger.RecordDeath(Plugin.Current.Game.GetPlayerFromDying(__instance), "PlayerDying.ServerDie");
			}
		});
	}

	private static object Arg(object[] args, int index)
	{
		if (args == null || index < 0 || index >= args.Length)
		{
			return null;
		}
		return args[index];
	}

	private static int IntArg(object[] args, int index)
	{
		object obj = Arg(args, index);
		try
		{
			return (obj != null) ? Convert.ToInt32(obj) : 0;
		}
		catch
		{
			return 0;
		}
	}

	private static bool BoolArg(object[] args, int index)
	{
		object obj = Arg(args, index);
		if (obj is bool)
		{
			return (bool)obj;
		}
		return false;
	}

	private static void Safe(Action action)
	{
		try
		{
			if ((Object)(object)Plugin.Current != (Object)null && Plugin.Current.Ledger != null)
			{
				action();
			}
		}
		catch (Exception ex)
		{
			Plugin.LogWarning("Collector callback failed: " + ex.Message);
		}
	}
}
internal sealed class PlayerHitState
{
	internal object Victim;

	internal object Attacker;

	internal bool WasAlive;
}
internal sealed class PlayerColorAssigner
{
	private readonly Dictionary<string, int> _assignments = new Dictionary<string, int>(StringComparer.Ordinal);

	private int _nextIndex;

	internal int GetIndex(string playerKey)
	{
		playerKey = playerKey ?? string.Empty;
		if (_assignments.TryGetValue(playerKey, out var value))
		{
			return value;
		}
		int num = _nextIndex++;
		_assignments[playerKey] = num;
		return num;
	}
}
internal static class PlayerKillTransitionRule
{
	internal static bool IsPlayerKill(bool wasAlive, bool isDeadAfter, bool hasAttacker)
	{
		return wasAlive && isDeadAfter && hasAttacker;
	}
}
[BepInPlugin("com.corntaxi.howtofish.checkmystats", "CheckMyStats", "1.0.0")]
[BepInProcess("How to Fish.exe")]
public sealed class Plugin : BaseUnityPlugin
{
	internal const string PluginGuid = "com.corntaxi.howtofish.checkmystats";

	internal const string PluginName = "CheckMyStats";

	internal const string PluginVersion = "1.0.0";

	private const string ExpectedAssemblyHash = "FA8C6F47874E69FE07B9C978F35CC05372DF2BDD3535DE5F5FAC355F999A5762";

	private Harmony _harmony;

	private StatsHud _hud;

	private ConfigEntry<KeyboardShortcut> _toggleHud;

	private ConfigEntry<KeyboardShortcut> _toggleMouseEdit;

	private ConfigEntry<KeyboardShortcut> _addPanel;

	private ConfigEntry<KeyboardShortcut> _removePanel;

	private ConfigEntry<bool> _hudEnabled;

	private ConfigEntry<string> _language;

	private ConfigEntry<float> _hudScale;

	private ConfigEntry<string> _panelLayout;

	private ConfigFile _settings;

	private float _nextDiscovery;

	private bool _shutdown;

	private StatsSyncService _sync;

	internal static Plugin Current { get; private set; }

	internal static ManualLogSource LogSource { get; private set; }

	internal GameIntrospection Game { get; private set; }

	internal StatsLedger Ledger { get; private set; }

	internal string CompatibilitySummary { get; private set; }

	internal bool IsFullyCompatible { get; private set; }

	private void Awake()
	{
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Expected O, but got Unknown
		Current = this;
		LogSource = ((BaseUnityPlugin)this).Logger;
		BindConfiguration();
		string sessionId = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 8);
		Game = new GameIntrospection();
		Ledger = new StatsLedger(Game, sessionId);
		_sync = new StatsSyncService(Ledger);
		string text = VerifyGameAssembly();
		_harmony = new Harmony("com.corntaxi.howtofish.checkmystats");
		string text2 = new PatchRegistrar(_harmony).ApplyAll();
		CompatibilitySummary = text + "; " + text2;
		IsFullyCompatible = text.StartsWith("verified", StringComparison.OrdinalIgnoreCase) && text2.StartsWith("all", StringComparison.OrdinalIgnoreCase);
		_hud = new StatsHud(Ledger, UseChinese, () => _hudScale.Value, _panelLayout.Value, SaveHudLayout);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("CheckMyStats 1.0.0 loaded. " + CompatibilitySummary));
	}

	private void BindConfiguration()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		//IL_0036: 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_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		_settings = new ConfigFile(Path.Combine(Paths.ConfigPath, "CheckMyStats.cfg"), true);
		_toggleMouseEdit = _settings.Bind<KeyboardShortcut>("Controls", "MouseEditKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Toggle mouse editing, dragging, and resizing.");
		_toggleHud = _settings.Bind<KeyboardShortcut>("Controls", "ToggleHudKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Show or hide all HUD panels.");
		_addPanel = _settings.Bind<KeyboardShortcut>("Controls", "AddPanelKey", new KeyboardShortcut((KeyCode)270, Array.Empty<KeyCode>()), "Add a HUD panel. Shift+= is always also supported.");
		_removePanel = _settings.Bind<KeyboardShortcut>("Controls", "RemovePanelKey", new KeyboardShortcut((KeyCode)269, Array.Empty<KeyCode>()), "Remove the newest HUD panel. Main keyboard minus is also supported.");
		_hudEnabled = _settings.Bind<bool>("HUD", "Enabled", true, "Show the multiplayer statistics HUD.");
		_hudScale = _settings.Bind<float>("HUD", "Scale", 1f, "HUD content scale from 0.75 to 2.0.");
		_language = _settings.Bind<string>("HUD", "Language", "Auto", "Auto, English, or Chinese.");
		_panelLayout = _settings.Bind<string>("HUD", "PanelLayout", string.Empty, "Saved metric, position, and size for up to five panels.");
		_settings.Save();
	}

	private void Update()
	{
		//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_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: 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_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		KeyboardShortcut value = _addPanel.Value;
		if (((KeyboardShortcut)(ref value)).IsDown() || MainKeyboardPlusDown())
		{
			_hud.AddPanel();
		}
		value = _removePanel.Value;
		if (((KeyboardShortcut)(ref value)).IsDown() || Input.GetKeyDown((KeyCode)45))
		{
			_hud.RemoveSelectedPanel();
		}
		value = _toggleMouseEdit.Value;
		if (((KeyboardShortcut)(ref value)).IsDown() && _hudEnabled.Value)
		{
			_hud.SetEditMode(!_hud.EditMode);
		}
		value = _toggleHud.Value;
		if (((KeyboardShortcut)(ref value)).IsDown())
		{
			_hudEnabled.Value = !_hudEnabled.Value;
			if (!_hudEnabled.Value)
			{
				_hud.SetEditMode(enabled: false);
			}
		}
		Ledger.Tick();
		if (_sync != null)
		{
			_sync.Tick();
		}
		if (Time.unscaledTime >= _nextDiscovery)
		{
			_nextDiscovery = Time.unscaledTime + 1f;
			Ledger.RefreshPlayers(Game.FindPlayers());
		}
	}

	private void LateUpdate()
	{
		if (_hud != null)
		{
			_hud.MaintainCursor();
		}
	}

	private void OnGUI()
	{
		if (_hudEnabled != null && _hudEnabled.Value && _hud != null)
		{
			_hud.Draw(IsFullyCompatible, CompatibilitySummary);
		}
	}

	private void OnApplicationQuit()
	{
		Shutdown();
	}

	private void OnDestroy()
	{
		Shutdown();
	}

	private void Shutdown()
	{
		if (!_shutdown)
		{
			_shutdown = true;
			if (_hud != null)
			{
				_hud.Shutdown();
			}
			if (_sync != null)
			{
				_sync.Shutdown();
			}
			if (Ledger != null)
			{
				Ledger.FlushPending();
			}
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}

	private bool MainKeyboardPlusDown()
	{
		if (Input.GetKeyDown((KeyCode)61))
		{
			if (!Input.GetKey((KeyCode)304))
			{
				return Input.GetKey((KeyCode)303);
			}
			return true;
		}
		return false;
	}

	private void SaveHudLayout(string serialized)
	{
		if (_panelLayout != null && !string.Equals(_panelLayout.Value, serialized, StringComparison.Ordinal))
		{
			_panelLayout.Value = serialized;
			if (_settings != null)
			{
				_settings.Save();
			}
		}
	}

	private string VerifyGameAssembly()
	{
		try
		{
			using FileStream inputStream = File.OpenRead(Path.Combine(Paths.GameRootPath, "How to Fish_Data", "Managed", "Assembly-CSharp.dll"));
			using SHA256 sHA = SHA256.Create();
			string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty);
			return string.Equals(text, "FA8C6F47874E69FE07B9C978F35CC05372DF2BDD3535DE5F5FAC355F999A5762", StringComparison.OrdinalIgnoreCase) ? ("verified Assembly-CSharp " + text.Substring(0, 12)) : ("unverified Assembly-CSharp " + text.Substring(0, 12));
		}
		catch (Exception ex)
		{
			return "assembly verification failed: " + ex.Message;
		}
	}

	private bool UseChinese()
	{
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Invalid comparison between Unknown and I4
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Invalid comparison between Unknown and I4
		string text = ((_language == null) ? "Auto" : _language.Value) ?? "Auto";
		if (text.Equals("Chinese", StringComparison.OrdinalIgnoreCase) || text.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		if (text.Equals("English", StringComparison.OrdinalIgnoreCase) || text.StartsWith("en", StringComparison.OrdinalIgnoreCase))
		{
			return false;
		}
		if ((int)Application.systemLanguage != 40)
		{
			return (int)Application.systemLanguage == 41;
		}
		return true;
	}

	internal static void LogWarning(string message)
	{
		if (LogSource != null)
		{
			LogSource.LogWarning((object)message);
		}
	}

	internal static void LogInfo(string message)
	{
		if (LogSource != null)
		{
			LogSource.LogInfo((object)message);
		}
	}

	internal static void LogDebug(string message)
	{
		if (LogSource != null)
		{
			LogSource.LogDebug((object)message);
		}
	}
}
internal static class ReflectionMemberAccessor
{
	private const BindingFlags InstanceFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private const BindingFlags StaticFlags = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private static readonly Dictionary<string, MemberInfo> Cache = new Dictionary<string, MemberInfo>(StringComparer.Ordinal);

	private static readonly object CacheLock = new object();

	internal static object GetValue(object target, string name)
	{
		if (target != null)
		{
			return Read(target.GetType(), target, name, isStatic: false, unwrapSyncVar: true);
		}
		return null;
	}

	internal static object GetStaticValue(Type type, string name)
	{
		if (!(type == null))
		{
			return Read(type, null, name, isStatic: true, unwrapSyncVar: false);
		}
		return null;
	}

	private static object Read(Type type, object target, string name, bool isStatic, bool unwrapSyncVar)
	{
		try
		{
			MemberInfo memberInfo = Find(type, name, isStatic);
			PropertyInfo propertyInfo = memberInfo as PropertyInfo;
			object obj;
			if (propertyInfo != null)
			{
				obj = propertyInfo.GetValue(target, null);
			}
			else
			{
				FieldInfo fieldInfo = memberInfo as FieldInfo;
				obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(target));
			}
			return unwrapSyncVar ? UnwrapSyncVar(obj) : obj;
		}
		catch
		{
			return null;
		}
	}

	private static MemberInfo Find(Type type, string name, bool isStatic)
	{
		string key = type.AssemblyQualifiedName + "|" + (isStatic ? "S|" : "I|") + name;
		lock (CacheLock)
		{
			if (Cache.TryGetValue(key, out var value))
			{
				return value;
			}
		}
		BindingFlags bindingAttr = (isStatic ? (BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
		MemberInfo memberInfo = null;
		Type type2 = type;
		while (type2 != null && memberInfo == null)
		{
			PropertyInfo property = type2.GetProperty(name, bindingAttr);
			memberInfo = ((!(property != null) || property.GetIndexParameters().Length != 0) ? ((MemberInfo)type2.GetField(name, bindingAttr)) : ((MemberInfo)property));
			type2 = type2.BaseType;
		}
		lock (CacheLock)
		{
			Cache[key] = memberInfo;
			return memberInfo;
		}
	}

	private static object UnwrapSyncVar(object value)
	{
		if (value == null)
		{
			return null;
		}
		Type type = value.GetType();
		if (!type.IsGenericType || !type.Name.StartsWith("SyncVar`", StringComparison.Ordinal) || !string.Equals(type.Namespace, "FishNet.Object.Synchronizing", StringComparison.Ordinal))
		{
			return value;
		}
		return Read(type, value, "Value", isStatic: false, unwrapSyncVar: false);
	}
}
internal sealed class StatsHud
{
	private readonly StatsLedger _ledger;

	private readonly Func<bool> _useChinese;

	private readonly Func<float> _scale;

	private readonly Action<string> _saveLayout;

	private readonly HudPanelCollection _panels;

	private readonly PlayerColorAssigner _playerColors = new PlayerColorAssigner();

	private GUIStyle _panel;

	private GUIStyle _selectedPanel;

	private GUIStyle _title;

	private GUIStyle _label;

	private GUIStyle _value;

	private GUIStyle _small;

	private Texture2D _background;

	private Texture2D _selectedBackground;

	private int _dragging = -1;

	private int _resizing = -1;

	private int _metricMenuPanel = -1;

	private Vector2 _metricMenuPosition;

	private CursorLockMode _previousCursorLock;

	private bool _previousCursorVisible;

	private bool _cursorCaptured;

	internal bool EditMode { get; private set; }

	internal StatsHud(StatsLedger ledger, Func<bool> useChinese, Func<float> scale, string layout, Action<string> saveLayout)
	{
		_ledger = ledger;
		_useChinese = useChinese;
		_scale = scale;
		_saveLayout = saveLayout;
		_panels = new HudPanelCollection(layout);
		if (_panels.ApplyCompactDefault(Screen.width, Screen.height) || _panels.Panels.Count > 1)
		{
			SaveLayout();
		}
	}

	internal void AddPanel()
	{
		if (_panels.AddAfterSelected())
		{
			SaveLayout();
		}
	}

	internal void RemoveSelectedPanel()
	{
		if (_panels.RemoveSelected())
		{
			SaveLayout();
		}
	}

	internal void SetEditMode(bool enabled)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		if (EditMode != enabled)
		{
			EditMode = enabled;
			_dragging = -1;
			_resizing = -1;
			_metricMenuPanel = -1;
			if (enabled)
			{
				_previousCursorLock = Cursor.lockState;
				_previousCursorVisible = Cursor.visible;
				_cursorCaptured = true;
				MaintainCursor();
			}
			else
			{
				RestoreCursor();
				SaveLayout();
			}
		}
	}

	internal void MaintainCursor()
	{
		if (EditMode)
		{
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
		}
	}

	internal void Shutdown()
	{
		SetEditMode(enabled: false);
	}

	internal void Draw(bool compatible, string compatibilitySummary)
	{
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		if (_panels.ApplyCompactDefault(Screen.width, Screen.height))
		{
			SaveLayout();
		}
		EnsureStyles();
		float scale = Mathf.Clamp(_scale(), 0.75f, 2f);
		UpdateFontSizes(scale);
		HandleActiveManipulation();
		int addAfter = -1;
		int remove = -1;
		int count = _panels.Panels.Count;
		for (int i = 0; i < count; i++)
		{
			HudPanelState hudPanelState = _panels.Panels[i];
			Rect rect = ToPixels(hudPanelState);
			ClampRect(ref rect);
			FromPixels(hudPanelState, rect);
			DrawPanel(i, hudPanelState, rect, scale, compatible, compatibilitySummary, ref addAfter, ref remove);
		}
		if (remove >= 0 && _panels.Remove(remove))
		{
			SaveLayout();
		}
		if (addAfter >= 0 && _panels.AddAfter(addAfter))
		{
			SaveLayout();
		}
		DrawMetricMenu(scale);
	}

	private void DrawPanel(int index, HudPanelState state, Rect rect, float scale, bool compatible, string compatibilitySummary, ref int addAfter, ref int remove)
	{
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: Unknown result type (might be due to invalid IL or missing references)
		//IL_0172: Unknown result type (might be due to invalid IL or missing references)
		//IL_0218: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0382: Unknown result type (might be due to invalid IL or missing references)
		//IL_0324: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
		if (EditMode && index == _panels.SelectedIndex)
		{
			GUI.Box(new Rect(((Rect)(ref rect)).x - 2f, ((Rect)(ref rect)).y - 2f, ((Rect)(ref rect)).width + 4f, ((Rect)(ref rect)).height + 4f), GUIContent.none, _selectedPanel);
		}
		GUI.Box(rect, GUIContent.none, _panel);
		float num = 12f * scale;
		float num2 = 30f * scale;
		float num3 = 27f * scale;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(((Rect)(ref rect)).x + num, ((Rect)(ref rect)).y + 8f * scale, ((Rect)(ref rect)).width - num * 2f, num2);
		Rect val2 = default(Rect);
		((Rect)(ref val2))..ctor(((Rect)(ref val)).xMax - num3 * 2f - 3f * scale, ((Rect)(ref val)).y, num3, ((Rect)(ref val)).height);
		Rect val3 = default(Rect);
		((Rect)(ref val3))..ctor(((Rect)(ref val2)).xMax + 3f * scale, ((Rect)(ref val)).y, num3, ((Rect)(ref val)).height);
		Rect val4 = (Rect)(EditMode ? new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y, Math.Max(40f, ((Rect)(ref val2)).x - ((Rect)(ref val)).x - 5f * scale), ((Rect)(ref val)).height) : val);
		if (EditMode)
		{
			if (GUI.Button(val2, "+"))
			{
				addAfter = index;
			}
			if (GUI.Button(val3, "−"))
			{
				remove = index;
			}
			GUI.Label(val4, MetricName(state.Metric), _title);
		}
		else
		{
			GUI.Label(val, MetricName(state.Metric), _title);
		}
		IList<PlayerMetricRow> rows = _ledger.GetRows(state.Metric);
		float num4 = 27f * scale;
		float num5 = ((Rect)(ref val)).yMax + 4f * scale;
		if (!compatible)
		{
			Rect val5 = default(Rect);
			((Rect)(ref val5))..ctor(((Rect)(ref rect)).x + num, ((Rect)(ref val)).yMax, ((Rect)(ref rect)).width - num * 2f, 20f * scale);
			GUI.Label(val5, WarningText(compatibilitySummary), WarningStyle());
			num5 = ((Rect)(ref val5)).yMax + 4f * scale;
		}
		float num6 = (EditMode ? (22f * scale) : (8f * scale));
		int num7 = Math.Max(0, Mathf.FloorToInt((((Rect)(ref rect)).yMax - num6 - num5) / num4));
		if (rows.Count == 0 && num7 > 0)
		{
			GUI.Label(new Rect(((Rect)(ref rect)).x + num, num5, ((Rect)(ref rect)).width - num * 2f, num4), _useChinese() ? "等待玩家对象……" : "Waiting for player objects…", _label);
		}
		else
		{
			long max = ((rows.Count == 0) ? 1 : Math.Max(1L, rows[0].Total));
			for (int i = 0; i < rows.Count && i < num7; i++)
			{
				DrawRow(rows[i], max, new Rect(((Rect)(ref rect)).x + num, num5 + (float)i * num4, ((Rect)(ref rect)).width - num * 2f, num4), scale);
			}
		}
		if (EditMode)
		{
			GUI.Label(new Rect(((Rect)(ref rect)).x + num, ((Rect)(ref rect)).yMax - 20f * scale, ((Rect)(ref rect)).width - num * 2f, 17f * scale), _useChinese() ? "右键标题选指标|拖动/缩放全部框" : "Right-click title | move/resize all", _small);
			Rect val6 = ResizeHandle(rect, scale);
			GUI.Label(val6, "◢", _value);
			HandlePointerDown(index, rect, val4, val6);
		}
	}

	private void DrawRow(PlayerMetricRow row, long max, Rect rowRect, float scale)
	{
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		float num = ((Rect)(ref rowRect)).width * 0.4f;
		float num2 = 76f * scale;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(((Rect)(ref rowRect)).x, ((Rect)(ref rowRect)).y, num, ((Rect)(ref rowRect)).height);
		Rect val2 = default(Rect);
		((Rect)(ref val2))..ctor(((Rect)(ref rowRect)).xMax - num2, ((Rect)(ref rowRect)).y, num2, ((Rect)(ref rowRect)).height);
		Rect val3 = default(Rect);
		((Rect)(ref val3))..ctor(((Rect)(ref val)).xMax + 5f * scale, ((Rect)(ref rowRect)).y + 8f * scale, Math.Max(10f, ((Rect)(ref val2)).x - ((Rect)(ref val)).xMax - 10f * scale), 11f * scale);
		string text = (row.IsPresent ? string.Empty : (_useChinese() ? "(已离开)" : " (left)"));
		GUI.Label(val, Trim(row.Name, 18) + text, _label);
		GUI.color = new Color(0.14f, 0.18f, 0.24f, 0.95f);
		GUI.DrawTexture(val3, (Texture)(object)Texture2D.whiteTexture);
		GUI.color = Color.HSVToRGB(Mathf.Repeat((float)_playerColors.GetIndex(row.Key) * 0.618034f, 1f), 0.68f, 1f);
		GUI.DrawTexture(new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y, ((Rect)(ref val3)).width * ((float)row.Total / (float)max), ((Rect)(ref val3)).height), (Texture)(object)Texture2D.whiteTexture);
		GUI.color = Color.white;
		GUI.Label(val2, row.Total.ToString("N0", CultureInfo.InvariantCulture), _value);
	}

	private void HandlePointerDown(int index, Rect panelRect, Rect titleRect, Rect resizeHandle)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: 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_004a: 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)
		Event current = Event.current;
		if ((int)current.type != 0)
		{
			return;
		}
		Vector2 mousePosition = current.mousePosition;
		if (!((Rect)(ref panelRect)).Contains(mousePosition))
		{
			return;
		}
		_panels.Select(index);
		if (current.button == 1 && ((Rect)(ref titleRect)).Contains(mousePosition))
		{
			_metricMenuPanel = index;
			_metricMenuPosition = mousePosition;
			_dragging = -1;
			_resizing = -1;
			current.Use();
		}
		else if (current.button == 0)
		{
			if (((Rect)(ref resizeHandle)).Contains(mousePosition))
			{
				_resizing = index;
				_dragging = -1;
				_metricMenuPanel = -1;
				current.Use();
			}
			else if (((Rect)(ref titleRect)).Contains(mousePosition))
			{
				_dragging = index;
				_resizing = -1;
				_metricMenuPanel = -1;
				current.Use();
			}
		}
	}

	private void HandleActiveManipulation()
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Invalid comparison between Unknown and I4
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Invalid comparison between Unknown and I4
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		if (!EditMode)
		{
			return;
		}
		Event current = Event.current;
		if ((int)current.type == 3 && (_dragging >= 0 || _resizing >= 0))
		{
			int num = ((_dragging >= 0) ? _dragging : _resizing);
			if (num < _panels.Panels.Count)
			{
				HudPanelState hudPanelState = _panels.Panels[num];
				if (_dragging >= 0)
				{
					_panels.MoveAll(current.delta.x / Math.Max(1f, Screen.width), current.delta.y / Math.Max(1f, Screen.height));
				}
				else
				{
					float requestedWidth = hudPanelState.Width + current.delta.x / Math.Max(1f, Screen.width);
					float requestedHeight = hudPanelState.Height + current.delta.y / Math.Max(1f, Screen.height);
					_panels.ResizeAll(requestedWidth, requestedHeight);
				}
				current.Use();
			}
		}
		else if ((int)current.rawType == 1 && (_dragging >= 0 || _resizing >= 0))
		{
			_dragging = -1;
			_resizing = -1;
			SaveLayout();
			current.Use();
		}
	}

	private void DrawMetricMenu(float scale)
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: 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)
		if (!EditMode || _metricMenuPanel < 0 || _metricMenuPanel >= _panels.Panels.Count)
		{
			return;
		}
		float num = 190f * scale;
		float num2 = 30f * scale;
		float num3 = num2 * (float)MetricCatalog.Visible.Length + 8f * scale;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(Mathf.Clamp(_metricMenuPosition.x, 4f, Math.Max(4f, (float)Screen.width - num - 4f)), Mathf.Clamp(_metricMenuPosition.y, 4f, Math.Max(4f, (float)Screen.height - num3 - 4f)), num, num3);
		GUI.Box(val, GUIContent.none, _panel);
		int num4 = 0;
		MetricKind[] visible = MetricCatalog.Visible;
		foreach (MetricKind metric in visible)
		{
			if (GUI.Button(new Rect(((Rect)(ref val)).x + 4f * scale, ((Rect)(ref val)).y + 4f * scale + (float)num4 * num2, ((Rect)(ref val)).width - 8f * scale, num2 - 2f * scale), MetricName(metric)))
			{
				_panels.SetMetric(_metricMenuPanel, metric);
				_metricMenuPanel = -1;
				SaveLayout();
			}
			num4++;
		}
		Event current = Event.current;
		if ((int)current.type == 0 && !((Rect)(ref val)).Contains(current.mousePosition))
		{
			_metricMenuPanel = -1;
			current.Use();
		}
	}

	private static Rect ResizeHandle(Rect rect, float scale)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		float num = 22f * scale;
		return new Rect(((Rect)(ref rect)).xMax - num, ((Rect)(ref rect)).yMax - num, num, num);
	}

	private static Rect ToPixels(HudPanelState panel)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		return new Rect(panel.X * (float)Screen.width, panel.Y * (float)Screen.height, panel.Width * (float)Screen.width, panel.Height * (float)Screen.height);
	}

	private static void FromPixels(HudPanelState panel, Rect rect)
	{
		panel.X = ((Rect)(ref rect)).x / Math.Max(1f, Screen.width);
		panel.Y = ((Rect)(ref rect)).y / Math.Max(1f, Screen.height);
		panel.Width = ((Rect)(ref rect)).width / Math.Max(1f, Screen.width);
		panel.Height = ((Rect)(ref rect)).height / Math.Max(1f, Screen.height);
	}

	private static void ClampRect(ref Rect rect)
	{
		float num = Math.Min(300f, (float)Screen.width * 0.6f);
		float num2 = Math.Min(150f, (float)Screen.height * 0.5f);
		((Rect)(ref rect)).width = Mathf.Clamp(((Rect)(ref rect)).width, num, Math.Max(num, (float)Screen.width - 8f));
		((Rect)(ref rect)).height = Mathf.Clamp(((Rect)(ref rect)).height, num2, Math.Max(num2, (float)Screen.height - 8f));
		((Rect)(ref rect)).x = Mathf.Clamp(((Rect)(ref rect)).x, 4f, Math.Max(4f, (float)Screen.width - ((Rect)(ref rect)).width - 4f));
		((Rect)(ref rect)).y = Mathf.Clamp(((Rect)(ref rect)).y, 4f, Math.Max(4f, (float)Screen.height - ((Rect)(ref rect)).height - 4f));
	}

	private string MetricName(MetricKind metric)
	{
		if (!_useChinese())
		{
			return metric switch
			{
				MetricKind.CreatureDamage => "Creature damage", 
				MetricKind.PlayerDamage => "Friendly fire", 
				MetricKind.MoneyEarned => "Money earned", 
				MetricKind.MoneySpent => "Money spent", 
				MetricKind.TeammatesRevived => "Teammates revived", 
				MetricKind.PlayerKills => "Teammates killed", 
				MetricKind.PlayerDeaths => "Deaths", 
				_ => "Fishing catches", 
			};
		}
		return metric switch
		{
			MetricKind.CreatureDamage => "对鱼造成伤害", 
			MetricKind.PlayerDamage => "友军伤害", 
			MetricKind.MoneyEarned => "赚钱数量", 
			MetricKind.MoneySpent => "花钱数量", 
			MetricKind.TeammatesRevived => "复活队友次数", 
			MetricKind.PlayerKills => "击杀次数", 
			MetricKind.PlayerDeaths => "死亡次数", 
			_ => "钓鱼数量", 
		};
	}

	private string WarningText(string detail)
	{
		return (_useChinese() ? "兼容性降级:部分采集器缺失" : "Compatibility degraded: collectors missing") + (string.IsNullOrEmpty(detail) ? string.Empty : (" — " + Trim(detail, 46)));
	}

	private GUIStyle WarningStyle()
	{
		//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_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		GUIStyle val = new GUIStyle(_small);
		val.normal.textColor = new Color(1f, 0.55f, 0.35f, 1f);
		return val;
	}

	private void EnsureStyles()
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Expected O, but got Unknown
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Expected O, but got Unknown
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Expected O, but got Unknown
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Expected O, but got Unknown
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: Expected O, but got Unknown
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0149: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Expected O, but got Unknown
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		if (_panel == null)
		{
			_background = SolidTexture(new Color(0.035f, 0.055f, 0.09f, 0.58f));
			_selectedBackground = SolidTexture(new Color(0.95f, 0.68f, 0.18f, 0.82f));
			GUIStyle val = new GUIStyle(GUI.skin.box);
			val.normal.background = _background;
			_panel = val;
			GUIStyle val2 = new GUIStyle(GUI.skin.box);
			val2.normal.background = _selectedBackground;
			_selectedPanel = val2;
			_title = new GUIStyle(GUI.skin.label)
			{
				alignment = (TextAnchor)4,
				fontStyle = (FontStyle)1
			};
			_title.normal.textColor = new Color(0.86f, 0.98f, 1f, 1f);
			_label = new GUIStyle(GUI.skin.label)
			{
				alignment = (TextAnchor)3
			};
			_label.normal.textColor = Color.white;
			_value = new GUIStyle(_label)
			{
				alignment = (TextAnchor)5,
				fontStyle = (FontStyle)1
			};
			_small = new GUIStyle(GUI.skin.label)
			{
				alignment = (TextAnchor)4
			};
			_small.normal.textColor = new Color(0.65f, 0.74f, 0.82f, 1f);
		}
	}

	private void UpdateFontSizes(float scale)
	{
		_title.fontSize = Mathf.RoundToInt(17f * scale);
		_label.fontSize = Mathf.RoundToInt(13f * scale);
		_value.fontSize = Mathf.RoundToInt(13f * scale);
		_small.fontSize = Mathf.RoundToInt(11f * scale);
	}

	private static Texture2D SolidTexture(Color color)
	{
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Expected O, but got Unknown
		Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
		val.SetPixel(0, 0, color);
		val.Apply();
		return val;
	}

	private void SaveLayout()
	{
		if (_saveLayout != null)
		{
			_saveLayout(_panels.Serialize());
		}
	}

	private void RestoreCursor()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (_cursorCaptured)
		{
			Cursor.lockState = _previousCursorLock;
			Cursor.visible = _previousCursorVisible;
			_cursorCaptured = false;
		}
	}

	private static string Trim(string value, int max)
	{
		if (string.IsNullOrEmpty(value) || value.Length <= max)
		{
			return value ?? string.Empty;
		}
		return value.Substring(0, max - 1) + "…";
	}
}
internal sealed class StatsLedger
{
	private sealed class MoneyCue
	{
		internal float Time;

		internal bool Increase;

		internal object Player;
	}

	private sealed class MoneyDelta
	{
		internal float Time;

		internal int Amount;
	}

	private const float DuplicateWindowSeconds = 0.45f;

	private const float CorrelationWindowSeconds = 1.5f;

	private readonly GameIntrospection _game;

	private readonly Dictionary<string, PlayerStats> _players = new Dictionary<string, PlayerStats>(StringComparer.Ordinal);

	private readonly Dictionary<string, PlayerStats> _serverPlayers = new Dictionary<string, PlayerStats>(StringComparer.Ordinal);

	private readonly CrossSourceDeduplicator _deduplicator = new CrossSourceDeduplicator(0.45f);

	private readonly FishingCatchTracker _catchTracker = new FishingCatchTracker();

	private readonly List<MoneyCue> _moneyCues = new List<MoneyCue>();

	private readonly List<MoneyDelta> _moneyDeltas = new List<MoneyDelta>();

	internal string SessionId { get; private set; }

	internal DateTime StartedUtc { get; private set; }

	internal long UnassignedMoneyEarned { get; private set; }

	internal long UnassignedMoneySpent { get; private set; }

	internal long Revision { get; private set; }

	internal bool UsingServerSnapshot { get; private set; }

	internal StatsLedger(GameIntrospection game, string sessionId)
	{
		_game = game;
		SessionId = sessionId;
		StartedUtc = DateTime.UtcNow;
	}

	internal void RefreshPlayers(IList<object> livePlayers)
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		foreach (object livePlayer in livePlayers)
		{
			PlayerIdentity playerIdentity = _game.GetPlayerIdentity(livePlayer);
			if (playerIdentity != null)
			{
				PlayerStats playerStats = EnsurePlayer(playerIdentity);
				playerStats.IsPresent = true;
				playerStats.LastSeenUtc = DateTime.UtcNow;
				hashSet.Add(playerStats.Key);
			}
		}
		foreach (PlayerStats value in _players.Values)
		{
			if (!hashSet.Contains(value.Key))
			{
				value.IsPresent = false;
			}
		}
		Revision++;
	}

	internal void RecordCreatureDamage(object player, object creature, int damage, string source, Confidence confidence)
	{
		if (damage > 0)
		{
			Record("creature_damage", MetricKind.CreatureDamage, player, damage, creature, source, confidence, string.Empty);
		}
	}

	internal void RecordPlayerDamage(object attacker, object target, int damage, string source, Confidence confidence, string damageType)
	{
		if (damage > 0)
		{
			Record("player_damage", MetricKind.PlayerDamage, attacker, damage, target, source, confidence, damageType ?? string.Empty);
		}
	}

	internal void RecordAuthoritativeMoney(object player, int amount, bool increase, string source)
	{
		if (amount > 0)
		{
			Record(increase ? "money_earned" : "money_spent", increase ? MetricKind.MoneyEarned : MetricKind.MoneySpent, player, amount, null, source, Confidence.Exact, string.Empty);
		}
	}

	internal void ObserveMoneyCue(bool increase, object player)
	{
		float now = Time.realtimeSinceStartup;
		MoneyDelta moneyDelta = (from d in _moneyDeltas
			where Math.Sign(d.Amount) == (increase ? 1 : (-1)) && Math.Abs(now - d.Time) <= 1.5f
			orderby d.Time descending
			select d).FirstOrDefault();
		if (moneyDelta != null)
		{
			_moneyDeltas.Remove(moneyDelta);
			RecordAuthoritativeOrInferredMoney(player, Math.Abs(moneyDelta.Amount), increase, "money_correlation");
			return;
		}
		_moneyCues.Add(new MoneyCue
		{
			Time = now,
			Increase = increase,
			Player = player
		});
	}

	internal void ObserveBalanceChange(int previous, int next, bool asServer)
	{
		int num = next - previous;
		if (!(num == 0 || asServer))
		{
			float now = Time.realtimeSinceStartup;
			bool increase = num > 0;
			List<MoneyCue> list = _moneyCues.Where((MoneyCue c) => c.Increase == increase && Math.Abs(now - c.Time) <= 1.5f).ToList();
			if (list.Count == 1)
			{
				_moneyCues.Remove(list[0]);
				RecordAuthoritativeOrInferredMoney(list[0].Player, Math.Abs(num), increase, "money_correlation");
			}
			else
			{
				_moneyDeltas.Add(new MoneyDelta
				{
					Time = now,
					Amount = num
				});
			}
		}
	}

	internal void BeginFishingCatch(object player, object item, object rod, bool authoritative)
	{
		_catchTracker.Begin(_game.GetObjectId(rod), player, item, authoritative);
	}

	internal void ConfirmFishingCatch(object rod, object landedItem, string source)
	{
		if (_catchTracker.TryConfirm(_game.GetObjectId(rod), out var pending))
		{
			object obj = (GameIntrospection.IsNull(landedItem) ? pending.Item : landedItem);
			Record("fish_caught", MetricKind.FishCaught, pending.Player, 1L, obj, source, (!pending.Authoritative) ? Confidence.Inferred : Confidence.Exact, _game.GetItemName(obj));
		}
	}

	internal void CancelFishingCatch(object rod)
	{
		_catchTracker.Cancel(_game.GetObjectId(rod));
	}

	internal void RecordResurrection(object victim, object deadPlayer, string source)
	{
		if (!GameIntrospection.IsNull(victim) && !GameIntrospection.IsNull(deadPlayer) && _game.IsPlayerDead(victim))
		{
			object deadPlayerHolder = _game.GetDeadPlayerHolder(deadPlayer);
			if (!GameIntrospection.IsNull(deadPlayerHolder) && !_game.IsSameObject(deadPlayerHolder, victim))
			{
				Record("teammate_revived", MetricKind.TeammatesRevived, deadPlayerHolder, 1L, victim, source, Confidence.Exact, string.Empty);
			}
		}
	}

	internal void RecordPlayerKill(object attacker, object victim, string source)
	{
		if (!GameIntrospection.IsNull(attacker) && !GameIntrospection.IsNull(victim) && !_game.IsSameObject(attacker, victim))
		{
			Record("player_kill", MetricKind.PlayerKills, attacker, 1L, victim, source, Confidence.Exact, string.Empty);
		}
	}

	internal void RecordDeath(object player, string source)
	{
		if (!GameIntrospection.IsNull(player))
		{
			Record("player_death", MetricKind.PlayerDeaths, player, 1L, null, source, Confidence.Exact, string.Empty);
		}
	}

	internal void Tick()
	{
		float now = Time.realtimeSinceStartup;
		for (int num = _moneyDeltas.Count - 1; num >= 0; num--)
		{
			if (!(now - _moneyDeltas[num].Time <= 1.5f))
			{
				MoneyDelta moneyDelta = _moneyDeltas[num];
				_moneyDeltas.RemoveAt(num);
				RecordUnknownMoney(moneyDelta.Amount);
			}
		}
		_moneyCues.RemoveAll((MoneyCue c) => now - c.Time > 1.5f);
	}

	internal IList<PlayerMetricRow> GetRows(MetricKind metric)
	{
		return (from p in ((IDictionary<string, PlayerStats>)(UsingServerSnapshot ? _serverPlayers : _players)).Values
			select new PlayerMetricRow
			{
				Key = p.Key,
				SteamId = p.SteamId,
				Name = p.Name,
				IsPresent = p.IsPresent,
				Exact = p.Metrics[metric].Exact,
				Inferred = p.Metrics[metric].Inferred
			} into row
			orderby row.Total descending, (row.SteamId != 0L) ? row.SteamId : ulong.MaxValue
			select row).ThenBy<PlayerMetricRow, string>((PlayerMetricRow row) => row.Key, StringComparer.Ordinal).ToList();
	}

	internal IList<PlayerStats> GetPlayers()
	{
		return ((IDictionary<string, PlayerStats>)(UsingServerSnapshot ? _serverPlayers : _players)).Values.OrderBy((PlayerStats p) => (p.SteamId != 0L) ? p.SteamId : ulong.MaxValue).ThenBy<PlayerStats, string>((PlayerStats p) => p.Key, StringComparer.Ordinal).ToList();
	}

	internal StatsSyncSnapshot CreateSyncSnapshot()
	{
		StatsSyncSnapshot statsSyncSnapshot = new StatsSyncSnapshot
		{
			SessionId = SessionId
		};
		int length = Enum.GetValues(typeof(MetricKind)).Length;
		foreach (PlayerStats item in _players.Values.OrderBy<PlayerStats, string>((PlayerStats p) => p.Key, StringComparer.Ordinal))
		{
			StatsSyncPlayer statsSyncPlayer = new StatsSyncPlayer
			{
				Key = item.Key,
				SteamId = item.SteamId,
				Name = item.Name,
				IsPresent = item.IsPresent,
				Exact = new long[length],
				Inferred = new long[length]
			};
			foreach (MetricKind value in Enum.GetValues(typeof(MetricKind)))
			{
				statsSyncPlayer.Exact[(int)value] = item.Metrics[value].Exact;
				statsSyncPlayer.Inferred[(int)value] = item.Metrics[value].Inferred;
			}
			statsSyncSnapshot.Players.Add(statsSyncPlayer);
		}
		return statsSyncSnapshot;
	}

	internal void ApplyServerSnapshot(StatsSyncSnapshot snapshot)
	{
		if (snapshot == null)
		{
			return;
		}
		_serverPlayers.Clear();
		int length = Enum.GetValues(typeof(MetricKind)).Length;
		foreach (StatsSyncPlayer player in snapshot.Players)
		{
			if (player != null && !string.IsNullOrEmpty(player.Key))
			{
				PlayerStats playerStats = new PlayerStats
				{
					Key = player.Key,
					SteamId = player.SteamId,
					Name = player.Name,
					IsPresent = player.IsPresent,
					LastSeenUtc = DateTime.UtcNow
				};
				for (int i = 0; i < length; i++)
				{
					MetricKind key = (MetricKind)i;
					playerStats.Metrics[key].Exact = ((player.Exact != null && i < player.Exact.Length) ? player.Exact[i] : 0);
					playerStats.Metrics[key].Inferred = ((player.Inferred != null && i < player.Inferred.Length) ? player.Inferred[i] : 0);
				}
				_serverPlayers[playerStats.Key] = playerStats;
			}
		}
		UsingServerSnapshot = true;
	}

	internal void ClearServerSnapshot()
	{
		if (UsingServerSnapshot || _serverPlayers.Count != 0)
		{
			UsingServerSnapshot = false;
			_serverPlayers.Clear();
		}
	}

	internal void FlushPending()
	{
		MoneyDelta[] array = _moneyDeltas.ToArray();
		foreach (MoneyDelta moneyDelta in array)
		{
			RecordUnknownMoney(moneyDelta.Amount);
		}
		_moneyDeltas.Clear();
		_moneyCues.Clear();
	}

	private void RecordAuthoritativeOrInferredMoney(object player, int amount, bool increase, string source)
	{
		Record(increase ? "money_earned" : "money_spent", increase ? MetricKind.MoneyEarned : MetricKind.MoneySpent, player, amount, null, source, Confidence.Inferred, string.Empty);
	}

	private void RecordUnknownMoney(int delta)
	{
		if (delta > 0)
		{
			UnassignedMoneyEarned += delta;
		}
		else
		{
			UnassignedMoneySpent += Math.Abs((long)delta);
		}
		Record((delta > 0) ? "money_delta_unassigned_earned" : "money_delta_unassigned_spent", null, null, Math.Abs((long)delta), null, "balance_delta", Confidence.Unknown, "Original game traffic did not uniquely identify a player.");
	}

	private void Record(string eventType, MetricKind? metric, object player, long value, object target, string source, Confidence confidence, string detail)
	{
		PlayerIdentity playerIdentity = _game.GetPlayerIdentity(player);
		string text = ((playerIdentity == null) ? string.Empty : playerIdentity.Key);
		string objectId = _game.GetObjectId(target);
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		string signature = eventType + "|" + text + "|" + objectId + "|" + value.ToString(CultureInfo.InvariantCulture);
		PlayerStats playerStats = ((playerIdentity == null) ? null : EnsurePlayer(playerIdentity));
		switch (_deduplicator.Observe(signature, source, confidence, realtimeSinceStartup))
		{
		case DeduplicationResult.PromoteToExact:
			if (playerStats != null && metric.HasValue)
			{
				MetricValue metricValue2 = playerStats.Metrics[metric.Value];
				metricValue2.Inferred = Math.Max(0L, metricValue2.Inferred - value);
				metricValue2.Exact += value;
				Revision++;
			}
			break;
		case DeduplicationResult.NewEvent:
			if (metric.HasValue && playerStats != null)
			{
				MetricValue metricValue = playerStats.Metrics[metric.Value];
				switch (confidence)
				{
				case Confidence.Exact:
				case Confidence.Corrected:
					metricValue.Exact += value;
					break;
				case Confidence.Inferred:
					metricValue.Inferred += value;
					break;
				}
			}
			Revision++;
			break;
		}
	}

	private PlayerStats EnsurePlayer(PlayerIdentity identity)
	{
		if (!_players.TryGetValue(identity.Key, out var value))
		{
			value = new PlayerStats
			{
				Key = identity.Key,
				SteamId = identity.SteamId,
				Name = identity.Name
			};
			_players.Add(identity.Key, value);
		}
		else
		{
			value.SteamId = identity.SteamId;
			if (!string.IsNullOrWhiteSpace(identity.Name))
			{
				value.Name = identity.Name;
			}
		}
		return value;
	}
}
internal sealed class StatsSyncSnapshot
{
	internal string SessionId;

	internal readonly List<StatsSyncPlayer> Players = new List<StatsSyncPlayer>();
}
internal sealed class StatsSyncPlayer
{
	internal string Key;

	internal ulong SteamId;

	internal string Name;

	internal bool IsPresent;

	internal long[] Exact;

	internal long[] Inferred;
}
internal static class StatsSyncPayloadCodec
{
	internal const byte ProtocolVersion = 1;

	internal static string Serialize(StatsSyncSnapshot snapshot)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append(((byte)1).ToString(CultureInfo.InvariantCulture)).Append('\n').Append(Encode((snapshot == null) ? string.Empty : snapshot.SessionId))
			.Append('\n');
		if (snapshot == null)
		{
			return stringBuilder.ToString();
		}
		int length = Enum.GetValues(typeof(MetricKind)).Length;
		foreach (StatsSyncPlayer player in snapshot.Players)
		{
			stringBuilder.Append(Encode(player.Key)).Append('|').Append(player.SteamId.ToString(CultureInfo.InvariantCulture))
				.Append('|')
				.Append(Encode(player.Name))
				.Append('|')
				.Append(player.IsPresent ? '1' : '0');
			for (int i = 0; i < length; i++)
			{
				stringBuilder.Append('|').Append(ValueAt(player.Exact, i).ToString(CultureInfo.InvariantCulture)).Append('|')
					.Append(ValueAt(player.Inferred, i).ToString(CultureInfo.InvariantCulture));
			}
			stringBuilder.Append('\n');
		}
		return stringBuilder.ToString();
	}

	internal static bool TryDeserialize(string payload, out StatsSyncSnapshot snapshot)
	{
		snapshot = null;
		if (string.IsNullOrEmpty(payload))
		{
			return false;
		}
		string[] array = payload.Replace("\r", string.Empty).Split(new char[1] { '\n' });
		if (array.Length < 2 || !byte.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result != 1)
		{
			return false;
		}
		if (!TryDecode(array[1], out var decoded))
		{
			return false;
		}
		StatsSyncSnapshot statsSyncSnapshot = new StatsSyncSnapshot
		{
			SessionId = decoded
		};
		int length = Enum.GetValues(typeof(MetricKind)).Length;
		int num = 4 + length * 2;
		for (int i = 2; i < array.Length; i++)
		{
			if (string.IsNullOrEmpty(array[i]))
			{
				continue;
			}
			string[] array2 = array[i].Split(new char[1] { '|' });
			if (array2.Length != num || !TryDecode(array2[0], out var decoded2) || !ulong.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || !TryDecode(array2[2], out var decoded3) || (array2[3] != "0" && array2[3] != "1"))
			{
				return false;
			}
			StatsSyncPlayer statsSyncPlayer = new StatsSyncPlayer
			{
				Key = decoded2,
				SteamId = result2,
				Name = decoded3,
				IsPresent = (array2[3] == "1"),
				Exact = new long[length],
				Inferred = new long[length]
			};
			for (int j = 0; j < length; j++)
			{
				if (!long.TryParse(array2[4 + j * 2], NumberStyles.Integer, CultureInfo.InvariantCulture, out statsSyncPlayer.Exact[j]) || !long.TryParse(array2[5 + j * 2], NumberStyles.Integer, CultureInfo.InvariantCulture, out statsSyncPlayer.Inferred[j]))
				{
					return false;
				}
			}
			statsSyncSnapshot.Players.Add(statsSyncPlayer);
		}
		snapshot = statsSyncSnapshot;
		return true;
	}

	private static long ValueAt(long[] values, int index)
	{
		if (values == null || index < 0 || index >= values.Length)
		{
			return 0L;
		}
		return values[index];
	}

	private static string Encode(string value)
	{
		return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? string.Empty));
	}

	private static bool TryDecode(string value, out string decoded)
	{
		decoded = string.Empty;
		try
		{
			decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value ?? string.Empty));
			return true;
		}
		catch
		{
			return false;
		}
	}
}
public struct StatsHandshakeBroadcast : IBroadcast
{
	public byte Protocol;
}
public struct StatsSnapshotBroadcast : IBroadcast
{
	public string Payload;
}
internal sealed class StatsSyncService
{
	private readonly StatsLedger _ledger;

	private readonly Dictionary<int, NetworkConnection> _moddedClients = new Dictionary<int, NetworkConnection>();

	private ServerManager _server;

	private ClientManager _client;

	private bool _handshakeSent;

	private bool _snapshotReceived;

	private float _nextSnapshot;

	internal StatsSyncService(StatsLedger ledger)
	{
		_ledger = ledger;
		RegisterSerializers();
	}

	internal void Tick()
	{
		EnsureManagers();
		if ((Object)(object)_client == (Object)null || !_client.Started)
		{
			_handshakeSent = false;
			_snapshotReceived = false;
			_ledger.ClearServerSnapshot();
		}
		else if (!_handshakeSent && !InstanceFinder.IsServerStarted && _client.Connection != (NetworkConnection)null && _client.Connection.IsAuthenticated)
		{
			_client.Broadcast<StatsHandshakeBroadcast>(new StatsHandshakeBroadcast
			{
				Protocol = 1
			}, (Channel)0);
			_handshakeSent = true;
			Plugin.LogDebug("Stats sync handshake sent to host.");
		}
		if ((Object)(object)_server != (Object)null && _server.Started && Time.unscaledTime >= _nextSnapshot)
		{
			_nextSnapshot = Time.unscaledTime + 1f;
			BroadcastSnapshot();
		}
	}

	internal void Shutdown()
	{
		if ((Object)(object)_server != (Object)null)
		{
			_server.UnregisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake);
		}
		if ((Object)(object)_client != (Object)null)
		{
			_client.UnregisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
			_client.OnAuthenticated -= OnAuthenticated;
			_client.OnClientConnectionState -= OnClientConnectionState;
		}
		_moddedClients.Clear();
		_ledger.ClearServerSnapshot();
	}

	private static void RegisterSerializers()
	{
		GenericWriter<StatsHandshakeBroadcast>.SetWrite((Action<Writer, StatsHandshakeBroadcast>)delegate(Writer writer, StatsHandshakeBroadcast value)
		{
			writer.WriteUInt8Unpacked(value.Protocol);
		});
		GenericReader<StatsHandshakeBroadcast>.SetRead((Func<Reader, StatsHandshakeBroadcast>)((Reader reader) => new StatsHandshakeBroadcast
		{
			Protocol = reader.ReadUInt8Unpacked()
		}));
		GenericWriter<StatsSnapshotBroadcast>.SetWrite((Action<Writer, StatsSnapshotBroadcast>)delegate(Writer writer, StatsSnapshotBroadcast value)
		{
			writer.WriteString(value.Payload ?? string.Empty);
		});
		GenericReader<StatsSnapshotBroadcast>.SetRead((Func<Reader, StatsSnapshotBroadcast>)((Reader reader) => new StatsSnapshotBroadcast
		{
			Payload = reader.ReadStringAllocated()
		}));
	}

	private void EnsureManagers()
	{
		ServerManager serverManager = InstanceFinder.ServerManager;
		if (serverManager != _server)
		{
			if ((Object)(object)_server != (Object)null)
			{
				_server.UnregisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake);
			}
			_server = serverManager;
			_moddedClients.Clear();
			if ((Object)(object)_server != (Object)null)
			{
				_server.RegisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake, false);
			}
		}
		ClientManager clientManager = InstanceFinder.ClientManager;
		if (clientManager != _client)
		{
			if ((Object)(object)_client != (Object)null)
			{
				_client.UnregisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
				_client.OnAuthenticated -= OnAuthenticated;
				_client.OnClientConnectionState -= OnClientConnectionState;
			}
			_client = clientManager;
			_handshakeSent = false;
			if ((Object)(object)_client != (Object)null)
			{
				_client.RegisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
				_client.OnAuthenticated += OnAuthenticated;
				_client.OnClientConnectionState += OnClientConnectionState;
			}
		}
	}

	private void OnAuthenticated()
	{
		_handshakeSent = false;
	}

	private void OnClientConnectionState(ClientConnectionStateArgs args)
	{
		if ((Object)(object)_client == (Object)null || !_client.Started)
		{
			_handshakeSent = false;
			_ledger.ClearServerSnapshot();
		}
	}

	private void OnHandshake(NetworkConnection connection, StatsHandshakeBroadcast message, Channel channel)
	{
		if (message.Protocol == 1 && !(connection == (NetworkConnection)null) && connection.IsValid)
		{
			bool num = !_moddedClients.ContainsKey(connection.ClientId);
			_moddedClients[connection.ClientId] = connection;
			SendSnapshot(connection);
			if (num)
			{
				Plugin.LogInfo("Stats sync client connected: " + connection.ClientId + ".");
			}
		}
	}

	private void OnSnapshot(StatsSnapshotBroadcast message, Channel channel)
	{
		if (InstanceFinder.IsServerStarted)
		{
			return;
		}
		if (!StatsSyncPayloadCodec.TryDeserialize(message.Payload, out var snapshot))
		{
			Plugin.LogWarning("Ignored an invalid multiplayer stats snapshot.");
			return;
		}
		_ledger.ApplyServerSnapshot(snapshot);
		if (!_snapshotReceived)
		{
			_snapshotReceived = true;
			Plugin.LogInfo("Authoritative host stats sync active.");
		}
	}

	private void BroadcastSnapshot()
	{
		foreach (int item in new List<int>(_moddedClients.Keys))
		{
			NetworkConnection val = _moddedClients[item];
			if (val == (NetworkConnection)null || !val.IsValid || !val.IsActive)
			{
				_moddedClients.Remove(item);
			}
			else
			{
				SendSnapshot(val);
			}
		}
	}

	private void SendSnapshot(NetworkConnection connection)
	{
		if (!((Object)(object)_server == (Object)null) && _server.Started && !(connection == (NetworkConnection)null))
		{
			string payload = StatsSyncPayloadCodec.Serialize(_ledger.CreateSyncSnapshot());
			_server.Broadcast<StatsSnapshotBroadcast>(connection, new StatsSnapshotBroadcast
			{
				Payload = payload
			}, true, (Channel)0);
		}
	}
}