Decompiled source of MySix Core v1.0.2

MySix.Core.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MySix
{
	internal static class SuiteVersion
	{
		public const string Value = "1.0.2";
	}
}
namespace MySix.Core
{
	public static class Compat
	{
		public static bool TakeOwnership(ZDO zdo)
		{
			if (zdo == null)
			{
				return false;
			}
			zdo.SetOwner(ZDOMan.GetSessionID());
			return zdo.IsOwner();
		}
	}
	internal static class ConfigSync
	{
		private const string RpcRequest = "MySix_ConfigRequest";

		private const string RpcValues = "MySix_ConfigValues";

		private const string RpcSet = "MySix_ConfigSet";

		private const string RpcReply = "MySix_ConfigReply";

		private static bool _registered;

		private static bool _requested;

		internal static void Register()
		{
			if (!_registered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigRequest", (Action<long, ZPackage>)OnRequest);
				ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigValues", (Action<long, ZPackage>)OnValues);
				ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigSet", (Action<long, ZPackage>)OnSet);
				ZRoutedRpc.instance.Register<string>("MySix_ConfigReply", (Action<long, string>)OnReply);
				_registered = true;
			}
		}

		internal static void Reset()
		{
			_registered = false;
			_requested = false;
			Settings.ClearRemoteValues();
		}

		internal static void RequestIfNeeded()
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			if (!_requested && _registered && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
			{
				ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
				if (serverPeer != null)
				{
					_requested = true;
					ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "MySix_ConfigRequest", new object[1] { (object)new ZPackage() });
					CorePlugin.Log.LogInfo((object)"Asked the server for MySix settings");
				}
			}
		}

		private static void OnRequest(long sender, ZPackage _)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				SendValuesTo(sender);
			}
		}

		internal static void BroadcastValues()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			foreach (ZNetPeer item in from p in ZNet.instance.GetPeers()
				where p != null && !p.m_server
				select p)
			{
				SendValuesTo(item.m_uid);
			}
		}

		private static void SendValuesTo(long peerId)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			List<SettingBase> list = Settings.ServerOwned.ToList();
			val.Write(list.Count);
			foreach (SettingBase item in list)
			{
				val.Write(item.FullName);
				val.Write(item.ValueText);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "MySix_ConfigValues", new object[1] { val });
		}

		private static void OnValues(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || pkg == null)
			{
				return;
			}
			int num = pkg.ReadInt();
			int num2 = 0;
			for (int i = 0; i < num; i++)
			{
				string fullName = pkg.ReadString();
				string text = pkg.ReadString();
				SettingBase settingBase = Settings.Find(fullName);
				if (settingBase != null)
				{
					settingBase.ApplyRemoteText(text);
					num2++;
				}
			}
			CorePlugin.Log.LogInfo((object)$"Applied {num2} server setting(s)");
		}

		internal static bool RequestSet(string fullName, string value, out string error)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			error = null;
			if (!_registered || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				error = "not connected to a server";
				return false;
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer == null)
			{
				error = "no server connection";
				return false;
			}
			ZPackage val = new ZPackage();
			val.Write(fullName);
			val.Write(value);
			ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "MySix_ConfigSet", new object[1] { val });
			return true;
		}

		private static void OnSet(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || pkg == null)
			{
				return;
			}
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			if (!IsAdmin(sender))
			{
				CorePlugin.Log.LogWarning((object)$"Refused setting change from non-admin peer {sender}: {text} = {text2}");
				Reply(sender, "You must be a server admin to change MySix settings.");
				return;
			}
			SettingBase settingBase = Settings.Find(text);
			if (settingBase == null)
			{
				Reply(sender, "Unknown setting: " + text);
				return;
			}
			if (settingBase.Scope != Scope.Server)
			{
				Reply(sender, settingBase.FullName + " is a per-player setting; change it locally.");
				return;
			}
			if (!settingBase.TrySetFromText(text2, out var error))
			{
				Reply(sender, "Invalid value for " + settingBase.FullName + ": " + error);
				return;
			}
			CorePlugin.Log.LogInfo((object)$"Admin peer {sender} set {settingBase.FullName} = {settingBase.ValueText}");
			Reply(sender, settingBase.FullName + " = " + settingBase.ValueText);
			BroadcastValues();
		}

		private static bool IsAdmin(long peerId)
		{
			ZNetPeer peer = ZNet.instance.GetPeer(peerId);
			if (peer?.m_socket == null)
			{
				return false;
			}
			return ZNet.instance.IsAdmin(peer.m_socket.GetHostName());
		}

		private static void Reply(long peerId, string message)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "MySix_ConfigReply", new object[1] { message });
		}

		private static void OnReply(long sender, string message)
		{
			CorePlugin.Log.LogInfo((object)("Server: " + message));
			if ((Object)(object)Console.instance != (Object)null)
			{
				((Terminal)Console.instance).AddString("MySix: " + message);
			}
		}
	}
	internal static class ConsoleCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEventFailable <>9__2_0;

			public static ConsoleOptionsFetcher <>9__2_1;

			public static Func<SettingBase, string> <>9__3_0;

			public static Func<SettingBase, string> <>9__5_0;

			public static Func<IGrouping<string, SettingBase>, string> <>9__5_1;

			internal object <RegisterIfNeeded>b__2_0(ConsoleEventArgs args)
			{
				return Run(args);
			}

			internal List<string> <RegisterIfNeeded>b__2_1()
			{
				return Options();
			}

			internal string <Options>b__3_0(SettingBase s)
			{
				return s.ShortName;
			}

			internal string <PrintList>b__5_0(SettingBase s)
			{
				return s.Feature;
			}

			internal string <PrintList>b__5_1(IGrouping<string, SettingBase> g)
			{
				return g.Key;
			}
		}

		private static bool _registered;

		private static readonly FieldRef<bool> TerminalInitialized = AccessTools.StaticFieldRefAccess<bool>(AccessTools.Field(typeof(Terminal), "m_terminalInitialized"));

		internal static void RegisterIfNeeded()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0069: Expected O, but got Unknown
			if (_registered || !TerminalInitialized.Invoke())
			{
				return;
			}
			_registered = true;
			object obj = <>c.<>9__2_0;
			if (obj == null)
			{
				ConsoleEventFailable val = (ConsoleEventArgs args) => Run(args);
				<>c.<>9__2_0 = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__2_1;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => Options();
				<>c.<>9__2_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("mysix", "MySix mods: list settings, or 'get <setting>' / 'set <setting> <value>'", (ConsoleEventFailable)obj, false, false, false, false, true, false, (ConsoleOptionsFetcher)obj2, false, false, false);
			CorePlugin.Log.LogInfo((object)"Console command 'mysix' registered");
		}

		private static List<string> Options()
		{
			List<string> list = new List<string>();
			list.Add("get");
			list.Add("set");
			list.Add("list");
			list.AddRange(Settings.Known.Select((SettingBase s) => s.ShortName));
			return list;
		}

		private static object Run(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "list")
			{
			case "list":
				PrintList(context);
				return true;
			case "get":
			{
				if (args.Length < 3)
				{
					return Print(context, "usage: mysix get <setting>");
				}
				SettingBase settingBase2 = Settings.Find(args[2]);
				if (settingBase2 == null)
				{
					return Print(context, "unknown setting: " + args[2]);
				}
				return Print(context, Describe(settingBase2));
			}
			case "set":
			{
				if (args.Length < 4)
				{
					return Print(context, "usage: mysix set <setting> <value>");
				}
				SettingBase settingBase = Settings.Find(args[2]);
				if (settingBase == null)
				{
					return Print(context, "unknown setting: " + args[2]);
				}
				string text = string.Join(" ", args.Args.Skip(3).ToArray());
				bool flag = (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer();
				if (settingBase.Scope == Scope.Server && flag)
				{
					if (!ConfigSync.RequestSet(settingBase.FullName, text, out var error))
					{
						return Print(context, "could not reach the server: " + error);
					}
					return Print(context, "asked the server to set " + settingBase.FullName + " to " + text);
				}
				if (!settingBase.TrySetFromText(text, out var error2))
				{
					return Print(context, "invalid value for " + settingBase.FullName + ": " + error2);
				}
				if (settingBase.Scope == Scope.Server)
				{
					ConfigSync.BroadcastValues();
				}
				return Print(context, settingBase.FullName + " = " + settingBase.ValueText);
			}
			default:
				return Print(context, "usage: mysix [list | get <setting> | set <setting> <value>]");
			}
		}

		private static void PrintList(Terminal ctx)
		{
			List<SettingBase> list = Settings.Known.ToList();
			if (list.Count == 0)
			{
				Print(ctx, "MySix: no features registered any settings");
				return;
			}
			Print(ctx, string.Format("MySix {0} - {1} setting(s):", "1.0.2", list.Count));
			foreach (IGrouping<string, SettingBase> item in (from s in list
				group s by s.Feature).OrderBy<IGrouping<string, SettingBase>, string>((IGrouping<string, SettingBase> g) => g.Key, StringComparer.OrdinalIgnoreCase))
			{
				Print(ctx, "  " + item.Key);
				foreach (SettingBase item2 in item)
				{
					Print(ctx, "    " + item2.ShortName + " = " + item2.ValueText + (item2.IsRemote ? "  (from server)" : ""));
				}
			}
			Print(ctx, "use 'mysix get <setting>' for details, 'mysix set <setting> <value>' to change one");
		}

		private static string Describe(SettingBase s)
		{
			return s.FullName + " = " + s.ValueText + "  [" + s.TypeHint + ", default " + s.DefaultText + ", " + ((s.Scope == Scope.Server) ? "server-owned" : "per-player") + (s.IsRemote ? ", currently from the server" : "") + "]" + (string.IsNullOrEmpty(s.Description) ? "" : (Environment.NewLine + "   " + s.Description));
		}

		private static bool Print(Terminal ctx, string text)
		{
			if ((Object)(object)ctx != (Object)null)
			{
				ctx.AddString(text);
			}
			else
			{
				CorePlugin.Log.LogInfo((object)text);
			}
			return true;
		}
	}
	public enum Role
	{
		Unknown,
		DedicatedServer,
		Host,
		Client
	}
	[BepInPlugin("com.mysix.core", "MySix Core", "1.0.2")]
	public class CorePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.mysix.core";

		public const string PluginName = "MySix Core";

		public const string PluginVersion = "1.0.2";

		public const int ApiVersion = 1;

		private static bool _readyRaised;

		private ConfigEntry<float> _cycleSeconds;

		private ConfigEntry<int> _maxObjectsPerFrame;

		public static CorePlugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static Role Role { get; private set; }

		public static bool IsServerSide
		{
			get
			{
				if (Role != Role.DedicatedServer)
				{
					return Role == Role.Host;
				}
				return true;
			}
		}

		public static bool IsPlayerSide
		{
			get
			{
				if (Role != Role.Host)
				{
					return Role == Role.Client;
				}
				return true;
			}
		}

		public static bool IsRemoteClient => Role == Role.Client;

		public WorldScanner Scanner { get; } = new WorldScanner();

		public static event Action<Role> Ready;

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_cycleSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Scanner", "CycleSeconds", 600f, "How long one full pass over the world takes on the server. Work is spread evenly across it.");
			_maxObjectsPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("Scanner", "MaxObjectsPerFrame", 2000, "Safety cap on objects inspected in a single frame (only matters if a pass falls behind).");
			Settings.Register<float>("MySix Core", _cycleSeconds, Scope.Server, "Seconds for one full world pass on the server.");
			Settings.Register<int>("MySix Core", _maxObjectsPerFrame, Scope.Server, "Maximum objects inspected per frame.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded (API {2})", "MySix Core", "1.0.2", 1));
		}

		public static bool RequireApi(string featureName, int requiredApiVersion)
		{
			if (requiredApiVersion <= 1)
			{
				return true;
			}
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogError((object)($"{featureName} needs MySix Core API {requiredApiVersion}, but this is Core " + string.Format("{0} (API {1}). Update MySix.Core.dll; {2} is disabled.", "1.0.2", 1, featureName)));
			}
			return false;
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				if (Role != Role.Unknown)
				{
					Role = Role.Unknown;
					_readyRaised = false;
					ConfigSync.Reset();
				}
				return;
			}
			if (Role == Role.Unknown)
			{
				Role = (instance.IsDedicated() ? Role.DedicatedServer : (instance.IsServer() ? Role.Host : Role.Client));
			}
			ConfigSync.Register();
			ConsoleCommands.RegisterIfNeeded();
			if (!_readyRaised)
			{
				_readyRaised = true;
				try
				{
					CorePlugin.Ready?.Invoke(Role);
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Ready handler failed: " + ex));
				}
			}
			if (IsRemoteClient)
			{
				ConfigSync.RequestIfNeeded();
			}
			if (!IsServerSide || !Scanner.HasConsumers)
			{
				return;
			}
			try
			{
				Scanner.Tick(Time.realtimeSinceStartup, _cycleSeconds.Value, _maxObjectsPerFrame.Value);
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("World scan failed: " + ex2));
			}
		}

		public static void PushSettingsToPlayers()
		{
			ConfigSync.BroadcastValues();
		}

		internal static bool TakeOwnership(ZDO zdo)
		{
			zdo.SetOwner(ZDOMan.GetSessionID());
			return zdo.IsOwner();
		}
	}
	public enum Scope
	{
		Server,
		Player
	}
	public abstract class SettingBase
	{
		public string Feature { get; protected set; }

		public string Key { get; protected set; }

		public string Description { get; protected set; }

		public Scope Scope { get; protected set; }

		public string FullName => Feature + "." + Key;

		public string ShortName => Feature.Replace("MySix", "").Replace(" ", "") + "." + Key;

		public bool IsRemote { get; internal set; }

		public abstract string ValueText { get; }

		public abstract string DefaultText { get; }

		public abstract string TypeHint { get; }

		public abstract bool TrySetFromText(string text, out string error);

		internal abstract void ApplyRemoteText(string text);

		internal abstract void ClearRemote();
	}
	public sealed class Setting<T> : SettingBase
	{
		private readonly ConfigEntry<T> _entry;

		private T _remote;

		public T Value
		{
			get
			{
				if (!base.IsRemote)
				{
					return _entry.Value;
				}
				return _remote;
			}
		}

		public override string ValueText => Format(Value);

		public override string DefaultText => Format((T)((ConfigEntryBase)_entry).DefaultValue);

		public override string TypeHint => Describe(typeof(T));

		public event Action<Setting<T>> Changed;

		internal Setting(string feature, string key, ConfigEntry<T> entry, Scope scope, string description)
		{
			base.Feature = feature;
			base.Key = key;
			object obj = description;
			if (obj == null)
			{
				ConfigDescription description2 = ((ConfigEntryBase)entry).Description;
				obj = ((description2 != null) ? description2.Description : null) ?? "";
			}
			base.Description = (string)obj;
			base.Scope = scope;
			_entry = entry;
			_entry.SettingChanged += delegate
			{
				if (!base.IsRemote)
				{
					Raise();
				}
			};
		}

		public override bool TrySetFromText(string text, out string error)
		{
			if (!TryParse(text, out var value, out error))
			{
				return false;
			}
			base.IsRemote = false;
			_entry.Value = value;
			Raise();
			error = null;
			return true;
		}

		internal override void ApplyRemoteText(string text)
		{
			if (!TryParse(text, out var value, out var error))
			{
				CorePlugin.Log.LogWarning((object)("Ignoring bad value for " + base.FullName + " from server: " + error));
				return;
			}
			bool num = !base.IsRemote || !EqualityComparer<T>.Default.Equals(_remote, value);
			_remote = value;
			base.IsRemote = true;
			if (num)
			{
				Raise();
			}
		}

		internal override void ClearRemote()
		{
			if (base.IsRemote)
			{
				base.IsRemote = false;
				Raise();
			}
		}

		private void Raise()
		{
			try
			{
				this.Changed?.Invoke(this);
			}
			catch (Exception arg)
			{
				CorePlugin.Log.LogError((object)$"{base.FullName} change handler failed: {arg}");
			}
		}

		private static string Format(T v)
		{
			if (!(v is float num))
			{
				if (!(v is bool))
				{
					return Convert.ToString(v, CultureInfo.InvariantCulture);
				}
				object obj = v;
				if (!(bool)((obj is bool) ? obj : null))
				{
					return "false";
				}
				return "true";
			}
			return num.ToString("0.###", CultureInfo.InvariantCulture);
		}

		private static bool TryParse(string text, out T value, out string error)
		{
			value = default(T);
			error = null;
			text = (text ?? "").Trim();
			Type typeFromHandle = typeof(T);
			try
			{
				if (typeFromHandle == typeof(bool))
				{
					if (text.Equals("true", StringComparison.OrdinalIgnoreCase) || text == "1")
					{
						value = (T)(object)true;
						return true;
					}
					if (text.Equals("false", StringComparison.OrdinalIgnoreCase) || text == "0")
					{
						value = (T)(object)false;
						return true;
					}
					error = "expected true or false";
					return false;
				}
				if (typeFromHandle == typeof(int))
				{
					if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						error = "expected a whole number";
						return false;
					}
					value = (T)(object)result;
					return true;
				}
				if (typeFromHandle == typeof(float))
				{
					if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
					{
						error = "expected a number";
						return false;
					}
					value = (T)(object)result2;
					return true;
				}
				if (typeFromHandle.IsEnum)
				{
					string[] names = Enum.GetNames(typeFromHandle);
					foreach (string text2 in names)
					{
						if (text2.Equals(text, StringComparison.OrdinalIgnoreCase))
						{
							value = (T)Enum.Parse(typeFromHandle, text2);
							return true;
						}
					}
					error = "expected one of: " + string.Join(", ", Enum.GetNames(typeFromHandle));
					return false;
				}
				if (typeFromHandle == typeof(string))
				{
					value = (T)(object)text;
					return true;
				}
				error = "unsupported setting type " + typeFromHandle.Name;
				return false;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		private static string Describe(Type t)
		{
			if (!(t == typeof(bool)))
			{
				if (!(t == typeof(int)))
				{
					if (!(t == typeof(float)))
					{
						if (!t.IsEnum)
						{
							return "text";
						}
						return string.Join("|", Enum.GetNames(t));
					}
					return "number";
				}
				return "whole number";
			}
			return "true/false";
		}
	}
	public static class Settings
	{
		private static readonly Dictionary<string, SettingBase> All = new Dictionary<string, SettingBase>(StringComparer.OrdinalIgnoreCase);

		public static IEnumerable<SettingBase> Known => All.Values.OrderBy<SettingBase, string>((SettingBase s) => s.FullName, StringComparer.OrdinalIgnoreCase);

		public static IEnumerable<SettingBase> ServerOwned => Known.Where((SettingBase s) => s.Scope == Scope.Server);

		public static IEnumerable<string> Names => Known.Select((SettingBase s) => s.FullName);

		public static Setting<T> Register<T>(string feature, ConfigEntry<T> entry, Scope scope, string description = null)
		{
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			Setting<T> setting = new Setting<T>(feature, ((ConfigEntryBase)entry).Definition.Key, entry, scope, description);
			if (All.ContainsKey(setting.FullName))
			{
				CorePlugin.Log.LogWarning((object)("Setting registered twice: " + setting.FullName));
			}
			All[setting.FullName] = setting;
			return setting;
		}

		public static SettingBase Find(string fullName)
		{
			if (All.TryGetValue(fullName ?? "", out var value))
			{
				return value;
			}
			List<SettingBase> list = All.Values.Where((SettingBase x) => string.Equals(x.ShortName, fullName, StringComparison.OrdinalIgnoreCase)).ToList();
			if (list.Count == 1)
			{
				return list[0];
			}
			List<SettingBase> list2 = All.Values.Where((SettingBase x) => string.Equals(x.Key, fullName, StringComparison.OrdinalIgnoreCase)).ToList();
			if (list2.Count != 1)
			{
				return null;
			}
			return list2[0];
		}

		internal static void ClearRemoteValues()
		{
			foreach (SettingBase value in All.Values)
			{
				value.ClearRemote();
			}
		}
	}
	public interface IWorldScanConsumer
	{
		void BeginPass();

		void Visit(ZDO zdo, int prefabHash);

		void EndPass(float realtimeNow);
	}
	public class WorldScanner
	{
		private static readonly FieldRef<ZDOMan, List<ZDO>[]> ObjectsBySector = AccessTools.FieldRefAccess<ZDOMan, List<ZDO>[]>("m_objectsBySector");

		private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");

		private readonly List<IWorldScanConsumer> _consumers = new List<IWorldScanConsumer>();

		private readonly Stopwatch _slice = new Stopwatch();

		private int _sector = -1;

		private float _passStart;

		private double _passWorkMs;

		private double _passMaxSliceMs;

		private int _passFrames;

		private int _passObjects;

		public int CompletedPasses { get; private set; }

		public bool WorldLoaded { get; private set; }

		public bool HasConsumers => _consumers.Count > 0;

		public void Add(IWorldScanConsumer consumer)
		{
			if (consumer != null && !_consumers.Contains(consumer))
			{
				_consumers.Add(consumer);
			}
		}

		public void FinishPassNow()
		{
			_passStart = -1.7014117E+38f;
		}

		public void Tick(float now, float cycleSeconds, int maxObjectsPerFrame)
		{
			if (_consumers.Count == 0 || ZDOMan.instance == null)
			{
				return;
			}
			List<ZDO>[] array = ObjectsBySector.Invoke(ZDOMan.instance);
			if (array == null)
			{
				return;
			}
			if (!WorldLoaded)
			{
				if (ObjectsById.Invoke(ZDOMan.instance).Count == 0)
				{
					return;
				}
				WorldLoaded = true;
			}
			if (_sector < 0)
			{
				_sector = 0;
				_passStart = now;
				_passWorkMs = (_passMaxSliceMs = 0.0);
				_passFrames = (_passObjects = 0);
				foreach (IWorldScanConsumer consumer in _consumers)
				{
					consumer.BeginPass();
				}
			}
			float num = Math.Max(1f, cycleSeconds);
			double num2 = (now - _passStart) / num;
			int num3 = ((num2 >= 1.0) ? array.Length : ((int)(num2 * (double)array.Length)));
			if (_sector >= num3 && _sector < array.Length)
			{
				return;
			}
			_slice.Restart();
			int num4 = Math.Max(100, maxObjectsPerFrame);
			int num5 = 0;
			while (_sector < array.Length && _sector < num3 && num5 < num4)
			{
				List<ZDO> list = array[_sector++];
				if (list == null)
				{
					continue;
				}
				for (int i = 0; i < list.Count; i++)
				{
					ZDO val = list[i];
					if (val != null && val.IsValid())
					{
						int prefab = val.GetPrefab();
						for (int j = 0; j < _consumers.Count; j++)
						{
							_consumers[j].Visit(val, prefab);
						}
					}
				}
				num5 += list.Count;
			}
			_passObjects += num5;
			bool flag = _sector >= array.Length;
			if (flag)
			{
				_sector = -1;
				foreach (IWorldScanConsumer consumer2 in _consumers)
				{
					consumer2.EndPass(now);
				}
			}
			double totalMilliseconds = _slice.Elapsed.TotalMilliseconds;
			_slice.Reset();
			_passWorkMs += totalMilliseconds;
			_passFrames++;
			if (totalMilliseconds > _passMaxSliceMs)
			{
				_passMaxSliceMs = totalMilliseconds;
			}
			if (flag)
			{
				if (CompletedPasses == 0 || _passMaxSliceMs > 8.0)
				{
					CorePlugin.Log.Log((LogLevel)((_passMaxSliceMs > 8.0) ? 4 : 16), (object)($"World pass over {_passObjects} objects took {now - _passStart:0}s: {_passFrames} frames, " + $"{_passWorkMs:0.0} ms total work, slowest frame {_passMaxSliceMs:0.00} ms"));
				}
				CompletedPasses++;
			}
		}
	}
}