Decompiled source of KadettNPSCompat v1.0.1

BepInEx/plugins/KadettNPSCompat.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using NetworkPerformanceSystem.Patches;
using NetworkPerformanceSystem.Runtime;
using Splatform;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("KadettNPSCompat")]
[assembly: AssemblyDescription("Unofficial ServerManager-compatible NetworkPerformanceSystem fork for Valheim.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("KadettNPSCompat contributors")]
[assembly: AssemblyProduct("KadettNPSCompat")]
[assembly: AssemblyCopyright("Copyright © 2026 KadettNPSCompat contributors and MidnightsFX")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
namespace NetworkPerformanceSystem
{
	internal static class ConfigFileWatcher
	{
		private class WatchEntry
		{
			internal DateTime LastWriteUTC;

			internal long FileLength;

			internal Action<string> Callback;
		}

		internal class ConfigFileWatcherBehaviour : MonoBehaviour
		{
			private float nextPollTime;

			public void Update()
			{
				if (!(Time.unscaledTime < nextPollTime))
				{
					nextPollTime = Time.unscaledTime + PollInterval();
					Poll();
				}
			}

			private static float PollInterval()
			{
				if (ValConfig.ConfigPollIntervalSeconds == null)
				{
					return 30f;
				}
				return ValConfig.ConfigPollIntervalSeconds.Value;
			}

			private static void Poll()
			{
				if (WatchedFiles.Count == 0)
				{
					return;
				}
				string[] array = WatchedFiles.Keys.ToArray();
				foreach (string text in array)
				{
					if (!WatchedFiles.TryGetValue(text, out var value) || !File.Exists(text))
					{
						continue;
					}
					FileInfo fileInfo = new FileInfo(text);
					DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
					long length = fileInfo.Length;
					if (!(lastWriteTimeUtc == value.LastWriteUTC) || length != value.FileLength)
					{
						value.LastWriteUTC = lastWriteTimeUtc;
						value.FileLength = length;
						try
						{
							value.Callback?.Invoke(text);
						}
						catch (Exception ex)
						{
							Logger.LogWarning("ConfigFileWatcher callback for " + text + " threw: " + ex.Message);
						}
					}
				}
			}
		}

		private const float FallbackPollSeconds = 30f;

		private static readonly Dictionary<string, WatchEntry> WatchedFiles = new Dictionary<string, WatchEntry>(StringComparer.OrdinalIgnoreCase);

		private static ConfigFileWatcherBehaviour watchProcess;

		internal static void Initialize()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)watchProcess != (Object)null))
			{
				GameObject val = new GameObject("KadettNPSCompat_ConfigFileWatcher");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				watchProcess = val.AddComponent<ConfigFileWatcherBehaviour>();
				Logger.LogDebug("ConfigFileWatcher initialized.");
			}
		}

		internal static void Register(string fullPath, Action<string> onChanged)
		{
			if (!string.IsNullOrEmpty(fullPath))
			{
				DateTime lastWriteUTC = DateTime.MinValue;
				long fileLength = 0L;
				if (File.Exists(fullPath))
				{
					FileInfo fileInfo = new FileInfo(fullPath);
					lastWriteUTC = fileInfo.LastWriteTimeUtc;
					fileLength = fileInfo.Length;
				}
				WatchedFiles[fullPath] = new WatchEntry
				{
					LastWriteUTC = lastWriteUTC,
					FileLength = fileLength,
					Callback = onChanged
				};
				Logger.LogDebug("ConfigFileWatcher watching " + fullPath);
			}
		}

		internal static void RefreshStamp(string fullPath)
		{
			if (string.IsNullOrEmpty(fullPath) || !WatchedFiles.TryGetValue(fullPath, out var value))
			{
				return;
			}
			try
			{
				FileInfo fileInfo = new FileInfo(fullPath);
				value.LastWriteUTC = fileInfo.LastWriteTimeUtc;
				value.FileLength = fileInfo.Length;
			}
			catch (Exception)
			{
				value.LastWriteUTC = DateTime.MinValue;
				value.FileLength = 0L;
			}
		}
	}
	internal class Logger
	{
		public static LogLevel Level = (LogLevel)16;

		public static void EnableDebugLogging(object sender, EventArgs e)
		{
			CheckEnableDebugLogging();
		}

		public static void CheckEnableDebugLogging()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (ValConfig.EnableDebugMode.Value)
			{
				Level = (LogLevel)32;
			}
			else
			{
				Level = (LogLevel)16;
			}
		}

		public static void SetDebugLogging(bool state)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (state)
			{
				Level = (LogLevel)32;
			}
			else
			{
				Level = (LogLevel)16;
			}
		}

		public static void LogDebug(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)Level >= 32)
			{
				NetworkPerformanceSystem.Log.LogInfo((object)("[DEBUG]" + message));
			}
		}

		public static void LogInfo(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)Level >= 16)
			{
				NetworkPerformanceSystem.Log.LogInfo((object)message);
			}
		}

		public static void LogWarning(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)Level >= 4)
			{
				NetworkPerformanceSystem.Log.LogWarning((object)message);
			}
		}

		public static void LogError(string message)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)Level >= 2)
			{
				NetworkPerformanceSystem.Log.LogError((object)message);
			}
		}
	}
	internal class ValConfig
	{
		public static ConfigFile cfg;

		public static ConfigEntry<bool> EnableDebugMode;

		public static ConfigEntry<bool> EnableLatencyCompensation;

		public static ConfigEntry<float> LatencyCompensationStrength;

		public static ConfigEntry<float> LatencyCompensationMaxMeters;

		public static ConfigEntry<bool> EnableDebugOverlay;

		public static ConfigEntry<bool> EnableGhostWatchdog;

		public static ConfigEntry<bool> AllowMonitoringUpload;

		public static ConfigEntry<bool> EnableEarlyZdoDataGuard;

		public static ConfigEntry<float> ConfigPollIntervalSeconds;

		public static ConfigEntry<bool> EnableSendWindowSizing;

		public static ConfigEntry<float> SendWindowBdpFactor;

		public static ConfigEntry<int> SendWindowMaxBytes;

		public static ConfigEntry<bool> EnableSchedulerFix;

		public static ConfigEntry<float> SendIntervalSeconds;

		public static ConfigEntry<float> SendSchedulerFrameBudgetMs;

		public static ConfigEntry<int> SendSchedulerMinPeersPercent;

		public static ConfigEntry<bool> EnableOwnershipArbitration;

		public static ConfigEntry<bool> OwnershipAllowHostOwner;

		public static ConfigEntry<float> OwnershipMinHoldSeconds;

		public static ConfigEntry<int> OwnershipChallengeMarginMs;

		public static ConfigEntry<int> OwnershipMaxReassignsPerPass;

		public static ConfigEntry<float> OwnershipLoadPenaltyMs;

		public static ConfigEntry<int> OwnershipUnmeasuredRttMs;

		public static ConfigEntry<bool> EnableInteractiveOwnership;

		public static ConfigEntry<float> OwnershipInteractiveClaimRadius;

		public static ConfigEntry<float> OwnershipInteractiveChallengeMargin;

		public static ConfigEntry<float> OwnershipInteractiveMinHoldSeconds;

		public static ConfigEntry<int> OwnershipInteractiveMaxReassignsPerPass;

		public static ConfigEntry<bool> EnableCreatureProximityOwnership;

		public static ConfigEntry<float> CreatureProximityRadius;

		public static ConfigEntry<bool> OwnershipArbitrateCreatures;

		public static ConfigEntry<bool> OwnershipFollowersStayWithLeader;

		public static ConfigEntry<bool> OwnershipKeepWhileLoaded;

		public static ConfigEntry<bool> RejectStaleOwnerUpdates;

		public static ConfigEntry<bool> ShipOwnershipFollowsHelmsman;

		public static ConfigEntry<bool> UseCharacterRefPos;

		public static ConfigEntry<bool> EnableRoutedRpcFilter;

		public static ConfigEntry<bool> LimitTargetedRelayByDistance;

		public static ConfigEntry<bool> EnableStationRpcRouting;

		public static ConfigEntry<bool> EnableCreatureHitRouting;

		public static ConfigEntry<bool> EnableStructureHitRouting;

		public static ConfigEntry<bool> LimitRepeatedStatusEffects;

		public static ConfigEntry<bool> EnableSyncListCache;

		public static ConfigEntry<float> SyncListCacheMs;

		public static ConfigEntry<bool> QuietIdleCreatures;

		public static ConfigEntry<bool> PaceCreatureSends;

		public static ConfigEntry<bool> SendFightingCreaturesFirst;

		public static ConfigEntry<bool> QuietWildlifeUpdates;

		public static ConfigEntry<bool> HoldUnchangedStructures;

		public static ConfigEntry<float> StructureMaxHoldSeconds;

		public static ConfigEntry<bool> EnableSteamTransportTuning;

		public static ConfigEntry<int> SteamSendRateKBps;

		public static ConfigEntry<int> SteamNagleMicros;

		public static ConfigEntry<bool> EnableLossBackoff;

		public static ConfigEntry<float> LossBackoffThreshold;

		public static ConfigEntry<int> LossBackoffFloorKBps;

		public static ConfigEntry<int> LossBackoffHoldSeconds;

		public static ConfigEntry<int> LossBackoffRecoverSeconds;

		public static ConfigEntry<bool> EnablePlayerLimitOverride;

		public static ConfigEntry<int> MaxPlayers;

		public static ConfigEntry<bool> SendPlayerHistoryOnlyWhenChanged;

		public static ConfigEntry<bool> EnableConnectionTimeoutTuning;

		public static ConfigEntry<int> ConnectTimeoutSeconds;

		public static ConfigEntry<int> ConnectionTimeoutSeconds;

		public static ConfigEntry<int> LoadingTimeoutSeconds;

		public static ConfigEntry<bool> EvictGhostOwners;

		public static ConfigEntry<float> GhostOwnerEvictSeconds;

		public static ConfigEntry<bool> ReportVanillaQueueSize;

		public static ConfigEntry<bool> EnableDeserializeFastPath;

		public static ConfigEntry<bool> EnablePacketReadFastPath;

		public static ConfigEntry<bool> EnableSendPackageReuse;

		public static ConfigEntry<bool> EnableRpcInvokeFastPath;

		public static ConfigEntry<bool> EnableRelaySendReuse;

		public static ConfigEntry<bool> EnableMonitoring;

		public static ConfigEntry<bool> MonitoringCollectFromClients;

		public static ConfigEntry<int> MonitoringClientBytesPerSecond;

		public static ConfigEntry<int> MonitoringMaxDiskMB;

		public const string cfgFolder = "KadettNPSCompat";

		private static List<string> reloadChanges;

		public ValConfig(ConfigFile cf)
		{
			cfg = cf;
			cfg.SaveOnConfigSet = false;
			CreateConfigValues(cf);
			Logger.SetDebugLogging(EnableDebugMode.Value);
		}

		public static void SaveOnSet(bool enabled)
		{
			cfg.SaveOnConfigSet = enabled;
			cfg.Save();
		}

		public static void WatchConfigFile()
		{
			ConfigFileWatcher.Register(cfg.ConfigFilePath, OnConfigFileChanged);
			ConfigFileWatcher.Initialize();
		}

		private static void OnConfigFileChanged(string _)
		{
			if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer())
			{
				ReloadFromDisk();
			}
		}

		internal static void ReloadFromDisk()
		{
			bool saveOnConfigSet = cfg.SaveOnConfigSet;
			cfg.SaveOnConfigSet = false;
			List<string> list = (reloadChanges = new List<string>());
			try
			{
				cfg.Reload();
			}
			finally
			{
				reloadChanges = null;
				cfg.SaveOnConfigSet = saveOnConfigSet;
			}
			if (list.Count == 0)
			{
				Logger.LogInfo("Config file changed on disk and was reloaded; no setting changed.");
			}
			else
			{
				Logger.LogInfo(string.Format("Config file changed on disk and was reloaded; {0} setting(s) changed: {1}", list.Count, string.Join(", ", list)));
			}
		}

		private void CreateConfigValues(ConfigFile Config)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Expected O, but got Unknown
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Expected O, but got Unknown
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Expected O, but got Unknown
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Expected O, but got Unknown
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Expected O, but got Unknown
			EnableDebugMode = Config.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			EnableDebugMode.SettingChanged += Logger.EnableDebugLogging;
			Logger.CheckEnableDebugLogging();
			EnableLatencyCompensation = Config.Bind<bool>("Client config", "EnableLatencyCompensation", true, new ConfigDescription("Render entities owned by other players at their estimated current position rather than their last-received one. Requires the server to be running this mod.", (AcceptableValueBase)null, Array.Empty<object>()));
			LatencyCompensationStrength = Config.Bind<float>("Client config", "LatencyCompensationStrength", 1f, new ConfigDescription("How much of the measured path latency to correct for. 1.0 corrects fully. Lower values trade accuracy for less overshooting. 0 Disables it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			LatencyCompensationMaxMeters = Config.Bind<float>("Client config", "LatencyCompensationMaxMeters", 3f, new ConfigDescription("How far objects can be displaced through latency compensation.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 4.5f), Array.Empty<object>()));
			EnableDebugOverlay = Config.Bind<bool>("Client config", "EnableDebugOverlay", false, new ConfigDescription("Show the per-entity latency compensation overlay (owner, estimated staleness, applied displacement).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			EnableGhostWatchdog = Config.Bind<bool>("Client config", "EnableGhostWatchdog", true, new ConfigDescription("Warn when the server stops answering, and return to the menu once it is certain. Otherwise you can continue to play on a disconnected world and any changes made would be lost.", (AcceptableValueBase)null, Array.Empty<object>()));
			AllowMonitoringUpload = Config.Bind<bool>("Client config", "AllowMonitoringUpload", true, new ConfigDescription("When the server turns on monitoring, this allows your client to upload data. As the client, this is your hard-opt out, server can't override it.", (AcceptableValueBase)null, Array.Empty<object>()));
			EnableEarlyZdoDataGuard = Config.Bind<bool>("Client config", "EnableEarlyZdoDataGuard", true, new ConfigDescription("Buffers updates that land before their associated receiving RCPs, prevents data loss on connection.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			ConfigPollIntervalSeconds = Config.Bind<float>("Config File", "Poll Interval Seconds", 30f, new ConfigDescription("Seconds between checks for edits to this file on disk. An edit is applied without a restart on a server, or in the main menu; a server then sends its new values to the connected players. Lower reacts faster to a hand edit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 300f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			EnableSendWindowSizing = BindServerConfig("Send Window", "Enable BDP Window Sizing", value: true, "Size each peer's in-flight ZDO window from their measured round-trip time and the rate Steam sends to them at. Low-latency peers are unaffected; high-latency peers stop being throttled by their distance.");
			SendWindowBdpFactor = BindServerConfig("Send Window", "BDP Factor", 1.25f, "Multiplier on the round-trip part of the window. 1.0 allows exactly one round trip of data in flight at Steam's send rate, on top of the one send interval of data every window carries so the link stays busy until the next send.", advanced: false, 1f, 3f);
			SendWindowMaxBytes = BindServerConfig("Send Window", "Max Window Bytes", 65536, "Upper bound on the send window. A player needs send rate x (round-trip time x BDP Factor + Send Interval Seconds) to receive at the full rate: at the default 150 KB/s this cap covers about 300ms of ping. Raise it together with Steam Transport's Send Rate KBps.", advanced: true, 10240, 262144);
			EnableSchedulerFix = BindServerConfig("Send Scheduler", "Enable Scheduler Fix", value: true, "Send to every peer each tick instead of one peer per rendered frame. Without this the effective per-peer send rate degrades linearly with player count.");
			SendIntervalSeconds = BindServerConfig("Send Scheduler", "Send Interval Seconds", 0.033f, "Seconds between ZDO send rounds. Vanilla is 0.05, lower = more updates, higher = slower updates.", advanced: true, 0.02f, 0.2f);
			SendSchedulerFrameBudgetMs = BindServerConfig("Send Scheduler", "Frame Budget Ms", 4f, "Maximum milliseconds per frame the host spends sending ZDOs to peers, once Min Players Per Frame Percent of them have been sent to. Peers are serviced in round-robin order until the budget runs out and the remainder is owed to the next frame, so nobody is starved. On a busy server this is what keeps the frame time bounded: the effective per-peer send rate becomes min(1/interval, budget/cost) - raise it to trade server frame time for send rate, lower it on a CPU-constrained host. nps_stats shows the effective rate and how often the budget is hit.", advanced: true, 0.5f, 16f);
			SendSchedulerMinPeersPercent = BindServerConfig("Send Scheduler", "Min Players Per Frame Percent", 25, "The frame budget never stops a frame's sends before this share of the connected players has been sent to (rounded up, at least one). On a big server each send costs more, and the budget alone can cut a frame down to one or two players, leaving each player waiting dozens of server frames for an update. With this, every player is sent to at least once every few server frames however many have joined: 25 is at least once every 4 frames. It only adds sends while the budget is what is limiting, and those sends cost server frame time. 0 leaves the budget alone in charge. nps_stats shows how many sends it adds and what each one costs.", advanced: true, 0, 100);
			EnableOwnershipArbitration = BindServerConfig("Ownership", "Enable Latency-Aware Ownership", value: true, "Assign ZDO ownership to minimise how stale the object looks to the players who can actually see it, instead of vanilla's first-peer-wins ordering. This is what makes it 'feel like singleplayer'");
			OwnershipAllowHostOwner = BindServerConfig("Ownership", "Allow Host As Owner", value: true, "Let the host compete for ownership of contested ZDOs in the zones it has loaded. (This is ONLY around spawn by default).");
			OwnershipMinHoldSeconds = BindServerConfig("Ownership", "Min Hold Seconds", 2f, "Minimum time an owner keeps a ZDO before it can be challenged. This prevents constant ZDO change thrash.", advanced: false, 0f, 60f);
			OwnershipChallengeMarginMs = BindServerConfig("Ownership", "Challenge Margin Ms", 25, "A challenger must improve estimated staleness by at least this many milliseconds to take ownership.", advanced: false, 0, 250);
			OwnershipMaxReassignsPerPass = BindServerConfig("Ownership", "Max Reassigns Per Pass", 8, "Cap on latency-driven ownership transfers of moving objects per arbitration pass (boats, creatures, carts etc). Each transfer costs a ZDO resend.", advanced: true, 1, 128);
			OwnershipLoadPenaltyMs = BindServerConfig("Ownership", "Load Penalty Ms", 0.02f, "Cost added per simulated object (creatures, ships - not walls or trees) a candidate already owns nearby, in milliseconds. This prevents one user from simulating a whole area even if they have the best connection. Low priority objects will be shuffled to others.", advanced: true, 0f, 0.5f);
			OwnershipUnmeasuredRttMs = BindServerConfig("Ownership", "Unmeasured Peer RTT Ms", 150, "Round-trip time assumed for a peer the host has no measurement for - crossplay/PlayFab connections never report one, and every Steam peer is unmeasured for its first seconds. Such a peer still wins objects only it can see, but loses contested ones to any measured peer with a lower ping. Treating unmeasured as 0ms instead would hand them everything in range.", advanced: true, 0, 1000);
			EnableInteractiveOwnership = BindServerConfig("Ownership", "Interactive Object Ownership", value: true, "Also place interactable-but-stationary objects - berry bushes and other pickables, ore deposits, rocks, trees, logs and destructibles - on whoever is standing nearest them. This allows most interactables to respond to the user instantly instead of waiting on a delayed RPC.");
			OwnershipInteractiveClaimRadius = BindServerConfig("Ownership", "Interactive Claim Radius", 32f, "How close a player has to be, in metres, before an interactable object is placed on them.", advanced: false, 4f, 64f);
			OwnershipInteractiveChallengeMargin = BindServerConfig("Ownership", "Interactive Challenge Margin", 4f, "How much nearer, in metres, a player must be than the current owner before an interactable object moves to them. This is the distance equivalent of Challenge Margin Ms.", advanced: false, 0f, 32f);
			OwnershipInteractiveMinHoldSeconds = BindServerConfig("Ownership", "Interactive Min Hold Seconds", 15f, "Minimum time before an interactable object that has changed ownership can change again. Nothing in the game hands these objects over by itself, so there is no player-initiated claim to wait out", advanced: false, 0f, 120f);
			OwnershipInteractiveMaxReassignsPerPass = BindServerConfig("Ownership", "Interactive Max Reassigns Per Pass", 16, "Minimum cap on how many interactable objects may be placed on a nearer player per arbitration pass. It is a separate budget from Max Reassigns Per Pass on purpose, so a zone full of contested creatures cannot starve the handful of moves that make a berry patch local.", advanced: true, 1, 256);
			EnableCreatureProximityOwnership = BindServerConfig("Ownership", "Creature Proximity Ownership", value: true, "When exactly one player is near a creature, that player owns it, whatever anyone else's latency is. Prevents lag when two players are within network distance but not fighting the same creatures.");
			CreatureProximityRadius = BindServerConfig("Ownership", "Creature Proximity Radius", 48f, "How close, in metres, a player has to be to a creature to count as near it for Creature Proximity Ownership. Two players both inside this distance of one creature are sharing a fight; two players further apart than about twice this are not.", advanced: false, 16f, 96f);
			OwnershipArbitrateCreatures = BindServerConfig("Ownership", "Arbitrate Creatures", value: true, "Give creatures first call on ownership arbitration. Game defaults to creatures having low priority for network updates.");
			OwnershipFollowersStayWithLeader = BindServerConfig("Ownership", "Followers Stay With Their Player", value: true, "A tame that is following a player, or a creature a player summoned with a staff, is simulated on that player's machine, so it keeps up with them and fights beside them without lag. A ridden animal still goes to its rider. Needs Arbitrate Creatures.");
			OwnershipKeepWhileLoaded = BindServerConfig("Ownership", "Keep Objects While Loaded", value: true, "Leave an object with its owner while that player still has it loaded, instead of releasing it the moment they step one zone away and claiming it again when they step back. A player standing on a zone edge otherwise flips every object in the next zone over each time they move - over a thousand at a time in a built-up base. Creatures already work this way.");
			RejectStaleOwnerUpdates = BindServerConfig("Ownership", "Reject Stale Owner Updates", value: true, "Prevents previous owners network updates immediately taking an owned object back, and tells that player straight away who owns it now. A creature's movement from its previous owner is dropped rather than shown to everyone else. Disabling this effectively neuters the arbiter.");
			ShipOwnershipFollowsHelmsman = BindServerConfig("Ownership", "Ship Ownership Follows Helmsman", value: true, "Hand a ship to whoever takes its helm, so the player steering simulates it on their own machine instead of watching it relayed through the host from another player.");
			UseCharacterRefPos = BindServerConfig("Reference Position", "Use Character Position", value: true, "Host-side. Take each player's position from their character, as it arrives with the game's own object updates, instead of waiting for the position their game reports every 2 seconds.");
			EnableRoutedRpcFilter = BindServerConfig("Routed RPC", "Enable Relay Filtering", value: true, "Relay broadcast RPCs (animation triggers, footsteps, damage numbers, object-destroyed notices, building damage and the like) only to the players that can actually use them, instead of to everyone on the server.");
			LimitTargetedRelayByDistance = BindServerConfig("Routed RPC", "Limit Relay By Distance", value: true, "Also stop relaying object RPCs (building damage and fragments, ward flashes, animation triggers, footsteps) to players who are too far away to have that object loaded, even if they visited it earlier in the session.");
			EnableStationRpcRouting = BindServerConfig("Routed RPC", "Route Station Requests To Owner", value: true, "Deliver fermenter, smelter, cooking station, fireplace, shield generator and ballista item requests (add item / ore / fuel / ammo, tap, empty) to whoever owns the object right now. Prevents RPCs being dropped and items being eaten. An ore, fuel, food or ammo item that still cannot be delivered is dropped at the station instead of being lost.");
			EnableCreatureHitRouting = BindServerConfig("Routed RPC", "Route Creature Hits To Owner", value: true, "Deliver hits on creatures to whoever is simulating the creature right now, prevents silently dropping hits.");
			EnableStructureHitRouting = BindServerConfig("Routed RPC", "Route Structure Hits To Owner", value: true, "Deliver hits, repairs, removals and snow changes on buildings, trees and rocks to whoever owns them right now. A player whose copy still names an earlier owner sends them to that player, who drops them without a word. Structure Updates only holds back owner changes while this is on.");
			LimitRepeatedStatusEffects = BindServerConfig("Routed RPC", "Limit Repeated Status Effects", value: true, "Send a status effect to a creature another player is simulating at most 4 times a second, instead of every time something asks for it. An area that gives a status effect keeps applying it, 50 times a second, to creatures that were yours when they walked in. Once one of them is handed to another player every one of those becomes a message, and a few animals in a base can fill a player's whole upload and stop the world loading for the player they were handed to. Runs on each player's game that has this mod, using the server's setting; the server also stops passing the repeats on from players who do not have it. The log names the effect either way.");
			EnableSyncListCache = BindServerConfig("Sync List Cache", "Enable Sector Scan Cache", value: true, "Reuse each peer's sector scan across the send sweep instead of rebuilding it on every send.");
			SyncListCacheMs = BindServerConfig("Sync List Cache", "Cache Ms", 100f, "How long a peer's sector scan may be reused, in milliseconds. This is the longest delay an object moving sectors from one player to another would have before being considered for ownership change etc. Vanilla rebuilds this every frame.", advanced: false, 0f, 500f);
			QuietIdleCreatures = BindServerConfig("Creature Updates", "Quiet Idle Creatures", value: true, "A creature standing still stops sending itself for movements too small to see - under 2 cm, 0.05 m/s, or 1 degree of tilt or turn - and for animation speed changes under 0.05. Without this, the physics engine's constant tiny wobble makes whoever simulates a creature re-send all of it up to 30 times a second, and the server passes each copy on to everyone nearby; a pen of tamed animals can fill a player's upload. Fighting creatures are not affected. Runs on each player's game that has this mod, using the server's setting.");
			PaceCreatureSends = BindServerConfig("Creature Updates", "Pace Creature Sends", value: true, "Server side. Send a creature that has settled down to each player at most 10 times a second (5 beyond 32 m), and one moving more than 64 m away at most 15 times a second, instead of on every update. Creatures that are fighting, hunting, being ridden or changing owner still go out at once. Also covers players who do not have this mod.");
			SendFightingCreaturesFirst = BindServerConfig("Creature Updates", "Send Fighting Creatures First", value: true, "When a player's upload is full, send the creatures they are simulating that are alert or chasing someone ahead of everything else they have changed, right after players and ships. Without this they wait their turn behind every fire, smelter and dropped item that player also simulates, and freeze for everyone else for a second or more. Changes only the order, never what is sent. Runs on each player's game that has this mod, using the server's setting.");
			QuietWildlifeUpdates = BindServerConfig("Creature Updates", "Quiet Wildlife", value: true, "Fish, seagulls and crows send themselves 5 times a second (fish) or 10 times a second (birds) instead of on every frame. They are always moving, so without this whoever simulates them re-sends each one up to 20 times a second, and on a lake or coast they can be a third of a player's upload. The server also passes them on to each player at most about 6 / 12 times a second (2 / 5 beyond 32 m), which covers players who do not have this mod. A fish on a fishing line is not affected. Runs on each player's game that has this mod and on the server, using the server's setting.");
			HoldUnchangedStructures = BindServerConfig("Structure Updates", "Hold Unchanged Structures", value: true, "Server side. Do not re-send a building piece, tree or rock to a player who already has it when all that changed is who owns it, the snow on it, or a value rewritten to what it already was. It goes out at once when its health or support changes, and to a player within 16 m of it; anything else waits until that player's connection has room, up to Max Hold Seconds. The game sends every building update ahead of every creature update, so while players walk through a ruin or a big base the creatures around them otherwise wait behind thousands of walls. Doors, chests, stations, fires, portals, ships and carts are never held. Also covers players who do not have this mod.");
			StructureMaxHoldSeconds = BindServerConfig("Structure Updates", "Max Hold Seconds", 30f, "How long a held building, tree or rock update waits before it goes out as soon as there is room, in seconds. It never waits more than twice this.", advanced: false, 1f, 120f);
			EnableSteamTransportTuning = BindServerConfig("Steam Transport", "Enable Transport Tuning", value: true, "Let this mod write Steam's global networking config (send rate and Nagle) and log a before/after readback of what the transport is actually doing. Send Rate KBps ships at the game's value; Nagle Micros ships at 0 rather than the game's 5000, so turning this on by itself only removes Nagle's hold-back delay. Requires the Steam backend; on crossplay-only processes it stands down quietly.");
			SteamSendRateKBps = BindServerConfig("Steam Transport", "Send Rate KBps", 0, "The rate Steam sends at on every connection from the host, in kilobytes/sec. 0 leaves the game's 150. Steam has no congestion control here: this is a fixed pace, not a ceiling it adapts under, and it never slows down for a player whose connection cannot keep up - that player gets packet loss and resends instead (nps_stats flags it as LOSSY). Budget the host's upload for players x this value: 10 players at 500 is 40 Mbit/s worst case. Written to both of Steam's rate bounds, on the host only - players' own uploads stay at the game's rate. Values from 1 to 31 are raised to 32. Applies immediately, no restart needed.", advanced: false, 0, 4096);
			SteamNagleMicros = BindServerConfig("Steam Transport", "Nagle Micros", 0, "Microseconds Steam may hold a small reliable message back to coalesce it with the next one. Vanilla and Steam both default to 5000 (5ms), which is up to 5ms added in each direction on every update for a saving that mattered on a modem. 0 sends immediately. This mod already batches at the ZDO layer, so there is very little left for Nagle to coalesce - which is why 0 is the default here rather than vanilla's 5000.", advanced: false, 0, 100000);
			EnableLossBackoff = BindServerConfig("Steam Transport", "Enable Loss Backoff", value: true, "Slow the server down for one player whose connection is losing what it is sent, without touching anyone else's rate. A player still losing as much at Loss Backoff Floor KBps goes back to full speed and is not slowed down again until the server restarts.");
			LossBackoffThreshold = BindServerConfig("Steam Transport", "Loss Backoff Threshold", 0.95f, "The share of the server's packets that must reach a player, as a fraction. Below it for Loss Backoff Hold Seconds, that player's connection is stepped down. This is also the line nps_stats marks as LOSSY.", advanced: false, 0.5f, 0.999f);
			LossBackoffFloorKBps = BindServerConfig("Steam Transport", "Loss Backoff Floor KBps", 32, "Never step a player's rate below this, in kilobytes/sec. 32 is the same floor Send Rate KBps has. A floor at or above Send Rate KBps leaves nothing to step down to, which switches the back-off off in effect.", advanced: false, 32, 4096);
			LossBackoffHoldSeconds = BindServerConfig("Steam Transport", "Loss Backoff Hold Seconds", 10, "How long a player must stay under the threshold before each step down, in seconds. Measured from the last step, so a connection is never lowered faster than this.", advanced: true, 3, 120);
			LossBackoffRecoverSeconds = BindServerConfig("Steam Transport", "Loss Backoff Recover Seconds", 60, "How long a player must stay clean - two points above the threshold - before each step back up, in seconds. Measured from the last step, so a connection is never raised faster than this.", advanced: true, 10, 600);
			EnablePlayerLimitOverride = BindServerConfig("Player Limit", "Enable Player Limit Override", value: false, "Disabled in KadettNPSCompat. ServerManager owns player admission and the player limit.");
			MaxPlayers = BindServerConfig("Player Limit", "Max Players", 60, "Unused in KadettNPSCompat; configure the player limit in ServerManager.yml instead.", advanced: false, 1, 255);
			SendPlayerHistoryOnlyWhenChanged = BindServerConfig("Player List", "Send Player History Only When Changed", value: true, "Server side. Send the list of everyone who has ever joined this world to a player only when it differs from the copy they already have. The game sends the whole list - about 60 bytes for every player the world has seen - to every player whenever it marks it changed, and on some servers it marks it changed on every 2-second update without anything changing: 20 KB to every player every 2 seconds on a world that has seen 340 players, over a third of the server's upload. Players who join still get it, and a real change still goes to everyone. Also covers players who do not have this mod. The log says what keeps marking it changed either way.");
			EnableConnectionTimeoutTuning = BindServerConfig("Connection Timeout", "Enable Timeout Tuning", value: true, "Let this mod set how long a connection may go quiet before either end hangs up, instead of the game's fixed 30 seconds.");
			ConnectTimeoutSeconds = BindServerConfig("Connection Timeout", "Connect Timeout Seconds", 10, "How long a connection attempt may take before Steam abandons it, in seconds. 10 is Steam's own default, which the game never changes. This covers only the handshake, before the connection exists.", advanced: false, 5, 600);
			ConnectionTimeoutSeconds = BindServerConfig("Connection Timeout", "Connection Timeout Seconds", 30, "How long a player who is already in the world may go without a packet before they are dropped, in seconds. 30 is vanilla. Keep it short: a player whose game or connection has died is sent back to the menu and their slot freed this much sooner, instead of being left in a frozen world. Players still joining get 'Loading Timeout Seconds' instead. On a crossplay server the game never drops anyone in under 90.", advanced: false, 10, 600);
			LoadingTimeoutSeconds = BindServerConfig("Connection Timeout", "Loading Timeout Seconds", 90, "How long a player who is still joining - connecting, downloading and loading the world, until their character appears - may go without a packet before they are dropped, in seconds. Loading a large modded world can freeze a player's game for a minute or more, so this is kept apart from 'Connection Timeout Seconds'. Never shorter than that setting.", advanced: true, 30, 900);
			EvictGhostOwners = BindServerConfig("Connection Timeout", "Evict Ghost Owners", value: true, "Stop giving objects to a player who has stopped answering. A peer that goes quiet keeps its slot for the full 'Connection Timeout Seconds' so it can come back, but the things it was simulating - creatures especially - are handed to players who are actually there.");
			GhostOwnerEvictSeconds = BindServerConfig("Connection Timeout", "Ghost Owner Evict Seconds", 10f, "How long a player may be silent before their objects are given to someone else, in seconds. The game pings every peer once a second, so ten seconds is ten missed replies.", advanced: true, 3f, 60f);
			ReportVanillaQueueSize = BindServerConfig("Compatibility", "Report Vanilla Queue Size", value: true, "Some mods - ServerSync and every mod that bundles it, ConditionalConfigSync, ServerCharacters, Jotunn - wait for a player's send queue to fall under a fixed 10000-20000 bytes before sending, and disconnect that player after 30 seconds if it never does. This adjusts that to handled changes made by this mod.");
			EnableDeserializeFastPath = BindServerConfig("Allocation", "Enable ZDO Deserialize Fast Path", value: true, "Read a received ZDO's fields directly instead of through the fourteen delegates the game allocates for every single one.");
			EnablePacketReadFastPath = BindServerConfig("Allocation", "Enable Packet Read Fast Path", value: true, "Read each incoming ZDO's payload straight into the buffer that is about to hold it instead of copying twice.");
			EnableSendPackageReuse = BindServerConfig("Allocation", "Enable Send Package Reuse", value: true, "Reuse the two packet buffers the send path builds, instead of constructing and discarding both on every send to every peer.");
			EnableRpcInvokeFastPath = BindServerConfig("Allocation", "Enable RPC Invoke Fast Path", value: true, "Call an incoming RPC's handler directly when its signature is the common one, instead of going through reflection for every message.");
			EnableRelaySendReuse = BindServerConfig("Allocation", "Enable Relay Send Reuse", value: true, "Write each relayed RPC (footsteps, hits, damage numbers, chat and the rest) once and hand the same bytes to every player it goes to, instead of rebuilding and re-copying the whole message for each one. The bytes sent are identical. Host-side only.");
			EnableMonitoring = BindServerConfig("Monitoring", "Enable Network Monitoring", value: false, "Record what the network is doing to BepInEx/KadettNPSCompatMonitoring, for sending with a bug or optimization report.");
			MonitoringCollectFromClients = BindServerConfig("Monitoring", "Collect From Clients", value: true, "While monitoring is on, also ask every player's game for its network related stats");
			MonitoringClientBytesPerSecond = BindServerConfig("Monitoring", "Client Upload Bytes Per Second", 2048, "The most each client may send the server in monitoring records, averaged over time. Records beyond it are dropped on the client and counted. Sent in 4KB batches that are held back whenever the client's link to the server is already near its send window, so they never delay the game's own traffic.", advanced: true, 256, 4096);
			MonitoringMaxDiskMB = BindServerConfig("Monitoring", "Max Disk MB", 4096, "The most BepInEx/KadettNPSCompatMonitoring may hold, across every session in it. Recording stops when it is reached and says so in the log. Nothing already recorded is ever deleted to make room. Files are compressed as they are closed.", advanced: false, 64, 65536);
			Config.SettingChanged += OnAnySettingChanged;
			EnableSteamTransportTuning.SettingChanged += OnSteamTransportSettingChanged;
			SteamSendRateKBps.SettingChanged += OnSteamTransportSettingChanged;
			SteamNagleMicros.SettingChanged += OnSteamTransportSettingChanged;
			EnableLossBackoff.SettingChanged += OnLossBackoffSettingChanged;
			LossBackoffThreshold.SettingChanged += OnLossBackoffSettingChanged;
			LossBackoffFloorKBps.SettingChanged += OnLossBackoffSettingChanged;
			LossBackoffHoldSeconds.SettingChanged += OnLossBackoffSettingChanged;
			LossBackoffRecoverSeconds.SettingChanged += OnLossBackoffSettingChanged;
			EnableConnectionTimeoutTuning.SettingChanged += OnConnectionTimeoutSettingChanged;
			ConnectTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged;
			ConnectionTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged;
			LoadingTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged;
			EnableSendWindowSizing.SettingChanged += OnJotunnQueueSettingChanged;
			SendWindowMaxBytes.SettingChanged += OnJotunnQueueSettingChanged;
		}

		private static void OnSteamTransportSettingChanged(object sender, EventArgs e)
		{
			SteamTransport.OnConfigChanged();
		}

		private static void OnLossBackoffSettingChanged(object sender, EventArgs e)
		{
			LossBackoff.OnConfigChanged();
		}

		private static void OnJotunnQueueSettingChanged(object sender, EventArgs e)
		{
			JotunnSendQueue.OnConfigChanged();
		}

		private static void OnConnectionTimeoutSettingChanged(object sender, EventArgs e)
		{
			ConnectionTimeout.OnConfigChanged();
		}

		private static void OnAnySettingChanged(object sender, SettingChangedEventArgs e)
		{
			reloadChanges?.Add($"[{e.ChangedSetting.Definition.Section}] {e.ChangedSetting.Definition.Key} = {e.ChangedSetting.BoxedValue}");
			Monitoring.OnSettingChanged(e.ChangedSetting);
		}

		public static ConfigEntry<float[]> BindServerConfig(string category, string key, float[] value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<float[]>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<bool> BindServerConfig(string category, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			return cfg.Bind<bool>(category, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<int> BindServerConfig(string category, string key, int value, string description, bool advanced = false, int valMin = 0, int valMax = 150)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<int>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<float> BindServerConfig(string category, string key, float value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<float>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<string> BindServerConfig(string category, string key, string value, string description, AcceptableValueList<string> acceptableValues = null, bool advanced = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			return cfg.Bind<string>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}
	}
	[BepInPlugin("Kadet_ttTeam.KadettNPSCompat", "KadettNPSCompat", "1.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("com.Fire.FiresGhettoNetworkMod")]
	[BepInIncompatibility("VitByr.VBNetTweaks")]
	[BepInIncompatibility("sighsorry.SkadiNet")]
	[BepInIncompatibility("CW_Jesse.BetterNetworking")]
	[BepInIncompatibility("org.bepinex.plugins.network")]
	[BepInIncompatibility("Searica.Valheim.NetworkTweaks")]
	[BepInIncompatibility("dzk.warheimnetwork")]
	[BepInIncompatibility("com.maxsch.valheim.TimeoutLimit")]
	internal class NetworkPerformanceSystem : BaseUnityPlugin
	{
		public const string PluginGUID = "Kadet_ttTeam.KadettNPSCompat";

		public const string PluginName = "KadettNPSCompat";

		public const string PluginVersion = "1.0.1";

		internal static ManualLogSource Log;

		internal static Harmony HarmonyInstance;

		internal ValConfig cfg;

		public void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			cfg = new ValConfig(((BaseUnityPlugin)this).Config);
			HarmonyInstance = new Harmony("Kadet_ttTeam.KadettNPSCompat");
			HarmonyInstance.PatchAll(typeof(NetworkPerformanceSystem).Assembly);
			PlayerLimitPatches.ApplyPlayFabCapacityPatch(HarmonyInstance);
			JotunnSendQueue.OnStartup();
			ValConfig.SaveOnSet(enabled: true);
			ValConfig.WatchConfigFile();
		}

		public void Start()
		{
			SendSchedulerPatches.CheckForReturnToSender();
			RoutedRpcPatches.CheckForOverlappingMods();
			RpcOwnerRouterPatches.CheckForOverlappingMods();
			GhostWatchdog.CheckForRival();
			PatchGuard.VerifyAfterPatching();
		}

		public void OnGUI()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if (ValConfig.EnableDebugOverlay != null && ValConfig.EnableDebugOverlay.Value && NpsEnv.NetReady() && !NpsEnv.IsDedicated())
			{
				GUI.Label(new Rect(10f, 10f, 1400f, 24f), NetworkStats.BuildOverlay());
			}
		}

		public void OnDestroy()
		{
			Monitoring.Shutdown();
			JotunnSendQueue.Restore();
			Harmony harmonyInstance = HarmonyInstance;
			if (harmonyInstance != null)
			{
				harmonyInstance.UnpatchSelf();
			}
		}
	}
}
namespace NetworkPerformanceSystem.Runtime
{
	internal static class AllocationRelief
	{
		internal static long DeserializeFast;

		internal static long PacketReadFast;

		internal static long SendPackagesReused;

		internal static long RpcFastPath;

		internal static long RelaySendsReused;

		internal static bool DeserializeHookInstalled;

		internal static bool PacketReadHookInstalled;

		internal static bool RpcInvokeHookInstalled;

		internal static bool DeserializeWanted
		{
			get
			{
				if (ValConfig.EnableDeserializeFastPath != null)
				{
					return ValConfig.EnableDeserializeFastPath.Value;
				}
				return false;
			}
		}

		internal static bool PacketReadWanted
		{
			get
			{
				if (ValConfig.EnablePacketReadFastPath != null)
				{
					return ValConfig.EnablePacketReadFastPath.Value;
				}
				return false;
			}
		}

		internal static bool SendPackageReuseWanted
		{
			get
			{
				if (ValConfig.EnableSendPackageReuse != null)
				{
					return ValConfig.EnableSendPackageReuse.Value;
				}
				return false;
			}
		}

		internal static bool RpcInvokeWanted
		{
			get
			{
				if (ValConfig.EnableRpcInvokeFastPath != null)
				{
					return ValConfig.EnableRpcInvokeFastPath.Value;
				}
				return false;
			}
		}

		internal static bool RelaySendWanted
		{
			get
			{
				if (ValConfig.EnableRelaySendReuse != null)
				{
					return ValConfig.EnableRelaySendReuse.Value;
				}
				return false;
			}
		}

		internal static bool DeserializeActive
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.DeserializeAlloc))
				{
					return DeserializeWanted;
				}
				return false;
			}
		}

		internal static bool PacketReadActive
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.PacketReadAlloc))
				{
					return PacketReadWanted;
				}
				return false;
			}
		}

		internal static bool SendPackageReuseActive
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.SendPacketReuse))
				{
					return SendPackageReuseWanted;
				}
				return false;
			}
		}

		internal static bool RpcInvokeActive
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.RpcInvokeFastPath))
				{
					return RpcInvokeWanted;
				}
				return false;
			}
		}

		internal static bool RelaySendActive
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.RelaySendReuse))
				{
					return RelaySendWanted;
				}
				return false;
			}
		}

		internal static bool TryGetZdoRates(out int sentPerSecond, out int recvPerSecond)
		{
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				sentPerSecond = 0;
				recvPerSecond = 0;
				return false;
			}
			sentPerSecond = instance.GetSentZDOs();
			recvPerSecond = instance.GetRecvZDOs();
			return true;
		}
	}
	internal static class ConnectionTimeout
	{
		internal const float VanillaRpcTimeoutSeconds = 30f;

		internal const float VanillaRpcLongTimeoutSeconds = 90f;

		internal const int VanillaSteamConnectedMillis = 30000;

		internal const int VanillaSteamInitialMillis = 10000;

		private const int MillisPerSecond = 1000;

		private static bool _longMode;

		private static bool _rpcSeamSeen;

		private static bool _steamUp;

		private static bool _perPeer;

		private static ConditionalWeakTable<ZRpc, object> _inWorld = new ConditionalWeakTable<ZRpc, object>();

		private static readonly object Present = new object();

		private static string _lastLoggedRpc;

		internal static float InWorldTimeoutSeconds { get; private set; } = 30f;

		internal static float JoiningTimeoutSeconds { get; private set; } = 30f;

		internal static string LastSteamReadback { get; private set; }

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.ConnectionTimeout) && ValConfig.EnableConnectionTimeoutTuning != null && ValConfig.ConnectionTimeoutSeconds != null && ValConfig.ConnectTimeoutSeconds != null && ValConfig.LoadingTimeoutSeconds != null)
				{
					return ValConfig.EnableConnectionTimeoutTuning.Value;
				}
				return false;
			}
		}

		internal static float DeadlineFor(ZRpc rpc)
		{
			if (rpc == null || !_inWorld.TryGetValue(rpc, out var _))
			{
				return JoiningTimeoutSeconds;
			}
			return InWorldTimeoutSeconds;
		}

		internal static void BeforeRpcUpdate(ZRpc rpc)
		{
			if (_perPeer)
			{
				ZRpc.m_timeout = DeadlineFor(rpc);
			}
		}

		internal static void NoteInWorld(ZRpc rpc)
		{
			if (rpc != null && !_inWorld.TryGetValue(rpc, out var _))
			{
				_inWorld.Add(rpc, Present);
			}
		}

		internal static void OnVanillaRpcTimeoutSet(bool longMode)
		{
			_longMode = longMode;
			_rpcSeamSeen = true;
			ApplyRpc(longMode ? "SetLongTimeout(true)" : "SetLongTimeout(false)");
		}

		internal static void OnGlobalCallbacksRegistered()
		{
			_steamUp = true;
			ApplySteam("RegisterGlobalCallbacks");
		}

		internal static void OnConfigChanged()
		{
			if (_rpcSeamSeen)
			{
				ApplyRpc("config changed");
			}
			if (_steamUp)
			{
				ApplySteam("config changed");
			}
		}

		internal static void Reset()
		{
			_longMode = false;
			_lastLoggedRpc = null;
			_inWorld = new ConditionalWeakTable<ZRpc, object>();
		}

		private static void ApplyRpc(string reason)
		{
			float num = (_longMode ? 90f : 30f);
			_perPeer = Active;
			if (_perPeer)
			{
				InWorldTimeoutSeconds = Math.Max(ValConfig.ConnectionTimeoutSeconds.Value, _longMode ? num : 0f);
				JoiningTimeoutSeconds = Math.Max(ValConfig.LoadingTimeoutSeconds.Value, InWorldTimeoutSeconds);
			}
			else
			{
				InWorldTimeoutSeconds = num;
				JoiningTimeoutSeconds = num;
			}
			float timeout = ZRpc.m_timeout;
			ZRpc.m_timeout = JoiningTimeoutSeconds;
			string text = (_perPeer ? $"ZRpc ping timeout {timeout}s -> {JoiningTimeoutSeconds}s while joining, {InWorldTimeoutSeconds}s once in the world" : $"ZRpc ping timeout {timeout}s -> {ZRpc.m_timeout}s (vanilla)");
			if (_longMode)
			{
				text += " [crossplay]";
			}
			if (!(text == _lastLoggedRpc))
			{
				_lastLoggedRpc = text;
				Logger.LogInfo("Connection timeout (" + reason + "): " + text);
			}
		}

		private static void ApplySteam(string reason)
		{
			if (SteamNetConfig.Available)
			{
				int value = (Active ? (ValConfig.ConnectTimeoutSeconds.Value * 1000) : 10000);
				int value2 = (Active ? (Math.Max(ValConfig.LoadingTimeoutSeconds.Value, ValConfig.ConnectionTimeoutSeconds.Value) * 1000) : 30000);
				int num = ReadOr((ESteamNetworkingConfigValue)24, 10000);
				int num2 = ReadOr((ESteamNetworkingConfigValue)25, 30000);
				SteamNetConfig.TryWrite((ESteamNetworkingConfigValue)24, value);
				SteamNetConfig.TryWrite((ESteamNetworkingConfigValue)25, value2);
				int millis = ReadOr((ESteamNetworkingConfigValue)24, num);
				int millis2 = ReadOr((ESteamNetworkingConfigValue)25, num2);
				string text = "TimeoutInitial " + Sec(num) + " -> " + Sec(millis) + ", TimeoutConnected " + Sec(num2) + " -> " + Sec(millis2);
				if (!(text == LastSteamReadback))
				{
					LastSteamReadback = text;
					Logger.LogInfo("Connection timeout (" + SteamNetConfig.InterfaceName + ", " + reason + "): " + text);
				}
			}
		}

		private static int ReadOr(ESteamNetworkingConfigValue key, int fallback)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!SteamNetConfig.TryRead(key, out var value))
			{
				return fallback;
			}
			return value;
		}

		private static string Sec(int millis)
		{
			return $"{(float)millis / 1000f}s";
		}
	}
	internal static class CreaturePacing
	{
		internal enum Hold : byte
		{
			Send,
			IdleNear,
			IdleFar,
			MovingFar
		}

		private sealed class PeerCounts
		{
			internal long Deferred;

			internal long Listed;
		}

		internal const float NearMetres = 32f;

		internal const float FarMetres = 64f;

		internal const float MovingSpeed = 0.25f;

		internal const float SettleSeconds = 0.5f;

		internal const float IdleNearInterval = 0.1f;

		internal const float IdleFarInterval = 0.2f;

		internal const float MovingFarInterval = 1f / 15f;

		private const float NearSq = 1024f;

		private const float FarSq = 4096f;

		private const float MovingSpeedSq = 0.0625f;

		private const float PruneIntervalSeconds = 10f;

		private static readonly Dictionary<ZDOID, float> MovingUntil = new Dictionary<ZDOID, float>();

		private static readonly List<ZDOID> Expired = new List<ZDOID>();

		private static float _lastPrune;

		private static readonly Dictionary<long, PeerCounts> ByPeer = new Dictionary<long, PeerCounts>();

		internal static long DeferredIdleNear;

		internal static long DeferredIdleFar;

		internal static long DeferredMovingFar;

		internal static long Listed;

		internal static long Deferred => DeferredIdleNear + DeferredIdleFar + DeferredMovingFar;

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.CreaturePacing) && ValConfig.PaceCreatureSends != null)
				{
					return ValConfig.PaceCreatureSends.Value;
				}
				return false;
			}
		}

		internal static int MovingAt(float now)
		{
			int num = 0;
			foreach (KeyValuePair<ZDOID, float> item in MovingUntil)
			{
				if (now < item.Value)
				{
					num++;
				}
			}
			return num;
		}

		internal static void Filter(ZDOPeer peer, List<ZDO> toSync, Vector3 refPos, float now, bool creatures, bool wildlife, bool structures, bool ownerMayWait)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			if (peer == null || toSync == null || toSync.Count == 0)
			{
				return;
			}
			PruneIfDue(now);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < toSync.Count; i++)
			{
				ZDO val = toSync[i];
				if (val != null)
				{
					if (creatures && OwnershipPolicy.IsCreature(val))
					{
						num2++;
						Hold hold = Decide(peer, val, refPos, now, OwnershipPolicy.IsDirectlyControlled(val));
						if (hold != Hold.Send)
						{
							num3++;
							switch (hold)
							{
							case Hold.IdleNear:
								DeferredIdleNear++;
								break;
							case Hold.IdleFar:
								DeferredIdleFar++;
								break;
							default:
								DeferredMovingFar++;
								break;
							}
							continue;
						}
					}
					else
					{
						QuietWildlife.Kind kind = (wildlife ? QuietWildlife.KindOf(val) : QuietWildlife.Kind.None);
						if (kind != QuietWildlife.Kind.None)
						{
							QuietWildlife.RelayListed++;
							if (HoldWildlife(peer, val, refPos, now, kind, out var near))
							{
								if (near)
								{
									QuietWildlife.RelayHeldNear++;
								}
								else
								{
									QuietWildlife.RelayHeldFar++;
								}
								continue;
							}
						}
						else if (structures && StructureUpdates.IsHeldStructure(val) && !StructureUpdates.ShouldSendNow(peer, val, refPos, now, ownerMayWait))
						{
							continue;
						}
					}
				}
				toSync[num++] = val;
			}
			if (num < toSync.Count)
			{
				toSync.RemoveRange(num, toSync.Count - num);
			}
			Listed += num2;
			if (num2 > 0 && peer.m_peer != null)
			{
				if (!ByPeer.TryGetValue(peer.m_peer.m_uid, out var value))
				{
					value = new PeerCounts();
					ByPeer[peer.m_peer.m_uid] = value;
				}
				value.Listed += num2;
				value.Deferred += num3;
			}
		}

		internal static Hold Decide(ZDOPeer peer, ZDO zdo, Vector3 refPos, float now, bool controlled)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			if (!peer.m_zdos.TryGetValue(zdo.m_uid, out var value))
			{
				return Hold.Send;
			}
			if (zdo.OwnerRevision > value.m_ownerRevision)
			{
				return Hold.Send;
			}
			if (peer.m_forceSend.Contains(zdo.m_uid))
			{
				return Hold.Send;
			}
			if (peer.m_peer != null && zdo.GetOwner() == peer.m_peer.m_uid)
			{
				return Hold.Send;
			}
			if (controlled)
			{
				return Hold.Send;
			}
			if (zdo.GetBool(ZDOVars.s_alert, false) || zdo.GetBool(ZDOVars.s_haveTargetHash, false))
			{
				return Hold.Send;
			}
			Vector3 vec = zdo.GetVec3(ZDOVars.s_velHash, Vector3.zero);
			bool flag = IsMoving(zdo.m_uid, vec.x * vec.x + vec.y * vec.y + vec.z * vec.z, now);
			Vector3 position = zdo.GetPosition();
			float num = position.x - refPos.x;
			float num2 = position.y - refPos.y;
			float num3 = position.z - refPos.z;
			float num4 = num * num + num2 * num2 + num3 * num3;
			float num5 = MinIntervalSeconds(num4, flag);
			if (num5 <= 0f || now - value.m_syncTime >= num5)
			{
				return Hold.Send;
			}
			if (flag)
			{
				return Hold.MovingFar;
			}
			if (!(num4 < 1024f))
			{
				return Hold.IdleFar;
			}
			return Hold.IdleNear;
		}

		internal static bool HoldWildlife(ZDOPeer peer, ZDO zdo, Vector3 refPos, float now, QuietWildlife.Kind kind, out bool near)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			near = false;
			if (!peer.m_zdos.TryGetValue(zdo.m_uid, out var value))
			{
				return false;
			}
			if (zdo.OwnerRevision > value.m_ownerRevision)
			{
				return false;
			}
			if (peer.m_forceSend.Contains(zdo.m_uid))
			{
				return false;
			}
			if (peer.m_peer != null && zdo.GetOwner() == peer.m_peer.m_uid)
			{
				return false;
			}
			if (kind == QuietWildlife.Kind.Fish && QuietWildlife.IsHooked(zdo))
			{
				return false;
			}
			Vector3 position = zdo.GetPosition();
			float num = position.x - refPos.x;
			float num2 = position.y - refPos.y;
			float num3 = position.z - refPos.z;
			float num4 = num * num + num2 * num2 + num3 * num3;
			if (now - value.m_syncTime >= QuietWildlife.RelayIntervalSeconds(kind, num4))
			{
				return false;
			}
			near = num4 < 1024f;
			return true;
		}

		internal static float MinIntervalSeconds(float distanceSq, bool moving)
		{
			if (moving)
			{
				if (!(distanceSq < 4096f))
				{
					return 1f / 15f;
				}
				return 0f;
			}
			if (!(distanceSq < 1024f))
			{
				return 0.2f;
			}
			return 0.1f;
		}

		internal static bool IsMoving(ZDOID id, float speedSq, float now)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (speedSq >= 0.0625f)
			{
				MovingUntil[id] = now + 0.5f;
				return true;
			}
			if (MovingUntil.TryGetValue(id, out var value))
			{
				return now < value;
			}
			return false;
		}

		private static void PruneIfDue(float now)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if (now - _lastPrune < 10f)
			{
				return;
			}
			_lastPrune = now;
			if (MovingUntil.Count == 0)
			{
				return;
			}
			Expired.Clear();
			foreach (KeyValuePair<ZDOID, float> item in MovingUntil)
			{
				if (item.Value <= now)
				{
					Expired.Add(item.Key);
				}
			}
			for (int i = 0; i < Expired.Count; i++)
			{
				MovingUntil.Remove(Expired[i]);
			}
		}

		internal static bool TryGetPeerCounts(long uid, out long deferred, out long listed)
		{
			if (ByPeer.TryGetValue(uid, out var value))
			{
				deferred = value.Deferred;
				listed = value.Listed;
				return true;
			}
			deferred = 0L;
			listed = 0L;
			return false;
		}

		internal static void ForgetPeer(long uid)
		{
			ByPeer.Remove(uid);
		}

		internal static void Reset()
		{
			MovingUntil.Clear();
			Expired.Clear();
			ByPeer.Clear();
			_lastPrune = 0f;
			DeferredIdleNear = 0L;
			DeferredIdleFar = 0L;
			DeferredMovingFar = 0L;
			Listed = 0L;
		}
	}
	internal static class FightingCreaturesFirst
	{
		private static readonly List<ZDO> Fighting = new List<ZDO>();

		private static readonly List<ZDO> Rest = new List<ZDO>();

		private static readonly Predicate<ZDO> FightingTest = IsFighting;

		internal static long ListsReordered;

		internal static long CreaturesMoved;

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.FightingCreaturesFirst) && ValConfig.SendFightingCreaturesFirst != null)
				{
					return ValConfig.SendFightingCreaturesFirst.Value;
				}
				return false;
			}
		}

		internal static void Reorder(ZDOPeer peer, List<ZDO> toSync)
		{
			Reorder(peer, toSync, FightingTest);
		}

		internal static void Reorder(ZDOPeer peer, List<ZDO> toSync, Predicate<ZDO> fighting)
		{
			if (peer == null || toSync == null || toSync.Count < 2)
			{
				return;
			}
			int i;
			for (i = 0; i < toSync.Count && IsHead(peer, toSync[i]); i++)
			{
			}
			int num = -1;
			int num2 = 0;
			for (int j = i; j < toSync.Count; j++)
			{
				if (fighting(toSync[j]))
				{
					if (num >= 0)
					{
						num2++;
					}
				}
				else if (num < 0)
				{
					num = j;
				}
			}
			if (num2 == 0)
			{
				return;
			}
			Fighting.Clear();
			Rest.Clear();
			for (int k = num; k < toSync.Count; k++)
			{
				ZDO val = toSync[k];
				if (fighting(val))
				{
					Fighting.Add(val);
				}
				else
				{
					Rest.Add(val);
				}
			}
			int num3 = num;
			for (int l = 0; l < Fighting.Count; l++)
			{
				toSync[num3++] = Fighting[l];
			}
			for (int m = 0; m < Rest.Count; m++)
			{
				toSync[num3++] = Rest[m];
			}
			Fighting.Clear();
			Rest.Clear();
			ListsReordered++;
			CreaturesMoved += num2;
		}

		private static bool IsHead(ZDOPeer peer, ZDO zdo)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (zdo != null && (int)zdo.Type != 1)
			{
				return peer.m_forceSend.Contains(zdo.m_uid);
			}
			return true;
		}

		internal static bool IsFighting(ZDO zdo)
		{
			if (zdo == null || !zdo.IsOwner())
			{
				return false;
			}
			if (!OwnershipPolicy.IsCreature(zdo))
			{
				return false;
			}
			if (!zdo.GetBool(ZDOVars.s_alert, false))
			{
				return zdo.GetBool(ZDOVars.s_haveTargetHash, false);
			}
			return true;
		}

		internal static void Reset()
		{
			Fighting.Clear();
			Rest.Clear();
			ListsReordered = 0L;
			CreaturesMoved = 0L;
		}
	}
	internal static class GhostWatchdog
	{
		private const float WarnFraction = 0.5f;

		private const float WarnRepeatSeconds = 1f;

		private static bool _warning;

		private static float _lastWarnAt;

		private static bool _tripped;

		internal static string PendingReason { get; private set; }

		internal static long TotalWarnings { get; private set; }

		internal static long TotalTrips { get; private set; }

		internal static bool Warning => _warning;

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.GhostWatchdog) && ValConfig.EnableGhostWatchdog != null)
				{
					return ValConfig.EnableGhostWatchdog.Value;
				}
				return false;
			}
		}

		internal static void ClearPendingReason()
		{
			PendingReason = null;
		}

		internal static void CheckForRival()
		{
			if (PatchGuard.IsPluginLoaded("dreamwraith.ClientGhostWatchdog"))
			{
				PatchGuard.Disable(Mechanism.GhostWatchdog, "ClientGhostWatchdog is installed and already watches this connection from the client side; its timeout is the one in force. Host-side ghost detection (PeerLiveness) is unaffected");
			}
		}

		internal static void Tick()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Invalid comparison between Unknown and I4
			if (!Active || _tripped || !NpsEnv.IsClient())
			{
				return;
			}
			Game instance = Game.instance;
			if ((Object)(object)instance == (Object)null || instance.IsShuttingDown() || (int)ZNet.GetConnectionStatus() != 2)
			{
				return;
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer == null || serverPeer.m_uid == 0L)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsTeleporting())
			{
				Recover(silent: true);
				return;
			}
			float num = ConnectionTimeout.DeadlineFor(serverPeer.m_rpc);
			if (!(num <= 0f))
			{
				float num2 = PeerLiveness.SilenceSeconds(serverPeer.m_uid);
				bool flag = PeerLiveness.LinkStateOf(serverPeer.m_uid) == RttProbe.LinkState.Dead;
				if (flag || num2 >= num)
				{
					Trip(flag, num2);
				}
				else if (num2 >= num * 0.5f)
				{
					Warn(num - num2);
				}
				else
				{
					Recover(silent: false);
				}
			}
		}

		private static void Warn(float secondsLeft)
		{
			if (!_warning || !(Time.realtimeSinceStartup - _lastWarnAt < 1f))
			{
				if (!_warning)
				{
					_warning = true;
					TotalWarnings++;
					Logger.LogWarning($"The server has stopped answering. Leaving in {secondsLeft:F0}s unless it comes back.");
				}
				_lastWarnAt = Time.realtimeSinceStartup;
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)2, $"Lost contact with the server - reconnecting ({Mathf.Max(0f, secondsLeft):F0}s)", 0, (Sprite)null, true, false);
				}
			}
		}

		private static void Recover(bool silent)
		{
			if (!_warning)
			{
				return;
			}
			_warning = false;
			if (!silent)
			{
				Logger.LogInfo("The server is answering again.");
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)2, "Contact with the server restored", 0, (Sprite)null, true, false);
				}
			}
		}

		private static void Trip(bool transportDead, float silence)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			_tripped = true;
			_warning = false;
			TotalTrips++;
			string text = (transportDead ? "the connection is dead as far as the transport is concerned" : $"nothing has arrived for {silence:F0}s");
			Logger.LogWarning("Leaving the session: " + text + ". Anything done since the server went quiet was never received by it.");
			PendingReason = "Lost connection to the server.\n\nYou were returned to the menu rather than left playing a world the server is no longer part of. Progress made after contact was lost did not reach it.";
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, "Lost connection to the server - returning to the menu", 0, (Sprite)null, true, false);
			}
			ZNet.m_connectionStatus = (ConnectionStatus)4;
		}

		internal static void Reset()
		{
			_warning = false;
			_tripped = false;
			_lastWarnAt = 0f;
		}
	}
	internal static class JotunnSendQueue
	{
		internal const int HeadroomBytes = 20000;

		private const string FieldName = "MaximumSendQueueSize";

		private static FieldInfo _field;

		private static int _original;

		private static bool _started;

		private static int _lastLogged = -1;

		internal static int EffectiveLimit { get; private set; }

		internal static int JotunnDefault => _original;

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.JotunnQueueLimit))
				{
					return _field != null;
				}
				return false;
			}
		}

		internal static void OnStartup()
		{
			try
			{
				Type typeFromHandle = typeof(CustomRPC);
				RuntimeHelpers.RunClassConstructor(typeFromHandle.TypeHandle);
				FieldInfo fieldInfo = AccessTools.Field(typeFromHandle, "MaximumSendQueueSize");
				if (fieldInfo == null || !fieldInfo.IsStatic || fieldInfo.FieldType != typeof(int))
				{
					StandDown(typeFromHandle, (fieldInfo == null) ? "is missing" : "is not a static int");
					return;
				}
				int num = (int)fieldInfo.GetValue(null);
				if (num <= 0)
				{
					StandDown(typeFromHandle, $"reads {num}, which is not a usable limit");
					return;
				}
				_field = fieldInfo;
				_original = num;
				EffectiveLimit = num;
				_started = true;
				Apply("startup");
			}
			catch (Exception ex)
			{
				_field = null;
				PatchGuard.Disable(Mechanism.JotunnQueueLimit, "could not reach Jotunn.Entities.CustomRPC.MaximumSendQueueSize: " + ex.GetType().Name + ": " + ex.Message + ". Jotunn's own 20000-byte send queue limit stays in force; Jotunn CustomRPC senders may time out against a BDP-sized window past ~100ms RTT.");
			}
		}

		private static void StandDown(Type customRpc, string what)
		{
			_field = null;
			PatchGuard.Disable(Mechanism.JotunnQueueLimit, "Jotunn.Entities.CustomRPC.MaximumSendQueueSize " + what + " in the installed Jotunn " + $"({customRpc.Assembly.GetName().Version}). Jotunn's own 20000-byte send queue limit stays in " + "force; Jotunn CustomRPC senders may time out against a BDP-sized window past ~100ms RTT.");
		}

		internal static void OnConfigChanged()
		{
			if (_started)
			{
				Apply("config changed");
			}
		}

		internal static void Restore()
		{
			if (_field == null)
			{
				return;
			}
			try
			{
				_field.SetValue(null, _original);
				EffectiveLimit = _original;
			}
			catch (Exception ex)
			{
				Logger.LogWarning("Could not restore Jotunn CustomRPC send queue limit: " + ex.Message);
			}
		}

		private static bool WindowSizingOn()
		{
			if (PatchGuard.IsActive(Mechanism.SendWindow) && ValConfig.EnableSendWindowSizing != null)
			{
				return ValConfig.EnableSendWindowSizing.Value;
			}
			return false;
		}

		private static int Desired()
		{
			if (!WindowSizingOn())
			{
				return _original;
			}
			int val = ((ValConfig.SendWindowMaxBytes != null) ? ValConfig.SendWindowMaxBytes.Value : 10240);
			int num = Math.Max(10240, val);
			return Math.Max(_original, num + 20000);
		}

		private static void Apply(string reason)
		{
			if (!Active)
			{
				return;
			}
			try
			{
				int num = (int)_field.GetValue(null);
				int num2 = Desired();
				_field.SetValue(null, num2);
				EffectiveLimit = (int)_field.GetValue(null);
				if (EffectiveLimit != _lastLogged)
				{
					_lastLogged = EffectiveLimit;
					string text = (WindowSizingOn() ? $"Max Window Bytes {ValConfig.SendWindowMaxBytes.Value} + {20000} headroom" : "send window sizing is off; Jotunn's own value");
					Logger.LogInfo($"Jotunn CustomRPC send queue limit ({reason}): {num} -> {EffectiveLimit} bytes ({text})");
				}
			}
			catch (Exception ex)
			{
				_field = null;
				PatchGuard.Disable(Mechanism.JotunnQueueLimit, "writing Jotunn.Entities.CustomRPC.MaximumSendQueueSize failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}
	}
	internal static class LatencyRegistry
	{
		internal sealed class PeerLatency
		{
			internal float EwmaMs;

			internal float JitterMs;

			internal int LastMs;

			internal float LastSampleRealtime;

			internal bool HasSample;
		}

		private const float SampleAlpha = 0.25f;

		private const float JitterDivisor = 16f;

		private const int MinPlausiblePingMs = 1;

		private const int MaxPlausiblePingMs = 2000;

		private static readonly Dictionary<long, PeerLatency> Measured = new Dictionary<long, PeerLatency>();

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

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

		private static bool _hasPublishedTable;

		private static float _publishedAtRealtime;

		private static bool _warnedBadTable;

		private const float PublishedTableTtlSeconds = 10f;

		private const int EntryBytes = 10;

		private const int MaxTableEntries = 4096;

		internal static IEnumerable<KeyValuePair<long, PeerLatency>> AllMeasured => Measured;

		internal static bool HasPublishedTable => _hasPublishedTable;

		internal static bool HasFreshTable
		{
			get
			{
				if (_hasPublishedTable)
				{
					return Time.realtimeSinceStartup - _publishedAtRealtime < 10f;
				}
				return false;
			}
		}

		internal static float PublishedTableAgeSeconds
		{
			get
			{
				if (!_hasPublishedTable)
				{
					return 0f;
				}
				return Time.realtimeSinceStartup - _publishedAtRealtime;
			}
		}

		internal static int PublishedEntryCount => Published.Count;

		internal static void Sample(long peerUid, int pingMs)
		{
			if (peerUid != 0L && pingMs >= 1 && pingMs <= 2000)
			{
				if (!Measured.TryGetValue(peerUid, out var value))
				{
					value = new PeerLatency();
					Measured[peerUid] = value;
				}
				if (!value.HasSample)
				{
					value.EwmaMs = pingMs;
					value.JitterMs = 0f;
					value.HasSample = true;
				}
				else
				{
					value.JitterMs += ((float)Mathf.Abs(pingMs - value.LastMs) - value.JitterMs) / 16f;
					value.EwmaMs += ((float)pingMs - value.EwmaMs) * 0.25f;
				}
				value.LastMs = pingMs;
				value.LastSampleRealtime = Time.realtimeSinceStartup;
			}
		}

		internal static float MeasuredRttMs(long peerUid)
		{
			if (!Measured.TryGetValue(peerUid, out var value) || !value.HasSample)
			{
				return 0f;
			}
			return value.EwmaMs;
		}

		internal static void NoteSteamSendRate(long peerUid, int bytesPerSec)
		{
			if (peerUid != 0L && bytesPerSec > 0)
			{
				SteamSendRate[peerUid] = bytesPerSec;
			}
		}

		internal static bool TryGetSteamSendRate(long peerUid, out int bytesPerSec)
		{
			return SteamSendRate.TryGetValue(peerUid, out bytesPerSec);
		}

		internal static float MeasuredJitterMs(long peerUid)
		{
			if (!Measured.TryGetValue(peerUid, out var value) || !value.HasSample)
			{
				return 0f;
			}
			return value.JitterMs;
		}

		internal static bool HasMeasurement(long peerUid)
		{
			if (Measured.TryGetValue(peerUid, out var value))
			{
				return value.HasSample;
			}
			return false;
		}

		internal static bool TryGetState(long peerUid, out PeerLatency state)
		{
			if (Measured.TryGetValue(peerUid, out state))
			{
				return state.HasSample;
			}
			return false;
		}

		internal static float RttMs(long peerUid)
		{
			if (NpsEnv.IsHost())
			{
				return MeasuredRttMs(peerUid);
			}
			if (!Published.TryGetValue(peerUid, out var value))
			{
				return 0f;
			}
			return value;
		}

		internal static float PathStalenessSeconds(long ownerUid)
		{
			if (ownerUid == 0L)
			{
				return 0f;
			}
			long num = NpsEnv.LocalSessionId();
			if (ownerUid == num)
			{
				return 0f;
			}
			if (NpsEnv.IsHost())
			{
				if (!HasMeasurement(ownerUid))
				{
					return 0f;
				}
				return MeasuredRttMs(ownerUid) * 0.5f / 1000f;
			}
			if (!HasFreshTable)
			{
				return 0f;
			}
			if (!Published.TryGetValue(ownerUid, out var value) || !Published.TryGetValue(num, out var value2))
			{
				return 0f;
			}
			return (float)(value + value2) * 0.5f / 1000f;
		}

		internal static ZPackage BuildTablePackage(ZNetPeer recipient)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0019: 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)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			List<ZNetPeer> peers = ZNet.instance.GetPeers();
			int radius = int.MaxValue;
			Vector2s recipientZone = default(Vector2s);
			if ((Object)(object)ZoneSystem.instance != (Object)null && recipient != null)
			{
				radius = 2 * ZoneCompat.NearFor(recipient) + 1;
				recipientZone = ZoneSystem.GetZone(recipient.GetRefPos());
			}
			int num = 1;
			for (int i = 0; i < peers.Count; i++)
			{
				if (Qualifies(peers[i], recipientZone, radius))
				{
					num++;
				}
			}
			val.Write(num);
			val.Write(NpsEnv.LocalSessionId());
			val.Write((ushort)0);
			for (int j = 0; j < peers.Count; j++)
			{
				ZNetPeer val2 = peers[j];
				if (Qualifies(val2, recipientZone, radius))
				{
					val.Write(val2.m_uid);
					val.Write((ushort)Mathf.Clamp(Mathf.RoundToInt(MeasuredRttMs(val2.m_uid)), 0, 65535));
				}
			}
			return val;
		}

		private static bool Qualifies(ZNetPeer peer, Vector2s recipientZone, int radius)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			long uid = peer.m_uid;
			if (uid == 0L || !HasMeasurement(uid))
			{
				return false;
			}
			if (radius == int.MaxValue)
			{
				return true;
			}
			return ZoneCompat.InActiveArea(ZoneSystem.GetZone(peer.GetRefPos()), recipientZone, radius);
		}

		internal static void ApplyTablePackage(ZPackage pkg)
		{
			if (pkg == null)
			{
				return;
			}
			int num = pkg.ReadInt();
			if (num < 0 || num > 4096 || pkg.Size() - pkg.GetPos() < num * 10)
			{
				if (!_warnedBadTable)
				{
					_warnedBadTable = true;
					Logger.LogWarning($"Ignoring malformed latency table ({num} entries, {pkg.Size() - pkg.GetPos()} bytes remaining). Further occurrences this session are not logged.");
				}
				return;
			}
			Published.Clear();
			for (int i = 0; i < num; i++)
			{
				long key = pkg.ReadLong();
				int value = pkg.ReadUShort();
				Published[key] = value;
			}
			_hasPublishedTable = true;
			_publishedAtRealtime = Time.realtimeSinceStartup;
		}

		internal static void ForgetPeer(long peerUid)
		{
			Measured.Remove(peerUid);
			SteamSendRate.Remove(peerUid);
		}

		internal static void Reset()
		{
			Measured.Clear();
			SteamSendRate.Clear();
			Published.Clear();
			_hasPublishedTable = false;
			_publishedAtRealtime = 0f;
			_warnedBadTable = false;
		}
	}
	internal static class LiveRefPos
	{
		internal enum Source : byte
		{
			Character,
			NoCharacter,
			PointsElsewhere
		}

		internal sealed class PeerState
		{
			internal Vector3 Reported;

			internal bool DivergedAtReport;

			internal ZDOID CharacterAtReport;

			internal Vector3 LastWritten;

			internal bool HasWritten;

			internal Source Current;

			internal double BehindSum;

			internal int BehindSamples;

			internal float BehindPeak;

			internal int ReportsElsewhere;

			internal float BehindMean
			{
				get
				{
					if (BehindSamples <= 0)
					{
						return 0f;
					}
					return (float)(BehindSum / (double)BehindSamples);
				}
			}
		}

		internal const float DivergenceLimitMeters = 64f;

		internal const float MaxCoordinate = 12000f;

		private static readonly Dictionary<long, PeerState> States = new Dictionary<long, PeerState>();

		private static bool _wasActive;

		internal static bool Active
		{
			get
			{
				if (PatchGuard.IsActive(Mechanism.LiveRefPos))
				{
					return ValConfig.UseCharacterRefPos.Value;
				}
				return false;
			}
		}

		internal static bool TryGetState(long peerUid, out PeerState state)
		{
			return States.TryGetValue(peerUid, out state);
		}

		internal static bool IsPlausible(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			if (float.IsNaN(p.x) || float.IsNaN(p.y) || float.IsNaN(p.z))
			{
				return false;
			}
			if (float.IsInfinity(p.x) || float.IsInfinity(p.y) || float.IsInfinity(p.z))
			{
				return false;
			}
			if (Mathf.Abs(p.x) <= 12000f && Mathf.Abs(p.y) <= 12000f)
			{
				return Mathf.Abs(p.z) <= 12000f;
			}
			return false;
		}

		internal static bool IsPlausibleBody(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (IsPlausible(p))
			{
				if (p.x == 0f && p.y == 0f)
				{
					return p.z != 0f;
				}
				return true;
			}
			return false;
		}

		internal static bool Diverged(bool hasBody, Vector3 body, Vector3 reported, float limitMeters)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!hasBody || !IsPlausibleBody(body))
			{
				return false;
			}
			if (!IsPlausibleBody(reported))
			{
				return false;
			}
			Vector3 val = body - reported;
			return ((Vector3)(ref val)).sqrMagnitude > limitMeters * limitMeters;
		}

		internal static bool Same(Vector3 a, Vector3 b)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (a.x.Equals(b.x) && a.y.Equals(b.y))
			{
				return a.z.Equals(b.z);
			}
			return false;
		}

		internal static Vector3 Choose(bool hasBody, Vector3 body, Vector3 reported, bool divergedAtReport, out Source source)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (!hasBody || !IsPlausibleBody(body))
			{
				source = Source.NoCharacter;
				return reported;
			}
			if (divergedAtReport)
			{
				source = Source.PointsElsewhere;
				return reported;
			}
			source = Source.Character;
			return body;
		}

		internal static void Refresh()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				Refresh(ZDOMan.s_instance, instance.GetPeers());
			}
		}

		internal static void Refresh(ZDOMan man, List<ZNetPeer> peers)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (peers == null)
			{
				return;
			}
			if (!Active)
			{
				if (_wasActive)
				{
					RestoreAll(peers);
				}
				_wasActive = false;
				return;
			}
			_wasActive = true;
			if (man == null)
			{
				return;
			}
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null && val.IsReady())
				{
					PeerState orCreate = GetOrCreate(man, val);
					if (orCreate.HasWritten && !Same(val.m_refPos, orCreate.LastWritten))
					{
						NoteReport(man, val, orCreate);
					}
					Apply(man, val, orCreate);
				}
			}
		}

		internal static void NoteReported(ZNetPeer peer)
		{
			if (!Active || peer == null || !peer.IsReady())
			{
				return;
			}
			ZDOMan s_instance = ZDOMan.s_instance;
			if (s_instance != null)
			{
				bool num = States.ContainsKey(peer.m_uid);
				PeerState orCreate = GetOrCreate(s_instance, peer);
				if (num)
				{
					NoteReport(s_instance, peer, orCreate);
				}
				Apply(s_instance, peer, orCreate);
			}
		}

		private static PeerState GetOrCreate(ZDOMan man, ZNetPeer peer)
		{
			if (!States.TryGetValue(peer.m_uid, out var value))
			{
				value = new PeerState();
				States[peer.m_uid] = value;
				NoteReport(man, peer, value);
			}
			return value;
		}

		private static void NoteReport(ZDOMan man, ZNetPeer peer, PeerState state)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			state.Reported = peer.m_refPos;
			Vector3 body;
			bool hasBody = TryGetBody(man, peer, out body);
			state.DivergedAtReport = Diverged(hasBody, body, state.Reported, 64f);
			state.CharacterAtReport = peer.m_characterID;
			if (state.DivergedAtReport)
			{
				state.ReportsElsewhere++;
			}
		}

		private static void Apply(ZDOMan man, ZNetPeer peer, PeerState state)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			if (!((ZDOID)(ref peer.m_characterID)).Equals(state.CharacterAtReport))
			{
				state.DivergedAtReport = false;
				state.CharacterAtReport = peer.m_characterID;
			}
			Vector3 body;
			Source source;
			Vector3 val = Choose(TryGetBody(man, peer, out body), body, state.Reported, state.DivergedAtReport, out source);
			state.Current = source;
			if (source == Source.Character && IsPlausibleBody(state.Reported))
			{
				float num = Vector3.Distance(body, state.Reported);
				state.BehindSum += num;
				state.BehindSamples++;
				if (num > state.BehindPeak)
				{
					state.BehindPeak = num;
				}
			}
			peer.m_refPos = val;
			state.LastWritten = val;
			state.HasWritten = true;
		}

		private static bool TryGetBody(ZDOMan man, ZNetPeer peer, out Vector3 body)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			body = Vector3.zero;
			ZDOID characterID = peer.m_characterID;
			if (((ZDOID)(ref characterID)).IsNone())
			{
				return false;
			}
			ZDO zDO = man.GetZDO(characterID);
			if (zDO == null)
			{
				return false;
			}
			if (zDO.GetOwner() != peer.m_uid)
			{
				return false;
			}
			body = zDO.GetPosition();
			return IsPlausibleBody(body);
		}

		private static void RestoreAll(List<ZNetPeer> peers)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null && States.TryGetValue(val.m_uid, out var value) && value.HasWritten && Same(val.m_refPos, value.LastWritten))
				{
					val.m_refPos = value.Reported;
				}
			}
			States.Clear();
		}

		internal static void ForgetPeer(long peerUid)
		{
			States.Remove(peerUid);
		}

		internal static void Reset()
		{
			States.Clear();
			_wasActive = false;
		}
	}
	internal static class LossBackoff
	{
		private enum Run : byte
		{
			None,
			Lossy,
			Clean
		}

		private sealed class Entry
		{
			internal uint Handle;

			internal float Delivered;

			internal bool HasSample;

			internal int Steps;

			internal int OverrideBytes;

			internal Run RunKind;

			internal float RunSince;

			internal int RunSamples;

			internal float LastChangeAt = float.NegativeInfinity;

			internal float BackedOffSince;

			internal float DeliveredAtStart;

			internal float Carried;

			internal bool HasCarried;

			internal float RunCarriedPeak;

			internal float CarriedAtStart;

			internal bool FloorLogged;

			internal bool Exempt;

			internal float ExemptSince;
		}

		internal struct View
		{
			internal float Delivered;

			internal bool HasSample;

			internal bool Lossy;

			internal int Steps;

			internal int OverrideBytesPerSec;

			internal bool AtFloor;

			internal float BackedOffSince;

			internal bool Exempt;

			internal float ExemptSince;
		}

		internal const float StepFactor = 0.75f;

		internal const float RecoverMargin = 0.02f;

		internal const int MinRunSamples = 4;

		internal const float ImprovementMargin = 0.02f;

		internal const float LocalFaultGraceSeconds = 30f;

		private const float SampleAlpha = 0.25f;

		private const int BytesPerKB = 1024;

		private static readonly Dictionary<long, Entry> Peers = new Dictionary<long, Entry>();

		private static readonly HashSet<string> ExemptPlayers = new HashSet<string>();

		internal static int BackedOffNow { get; private set; }

		internal static long TotalStepsDown { get; private set; }

		internal static long TotalStepsUp { get; private set; }

		internal static long TotalCleared { get; private set; }

		internal static long TotalExempted { get; private set; }

		internal static int ExemptPlayerCount => ExemptPlayers.Count;

		internal static bool Wanted
		{
			get
			{
				if (NpsEnv.IsHost() && PatchGuard.IsActive(Mechanism.LossBackoff) && PatchGuard.IsActive(Mechanism.SteamTransport) && ValConfig.EnableSteamTransportTuning.Value && ValConfig.EnableLossBackoff.Value)
				{
					return SteamTransport.PinnedSendRateBytesPerSec > 0;
				}
				return false;
			}
		}

		internal static int FloorBytesPerSec => Mathf.Max(ValConfig.LossBackoffFloorKBps.Value, 32) * 1024;

		internal static int RateFor(int pinnedBytesPerSec, int steps, int floorBytesPerSec)
		{
			if (pinnedBytesPerSec <= 0)
			{
				return 0;
			}
			float num = pinnedBytesPerSec;
			for (int i = 0; i < steps; i++)
			{
				num *= 0.75f;
			}
			return Mathf.Max(Mathf.Min(floorBytesPerSec, pinnedBytesPerSec), Mat