Decompiled source of FiresGhettoNetworking v1.4.31

VAGhettoNetworking.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using FiresGhettoNetworkMod.AutoTune;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("VAGhettoNetworking")]
[assembly: AssemblyProduct("VAGhettoNetworking")]
[assembly: AssemblyCopyright("Copyright 2025")]
[assembly: ComVisible(false)]
[assembly: Guid("f234bd11-429c-45e2-b280-1fb9121d050d")]
[assembly: AssemblyFileVersion("1.4.12.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.12.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FiresGhettoNetworkMod
{
	[BepInPlugin("com.Fire.FiresGhettoNetworkMod", "FiresGhettoNetworkMod", "1.4.31")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class FiresGhettoNetworkMod : BaseUnityPlugin
	{
		[HarmonyPatch]
		public static class WackyDatabaseCompatibilityPatch
		{
			public static void Init(Harmony harmony)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Expected O, but got Unknown
				Type type = Type.GetType("wackydatabase.Util.Functions, WackysDatabase");
				if (type == null)
				{
					LoggerOptions.LogInfo("WackyDatabase not detected — skipping compatibility patch.");
					return;
				}
				MethodInfo method = type.GetMethod("SnapshotItem", BindingFlags.Static | BindingFlags.Public);
				if (method == null)
				{
					LoggerOptions.LogWarning("WackyDatabase detected but SnapshotItem method not found — patch skipped.");
					return;
				}
				harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(WackyDatabaseCompatibilityPatch), "SnapshotItem_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				LoggerOptions.LogInfo("WackyDatabase compatibility patch applied — will skip snapshots for invalid/broken clones.");
			}

			[HarmonyPrefix]
			public static bool SnapshotItem_Prefix(ref ItemDrop item)
			{
				if ((Object)(object)item == (Object)null)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for null ItemDrop (likely broken clone).");
					return false;
				}
				if ((Object)(object)((Component)item).gameObject == (Object)null)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for " + ((Object)item).name + " — gameObject is null (missing prefab from removed mod).");
					return false;
				}
				bool num = ((Component)item).GetComponentsInChildren<Renderer>(true).Length != 0;
				bool flag = ((Component)item).GetComponentsInChildren<MeshFilter>(true).Length != 0;
				if (!num && !flag)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for " + ((Object)item).name + " — no renderers or meshes (broken model).");
					return false;
				}
				return true;
			}
		}

		public const string PluginGUID = "com.Fire.FiresGhettoNetworkMod";

		public const string PluginName = "FiresGhettoNetworkMod";

		public const string PluginVersion = "1.4.31";

		public static ManualLogSource Log;

		public static ConfigEntry<LogLevel> ConfigLogLevel;

		public static ConfigEntry<bool> ConfigEnableCompression;

		public static ConfigEntry<UpdateRateOptions> ConfigUpdateRate;

		public static ConfigEntry<SendRateMinOptions> ConfigSendRateMin;

		public static ConfigEntry<SendRateMaxOptions> ConfigSendRateMax;

		public static ConfigEntry<QueueSizeOptions> ConfigQueueSize;

		public static ConfigEntry<ForceCrossplayOptions> ConfigForceCrossplay;

		public static ConfigEntry<int> ConfigPlayerLimit;

		public static ConfigEntry<int> ConfigAdvertisedPlayerLimit;

		public static ConfigEntry<bool> ConfigEnableShipFixes;

		public static ConfigEntry<bool> ConfigEnableServerSideShipSimulation;

		public static ConfigEntry<int> ConfigExtendedZoneRadius;

		public static ConfigEntry<bool> ConfigEnableZDOThrottling;

		public static ConfigEntry<float> ConfigZDOThrottleDistance;

		public static ConfigEntry<bool> ConfigEnableAILOD;

		public static ConfigEntry<float> ConfigAILODNearDistance;

		public static ConfigEntry<float> ConfigAILODFarDistance;

		public static ConfigEntry<float> ConfigAILODThrottleFactor;

		public static ConfigEntry<bool> ConfigEnableAdaptiveThrottling;

		public static ConfigEntry<int> ConfigSendCongestionThresholdPct;

		public static ConfigEntry<bool> ConfigEnableSendHeartbeatLog;

		public static ConfigEntry<bool> ConfigEnableFallThroughDiagnostics;

		public static ConfigEntry<bool> ConfigEnableCapeCrashDiagnostics;

		public static ConfigEntry<int> ConfigZoneLoadBatchSize;

		public static ConfigEntry<int> ConfigZPackageReceiveBufferSize;

		public static ConfigEntry<bool> ConfigEnableTimeSliceInstantiation;

		public static ConfigEntry<int> ConfigInstantiationBudgetMs;

		public static ConfigEntry<int> ConfigMaxInstancesPerFrame;

		public static ConfigEntry<bool> ConfigSafetyFallbackEnabled;

		public static ConfigEntry<int> ConfigSafetyFallbackThreshold;

		public static ConfigEntry<bool> ConfigEnablePredictiveZoneStreaming;

		public static ConfigEntry<float> ConfigPredictionLookaheadSec;

		public static ConfigEntry<float> ConfigPredictionMinVelocity;

		public static ConfigEntry<int> ConfigPredictionMaxLookaheadZones;

		public static ConfigEntry<bool> ConfigEnableInvulnerableSupportSkip;

		public static ConfigEntry<bool> ConfigEnableInstanceOrphanPrune;

		public static ConfigEntry<bool> ConfigFixTeleportGhosts;

		public static ConfigEntry<bool> ConfigFixSlowSleep;

		public static ConfigEntry<bool> ConfigKeepaliveFirst;

		public static ConfigEntry<bool> ConfigFixBoatDamageFromTimeSync;

		public static ConfigEntry<bool> ConfigFixGroundSnapThroughFloors;

		public static ConfigEntry<bool> ConfigEnableRpcRouter;

		public static ConfigEntry<bool> ConfigEnableRpcAoI;

		public static ConfigEntry<float> ConfigRpcAoIRadius;

		public static ConfigEntry<bool> ConfigEnableZDODelta;

		public static ConfigEntry<bool> ConfigEnableWNTServerOptimization;

		public static ConfigEntry<bool> ConfigEnableServerAuthority;

		public static ConfigEntry<bool> ConfigEnableServerOwnership;

		public static ConfigEntry<bool> ConfigEnableServerOwnershipSelective;

		public static ConfigEntry<bool> ConfigShowAILODInServerStatus;

		public static ConfigEntry<bool> ConfigEnableBootPatchVerification;

		public static ConfigEntry<bool> ConfigEnableLargeZdoDiagnostics;

		public static ConfigEntry<int> ConfigClientMaxDestroysPerFrame;

		public static ConfigEntry<string> ConfigDediFellOutRescueLayers;

		public static ConfigEntry<float> ConfigDiagnosticIntervalSec;

		public static ConfigEntry<bool> ConfigEnableBulkTransferBoost;

		public static ConfigEntry<int> ConfigBulkTransferBudgetPercent;

		public static ConfigEntry<bool> ConfigHyperBoost;

		private const float JoinAddressWaitSeconds = 60f;

		internal static Harmony Harmony { get; private set; }

		public static FiresGhettoNetworkMod Instance { get; private set; }

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Harmony = new Harmony("com.Fire.FiresGhettoNetworkMod");
			try
			{
				VAGhettoBanner.PrintBig();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FiresGhettoNetworkMod] VAGhettoBanner.PrintBig failed: " + ex.Message));
			}
			try
			{
				FiresLogColorPatch.Install(Harmony);
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[FiresGhettoNetworkMod] FiresLogColorPatch.Install failed: " + ex2.Message));
			}
			BindConfigs();
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to save config file immediately: " + ex3.Message));
			}
			LoggerOptions.Init(((BaseUnityPlugin)this).Logger);
			TierPresets.ValidateVanillaFloors();
			ServerClientUtils.Detect(((BaseUnityPlugin)this).Logger);
			bool isDedicatedServerDetected = ServerClientUtils.IsDedicatedServerDetected;
			if (isDedicatedServerDetected)
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.4.31 — Running on DEDICATED SERVER, enabling server-side features.");
			}
			else
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.4.31 — Running on CLIENT or SINGLE-PLAYER/LISTEN SERVER, only client-safe features will be applied.");
			}
			ValheimCommunityPatchCompat.Detect();
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.CompressionGroup", "InitConfig", new object[1] { ((BaseUnityPlugin)this).Config });
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.NetworkingRatesGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.DedicatedServerGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			SendQueueHeadroomMonitor.InitConfig(((BaseUnityPlugin)this).Config);
			AdaptiveSendRate.InitConfig(((BaseUnityPlugin)this).Config);
			LinkController.InitConfig(((BaseUnityPlugin)this).Config);
			SendScheduler.InitConfig(((BaseUnityPlugin)this).Config);
			StationRouter.InitConfig(((BaseUnityPlugin)this).Config);
			CreatureOwnership.InitConfig(((BaseUnityPlugin)this).Config);
			SectorChangeTracker.InitConfig(((BaseUnityPlugin)this).Config);
			Harmony.PatchAll(typeof(CompressionGroup));
			Harmony.PatchAll(typeof(NetworkingRatesGroup));
			Harmony.PatchAll(typeof(DedicatedServerGroup));
			Harmony.PatchAll(typeof(SendZDOsHeartbeatDiagnostic));
			if (ConfigEnableFallThroughDiagnostics.Value)
			{
				Harmony.PatchAll(typeof(FallThroughProbe));
				Harmony.PatchAll(typeof(PieceTypeAudit));
			}
			Harmony.PatchAll(typeof(ServerDisconnectDiagnostics));
			Harmony.PatchAll(typeof(BulkTransferGatePatches));
			Harmony.PatchAll(typeof(BigZdoDiagnostic));
			Harmony.PatchAll(typeof(DisarmOverloadTest));
			Harmony.PatchAll(typeof(CompressionRoundTripTest));
			Harmony.PatchAll(typeof(SocketStressTests));
			Harmony.PatchAll(typeof(SendQueueHeadroomMonitor));
			Harmony.PatchAll(typeof(LinkController));
			Harmony.PatchAll(typeof(SendScheduler));
			Harmony.PatchAll(typeof(ConnectionEcho));
			Harmony.PatchAll(typeof(CreatureOwnership));
			Harmony.PatchAll(typeof(KeepaliveFirst));
			Harmony.PatchAll(typeof(ZdoFloodTest));
			WackyDatabaseCompatibilityPatch.Init(Harmony);
			Harmony.PatchAll(typeof(PlayerPositionSyncPatches));
			Harmony.PatchAll(typeof(WearNTearClientSupportPatches));
			Harmony.PatchAll(typeof(ClientCleanupThrottle));
			Harmony.PatchAll(typeof(GroundSnapPatches));
			Harmony.PatchAll(typeof(OwnershipHandoffPatches));
			try
			{
				TeleportGhostFix.Init();
				Harmony.PatchAll(typeof(TeleportGhostFix));
			}
			catch (Exception ex4)
			{
				LoggerOptions.LogWarning("[TeleportGhostFix] could not be attached; vanilla behaviour is unchanged. " + ex4.Message);
			}
			try
			{
				Harmony.PatchAll(typeof(TerrainCompInitRace));
			}
			catch (Exception ex5)
			{
				LoggerOptions.LogWarning("[TerrainComp] could not be attached; vanilla behaviour is unchanged. " + ex5.Message);
			}
			Harmony.PatchAll(typeof(SleepTimeSkipFix));
			if (!isDedicatedServerDetected)
			{
				Harmony.PatchAll(typeof(WaveClockSmoothing));
			}
			if (!isDedicatedServerDetected && ConfigEnableCapeCrashDiagnostics.Value)
			{
				Harmony.PatchAll(typeof(CapeCrashDiagnostics));
				Harmony.PatchAll(typeof(MagicaColliderRegistrationDiagnostics));
				Harmony.PatchAll(typeof(MagicaClothLifecycleDiagnostics));
				LoggerOptions.LogWarning("[CapeDiag] Cape crash diagnostics are ON; turn 'Enable Cape Crash Diagnostics' off once the crash is found.");
			}
			Harmony.PatchAll(typeof(AutoTuneProbeHooks));
			Harmony.PatchAll(typeof(ZoneLoadPatches));
			ServerAutoTune.InitServerSide();
			if (isDedicatedServerDetected)
			{
				ApplyServerTrafficPatches();
				bool simulationActive = false;
				if (!ConfigEnableServerAuthority.Value)
				{
					LoggerOptions.LogInfo("Server-side simulation disabled via ConfigEnableServerAuthority = false.");
				}
				else if (ValheimCommunityPatchCompat.SchedulesSceneObjects)
				{
					LoggerOptions.LogWarning("Server-Side Simulation is OFF for this session: ValheimCommunityPatch replaces the server's object create/destroy pass with its own, built around world origin, and destroys every object FGN creates for players. Remove ValheimCommunityPatch from the server to use Server-Side Simulation, or turn 'Enable Server-Side Simulation' off to silence this warning.");
				}
				else
				{
					ApplyServerSideSimulationPatches();
					simulationActive = true;
				}
				EmitServerFeatureSummary(simulationActive);
			}
			else
			{
				LoggerOptions.LogInfo("Server-side features skipped — not running on a dedicated server.");
			}
			((MonoBehaviour)this).StartCoroutine(EmitCompactBannerWhenZNetReady());
		}

		private static void ApplyServerTrafficPatches()
		{
			Harmony.PatchAll(typeof(ServerFrameProfile));
			Harmony.PatchAll(typeof(SectorChangeTracker));
			Harmony.PatchAll(typeof(ZDOThrottlingPatches));
			Harmony.PatchAll(typeof(AILODPatches));
			if (ConfigEnableRpcRouter.Value || StationRouter.ConfigEnabled.Value)
			{
				Harmony.PatchAll(typeof(RpcRouterPatches));
				Harmony.PatchAll(typeof(StationRouter));
				DamageTextHandler.Register();
				VAGhettoLoadSummary.EmitRpcRouter(RoutedRpcManager.HandlerCount, RoutedRpcManager.PositionRadius(), RoutedRpcManager.FilteringEnabled());
				if (VAGhettoLoadSummary.VerboseEnabled)
				{
					LoggerOptions.LogInfo("RPC Router enabled — handlers: " + string.Join(", ", RoutedRpcManager.HandlerMethodNames) + ".");
				}
			}
			if (ConfigEnableZDODelta.Value)
			{
				Harmony.PatchAll(typeof(ZDODeltaPatches));
				LoggerOptions.LogInfo("ZDO delta compression enabled.");
			}
			if (ConfigEnableWNTServerOptimization.Value)
			{
				Harmony.PatchAll(typeof(WearNTearServerPatches));
				LoggerOptions.LogInfo("WearNTear server optimization enabled.");
			}
		}

		private static void ApplyServerSideSimulationPatches()
		{
			if (ConfigEnableShipFixes.Value)
			{
				Harmony.PatchAll(typeof(ShipFixesGroup));
			}
			Harmony.PatchAll(typeof(ServerShipSimulationPatches));
			Harmony.PatchAll(typeof(ServerAuthorityPatches));
			Harmony.PatchAll(typeof(ServerStabilityPatches));
			Harmony.PatchAll(typeof(MonsterAIPatches));
			if (ConfigEnableServerOwnershipSelective.Value)
			{
				if (ConfigEnableServerOwnership.Value)
				{
					LoggerOptions.LogWarning("Both 'Server ZDO Ownership Transfer' flags are ENABLED — selective (V3) takes precedence; broad (V2) is being ignored. Disable one to silence this warning.");
				}
				Harmony.PatchAll(typeof(ServerOwnershipPatchesV3));
				CreatureOwnership.ServerOwnsCreatures = true;
				LoggerOptions.LogMessage("Server ZDO ownership (V3 SELECTIVE) ENABLED — Character/Ship only; drops/voxel/interactables/carts stay peer-owned.");
			}
			else if (ConfigEnableServerOwnership.Value)
			{
				Harmony.PatchAll(typeof(ServerOwnershipPatches));
				CreatureOwnership.ServerOwnsCreatures = true;
				LoggerOptions.LogMessage("Server ZDO ownership (V2 BROAD SSS-exact) ENABLED — every persistent ZDO in any peer's active area will be claimed by the server.");
				if (!ConfigEnableServerSideShipSimulation.Value)
				{
					LoggerOptions.LogWarning("V2 BROAD claims SHIPS as well, regardless of 'Server-Side Ship Simulation' being off — it is a deliberate verbatim port with no per-prefab exclusions. A server-owned hull runs its own physics, and ImpactEffect only fires for the owner, so boats can take phantom damage on calm water. Use the Selective (V3) toggle instead if your players sail; it honours that setting.");
				}
			}
			else
			{
				LoggerOptions.LogInfo("Server ZDO ownership transfer disabled (both V2 and V3 flags = false). Vanilla peer ownership in effect.");
			}
			if (ConfigEnableBootPatchVerification.Value)
			{
				DumpPatchInfo(typeof(BaseAI), "UpdateAI");
				DumpPatchInfo(typeof(BaseAI), "Awake");
				DumpPatchInfo(typeof(MonsterAI), "UpdateAI");
				DumpPatchInfo(typeof(Character), "CustomFixedUpdate");
				DumpPatchInfo(typeof(MonoUpdaters), "FixedUpdate");
				DumpPatchInfo(typeof(ZNetScene), "CreateDestroyObjects");
				DumpPatchInfo(typeof(ZNetScene), "CreateObject");
				DumpPatchInfo(typeof(ZoneSystem), "IsActiveAreaLoaded");
				DumpPatchInfo(typeof(SpawnSystem), "UpdateSpawning");
				DumpPatchInfo(typeof(ShieldDomeImageEffect), "Awake");
				DumpPatchInfo(typeof(Heightmap), "RebuildRenderMesh");
			}
			if (VAGhettoLoadSummary.VerboseEnabled)
			{
				LoggerOptions.LogInfo("All server-side simulation patches enabled.");
			}
		}

		private static void EmitServerFeatureSummary(bool simulationActive)
		{
			string ownership = (ConfigEnableServerOwnershipSelective.Value ? "V3 selective" : (ConfigEnableServerOwnership.Value ? "V2 broad" : "vanilla peer"));
			VAGhettoLoadSummary.EmitServerAuthority(simulationActive, ownership, ConfigEnableZDODelta?.Value ?? false, ConfigEnableZDOThrottling?.Value ?? false, ConfigEnableAILOD?.Value ?? false, ConfigEnableWNTServerOptimization?.Value ?? false);
		}

		private IEnumerator EmitCompactBannerWhenZNetReady()
		{
			while ((Object)(object)ZNetScene.instance == (Object)null || ZNetScene.instance.m_prefabs == null || ZNetScene.instance.m_prefabs.Count == 0)
			{
				yield return null;
			}
			yield return (object)new WaitForEndOfFrame();
			string address = null;
			bool dedicated = ServerClientUtils.IsDedicatedServerDetected;
			if (dedicated)
			{
				float giveUpAt = Time.realtimeSinceStartup + 60f;
				while (!TryGetJoinAddress(out address) && Time.realtimeSinceStartup < giveUpAt)
				{
					yield return (object)new WaitForSecondsRealtime(0.5f);
				}
			}
			try
			{
				VAGhettoBanner.Print();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("FiresGhettoNetworkMod v1.4.31 loaded. (banner failed: " + ex.Message + ")"));
			}
			if (dedicated)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)((address != null) ? ("Join address: " + address) : $"Join address: this server's public IP, port {ServerPort()} (Steam did not report the public IP within {60f:0} s)."));
			}
		}

		private static bool TryGetJoinAddress(out string address)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			address = null;
			int num = ServerPort();
			if (num <= 0)
			{
				return false;
			}
			try
			{
				if (!SteamGameServer.BLoggedOn())
				{
					return false;
				}
				SteamIPAddress_t publicIP = SteamGameServer.GetPublicIP();
				if (!((SteamIPAddress_t)(ref publicIP)).IsSet())
				{
					return false;
				}
				IPAddress iPAddress = ((SteamIPAddress_t)(ref publicIP)).ToIPAddress();
				address = ((iPAddress.AddressFamily == AddressFamily.InterNetworkV6) ? $"[{iPAddress}]:{num}" : $"{iPAddress}:{num}");
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static int ServerPort()
		{
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			for (int i = 0; i < commandLineArgs.Length - 1; i++)
			{
				if (string.Equals(commandLineArgs[i], "-port", StringComparison.OrdinalIgnoreCase) && int.TryParse(commandLineArgs[i + 1], out var result) && result > 0)
				{
					return result;
				}
			}
			return 2456;
		}

		private void OnApplicationQuit()
		{
		}

		private void TryPatchAll(Type type)
		{
			if (type == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Tried to patch a null type!");
			}
			else
			{
				Harmony.PatchAll(type);
			}
		}

		private static void DumpPatchInfo(Type type, string methodName)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				LoggerOptions.LogMessage("[PatchVerify] " + type.FullName + "." + methodName + " → method NOT FOUND by AccessTools.");
				return;
			}
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
			if (patchInfo == null)
			{
				LoggerOptions.LogMessage("[PatchVerify] " + type.FullName + "." + methodName + " → NO patches attached (info=null).");
				return;
			}
			int num = (patchInfo.Prefixes?.Count ?? 0) + (patchInfo.Postfixes?.Count ?? 0) + (patchInfo.Transpilers?.Count ?? 0) + (patchInfo.Finalizers?.Count ?? 0);
			LoggerOptions.LogMessage($"[PatchVerify] {type.FullName}.{methodName} → total={num} (prefixes={patchInfo.Prefixes?.Count ?? 0}, postfixes={patchInfo.Postfixes?.Count ?? 0}, transpilers={patchInfo.Transpilers?.Count ?? 0}, finalizers={patchInfo.Finalizers?.Count ?? 0}).");
			DumpList("Prefix", patchInfo.Prefixes);
			DumpList("Postfix", patchInfo.Postfixes);
			DumpList("Transpiler", patchInfo.Transpilers);
			DumpList("Finalizer", patchInfo.Finalizers);
			static void DumpList(string kind, IEnumerable<Patch> ps)
			{
				if (ps == null)
				{
					return;
				}
				foreach (Patch p in ps)
				{
					LoggerOptions.LogMessage($"[PatchVerify]   [{kind}] owner='{p.owner}' method={p.PatchMethod.DeclaringType?.FullName}.{p.PatchMethod.Name} prio={p.priority}");
				}
			}
		}

		private void InvokeStaticInitByTypeName(string typeName, string methodName, object[] args)
		{
			try
			{
				Type type = Type.GetType(typeName);
				if (type == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Type " + typeName + " not found; skipping " + methodName + "."));
					return;
				}
				MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (method == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Method " + methodName + " not found on " + typeName + "."));
				}
				else
				{
					method.Invoke(null, args);
				}
			}
			catch (TypeLoadException arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"TypeLoadException while invoking {typeName}.{methodName}: {arg}");
			}
			catch (Exception arg2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Exception while invoking {typeName}.{methodName}: {arg2}");
			}
		}

		private void BindConfigs()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Expected O, but got Unknown
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Expected O, but got Unknown
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Expected O, but got Unknown
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Expected O, but got Unknown
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Expected O, but got Unknown
			//IL_03b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Expected O, but got Unknown
			//IL_03ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f4: Expected O, but got Unknown
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_0423: Expected O, but got Unknown
			//IL_0444: Unknown result type (might be due to invalid IL or missing references)
			//IL_044e: Expected O, but got Unknown
			//IL_04a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ab: Expected O, but got Unknown
			//IL_04cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d6: Expected O, but got Unknown
			//IL_0500: Unknown result type (might be due to invalid IL or missing references)
			//IL_050a: Expected O, but got Unknown
			//IL_052b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0535: Expected O, but got Unknown
			//IL_0556: Unknown result type (might be due to invalid IL or missing references)
			//IL_0560: Expected O, but got Unknown
			//IL_0581: Unknown result type (might be due to invalid IL or missing references)
			//IL_058b: Expected O, but got Unknown
			//IL_05b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_05bc: Expected O, but got Unknown
			//IL_060f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0619: Expected O, but got Unknown
			//IL_064c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0656: Expected O, but got Unknown
			//IL_067f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0689: Expected O, but got Unknown
			//IL_06dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e6: Expected O, but got Unknown
			//IL_0739: Unknown result type (might be due to invalid IL or missing references)
			//IL_0743: Expected O, but got Unknown
			//IL_0776: Unknown result type (might be due to invalid IL or missing references)
			//IL_0780: Expected O, but got Unknown
			//IL_07b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07bd: Expected O, but got Unknown
			//IL_0807: Unknown result type (might be due to invalid IL or missing references)
			//IL_0811: Expected O, but got Unknown
			//IL_0852: Unknown result type (might be due to invalid IL or missing references)
			//IL_085c: Expected O, but got Unknown
			//IL_087d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0887: Expected O, but got Unknown
			ConfigLogLevel = ((BaseUnityPlugin)this).Config.Bind<LogLevel>("01 - General", "Log Level", LogLevel.Message, "Controls verbosity in BepInEx log.");
			ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Zone Load Batch Size", 1, new ConfigDescription("How aggressively the client consumes incoming zone-stream backlog per frame.\n1 = vanilla (one CreateObjects pass per frame, capped by Valheim).\n2 = double the per-frame cap (faster zone load, bigger frame hitches).\n4 = quadruple (zone-cross stutter masking on capable machines).\nAuto-Tune may override this on the client based on measured frame time.\nCLIENT-ONLY — no effect on server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			PlayerPositionSyncPatches.Init(((BaseUnityPlugin)this).Config);
			ConfigEnableCompression = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Compression", true, "Enable Deflate network compression (highly recommended). It is negotiated per player, so it only\nengages with players whose FGN uses the same compression format. Data Valheim already compresses\n(terrain edits, tar pits, map tables) is sent as is rather than compressed again.");
			ConfigUpdateRate = ((BaseUnityPlugin)this).Config.Bind<UpdateRateOptions>("04 - Networking", "ZDO Send Rate", UpdateRateOptions._100, "How many times a second each player is sent world updates (and a client sends its own). This is a\nNETWORK send-cadence setting ONLY: it does NOT change the world tick, day length, smelter or cooking\ntimers, cooldowns or any simulation speed. Higher = other players and creatures look smoother, at the\ncost of more bandwidth and server CPU.\n100% (20 per second) is vanilla's intended rate. Vanilla only reaches it with one or two players, because\nit serves one player per frame; 'Send To Every Player Each Frame' gives every player this rate.\n150% (30 per second) can look smoother when bandwidth and CPU allow. Never goes below vanilla.");
			ConfigSendRateMin = ((BaseUnityPlugin)this).Config.Bind<SendRateMinOptions>("05 - Networking - Steamworks", "Send Rate Min", SendRateMinOptions._512KB, "Minimum send rate Steam will attempt. Steam's adapter has a sticky-down quirk — peers that back off toward this value tend to stay there. Keep it well above unplayable.");
			ConfigSendRateMax = ((BaseUnityPlugin)this).Config.Bind<SendRateMaxOptions>("05 - Networking - Steamworks", "Send Rate Max", SendRateMaxOptions._2048KB, "Maximum send rate Steam will attempt. This is 'permission to burst' — Steam still ramps adaptively between Min and Max, this just removes the artificial ceiling.");
			ConfigHyperBoost = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Networking - Steamworks", "HYPERBOOST", false, "MAX-THROUGHPUT MODE. Overrides Auto-Tune AND the Send Rate Min/Max above, lifting Steam's\nsend rate, send buffer, and recv buffer / per-message ceiling to their proven unlocked\nmaximums — the same lifts fgn_socketramp uses to reach ~40 MB/s, versus the ~8 MB/s the\nHigh tier caps everyday traffic at. Applies LIVE the instant you toggle it (no reconnect).\nFor a server->client transfer, set it on BOTH sides: the server lifts its outbound, the\nclient lifts its inbound. The recv side needs FiresSteamworksPatcher installed.\nThis trades Steam's conservative congestion ceiling for raw headroom and lets the buffers\ngrow large under load, so leave it OFF for normal play and ON for high-bandwidth links or\nbenchmarking.");
			AutoTuneConfig.Init(((BaseUnityPlugin)this).Config);
			ConfigQueueSize = ((BaseUnityPlugin)this).Config.Bind<QueueSizeOptions>("04 - Networking", "Queue Size", QueueSizeOptions._32KB, "The largest single package of world updates sent to one player at a time, and the starting send window\nfor each player. With 'Adaptive Send Window' off, or for crossplay players, it is also the fixed limit on\ndata in flight to each player. Vanilla is 10 KB.");
			ConfigForceCrossplay = ((BaseUnityPlugin)this).Config.Bind<ForceCrossplayOptions>("09 - Dedicated Server", "Force Crossplay", ForceCrossplayOptions.vanilla, "Requires restart. Selects the networking backend for a DEDICATED SERVER.\nvanilla = respect the command-line -crossplay flag (DEFAULT — does NOT change how your server connects).\nsteamworks = force Steam-only; DISABLES crossplay. Best performance for an all-Steam playerbase, but Xbox / Game Pass / PlayStation players cannot join.\nplayfab = force crossplay ENABLED (PlayFab matchmaking) regardless of the -crossplay flag.");
			ConfigPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Player Limit", 10, new ConfigDescription("Max players on dedicated server. Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 999), Array.Empty<object>()));
			ConfigAdvertisedPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Advertised Player Limit", 0, new ConfigDescription("Max players advertised to matchmaking (Steam server browser → BattleMetrics, PlayFab session/Party). Independent of the actual in-game limit set by 'Player Limit' — useful when an operator wants their server listed as '/500' for marketing while running a real 30-slot cap. 0 = mirror 'Player Limit' (advertised matches reality). Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 9999), Array.Empty<object>()));
			ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Zone Load Batch Size", 1, new ConfigDescription("How aggressively the client consumes incoming zone-stream backlog per frame.\n1 = vanilla (one CreateObjects pass per frame, capped by Valheim).\n2 = double the per-frame cap (faster zone load, bigger frame hitches).\n4 = quadruple (zone-cross stutter masking on capable machines).\nAuto-Tune may override this on the client based on measured frame time.\nCLIENT-ONLY — no effect on server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			ConfigZPackageReceiveBufferSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "ZPackage Receive Buffer Bytes", 262144, new ConfigDescription("Steam recv-buffer size for inbound network packages. Bigger = fewer dropped packets\nif the client briefly stalls (GC pause, disk hitch), but uses more RAM.\nAuto-Tune may override this on the client based on measured tier.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(65536, 4194304), Array.Empty<object>()));
			ConfigEnableTimeSliceInstantiation = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Enable Time-Slice Instantiation", false, "Replace vanilla's fixed 10/100 per-frame ZDO instantiation cap with a per-frame ms\nbudget that drains incoming objects across multiple ticks. Eliminates the big spike\nwhen crossing into a heavy zone. Disable to fall back to the cap-bump transpiler\n(set 'Zone Load Batch Size' to control its multiplier).\nOFF by default: instantiating this fast can spawn a creature/item the instant its ZDO\narrives — before the structure it rests on, when that ZDO lags a tick behind — which can\nlet tames slip locked pens or drop items through floors on zone load. Opt in for the\nsmoother zone crossings if your world doesn't hit that.\nCLIENT-ONLY — no effect on dedicated server.");
			ConfigInstantiationBudgetMs = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Instantiation Budget Ms", 3, new ConfigDescription("Per-frame millisecond budget for ZDO → GameObject instantiation when time-slicing\nis enabled. Lower = smoother frame times during zone load (longer ramp-in); higher\n= zone loads finish faster (bigger spikes). 3 ms keeps 60-fps clients comfortable.\nAuto-Tune overrides this with tier-specific values when active.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>()));
			ConfigMaxInstancesPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Instances Per Frame", 100, new ConfigDescription("Hard ceiling on instantiations per frame regardless of how much budget remains.\nBelt-and-suspenders against a runaway pending list.\nAuto-Tune overrides this with tier-specific values when active.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 500), Array.Empty<object>()));
			ConfigSafetyFallbackEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Safety Fallback Enabled", true, "When the pending instantiation list grows past 'Safety Fallback Threshold' items\n(teleport into megabase, freshly-loaded zones), widen the per-frame budget so we\ndon't bleed across many seconds. Capped at 16 ms so we never burn an entire frame.");
			ConfigSafetyFallbackThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Safety Fallback Threshold", 5000, new ConfigDescription("Pending-ZDO count at which the safety fallback widens the instantiation budget.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 50000), Array.Empty<object>()));
			ConfigEnableRpcRouter = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Router", true, "Server-side relay for the messages players broadcast. Vanilla sends every footstep, swing, damage number and\ndestroyed object to every player on the server. With 'Enable RPC Area-of-Interest' on, a message about an\nobject only goes to players the server has sent that object, and a damage number only to players near it.\nNothing is dropped that a player could have used. Requires restart.\nDEDICATED SERVER ONLY.");
			ConfigEnableShipFixes = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Enable Universal Ship Fixes", true, "Apply permanent autopilot + jitter fixes to ALL ships.");
			ConfigEnableServerSideShipSimulation = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Server-Side Ship Simulation", false, "Server authoritatively simulates ship physics.\nDisabled by default — enable manually if you want the server to drive ship physics.\n\nThis also gates whether Selective (V3) ZDO ownership claims ships. Ownership IS simulation:\nwhoever owns a hull runs its Rigidbody, and vanilla only applies impact damage on the owner,\nso a server that owns an empty boat can collide it against its own streaming colliders and\ndamage it on flat water. Left OFF, ships stay peer-owned and the sailing client simulates\nthem exactly as in vanilla, while creatures still move to the server.\nNOTE: the BROAD (V2) ownership toggle ignores this and claims ships regardless.");
			ConfigEnableRpcAoI = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Area-of-Interest", true, "Relays a broadcast about an object only to players who have that object, destroyed objects only to players\nwho held them, and damage numbers only to players within 'RPC AoI Radius'. Each of those players would\ndiscard the message anyway. Broadcasts with no object or position still go to everyone.\nRequires Enable RPC Router = true. DEDICATED SERVER ONLY.");
			ConfigRpcAoIRadius = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "RPC AoI Radius", 256f, new ConfigDescription("Distance in meters within which players are sent messages that carry only a position, such as damage\nnumbers (each player shows those within 30 m of their camera). Auto-Tune sets it when it is on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(64f, 1024f), Array.Empty<object>()));
			ConfigClientMaxDestroysPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Destroys Per Frame", 200, new ConfigDescription("Maximum ZNetScene instances the client destroys per CreateDestroyObjects pass.\nVanilla destroys every out-of-area instance in a single frame, producing a\nmulti-second hitch when leaving a heavily-loaded zone (e.g. ~150k instances\non a megabase). The throttle defers the surplus to subsequent frames so the\ndeparture cost spreads out smoothly.\n0 = no throttle (vanilla single-frame destruction).\n200 = ~12s to clear a 150k-instance backlog at 60 fps, with each frame's\ndestroy cost roughly equal to instantiating 200 objects.\nCLIENT-ONLY — no effect on dedicated server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5000), Array.Empty<object>()));
			ConfigDediFellOutRescueLayers = ((BaseUnityPlugin)this).Config.Bind<string>("10 - Server Authority", "Dedi Fell-Out Rescue Layers", "Default,static_solid,Default_small,piece,terrain,vehicle", new ConfigDescription("Comma-separated Unity layer names the dedi raycasts against when rescuing a mob\nthat fell below the kill plane (y < -5000). Default mirrors vanilla BaseAI's\nsolid-ray mask so any vanilla collider catches the mob. If your modlist ships\ncustom terrain on a non-vanilla layer (RPGmaker overlay, etc.) and you see mobs\npermanently parked instead of recovering, add that layer name here. The cache\nrefreshes when this value changes; no restart needed.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigFixGroundSnapThroughFloors = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Ground Snap Through Floors", true, new ConfigDescription("Vanilla decides whether something has fallen out of the world by comparing it to the\nHEIGHTMAP, which cannot see build pieces. Digging moves the heightmap, so pits and mines\nare fine, but anything resting on a piece BELOW the heightmap is not: a tame penned on a\ncellar floor under a mound, or a gravestone in that cellar, reads as under the world and\nis teleported up to the dirt, through the floor that was holding it. With this on, that\none check asks what is actually underneath first (see 'Dedi Fell-Out Rescue Layers' for\nthe surfaces that count) and only falls back to the heightmap when nothing is there.\nAffects Character.UnderWorldCheck and TombStone.PositionCheck. Costs a single raycast,\nand only in the moment vanilla was about to teleport something.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigShowAILODInServerStatus = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Show AILOD in ServerStatus", true, "When ON, appends the AILOD throttle's per-window stats (mobs examined,\nnear/mid/far decision counts, nearest-peer distance range) to the\nconsolidated [ServerStatus] line. Useful for tuning the near/far gates.\nTurn OFF once tuning is validated and you want a quieter log.\nHas no effect when Enable AI LOD Throttling is OFF — there's nothing to report.");
			ConfigDiagnosticIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("01 - General", "Diagnostic Rollup Interval (sec)", 60f, new ConfigDescription("How often the consolidated [ServerStatus] line is logged on a dedicated server.\nSummarises MonoUpdaters tick rate, AI list sizes, BaseAI/MonsterAI tick counts,\nZNetScene instantiation, our CreateDestroyObjects / IsActiveAreaLoaded gate\nbehavior, and ServerOwnership transfer/release churn in a single line.\nLower = more visibility, more log spam. Higher = quieter. Change takes effect on the next window.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 3600f), Array.Empty<object>()));
			ConfigEnableBulkTransferBoost = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Bulk Transfer Queue Boost", true, new ConfigDescription("When ON, reflectively patches the 20 KB GetSendQueueSize gate inside every loaded ServerSync.ConfigSync (AzuEPI, Marketplace, EpicLoot, Wizardry, EW Data, etc.) AND ServerCharacters.Shared copy so their fragment loops keep up with our raised ZDOMan cap.\nReplaces the old BulkTransferGuard which suppressed ZDOMan.SendZDOs during bulk bursts and incidentally starved player position ZDOs (the source of 'players flying / teleporting / hits from across the map' complaints).\nDisable as a kill switch if you suspect the scan is causing freezes or false-positive patching.\nBoth sides — applies on client and dedicated server.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigBulkTransferBudgetPercent = ((BaseUnityPlugin)this).Config.Bind<int>("04 - Networking", "Bulk Transfer Budget Percent", 40, new ConfigDescription("Caps how much of the Steam per-connection send buffer the raised ServerSync gates may collectively claim, so many ServerSync mods (Azu/EpicLoot/Marketplace/EW/etc.) can't stack their raised gates and overflow the buffer (the cause of the heavy-area peer disconnects). The per-mod gate is computed as min(Queue Size, (buffer * this% / ServerSync-mod-count)), floored at the vanilla 20 KB so it never throttles tighter than stock. The buffer is 512 KB by default, or the larger value set when FiresSteamworksPatcher is installed. 40% leaves headroom for ZDO + RPC traffic. Lower if you still see heavy-area disconnects; raise if config syncs feel slow on join. Only matters when Bulk Transfer Queue Boost is ON.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80), Array.Empty<object>()));
			ConfigEnableServerAuthority = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server-Side Simulation", false, new ConfigDescription("Makes the server fully authoritative over zones, ZDO ownership, monster AI, events, etc. (does NOT override your existing ship fixes).\n\nThe RPC router and RPC area-of-interest, ZDO delta compression, ZDO throttling, AI LOD and the\nWearNTear server optimization do not need this: each follows its own toggle either way.\n\nDisabled by default — enable manually on your DEDICATED SERVER if desired.\n\nWARNING: THIS IS A SERVER-ONLY FEATURE!\nEnabling this on a CLIENT will cause INFINITE LOADING SCREEN.\nThe mod automatically disables it on clients regardless of this setting.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableServerOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer (EXPERIMENTAL)", false, new ConfigDescription("EXPERIMENTAL — defaults OFF. Direct port of the original Serverside Simulations mod's\nZDOMan.ReleaseNearbyZDOS prefix. When enabled, the server takes ownership of EVERY\npersistent ZDO in any peer's active area (mobs, ships, terrain, structures, items, doors,\nthe whole world). Peers no longer own anything.\n\nWHEN TO ENABLE: you've validated the rest of your modlist plays nicely with broad\nserver ownership and you want maximum server-side simulation (offloads client CPU,\nflips the bandwidth pattern so server send dominates).\n\nWHEN TO LEAVE OFF: you're running a heavy modpack, you've seen mob freezes or\ninteraction issues after a previous attempt, or you don't know yet. Leaving this off\npreserves vanilla peer ownership — every other server-authority feature (zones,\nspawning, raids, throttling) still works without it.\n\nREQUIRES: ConfigEnableServerAuthority = true AND running on a dedicated server.\nTOGGLE IS INDEPENDENT — flip this without touching ConfigEnableServerAuthority.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableServerOwnershipSelective = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer — Selective (EXPERIMENTAL)", false, new ConfigDescription("EXPERIMENTAL — defaults OFF. Selective variant of the broad ownership transfer above.\nOnly Character (non-Player) and Ship prefabs are claimed by the server. Drops,\ncontainers, doors, signs, workstations, pickables, beds, traders, wards, voxel\nterrain, built structures, and carts all stay under vanilla peer ownership.\n\nRATIONALE: broad SSS-style ownership (the toggle above) exposes interaction-RPC\nrace conditions — `removedrops` failing for mob-dropped items, voxel mining/flattening\nbreaking intermittently under load, carts shaking/sinking when parked. Selective scope\navoids all three by keeping interactables on the vanilla peer-owned path.\n\nMUTUALLY EXCLUSIVE with the broad toggle above. If both are true, this selective\nvariant takes precedence (the safer choice — drops/voxel/etc. stay working).\n\nREQUIRES: ConfigEnableServerAuthority = true AND running on a dedicated server.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigExtendedZoneRadius = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Extended Zone Radius", 1, new ConfigDescription("Additional zone layers the server pre-loads around players for smoother zone transitions.\n0 = vanilla (no extra pre-load)\n1 = +1 layer (recommended, ~7x7 zones total)\n2 = +2 layers (~9x9 zones)\n3 = +3 layers (~11x11 zones)\n\nHigher values reduce stutter when crossing zone borders but increase server CPU/RAM usage.\nSERVER-ONLY — clients ignore this setting.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			ConfigEnablePredictiveZoneStreaming = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Predictive Zone Streaming", true, "Bias each peer's active-area center forward along their velocity vector so the\nserver starts loading zones BEFORE the peer crosses the boundary. Composes with\n'Extended Zone Radius' — the symmetric ring still expands, the center just slides\nforward. By the time the peer arrives, the predicted zones are already loaded.\nSERVER-ONLY — clients ignore this setting.");
			ConfigPredictionLookaheadSec = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Lookahead Sec", 3f, new ConfigDescription("How many seconds ahead the server projects each peer's position when deciding\nwhich zones to pre-load. 3 s gives one zone of headroom at running speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			ConfigPredictionMinVelocity = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Min Velocity", 2f, new ConfigDescription("Minimum smoothed speed (m/s) below which prediction is suppressed and the peer's\nreal position is used. Stops idle / slow-walking peers from triggering pre-load.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>()));
			ConfigPredictionMaxLookaheadZones = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Prediction Max Lookahead Zones", 9, new ConfigDescription("Hard cap on prediction distance, expressed in zones (64 m each). Stops a\nteleporting / glitching peer from subscribing to zones across the world.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 25), Array.Empty<object>()));
			ConfigEnableZDOThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable ZDO Throttling", true, "Reduce update frequency for distant ZDOs (creatures/structures far away) to save bandwidth.\nSERVER-ONLY — no effect on client.");
			ConfigZDOThrottleDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "ZDO Throttle Distance", 500f, new ConfigDescription("Distance (meters) beyond which ZDOs are throttled (lower update rate).\n0 = disable throttling.\nRecommended: 400-600m.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
			ConfigEnableAILOD = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable AI LOD Throttling", true, "Reduce FixedUpdate frequency for distant AI (saves server CPU).\nNearby AI stays full speed for smooth combat.\nSERVER-ONLY — no effect on client.");
			ConfigAILODNearDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Near Distance", 100f, new ConfigDescription("Full-speed AI within this range (meters).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 200f), Array.Empty<object>()));
			ConfigAILODFarDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Far Distance", 300f, new ConfigDescription("Beyond this distance, AI is throttled (meters).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(200f, 600f), Array.Empty<object>()));
			ConfigAILODThrottleFactor = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Throttle Factor", 0.5f, new ConfigDescription("Update multiplier for throttled AI (0.5 = half speed, 0.25 = quarter). Lower = more savings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 0.75f), Array.Empty<object>()));
			ConfigEnableAdaptiveThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Adaptive Throttling", true, "Only engage FGN's send-side optimizations (distant-ZDO throttling, player\nboost, AI LOD) when a peer's send queue is actually backing up. On a server\nwith bandwidth to spare, FGN leaves vanilla update order untouched — leaner\nand lower-latency. Turn OFF to force the optimizations on at all times.\nSERVER-ONLY.");
			ConfigSendCongestionThresholdPct = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Congestion Threshold", 50, new ConfigDescription("How full a peer's send queue must get (percent of cap) before Adaptive\nThrottling engages the optimizations. Lower = engages sooner.\nSERVER-ONLY.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 100), Array.Empty<object>()));
			ConfigEnableSendHeartbeatLog = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Log Send Queue Heartbeat", false, "Diagnostic: log each peer's send-queue health every 10s on a dedicated\nserver. Useful when investigating lag, but writes ~1 line per player per\n10s to the log. Leave OFF for normal play. SERVER-ONLY.");
			ConfigEnableBootPatchVerification = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Boot Patch Verification", false, new ConfigDescription("OFF by default. When ON, logs every Harmony patch attached to the AI tick,\ninstantiation, and zone-gate methods this mod cares about — useful when\ntroubleshooting mod-conflict scenarios (another mod's transpiler stomping our\nprefix, etc.) or when bringing up a new feature.\n\nThe runtime [ServerStatus] rollup is the ongoing health indicator; this toggle\nis only useful when you suspect Harmony itself didn't attach something.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableLargeZdoDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Large ZDO Diagnostics", false, new ConfigDescription("OFF by default. When ON, adds enriched [BigZdoDiag] log lines next to vanilla's\nexisting 'Writing a lot of data; X items, is not optimal' warning so you can see\nwhich ZDO is bloating its extra-data buckets (uid, prefab name, world position,\nowner peer id, bucket type and count).\n\nNOTE: the underlying warning is emitted by vanilla Valheim, not by this mod.\nThis toggle only controls whether we ATTACH context to it. Turn this on when you\nsee the vanilla warning and want to track down the offending entity; leave it off\notherwise so the log stays quiet.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableZDODelta = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable ZDO Delta Compression", true, "On re-syncs, only send ZDO fields that changed since last send to each peer.\nInitial sync always sends full ZDO state. Re-syncs only send the diff.\nSignificant bandwidth reduction for high-field ZDOs (creatures, players) where\nonly 1-2 fields change per tick (e.g. health, position).\nSERVER-ONLY — no effect on client.");
			ConfigEnableWNTServerOptimization = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable WearNTear Server Optimization", true, "Skips the wear and support update for building pieces whose damage modifiers are all\nImmune or Ignore (Infinity Hammer, admin-flagged pieces), on the server that owns them.\nEvery other piece updates exactly as in vanilla.\nSERVER-ONLY — no effect on client.");
			ConfigEnableInvulnerableSupportSkip = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Invulnerable Support Skip", true, "CLIENT-side counterpart to the WearNTear server optimization. Short-circuits the\nexpensive WearNTear.UpdateSupport call (Physics.OverlapBoxNonAlloc per piece) for\npieces whose damage modifiers are all Immune/Ignore — e.g. Infinity Hammer pieces.\nPins m_support at the material's max value so neighbouring mortal pieces still\nsee full support when querying. Massive steady-state CPU saving in megabases\ndominated by invulnerable pieces.");
			ConfigEnableInstanceOrphanPrune = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Instance Orphan Prune", true, "SERVER-ONLY defensive cleanup. Before vanilla ZNetScene.RemoveObjects walks\nm_instances on the dedicated server, scan for entries whose ZNetView is\nUnity-destroyed OR whose view.GetZDO() returns null, and remove just the dict\nentry (the GameObject is left alone). These are exactly the entries that would\nNRE vanilla RemoveObjects, so we're only purging things vanilla can't handle.\nTriggered by mods that block WearNTear.RPC_Remove on the server while ZDOMan\nstill reaps the ZDO via a separate path — e.g. TargetPortalProtection's\nPlayer.m_localPlayer-based permission check on a headless dedi when ZDO\nownership has been moved to the server (FGN's Server-Side Simulation).\n\nEvery prune is logged at warning level (rate-limited to once per 5s) and the\nper-window count appears in the [ServerStatus] CDO segment. If you see this\nfiring repeatedly on a healthy server, another mod is mismanaging ZNetScene\nstate and should be investigated. Disable as a kill switch if it ever causes\ntrouble (you'd then see the original NRE caught by the existing fallback).");
			ConfigFixTeleportGhosts = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Teleport Ghost Players", true, "Fixes a Valheim 1.0 bug. When something jumps out of a player's area in one step\n(a portal, a teleport command), the server tests the position it is LEAVING, never tells\nthat player, and they keep seeing the traveller frozen where they left until it next\ncrosses a zone line. Re-runs vanilla's own check once the new position has landed.\nStands down on its own when the game or another mod already fixes it, e.g.\nValheimCommunityPatch's 'Fix Teleport Ghost Players' (deferred to while that is on).\nSERVER / LISTEN-HOST ONLY. No effect on a connecting client.");
			ConfigFixSlowSleep = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Slow Sleep On Busy Servers", true, "Vanilla moves the sleep time skip forward by one physics step per rendered frame, so the\nskip meant to last 12 seconds takes longer the lower the server's frame rate. A busy server\ncan keep everyone in bed for a minute or more. With this on, the skip follows real time and\nmorning arrives after about 12 seconds however busy the server is.\nSERVER / LISTEN-HOST ONLY. No effect on a connecting client.");
			ConfigKeepaliveFirst = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Keep Busy Connections Alive", true, "Valheim disconnects a player whose keepalive goes unanswered for 30 seconds, and its keepalives\nwait behind everything already queued to send. A large transfer, such as a big base loading in,\ncould time a player out while data was still arriving. With this on, keepalives go ahead of the\nqueue. Works on whichever side has it; install on server and clients to cover both directions.");
			ConfigFixBoatDamageFromTimeSync = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Boat Damage From Server Time Sync", true, "Every 2 seconds the server corrects each player's clock, and vanilla applies the correction at once.\nWaves are worked out from that clock, so every correction moves the water under a ship in a single\nphysics step, and a boat with players aboard takes the jump as slamming into the water and loses hull\nhealth. The more a connection's timing wobbles, the bigger and more frequent the hits. With this on,\nwaves follow a clock that eases each correction in over a few seconds, so the water never jumps.\nCorrections of 5 seconds or more (sleeping, reconnecting) still apply at once, as in vanilla.\nCLIENT-SIDE. A ship is damaged by the game of the player who owns it, normally someone aboard,\nso every player who sails needs this on. No effect on a dedicated server.");
			ConfigEnableCapeCrashDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Cape Crash Diagnostics", false, "Temporary client diagnostic for the crash in cape cloth setup. Logs taking and leaving ship controls, shoulder\nitem changes, every cloth collider handed to MagicaCloth2, every cloth built or destroyed, a per-frame check of\neach cape collider list, object creation bursts, data overwriting the local player, auto-tune probe steps, server\nclock corrections, every Steam setting FGN applies and a 5 second heartbeat with memory and compression totals.\nEach line is written before its step runs, so the last line before a crash names the step. Takes effect on the\nnext start. Leave OFF for normal play.\nCLIENT-SIDE.");
			ConfigEnableFallThroughDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Fall-Through Diagnostics", false, "Verbose, opt-in diagnostics for investigating items and tombstones sinking through\nstructures. Off by default. When on, two probes run: a per-spawn probe that raycasts\nunder each dropped item/tombstone and logs the ones at risk (and any that actually\nfall), and a one-shot startup audit that names build pieces left non-Solid (the load\norder that lets an item spawn before its support). The per-spawn probe adds real cost\nand log volume on a busy server, so leave this OFF for normal play and the live read —\nturn it on only to investigate a suspected fall-through. These probes only observe and\nlog; they do not change any physics.");
			ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[49]
			{
				(ConfigEntryBase)ConfigLogLevel,
				(ConfigEntryBase)ConfigEnableCompression,
				(ConfigEntryBase)ConfigUpdateRate,
				(ConfigEntryBase)ConfigSendRateMin,
				(ConfigEntryBase)ConfigSendRateMax,
				(ConfigEntryBase)ConfigQueueSize,
				(ConfigEntryBase)ConfigForceCrossplay,
				(ConfigEntryBase)ConfigPlayerLimit,
				(ConfigEntryBase)ConfigAdvertisedPlayerLimit,
				(ConfigEntryBase)ConfigEnableShipFixes,
				(ConfigEntryBase)ConfigEnableServerSideShipSimulation,
				(ConfigEntryBase)ConfigEnableRpcRouter,
				(ConfigEntryBase)ConfigEnableRpcAoI,
				(ConfigEntryBase)ConfigRpcAoIRadius,
				(ConfigEntryBase)ConfigEnableServerAuthority,
				(ConfigEntryBase)ConfigExtendedZoneRadius,
				(ConfigEntryBase)ConfigEnableZDOThrottling,
				(ConfigEntryBase)ConfigZDOThrottleDistance,
				(ConfigEntryBase)ConfigEnableAILOD,
				(ConfigEntryBase)ConfigAILODNearDistance,
				(ConfigEntryBase)ConfigAILODFarDistance,
				(ConfigEntryBase)ConfigAILODThrottleFactor,
				(ConfigEntryBase)ConfigEnableZDODelta,
				(ConfigEntryBase)ConfigShowAILODInServerStatus,
				(ConfigEntryBase)ConfigEnableBootPatchVerification,
				(ConfigEntryBase)ConfigEnableLargeZdoDiagnostics,
				(ConfigEntryBase)ConfigEnableWNTServerOptimization,
				(ConfigEntryBase)ConfigZoneLoadBatchSize,
				(ConfigEntryBase)ConfigZPackageReceiveBufferSize,
				(ConfigEntryBase)ConfigEnableTimeSliceInstantiation,
				(ConfigEntryBase)ConfigInstantiationBudgetMs,
				(ConfigEntryBase)ConfigMaxInstancesPerFrame,
				(ConfigEntryBase)ConfigSafetyFallbackEnabled,
				(ConfigEntryBase)ConfigSafetyFallbackThreshold,
				(ConfigEntryBase)ConfigEnablePredictiveZoneStreaming,
				(ConfigEntryBase)ConfigPredictionLookaheadSec,
				(ConfigEntryBase)ConfigPredictionMinVelocity,
				(ConfigEntryBase)ConfigPredictionMaxLookaheadZones,
				(ConfigEntryBase)ConfigEnableInvulnerableSupportSkip,
				(ConfigEntryBase)ConfigEnableInstanceOrphanPrune,
				(ConfigEntryBase)ConfigFixTeleportGhosts,
				(ConfigEntryBase)ConfigFixSlowSleep,
				(ConfigEntryBase)ConfigKeepaliveFirst,
				(ConfigEntryBase)ConfigFixBoatDamageFromTimeSync,
				(ConfigEntryBase)ConfigEnableFallThroughDiagnostics,
				(ConfigEntryBase)ConfigEnableBulkTransferBoost,
				(ConfigEntryBase)ConfigBulkTransferBudgetPercent,
				(ConfigEntryBase)ConfigEnableServerOwnership,
				(ConfigEntryBase)ConfigEnableServerOwnershipSelective
			};
			foreach (ConfigEntryBase val in array)
			{
				EventInfo eventInfo = ((object)val).GetType().GetEvent("SettingChanged");
				if (eventInfo != null)
				{
					EventHandler handler = delegate(object sender, EventArgs __)
					{
						//IL_0027: Unknown result type (might be due to invalid IL or missing references)
						//IL_002d: Expected O, but got Unknown
						string text = (((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) ? "SERVER" : "CLIENT");
						ConfigEntryBase val2 = (ConfigEntryBase)sender;
						LoggerOptions.LogInfo($"[{text}] Config changed: {val2.Definition.Section} → {val2.Definition.Key} = {val2.BoxedValue}");
					};
					eventInfo.AddEventHandler(val, handler);
				}
			}
			if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
			{
				ConfigEnableServerAuthority.Value = false;
			}
		}
	}
	public enum LogLevel
	{
		[Description("Errors/Warnings only")]
		Warning,
		[Description("Errors/Warnings/Messages [default]")]
		Message,
		[Description("Everything including Info")]
		Info
	}
	public enum UpdateRateOptions
	{
		[Description("150% - 30 network sends/sec [smoother, more bandwidth]")]
		_150,
		[Description("100% - 20 network sends/sec [default, vanilla]")]
		_100,
		[Description("75% - 15 network sends/sec")]
		_75,
		[Description("50% - 10 network sends/sec")]
		_50
	}
	public enum SendRateMinOptions
	{
		[Description("1024 KB/s | 8 Mbit/s")]
		_1024KB,
		[Description("768 KB/s | 6 Mbit/s")]
		_768KB,
		[Description("512 KB/s | 4 Mbit/s")]
		_512KB,
		[Description("256 KB/s | 2 Mbit/s [default]")]
		_256KB,
		[Description("150 KB/s | 1.2 Mbit/s [vanilla]")]
		_150KB
	}
	public enum SendRateMaxOptions
	{
		[Description("8192 KB/s | 64 Mbit/s")]
		_8192KB,
		[Description("4096 KB/s | 32 Mbit/s")]
		_4096KB,
		[Description("2048 KB/s | 16 Mbit/s")]
		_2048KB,
		[Description("1024 KB/s | 8 Mbit/s")]
		_1024KB,
		[Description("768 KB/s | 6 Mbit/s")]
		_768KB,
		[Description("512 KB/s | 4 Mbit/s [default]")]
		_512KB,
		[Description("256 KB/s | 2 Mbit/s")]
		_256KB,
		[Description("150 KB/s | 1.2 Mbit/s [vanilla]")]
		_150KB
	}
	public enum QueueSizeOptions
	{
		[Description("80 KB")]
		_80KB,
		[Description("64 KB")]
		_64KB,
		[Description("48 KB")]
		_48KB,
		[Description("32 KB [default]")]
		_32KB,
		[Description("Vanilla (~10 KB)")]
		_vanilla
	}
	public enum ForceCrossplayOptions
	{
		[Description("Vanilla behaviour - respect -crossplay flag [default]")]
		vanilla,
		[Description("Force crossplay ENABLED (use PlayFab backend)")]
		playfab,
		[Description("Force crossplay DISABLED (use Steamworks backend)")]
		steamworks
	}
	[HarmonyPatch]
	public static class AILODPatches
	{
		private const float PerInstanceHashJitterMultiplier = 0.001f;

		[HarmonyPatch(typeof(Character), "CustomFixedUpdate")]
		[HarmonyPrefix]
		public static bool CustomFixedUpdate_Prefix(Character __instance, float dt)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (!IsAILODActiveOnDedicatedServer())
			{
				return true;
			}
			if ((FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling == null || FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling.Value) && !SendCongestion.AnyPeerCongested())
			{
				return true;
			}
			ServerStatusDiagnostics.s_aiLod_examined++;
			if (__instance.IsPlayer() || __instance.IsTamed())
			{
				ServerStatusDiagnostics.s_aiLod_playerOrTamed++;
				return true;
			}
			int peerCount;
			float num = ComputeDistanceToNearestPeer(((Component)__instance).transform.position, out peerCount);
			ServerStatusDiagnostics.s_aiLod_peersLastSeen = peerCount;
			UpdateNearestDistanceObservedRange(num);
			float value = FiresGhettoNetworkMod.ConfigAILODNearDistance.Value;
			float value2 = FiresGhettoNetworkMod.ConfigAILODFarDistance.Value;
			if (num <= value)
			{
				ServerStatusDiagnostics.s_aiLod_decidedNear++;
				return true;
			}
			if (num > value2)
			{
				return DecideFarBandTickOrSkip(__instance, dt);
			}
			ServerStatusDiagnostics.s_aiLod_decidedMidBand++;
			return true;
		}

		private static bool IsAILODActiveOnDedicatedServer()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated())
			{
				return FiresGhettoNetworkMod.ConfigEnableAILOD.Value;
			}
			return false;
		}

		private static float ComputeDistanceToNearestPeer(Vector3 origin, out int peerCount)
		{
			//IL_002d: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			float num = float.MaxValue;
			peerCount = 0;
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null)
				{
					peerCount++;
					Vector3 refPos = peer.GetRefPos();
					float num2 = origin.x - refPos.x;
					float num3 = origin.y - refPos.y;
					float num4 = origin.z - refPos.z;
					float num5 = num2 * num2 + num3 * num3 + num4 * num4;
					if (num5 < num)
					{
						num = num5;
					}
				}
			}
			if (!(num < float.MaxValue))
			{
				return float.MaxValue;
			}
			return Mathf.Sqrt(num);
		}

		private static void UpdateNearestDistanceObservedRange(float nearestDist)
		{
			if (nearestDist < ServerStatusDiagnostics.s_aiLod_minNearestDist)
			{
				ServerStatusDiagnostics.s_aiLod_minNearestDist = nearestDist;
			}
			if (nearestDist > ServerStatusDiagnostics.s_aiLod_maxNearestDist && nearestDist < float.MaxValue)
			{
				ServerStatusDiagnostics.s_aiLod_maxNearestDist = nearestDist;
			}
		}

		private static bool DecideFarBandTickOrSkip(Character mob, float dt)
		{
			float num = 1f / FiresGhettoNetworkMod.ConfigAILODThrottleFactor.Value;
			if ((Time.time + (float)((object)mob).GetHashCode() * 0.001f) % num > dt)
			{
				ServerStatusDiagnostics.s_aiLod_decidedFarSkipped++;
				return false;
			}
			ServerStatusDiagnostics.s_aiLod_decidedFarRan++;
			return true;
		}
	}
	[HarmonyPatch]
	public static class BigZdoDiagnostic
	{
		private struct BucketSnapshot
		{
			public int Floats;

			public int Vector3s;

			public int Quaternions;

			public int Ints;

			public int Longs;

			public int Strings;

			public int ByteArrays;

			public bool AnyExceeds(int threshold)
			{
				if (Floats <= threshold && Vector3s <= threshold && Quaternions <= threshold && Ints <= threshold && Longs <= threshold && Strings <= threshold)
				{
					return ByteArrays > threshold;
				}
				return true;
			}

			public (string name, int count)[] AsTuples()
			{
				return new(string, int)[7]
				{
					("float", Floats),
					("Vector3", Vector3s),
					("Quaternion", Quaternions),
					("int", Ints),
					("long", Longs),
					("string", Strings),
					("byte[]", ByteArrays)
				};
			}
		}

		private const int VanillaWarningThreshold = 100;

		private const int WireFormatByteCountCeiling = 255;

		private static bool s_bulkSaveInProgress;

		[HarmonyPatch(typeof(ZDOMan), "SaveChunk")]
		[HarmonyPrefix]
		public static void ZDOMan_SaveChunk_Prefix()
		{
			s_bulkSaveInProgress = true;
		}

		[HarmonyPatch(typeof(ZDOMan), "SaveChunk")]
		[HarmonyFinalizer]
		public static void ZDOMan_SaveChunk_Finalizer()
		{
			s_bulkSaveInProgress = false;
		}

		[HarmonyPatch(typeof(ZDO), "Save")]
		[HarmonyPrefix]
		public static void ZDO_Save_Prefix(ZDO __instance)
		{
			if (!s_bulkSaveInProgress && __instance != null && LargeZdoDiagnosticIsEnabled())
			{
				TryReportOversizedBuckets(__instance, "Save", canTruncateOnWire: false, SnapshotSaveBuckets);
			}
		}

		[HarmonyPatch(typeof(ZDO), "Serialize")]
		[HarmonyPrefix]
		public static void ZDO_Serialize_Prefix(ZDO __instance)
		{
			if (__instance != null && LargeZdoDiagnosticIsEnabled())
			{
				TryReportOversizedBuckets(__instance, "Serialize", canTruncateOnWire: true, SnapshotLiveBuckets);
			}
		}

		private static bool LargeZdoDiagnosticIsEnabled()
		{
			return FiresGhettoNetworkMod.ConfigEnableLargeZdoDiagnostics?.Value ?? false;
		}

		private static void TryReportOversizedBuckets(ZDO zdo, string sourceLabel, bool canTruncateOnWire, Func<ZDOID, BucketSnapshot> snapshotter)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				BucketSnapshot bucketSnapshot = snapshotter(zdo.m_uid);
				if (bucketSnapshot.AnyExceeds(100))
				{
					string context = FormatZdoContext(zdo, sourceLabel);
					(string, int)[] array = bucketSnapshot.AsTuples();
					for (int i = 0; i < array.Length; i++)
					{
						(string, int) tuple = array[i];
						LogIfOverThreshold(context, tuple.Item1, tuple.Item2, canTruncateOnWire);
					}
				}
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("[BigZdoDiag] " + sourceLabel + " prefix threw: " + ex.Message);
			}
		}

		private static BucketSnapshot SnapshotSaveBuckets(ZDOID uid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<int, float>> floats = default(List<KeyValuePair<int, float>>);
			List<KeyValuePair<int, Vector3>> vec3s = default(List<KeyValuePair<int, Vector3>>);
			List<KeyValuePair<int, Quaternion>> quats = default(List<KeyValuePair<int, Quaternion>>);
			List<KeyValuePair<int, int>> ints = default(List<KeyValuePair<int, int>>);
			List<KeyValuePair<int, long>> longs = default(List<KeyValuePair<int, long>>);
			List<KeyValuePair<int, string>> strings = default(List<KeyValuePair<int, string>>);
			List<KeyValuePair<int, byte[]>> byteArrays = default(List<KeyValuePair<int, byte[]>>);
			ZDOConnectionHashData val = default(ZDOConnectionHashData);
			ZDOExtraData.GetSaveData(uid, ref floats, ref vec3s, ref quats, ref ints, ref longs, ref strings, ref byteArrays, ref val);
			return Count(floats, vec3s, quats, ints, longs, strings, byteArrays);
		}

		private static BucketSnapshot SnapshotLiveBuckets(ZDOID uid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<int, float>> floats = default(List<KeyValuePair<int, float>>);
			List<KeyValuePair<int, Vector3>> vec3s = default(List<KeyValuePair<int, Vector3>>);
			List<KeyValuePair<int, Quaternion>> quats = default(List<KeyValuePair<int, Quaternion>>);
			List<KeyValuePair<int, int>> ints = default(List<KeyValuePair<int, int>>);
			List<KeyValuePair<int, long>> longs = default(List<KeyValuePair<int, long>>);
			List<KeyValuePair<int, string>> strings = default(List<KeyValuePair<int, string>>);
			List<KeyValuePair<int, byte[]>> byteArrays = default(List<KeyValuePair<int, byte[]>>);
			ZDOConnection val = default(ZDOConnection);
			ZDOExtraData.GetData(uid, ref floats, ref vec3s, ref quats, ref ints, ref longs, ref strings, ref byteArrays, ref val);
			return Count(floats, vec3s, quats, ints, longs, strings, byteArrays);
		}

		private static BucketSnapshot Count<TF, TV, TQ, TI, TL, TS, TB>(List<TF> floats, List<TV> vec3s, List<TQ> quats, List<TI> ints, List<TL> longs, List<TS> strings, List<TB> byteArrays)
		{
			return new BucketSnapshot
			{
				Floats = (floats?.Count ?? 0),
				Vector3s = (vec3s?.Count ?? 0),
				Quaternions = (quats?.Count ?? 0),
				Ints = (ints?.Count ?? 0),
				Longs = (longs?.Count ?? 0),
				Strings = (strings?.Count ?? 0),
				ByteArrays = (byteArrays?.Count ?? 0)
			};
		}

		private static string FormatZdoContext(ZDO zdo, string sourceLabel)
		{
			//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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = zdo.GetPosition();
			string text = ResolvePrefabNameFromHash(zdo.GetPrefab());
			return $"src={sourceLabel} uid={zdo.m_uid} prefab='{text}'({zdo.GetPrefab()}) " + $"pos=({position.x:F1},{position.z:F1},{position.y:F1}) owner={zdo.GetOwner()}";
		}

		private static void LogIfOverThreshold(string context, string bucketName, int count, bool canTruncateOnWire)
		{
			if (count > 100)
			{
				if (canTruncateOnWire && count > 255)
				{
					LoggerOptions.LogWarning($"[BigZdoDiag TRUNCATING] {context} bucket={bucketName,-10} count={count} " + $"(>{255} — receiver will read wrapped count, payload corrupt)");
				}
				else
				{
					LoggerOptions.LogWarning($"[BigZdoDiag] {context} bucket={bucketName,-10} count={count}");
				}
			}
		}

		private static string ResolvePrefabNameFromHash(int prefabHash)
		{
			try
			{
				ZNetScene instance = ZNetScene.instance;
				GameObject val = ((instance != null) ? instance.GetPrefab(prefabHash) : null);
				if ((Object)(object)val != (Object)null)
				{
					return ((Object)val).name;
				}
			}
			catch
			{
			}
			return "<unresolved>";
		}
	}
	[HarmonyPatch]
	public static class BulkTransferGatePatches
	{
		private readonly struct Target
		{
			public readonly string TypeName;

			public readonly string GateMethodName;

			public readonly int VanillaConstant;

			public Target(string typeName, string gateMethodName, int vanillaConstant)
			{
				TypeName = typeName;
				GateMethodName = gateMethodName;
				VanillaConstant = vanillaConstant;
			}
		}

		private static readonly Target[] Targets = new Target[2]
		{
			new Target("ServerSync.ConfigSync", "GetSendQueueSize", 20000),
			new Target("ServerCharacters.Shared", "GetSendQueueSize", 20000)
		};

		public static float DisconnectTimeoutSeconds = 86400f;

		private static readonly FieldInfo TimeoutField = AccessTools.Field(typeof(BulkTransferGatePatches), "DisconnectTimeoutSeconds");

		private static FieldInfo s_jotunnTimeoutField;

		private static bool s_jotunnResolved;

		private static int s_currentVanillaConstant;

		private static int s_lastTimeoutSitesDisarmed;

		private static bool s_applied;

		private static int s_serverSyncCopyCount = 1;

		private static int s_budgetedGate;

		private const int ThirdPartyGateFloorBytes = 20000;

		[HarmonyPatch(typeof(ZNet), "Start")]
		[HarmonyPostfix]
		private static void ApplyOnZNetStart()
		{
			if (s_applied)
			{
				return;
			}
			s_applied = true;
			ConfigEntry<bool> configEnableBulkTransferBoost = FiresGhettoNetworkMod.ConfigEnableBulkTransferBoost;
			if (configEnableBulkTransferBoost != null && !configEnableBulkTransferBoost.Value)
			{
				LoggerOptions.LogMessage("Bulk-transfer gate scan skipped — ConfigEnableBulkTransferBoost = false.");
				return;
			}
			try
			{
				ApplyAll(FiresGhettoNetworkMod.Harmony);
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("Bulk-transfer gate scan threw: " + ex.Message);
			}
		}

		private static void ApplyAll(Harmony harmony)
		{
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Expected O, but got Unknown
			int targetQueueSize = GetTargetQueueSize();
			s_serverSyncCopyCount = CountServerSyncCopies();
			s_budgetedGate = GetBudgetedGate();
			LoggerOptions.LogInfo($"Bulk-transfer gate budget: {s_serverSyncCopyCount} ServerSync.ConfigSync copy/ies, " + $"Steam buffer ceiling {GetEffectiveSendBufferCeilingBytes() / 1024}KB, " + $"{FiresGhettoNetworkMod.ConfigBulkTransferBudgetPercent?.Value ?? 40}% budget → per-mod gate " + $"{s_budgetedGate} bytes (Queue Size target {targetQueueSize}).");
			if (s_budgetedGate <= 20000)
			{
				LoggerOptions.LogInfo("Bulk-transfer budget floored the per-mod gate at the vanilla 20 KB (many ServerSync mods on the current Steam send buffer), so the gate is NOT raised — but the 30s self-disconnect is disarmed regardless, so slow/high-ping peers are never dropped. Install FiresSteamworksPatcher or lower 'Queue Size' only if you also want the gate raise.");
			}
			int num = 0;
			int num2 = 0;
			Target[] targets = Targets;
			for (int i = 0; i < targets.Length; i++)
			{
				Target target = targets[i];
				bool flag = s_budgetedGate > target.VanillaConstant;
				int num3 = 0;
				int num4 = 0;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					Type type = null;
					try
					{
						type = assembly.GetType(target.TypeName, throwOnError: false);
					}
					catch
					{
						continue;
					}
					if (type == null)
					{
						continue;
					}
					num3++;
					foreach (Type item in WalkAllNestedTypes(type))
					{
						MethodInfo[] methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (MethodInfo methodInfo in methods)
						{
							if (!methodInfo.IsAbstract && !methodInfo.ContainsGenericParameters && IsGateLoop(methodInfo, target.GateMethodName))
							{
								try
								{
									s_currentVanillaConstant = target.VanillaConstant;
									s_lastTimeoutSitesDisarmed = 0;
									harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BulkTransferGatePatches), "RaiseGateAndDisarmTimeout", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
									num4++;
									LoggerOptions.LogInfo("Bulk-transfer patched: " + assembly.GetName().Name + " → " + item.FullName + "." + methodInfo.Name + " " + $"(gate {target.VanillaConstant} → {(flag ? s_budgetedGate : target.VanillaConstant)} bytes, " + $"30s self-disconnect disarmed at {s_lastTimeoutSitesDisarmed} site(s)).");
								}
								catch (Exception ex)
								{
									LoggerOptions.LogWarning("Failed to patch bulk-transfer gate at " + assembly.GetName().Name + "." + item.FullName + "." + methodInfo.Name + ": " + ex.Message);
								}
							}
						}
					}
				}
				num += num3;
				num2 += num4;
				LoggerOptions.LogInfo($"Bulk-transfer scan for {target.TypeName}: {num3} copies found, {num4} gate sites patched.");
			}
			LoggerOptions.LogMessage($"Bulk-transfer scan complete: {num} third-party copies found across loaded assemblies, " + $"{num2} gate sites patched (per-mod gate {s_budgetedGate} bytes, 30s self-disconnect disarmed).");
			ApplyJotunnTimeout();
			LoggerOptions.LogInfo((s_jotunnTimeoutField != null) ? $"Jotunn CustomRPC self-disconnect disarmed (Timeout -> {DisconnectTimeoutSeconds}s)." : "Jotunn CustomRPC not present — no Jotunn timeout to disarm.");
		}

		public static void ApplyJotunnTimeout()
		{
			try
			{
				if (!s_jotunnResolved)
				{
					s_jotunnResolved = true;
					if (IsAssemblyLoaded("Jotunn"))
					{
						Type type = AccessTools.TypeByName("Jotunn.Entities.CustomRPC");
						if (type != null)
						{
							s_jotunnTimeoutField = AccessTools.Field(type, "Timeout");
						}
					}
				}
				s_jotunnTimeoutField?.SetValue(null, DisconnectTimeoutSeconds);
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("Jotunn CustomRPC.Timeout disarm failed: " + ex.Message);
			}
		}

		private static bool IsAssemblyLoaded(string assemblyName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				if (string.Equals(assemblies[i].GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<Type> WalkAllNestedTypes(Type root)
		{
			yield return root;
			Type[] nestedTypes;
			try
			{
				nestedTypes = root.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
			}
			catch
			{
				yield break;
			}
			Type[] array = nestedTypes;
			foreach (Type root2 in array)
			{
				foreach (Type item in WalkAllNestedTypes(root2))
				{
					yield return item;
				}
			}
		}

		private static bool IsGateLoop(MethodBase m, string gateMethodName)
		{
			List<CodeInstruction> currentInstructions;
			try
			{
				currentInstructions = PatchProcessor.GetCurrentInstructions(m, int.MaxValue, (ILGenerator)null);
			}
			catch
			{
				return false;
			}
			bool flag = false;
			bool flag2 = false;
			foreach (CodeInstruction item in currentInstructions)
			{
				if ((item.opcode == OpCodes.Call || item.opcode == OpCodes.Callvirt) && item.operand is MethodInfo methodInfo)
				{
					if (methodInfo.Name == gateMethodName)
					{
						flag = true;
					}
					else if (methodInfo.Name == "Disconnect")
					{
						flag2 = true;
					}
					if (flag && flag2)
					{
						return true;
					}
				}
			}
			return false;
		}

		public static IEnumerable<CodeInstruction> RaiseGateAndDisarmTimeout(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Expected O, but got Unknown
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			int num = s_currentVanillaConstant;
			int num2 = s_budgetedGate;
			int num3 = 0;
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			List<int> list2 = new List<int>();
			for (int i = 0; i < list.Count; i++)
			{
				if ((list[i].opcode == OpCodes.Call || list[i].opcode == OpCodes.Callvirt) && list[i].operand is MethodInfo { Name: "GetSendQueueSize" })
				{
					list2.Add(i);
				}
			}
			if (list2.Count == 0)
			{
				s_lastTimeoutSitesDisarmed = 0;
				return list;
			}
			for (int j = 0; j < list.Count; j++)
			{
				if (num2 > num && (list[j].opcode == OpCodes.Ldc_I4 || list[j].opcode == OpCodes.Ldc_I4_S) && list[j].operand is int num4 && num4 == num && NearAnyCall(list2, j))
				{
					list[j] = new CodeInstruction(OpCodes.Ldc_I4, (object)num2);
				}
				else if (list[j].opcode == OpCodes.Ldc_R4 && list[j].operand is float num5 && num5 == 30f)
				{
					list[j] = new CodeInstruction(OpCodes.Ldsfld, (object)TimeoutField);
					num3++;
				}
				else if (list[j].opcode == OpCodes.Ldc_R8 && list[j].operand is double num6 && num6 == 30.0)
				{
					list[j] = new CodeInstruction(OpCodes.Ldc_R8, (object)86400.0);
					num3++;
				}
			}
			s_lastTimeoutSitesDisarmed = num3;
			return list;
		}

		private static bool NearAnyCall(List<int> callIdx, int i)
		{
			foreach (int item in callIdx)
			{
				if (Math.Abs(item - i) <= 8)
				{
					return true;
				}
			}
			return false;
		}

		private static int CountServerSyncCopies()
		{
			int num = 0;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					if (assembly.GetType("ServerSync.ConfigSync", throwOnError: false) != null)
					{
						num++;
					}
				}
				catch
				{
				}
			}
			return Math.Max(1, num);
		}

		private static int GetEffectiveSendBufferCeilingBytes()
		{
			try
			{
				if (NetworkingRatesGroup.IsSendBufferRaiseApplied())
				{
					return Math.Max(524288, EffectiveConfig.SteamSendBufferBytes());
				}
			}
			catch
			{
			}
			return 524288;
		}

		private static int GetBudgetedGate()
		{
			int targetQueueSize = GetTargetQueueSize();
			int num = FiresGhettoNetworkMod.ConfigBulkTransferBudgetPercent?.Value ?? 40;
			int val = (int)((long)GetEffectiveSendBufferCeilingBytes() * (long)num / 100 / Math.Max(1, s_serverSyncCopyCount));
			return Math.Max(20000, Math.Min(targetQueueSize, val));
		}

		private static int GetTargetQueueSize()
		{
			return EffectiveConfig.QueueSizeBytes(20000);
		}
	}
	[HarmonyPatch]
	public static class ClientCleanupThrottle
	{
		private static readonly HashSet<ZNetView> _pendingDestroySet = new HashSet<ZNetView>();

		private static readonly Queue<ZNetView> _pendingLooseQueue = new Queue<ZNetView>();

		private static readonly Queue<ZNetView> _pendingSolidQueue = new Queue<ZNetView>();

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

		private static readonly List<ZNetView> _destroyNowScratch = new List<ZNetView>(64);

		private static readonly HashSet<ZDO> _inAreaThisFrame = new HashSet<ZDO>();

		private static int _sessionMoversDestroyed;

		private static int _sessionStaticDeferred;

		private static float _nextTeardownLogTime;

		private const float TeardownLogIntervalSec = 5f;

		private const int MoverNone = 0;

		private const int MoverCharacter = 1;

		private const int MoverRigidbody = 2;

		[HarmonyPatch(typeof(ZNetScene), "RemoveObjects")]
		[HarmonyPrefix]
		public static bool RemoveObjects_ClientThrottle_Prefix(ZNetScene __instance, List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return false;
			}
			if (ValheimCommunityPatchCompat.SchedulesObjectRemoval)
			{
				return true;
			}
			if (ServerClientUtils.ZNetIsDedicated())
			{
				return true;
			}
			int num = FiresGhettoNetworkMod.ConfigClientMaxDestroysPerFrame?.Value ?? 0;
			if (num <= 0)
			{
				return true;
			}
			RebuildInAreaSet(currentNearObjects, currentDistantObjects);
			ScanInstances(__instance);
			DestroyUpToBudget(__instance, num);
			return false;
		}

		[HarmonyPatch(typeof(ZNetScene), "Shutdown")]
		[HarmonyPostfix]
		public static void ZNetScene_Shutdown_ClearPendingState()
		{
			_pendingDestroySet.Clear();
			_pendingLooseQueue.Clear();
			_pendingSolidQueue.Clear();
			_zdosToUnregisterScratch.Clear();
			_destroyNowScratch.Clear();
			_inAreaThisFrame.Clear();
			_sessionMoversDestroyed = 0;
			_sessionStaticDeferred = 0;
		}

		private static void ReportTeardown(int moverCharacters, int moverBodies, int queuedLoose, int queuedSolid)
		{
			int num = moverCharacters + moverBodies;
			int num2 = queuedLoose + queuedSolid;
			if (num != 0 || num2 != 0)
			{
				_sessionMoversDestroyed += num;
				_sessionStaticDeferred += num2;
				if (num != 0 && !(Time.time < _nextTeardownLogTime))
				{
					_nextTeardownLogTime = Time.time + 5f;
					LoggerOptions.LogMessage($"[CleanupThrottle] teardown: {num} mover(s) destroyed IN-FRAME " + $"({moverCharacters} Character, {moverBodies} rigidbody) — these would previously have been queued " + "and left simulating while their surroundings were destroyed. " + $"{num2} static deferred ({queuedSolid} solid last). " + $"Session totals: {_sessionMoversDestroyed} movers / {_sessionStaticDeferred} static.");
				}
			}
		}

		private static void RebuildInAreaSet(List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects)
		{
			_inAreaThisFrame.Clear();
			AddAllToInAreaSet(currentNearObjects);
			AddAllToInAreaSet(currentDistantObjects);
		}

		private static void AddAllToInAreaSet(List<ZDO> zdos)
		{
			if (zdos == null)
			{
				return;
			}
			for (int i = 0; i < zdos.Count; i++)
			{
				ZDO val = zdos[i];
				if (val != null)
				{
					_inAreaThisFrame.Add(val);
				}
			}
		}

		private static int ClassifyMover(ZNetView view)
		{
			if ((Object)(object)((Component)view).GetComponent<Character>() != (Object)null)
			{
				return 1;
			}
			Rigidbody component = ((Component)view).GetComponent<Rigidbody>();
			if (!((Object)(object)component != (Object)null) || component.isKinematic)
			{
				return 0;
			}
			return 2;
		}

		private static void ScanInstances(ZNetScene scene)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Invalid comparison between Unknown and I4
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Invalid comparison between Unknown and I4
			_destroyNowScratch.Clear();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			foreach (KeyValuePair<ZDO, ZNetView> instance in scene.m_instances)
			{
				ZNetView value = instance.Value;
				if ((Object)(object)value == (Object)null)
				{
					continue;
				}
				ZDO zDO = value.GetZDO();
				if (zDO == null || _inAreaThisFrame.Contains(zDO) || !_pendingDestroySet.Add(value))
				{
					continue;
				}
				int num5 = ClassifyMover(value);
				if (num5 != 0)
				{
					_destroyNowScratch.Add(value);
					if (num5 == 1)
					{
						num++;
					}
					else
					{
						num2++;
					}
				}
				else if ((int)zDO.Type == 2 || (int)zDO.Type == 3)
				{
					_pendingSolidQueue.Enqueue(value);
					num4++;
				}
				else
				{
					_pendingLooseQueue.Enqueue(value);
					num3++;
				}
			}
			ReportTeardown(num, num2, num3, num4);
			_zdosToUnregisterScratch.Clear();
			for (int i = 0; i < _destroyNowScratch.Count; i++)
			{
				ZNetView val = _destroyNowScratch[i];
				_pendingDestroySet.Remove(val);
				DestroyInstance(val);
			}
			_destroyNowScratch.Clear();
			UnregisterDestroyedFromScene(scene);
		}

		private static void DestroyUpToBudget(ZNetScene scene, int budget)
		{
			_zdosToUnregisterScratch.Clear();
			int num = 0;
			ZNetView view;
			while (num < budget && TryDequeueNextPending(out view))
			{
				if (!((Object)(object)view == (Object)null))
				{
					ZDO zDO = view.GetZDO();
					if (zDO != null && !_inAreaThisFrame.Contains(zDO))
					{
						DestroyInstance(view);
						num++;
					}
				}
			}
			UnregisterDestroyedFromScene(scene);
		}

		private static bool TryDequeueNextPending(out ZNetView view)
		{
			if (_pendingLooseQueue.Count > 0)
			{
				view = _pendingLooseQueue.Dequeue();
				_pendingDestroySet.Remove(view);
				return true;
			}
			if (_pendingSolidQueue.Count > 0)
			{
				view = _pendingSolidQueue.Dequeue();
				_pendingDestroySet.Remove(view);
				return true;
			}
			view = null;
			return false;
		}

		private static void DestroyInstance(ZNetView view)
		{
			if ((Object)(object)view == (Object)null)
			{
				return;
			}
			ZDO zDO = view.GetZDO();
			if (zDO != null)
			{
				view.ResetZDO();
				_zdosToUnregisterScratch.Add(zDO);
				if (!zDO.Persistent && zDO.IsOwner())
				{
					ZDOMan.instance.DestroyZDO(zDO);
				}
				Object.Destroy((Object)(object)((Component)view).gameObject);
			}
		}

		private static void UnregisterDestroyedFromScene(ZNetScene scene)
		{
			for (int i = 0; i < _zdosToUnregisterScratch.Count; i++)
			{
				scene.m_instances.Remove(_zdosToUnregisterScratch[i]);
			}
			_zdosToUnregisterScratch.Clear();
		}
	}
	public static class SolidSurface
	{
		public const string DefaultLayerCsv = "Default,static_solid,Default_small,piece,terrain,vehicle";

		private static int _cachedMask;

		private static string _cachedLayerCsv;

		public static int Mask()
		{
			string text = FiresGhettoNetworkMod.ConfigDediFellOutRescueLayers?.Value ?? "Default,static_solid,Default_small,piece,terrain,vehicle";
			if (_cachedLayerCsv == text && _cachedMask != 0)
			{
				return _cachedMask;
			}
			int num = ParseLayerMask(text);
			if (num == 0)
			{
				LoggerOptions.LogWarning("[SolidSurface] '" + text + "' matched no layers in this build; falling back to terrain only.");
				num = LayerMask.GetMask(new string[1] { "terrain" });
			}
			_cachedMask = num;
			_cachedLayerCsv = text;
			return num;
		}

		private static int ParseLayerMask(string layerCsv)
		{
			List<string> list = new List<string>();
			string[] array = layerCsv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i]?.Trim();
				if (!string.IsNullOrEmpty(text))
				{
					list.Add(text);
				}
			}
			if (list.Count <= 0)
			{
				return 0;
			}
			return LayerMask.GetMask(list.ToArray());
		}
	}
	[HarmonyPatch]
	public static class GroundSnapPatches
	{
		private const float ProbeStartHeightAboveObject = 2f;

		private const float ProbeMaxDistance = 8f;

		[HarmonyPatch(typeof(Character), "UnderWorldCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Character_UnderWorldCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "Character.UnderWorldCheck");
		}

		[HarmonyPatch(typeof(TombStone), "PositionCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> TombStone_PositionCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "TombStone.PositionCheck");
		}

		[HarmonyPatch(typeof(ItemDrop), "TerrainCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> ItemDrop_TerrainCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "ItemDrop.TerrainCheck");
		}

		[HarmonyPatch(typeof(Floating), "TerrainCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Floating_TerrainCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "Floating.TerrainCheck");
		}

		private static IEnumerable<CodeInstruction> RedirectGroundHeightToSupportProbe(IEnumerable<CodeInstruction> instructions, string patchedMethod)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			MethodInfo methodInfo = AccessTools.Method(typeof(GroundSnapPatches), "SupportOrGroundHeight", (Type[])null, (Type[])null);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode != OpCodes.Call) || !(list[i].opcode != OpCodes.Callvirt))
				{
					MethodInfo methodInfo2 = list[i].operand as MethodInfo;
					if (!(methodInfo2 == null) && !(methodInfo2.Name != "GetGroundHeight") && methodInfo2.GetParameters().Length == 1)
					{
						list[i] = new CodeInstruction(OpCodes.Call, (object)methodInfo);
						num++;
					}
				}
			}
			if (num == 0)
			{
				LoggerOptions.LogWarning("[GroundSnap] " + patchedMethod + " has no GetGroundHeight call to redirect; vanilla behaviour stands there.");
			}
			return list;
		}

		public static float SupportOrGroundHeight(ZoneSystem zoneSystem, Vector3 point)
		{
			//IL_0001: 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_0028: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			float groundHeight = zoneSystem.GetGroundHeight(point);
			ConfigEntry<bool> configFixGroundSnapThroughFloors = FiresGhettoNetworkMod.ConfigFixGroundSnapThroughFloors;
			if (configFixGroundSnapThroughFloors != null && !configFixGroundSnapThroughFloors.Value)
			{
				return groundHeight;
			}
			if (point.y >= groundHeight)
			{
				return groundHeight;
			}
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(point + Vector3.up * 2f, Vector3.down, ref val, 8f, SolidSurface.Mask(), (QueryTriggerInteraction)1))
			{
				return ((RaycastHit)(ref val)).point.y;
			}
			return groundHeight;
		}
	}
	[HarmonyPatch]
	public static class OwnershipHandoffPatches
	{
		private static readonly FieldRef<ZSyncTransform, bool> _wasOwnerRef = AccessTools.FieldRefAccess<ZSyncTransform, bool>("m_wasOwner");

		private const float MinDriftToSnap = 0.05f;

		private const float DriftReportMeters = 2f;

		private const float ReportIntervalSec = 30f;

		private static float _nextReportTime;

		[HarmonyPatch(typeof(ZSyncTransform), "CustomFixedUpdate")]
		[HarmonyPrefix]
		public static void ZSyncTransform_CustomFixedUpdate_PreSnapOnOwnershipGain(ZSyncTransform __instance, ZNetView ___m_nview, Rigidbody ___m_body)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: 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)
			if ((Object)(object)__instance == (Object)null || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid())
			{
				return;
			}
			ZDO zDO = ___m_nview.GetZDO();
			if (zDO == null || !zDO.IsOwner() || _wasOwnerRef.Invoke(__instance))
			{
				return;
			}
			Character component = ((Component)__instance).GetComponent<Character>();
			if ((Object)(object)component == (Object)null || component is Player)
			{
				return;
			}
			Vector3 position = zDO.GetPosition();
			float num = Vector3.Distance(((Component)__instance).transform.position, position);
			if (!(num < 0.05f))
			{
				bool flag = false;
				if (__instance.m_syncPosition)
				{
					((Component)__instance).transform.position = position;
					flag = true;
				}
				if (__instance.m_syncRotation)
				{
					((Component)__instance).transform.rotation = zDO.GetRotation();
					flag = true;
				}
				if (flag && (Object)(object)___m_body != (Object)null)
				{
					Physics.SyncTransforms();
				}
				if (num >= 2f && Time.time >= _nextReportTime)
				{
					_nextReportTime = Time.time + 30f;
					LoggerOptions.LogInfo($"[OwnershipHandoff] '{((Object)component).name}' was {num:F1}m from its ZDO position when this peer took ownership; " + "pre-snapped before the physics step (vanilla would have simulated there first). " + $"Further reports suppressed for {30f:F0}s.");
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZDO), "InternalSetPosition")]
	public static class TeleportGhostFix
	{
		private enum Mode
		{
			StoodDown,
			Active,
			DeferToCompetitor
		}

		private static class PeerQueues
		{
			private static FieldRef<ZDOMan, List<ZDOPeer>> _peers;

			[MethodImpl(MethodImplOptions.NoInlining)]
			public static int Count(ZDOMan man)
			{
				return Peers(man)?.Count ?? (-1);
			}

			[MethodImpl(MethodImplO