Decompiled source of SkadiNet v1.0.1

SkadiNet/SkadiNet.dll

Decompiled 4 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyProduct("SkadiNet")]
[assembly: AssemblyTitle("SkadiNet")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SkadiNet
{
	internal enum ConfigSyncScope
	{
		ServerSynced,
		ClientLocal,
		MigrationOnly
	}
	internal static class ConfigSyncManager
	{
		private static MethodInfo _addConfigEntryMethod;

		internal static ConfigSync Sync { get; private set; }

		internal static ConfigEntry<bool> LockServerConfig { get; private set; }

		internal static void Initialize(ConfigFile config)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			Sync = new ConfigSync("sighsorry.SkadiNet")
			{
				DisplayName = "SkadiNet",
				CurrentVersion = "1.0.1",
				MinimumRequiredVersion = "1.0.1",
				ModRequired = true
			};
			LockServerConfig = config.Bind<bool>("General", "LockServerConfig", true, new ConfigDescription("Lock server-synced config for non-admin clients.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 90
				}
			}));
			Sync.AddLockingConfigEntry<bool>(LockServerConfig).SynchronizedConfig = true;
		}

		internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, string description, ConfigSyncScope scope)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return Bind(config, group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), scope);
		}

		internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, ConfigDescription description, ConfigSyncScope scope)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			string text = SuffixFor(scope);
			ConfigDescription val = new ConfigDescription(description.Description + text, description.AcceptableValues, description.Tags);
			ConfigEntry<T> val2 = config.Bind<T>(group, name, value, val);
			if (scope != ConfigSyncScope.MigrationOnly)
			{
				Register((ConfigEntryBase)(object)val2, scope == ConfigSyncScope.ServerSynced);
			}
			return val2;
		}

		private static string SuffixFor(ConfigSyncScope scope)
		{
			return scope switch
			{
				ConfigSyncScope.ServerSynced => " [Synced with Server]", 
				ConfigSyncScope.ClientLocal => " [Client Local; Not Synced with Server]", 
				ConfigSyncScope.MigrationOnly => " [Migration Only; Not Synced with Server]", 
				_ => throw new ArgumentOutOfRangeException("scope", scope, null), 
			};
		}

		private static void Register(ConfigEntryBase entry, bool synchronized)
		{
			if (Sync != null && entry != null && (LockServerConfig == null || !object.Equals(entry.Definition, ((ConfigEntryBase)LockServerConfig).Definition)) && GetAddConfigEntryMethod().MakeGenericMethod(entry.SettingType).Invoke(Sync, new object[1] { entry }) is OwnConfigEntryBase ownConfigEntryBase)
			{
				ownConfigEntryBase.SynchronizedConfig = synchronized;
			}
		}

		private static MethodInfo GetAddConfigEntryMethod()
		{
			if (_addConfigEntryMethod != null)
			{
				return _addConfigEntryMethod;
			}
			_addConfigEntryMethod = typeof(ConfigSync).GetMethods(BindingFlags.Instance | BindingFlags.Public).First((MethodInfo method) => method.Name == "AddConfigEntry" && method.IsGenericMethodDefinition);
			return _addConfigEntryMethod;
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;
	}
	internal static class EffectiveConfig
	{
		internal const int SteamSendRateBytes = 36000000;

		internal const int CompressionFailureLimitPerPeer = 1;

		internal static bool SchedulerEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.SchedulerThroughput);
				}
				return false;
			}
		}

		internal static bool PayloadReducerEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.PayloadReducerStrength);
				}
				return false;
			}
		}

		internal static bool CompressionEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.CompressionAggression);
				}
				return false;
			}
		}

		internal static bool RpcAoiEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.RpcAoiAggression);
				}
				return false;
			}
		}

		internal static bool ClientStutterGuardEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.ClientStutterGuardStrength);
				}
				return false;
			}
		}

		internal static bool AdaptiveOwnershipEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.OwnershipIntensity);
				}
				return false;
			}
		}

		internal static bool PeerQualityEnabled => AdaptiveOwnershipEnabled;

		internal static bool OwnerHintsEnabled => AdaptiveOwnershipEnabled;

		internal static float SendInterval => Map(ModConfig.SchedulerThroughput, 0.1f, 0.05f, 0.02f);

		internal static float MinSendInterval => Map(ModConfig.SchedulerThroughput, 0.06f, 0.03f, 0.015f);

		internal static float MaxSendInterval => Map(ModConfig.SchedulerThroughput, 0.2f, 0.1f, 0.045f);

		internal static int BasePeersPerTick => MapInt(ModConfig.SchedulerThroughput, 1, 4, 12);

		internal static int MaxPeersPerTick => MapInt(ModConfig.SchedulerThroughput, 4, 12, 32);

		internal static int ZdoQueueLimitBytes => MapInt(ModConfig.SchedulerThroughput, 10240, 65536, 196608);

		internal static int ZdoQueueMinPackageBytes => MapInt(ModConfig.SchedulerThroughput, 2048, 2048, 768);

		internal static int PeerQueueSoftLimitBytes => MapInt(ModConfig.SchedulerThroughput, 1572864, 524288, 196608);

		internal static int PeerQueueHardLimitBytes => MapInt(ModConfig.SchedulerThroughput, 6291456, 2097152, 786432);

		internal static float LaggingPeerMaxSkipSeconds => Map(ModConfig.SchedulerThroughput, 3f, 1f, 0.2f);

		internal static float PayloadVec3CullSize => Map(ModConfig.PayloadReducerStrength, 0.005f, 0.04f, 0.1f);

		internal static float PayloadQuaternionDotThreshold => Map(ModConfig.PayloadReducerStrength, 0.9998f, 0.995f, 0.99f);

		internal static float PayloadForceRefreshSeconds => Map(ModConfig.PayloadReducerStrength, 0.15f, 1f, 2f);

		internal static int CompressionThresholdBytes => MapInt(ModConfig.CompressionAggression, 8192, 1024, 256);

		internal static float CompressionMinUsefulRatio => Map(ModConfig.CompressionAggression, 0.7f, 0.9f, 0.97f);

		internal static float RpcAoiVisualRadius => Map(ModConfig.RpcAoiAggression, 640f, 192f, 64f);

		internal static float ClientStutterInitialSyncWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 2f, 10f, 24f);

		internal static float ClientStutterTeleportWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 1f, 5f, 14f);

		internal static float ClientStutterFullSnapshotWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1.5f, 5f);

		internal static float ClientStutterCombatWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f);

		internal static float ClientStutterShipWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f);

		internal static float ClientStutterMaxDelaySeconds => Map(ModConfig.ClientStutterGuardStrength, 6f, 30f, 60f);

		internal static int ClientStutterMemoryPressureThresholdPercent => MapInt(ModConfig.ClientStutterGuardStrength, 55, 75, 90);

		internal static int ClientStutterMinimumFreeMemoryMB => MapInt(ModConfig.ClientStutterGuardStrength, 6144, 2048, 1024);

		internal static float ClientStutterIdleCleanupPollSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1f, 4f);

		internal static float PeerPingEmaHalfLifeSeconds => Map(ModConfig.OwnershipIntensity, 6f, 2.5f, 0.75f);

		internal static int PeerPingSampleWindow => MapInt(ModConfig.OwnershipIntensity, 120, 60, 20);

		internal static float PeerQualityMeanWeight => Map(ModConfig.OwnershipIntensity, 0.35f, 0f, 0f);

		internal static float PeerQualityStdDevWeight => Map(ModConfig.OwnershipIntensity, 0.1f, 0.25f, 0.7f);

		internal static float PeerQualityJitterWeight => Map(ModConfig.OwnershipIntensity, 0.2f, 0.5f, 1.2f);

		internal static float PeerQualityEmaWeight => 1f;

		internal static float MaxCandidatePingMs => Map(ModConfig.OwnershipIntensity, 320f, 220f, 140f);

		internal static float MaxCandidateJitterMs => Map(ModConfig.OwnershipIntensity, 180f, 100f, 45f);

		internal static int OwnershipScanBudget => MapInt(ModConfig.OwnershipIntensity, 24, 96, 256);

		internal static int OwnershipScanStride => MapInt(ModConfig.OwnershipIntensity, 10, 4, 1);

		internal static float OwnershipScanIntervalSeconds => Map(ModConfig.OwnershipIntensity, 3f, 1f, 0.25f);

		internal static float OwnershipRelativeHysteresis => Map(ModConfig.OwnershipIntensity, 0.03f, 0.15f, 0.4f);

		internal static float OwnershipAbsoluteHysteresisMs => Map(ModConfig.OwnershipIntensity, 5f, 20f, 90f);

		internal static float OwnerSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 0.75f, 3f, 12f);

		internal static float OwnerHintSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 1f, 5f, 16f);

		internal static float ShipOwnerSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 3f, 8f, 24f);

		internal static float RecoverUnownedAfterSeconds => Map(ModConfig.OwnershipIntensity, 0.75f, 2f, 6f);

		internal static float OwnershipCandidateRadius => Map(ModConfig.OwnershipIntensity, 80f, 160f, 360f);

		internal static float OwnerHintCandidateRadius => Map(ModConfig.OwnershipIntensity, 128f, 256f, 640f);

		internal static float OwnershipDistanceScoreWeight => Map(ModConfig.OwnershipIntensity, 0.4f, 0.2f, 0.06f);

		internal static float OwnershipLoadPenaltyPerZdo => Map(ModConfig.OwnershipIntensity, 0.5f, 0.35f, 0.15f);

		internal static float ServerFallbackPenaltyMs => Map(ModConfig.OwnershipIntensity, 450f, 650f, 1000f);

		internal static float OwnerHintScoreBonusMs => Map(ModConfig.OwnershipIntensity, 20f, 90f, 240f);

		internal static float OwnerHintLifetimeSeconds => Map(ModConfig.OwnershipIntensity, 2f, 8f, 20f);

		private static bool IsPositive(ConfigEntry<int> entry)
		{
			return Clamp(entry?.Value ?? 0, 0, 100) > 0;
		}

		private static float Strength(ConfigEntry<int> entry)
		{
			return (float)Clamp(entry?.Value ?? 50, 0, 100) / 100f;
		}

		private static float Map(ConfigEntry<int> entry, float safe, float current, float aggressive)
		{
			float num = Strength(entry);
			if (!(num <= 0.5f))
			{
				return Lerp(current, aggressive, (num - 0.5f) * 2f);
			}
			return Lerp(safe, current, num * 2f);
		}

		private static int MapInt(ConfigEntry<int> entry, int safe, int current, int aggressive)
		{
			return (int)Math.Round(Map(entry, safe, current, aggressive));
		}

		private static float Clamp(float value, float min, float max)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return min;
			}
			return Math.Max(min, Math.Min(max, value));
		}

		private static int Clamp(int value, int min, int max)
		{
			return Math.Max(min, Math.Min(max, value));
		}

		private static float Lerp(float a, float b, float t)
		{
			return a + (b - a) * Math.Max(0f, Math.Min(1f, t));
		}
	}
	internal static class ModConfig
	{
		private const string GeneralSection = "General";

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> LockServerConfig;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<int> SchedulerThroughput;

		internal static ConfigEntry<int> PayloadReducerStrength;

		internal static ConfigEntry<int> OwnershipIntensity;

		internal static ConfigEntry<int> CompressionAggression;

		internal static ConfigEntry<int> RpcAoiAggression;

		internal static ConfigEntry<int> ClientStutterGuardStrength;

		internal static void Bind(ConfigFile config)
		{
			Enabled = ConfigSyncManager.Bind(config, "General", "Enabled", value: true, Description("Master switch.", 100), ConfigSyncScope.ServerSynced);
			LockServerConfig = ConfigSyncManager.LockServerConfig;
			DebugLogging = ConfigSyncManager.Bind(config, "General", "DebugLogging", value: false, Description("Diagnostic log output. Enable temporarily to inspect frame hitches, FPS, network queues, scheduler stalls, compression cost, and ownership scan cost; keep this off during normal live play.", 80), ConfigSyncScope.ClientLocal);
			SchedulerThroughput = ConfigSyncManager.Bind(config, "General", "SchedulerThroughput", 35, FeatureSliderDescription("0 disables the adaptive scheduler. 1 keeps package caps close to vanilla while using the gentlest adaptive scheduler; 35 is recommended; 50 is balanced; 100 favors lower latency, higher ZDO throughput, and faster lagging-peer backfill. Steam send-rate is fixed internally at 36 MB/s while SkadiNet is enabled.", 70), ConfigSyncScope.ServerSynced);
			PayloadReducerStrength = ConfigSyncManager.Bind(config, "General", "PayloadReducerStrength", 30, FeatureSliderDescription("0 disables the payload reducer. 1 favors sync fidelity; 50 is balanced; 100 applies stronger Vector3/Quaternion micro-update reduction.", 60), ConfigSyncScope.ServerSynced);
			CompressionAggression = ConfigSyncManager.Bind(config, "General", "CompressionAggression", 50, FeatureSliderDescription("0 disables negotiated package compression. 1 compresses only large/high-value packets; 50 is balanced; 100 considers smaller packets and smaller savings.", 50), ConfigSyncScope.ServerSynced);
			OwnershipIntensity = ConfigSyncManager.Bind(config, "General", "OwnershipIntensity", 45, FeatureSliderDescription("0 disables Profile A adaptive ownership, peer-quality gates, and combat owner hints. 1 is very conservative with low CPU, narrow candidate reach, weak hints, and forgiving peer quality; 45 is recommended; 50 is balanced; 100 scans farther/faster, uses stronger hints, and rejects poor ping/jitter candidates more aggressively.", 40), ConfigSyncScope.ServerSynced);
			ClientStutterGuardStrength = ConfigSyncManager.Bind(config, "General", "ClientStutterGuardStrength", 50, FeatureSliderDescription("0 disables the client stutter guard. 50 is the balanced default. 1 runs cleanup sooner under pressure; 100 protects longer against cleanup stutter.", 30), ConfigSyncScope.ClientLocal);
			RpcAoiAggression = ConfigSyncManager.Bind(config, "General", "RpcAoiAggression", 35, FeatureSliderDescription("0 disables RPC AoI. 35 is the conservative default. 1 keeps a larger radius for safe visual RPCs; 50 is balanced; 100 routes eligible visual RPCs to smaller local areas. Unknown, unresolved, global, animation, noise, and state-critical RPCs always use vanilla routing.", 20), ConfigSyncScope.ServerSynced);
		}

		private static ConfigDescription Description(string description, int order)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			});
		}

		private static ConfigDescription FeatureSliderDescription(string description, int order)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			});
		}
	}
	internal static class FrameHitchDiagnostics
	{
		private struct NetworkSnapshot
		{
			public bool IsServer;

			public bool IsDedicated;

			public int ZNetPeers;

			public int ZdoPeers;

			public int QueueTotalBytes;

			public int QueueMaxBytes;

			public int QueueSoftPeers;

			public int QueueHardPeers;

			public int ZdoSectors;

			public int ZdoObjectsApprox;

			public bool ZdoObjectCountCapped;

			public long ManagedMemoryBytes;

			public override string ToString()
			{
				string text = (ZdoObjectCountCapped ? "+" : "");
				return $"network[server={IsServer} dedicated={IsDedicated} znetPeers={ZNetPeers} zdoPeers={ZdoPeers} " + $"queueTotal={FormatBytes(QueueTotalBytes)} queueMax={FormatBytes(QueueMaxBytes)} queueSoftPeers={QueueSoftPeers} queueHardPeers={QueueHardPeers} " + $"zdoSectors={ZdoSectors} zdoObjects~={ZdoObjectsApprox}{text} managedMem={FormatBytes(ManagedMemoryBytes)}]";
			}
		}

		private const double HitchThresholdSeconds = 0.12;

		private const double SevereHitchThresholdSeconds = 0.25;

		private const double SummaryIntervalSeconds = 10.0;

		private const double HitchLogCooldownSeconds = 0.75;

		private const int MaxZdoObjectsSnapshotCount = 50000;

		private static double _lastRealtime;

		private static double _lastHitchLogTime;

		private static double _nextSummaryTime;

		private static double _summarySeconds;

		private static int _summaryFrames;

		private static int _summaryHitches;

		private static int _summarySevereHitches;

		private static double _summaryMaxFrameSeconds;

		internal static void Update()
		{
			if (!DiagnosticsEnabled())
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (_lastRealtime <= 0.0)
			{
				_lastRealtime = realtimeSinceStartupAsDouble;
				_nextSummaryTime = realtimeSinceStartupAsDouble + 10.0;
				return;
			}
			double num = Math.Max(0.0, realtimeSinceStartupAsDouble - _lastRealtime);
			_lastRealtime = realtimeSinceStartupAsDouble;
			double num2 = Math.Max(Time.unscaledDeltaTime, num);
			_summaryFrames++;
			_summarySeconds += num2;
			if (num2 > _summaryMaxFrameSeconds)
			{
				_summaryMaxFrameSeconds = num2;
			}
			if (num2 >= 0.12)
			{
				_summaryHitches++;
			}
			if (num2 >= 0.25)
			{
				_summarySevereHitches++;
			}
			if (num2 >= 0.12 && realtimeSinceStartupAsDouble - _lastHitchLogTime >= 0.75)
			{
				_lastHitchLogTime = realtimeSinceStartupAsDouble;
				LogFrameSnapshot(num2, num, (num2 >= 0.25) ? "severe" : "mild");
			}
			if (realtimeSinceStartupAsDouble >= _nextSummaryTime)
			{
				LogSummary();
				_nextSummaryTime = realtimeSinceStartupAsDouble + 10.0;
			}
		}

		private static bool DiagnosticsEnabled()
		{
			try
			{
				return Plugin.Log != null && ModConfig.Enabled != null && ModConfig.Enabled.Value && ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value;
			}
			catch
			{
				return false;
			}
		}

		private static void LogFrameSnapshot(double frameDelta, double realtimeDelta, string severity)
		{
			NetworkSnapshot networkSnapshot = CaptureNetworkSnapshot();
			string text = ZDOManSendSchedulerPatch.DescribeRecentState();
			string text2 = OwnershipManager.DescribeRecentState();
			Plugin.Log.LogInfo((object)($"Frame hitch {severity}: frameMs={frameDelta * 1000.0:F1} realtimeGapMs={realtimeDelta * 1000.0:F1} " + $"instantFps={SafeFps(frameDelta):F1} avgFps10s={AverageFps():F1} " + $"{networkSnapshot} {text} {text2} sliders[scheduler={Value(ModConfig.SchedulerThroughput)} payload={Value(ModConfig.PayloadReducerStrength)} " + $"compression={Value(ModConfig.CompressionAggression)} ownership={Value(ModConfig.OwnershipIntensity)} rpcAoi={Value(ModConfig.RpcAoiAggression)} " + $"stutterGuard={Value(ModConfig.ClientStutterGuardStrength)}]"));
		}

		private static void LogSummary()
		{
			if (_summaryFrames > 0)
			{
				NetworkSnapshot networkSnapshot = CaptureNetworkSnapshot();
				Plugin.Log.LogInfo((object)($"Frame diagnostics {10.0:F0}s: frames={_summaryFrames} avgFps={AverageFps():F1} " + $"maxFrameMs={_summaryMaxFrameSeconds * 1000.0:F1} hitches>={120.0:F0}ms={_summaryHitches} " + $"severe>={250.0:F0}ms={_summarySevereHitches} {networkSnapshot}"));
				_summaryFrames = 0;
				_summarySeconds = 0.0;
				_summaryHitches = 0;
				_summarySevereHitches = 0;
				_summaryMaxFrameSeconds = 0.0;
			}
		}

		private static double AverageFps()
		{
			if (!(_summarySeconds > 0.0))
			{
				return 0.0;
			}
			return (double)_summaryFrames / _summarySeconds;
		}

		private static double SafeFps(double frameDelta)
		{
			if (!(frameDelta > 0.0))
			{
				return 0.0;
			}
			return 1.0 / frameDelta;
		}

		private static int Value(ConfigEntry<int> entry)
		{
			try
			{
				return entry?.Value ?? 0;
			}
			catch
			{
				return 0;
			}
		}

		private static NetworkSnapshot CaptureNetworkSnapshot()
		{
			NetworkSnapshot networkSnapshot = default(NetworkSnapshot);
			networkSnapshot.IsServer = NetReflection.IsServer();
			networkSnapshot.IsDedicated = NetReflection.IsDedicatedServer();
			NetworkSnapshot snapshot = networkSnapshot;
			object zDOManInstance = ZdoReflection.ZDOManInstance;
			foreach (object item in ZdoReflection.EnumeratePeers(zDOManInstance))
			{
				snapshot.ZdoPeers++;
				int sendQueueSizeForPeer = NetReflection.GetSendQueueSizeForPeer(item);
				snapshot.QueueTotalBytes += sendQueueSizeForPeer;
				if (sendQueueSizeForPeer > snapshot.QueueMaxBytes)
				{
					snapshot.QueueMaxBytes = sendQueueSizeForPeer;
				}
				if (sendQueueSizeForPeer > EffectiveConfig.PeerQueueSoftLimitBytes)
				{
					snapshot.QueueSoftPeers++;
				}
				if (sendQueueSizeForPeer > EffectiveConfig.PeerQueueHardLimitBytes)
				{
					snapshot.QueueHardPeers++;
				}
			}
			foreach (object item2 in NetReflection.EnumerateZNetPeers())
			{
				_ = item2;
				snapshot.ZNetPeers++;
			}
			CountZdoSectors(zDOManInstance, ref snapshot);
			snapshot.ManagedMemoryBytes = GC.GetTotalMemory(forceFullCollection: false);
			return snapshot;
		}

		private static void CountZdoSectors(object zdoMan, ref NetworkSnapshot snapshot)
		{
			try
			{
				if (zdoMan == null || ReflectionCache.ZDOObjectsBySectorField == null)
				{
					return;
				}
				object value = ReflectionCache.ZDOObjectsBySectorField.GetValue(zdoMan);
				if (value == null)
				{
					return;
				}
				if (value is IDictionary dictionary)
				{
					snapshot.ZdoSectors = dictionary.Count;
					{
						foreach (object value2 in dictionary.Values)
						{
							int num = 50000 - snapshot.ZdoObjectsApprox;
							if (num <= 0)
							{
								snapshot.ZdoObjectCountCapped = true;
								break;
							}
							snapshot.ZdoObjectsApprox += CountEnumerable(value2, num, ref snapshot.ZdoObjectCountCapped);
						}
						return;
					}
				}
				if (!(value is IEnumerable enumerable))
				{
					return;
				}
				foreach (object item in enumerable)
				{
					snapshot.ZdoSectors++;
					int num2 = 50000 - snapshot.ZdoObjectsApprox;
					if (num2 <= 0)
					{
						snapshot.ZdoObjectCountCapped = true;
						break;
					}
					snapshot.ZdoObjectsApprox += CountSectorEntry(item, num2, ref snapshot.ZdoObjectCountCapped);
				}
			}
			catch
			{
			}
		}

		private static int CountSectorEntry(object entry, int cap, ref bool capped)
		{
			if (entry == null)
			{
				return 0;
			}
			try
			{
				object value = entry;
				Type type = entry.GetType();
				if (type.IsGenericType && type.FullName != null && type.FullName.StartsWith("System.Collections.Generic.KeyValuePair", StringComparison.Ordinal))
				{
					value = type.GetProperty("Value")?.GetValue(entry, null) ?? entry;
				}
				return CountEnumerable(value, cap, ref capped);
			}
			catch
			{
				return 0;
			}
		}

		private static int CountEnumerable(object value, int cap, ref bool capped)
		{
			if (value == null)
			{
				return 0;
			}
			if (value is ICollection collection)
			{
				if (collection.Count > cap)
				{
					capped = true;
				}
				return Math.Min(collection.Count, cap);
			}
			if (!(value is IEnumerable enumerable))
			{
				return 0;
			}
			int num = 0;
			foreach (object item in enumerable)
			{
				_ = item;
				num++;
				if (num >= cap)
				{
					capped = true;
					break;
				}
			}
			return num;
		}

		private static string FormatBytes(long bytes)
		{
			if (bytes >= 1073741824)
			{
				return $"{(float)bytes / 1.0737418E+09f:F2}GB";
			}
			if (bytes >= 1048576)
			{
				return $"{(float)bytes / 1048576f:F1}MB";
			}
			if (bytes >= 1024)
			{
				return $"{(float)bytes / 1024f:F1}KB";
			}
			return $"{bytes}B";
		}
	}
	internal enum ClientCleanupKind
	{
		GarbageCollect
	}
	internal enum ClientCriticalWindow
	{
		InitialSync,
		Teleport,
		FullSnapshotBurst,
		Combat,
		ShipTravel
	}
	internal struct MemoryPressureSnapshot
	{
		public bool Known;

		public ulong TotalMB;

		public ulong AvailableMB;

		public int LoadPercent;

		public override string ToString()
		{
			if (!Known)
			{
				return "unknown";
			}
			return $"load={LoadPercent}% available={AvailableMB}MB total={TotalMB}MB";
		}
	}
	internal static class ClientStutterGuard
	{
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		private struct MEMORYSTATUSEX
		{
			public uint dwLength;

			public uint dwMemoryLoad;

			public ulong ullTotalPhys;

			public ulong ullAvailPhys;

			public ulong ullTotalPageFile;

			public ulong ullAvailPageFile;

			public ulong ullTotalVirtual;

			public ulong ullAvailVirtual;

			public ulong ullAvailExtendedVirtual;
		}

		[CompilerGenerated]
		private sealed class <CleanupScheduler>d__36 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <CleanupScheduler>d__36(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Expected O, but got Unknown
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					if (IsActive && RunCleanupWhenIdle)
					{
						TryRunPendingCleanup(forced: false);
					}
				}
				else
				{
					<>1__state = -1;
				}
				float num2 = Math.Max(0.25f, EffectiveConfig.ClientStutterIdleCleanupPollSeconds);
				<>2__current = (object)new WaitForSeconds(num2);
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly Dictionary<ClientCriticalWindow, double> CriticalUntil = new Dictionary<ClientCriticalWindow, double>();

		private static Plugin _plugin;

		private static Coroutine _cleanupCoroutine;

		private static bool _pendingGc;

		private static double _firstPendingSince;

		private static bool _runningCleanup;

		private static bool DelayGcCollect => true;

		private static bool DelayDuringInitialSync => true;

		private static bool DelayDuringTeleport => true;

		private static bool DelayDuringFullSnapshotBurst => true;

		private static bool DelayDuringCombat => true;

		private static bool DelayDuringShipTravel => true;

		private static bool RunCleanupWhenIdle => true;

		private static bool UseMemoryPressureGate => true;

		internal static bool IsActive
		{
			get
			{
				if (!EffectiveConfig.ClientStutterGuardEnabled)
				{
					return false;
				}
				if (IsDedicatedLike())
				{
					return false;
				}
				return true;
			}
		}

		internal static void Initialize(Plugin plugin)
		{
			_plugin = plugin;
			CriticalUntil.Clear();
			_pendingGc = false;
			_firstPendingSince = 0.0;
			_runningCleanup = false;
			EnsureCleanupScheduler();
		}

		internal static void Shutdown()
		{
			try
			{
				if (_cleanupCoroutine != null && (Object)(object)_plugin != (Object)null)
				{
					((MonoBehaviour)_plugin).StopCoroutine(_cleanupCoroutine);
				}
			}
			catch
			{
			}
			_cleanupCoroutine = null;
			_plugin = null;
		}

		private static bool IsDedicatedLike()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			try
			{
				if (NetReflection.IsDedicatedServer())
				{
					return true;
				}
			}
			catch
			{
			}
			try
			{
				if (Application.isBatchMode)
				{
					return true;
				}
				if ((int)SystemInfo.graphicsDeviceType == 4)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void MarkCriticalWindow(ClientCriticalWindow window, float seconds)
		{
			if (IsActive && !(seconds <= 0f) && IsWindowEnabled(window))
			{
				double num = Time.realtimeSinceStartupAsDouble + (double)seconds;
				if (!CriticalUntil.TryGetValue(window, out var value) || value < num)
				{
					CriticalUntil[window] = num;
				}
			}
		}

		internal static void MarkInitialSyncWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.InitialSync, Math.Max(0.1f, EffectiveConfig.ClientStutterInitialSyncWindowSeconds));
		}

		internal static void MarkTeleportWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.Teleport, Math.Max(0.1f, EffectiveConfig.ClientStutterTeleportWindowSeconds));
		}

		internal static void MarkFullSnapshotBurstWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.FullSnapshotBurst, Math.Max(0.1f, EffectiveConfig.ClientStutterFullSnapshotWindowSeconds));
		}

		internal static void MarkCombatWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.Combat, Math.Max(0.1f, EffectiveConfig.ClientStutterCombatWindowSeconds));
		}

		internal static void MarkShipTravelWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.ShipTravel, Math.Max(0.1f, EffectiveConfig.ClientStutterShipWindowSeconds));
		}

		internal static bool TryHandleGcCollect()
		{
			if (!IsActive || !DelayGcCollect || _runningCleanup)
			{
				return true;
			}
			if (!RunCleanupWhenIdle)
			{
				return true;
			}
			if (ShouldDelayCleanup(ClientCleanupKind.GarbageCollect, out var reason))
			{
				RequestPending(ClientCleanupKind.GarbageCollect, reason);
				return false;
			}
			return true;
		}

		private static void RequestPending(ClientCleanupKind kind, string reason)
		{
			if (kind == ClientCleanupKind.GarbageCollect)
			{
				_pendingGc = true;
			}
			if (_firstPendingSince <= 0.0)
			{
				_firstPendingSince = Time.realtimeSinceStartupAsDouble;
			}
			EnsureCleanupScheduler();
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)$"ClientStutterGuard: deferred {kind} ({reason}).");
			}
		}

		private static void EnsureCleanupScheduler()
		{
			if (_cleanupCoroutine == null && !((Object)(object)_plugin == (Object)null) && IsActive && RunCleanupWhenIdle)
			{
				_cleanupCoroutine = ((MonoBehaviour)_plugin).StartCoroutine(CleanupScheduler());
			}
		}

		[IteratorStateMachine(typeof(<CleanupScheduler>d__36))]
		private static IEnumerator CleanupScheduler()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <CleanupScheduler>d__36(0);
		}

		private static bool TryRunPendingCleanup(bool forced)
		{
			if (!_pendingGc)
			{
				return false;
			}
			if (!IsActive && !forced)
			{
				return false;
			}
			if (_runningCleanup)
			{
				return false;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			bool flag = _firstPendingSince > 0.0 && realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds);
			if (!forced && !flag && TryGetCriticalReason(out var reason))
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)("ClientStutterGuard: pending cleanup waits for " + reason + "."));
				}
				return false;
			}
			if (!forced && !flag && UseMemoryPressureGate && IsMemoryPlentiful(out var snapshot))
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)$"ClientStutterGuard: pending cleanup waits; memory plentiful ({snapshot}).");
				}
				return false;
			}
			bool flag2 = _pendingGc && DelayGcCollect;
			_pendingGc = false;
			_firstPendingSince = 0.0;
			_runningCleanup = true;
			try
			{
				if (flag2)
				{
					if (ModConfig.DebugLogging.Value)
					{
						Plugin.Log.LogDebug((object)"ClientStutterGuard: running coalesced GC.Collect.");
					}
					GC.Collect();
				}
			}
			finally
			{
				_runningCleanup = false;
			}
			return flag2;
		}

		private static bool ShouldDelayCleanup(ClientCleanupKind kind, out string reason)
		{
			reason = "safe window";
			if (!IsActive)
			{
				return false;
			}
			if (_firstPendingSince > 0.0 && Time.realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds))
			{
				reason = "max delay exceeded";
				return false;
			}
			if (TryGetCriticalReason(out var reason2))
			{
				if (UseMemoryPressureGate && IsMemoryPressure(out var snapshot))
				{
					reason = $"memory pressure overrides critical window ({snapshot})";
					return false;
				}
				reason = reason2;
				return true;
			}
			if (UseMemoryPressureGate && IsMemoryPlentiful(out var snapshot2))
			{
				reason = $"memory plentiful ({snapshot2})";
				return true;
			}
			return false;
		}

		private static bool TryGetCriticalReason(out string reason)
		{
			reason = null;
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			double num = 0.0;
			ClientCriticalWindow clientCriticalWindow = ClientCriticalWindow.InitialSync;
			foreach (KeyValuePair<ClientCriticalWindow, double> item in CriticalUntil)
			{
				if (item.Value > realtimeSinceStartupAsDouble && item.Value > num && IsWindowEnabled(item.Key))
				{
					clientCriticalWindow = item.Key;
					num = item.Value;
				}
			}
			if (num <= realtimeSinceStartupAsDouble)
			{
				return false;
			}
			reason = $"{clientCriticalWindow} for {num - realtimeSinceStartupAsDouble:F1}s";
			return true;
		}

		private static bool IsWindowEnabled(ClientCriticalWindow window)
		{
			return window switch
			{
				ClientCriticalWindow.InitialSync => DelayDuringInitialSync, 
				ClientCriticalWindow.Teleport => DelayDuringTeleport, 
				ClientCriticalWindow.FullSnapshotBurst => DelayDuringFullSnapshotBurst, 
				ClientCriticalWindow.Combat => DelayDuringCombat, 
				ClientCriticalWindow.ShipTravel => DelayDuringShipTravel, 
				_ => true, 
			};
		}

		private static bool IsMemoryPlentiful(out MemoryPressureSnapshot snapshot)
		{
			snapshot = GetMemorySnapshot();
			if (!snapshot.Known)
			{
				return false;
			}
			if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent))
			{
				return snapshot.AvailableMB >= (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB);
			}
			return false;
		}

		private static bool IsMemoryPressure(out MemoryPressureSnapshot snapshot)
		{
			snapshot = GetMemorySnapshot();
			if (!snapshot.Known)
			{
				return false;
			}
			if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent))
			{
				return snapshot.AvailableMB < (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB);
			}
			return true;
		}

		private static MemoryPressureSnapshot GetMemorySnapshot()
		{
			if (TryGetWindowsMemory(out var snapshot))
			{
				return snapshot;
			}
			if (TryGetProcMemInfo(out var snapshot2))
			{
				return snapshot2;
			}
			try
			{
				if (SystemInfo.systemMemorySize > 0)
				{
					ulong num = (ulong)SystemInfo.systemMemorySize;
					MemoryPressureSnapshot result = default(MemoryPressureSnapshot);
					result.Known = true;
					result.TotalMB = num;
					result.AvailableMB = num;
					result.LoadPercent = 0;
					return result;
				}
			}
			catch
			{
			}
			MemoryPressureSnapshot result2 = default(MemoryPressureSnapshot);
			result2.Known = false;
			return result2;
		}

		private static bool TryGetWindowsMemory(out MemoryPressureSnapshot snapshot)
		{
			snapshot = default(MemoryPressureSnapshot);
			try
			{
				MEMORYSTATUSEX lpBuffer = default(MEMORYSTATUSEX);
				lpBuffer.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
				if (!GlobalMemoryStatusEx(ref lpBuffer))
				{
					return false;
				}
				snapshot.Known = true;
				snapshot.TotalMB = lpBuffer.ullTotalPhys / 1024 / 1024;
				snapshot.AvailableMB = lpBuffer.ullAvailPhys / 1024 / 1024;
				snapshot.LoadPercent = (int)lpBuffer.dwMemoryLoad;
				return snapshot.TotalMB != 0;
			}
			catch
			{
				return false;
			}
		}

		private static bool TryGetProcMemInfo(out MemoryPressureSnapshot snapshot)
		{
			snapshot = default(MemoryPressureSnapshot);
			try
			{
				if (!File.Exists("/proc/meminfo"))
				{
					return false;
				}
				ulong num = 0uL;
				ulong num2 = 0uL;
				string[] array = File.ReadAllLines("/proc/meminfo");
				foreach (string text in array)
				{
					if (text.StartsWith("MemTotal:", StringComparison.Ordinal))
					{
						num = ParseKb(text);
					}
					else if (text.StartsWith("MemAvailable:", StringComparison.Ordinal))
					{
						num2 = ParseKb(text);
					}
				}
				if (num == 0L || num2 == 0L)
				{
					return false;
				}
				snapshot.Known = true;
				snapshot.TotalMB = num / 1024;
				snapshot.AvailableMB = num2 / 1024;
				snapshot.LoadPercent = (int)Math.Round(100.0 * (1.0 - (double)num2 / (double)num));
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static ulong ParseKb(string line)
		{
			string[] array = line.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 1; i < array.Length; i++)
			{
				if (ulong.TryParse(array[i], out var result))
				{
					return result;
				}
			}
			return 0uL;
		}

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardGcCollectPatch
	{
		[CompilerGenerated]
		private sealed class <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private MethodBase <>2__current;

			private int <>l__initialThreadId;

			private MethodInfo[] <>7__wrap1;

			private int <>7__wrap2;

			MethodBase IEnumerator<MethodBase>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TargetMethods>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_006e;
				}
				<>1__state = -1;
				<>7__wrap1 = typeof(GC).GetMethods(BindingFlags.Static | BindingFlags.Public);
				<>7__wrap2 = 0;
				goto IL_007c;
				IL_006e:
				<>7__wrap2++;
				goto IL_007c;
				IL_007c:
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					MethodInfo methodInfo = <>7__wrap1[<>7__wrap2];
					if (methodInfo.Name == "Collect")
					{
						<>2__current = methodInfo;
						<>1__state = 1;
						return true;
					}
					goto IL_006e;
				}
				<>7__wrap1 = null;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <TargetMethods>d__0(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<MethodBase>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<TargetMethods>d__0))]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TargetMethods>d__0(-2);
		}

		private static bool Prefix()
		{
			return ClientStutterGuard.TryHandleGcCollect();
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardZNetConnectionPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet");
			Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer");
			return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null);
		}

		private static void Postfix()
		{
			ClientStutterGuard.MarkInitialSyncWindow();
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardZdoDataPatch
	{
		[CompilerGenerated]
		private sealed class <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private MethodBase <>2__current;

			private int <>l__initialThreadId;

			private MethodInfo[] <>7__wrap1;

			private int <>7__wrap2;

			MethodBase IEnumerator<MethodBase>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TargetMethods>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_0084;
				}
				<>1__state = -1;
				Type type = ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan");
				if (type == null)
				{
					return false;
				}
				<>7__wrap1 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				<>7__wrap2 = 0;
				goto IL_0092;
				IL_0092:
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					MethodInfo methodInfo = <>7__wrap1[<>7__wrap2];
					if (methodInfo.Name == "RPC_ZDOData")
					{
						<>2__current = methodInfo;
						<>1__state = 1;
						return true;
					}
					goto IL_0084;
				}
				<>7__wrap1 = null;
				return false;
				IL_0084:
				<>7__wrap2++;
				goto IL_0092;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <TargetMethods>d__0(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<MethodBase>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<TargetMethods>d__0))]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TargetMethods>d__0(-2);
		}

		private static void Prefix(object[] __args)
		{
			try
			{
				if (NetReflection.IsServer() || __args == null || ReflectionCache.ZPackageType == null)
				{
					return;
				}
				foreach (object obj in __args)
				{
					if (obj != null && ReflectionCache.ZPackageType.IsInstanceOfType(obj) && ZPackageTools.Size(obj) >= 32768)
					{
						ClientStutterGuard.MarkFullSnapshotBurstWindow();
						break;
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardLoadingScreenPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("ZNetScene"), "InLoadingScreen", (Type[])null, (Type[])null);
		}

		private static void Postfix(bool __result)
		{
			if (__result)
			{
				ClientStutterGuard.MarkTeleportWindow();
			}
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardShipTravelPatch
	{
		private static FieldInfo _bodyField;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("Ship");
			_bodyField = ReflectionCache.SilentField(type, "m_body");
			return AccessTools.Method(type, "CustomFixedUpdate", (Type[])null, (Type[])null) ?? AccessTools.Method(type, "FixedUpdate", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if (__instance == null)
			{
				return;
			}
			try
			{
				object? obj = _bodyField?.GetValue(__instance);
				Rigidbody val = (Rigidbody)((obj is Rigidbody) ? obj : null);
				if ((Object)(object)val != (Object)null)
				{
					Vector3 linearVelocity = val.linearVelocity;
					if (((Vector3)(ref linearVelocity)).sqrMagnitude > 4f)
					{
						ClientStutterGuard.MarkShipTravelWindow();
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class MonsterAISetTargetOwnershipPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("MonsterAI");
			Type type2 = AccessTools.TypeByName("Character");
			return AccessTools.Method(type, "SetTarget", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method("MonsterAI:SetTarget", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, object[] __args)
		{
			if (__args != null && __args.Length != 0)
			{
				ClientStutterGuard.MarkCombatWindow();
				OwnershipManager.TryTransferCombatOwnership(__instance, __args[0]);
			}
		}
	}
	internal enum OwnershipCandidateReason
	{
		Generic,
		CombatTarget,
		DisconnectedOwner,
		LongUnownedPersistent
	}
	internal sealed class OwnerState
	{
		public long CurrentOwner;

		public long PreviousOwner;

		public double LastOwnerChangeTime;

		public double LastCombatOwnerChangeTime;

		public double LastSeenUnownedTime;

		public double LastTouchedTime;

		public int ChangeCount;

		public long CombatTargetUid;

		public double CombatTargetHintTime;
	}
	internal sealed class OwnerCandidate
	{
		public long Uid;

		public object Peer;

		public float Distance;

		public float Quality;

		public float Score;

		public int EstimatedLoad;

		public bool IsCombatTarget;
	}
	internal static class OwnershipManager
	{
		private const int MaxOwnerStates = 50000;

		private const double OwnerStateTtlSeconds = 600.0;

		private const double OwnerStatePruneIntervalSeconds = 30.0;

		private const bool AllowShipOwnership = false;

		private const bool AllowHealthyOwnerChallenge = false;

		private const bool AllowServerFallbackForPersistentRecovery = true;

		private static readonly Dictionary<ZdoIdKey, OwnerState> ByZdoId = new Dictionary<ZdoIdKey, OwnerState>();

		private static readonly Dictionary<long, int> OwnerLoadEstimate = new Dictionary<long, int>();

		private static double _nextProfileAScan;

		private static double _nextOwnerStatePrune;

		private static int _scanCursor;

		private static int _sectorCursor;

		private static double _lastScanTime;

		private static double _lastScanSeconds;

		private static int _lastScanVisited;

		private static int _lastScanBudget;

		private static int _lastScanOwnerChanges;

		private static int _lastScanPeerCount;

		private static int _lastScanSectorCursor;

		private static bool _lastScanSkippedNoPeers;

		internal static bool ProfileAEnabled
		{
			get
			{
				if (!EffectiveConfig.AdaptiveOwnershipEnabled || !NetReflection.IsServer())
				{
					return false;
				}
				return true;
			}
		}

		internal static void Initialize()
		{
			ByZdoId.Clear();
			OwnerLoadEstimate.Clear();
			_nextProfileAScan = 0.0;
			_nextOwnerStatePrune = 0.0;
			_scanCursor = 0;
			_sectorCursor = 0;
			_lastScanTime = 0.0;
			_lastScanSeconds = 0.0;
			_lastScanVisited = 0;
			_lastScanBudget = 0;
			_lastScanOwnerChanges = 0;
			_lastScanPeerCount = 0;
			_lastScanSectorCursor = 0;
			_lastScanSkippedNoPeers = false;
		}

		internal static bool TryTransferCombatOwnership(object monsterAI, object target)
		{
			if (!ProfileAEnabled || !EffectiveConfig.OwnerHintsEnabled)
			{
				return false;
			}
			if (!GameplayReflection.LooksLikePlayer(target))
			{
				return false;
			}
			if (!GameplayReflection.TryGetPlayerId(target, out var id) || id == 0L)
			{
				return false;
			}
			object zdoFromCharacterLike = GameplayReflection.GetZdoFromCharacterLike(monsterAI);
			if (zdoFromCharacterLike == null)
			{
				return false;
			}
			OwnerState ownerState = GetOwnerState(zdoFromCharacterLike);
			ownerState.CombatTargetUid = id;
			ownerState.CombatTargetHintTime = Time.realtimeSinceStartupAsDouble;
			return TryMaybeImproveOwner(zdoFromCharacterLike, OwnershipCandidateReason.CombatTarget);
		}

		internal static void ClearPeer(long uid)
		{
			if (uid == 0L)
			{
				return;
			}
			OwnerLoadEstimate.Remove(uid);
			foreach (OwnerState value in ByZdoId.Values)
			{
				if (value.CombatTargetUid == uid)
				{
					value.CombatTargetUid = 0L;
					value.CombatTargetHintTime = 0.0;
				}
			}
		}

		internal static void TickLightweight(object zdoMan)
		{
			if (!ProfileAEnabled)
			{
				return;
			}
			if (!HasAnyZdoPeer(zdoMan, out var peerCount))
			{
				_lastScanSkippedNoPeers = true;
				_lastScanPeerCount = 0;
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			PruneOwnerStatesIfDue(realtimeSinceStartupAsDouble);
			if (!(realtimeSinceStartupAsDouble < _nextProfileAScan))
			{
				_nextProfileAScan = realtimeSinceStartupAsDouble + (double)Math.Max(0.1f, EffectiveConfig.OwnershipScanIntervalSeconds);
				_lastScanPeerCount = peerCount;
				TryProfileAScan(zdoMan);
			}
		}

		internal static string DescribeRecentState()
		{
			double num = ((_lastScanTime > 0.0) ? Math.Max(0.0, Time.realtimeSinceStartupAsDouble - _lastScanTime) : (-1.0));
			string text = ((num >= 0.0) ? $"{num:F2}s" : "n/a");
			return $"ownershipRecent age={text} scanMs={_lastScanSeconds * 1000.0:F2} visited={_lastScanVisited}/{_lastScanBudget} " + $"ownerChanges={_lastScanOwnerChanges} zdoPeers={_lastScanPeerCount} sectorCursor={_lastScanSectorCursor} " + $"skippedNoPeers={_lastScanSkippedNoPeers}";
		}

		private static bool HasAnyZdoPeer(object zdoMan, out int peerCount)
		{
			peerCount = 0;
			try
			{
				if (zdoMan == null || ReflectionCache.ZDOManPeersField == null)
				{
					return false;
				}
				object value = ReflectionCache.ZDOManPeersField.GetValue(zdoMan);
				if (value is ICollection collection)
				{
					peerCount = collection.Count;
					return peerCount > 0;
				}
				if (value is IEnumerable enumerable)
				{
					{
						IEnumerator enumerator = enumerable.GetEnumerator();
						try
						{
							if (enumerator.MoveNext())
							{
								_ = enumerator.Current;
								peerCount++;
								return true;
							}
						}
						finally
						{
							IDisposable disposable = enumerator as IDisposable;
							if (disposable != null)
							{
								disposable.Dispose();
							}
						}
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private static void TryProfileAScan(object zdoMan)
		{
			try
			{
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				OwnerLoadEstimate.Clear();
				object obj = ReflectionCache.ZDOObjectsBySectorField?.GetValue(zdoMan);
				if (obj == null)
				{
					return;
				}
				int visited = 0;
				int acted = 0;
				int num = Math.Max(1, EffectiveConfig.OwnershipScanBudget);
				ScanSectorBuckets(obj, num, ref visited, ref acted);
				double num2 = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble;
				_lastScanTime = Time.realtimeSinceStartupAsDouble;
				_lastScanSeconds = num2;
				_lastScanVisited = visited;
				_lastScanBudget = num;
				_lastScanOwnerChanges = acted;
				_lastScanSectorCursor = _sectorCursor;
				_lastScanSkippedNoPeers = false;
				if (ModConfig.DebugLogging.Value && (acted > 0 || visited > 0))
				{
					bool flag = num2 >= 0.008;
					if (acted > 0 || flag)
					{
						Plugin.Log.LogInfo((object)$"Adaptive ownership scan: elapsed={num2 * 1000.0:F2}ms slow={flag} visited={visited}/{num}, ownerChanges={acted}, zdoPeers={_lastScanPeerCount}, loadOwners={OwnerLoadEstimate.Count}, sectorCursor={_sectorCursor}, stride={EffectiveConfig.OwnershipScanStride}");
					}
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Adaptive ownership scan failed: " + ex.Message));
				}
			}
		}

		private static void ScanSectorBuckets(object sectors, int budget, ref int visited, ref int acted)
		{
			if (sectors is IList buckets)
			{
				ScanIndexedBuckets(buckets, budget, ref visited, ref acted);
			}
			else if (sectors is IDictionary dictionary)
			{
				ScanEnumerableBuckets(dictionary.Values, dictionary.Count, budget, ref visited, ref acted);
			}
			else if (sectors is ICollection collection && sectors is IEnumerable buckets2)
			{
				ScanEnumerableBuckets(buckets2, collection.Count, budget, ref visited, ref acted);
			}
			else if (sectors is IEnumerable buckets3)
			{
				ScanEnumerableBuckets(buckets3, 0, budget, ref visited, ref acted);
			}
		}

		private static void ScanIndexedBuckets(IList buckets, int budget, ref int visited, ref int acted)
		{
			if (buckets == null || buckets.Count <= 0)
			{
				return;
			}
			if (_sectorCursor < 0 || _sectorCursor >= buckets.Count)
			{
				_sectorCursor = 0;
			}
			int sectorCursor = _sectorCursor;
			for (int i = 0; i < buckets.Count; i++)
			{
				int num = (sectorCursor + i) % buckets.Count;
				if (ScanBucket(buckets[num], budget, ref visited, ref acted))
				{
					_sectorCursor = (num + 1) % buckets.Count;
					return;
				}
			}
			_sectorCursor = 0;
		}

		private static void ScanEnumerableBuckets(IEnumerable buckets, int bucketCount, int budget, ref int visited, ref int acted)
		{
			if (buckets == null)
			{
				return;
			}
			if (bucketCount <= 0)
			{
				foreach (object bucket in buckets)
				{
					if (ScanBucket(bucket, budget, ref visited, ref acted))
					{
						return;
					}
				}
				_sectorCursor = 0;
				return;
			}
			if (_sectorCursor < 0 || _sectorCursor >= bucketCount)
			{
				_sectorCursor = 0;
			}
			int sectorCursor = _sectorCursor;
			int num = 0;
			foreach (object bucket2 in buckets)
			{
				if (num++ >= sectorCursor && ScanBucket(bucket2, budget, ref visited, ref acted))
				{
					_sectorCursor = num % bucketCount;
					return;
				}
			}
			if (sectorCursor > 0)
			{
				num = 0;
				foreach (object bucket3 in buckets)
				{
					if (num >= sectorCursor)
					{
						break;
					}
					num++;
					if (ScanBucket(bucket3, budget, ref visited, ref acted))
					{
						_sectorCursor = num % bucketCount;
						return;
					}
				}
			}
			_sectorCursor = 0;
		}

		private static bool ScanBucket(object bucket, int budget, ref int visited, ref int acted)
		{
			bucket = GetBucketValue(bucket);
			if (!(bucket is IEnumerable enumerable))
			{
				return false;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				_scanCursor++;
				int num = Math.Max(1, EffectiveConfig.OwnershipScanStride);
				if (_scanCursor % num == 0)
				{
					TrackCurrentOwnerLoad(item);
					visited++;
					if (TryMaybeImproveOwner(item, OwnershipCandidateReason.Generic))
					{
						acted++;
					}
					if (visited >= budget)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static object GetBucketValue(object bucket)
		{
			if (bucket == null)
			{
				return null;
			}
			if (bucket is DictionaryEntry dictionaryEntry)
			{
				return dictionaryEntry.Value;
			}
			try
			{
				Type type = bucket.GetType();
				if (type.IsGenericType && type.FullName != null && type.FullName.StartsWith("System.Collections.Generic.KeyValuePair", StringComparison.Ordinal))
				{
					return type.GetProperty("Value")?.GetValue(bucket, null) ?? bucket;
				}
			}
			catch
			{
			}
			return bucket;
		}

		private static void TrackCurrentOwnerLoad(object zdo)
		{
			if (ZdoReflection.TryGetOwner(zdo, out var owner) && owner != 0L)
			{
				OwnerLoadEstimate.TryGetValue(owner, out var value);
				OwnerLoadEstimate[owner] = value + 1;
			}
		}

		private static bool TryMaybeImproveOwner(object zdo, OwnershipCandidateReason reason)
		{
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: 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)
			if (zdo == null || ReflectionCache.ZDOGetPositionMethod == null)
			{
				return false;
			}
			if (!ZdoReflection.TryGetOwner(zdo, out var owner))
			{
				owner = 0L;
			}
			bool persistent;
			bool flag = ZdoReflection.TryGetPersistent(zdo, out persistent) && persistent;
			bool num = ZdoKeyPolicy.LooksPlayerLike(zdo);
			bool flag2 = ZdoKeyPolicy.LooksShipLike(zdo);
			if (num)
			{
				return false;
			}
			if (flag2 && reason != OwnershipCandidateReason.DisconnectedOwner)
			{
				return false;
			}
			OwnerState ownerState = GetOwnerState(zdo);
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			bool flag3 = owner != 0L && IsUidCurrentlyConnected(owner);
			long uid;
			bool flag4 = owner != 0L && ZdoReflection.TryGetServerSessionId(out uid) && owner == uid;
			if (owner == 0L)
			{
				if (ownerState.LastSeenUnownedTime <= 0.0)
				{
					ownerState.LastSeenUnownedTime = realtimeSinceStartupAsDouble;
				}
				if (flag && realtimeSinceStartupAsDouble - ownerState.LastSeenUnownedTime >= (double)EffectiveConfig.RecoverUnownedAfterSeconds)
				{
					reason = OwnershipCandidateReason.LongUnownedPersistent;
				}
			}
			else
			{
				ownerState.LastSeenUnownedTime = 0.0;
			}
			if (flag && reason != OwnershipCandidateReason.LongUnownedPersistent && reason != OwnershipCandidateReason.DisconnectedOwner)
			{
				return false;
			}
			if (!flag3 && owner != 0L && !flag4)
			{
				reason = OwnershipCandidateReason.DisconnectedOwner;
			}
			Vector3 position = ZdoReflection.GetPosition(zdo, Vector3.zero);
			if (reason == OwnershipCandidateReason.Generic && flag3)
			{
				return false;
			}
			OwnerCandidate ownerCandidate = FindBestCandidate(zdo, position, ownerState, reason);
			if (ownerCandidate == null)
			{
				if (reason == OwnershipCandidateReason.LongUnownedPersistent)
				{
					return RecoverToServerOwner(zdo, ownerState, owner, reason);
				}
				return false;
			}
			if (ownerCandidate.Uid == owner)
			{
				return false;
			}
			float num2 = ComputeCurrentOwnerScore(owner, position, ownerState, reason, flag3, flag4);
			if (!IsCandidateBetter(ownerCandidate.Score, num2, reason))
			{
				return false;
			}
			float num3 = ((reason == OwnershipCandidateReason.CombatTarget) ? EffectiveConfig.OwnerHintSwitchCooldownSeconds : EffectiveConfig.OwnerSwitchCooldownSeconds);
			if (flag2)
			{
				num3 = Math.Max(num3, EffectiveConfig.ShipOwnerSwitchCooldownSeconds);
			}
			if (realtimeSinceStartupAsDouble - ownerState.LastOwnerChangeTime < (double)num3)
			{
				return false;
			}
			if (reason == OwnershipCandidateReason.CombatTarget && realtimeSinceStartupAsDouble - ownerState.LastCombatOwnerChangeTime < (double)EffectiveConfig.OwnerHintSwitchCooldownSeconds)
			{
				return false;
			}
			if (!ZdoReflection.TrySetOwner(zdo, ownerCandidate.Uid))
			{
				return false;
			}
			ownerState.PreviousOwner = owner;
			ownerState.CurrentOwner = ownerCandidate.Uid;
			ownerState.LastOwnerChangeTime = realtimeSinceStartupAsDouble;
			if (reason == OwnershipCandidateReason.CombatTarget)
			{
				ownerState.LastCombatOwnerChangeTime = realtimeSinceStartupAsDouble;
			}
			ownerState.ChangeCount++;
			ZdoReflection.ForceSend(zdo);
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)$"ProfileA owner {reason}: {owner}->{ownerCandidate.Uid}, best={ownerCandidate.Score:F1}, current={num2:F1}, q={ownerCandidate.Quality:F1}, dist={ownerCandidate.Distance:F1}, load={ownerCandidate.EstimatedLoad}");
			}
			return true;
		}

		private static OwnerCandidate FindBestCandidate(object zdo, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			OwnerCandidate ownerCandidate = null;
			float num = ((reason == OwnershipCandidateReason.CombatTarget) ? Math.Max(EffectiveConfig.OwnershipCandidateRadius, EffectiveConfig.OwnerHintCandidateRadius) : EffectiveConfig.OwnershipCandidateRadius);
			foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
			{
				if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L)
				{
					continue;
				}
				Vector3 peerRefPos = NetReflection.GetPeerRefPos(item);
				float num2 = Vector3.Distance(zdoPosition, peerRefPos);
				if (num2 > num)
				{
					continue;
				}
				PeerQualityState peerQualityState = PeerQualityMeter.UpdateFromPeer(item) ?? PeerQualityMeter.GetByUid(uid) ?? PeerQualityMeter.GetByPeer(item);
				if (CandidateConnectionAllowed(peerQualityState, reason))
				{
					int num3 = EstimateOwnerLoad(uid);
					float num4 = ComputeCandidateScore(uid, num2, peerQualityState, num3, state, reason);
					if (ownerCandidate == null || num4 < ownerCandidate.Score)
					{
						ownerCandidate = new OwnerCandidate
						{
							Uid = uid,
							Peer = item,
							Distance = num2,
							Quality = (peerQualityState?.ConnectionQualityMs ?? 999f),
							Score = num4,
							EstimatedLoad = num3,
							IsCombatTarget = (uid == state.CombatTargetUid && IsCombatHintFresh(state))
						};
					}
				}
			}
			return ownerCandidate;
		}

		private static float ComputeCandidateScore(long uid, float distance, PeerQualityState quality, int load, OwnerState state, OwnershipCandidateReason reason)
		{
			float num = quality?.ConnectionQualityMs ?? 999f;
			num += distance * Math.Max(0f, EffectiveConfig.OwnershipDistanceScoreWeight);
			num += (float)load * Math.Max(0f, EffectiveConfig.OwnershipLoadPenaltyPerZdo);
			if (uid == state.CombatTargetUid && IsCombatHintFresh(state))
			{
				num -= Math.Max(0f, EffectiveConfig.OwnerHintScoreBonusMs);
			}
			return num;
		}

		private static float ComputeCurrentOwnerScore(long currentOwner, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason, bool currentConnected, bool currentIsServer)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (currentOwner == 0L)
			{
				return 999f;
			}
			if (!currentConnected && !currentIsServer)
			{
				return 999f;
			}
			if (currentIsServer)
			{
				return EffectiveConfig.ServerFallbackPenaltyMs;
			}
			PeerQualityState byUid = PeerQualityMeter.GetByUid(currentOwner);
			object obj = FindZdoPeerByUid(currentOwner);
			float distance = ((obj != null) ? Vector3.Distance(zdoPosition, NetReflection.GetPeerRefPos(obj)) : 0f);
			return ComputeCandidateScore(currentOwner, distance, byUid, EstimateOwnerLoad(currentOwner), state, reason);
		}

		private static bool RecoverToServerOwner(object zdo, OwnerState state, long currentOwner, OwnershipCandidateReason reason)
		{
			if (!ZdoReflection.TryGetServerSessionId(out var uid) || uid == 0L)
			{
				return false;
			}
			if (!ZdoReflection.TrySetOwner(zdo, uid))
			{
				return false;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			state.PreviousOwner = currentOwner;
			state.CurrentOwner = uid;
			state.LastOwnerChangeTime = realtimeSinceStartupAsDouble;
			state.ChangeCount++;
			ZdoReflection.ForceSend(zdo);
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)$"ProfileA recovery to server owner {reason}: {currentOwner}->{uid}");
			}
			return true;
		}

		private static int EstimateOwnerLoad(long uid)
		{
			if (uid == 0L)
			{
				return 0;
			}
			if (OwnerLoadEstimate.TryGetValue(uid, out var value))
			{
				return value;
			}
			return PeerQualityMeter.GetByUid(uid)?.OwnedDynamicEstimate ?? 0;
		}

		private static bool CandidateConnectionAllowed(PeerQualityState candidate, OwnershipCandidateReason reason)
		{
			if (!EffectiveConfig.PeerQualityEnabled)
			{
				return true;
			}
			if (candidate == null || !candidate.HasAnySample)
			{
				return false;
			}
			if (candidate.PingEmaMs > EffectiveConfig.MaxCandidatePingMs)
			{
				return false;
			}
			if (candidate.PingJitterMs > EffectiveConfig.MaxCandidateJitterMs)
			{
				return false;
			}
			return true;
		}

		private static bool IsCandidateBetter(float candidateScore, float currentScore, OwnershipCandidateReason reason)
		{
			if (currentScore <= 0f || currentScore >= 900f)
			{
				return true;
			}
			float num = Math.Max(0f, EffectiveConfig.OwnershipRelativeHysteresis);
			float val = Math.Max(0f, EffectiveConfig.OwnershipAbsoluteHysteresisMs);
			if (reason == OwnershipCandidateReason.DisconnectedOwner || reason == OwnershipCandidateReason.LongUnownedPersistent)
			{
				val = Math.Min(val, 5f);
			}
			float num2 = Math.Max(val, currentScore * num);
			return currentScore - candidateScore >= num2;
		}

		private static OwnerState GetOwnerState(object zdo)
		{
			if (!ZdoReflection.TryGetIdKey(zdo, out var key))
			{
				key = ZdoIdKey.FromRuntimeObject(zdo);
			}
			if (!ByZdoId.TryGetValue(key, out var value))
			{
				value = new OwnerState();
				ByZdoId[key] = value;
			}
			value.LastTouchedTime = Time.realtimeSinceStartupAsDouble;
			return value;
		}

		private static void PruneOwnerStatesIfDue(double now)
		{
			if (now < _nextOwnerStatePrune)
			{
				return;
			}
			_nextOwnerStatePrune = now + 30.0;
			List<ZdoIdKey> list = new List<ZdoIdKey>();
			foreach (KeyValuePair<ZdoIdKey, OwnerState> item in ByZdoId)
			{
				OwnerState value = item.Value;
				if (value == null || (value.LastTouchedTime > 0.0 && now - value.LastTouchedTime >= 600.0))
				{
					list.Add(item.Key);
				}
			}
			foreach (ZdoIdKey item2 in list)
			{
				ByZdoId.Remove(item2);
			}
			while (ByZdoId.Count > 50000 && RemoveOldestOwnerState())
			{
			}
		}

		private static bool RemoveOldestOwnerState()
		{
			ZdoIdKey key = default(ZdoIdKey);
			double num = double.MaxValue;
			bool flag = false;
			foreach (KeyValuePair<ZdoIdKey, OwnerState> item in ByZdoId)
			{
				double num2 = item.Value?.LastTouchedTime ?? 0.0;
				if (num2 < num)
				{
					num = num2;
					key = item.Key;
					flag = true;
				}
			}
			if (!flag)
			{
				return false;
			}
			ByZdoId.Remove(key);
			return true;
		}

		private static bool IsCombatHintFresh(OwnerState state)
		{
			if (state == null || state.CombatTargetUid == 0L)
			{
				return false;
			}
			return Time.realtimeSinceStartupAsDouble - state.CombatTargetHintTime <= (double)Math.Max(0.1f, EffectiveConfig.OwnerHintLifetimeSeconds);
		}

		private static bool IsUidCurrentlyConnected(long uid)
		{
			return FindZdoPeerByUid(uid) != null;
		}

		private static object FindZdoPeerByUid(long uid)
		{
			if (uid == 0L)
			{
				return null;
			}
			foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
			{
				if (NetReflection.TryGetPeerUid(item, out var uid2) && uid2 == uid)
				{
					return item;
				}
			}
			return null;
		}
	}
	internal static class CompressionDiagnostics
	{
		private const double SummaryIntervalSeconds = 10.0;

		private static int _encoded;

		private static int _decoded;

		private static long _rawEncodeBytes;

		private static long _compressedEncodeBytes;

		private static long _compressedDecodeBytes;

		private static long _rawDecodeBytes;

		private static double _encodeSeconds;

		private static double _decodeSeconds;

		private static double _maxEncodeSeconds;

		private static double _maxDecodeSeconds;

		private static double _nextSummaryTime;

		internal static void RecordEncode(int rawBytes, int compressedBytes, double seconds)
		{
			if (ModConfig.DebugLogging.Value)
			{
				_encoded++;
				_rawEncodeBytes += Math.Max(0, rawBytes);
				_compressedEncodeBytes += Math.Max(0, compressedBytes);
				_encodeSeconds += Math.Max(0.0, seconds);
				if (seconds > _maxEncodeSeconds)
				{
					_maxEncodeSeconds = seconds;
				}
				LogIfDue();
			}
		}

		internal static void RecordDecode(int compressedBytes, int rawBytes, double seconds)
		{
			if (ModConfig.DebugLogging.Value)
			{
				_decoded++;
				_compressedDecodeBytes += Math.Max(0, compressedBytes);
				_rawDecodeBytes += Math.Max(0, rawBytes);
				_decodeSeconds += Math.Max(0.0, seconds);
				if (seconds > _maxDecodeSeconds)
				{
					_maxDecodeSeconds = seconds;
				}
				LogIfDue();
			}
		}

		private static void LogIfDue()
		{
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (!(realtimeSinceStartupAsDouble < _nextSummaryTime))
			{
				_nextSummaryTime = realtimeSinceStartupAsDouble + 10.0;
				if (_encoded > 0 || _decoded > 0)
				{
					float num = ((_rawEncodeBytes > 0) ? ((float)_compressedEncodeBytes / (float)_rawEncodeBytes) : 0f);
					Plugin.Log.LogDebug((object)($"Compression summary {10.0:F0}s: encoded={_encoded} raw={FormatBytes(_rawEncodeBytes)} compressed={FormatBytes(_compressedEncodeBytes)} ratio={num:F2} " + $"encodeAvgMs={_encodeSeconds / (double)Math.Max(1, _encoded) * 1000.0:F2} encodeMaxMs={_maxEncodeSeconds * 1000.0:F2} " + $"decoded={_decoded} compressedIn={FormatBytes(_compressedDecodeBytes)} rawOut={FormatBytes(_rawDecodeBytes)} " + $"decodeAvgMs={_decodeSeconds / (double)Math.Max(1, _decoded) * 1000.0:F2} decodeMaxMs={_maxDecodeSeconds * 1000.0:F2}"));
				}
				_encoded = 0;
				_decoded = 0;
				_rawEncodeBytes = 0L;
				_compressedEncodeBytes = 0L;
				_compressedDecodeBytes = 0L;
				_rawDecodeBytes = 0L;
				_encodeSeconds = 0.0;
				_decodeSeconds = 0.0;
				_maxEncodeSeconds = 0.0;
				_maxDecodeSeconds = 0.0;
			}
		}

		private static string FormatBytes(long bytes)
		{
			if (bytes >= 1048576)
			{
				return $"{(float)bytes / 1048576f:F1}MB";
			}
			if (bytes >= 1024)
			{
				return $"{(float)bytes / 1024f:F1}KB";
			}
			return $"{bytes}B";
		}
	}
	[HarmonyPatch]
	internal static class ZSteamSocketSendCompressionPatch
	{
		private static MethodBase TargetMethod()
		{
			return ReflectionCache.ZSteamSocketSendMethod ?? AccessTools.Method("ZSteamSocket:Send", (Type[])null, (Type[])null);
		}

		private static void Prefix(object __instance, ref object __0)
		{
			if (!EffectiveConfig.CompressionEnabled || __instance == null || __0 == null || !FeatureNegotiation.IsCompressionActiveForSocket(__instance))
			{
				return;
			}
			try
			{
				int rawBytes = ZPackageTools.Size(__0);
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				if (ZPackageTools.TryBuildCompressedPackage(__0, out var compressedPackage))
				{
					__0 = compressedPackage;
					CompressionDiagnostics.RecordEncode(rawBytes, ZPackageTools.Size(compressedPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble);
				}
			}
			catch (Exception ex)
			{
				FeatureNegotiation.RecordCompressionFailure(__instance);
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("ZSteamSocket.Send compression failed: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class ZSteamSocketRecvCompressionPatch
	{
		private static MethodBase TargetMethod()
		{
			return ReflectionCache.ZSteamSocketRecvMethod ?? AccessTools.Method("ZSteamSocket:Recv", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, ref object __result)
		{
			if (__result == null)
			{
				return;
			}
			try
			{
				int compressedBytes = ZPackageTools.Size(__result);
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				if (ZPackageTools.TryDecompressPackage(__result, out var rawPackage))
				{
					__result = rawPackage;
					CompressionDiagnostics.RecordDecode(compressedBytes, ZPackageTools.Size(rawPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble);
				}
			}
			catch (Exception ex)
			{
				FeatureNegotiation.RecordCompressionFailure(__instance);
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("ZSteamSocket.Recv decompression failed: " + ex.Message));
				}
			}
		}
	}
	[Flags]
	internal enum PeerFeatureFlags
	{
		None = 0,
		Compression = 1,
		RpcAoi = 4
	}
	internal sealed class PeerFeatureState
	{
		public object Rpc;

		public object Socket;

		public long Uid;

		public bool RegisteredRpc;

		public bool HandshakeSent;

		public bool HandshakeReceived;

		public int RemoteProtocol;

		public PeerFeatureFlags RemoteCapabilities;

		public bool CompressionActive;

		public bool RpcAoiActive;

		public int CompressionFailures;

		public double LastHandshakeTime;
	}
	internal static class FeatureNegotiation
	{
		internal const int ProtocolVersion = 2;

		internal const int FeatureMagic = 1179536211;

		internal const string RpcName = "SkadiNet_Features";

		private static readonly object Lock = new object();

		private static readonly Dictionary<object, PeerFeatureState> ByRpc = new Dictionary<object, PeerFeatureState>();

		private static readonly Dictionary<object, PeerFeatureState> BySocket = new Dictionary<object, PeerFeatureState>();

		private static readonly Dictionary<long, PeerFeatureState> ByUid = new Dictionary<long, PeerFeatureState>();

		internal static PeerFeatureFlags LocalCapabilities => PeerFeatureFlags.Compression | PeerFeatureFlags.RpcAoi;

		internal static void Initialize()
		{
			lock (Lock)
			{
				ByRpc.Clear();
				BySocket.Clear();
				ByUid.Clear();
			}
		}

		internal static PeerFeatureState GetOrCreateByRpc(object rpc, long uid = 0L)
		{
			if (rpc == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (!ByRpc.TryGetValue(rpc, out var value))
				{
					value = new PeerFeatureState
					{
						Rpc = rpc,
						Uid = uid
					};
					ByRpc[rpc] = value;
				}
				if (uid != 0L)
				{
					value.Uid = uid;
					ByUid[uid] = value;
				}
				object socketFromRpc = NetReflection.GetSocketFromRpc(rpc);
				if (socketFromRpc != null)
				{
					value.Socket = socketFromRpc;
					BySocket[socketFromRpc] = value;
				}
				return value;
			}
		}

		internal static PeerFeatureState GetBySocket(object socket)
		{
			if (socket == null)
			{
				return null;
			}
			lock (Lock)
			{
				BySocket.TryGetValue(socket, out var value);
				return value;
			}
		}

		internal static PeerFeatureState GetByUid(long uid)
		{
			lock (Lock)
			{
				ByUid.TryGetValue(uid, out var value);
				return value;
			}
		}

		internal static void ClearPeer(object peerOrRpc, long uid)
		{
			object obj = NetReflection.GetPeerRpc(peerOrRpc);
			if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc))
			{
				obj = peerOrRpc;
			}
			object socketFromRpc = NetReflection.GetSocketFromRpc(obj);
			lock (Lock)
			{
				PeerFeatureState value = null;
				if (uid != 0L)
				{
					ByUid.TryGetValue(uid, out value);
				}
				if (value == null && obj != null)
				{
					ByRpc.TryGetValue(obj, out value);
				}
				if (value == null && socketFromRpc != null)
				{
					BySocket.TryGetValue(socketFromRpc, out value);
				}
				if (uid != 0L)
				{
					ByUid.Remove(uid);
				}
				if (obj != null)
				{
					ByRpc.Remove(obj);
				}
				if (socketFromRpc != null)
				{
					BySocket.Remove(socketFromRpc);
				}
				if (value != null)
				{
					if (value.Uid != 0L)
					{
						ByUid.Remove(value.Uid);
					}
					if (value.Rpc != null)
					{
						ByRpc.Remove(value.Rpc);
					}
					if (value.Socket != null)
					{
						BySocket.Remove(value.Socket);
					}
					RemoveState(ByRpc, value);
					RemoveState(BySocket, value);
					RemoveState(ByUid, value);
				}
			}
		}

		private static void RemoveState<TKey>(Dictionary<TKey, PeerFeatureState> map, PeerFeatureState state)
		{
			if (state == null || map.Count == 0)
			{
				return;
			}
			List<TKey> list = new List<TKey>();
			foreach (KeyValuePair<TKey, PeerFeatureState> item in map)
			{
				if (item.Value == state)
				{
					list.Add(item.Key);
				}
			}
			foreach (TKey item2 in list)
			{
				map.Remove(item2);
			}
		}

		internal static bool IsCompressionActiveForSocket(object socket)
		{
			if (!EffectiveConfig.CompressionEnabled)
			{
				return false;
			}
			PeerFeatureState bySocket = GetBySocket(socket);
			if (bySocket == null)
			{
				return false;
			}
			if (bySocket.CompressionFailures >= 1)
			{
				return false;
			}
			if (bySocket.HandshakeReceived)
			{
				return Supports(bySocket, PeerFeatureFlags.Compression);
			}
			return false;
		}

		internal static bool IsRpcAoiActiveForUid(long uid)
		{
			if (!EffectiveConfig.RpcAoiEnabled)
			{
				return false;
			}
			PeerFeatureState byUid = GetByUid(uid);
			if (byUid == null)
			{
				return true;
			}
			if (byUid.HandshakeReceived)
			{
				return Supports(byUid, PeerFeatureFlags.RpcAoi);
			}
			return true;
		}

		internal static void RecordCompressionFailure(object socket)
		{
			PeerFeatureState bySocket = GetBySocket(socket);
			if (bySocket != null)
			{
				bySocket.CompressionFailures++;
				bySocket.CompressionActive = false;
			}
		}

		internal static void OnNewConnection(object znetPeer)
		{
			if (ModConfig.Enabled.Value)
			{
				object peerRpc = NetReflection.GetPeerRpc(znetPeer);
				if (peerRpc != null)
				{
					NetReflection.TryGetPeerUid(znetPeer, out var uid);
					PeerFeatureState orCreateByRpc = GetOrCreateByRpc(peerRpc, uid);
					RegisterRpc(peerRpc, orCreateByRpc);
					SendHello(peerRpc, orCreateByRpc);
				}
			}
		}

		private static void RegisterRpc(object rpc, PeerFeatureState state)
		{
			if (rpc == null || state == null || state.RegisteredRpc)
			{
				return;
			}
			try
			{
				if (!(ReflectionCache.ZRpcRegisterGenericPackageMethod == null) && !(ReflectionCache.ZRpcType == null) && !(ReflectionCache.ZPackageType == null))
				{
					MethodInfo method = typeof(FeatureNegotiation).GetMethod("RPC_Features_Generic", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType);
					Delegate @delegate = Delegate.CreateDelegate(typeof(Action<, >).MakeGenericType(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType), method);
					ReflectionCache.ZRpcRegisterGenericPackageMethod.Invoke(rpc, new object[2] { "SkadiNet_Features", @delegate });
					state.RegisteredRpc = true;
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Could not register SkadiNet_Features: " + ex.Message));
				}
			}
		}

		private static void SendHello(object rpc, PeerFeatureState state)
		{
			if (rpc == null || state == null || state.HandshakeSent)
			{
				return;
			}
			try
			{
				object obj = ZPackageTools.NewPackage();
				ZPackageTools.WriteInt(obj, 1179536211);
				ZPackageTools.WriteInt(obj, 2);
				ZPackageTools.WriteInt(obj, (int)LocalCapabilities);
				ZPackageTools.WriteString(obj, "1.0.1");
				ReflectionCache.ZRpcInvokeMethod?.Invoke(rpc, new object[2]
				{
					"SkadiNet_Features",
					new object[1] { obj }
				});
				state.HandshakeSent = true;
				state.LastHandshakeTime = Time.realtimeSinceStartupAsDouble;
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Could not send SkadiNet feature handshake: " + ex.Message));
				}
			}
		}

		private static void RPC_Features_Generic<TRpc, TPkg>(TRpc rpc, TPkg pkg)
		{
			RPC_Features(rpc, pkg);
		}

		private static void RPC_Features(object rpc, object pkg)
		{
			try
			{
				PeerFeatureState orCreateByRpc = GetOrCreateByRpc(rpc, 0L);
				if (orCreateByRpc == null || pkg == null)
				{
					return;
				}
				int pos = ZPackageTools.GetPos(pkg);
				ZPackageTools.SetPos(pkg, 0);
				if (ZPackageTools.ReadInt(pkg) != 1179536211)
				{
					ZPackageTools.SetPos(pkg, pos);
					return;
				}
				int num = ZPackageTools.ReadInt(pkg);
				int remoteCapabilities = ZPackageTools.ReadInt(pkg);
				string text = ZPackageTools.ReadString(pkg);
				orCreateByRpc.RemoteProtocol = num;
				orCreateByRpc.RemoteCapabilities = (PeerFeatureFlags)remoteCapabilities;
				orCreateByRpc.HandshakeReceived = num >= 1;
				RefreshActiveFlags(orCreateByRpc);
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)$"SkadiNet feature handshake: protocol={num}, capabilities={orCreateByRpc.RemoteCapabilities}, version={text}, compression={orCreateByRpc.CompressionActive}, rpcAoi={orCreateByRpc.RpcAoiActive}");
				}
				if (!orCreateByRpc.HandshakeSent)
				{
					SendHello(rpc, orCreateByRpc);
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("SkadiNet feature handshake receive failed: " + ex.Message));
				}
			}
		}

		private static bool Supports(PeerFeatureState state, PeerFeatureFlags flag)
		{
			if (state != null)
			{
				return (state.RemoteCapabilities & flag) != 0;
			}
			return false;
		}

		private static void RefreshActiveFlags(PeerFeatureState state)
		{
			if (state != null)
			{
				state.CompressionActive = EffectiveConfig.CompressionEnabled && Supports(state, PeerFeatureFlags.Compression);
				state.RpcAoiActive = EffectiveConfig.RpcAoiEnabled && Supports(state, PeerFeatureFlags.RpcAoi);
			}
		}
	}
	[HarmonyPatch]
	internal static class ZNetOnNewConnectionFeatureHandshakePatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet");
			Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer");
			return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __0)
		{
			FeatureNegotiation.OnNewConnection(__0);
		}
	}
	internal static class PeerLifecycle
	{
		internal static void ClearDisconnectedPeer(object zdoPeer)
		{
			long uid = 0L;
			NetReflection.TryGetPeerUid(zdoPeer, out uid);
			FeatureNegotiation.ClearPeer(zdoPeer, uid);
			PeerQualityMeter.ClearPeer(zdoPeer, uid);
			OwnershipManager.ClearPeer(uid);
			ZDOManSendSchedulerPatch.ClearPeer(uid);
		}
	}
	[HarmonyPatch]
	internal static class PeerLifecycleDisconnectPatch
	{
		[CompilerGenerated]
		private sealed class <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private MethodBase <>2__current;

			private int <>l__initialThreadId;

			private HashSet<MethodBase>.Enumerator <>7__wrap1;

			MethodBase IEnumerator<MethodBase>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TargetMethods>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 1)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>7__wrap1 = default(HashSet<MethodBase>.Enumerator);
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						HashSet<MethodBase> hashSet = new HashSet<MethodBase>();
						AddNamedMethods(hashSet, ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet"), "Disconnect", "OnDisconnected", "OnPeerDisconnected", "RemovePeer", "RPC_Disconnect");
						AddNamedMethods(hashSet, ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"), "RemovePeer", "RemoveZdoPeer", "DisconnectPeer");
						<>7__wrap1 = hashSet.GetEnumerator();
						<>1__state = -3;
						break;
					}
					case 1:
						<>1__state = -3;
						break;
					}
					if (<>7__wrap1.MoveNext())
					{
						MethodBase current = <>7__wrap1.Current;
						<>2__current = current;
						<>1__state = 1;
						return true;
					}
					<>m__Finally1();
					<>7__wrap1 = default(HashSet<MethodBase>.Enumerator);
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				((IDisposable)<>7__wrap1).Dispose();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <TargetMethods>d__0(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<MethodBase>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<TargetMethods>d__0))]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TargetMethods>d__0(-2);
		}

		private static void AddNamedMethods(HashSet<MethodBase> targets, Type type, params string[] names)
		{
			if (targets == null || type == null || names == null)
			{
				return;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				for (int j = 0; j < names.Length; j++)
				{
					if (methodInfo.Name == names[j])
					{
						targets.Add(methodInfo);
						break;
					}
				}
			}
		}

		private static void Postfix(object[] __args)
		{
			if (__args != null)
			{
				for (int i = 0; i < __args.Length; i++)
				{
					TryClear(__args[i]);
				}
			}
		}

		private static void TryClear(object candidate)
		{
			if (candidate != null)
			{
				long uid;
				bool num = NetReflection.TryGetPeerUid(candidate, out uid) && uid != 0;
				bool flag = NetReflection.GetPeerRpc(candidate) != null;
				bool flag2 = ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(candidate);
				if (num || flag || flag2)
				{
					PeerLifecycle.ClearDisconnectedPeer(candidate);
				}
			}
		}
	}
	internal sealed class PeerQualityState
	{
		public object Rpc;

		public long Uid;

		public readonly Queue<float> Samples = new Queue<float>();

		public float LastPingMs;

		public float PingEmaMs;

		public float PingMeanMs;

		public float PingStdDevMs;

		public float PingJitterMs;

		public float ConnectionQualityMs;

		public double LastUpdateTime;

		public int OwnedDynamicEstimate;

		public bool HasAnySample;
	}
	internal static class PeerQualityMeter
	{
		private static readonly Dictionary<object, PeerQualityState> ByRpc = new Dictionary<object, PeerQualityState>();

		private static readonly Dictionary<long, PeerQualityState> ByUid = new Dictionary<long, PeerQualityState>();

		private static readonly Dictionary<Type, MethodInfo> SocketQualityMethods = new Dictionary<Type, MethodInfo>();

		private static readonly object Lock = new object();

		private static FieldInfo _zrpcPingField;

		internal static void Initialize()
		{
			lock (Lock)
			{
				ByRpc.Clear();
				ByUid.Clear();
				SocketQualityMethods.Clear();
			}
			if (ReflectionCache.ZRpcType != null)
			{
				_zrpcPingField = ReflectionCache.SilentField(ReflectionCache.ZRpcType, "m_ping");
			}
		}

		internal static PeerQualityState GetOrCreateByRpc(object rpc, long uid = 0L)
		{
			if (rpc == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (!ByRpc.TryGetValue(rpc, out var value))
				{
					value = new PeerQualityState
					{
						Rpc = rpc,
						Uid = uid,
						PingEmaMs = 999f,
						ConnectionQualityMs = 999f
					};
					ByRpc[rpc] = value;
				}
				else if (value.Rpc == null)
				{
					value.Rpc = rpc;
				}
				if (uid != 0L)
				{
					value.Uid = uid;
					ByUid[uid] = value;
				}
				return value;
			}
		}

		internal static void ClearPeer(object peerOrRpc, long uid)
		{
			object obj = NetReflection.GetPeerRpc(peerOrRpc);
			if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc))
			{
				obj = peerOrRpc;
			}
			lock (Lock)
			{
				PeerQualityState value = null;
				if (uid != 0L)
				{
					ByUid.TryGetValue(uid, out value);
				}
				if (value == null && obj != null)
				{
					ByRpc.TryGetValue(obj, out value);
				}
				if (uid != 0L)
				{
					ByUid.Remove(uid);
				}
				if (obj != null)
				{
					ByRpc.Remove(obj);
				}
				if (value != null)
				{
					if (value.Uid != 0L)
					{
						ByUid.Remove(value.Uid);
					}
					if (value.Rpc != null)
					{
						ByRpc.Remove(value.Rpc);
					}
					RemoveState(ByRpc, value);
					RemoveState(ByUid, value);
				}
			}
		}

		private static void RemoveState<TKey>(Dictionary<TKey, PeerQualityState> map, PeerQualityState state)
		{
			if (state == null || map.Count == 0)
			{
				return;
			}
			List<TKey> list = new List<TKey>();
			foreach (KeyValuePair<TKey, PeerQualityState> item in map)
			{
				if (item.Value == state)
				{
					list.Add(item.Key);
				}
			}
			foreach (TKey item2 in list)
			{
				map.Remove(item2);
			}
		}

		internal static PeerQualityState GetByUid(long uid)
		{
			lock (Lock)
			{
				ByUid.TryGetValue(uid, out var value);
				return value;
			}
		}

		internal static PeerQualityState GetByPeer(object zdoPeer)
		{
			object peerRpc = NetReflection.GetPeerRpc(zdoPeer);
			NetReflection.TryGetPeerUid(zdoPeer, out var uid);
			return GetOrCreateByRpc(peerRpc, uid);
		}

		internal static PeerQualityState UpdateFromPeer(object zdoPeer)
		{
			if (zdoPeer == null)
			{
				return null;
			}
			object peerRpc = NetReflection.GetPeerRpc(zdoPeer);
			NetReflection.TryGetPeerUid(zdoPeer, out var uid);
			return UpdateFromRpcCore(peerRpc, uid);
		}

		internal static float GetQualityForUid(long uid, float fallback = 999f)
		{
			PeerQualityState byUid = GetByUid(uid);
			if (byUid == null || !byUid.HasAnySample)
			{
				return fallback;
			}
			return byUid.ConnectionQualityMs;
		}

		internal static void UpdateFromRpc(object rpc)
		{
			UpdateFromRpcCore(rpc, 0L);
		}

		private static PeerQualityState UpdateFromRpcCore(object rpc, long uid)
		{
			if (!EffectiveConfig.PeerQualityEnabled || rpc == null)
			{
				return null;
			}
			float num = TryReadPingMs(rpc);
			if (num <= 0f || float.IsNaN(num) || float.IsInfinity(num))
			{
				return GetOrCreateByRpc(rpc, uid);
			}
			if (uid == 0L)
			{
				NetReflection.TryGetUidFromPeerObject(rpc, out uid);
			}
			PeerQualityState orCreateByRpc = GetOrCreateByRpc(rpc, uid);
			if (orCreateByRpc == null)
			{
				return null;
			}
			double num2 = Time.realtimeSinceStartup;
			lock (Lock)
			{
				double num3 = ((orCreateByRpc.LastUpdateTime > 0.0) ? Math.Max(0.001, num2 - orCreateByRpc.LastUpdateTime) : 0.05);
				orCreateByRpc.LastUpdateTime = num2;
				float lastPingMs = orCreateByRpc.LastPingMs;
				orCreateByRpc.LastPingMs = num;
				double num4 = (double)Math.Max(0.1f, EffectiveConfig.PeerPingEmaHalfLifeSeconds) / Math.Log(2.0);
				float num5 = (float)(1.0 - Math.Exp((0.0 - num3) / num4));
				orCreateByRpc.PingEmaMs = (orCreateByRpc.HasAnySample ? (num5 * num + (1f - num5) * orCreateByRpc.PingEmaMs) : num);
				orCreateByRpc.HasAnySample = true;
				orCreateByRpc.Samples.Enqueue(num);
				while (orCreateByRpc.Samples.Count > Math.Max(4, EffectiveConfig.PeerPingSampleWindow))
				{
					orCreateByRpc.Samples.Dequeue();
				}
				RecalculateWindowStats(orCreateByRpc, lastPingMs);
				orCreateByRpc.ConnectionQualityMs = orCreateByRpc.PingMeanMs * EffectiveConfig.PeerQualityMeanWeight + orCreateByRpc.PingStdDevMs * EffectiveConfig.PeerQualityStdDevWeight + orCreateByRpc.PingJitterMs * EffectiveConfig.PeerQualityJitterWeight + orCreateByRpc.PingEmaMs * EffectiveConfig.PeerQualityEmaWeight;
				return orCreateByRpc;
			}
		}

		private static float TryReadPingMs(object rpc)
		{
			if (TryReadSocketPingMs(NetReflection.GetSocketFromRpc(rpc), out var pingMs))
			{
				return pingMs;
			}
			try
			{
				object obj = _zrpcPingField?.GetValue(rpc);
				if (obj is float num)
				{
					return (num < 10f) ? (num * 1000f) : num;
				}
				if (obj is double)
				{
					double num2 = (double)obj;
					return (num2 < 10.0) ? ((float)(num2 * 1000.0)) : ((float)num2);
				}
			}
			catch
			{
			}
			return -1f;
		}

		private static bool TryReadSocketPingMs(object socket, out float pingMs)
		{
			pingMs = 0f;
			if (socket == null)
			{
				return false;
			}
			try
			{
				MethodInfo socketQualityMethod = GetSocketQualityMethod(socket.GetType());
				if (socketQualityMethod == null)
				{
					return false;
				}
				object[] array = new object[5] { 0f, 0f, 0, 0f, 0f };
				socketQualityMethod.Invoke(socket, array);
				float num2 = ((array[0] is float num) ? num : 0f);
				float num4 = ((array[1] is float num3) ? num3 : 0f);
				int num6 = ((array[2] is int num5) ? num5 : Convert.ToInt32(array[2]));
				if (num6 > 0)
				{
					pingMs = num6;
					return true;
				}
				if (num2 > 0f || num4 > 0f)
				{
					pingMs = 1f;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static MethodInfo GetSocketQualityMethod(Type socketType)
		{
			if (socketType == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (SocketQualityMethods.TryGetValue(socketType, out var value))
				{
					return value;
				}
				MethodInfo methodInfo = null;
				MethodInfo[] methods = socketType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					if (!(methodInfo2.Name != "GetConnectionQuality") && methodInfo2.GetParameters().Length == 5)
					{
						methodInfo = methodInfo2;
						break;
					}
				}
				SocketQualityMethods[socketType] = methodInfo;
				return methodInfo;
			}
		}

		private static void RecalculateWindowStats(PeerQualityState state, float previousLast)
		{
			int count = state.Samples.Count;
			if (count == 0)
			{
				return;
			}
			float num = 0f;
			foreach (float sample in state.Samples)
			{
				num += sample;
			}
			float num2 = num / (float)count;
			float num3 = 0f;
			foreach (float sample2 in state.Samples)
			{
				float num4 = sample2 - num2;
				num3 += num4 * num4;
			}
			state.PingMeanMs = num2;
			state.PingStdDevMs = (float)Math.Sqrt(num3 / (float)Math.Max(1, count));
			state.PingJitterMs = ((previousLast > 0f) ? Math.Abs(state.LastPingMs - previousLast) : 0f);
		}
	}
	[HarmonyPatch]
	internal static class ZRpcReceivePingPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(ReflectionCache.ZRpcType ?? AccessTools.TypeByName("ZRpc"), "ReceivePing", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			PeerQualityMeter.UpdateFromRpc(__instance);
		}
	}
	internal enum RpcAoiKind
	{
		Visual,
		StateCritical,
		Unknown
	}
	internal static class RpcAoiRouter
	{
		private static readonly Dictionary<int, RpcAoiKind> Known = new Dictionary<int, RpcAoiKind>();

		private static int _damageTextHash;

		private static int _talkerSayHash;

		private static bool _initialized;

		internal static void Initialize()
		{
			Known.Clear();
			_damageTextHash = StableHash("DamageText");
			_talkerSayHash = StableHash("TalkerSay");
			Add(_damageTextHash, RpcAoiKind.Visual);
			Add(_talkerSayHash, RpcAoiKind.Visual);
			Add("HealthChanged", RpcAoiKind.StateCritical);
			Add("WNTHealthChanged", RpcAoiKind.StateCritical);
			Add("SetTarget", RpcAoiKind.StateCritical);
			Add("TriggerOnDeath", RpcAoiKind.StateCritical);
			Add("SpawnedZone", RpcAoiKind.StateCritical);
			_initialized = true;
		}

		private static void Add(string name, RpcAoiKind kind)
		{
			Add(StableHash(name), kind);
		}

		private static void Add(int methodHash, RpcAoiKind kind)
		{
			Known[methodHash] = kind;
		}

		internal static bool TryRoute(object routedRpc, object package)
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			if (!EffectiveConfig.RpcAoiEnabled || !NetReflection.IsServer())
			{
				return false;
			}
			if (routedRpc == null || package == null || RpcReflection.RoutedRPCDataType == null)
			{
				return false;
			}
			if (!_initialized)
			{
				Initialize();
			}
			try
			{
				int pos = ZPackageTools.GetPos(package);
				ZPackageTools.SetPos(package, 0);
				object routedData = RpcReflection.CreateRoutedRpcData();
				bool num = RpcReflection.DeserializeRoutedRpcData(routedData, package);
				ZPackageTools.SetPos(package, pos);
				if (!num)
				{
					return false;
				}
				int methodHash = RpcReflection.GetMethodHash(routedData);
				RpcAoiKind value;
				RpcAoiKind rpcAoiKind = (Known.TryGetValue(methodHash, out value) ? value : RpcAoiKind.Unknown);
				switch (rpcAoiKind)
				{
				case RpcAoiKind.Unknown:
					return false;
				case RpcAoiKind.StateCritical:
					return false;
				default:
				{
					if (!IsKindEnabled(rpcAoiKind, methodHash))
					{
						return false;
					}
					if (RpcReflection.GetTargetPeerId(routedData) != 0L)
					{
						return false;
					}
					float num2 = RadiusFor(rpcAoiKind);
					if (num2 <= 0f)
					{
						return false;
					}
					if (!TryResolveZdoOrigin(routedData, out var origin))
					{
						return false;
					}
					List<long> list = new List<long>();
					int num3 = 0;
					foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
					{
						if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L)
						{
							continue;
						}
						num3++;
						Vector3 val = NetReflection.GetPeerRefPos(item) - origin;
						if (!(((Vector3)(ref val)).sqrMagnitude > num2 * num2))
						{
							if (!FeatureNegotiation.IsRpcAoiActiveForUid(uid))
							{
								return false;
							}
							list.Add(uid);
						}
					}
					if (list.Count <= 0)
					{
						return false;
					}
					if (list.Count >= num3)
					{
						return false;
					}
					int num4 = 0;
					foreach (long item2 in list)
					{
						RpcReflection.SetTargetPeerId(routedData, item2);
						if (RpcReflection.RouteRpc(routedRpc, routedData))
						{
							num4++;
						}
					}
					if (num4 <= 0)
					{
						return false;
					}
					if (num4 < list.Count && ModConfig.DebugLogging.Value)
					{
						Plugin.Log.LogDebug((object)$"RPC AoI routed partial hash={methodHash} kind={rpcAoiKind} recipients={num4}/{list.Count} radius={num2}; vanilla fallback is suppressed to avoid duplicates for already-routed visual RPCs.");
					}
					if (ModConfig.DebugLogging.Value)
					{
						Plugin.Log