Decompiled source of WorldCache v0.1.10

plugins/WorldCache.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Valheim.SettingsGui;
using WorldCache;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("WorldCacheTest")]
[assembly: AssemblyVersion("0.0.0.0")]
[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 WorldCache
{
	internal static class Codec
	{
		private const int Magic = 826622807;

		internal const int Shuffle4 = 1;

		private static readonly uint[] table = MakeTable();

		internal static byte[] Encode(byte[] raw, string key, string revision, int flags, bool fast = false)
		{
			MemoryStream memoryStream = new MemoryStream(raw.Length / 3 + 64);
			BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(826622807);
			binaryWriter.Write(flags);
			byte[] bytes = Encoding.UTF8.GetBytes(key ?? "");
			binaryWriter.Write(bytes.Length);
			binaryWriter.Write(bytes);
			byte[] bytes2 = Encoding.UTF8.GetBytes(revision ?? "");
			binaryWriter.Write(bytes2.Length);
			binaryWriter.Write(bytes2);
			binaryWriter.Write(raw.Length);
			binaryWriter.Write(Crc32(raw));
			binaryWriter.Flush();
			byte[] array = (((flags & 1) != 0) ? Shuffle(raw) : raw);
			using (DeflateStream deflateStream = new DeflateStream(memoryStream, fast ? CompressionLevel.Fastest : CompressionLevel.Optimal, leaveOpen: true))
			{
				deflateStream.Write(array, 0, array.Length);
			}
			return memoryStream.ToArray();
		}

		internal static byte[] Decode(Stream stream, string revision)
		{
			string key;
			return Decode(stream, revision, out key);
		}

		internal static byte[] Decode(Stream stream, string revision, out string key)
		{
			BinaryReader binaryReader = new BinaryReader(stream);
			if (binaryReader.ReadInt32() != 826622807)
			{
				throw new InvalidDataException("not a WorldCache entry");
			}
			int num = binaryReader.ReadInt32();
			int num2 = binaryReader.ReadInt32();
			if (num2 < 0 || num2 > 4096)
			{
				throw new InvalidDataException("bad key length");
			}
			key = Encoding.UTF8.GetString(binaryReader.ReadBytes(num2));
			int num3 = binaryReader.ReadInt32();
			if (num3 < 0 || num3 > 4096)
			{
				throw new InvalidDataException("bad revision length");
			}
			if (!string.Equals(Encoding.UTF8.GetString(binaryReader.ReadBytes(num3)), revision ?? "", StringComparison.Ordinal))
			{
				return null;
			}
			int num4 = binaryReader.ReadInt32();
			uint num5 = binaryReader.ReadUInt32();
			if (num4 < 0 || num4 > 536870912)
			{
				throw new InvalidDataException("bad length");
			}
			byte[] array = new byte[num4];
			using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true))
			{
				int num6;
				for (int i = 0; i < num4; i += num6)
				{
					num6 = deflateStream.Read(array, i, num4 - i);
					if (num6 <= 0)
					{
						throw new InvalidDataException("truncated entry");
					}
				}
			}
			byte[] obj = (((num & 1) != 0) ? Unshuffle(array) : array);
			if (Crc32(obj) != num5)
			{
				throw new InvalidDataException("checksum mismatch");
			}
			return obj;
		}

		private static byte[] Shuffle(byte[] data)
		{
			int num = data.Length / 4;
			int count = data.Length - num * 4;
			byte[] array = new byte[data.Length];
			for (int i = 0; i < num; i++)
			{
				int num2 = i * 4;
				array[i] = data[num2];
				array[num + i] = data[num2 + 1];
				array[2 * num + i] = data[num2 + 2];
				array[3 * num + i] = data[num2 + 3];
			}
			Buffer.BlockCopy(data, num * 4, array, num * 4, count);
			return array;
		}

		private static byte[] Unshuffle(byte[] data)
		{
			int num = data.Length / 4;
			int count = data.Length - num * 4;
			byte[] array = new byte[data.Length];
			for (int i = 0; i < num; i++)
			{
				int num2 = i * 4;
				array[num2] = data[i];
				array[num2 + 1] = data[num + i];
				array[num2 + 2] = data[2 * num + i];
				array[num2 + 3] = data[3 * num + i];
			}
			Buffer.BlockCopy(data, num * 4, array, num * 4, count);
			return array;
		}

		private static uint[] MakeTable()
		{
			uint[] array = new uint[256];
			for (uint num = 0u; num < 256; num++)
			{
				uint num2 = num;
				for (int i = 0; i < 8; i++)
				{
					num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1));
				}
				array[num] = num2;
			}
			return array;
		}

		internal static uint Crc32(byte[] data)
		{
			uint num = uint.MaxValue;
			for (int i = 0; i < data.Length; i++)
			{
				num = table[(num ^ data[i]) & 0xFF] ^ (num >> 8);
			}
			return num ^ 0xFFFFFFFFu;
		}

		internal static string Hash(string text)
		{
			ulong num = 14695981039346656037uL;
			byte[] bytes = Encoding.UTF8.GetBytes(text ?? "");
			foreach (byte b in bytes)
			{
				num ^= b;
				num *= 1099511628211L;
			}
			return num.ToString("x16");
		}

		internal static string FileName(string key)
		{
			StringBuilder stringBuilder = new StringBuilder(Math.Min(key.Length, 80) + 20);
			foreach (char c in key)
			{
				if (stringBuilder.Length >= 80)
				{
					break;
				}
				stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '.') ? c : '_');
			}
			stringBuilder.Append('~').Append(Hash(key).Substring(0, 12)).Append(".wce");
			return stringBuilder.ToString();
		}
	}
	internal static class EntryPrompt
	{
		[HarmonyPatch(typeof(Hud), "UpdateBlackScreen")]
		private static class LoadingTip
		{
			private static void Postfix(Hud __instance)
			{
				try
				{
					if (Wanted && !((Object)(object)__instance.m_loadingTip == (Object)null) && !((Object)(object)__instance.m_loadingProgress == (Object)null) && __instance.m_loadingProgress.activeSelf && __instance.m_loadingTip.text != Tip)
					{
						__instance.m_loadingTip.text = Tip;
					}
				}
				catch (Exception ex)
				{
					Plugin.Debug("loading tip: " + ex.Message);
				}
			}
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static PopupButtonCallback <>9__8_0;

			public static PopupButtonCallback <>9__8_1;

			public static RetrieveFromStringSource <>9__10_0;

			public static RetrieveFromStringSource <>9__10_1;

			public static RetrieveFromBoolSource <>9__10_2;

			public static PopupButtonCallback <>9__10_3;

			internal void <Ask>b__8_0()
			{
				UnifiedPopup.Pop();
				Wait();
			}

			internal void <Ask>b__8_1()
			{
				UnifiedPopup.Pop();
				Play();
			}

			internal string <Wait>b__10_0()
			{
				return Plugin.T("Подготовка мира", "Preparing the world");
			}

			internal string <Wait>b__10_1()
			{
				return $"{Prepare.Progress * 100f:0} % · {Plugin.Megabytes(Prepare.WrittenBytes)} / {Plugin.Megabytes(Prepare.Budget)}\n{Prepare.Phase}\n\n" + Plugin.T("«Играть» - в игру сейчас, подготовка продолжится в фоне.", "\"Play\": into the game now; the preparation goes on in the background.");
			}

			internal bool <Wait>b__10_2()
			{
				if (Prepare.Running)
				{
					return !Prepare.Fast;
				}
				return true;
			}

			internal void <Wait>b__10_3()
			{
				Prepare.ToBackground();
				UnifiedPopup.Pop();
			}
		}

		private static bool asked;

		private static bool Wanted
		{
			get
			{
				if (Plugin.Enabled.Value && Plugin.AskOnFirstEntry.Value && Stages.Open && Stages.Fresh)
				{
					return !asked;
				}
				return false;
			}
		}

		internal static string Tip => Plugin.T("WorldCache: этот мир ещё не в кэше - места генерируются и сохраняются на диск, поэтому первая загрузка может идти дольше. Следующие входы будут быстрее.", "WorldCache: this world is not cached yet - places are generated and written to the disk, so this first load may take longer. The next ones will be faster.");

		internal static void WorldOpened()
		{
			asked = false;
		}

		internal static void OnSpawned()
		{
			if (!Wanted || Prepare.Running)
			{
				if (Stages.Open)
				{
					asked = true;
				}
			}
			else
			{
				asked = true;
				Stages.Fresh = false;
				Ask();
			}
		}

		internal static void Ask()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			string text = Plugin.Megabytes(Plugin.VolumeChoice() << 20);
			string text2 = Plugin.T("Этот мир ещё не в кэше WorldCache: места генерируются и сохраняются на диск, поэтому первая загрузка и первые минуты могут идти медленнее.\n\n«Ждать» - сейчас подготовить места вокруг вас, у баз и порталов, открытое на карте и найденные места (до " + text + "), затем играть.\n«Играть» - сразу в игру: кэш пополняется по ходу игры и подготовкой в фоне.", "This world is not in the WorldCache yet: places are generated and written to the disk, so the first load and the first minutes may be slower.\n\n\"Wait\": prepare the places around you, at the bases and portals, explored and found places now (up to " + text + "), then play.\n\"Play\": play at once; the cache fills as you go and from the preparation in the background.");
			try
			{
				string text3 = Plugin.T("Кэш мира", "World cache");
				object obj = <>c.<>9__8_0;
				if (obj == null)
				{
					PopupButtonCallback val = delegate
					{
						UnifiedPopup.Pop();
						Wait();
					};
					<>c.<>9__8_0 = val;
					obj = (object)val;
				}
				object obj2 = <>c.<>9__8_1;
				if (obj2 == null)
				{
					PopupButtonCallback val2 = delegate
					{
						UnifiedPopup.Pop();
						Play();
					};
					<>c.<>9__8_1 = val2;
					obj2 = (object)val2;
				}
				UnifiedPopup.Push((PopupBase)new YesNoPopup(text3, text2, (PopupButtonCallback)obj, (PopupButtonCallback)obj2, false, false));
				Relabel("buttonRightText", Plugin.T("Ждать", "Wait"));
				Relabel("buttonLeftText", Plugin.T("Играть", "Play"));
			}
			catch (Exception ex)
			{
				Plugin.Warn("first entry question not shown: " + ex.Message);
			}
		}

		private static void Play()
		{
			if (Prepare.Start(PrepareMode.Smart, Plugin.VolumeChoice() << 20))
			{
				Plugin.Message(Plugin.T("Кэш пополняется по ходу игры и подготовкой в фоне.", "The cache fills as you go and in the background."));
			}
		}

		private static void Wait()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			//IL_0084: 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_008f: Expected O, but got Unknown
			if (!Prepare.Start(PrepareMode.Entry, Plugin.VolumeChoice() << 20, null, fastMode: true))
			{
				return;
			}
			object obj = <>c.<>9__10_0;
			if (obj == null)
			{
				RetrieveFromStringSource val = () => Plugin.T("Подготовка мира", "Preparing the world");
				<>c.<>9__10_0 = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__10_1;
			if (obj2 == null)
			{
				RetrieveFromStringSource val2 = () => $"{Prepare.Progress * 100f:0} % · {Plugin.Megabytes(Prepare.WrittenBytes)} / {Plugin.Megabytes(Prepare.Budget)}\n{Prepare.Phase}\n\n" + Plugin.T("«Играть» - в игру сейчас, подготовка продолжится в фоне.", "\"Play\": into the game now; the preparation goes on in the background.");
				<>c.<>9__10_1 = val2;
				obj2 = (object)val2;
			}
			object obj3 = <>c.<>9__10_2;
			if (obj3 == null)
			{
				RetrieveFromBoolSource val3 = () => !Prepare.Running || !Prepare.Fast;
				<>c.<>9__10_2 = val3;
				obj3 = (object)val3;
			}
			object obj4 = <>c.<>9__10_3;
			if (obj4 == null)
			{
				PopupButtonCallback val4 = delegate
				{
					Prepare.ToBackground();
					UnifiedPopup.Pop();
				};
				<>c.<>9__10_3 = val4;
				obj4 = (object)val4;
			}
			UnifiedPopup.Push((PopupBase)new CancelableTaskPopup((RetrieveFromStringSource)obj, (RetrieveFromStringSource)obj2, (RetrieveFromBoolSource)obj3, (PopupButtonCallback)obj4));
			Relabel("buttonCenterText", Plugin.T("Играть", "Play"));
		}

		private static void Relabel(string field, string text)
		{
			UnifiedPopup value = Traverse.Create(typeof(UnifiedPopup)).Field("instance").GetValue<UnifiedPopup>();
			TMP_Text val = (((Object)(object)value != (Object)null) ? Traverse.Create((object)value).Field(field).GetValue<TMP_Text>() : null);
			if ((Object)(object)val != (Object)null)
			{
				val.text = text;
			}
		}
	}
	internal static class HeightmapProvider
	{
		[HarmonyPatch(typeof(HeightmapBuilder), "IsTerrainReady")]
		private static class IsReadyPatch
		{
			private static bool Prefix(HeightmapBuilder __instance, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen, ref bool __result)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					if (Offer(__instance, center, width, scale, distantLod, worldGen))
					{
						__result = false;
						return false;
					}
				}
				catch (Exception ex)
				{
					Plugin.Warn("IsTerrainReady: " + ex.Message);
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(HeightmapBuilder), "RequestTerrain")]
		private static class RequestPatch
		{
			private static bool Prefix(HeightmapBuilder __instance, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen, ref HMBuildData __result)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					if (Offer(__instance, center, width, scale, distantLod, worldGen))
					{
						__result = null;
						return false;
					}
				}
				catch (Exception ex)
				{
					Plugin.Warn("RequestTerrain: " + ex.Message);
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(HeightmapBuilder), "Build")]
		private static class BuildPatch
		{
			private static void Postfix(HMBuildData data)
			{
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				if (generating || data == null || data.m_baseHeights == null || !Usable(data.m_worldGen) || (data.m_distantLod && !Plugin.CacheDistantTerrain.Value))
				{
					return;
				}
				try
				{
					string key = Key(data.m_center, data.m_width, data.m_scale, data.m_distantLod);
					missing.TryRemove(key, out var _);
					Stages.Save("game.heightmap", key, "0", Serialize(data));
				}
				catch (Exception ex)
				{
					Plugin.Warn("heightmap not cached: " + ex.Message);
				}
			}
		}

		internal const string Id = "game.heightmap";

		private static readonly FieldRef<HeightmapBuilder, object> Lock = AccessTools.FieldRefAccess<HeightmapBuilder, object>("m_lock");

		private static readonly FieldRef<HeightmapBuilder, List<HMBuildData>> Ready = AccessTools.FieldRefAccess<HeightmapBuilder, List<HMBuildData>>("m_ready");

		private static readonly FieldRef<HeightmapBuilder, List<HMBuildData>> ToBuild = AccessTools.FieldRefAccess<HeightmapBuilder, List<HMBuildData>>("m_toBuild");

		private static readonly Action<HeightmapBuilder, HMBuildData> BuildMethod = AccessTools.MethodDelegate<Action<HeightmapBuilder, HMBuildData>>(AccessTools.Method(typeof(HeightmapBuilder), "Build", (Type[])null, (Type[])null), (object)null, true);

		private static readonly ConcurrentDictionary<string, HMBuildData> loaded = new ConcurrentDictionary<string, HMBuildData>(StringComparer.Ordinal);

		private static readonly ConcurrentDictionary<string, byte> loading = new ConcurrentDictionary<string, byte>(StringComparer.Ordinal);

		private static readonly ConcurrentDictionary<string, byte> missing = new ConcurrentDictionary<string, byte>(StringComparer.Ordinal);

		[ThreadStatic]
		private static bool generating;

		internal static volatile bool ZonesUsed;

		private static int zoneWidth = 64;

		private static float zoneScale = 1f;

		internal static Stage Stage;

		internal static void Register()
		{
			Stage = new Stage
			{
				Id = "game.heightmap",
				Owner = "chistix.worldcache",
				Version = 1,
				Flags = 1,
				Title = Plugin.P("Высоты и биомы зон (игра)", "Zone heights and biomes (game)"),
				Parts = () => (!Plugin.AllModsInKey.Value) ? new SortedDictionary<string, string>() : WorldKey.Mods(),
				CanVerify = () => ZonesUsed && WorldGenerator.instance != null,
				Generate = GenerateKey,
				KeysNear = KeysNear
			};
			Stages.AddBuiltin(Stage);
		}

		internal static void Reset()
		{
			loaded.Clear();
			loading.Clear();
			missing.Clear();
			ZonesUsed = false;
		}

		private static string Key(Vector3 center, int width, float scale, bool distantLod)
		{
			//IL_0023: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			return (distantLod ? "L" : "Z") + "_" + Bits(center.x) + "_" + Bits(center.y) + "_" + Bits(center.z) + "_" + width.ToString(CultureInfo.InvariantCulture) + "_" + Bits(scale);
		}

		private static string Bits(float f)
		{
			return BitConverter.ToInt32(BitConverter.GetBytes(f), 0).ToString("x8");
		}

		private static float FromBits(string hex)
		{
			return BitConverter.ToSingle(BitConverter.GetBytes(int.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture)), 0);
		}

		private static bool Parse(string key, out Vector3 center, out int width, out float scale, out bool distantLod)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			center = default(Vector3);
			width = 0;
			scale = 0f;
			distantLod = false;
			string[] array = key.Split('_');
			if (array.Length != 6 || (array[0] != "L" && array[0] != "Z"))
			{
				return false;
			}
			distantLod = array[0] == "L";
			center = new Vector3(FromBits(array[1]), FromBits(array[2]), FromBits(array[3]));
			width = int.Parse(array[4], CultureInfo.InvariantCulture);
			scale = FromBits(array[5]);
			if (width > 0)
			{
				return width <= 2048;
			}
			return false;
		}

		private static byte[] Serialize(HMBuildData data)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			int count = data.m_baseHeights.Count;
			MemoryStream memoryStream = new MemoryStream(8 + count * 20);
			BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(count);
			binaryWriter.Write(data.m_baseMask.Length);
			for (int i = 0; i < count; i++)
			{
				binaryWriter.Write(data.m_baseHeights[i]);
			}
			for (int j = 0; j < 4; j++)
			{
				Color[] baseMask = data.m_baseMask;
				for (int k = 0; k < baseMask.Length; k++)
				{
					Color val = baseMask[k];
					binaryWriter.Write(((Color)(ref val))[j]);
				}
			}
			binaryWriter.Flush();
			return memoryStream.ToArray();
		}

		private static HMBuildData Deserialize(byte[] raw, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			BinaryReader binaryReader = new BinaryReader(new MemoryStream(raw));
			int num = binaryReader.ReadInt32();
			int num2 = binaryReader.ReadInt32();
			if (num != (width + 1) * (width + 1) || num2 != num)
			{
				throw new InvalidDataException("heightmap of another size");
			}
			HMBuildData val = new HMBuildData(center, width, scale, distantLod, worldGen);
			val.m_baseHeights = new List<float>(num);
			for (int i = 0; i < num; i++)
			{
				val.m_baseHeights.Add(binaryReader.ReadSingle());
			}
			val.m_baseMask = (Color[])(object)new Color[num2];
			for (int j = 0; j < 4; j++)
			{
				for (int k = 0; k < num2; k++)
				{
					Color val2 = val.m_baseMask[k];
					((Color)(ref val2))[j] = binaryReader.ReadSingle();
					val.m_baseMask[k] = val2;
				}
			}
			Vector3 val3 = center + new Vector3((float)width * scale * -0.5f, 0f, (float)width * scale * -0.5f);
			double num3 = (double)width * (double)scale;
			val.m_cornerBiomes = (BiomeSector[])(object)new BiomeSector[4];
			val.m_cornerBiomes[0] = worldGen.GetBiomeSector(val3.x, val3.z, false);
			val.m_cornerBiomes[1] = worldGen.GetBiomeSector((float)((double)val3.x + num3), val3.z, false);
			val.m_cornerBiomes[2] = worldGen.GetBiomeSector(val3.x, (float)((double)val3.z + num3), false);
			val.m_cornerBiomes[3] = worldGen.GetBiomeSector((float)((double)val3.x + num3), (float)((double)val3.z + num3), false);
			return val;
		}

		private static bool Usable(WorldGenerator worldGen)
		{
			if (Plugin.Enabled.Value && Stages.Open && worldGen != null && worldGen == WorldGenerator.instance && worldGen.m_world != null)
			{
				return !worldGen.m_world.m_menu;
			}
			return false;
		}

		private static bool Offer(HeightmapBuilder builder, Vector3 center, int width, float scale, bool distantLod, WorldGenerator worldGen)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			if (!distantLod && !ZonesUsed)
			{
				ZonesUsed = true;
				zoneWidth = width;
				zoneScale = scale;
			}
			if (!Usable(worldGen) || Stage.State != StageState.Ready)
			{
				return false;
			}
			if (distantLod && !Plugin.CacheDistantTerrain.Value)
			{
				return false;
			}
			string key = Key(center, width, scale, distantLod);
			if (loaded.TryRemove(key, out var value))
			{
				if (value.m_worldGen != worldGen)
				{
					return false;
				}
				lock (Lock.Invoke(builder))
				{
					List<HMBuildData> list = Ready.Invoke(builder);
					if (!list.Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen)))
					{
						list.Add(value);
					}
				}
				return false;
			}
			if (loading.ContainsKey(key))
			{
				return true;
			}
			if (missing.ContainsKey(key) || !Stages.Has("game.heightmap", key))
			{
				return false;
			}
			lock (Lock.Invoke(builder))
			{
				if (ToBuild.Invoke(builder).Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen)))
				{
					return false;
				}
				if (Ready.Invoke(builder).Exists((HMBuildData d) => d.IsEqual(center, width, scale, distantLod, worldGen)))
				{
					return false;
				}
			}
			if (!loading.TryAdd(key, 0))
			{
				return true;
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					byte[] array = Stages.Load("game.heightmap", key, "0");
					if (array != null)
					{
						loaded[key] = Deserialize(array, center, width, scale, distantLod, worldGen);
					}
					else
					{
						missing[key] = 0;
					}
				}
				catch (Exception ex)
				{
					Plugin.Warn("heightmap " + key + ": " + ex.Message + "; built again");
					Stages.Invalidate("game.heightmap", key);
					missing[key] = 0;
				}
				finally
				{
					loading.TryRemove(key, out var _);
				}
			});
			return true;
		}

		private static byte[] GenerateKey(string key)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			WorldGenerator instance = WorldGenerator.instance;
			if (instance == null || BuildMethod == null || HeightmapBuilder.instance == null || !Parse(key, out var center, out var width, out var scale, out var distantLod))
			{
				return null;
			}
			HMBuildData val = new HMBuildData(center, width, scale, distantLod, instance);
			generating = true;
			try
			{
				BuildMethod(HeightmapBuilder.instance, val);
			}
			finally
			{
				generating = false;
			}
			return Serialize(val);
		}

		private static string[] KeysNear(float x, float y, float z, float radius)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if (!ZonesUsed)
			{
				return new string[0];
			}
			float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.m_zoneSize : 64f);
			radius = Mathf.Min(radius, 10500f + num);
			Vector2s zone = ZoneSystem.GetZone(new Vector3(x, 0f, z));
			int num2 = Mathf.CeilToInt(radius / num);
			List<KeyValuePair<float, string>> list = new List<KeyValuePair<float, string>>();
			for (int i = -num2; i <= num2; i++)
			{
				for (int j = -num2; j <= num2; j++)
				{
					Vector3 zonePos = ZoneSystem.GetZonePos(new Vector2s(zone.x + j, zone.y + i));
					Vector2 val = new Vector2(zonePos.x - x, zonePos.z - z);
					float magnitude = ((Vector2)(ref val)).magnitude;
					if (!(magnitude > radius))
					{
						val = new Vector2(zonePos.x, zonePos.z);
						if (!(((Vector2)(ref val)).magnitude > 10500f + num))
						{
							list.Add(new KeyValuePair<float, string>(magnitude, Key(zonePos, zoneWidth, zoneScale, distantLod: false)));
						}
					}
				}
			}
			list.Sort((KeyValuePair<float, string> a, KeyValuePair<float, string> b) => a.Key.CompareTo(b.Key));
			string[] array = new string[list.Count];
			for (int num3 = 0; num3 < array.Length; num3++)
			{
				array[num3] = list[num3].Value;
			}
			return array;
		}
	}
	internal static class Invite
	{
		[HarmonyPatch(typeof(Game), "Start")]
		private static class RegisterPatch
		{
			private static void Postfix()
			{
				if (ZRoutedRpc.instance != null)
				{
					ZRoutedRpc.instance.Register<ZPackage>("WorldCache_Invite", (Action<long, ZPackage>)OnInvite);
					ZRoutedRpc.instance.Register<int>("WorldCache_InviteRequest", (Action<long, int>)OnRequest);
					ZRoutedRpc.instance.Register<int>("WorldCache_InviteSent", (Action<long, int>)delegate(long sender, int count)
					{
						Status = Plugin.T($"Приглашение разослано: {count} мест.", $"Invitation sent: {count} places.");
						requestedAt = -1f;
						Plugin.Message(Status);
					});
				}
			}
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static PopupButtonCallback <>9__18_1;

			internal void <OnInvite>b__18_1()
			{
				UnifiedPopup.Pop();
			}
		}

		private const string InviteRpc = "WorldCache_Invite";

		private const string RequestRpc = "WorldCache_InviteRequest";

		private const string SentRpc = "WorldCache_InviteSent";

		private static List<Vector3> hostPoints = new List<Vector3>();

		private static bool collecting;

		private static float requestedAt = -1f;

		internal static string Status;

		private static readonly string[] Prefabs = new string[5] { "portal_wood", "portal_stone", "portal", "bed", "piece_bed02" };

		internal static bool CanInvite
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					if (!ZNet.instance.IsServer())
					{
						return ZNet.instance.LocalPlayerIsAdminOrHost();
					}
					return true;
				}
				return false;
			}
		}

		internal static IEnumerable<Vector3> HostPoints()
		{
			return hostPoints;
		}

		internal static void Send()
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null)
			{
				int value = Plugin.InviteMegabytes.Value;
				if (ZNet.instance.IsServer())
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(CollectAndSend(value, 0L));
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC("WorldCache_InviteRequest", new object[1] { value });
				requestedAt = Time.unscaledTime;
				Status = Plugin.T("Запрос отправлен серверу…", "Asked the server…");
			}
		}

		internal static void Update()
		{
			if (requestedAt > 0f && Time.unscaledTime - requestedAt > 6f)
			{
				requestedAt = -1f;
				Status = Plugin.T("Сервер не ответил: на нём нет WorldCache или вы не админ.", "No answer: the server has no WorldCache or you are not an admin.");
				Plugin.Message(Status);
			}
		}

		private static void OnRequest(long sender, int megabytes)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			object obj;
			if (peer == null)
			{
				obj = null;
			}
			else
			{
				ZRpc rpc = peer.m_rpc;
				if (rpc == null)
				{
					obj = null;
				}
				else
				{
					ISocket socket = rpc.GetSocket();
					obj = ((socket != null) ? socket.GetHostName() : null);
				}
			}
			string text = (string)obj;
			if (text == null || !ZNet.instance.IsAdmin(text))
			{
				Plugin.Log("invitation asked by " + (peer?.m_playerName ?? sender.ToString()) + ": not an admin, refused");
			}
			else
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(CollectAndSend(megabytes, sender));
			}
		}

		private static IEnumerator Collect()
		{
			if (collecting)
			{
				yield break;
			}
			collecting = true;
			List<Vector3> points = new List<Vector3>();
			List<ZDO> zdos = new List<ZDO>();
			string[] prefabs = Prefabs;
			foreach (string prefab in prefabs)
			{
				int index = 0;
				while (ZDOMan.instance != null && !ZDOMan.instance.GetAllZDOsWithPrefabIterative(prefab, zdos, ref index))
				{
					yield return null;
				}
			}
			foreach (ZDO item in zdos)
			{
				Vector3 p = item.GetPosition();
				if (!points.Any(delegate(Vector3 q)
				{
					//IL_0000: Unknown result type (might be due to invalid IL or missing references)
					//IL_0002: Unknown result type (might be due to invalid IL or missing references)
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val = q - p;
					return ((Vector3)(ref val)).sqrMagnitude < 4096f;
				}))
				{
					points.Add(p);
				}
			}
			hostPoints = points;
			collecting = false;
		}

		private static IEnumerator CollectAndSend(int megabytes, long requester)
		{
			yield return Collect();
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(megabytes);
			val.Write(hostPoints.Count);
			foreach (Vector3 hostPoint in hostPoints)
			{
				val.Write(hostPoint);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "WorldCache_Invite", new object[1] { val });
			Plugin.Log($"invitation to prepare {hostPoints.Count} places of bases and portals sent");
			if (requester != 0L)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(requester, "WorldCache_InviteSent", new object[1] { hostPoints.Count });
			}
			else
			{
				Status = Plugin.T($"Приглашение разослано: {hostPoints.Count} мест.", $"Invitation sent: {hostPoints.Count} places.");
				Plugin.Message(Status);
			}
		}

		internal static void RefreshHost()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)Plugin.Instance != (Object)null)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(Collect());
			}
		}

		private static void OnInvite(long sender, ZPackage package)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Expected O, but got Unknown
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Expected O, but got Unknown
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Expected O, but got Unknown
			if ((Object)(object)ZNet.instance == (Object)null || sender == ZNet.GetUID() || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			if (!Plugin.Enabled.Value)
			{
				Plugin.Log("invitation to prepare the world ignored: the cache is off");
			}
			else
			{
				if (package.ReadInt() != 1)
				{
					return;
				}
				int megabytes = package.ReadInt();
				int num = package.ReadInt();
				List<Vector3> points = new List<Vector3>(num);
				for (int i = 0; i < num; i++)
				{
					points.Add(package.ReadVector3());
				}
				if (points.Count == 0)
				{
					return;
				}
				string text = Plugin.T($"Хост предлагает заранее подготовить мир у баз и порталов: {points.Count} мест, до {megabytes} МБ на диске. Подготовка идёт в фоне и встаёт на паузу в бою. Подготовить?", $"The host suggests preparing the world at the bases and portals ahead: {points.Count} places, up to {megabytes} MB on the disk. It runs in the background and pauses in combat. Prepare?");
				string text2 = Plugin.T("Подготовить мир", "Prepare the world");
				PopupButtonCallback val = delegate
				{
					UnifiedPopup.Pop();
					Prepare.Start(PrepareMode.Points, (long)megabytes << 20, points);
				};
				object obj = <>c.<>9__18_1;
				if (obj == null)
				{
					PopupButtonCallback val2 = delegate
					{
						UnifiedPopup.Pop();
					};
					<>c.<>9__18_1 = val2;
					obj = (object)val2;
				}
				UnifiedPopup.Push((PopupBase)new YesNoPopup(text2, text, val, (PopupButtonCallback)obj, false, false));
			}
		}
	}
	internal static class MinimapProvider
	{
		[HarmonyPatch(typeof(Minimap), "GenerateWorldMap")]
		private static class GeneratePatch
		{
			[HarmonyPriority(200)]
			private static bool Prefix(Minimap __instance)
			{
				try
				{
					if (!Applies(__instance) || Stage.State == StageState.Waiting)
					{
						return true;
					}
					Stopwatch stopwatch = Stopwatch.StartNew();
					bool flag = Stage.State == StageState.Ready;
					byte[] array = (flag ? Stages.Load("game.minimap", "world", "0") : Stages.Store?.Load("game.minimap", "world", "0"));
					if (array == null)
					{
						return true;
					}
					if (!flag && VerifyPixels("world", array) == false)
					{
						Stages.InvalidateStage("game.minimap");
						Plugin.Warn("world map in the cache differs from the generation now: thrown away. Mods patching the generation: " + WorldKey.GenerationOwners());
						return true;
					}
					int expected = Size.Invoke(__instance);
					if (!Deserialize(array, expected, out var mask, out var biome, out var heights))
					{
						Stages.Invalidate("game.minimap", "world");
						return true;
					}
					Color[] array2 = (Color[])(object)new Color[heights.Length];
					for (int i = 0; i < heights.Length; i++)
					{
						array2[i].r = heights[i];
					}
					Texture2D obj = MaskTexture.Invoke(__instance);
					obj.SetPixels32(mask);
					obj.Apply();
					Texture2D obj2 = MapTexture.Invoke(__instance);
					obj2.SetPixels32(biome);
					obj2.Apply();
					Texture2D obj3 = HeightTexture.Invoke(__instance);
					obj3.SetPixels(array2);
					obj3.Apply();
					Plugin.Log($"world map read from the cache in {stopwatch.ElapsedMilliseconds} ms");
					return false;
				}
				catch (Exception ex)
				{
					Plugin.Warn("world map not read: " + ex.Message);
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(Minimap), "SaveMapTextureDataToDisk")]
		private static class SavePatch
		{
			private static void Prefix(Minimap __instance, Color32[] maskPixels, Color32[] biomePixels, float[] savedHeightPixels)
			{
				try
				{
					if (Applies(__instance) && maskPixels != null && biomePixels != null && savedHeightPixels != null)
					{
						Stages.Save("game.minimap", "world", "0", Serialize(Size.Invoke(__instance), maskPixels, biomePixels, savedHeightPixels));
						Plugin.Log("world map kept for the next visit");
					}
				}
				catch (Exception ex)
				{
					Plugin.Warn("world map not kept: " + ex.Message);
				}
			}
		}

		internal const string Id = "game.minimap";

		internal const string Key = "world";

		private static readonly FieldRef<Minimap, string> CachePath = AccessTools.FieldRefAccess<Minimap, string>("m_cachedMinimapMaskTexturePath");

		private static readonly FieldRef<Minimap, int> Size = AccessTools.FieldRefAccess<Minimap, int>("m_textureSize");

		private static readonly FieldRef<Minimap, float> PixelSize = AccessTools.FieldRefAccess<Minimap, float>("m_pixelSize");

		private static readonly FieldRef<Minimap, Texture2D> MapTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_mapTexture");

		private static readonly FieldRef<Minimap, Texture2D> MaskTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_forestMaskTexture");

		private static readonly FieldRef<Minimap, Texture2D> HeightTexture = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_heightTexture");

		private static readonly Func<Minimap, Biome, Color> PixelColor = AccessTools.MethodDelegate<Func<Minimap, Biome, Color>>(AccessTools.Method(typeof(Minimap), "GetPixelColor", (Type[])null, (Type[])null), (object)null, true);

		internal static Stage Stage;

		internal static void Register()
		{
			Stage = new Stage
			{
				Id = "game.minimap",
				Owner = "chistix.worldcache",
				Version = 1,
				Flags = 1,
				Title = Plugin.P("Карта мира (клиент сервера)", "World map (server client)"),
				Parts = delegate
				{
					SortedDictionary<string, string> sortedDictionary = WorldKey.Map();
					if (Plugin.AllModsInKey.Value)
					{
						foreach (KeyValuePair<string, string> item in WorldKey.Mods())
						{
							sortedDictionary[item.Key] = item.Value;
						}
					}
					return sortedDictionary;
				},
				Verify = VerifyPixels
			};
			Stages.AddBuiltin(Stage);
		}

		private static bool Applies(Minimap map)
		{
			if (!Plugin.Enabled.Value || !Stages.Open || (Object)(object)map == (Object)null || ZNet.World == null)
			{
				return false;
			}
			if (!string.IsNullOrEmpty(CachePath.Invoke(map)))
			{
				return false;
			}
			return Harmony.GetPatchInfo((MethodBase)AccessTools.Method(typeof(Minimap), "GenerateWorldMap", (Type[])null, (Type[])null))?.Prefixes.All((Patch p) => p.PatchMethod.DeclaringType.Assembly == typeof(Plugin).Assembly) ?? true;
		}

		private static byte[] Serialize(int size, Color32[] mask, Color32[] biome, float[] heights)
		{
			int num = size * size;
			byte[] array = new byte[4 + num * 12];
			Buffer.BlockCopy(BitConverter.GetBytes(size), 0, array, 0, 4);
			Copy(mask, array, 4, num * 4, toRaw: true);
			Copy(biome, array, 4 + num * 4, num * 4, toRaw: true);
			Buffer.BlockCopy(heights, 0, array, 4 + num * 8, num * 4);
			return array;
		}

		private static bool Deserialize(byte[] raw, int expected, out Color32[] mask, out Color32[] biome, out float[] heights)
		{
			mask = (biome = null);
			heights = null;
			if (!Fits(raw, expected))
			{
				return false;
			}
			int num = expected * expected;
			mask = (Color32[])(object)new Color32[num];
			biome = (Color32[])(object)new Color32[num];
			heights = new float[num];
			Copy(mask, raw, 4, num * 4, toRaw: false);
			Copy(biome, raw, 4 + num * 4, num * 4, toRaw: false);
			Buffer.BlockCopy(raw, 4 + num * 8, heights, 0, num * 4);
			return true;
		}

		private static bool Fits(byte[] raw, int size)
		{
			if (raw != null && raw.Length >= 4 && BitConverter.ToInt32(raw, 0) == size)
			{
				return raw.Length == 4 + size * size * 12;
			}
			return false;
		}

		private static void Copy(Color32[] colors, byte[] raw, int offset, int length, bool toRaw)
		{
			GCHandle gCHandle = GCHandle.Alloc(colors, GCHandleType.Pinned);
			try
			{
				if (toRaw)
				{
					Marshal.Copy(gCHandle.AddrOfPinnedObject(), raw, offset, length);
				}
				else
				{
					Marshal.Copy(raw, offset, gCHandle.AddrOfPinnedObject(), length);
				}
			}
			finally
			{
				gCHandle.Free();
			}
		}

		private static bool? VerifyPixels(string key, byte[] raw)
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			Minimap instance = Minimap.instance;
			WorldGenerator instance2 = WorldGenerator.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null || PixelColor == null)
			{
				return null;
			}
			int num = Size.Invoke(instance);
			float num2 = PixelSize.Invoke(instance);
			if (!Fits(raw, num))
			{
				return false;
			}
			int num3 = num * num;
			Random random = new Random();
			int num4 = num / 2;
			float num5 = num2 / 2f;
			Color val = default(Color);
			for (int i = 0; i < 16; i++)
			{
				int num6 = random.Next(num);
				int num7 = random.Next(num);
				int num8 = num6 * num + num7;
				float num9 = (float)(num6 - num4) * num2 + num5;
				float num10 = (float)(num7 - num4) * num2 + num5;
				Biome biome = instance2.GetBiome(num10, num9, 0.02f, false);
				if (BitConverter.ToInt32(BitConverter.GetBytes(instance2.GetBiomeHeight(biome, num10, num9, ref val, false, true)), 0) != BitConverter.ToInt32(raw, 4 + num3 * 8 + num8 * 4))
				{
					return false;
				}
				Color32 val2 = Color32.op_Implicit(PixelColor(instance, biome));
				int num11 = 4 + num3 * 4 + num8 * 4;
				if (val2.r != raw[num11] || val2.g != raw[num11 + 1] || val2.b != raw[num11 + 2])
				{
					return false;
				}
			}
			return true;
		}
	}
	[BepInPlugin("chistix.worldcache", "WorldCache", "0.1.10")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(WorldGenerator), "Initialize")]
		private static class OpenPatch
		{
			private static void Postfix(World world)
			{
				if (world == null || world.m_menu)
				{
					return;
				}
				try
				{
					HeightmapProvider.Reset();
					Stages.OpenWorld(world);
				}
				catch (Exception ex)
				{
					Warn("world cache not opened: " + ex);
				}
			}
		}

		[HarmonyPatch(typeof(WorldGenerator), "Deitialize")]
		private static class ClosePatch
		{
			private static void Prefix()
			{
				try
				{
					Stages.CloseWorld();
					HeightmapProvider.Reset();
				}
				catch (Exception ex)
				{
					Warn("world cache not closed: " + ex);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static class SpawnPatch
		{
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return;
				}
				try
				{
					Stages.Recheck("the player spawned", makeFinal: true);
					Stages.LogEntry();
					Invite.RefreshHost();
					Prepare.OnPlayerSpawned();
					EntryPrompt.OnSpawned();
				}
				catch (Exception ex)
				{
					Warn("spawn: " + ex);
				}
			}
		}

		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		private static class CommandPatch
		{
			[Serializable]
			[CompilerGenerated]
			private sealed class <>c
			{
				public static readonly <>c <>9 = new <>c();

				public static ConsoleEvent <>9__0_0;

				internal void <Postfix>b__0_0(ConsoleEventArgs args)
				{
					<>c__DisplayClass0_0 CS$<>8__locals9 = new <>c__DisplayClass0_0
					{
						args = args
					};
					switch ((CS$<>8__locals9.args.Args.Length > 1) ? CS$<>8__locals9.args.Args[1].ToLowerInvariant() : "status")
					{
					case "prepare":
					{
						PrepareMode m = PrepareModeSetting.Value;
						if (CS$<>8__locals9.args.Args.Length > 2)
						{
							string text = CS$<>8__locals9.args.Args[2].ToLowerInvariant();
							m = (text.StartsWith("around") ? PrepareMode.AroundMe : ((text.StartsWith("world") || text.StartsWith("whole")) ? PrepareMode.WholeWorld : PrepareMode.Smart));
						}
						long result;
						long num = ((CS$<>8__locals9.args.Args.Length > 3 && long.TryParse(CS$<>8__locals9.args.Args[3], out result)) ? result : VolumeChoice());
						Out(Prepare.Start(m, num << 20) ? $"WorldCache: подготовка {Prepare.ModeName(m)}, {num} МБ" : "WorldCache: не начато");
						return;
					}
					case "stop":
						Prepare.Stop(byUser: true);
						Out("WorldCache: остановлено");
						return;
					case "clear":
						ClearCache(CS$<>8__locals9.args.Args.Length > 2 && CS$<>8__locals9.args.Args[2] == "all", Out);
						return;
					case "invite":
						Invite.Send();
						Out("WorldCache: приглашение…");
						return;
					}
					foreach (string item in StatusLines())
					{
						Out(item);
					}
					string Out(string s)
					{
						Terminal context = CS$<>8__locals9.args.Context;
						if (context != null)
						{
							context.AddString(s);
						}
						return s;
					}
				}
			}

			[CompilerGenerated]
			private sealed class <>c__DisplayClass0_0
			{
				public ConsoleEventArgs args;
			}

			private static void Postfix()
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Expected O, but got Unknown
				object obj = <>c.<>9__0_0;
				if (obj == null)
				{
					ConsoleEvent val = delegate(ConsoleEventArgs args)
					{
						switch ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status")
						{
						case "prepare":
						{
							PrepareMode m = PrepareModeSetting.Value;
							if (args.Args.Length > 2)
							{
								string text = args.Args[2].ToLowerInvariant();
								m = (text.StartsWith("around") ? PrepareMode.AroundMe : ((text.StartsWith("world") || text.StartsWith("whole")) ? PrepareMode.WholeWorld : PrepareMode.Smart));
							}
							long result;
							long num = ((args.Args.Length > 3 && long.TryParse(args.Args[3], out result)) ? result : VolumeChoice());
							Out(Prepare.Start(m, num << 20) ? $"WorldCache: подготовка {Prepare.ModeName(m)}, {num} МБ" : "WorldCache: не начато");
							break;
						}
						case "stop":
							Prepare.Stop(byUser: true);
							Out("WorldCache: остановлено");
							break;
						case "clear":
							ClearCache(args.Args.Length > 2 && args.Args[2] == "all", Out);
							break;
						case "invite":
							Invite.Send();
							Out("WorldCache: приглашение…");
							break;
						default:
						{
							foreach (string item in StatusLines())
							{
								Out(item);
							}
							break;
						}
						}
						string Out(string s)
						{
							Terminal context = args.Context;
							if (context != null)
							{
								context.AddString(s);
							}
							return s;
						}
					};
					<>c.<>9__0_0 = val;
					obj = (object)val;
				}
				new ConsoleCommand("worldcache", "status | prepare [smart|around|world] [MB] | stop | clear [all] | invite - кэш мира", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
		}

		public const string Guid = "chistix.worldcache";

		public const string Version = "0.1.10";

		internal static Plugin Instance;

		private static ManualLogSource log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> AllModsInKey;

		internal static ConfigEntry<bool> CacheDistantTerrain;

		internal static ConfigEntry<bool> ShowProgress;

		internal static ConfigEntry<bool> PauseInCombat;

		internal static ConfigEntry<bool> DebugLog;

		internal static ConfigEntry<bool> AskOnFirstEntry;

		internal static ConfigEntry<int> LimitMegabytes;

		internal static ConfigEntry<int> SpotChecks;

		internal static ConfigEntry<int> VolumeMegabytes;

		internal static ConfigEntry<int> CustomMegabytes;

		internal static ConfigEntry<int> MinFps;

		internal static ConfigEntry<int> PrepareThreads;

		internal static ConfigEntry<int> PrepareSleepMs;

		internal static ConfigEntry<int> InviteMegabytes;

		internal static ConfigEntry<float> AroundRadius;

		internal static ConfigEntry<PrepareMode> PrepareModeSetting;

		private static ConfigEntry<string> ignoredMods;

		internal static HashSet<string> IgnoredMods = new HashSet<string>(StringComparer.Ordinal);

		internal static readonly int[] Volumes = new int[4] { 500, 1024, 2048, 5120 };

		internal static readonly int[] Limits = new int[6] { 500, 1024, 2048, 5120, 10240, 20480 };

		internal static bool Quitting;

		private static volatile bool russian = true;

		internal static long LimitBytes => Math.Max(100L, LimitMegabytes.Value) << 20;

		private void Awake()
		{
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			log = ((BaseUnityPlugin)this).Logger;
			MoveOldConfig();
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, D("The world's generation kept on the disk (off: the game generates as usual, the files are not touched).", "Кэш генерации мира на диске (выкл - игра генерирует как обычно, файлы не трогаются)."));
			LimitMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "LimitMB", 2048, D("Disk space for the caches of all worlds together (MB). Beyond it, what was unused the longest goes: other worlds first, then old places of this one.", "Сколько места на диске занимают кэши всех миров вместе (МБ). Сверх - удаляется давно не нужное: сначала другие миры, потом старые места этого мира."));
			AskOnFirstEntry = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AskOnFirstEntry", true, D("Entering a world without a cache: a note on the loading screen and a choice - Wait (prepare the nearest places now) or Play (cache as you go).", "Первый вход в мир без кэша: предупредить на экране загрузки и спросить - «Ждать» (подготовить ближайшее сейчас) или «Играть» (кэшировать по ходу игры)."));
			ShowProgress = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowProgress", true, D("The preparation's bar at the top of the screen.", "Полоска подготовки мира вверху экрана."));
			DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, D("More details in the log (spot checks, read errors).", "Подробности в журнал (сверки, ошибки чтения)."));
			AllModsInKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Key", "AllModsInKey", true, D("Every mod and its version is part of the key of the game's own stages (zone heights, the map): a new or updated mod clears them. Off: only mods patching the generation, plus the spot checks.", "Список всех модов и их версий входит в ключ кэша игры (высоты зон, карта): любой новый или обновлённый мод сбрасывает их. Выкл - только моды, перехватывающие генерацию, и выборочная сверка."));
			ignoredMods = ((BaseUnityPlugin)this).Config.Bind<string>("Key", "IgnoredMods", "local.valheim.probe", D("GUIDs of mods (comma separated) that patch generation methods without changing the results (diagnostics): left out of the key.", "GUID модов через запятую, которые не меняют генерацию, хоть и перехватывают её методы (диагностика): не входят в ключ."));
			SpotChecks = ((BaseUnityPlugin)this).Config.Bind<int>("Key", "SpotChecks", 3, D("How many cached pieces of each stage are generated again on entering a world and compared (0: no checks).", "Сколько сохранённых кусков каждого этапа пересчитать при входе в мир и сравнить с генерацией (0 - не сверять)."));
			CacheDistantTerrain = ((BaseUnityPlugin)this).Config.Bind<bool>("Game", "CacheDistantTerrain", true, D("Cache the distant terrain too (large tiles around the player): faster return to the same place (bed, start), more disk space.", "Кэшировать и дальний рельеф (большие плитки вокруг игрока): быстрее возврат на то же место (кровать, старт), но больше места."));
			PrepareModeSetting = ((BaseUnityPlugin)this).Config.Bind<PrepareMode>("Prepare", "Mode", PrepareMode.Smart, D("Preparing the world: Smart - around the player, bases and portals, explored on the map, altars and found places, then the rest by distance; AroundMe; WholeWorld.", "Режим подготовки мира: Smart - вокруг игрока, базы и порталы, открытое на карте, алтари и найденные места, потом остальное по расстоянию; AroundMe; WholeWorld."));
			VolumeMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "VolumeMB", 1024, D("Preparation volume (MB on the disk): 500, 1024, 2048, 5120, or 0 for CustomMB.", "Объём подготовки (МБ на диске): 500, 1024, 2048, 5120 или 0 - своё значение (CustomMB)."));
			CustomMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "CustomMB", 3072, D("The custom preparation volume (MB).", "Своё значение объёма подготовки (МБ)."));
			AroundRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Prepare", "AroundRadius", 300f, D("Radius of \"around me\", the first in the order (m).", "Радиус «вокруг меня» в первой очереди (м)."));
			MinFps = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "MinFps", 30, D("Below this FPS the preparation pauses (resumes after 3 s above it).", "Ниже этого FPS подготовка встаёт на паузу (продолжает через 3 с нормы)."));
			PauseInCombat = ((BaseUnityPlugin)this).Config.Bind<bool>("Prepare", "PauseInCombat", true, D("Pause the preparation in combat (sensed or targeted).", "Пауза подготовки в бою (вас заметили или выбрали целью)."));
			PrepareThreads = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "Threads", 1, D("Preparation threads (1-8): more is faster but loads the CPU more.", "Потоков подготовки (1-8). Больше - быстрее, но сильнее грузит процессор."));
			PrepareSleepMs = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "SleepMs", 5, D("A thread's rest after each piece (ms): small portions.", "Пауза потока после каждого куска (мс): малые порции."));
			InviteMegabytes = ((BaseUnityPlugin)this).Config.Bind<int>("Prepare", "InviteMB", 500, D("Disk space the host suggests to the players for preparing the bases and portals (MB).", "Сколько места хост предлагает игрокам на подготовку баз и порталов (МБ)."));
			ParseIgnored();
			ignoredMods.SettingChanged += delegate
			{
				ParseIgnored();
			};
			LimitMegabytes.SettingChanged += delegate
			{
				Stages.Store?.Trim();
			};
			Enabled.SettingChanged += delegate
			{
				if (!Enabled.Value)
				{
					Prepare.Stop(byUser: true);
				}
			};
			HeightmapProvider.Register();
			MinimapProvider.Register();
			new Harmony("chistix.worldcache").PatchAll(typeof(Plugin).Assembly);
			Log("WorldCache 0.1.10: limit " + Megabytes(LimitBytes));
		}

		private void MoveOldConfig()
		{
			try
			{
				string text = Path.Combine(Paths.ConfigPath, "local.valheim.worldcache.cfg");
				if (File.Exists(text) && !File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
				{
					File.Copy(text, ((BaseUnityPlugin)this).Config.ConfigFilePath);
					((BaseUnityPlugin)this).Config.Reload();
					Log("settings taken over from local.valheim.worldcache.cfg");
				}
			}
			catch (Exception ex)
			{
				Warn("old settings not taken over: " + ex.Message);
			}
		}

		private static string D(string en, string ru)
		{
			return en + "\n" + ru;
		}

		private static void ParseIgnored()
		{
			IgnoredMods = new HashSet<string>(from s in (ignoredMods.Value ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s, StringComparer.Ordinal);
		}

		private void Update()
		{
			try
			{
				if (Localization.instance != null)
				{
					russian = Localization.instance.GetSelectedLanguage() == "Russian";
				}
				Stages.Update();
				Stages.Tick();
				Prepare.Tick();
				Invite.Update();
			}
			catch (Exception ex)
			{
				Warn("update: " + ex);
			}
		}

		private void OnGUI()
		{
			try
			{
				Prepare.DrawOverlay();
			}
			catch (Exception ex)
			{
				Debug("overlay: " + ex.Message);
			}
		}

		private void OnApplicationQuit()
		{
			Quitting = true;
			Stages.CloseWorld();
		}

		internal static long VolumeChoice()
		{
			return (VolumeMegabytes.Value > 0) ? VolumeMegabytes.Value : Math.Max(100, CustomMegabytes.Value);
		}

		internal static void ClearCache(bool allWorlds, Func<string, string> report)
		{
			Prepare.Stop(byUser: false);
			Store store = Stages.Store;
			if (store == null)
			{
				long bytes = Store.Worlds().Sum((Store.WorldInfo w) => w.Bytes);
				foreach (Store.WorldInfo item in Store.Worlds())
				{
					Store.TryDeleteFolder(item.Path);
				}
				report?.Invoke(T("Кэш всех миров очищен (" + Megabytes(bytes) + ").", "Every world's cache cleared (" + Megabytes(bytes) + ")."));
				return;
			}
			long bytes2 = store.Total + (allWorlds ? store.Others : 0);
			store.Clear(delegate
			{
				Log("cache of this world cleared");
			});
			if (allWorlds)
			{
				store.ClearOthers(delegate
				{
					Log("cache of other worlds cleared");
				});
			}
			report?.Invoke(T("Кэш очищен (" + Megabytes(bytes2) + ").", "Cache cleared (" + Megabytes(bytes2) + ")."));
		}

		internal static IEnumerable<string> StatusLines()
		{
			Store store = Stages.Store;
			yield return "WorldCache 0.1.10: " + (Enabled.Value ? T("вкл", "on") : T("выкл", "off")) + ", " + T("лимит", "limit") + " " + Megabytes(LimitBytes) + ", " + UsageText();
			if (store != null)
			{
				foreach (Stage item in Stages.All())
				{
					yield return string.Format("  {0} ({1}): {2}, {3}, {4} {5}, {6} {7}, {8} {9}", item.Id, item.Owner, StateName(item.State), Megabytes(store.StageBytes(item.Id)), T("попаданий", "hits"), item.Hits, T("промахов", "misses"), item.Misses, T("записей", "saves"), item.Saves);
				}
			}
			if (Prepare.Running)
			{
				yield return string.Format("  {0}: {1} {2:0} % ({3} / {4}) {5}", T("подготовка", "preparing"), Prepare.ModeName(Prepare.Mode), Prepare.Progress * 100f, Megabytes(Prepare.WrittenBytes), Megabytes(Prepare.Budget), Prepare.Phase) + ((Prepare.PauseReason != null) ? (" - " + T("пауза", "paused") + ": " + Prepare.PauseReason) : "");
			}
		}

		internal static string StateName(StageState s)
		{
			return s switch
			{
				StageState.Checking => T("сверка", "checking"), 
				StageState.Ready => T("готов", "ready"), 
				_ => T("ждёт", "waiting"), 
			};
		}

		internal static string UsageText()
		{
			Store store = Stages.Store;
			if (store != null)
			{
				return T("занято", "used") + " " + Megabytes(store.Total + store.Others) + " (" + T("этот мир", "this world") + " " + Megabytes(store.Total) + ")";
			}
			return T("занято", "used") + " " + Megabytes(Store.Worlds().Sum((Store.WorldInfo w) => w.Bytes));
		}

		internal static string T(string ru, string en)
		{
			if (!russian)
			{
				return en;
			}
			return ru;
		}

		internal static string P(string ru, string en)
		{
			return ru + "\u0001" + en;
		}

		internal static string Pick(string text)
		{
			int num = text?.IndexOf('\u0001') ?? (-1);
			if (num >= 0)
			{
				return T(text.Substring(0, num), text.Substring(num + 1));
			}
			return text;
		}

		internal static string Megabytes(long bytes)
		{
			CultureInfo cultureInfo = CultureInfo.GetCultureInfo(russian ? "ru-RU" : "en-US");
			if (bytes < 1073741824)
			{
				return ((double)bytes / 1048576.0).ToString("0", cultureInfo) + T(" МБ", " MB");
			}
			return ((double)bytes / 1073741824.0).ToString("0.##", cultureInfo) + T(" ГБ", " GB");
		}

		internal static void Message(string text)
		{
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
			}
			Log(text);
		}

		internal static void Log(string text)
		{
			ManualLogSource obj = log;
			if (obj != null)
			{
				obj.LogInfo((object)text);
			}
		}

		internal static void Warn(string text)
		{
			ManualLogSource obj = log;
			if (obj != null)
			{
				obj.LogWarning((object)text);
			}
		}

		internal static void Debug(string text)
		{
			if (DebugLog != null && DebugLog.Value)
			{
				ManualLogSource obj = log;
				if (obj != null)
				{
					obj.LogInfo((object)text);
				}
			}
		}
	}
	internal enum PrepareMode
	{
		Smart,
		AroundMe,
		WholeWorld,
		Points,
		Entry
	}
	internal static class Prepare
	{
		private sealed class Area
		{
			internal Vector3 Pos;

			internal float Radius;

			internal string Phase;
		}

		private sealed class State
		{
			internal bool Running;

			internal PrepareMode Mode;

			internal long Budget;

			internal long Written;

			internal List<Vector3> Points;
		}

		private static readonly object gate = new object();

		private static volatile int job;

		private static volatile bool running;

		private static volatile bool fast;

		private static volatile bool continueSmart;

		private static long continueBudget;

		private static PrepareMode mode;

		private static long budget;

		private static long written;

		private static long itemsDone;

		private static List<Area> areas = new List<Area>();

		private static int areaCursor;

		private static int busy;

		private static readonly ConcurrentQueue<KeyValuePair<Stage, string>> items = new ConcurrentQueue<KeyValuePair<Stage, string>>();

		private static readonly List<Thread> threads = new List<Thread>();

		private static volatile string pauseReason;

		private static string phase = "";

		private static string finished;

		private static float finishedAt = -100f;

		private static readonly Stopwatch clock = Stopwatch.StartNew();

		internal static List<Vector3> InvitePoints = new List<Vector3>();

		private static readonly FieldRef<Minimap, List<PinData>> Pins = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins");

		private static readonly FieldRef<Minimap, BitArray> ExploredBits = AccessTools.FieldRefAccess<Minimap, BitArray>("m_explored");

		private static readonly FieldRef<Minimap, int> TextureSize = AccessTools.FieldRefAccess<Minimap, int>("m_textureSize");

		private static readonly FieldRef<Minimap, float> PixelSize = AccessTools.FieldRefAccess<Minimap, float>("m_pixelSize");

		private static readonly Func<Minimap, float, float, Vector3> MapPointToWorld = AccessTools.MethodDelegate<Func<Minimap, float, float, Vector3>>(AccessTools.Method(typeof(Minimap), "MapPointToWorld", (Type[])null, (Type[])null), (object)null, true);

		private static readonly FieldRef<ZoneSystem, Dictionary<Vector3, string>> LocationIcons = AccessTools.FieldRefAccess<ZoneSystem, Dictionary<Vector3, string>>("m_locationIcons");

		private static float exploredCell = 256f;

		private static float fpsAverage = 60f;

		private static float goodSince;

		private static float startDelay;

		private static GUIStyle label;

		private static Texture2D white;

		internal static bool Fast => fast;

		private static float Clock => (float)clock.Elapsed.TotalSeconds;

		internal static bool Running => running;

		internal static PrepareMode Mode => mode;

		internal static long Budget => budget;

		internal static long WrittenBytes => Interlocked.Read(in written);

		internal static string PauseReason => pauseReason;

		internal static string Phase => Plugin.Pick(phase);

		internal static string Finished => finished;

		internal static float Progress
		{
			get
			{
				if (budget > 0)
				{
					return Mathf.Clamp01((float)WrittenBytes / (float)budget);
				}
				return 0f;
			}
		}

		internal static void ToBackground()
		{
			fast = false;
		}

		internal static string ModeName(PrepareMode m)
		{
			return m switch
			{
				PrepareMode.Smart => Plugin.T("Умно", "Smart"), 
				PrepareMode.AroundMe => Plugin.T("Вокруг меня", "Around me"), 
				PrepareMode.WholeWorld => Plugin.T("Весь мир", "Whole world"), 
				PrepareMode.Entry => Plugin.T("Ближайшее", "Nearest"), 
				_ => Plugin.T("Базы и порталы хоста", "Host's bases and portals"), 
			};
		}

		internal static bool Start(PrepareMode m, long bytes, List<Vector3> points = null, bool fastMode = false)
		{
			if (!Stages.Open || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				Plugin.Message(Plugin.T("Подготовка - только в мире.", "Preparing works in a world only."));
				return false;
			}
			if (!Plugin.Enabled.Value)
			{
				Plugin.Message(Plugin.T("Кэш мира выключен.", "The world cache is off."));
				return false;
			}
			Stop(byUser: false);
			lock (gate)
			{
				mode = m;
				budget = ((m == PrepareMode.WholeWorld) ? Plugin.LimitBytes : Math.Max(16777216L, bytes));
				Interlocked.Exchange(ref written, 0L);
				Interlocked.Exchange(ref itemsDone, 0L);
				if (points != null)
				{
					InvitePoints = points;
				}
				fast = fastMode;
				Begin();
			}
			Save();
			Plugin.Log($"preparing the world: {ModeName(mode)}, {Plugin.Megabytes(budget)}, {areas.Count} areas");
			return true;
		}

		private static void Begin()
		{
			areas = Plan(mode);
			areaCursor = 0;
			KeyValuePair<Stage, string> result;
			while (items.TryDequeue(out result))
			{
			}
			finished = null;
			phase = "";
			int id = ++job;
			running = true;
			threads.Clear();
			int num = (fast ? Math.Max(1, Environment.ProcessorCount - 1) : Mathf.Clamp(Plugin.PrepareThreads.Value, 1, 8));
			for (int i = 0; i < num; i++)
			{
				int index = i;
				Thread thread = new Thread((ThreadStart)delegate
				{
					Work(id, index);
				})
				{
					Name = "WorldCache prepare " + i,
					IsBackground = true,
					Priority = ((!fast) ? ThreadPriority.BelowNormal : ThreadPriority.Normal)
				};
				threads.Add(thread);
				thread.Start();
			}
		}

		internal static void Stop(bool byUser)
		{
			lock (gate)
			{
				if (!running)
				{
					return;
				}
				running = false;
				job++;
				KeyValuePair<Stage, string> result;
				while (items.TryDequeue(out result))
				{
				}
				fast = false;
				continueSmart = false;
				if (byUser)
				{
					finished = Plugin.T("Остановлено", "Stopped");
					finishedAt = Clock;
					Plugin.Log("preparing stopped");
				}
			}
			if (byUser)
			{
				Save();
			}
		}

		internal static void WorldClosing()
		{
			if (running)
			{
				Save();
			}
			fast = false;
			continueSmart = false;
			running = false;
			job++;
			KeyValuePair<Stage, string> result;
			while (items.TryDequeue(out result))
			{
			}
		}

		private static void Finish(int id, string why, bool exhausted = false)
		{
			lock (gate)
			{
				if (id != job || !running)
				{
					return;
				}
				running = false;
				job++;
				finished = why;
				finishedAt = Clock;
				if (mode == PrepareMode.Entry && exhausted && budget - WrittenBytes > 16777216)
				{
					continueSmart = true;
					continueBudget = budget - WrittenBytes;
				}
				fast = false;
			}
			Plugin.Log($"preparing finished: {why} ({Plugin.Megabytes(WrittenBytes)}, {Interlocked.Read(in itemsDone)} pieces)");
			Save();
		}

		internal static void OnWritten(string folder, long delta)
		{
			if (running && delta > 0)
			{
				Interlocked.Add(ref written, delta);
			}
		}

		private static List<Area> Plan(PrepareMode m)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			List<Area> list = new List<Area>();
			Player localPlayer = Player.m_localPlayer;
			Vector3 me = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero);
			float value = Plugin.AroundRadius.Value;
			switch (m)
			{
			case PrepareMode.Points:
				Add(InvitePoints, 200f, Plugin.P("Базы и порталы хоста", "Host's bases and portals"));
				break;
			case PrepareMode.AroundMe:
				list.Add(new Area
				{
					Pos = me,
					Radius = value,
					Phase = Plugin.P("Вокруг меня", "Around me")
				});
				Rings(value * 2f);
				break;
			case PrepareMode.WholeWorld:
				list.Add(new Area
				{
					Pos = me,
					Radius = value,
					Phase = Plugin.P("Вокруг меня", "Around me")
				});
				Rings(value * 2f);
				break;
			case PrepareMode.Entry:
				list.Add(new Area
				{
					Pos = me,
					Radius = value,
					Phase = Plugin.P("Вокруг меня", "Around me")
				});
				Add(Bases(), 200f, Plugin.P("Базы и порталы", "Bases and portals"));
				Add(Explored(), 0f, Plugin.P("Открытое на карте", "Explored on the map"));
				Add(Places(), 200f, Plugin.P("Алтари и найденные места", "Altars and found places"));
				break;
			default:
				list.Add(new Area
				{
					Pos = me,
					Radius = value,
					Phase = Plugin.P("Вокруг меня", "Around me")
				});
				Add(Bases(), 200f, Plugin.P("Базы и порталы", "Bases and portals"));
				Add(Explored(), 0f, Plugin.P("Открытое на карте", "Explored on the map"));
				Add(Places(), 200f, Plugin.P("Алтари и найденные места", "Altars and found places"));
				Rings(value * 2f);
				break;
			}
			foreach (Area item in list)
			{
				if (item.Radius <= 0f)
				{
					item.Radius = exploredCell * 0.75f;
				}
			}
			return list;
			void Add(IEnumerable<Vector3> points, float radius, string name)
			{
				//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_0045: 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)
				foreach (Vector3 item2 in points.Distinct().OrderBy(delegate(Vector3 p)
				{
					//IL_0000: Unknown result type (might be due to invalid IL or missing references)
					//IL_0002: Unknown result type (might be due to invalid IL or missing references)
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val = p - me;
					return ((Vector3)(ref val)).sqrMagnitude;
				}))
				{
					list.Add(new Area
					{
						Pos = item2,
						Radius = radius,
						Phase = name
					});
				}
			}
			void Rings(float from)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				for (float num = from; num <= 80000f; num *= 2f)
				{
					list.Add(new Area
					{
						Pos = me,
						Radius = num,
						Phase = Plugin.P("Остальное по расстоянию", "The rest by distance")
					});
				}
			}
		}

		private static IEnumerable<Vector3> Bases()
		{
			//IL_0031: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Invalid comparison between Unknown and I4
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Invalid comparison between Unknown and I4
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Invalid comparison between Unknown and I4
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Invalid comparison between Unknown and I4
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Invalid comparison between Unknown and I4
			List<Vector3> list = new List<Vector3>();
			Game instance = Game.instance;
			PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null);
			if (val != null)
			{
				if (val.HaveCustomSpawnPoint())
				{
					list.Add(val.GetCustomSpawnPoint());
				}
				list.Add(val.GetHomePoint());
			}
			if ((Object)(object)Minimap.instance != (Object)null)
			{
				foreach (PinData item in Pins.Invoke(Minimap.instance))
				{
					if ((int)item.m_type == 5 || (int)item.m_type == 0 || (int)item.m_type == 1 || (int)item.m_type == 2 || (int)item.m_type == 3 || (int)item.m_type == 6)
					{
						list.Add(item.m_pos);
					}
				}
			}
			list.AddRange(InvitePoints);
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				list.AddRange(Invite.HostPoints());
			}
			return list.Where((Vector3 p) => ((Vector3)(ref p)).sqrMagnitude > 1f);
		}

		private static IEnumerable<Vector3> Explored()
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			Minimap instance = Minimap.instance;
			List<Vector3> list = new List<Vector3>();
			if ((Object)(object)instance == (Object)null || MapPointToWorld == null)
			{
				return list;
			}
			BitArray bitArray = ExploredBits.Invoke(instance);
			int num = TextureSize.Invoke(instance);
			float num2 = PixelSize.Invoke(instance);
			if (bitArray == null || num <= 0)
			{
				return list;
			}
			int num3 = Mathf.Max(1, Mathf.RoundToInt(256f / Mathf.Max(1f, num2)));
			exploredCell = (float)num3 * num2;
			for (int i = num3 / 2; i < num; i += num3)
			{
				for (int j = num3 / 2; j < num; j += num3)
				{
					if (bitArray[i * num + j])
					{
						list.Add(MapPointToWorld(instance, ((float)j + 0.5f) / (float)num, ((float)i + 0.5f) / (float)num));
					}
				}
			}
			return list;
		}

		private static IEnumerable<Vector3> Places()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_005d: 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_0045: Invalid comparison between Unknown and I4
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			List<Vector3> list = new List<Vector3>();
			if ((Object)(object)Minimap.instance != (Object)null)
			{
				foreach (PinData item in Pins.Invoke(Minimap.instance))
				{
					if ((int)item.m_type == 9 || (int)item.m_type == 14 || (int)item.m_type == 15 || (int)item.m_type == 16)
					{
						list.Add(item.m_pos);
					}
				}
			}
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				list.AddRange(LocationIcons.Invoke(ZoneSystem.instance).Keys);
			}
			return list;
		}

		private static void Work(int id, int index)
		{
			try
			{
				while (id == job)
				{
					Store store = Stages.Store;
					if (store == null || (!fast && index >= Math.Max(1, Math.Min(8, Plugin.PrepareThreads.Value))))
					{
						break;
					}
					if (pauseReason != null || !Plugin.Enabled.Value)
					{
						Thread.Sleep(250);
						continue;
					}
					if (WrittenBytes >= budget)
					{
						Finish(id, Plugin.T("Готово: набран объём", "Done: the volume is reached"));
						break;
					}
					if (store.Total + store.Others >= (long)((double)Plugin.LimitBytes * 0.9))
					{
						Finish(id, Plugin.T("Достигнут лимит кэша", "The cache limit is reached"));
						break;
					}
					if (store.PendingBytes > 16777216)
					{
						Thread.Sleep(50);
						continue;
					}
					if (!items.TryDequeue(out var result))
					{
						if (!Expand(id))
						{
							if (Volatile.Read(in busy) == 0 && items.IsEmpty)
							{
								Finish(id, Plugin.T("Готово: всё подготовлено", "Done: everything is prepared"), exhausted: true);
								break;
							}
							Thread.Sleep(100);
						}
						continue;
					}
					Stage key = result.Key;
					if (key.State != StageState.Ready || Stages.Has(key.Id, result.Value))
					{
						continue;
					}
					Interlocked.Increment(ref busy);
					try
					{
						byte[] array = key.Generate(result.Value);
						if (array != null && id == job)
						{
							Stages.Save(key.Id, result.Value, "0", array);
							Interlocked.Increment(ref itemsDone);
						}
					}
					catch (Exception ex)
					{
						Plugin.Warn("prepare " + key.Id + " " + result.Value + ": " + ex.GetBaseException().Message);
					}
					finally
					{
						Interlocked.Decrement(ref busy);
					}
					int num = ((!fast) ? Plugin.PrepareSleepMs.Value : 0);
					if (num > 0)
					{
						Thread.Sleep(num);
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.Warn("prepare worker: " + ex2);
			}
		}

		private static bool Expand(int id)
		{
			Area area;
			lock (gate)
			{
				if (id != job)
				{
					return false;
				}
				if (!items.IsEmpty)
				{
					return true;
				}
				if (areaCursor >= areas.Count)
				{
					return false;
				}
				area = areas[areaCursor++];
				phase = area.Phase;
			}
			Interlocked.Increment(ref busy);
			try
			{
				foreach (Stage item in Stages.All())
				{
					if (item.KeysNear == null || item.Generate == null || item.State != StageState.Ready)
					{
						continue;
					}
					string[] array;
					try
					{
						array = item.KeysNear(area.Pos.x, area.Pos.y, area.Pos.z, area.Radius);
					}
					catch (Exception ex)
					{
						Plugin.Warn("prepare " + item.Id + ": keysNear failed: " + ex.GetBaseException().Message);
						continue;
					}
					if (array == null)
					{
						continue;
					}
					string[] array2 = array;
					foreach (string text in array2)
					{
						if (text != null && !Stages.Has(item.Id, text))
						{
							items.Enqueue(new KeyValuePair<Stage, string>(item, text));
						}
					}
				}
			}
			finally
			{
				Interlocked.Decrement(ref busy);
			}
			return true;
		}

		internal static void Tick()
		{
			float num = Mathf.Max(0.0001f, Time.unscaledDeltaTime);
			fpsAverage = Mathf.Lerp(fpsAverage, 1f / num, Mathf.Clamp01(num * 2f));
			if (!running)
			{
				pauseReason = null;
				if (continueSmart && Stages.Open && (Object)(object)Player.m_localPlayer != (Object)null)
				{
					continueSmart = false;
					Start(PrepareMode.Smart, continueBudget);
				}
				return;
			}
			if (fast)
			{
				pauseReason = null;
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			string text = null;
			if ((Object)(object)localPlayer == (Object)null)
			{
				text = Plugin.T("нет игрока", "no player");
			}
			else if (((Character)localPlayer).IsTeleporting() || Time.unscaledTime < startDelay)
			{
				text = Plugin.T("загрузка", "loading");
			}
			else if (Plugin.PauseInCombat.Value && (localPlayer.IsSensed() || localPlayer.IsTargeted()))
			{
				text = Plugin.T("бой", "combat");
			}
			else if (fpsAverage < (float)Plugin.MinFps.Value)
			{
				text = Plugin.T("мало FPS", "low FPS");
			}
			if (text != null)
			{
				goodSince = Time.unscaledTime;
				pauseReason = text;
			}
			else if (pauseReason == null || !(Time.unscaledTime - goodSince < 3f))
			{
				pauseReason = null;
			}
		}

		internal static void OnPlayerSpawned()
		{
			startDelay = Time.unscaledTime + 10f;
			continueSmart = false;
			if (running || !Stages.Open)
			{
				return;
			}
			State state = Load();
			if (state == null || !state.Running)
			{
				return;
			}
			lock (gate)
			{
				mode = ((state.Mode != PrepareMode.Entry) ? state.Mode : PrepareMode.Smart);
				budget = state.Budget;
				fast = false;
				Interlocked.Exchange(ref written, state.Written);
				Interlocked.Exchange(ref itemsDone, 0L);
				if (state.Points != null)
				{
					InvitePoints = state.Points;
				}
				Begin();
			}
			Plugin.Log("preparing the world carries on: " + ModeName(mode) + ", " + Plugin.Megabytes(WrittenBytes) + " of " + Plugin.Megabytes(budget));
		}

		private static void Save()
		{
			Store store = Stages.Store;
			if (store == null)
			{
				return;
			}
			try
			{
				CultureInfo c = CultureInfo.InvariantCulture;
				List<string> contents = new List<string>
				{
					"running=" + (running ? "1" : "0"),
					"mode=" + mode,
					"budget=" + budget.ToString(c),
					"written=" + WrittenBytes.ToString(c),
					"points=" + string.Join(";", InvitePoints.Select((Vector3 p) => p.x.ToString("R", c) + "," + p.y.ToString("R", c) + "," + p.z.ToString("R", c)))
				};
				File.WriteAllLines(Path.Combine(store.Root, "prepare.txt"), contents);
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
			{
				Plugin.Warn("prepare.txt: " + ex.Message);
			}
		}

		private static State Load()
		{
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			Store store = Stages.Store;
			string text = ((store != null) ? Path.Combine(store.Root, "prepare.txt") : null);
			if (text == null || !File.Exists(text))
			{
				return null;
			}
			try
			{
				CultureInfo invariantCulture = CultureInfo.InvariantCulture;
				Dictionary<string, string> dictionary = (from l in File.ReadAllLines(text)
					select l.Split(new char[1] { '=' }, 2) into p
					where p.Length == 2
					select p).ToDictionary((string[] p) => p[0], (string[] p) => p[1]);
				string value;
				string value2;
				PrepareMode result;
				string value3;
				string value4;
				State state = new State
				{
					Running = (dictionary.TryGetValue("running", out value) && value == "1"),
					Mode = ((dictionary.TryGetValue("mode", out value2) && Enum.TryParse<PrepareMode>(value2, out result)) ? result : PrepareMode.Smart),
					Budget = (dictionary.TryGetValue("budget", out value3) ? long.Parse(value3, invariantCulture) : 1073741824),
					Written = (dictionary.TryGetValue("written", out value4) ? long.Parse(value4, invariantCulture) : 0),
					Points = new List<Vector3>()
				};
				if (dictionary.TryGetValue("points", out var value5))
				{
					string[] array = value5.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
					for (int num = 0; num < array.Length; num++)
					{
						string[] array2 = array[num].Split(',');
						if (array2.Length == 3)
						{
							state.Points.Add(new Vector3(float.Parse(array2[0], invariantCulture), float.Parse(array2[1], invariantCulture), float.Parse(array2[2], invariantCulture)));
						}
					}
				}
				return state;
			}
			catch (Exception ex)
			{
				Plugin.Warn("prepare.txt unreadable: " + ex.Message);
				return null;
			}
		}

		internal static void DrawOverlay()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			bool flag = finished != null && Clock - finishedAt < 6f;
			if (Plugin.ShowProgress.Value && (running || flag) && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				if ((Object)(object)white == (Object)null)
				{
					white = new Texture2D(1, 1);
					white.SetPixel(0, 0, Color.white);
					white.Apply();
				}
				if (label == null)
				{
					label = new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)4,
						fontSize = Mathf.RoundToInt(13f * Mathf.Max(1f, (float)Screen.height / 1080f))
					};
				}
				float num = Mathf.Max(1f, (float)Screen.height / 1080f);
				float num2 = 420f * num;
				float num3 = 40f * num;
				float num4 = ((float)Screen.width - num2) / 2f;
				float num5 = 8f * num;
				Color color = GUI.color;
				GUI.color = new Color(0f, 0f, 0f, 0.55f);
				GUI.DrawTexture(new Rect(num4, num5, num2, num3), (Texture)(object)white);
				GUI.color = new Color(0.35f, 0.75f, 1f, 0.85f);
				GUI.DrawTexture(new Rect(num4 + 3f, num5 + num3 - 7f * num, (num2 - 6f) * Progress, 4f * num), (Texture)(object)white);
				GUI.color = color;
				string text = (running ? (Plugin.T("Подготовка мира", "Preparing the world") + $": {Progress * 100f:0} % · {Plugin.Megabytes(WrittenBytes)} / {Plugin.Megabytes(budget)} · {Phase}" + ((pauseReason != null) ? (" · " + Plugin.T("пауза", "paused") + ": " + pauseReason) : "")) : finished);
				GUI.Label(new Rect(num4, num5, num2, num3 - 6f * num), text, label);
			}
		}
	}
	[HarmonyPatch(typeof(GraphicsSettings), "Initialize")]
	internal static class SettingsMenu
	{
		private sealed class Live : MonoBehaviour
		{
			internal Action Refresh;

			private float next;

			private void Update()
			{
				if (Time.unscaledTime < next)
				{
					return;
				}
				next = Time.unscaledTime + 0.5f;
				try
				{
					Refresh?.Invoke();
				}
				catch (Exception ex)
				{
					Plugin.Debug("settings row: " + ex.Message);
				}
			}
		}

		private sealed class Button2
		{
			internal Func<string> Label;

			internal Action Click;

			internal Func<bool> Enabled;

			internal Button2(string label, Action click, Func<bool> enabled)
			{
				Label = () => label;
				Click = click;
				Enabled = enabled;
			}

			internal Button2(Func<string> label, Action click, Func<bool> enabled)
			{
				Label = label;
				Click = click;
				Enabled = enabled;
			}
		}

		private static GameObject toggleRow;

		private static GameObject stepperRow;

		private static GameObject button;

		private static GameObject warning;

		private static Transform parent;

		private static float nextUsage;

		private static string usage = "";

		private static void Postfix(GraphicsSettings __instance)
		{
			try
			{
				Add(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Warn("game settings: the cache's rows were not added: " + ex);
			}
		}

		private static void Add(GraphicsSettings tab)
		{
			//IL_05a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05aa: Expected O, but got Unknown
			//IL_05bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05c1: Unknown result type (might be due to invalid IL or missing references)
			Traverse obj = Traverse.Create((object)tab);
			Toggle value = obj.Field("m_vsyncToggle").GetValue<Toggle>();
			Button value2 = obj.Field("m_graphicPresetLeft").GetValue<Button>();
			Component value3 = obj.Field("m_testResolutionButton").GetValue<Component>();
			GameObject value4 = obj.Field("m_qualitySliderPrefab").GetValue<GameObject>();
			RectTransform value5 = obj.Field("m_listRoot").GetValue<RectTransform>();
			VerticalLayoutGroup value6 = obj.Field("m_verticalLayoutGroup").GetValue<VerticalLayoutGroup>();
			toggleRow = (((Object)(object)value != (Object)null) ? ((Component)value).gameObject : null);
			object obj2;
			if (!((Object)(object)value2 != (Object)null))
			{
				obj2 = null;
			}
			else
			{
				Transform obj3 = ((Component)value2).transform.parent;
				if (obj3 == null)
				{
					obj2 = null;
				}
				else
				{
					Transform obj4 = obj3.parent;
					obj2 = ((obj4 != null) ? ((Component)obj4).gameObject : null);
				}
			}
			stepperRow = (GameObject)obj2;
			button = (((Object)(object)value3 != (Object)null) ? value3.gameObject : null);
			object obj5;
			if (!((Object)(object)value4 != (Object)null))
			{
				obj5 = null;
			}
			else
			{
				Transform obj6 = value4.transform.Find("Info/Warning");
				obj5 = ((obj6 != null) ? ((Component)obj6).gameObject : null);
			}
			warning = (GameObject)obj5;
			if ((Object)(object)toggleRow == (Object)null || (Object)(object)stepperRow == (Object)null || (Object)(object)stepperRow.transform.Find("Stepper/Value/Text") == (Object)null || (Object)(object)button == (Object)null || (Object)(object)value5 == (Object)null)
			{
				Plugin.Warn("game settings: the graphics tab is not as expected (the game changed?): the cache's rows are not added");
				return;
			}
			parent = stepperRow.transform.parent;
			Heading(Plugin.T("Кэш мира - WorldCache", "World cache - WorldCache"));
			Toggle(Plugin.T("Кэш мира", "World cache"), Plugin.T("Сгенерированные места хранятся на диске и при следующем визите читаются, а не считаются заново: быстрее загрузка ценой места. Сохранение мира не меняется; кэш можно удалить в любой момент.", "Generated places are kept on the disk and read on the next visit instead of being generated again. The world's save does not change; the cache can be deleted at any time."), () => Plugin.Enabled.Value, delegate(bool v)
			{
				Plugin.Enabled.Value = v;
			});
			Toggle(Plugin.T("Спрашивать при первом входе", "Ask on the first entry"), Plugin.T("Вход в мир без кэша: предупреждение на экране загрузки и выбор - «Ждать» (подготовить ближайшие места сейчас) или «Играть» (кэшировать по ходу игры и в фоне).", "Entering a world without a cache: a note on the loading screen and a choice - Wait (prepare the nearest places now) or Play (cache as you go and in the background)."), () => Plugin.AskOnFirstEntry.Value, delegate(bool v)
			{
				Plugin.AskOnFirstEntry.Value = v;
			});
			Stepper(Plugin.T("Лимит места", "Disk limit"), Plugin.T("Сколько места занимают кэши всех миров вместе. Сверх - удаляется давно не нужное: сначала другие миры, потом старые места этого.", "How much the caches of all worlds take together. Beyond it, what was unused the longest goes: other worlds first, then old places of this one."), Plugin.Limits.Length, () => Nearest(Plugin.Limits, Plugin.LimitMegabytes.Value), delegate(int i)
			{
				Plugin.LimitMegabytes.Value = Plugin.Limits[i];
			}, (int i) => Size(Plugin.Limits[i]));
			Field(Plugin.T("Занято", "Used"), Plugin.T("Сколько сейчас занимают кэши всех миров (в скобках - этот мир).", "What the caches of all worlds take now (this world in brackets)."), UsageShort);
			Buttons(Plugin.T("Очистить кэш", "Clear the cache"), Plugin.T("Удаляет кэш. Из сохранения ничего не теряется: места просто будут сгенерированы заново.", "Deletes the cache. Nothing of the save is lost: places are simply generated again."), new Button2(Plugin.T("Этот мир", "This world"), delegate
			{
				Plugin.ClearCache(allWorlds: false, Report);
			}, () => Stages.Open), new Button2(Plugin.T("Все миры", "All worlds"), delegate
			{
				Plugin.ClearCache(allWorlds: true, Report);
			}, null));
			Stepper(Plugin.T("Подготовка: режим", "Preparing: mode"), Plugin.T("«Умно» - вокруг вас, базы и порталы, открытое на карте, алтари и найденные места, потом остальное по расстоянию. «Вокруг меня» - кольцами от вас. «Весь мир» - до лимита места.", "Smart: around you, bases and portals, what the map shows explored, altars and found places, then the rest by distance. Around me: rings from you. Whole world: up to the disk limit."), 3, () => Math.Min(2, (int)Plugin.PrepareModeSetting.Value), delegate(int i)
			{
				Plugin.PrepareModeSetting.Value = (PrepareMode)i;
			}, (int i) => Prepare.ModeName((PrepareMode)i));
			Stepper(Plugin.T("Подготовка: объём", "Preparing: volume"), Plugin.T("Сколько места на диске занять подготовкой. «Своё» - значение CustomMB в настройках мода.", "How much disk space the preparation may take. Custom: CustomMB in the mod's configuration."), Plugin.Volumes.Length + 1, () => (Plugin.VolumeMegabytes.Value > 0) ? Nearest(Plugin.Volumes, Plugin.VolumeMegabytes.Value) : Plugin.Volumes.Length, delegate(int i)
			{
				Plugin.VolumeMegabytes.Value = ((i < Plugin.Volumes.Length) ? Plugin.Volumes[i] : 0);
			}, (int i) => (i >= Plugin.Volumes.Length) ? (Plugin.T("Своё ", "Custom ") + Size(Math.Max(100, Plugin.CustomMegabytes.Value))) : Size(Plugin.Volumes[i]));
			Buttons(Plugin.T("Подготовить мир", "Prepare the world"), Plugin.T("Заранее генерирует данные мест (без объектов мира) в фоне малыми порциями; пауза при низком FPS, в бою и при загрузке. Вверху экрана - полоска. Продолжает при следующем входе, пока не готово или не остановлено.", "Generates the places' data ahead (no objects of the world) in the background in small portions; pauses on low FPS, in combat and while loading. A bar at the top of the screen. Carries on at the next visit until done or stopped."), new Button2(() => (!Prepare.Running) ? Plugin.T("Подготовить", "Prepare") : Plugin.T("Остановить", "Stop"), delegate
			{
				if (Prepare.Running)
				{
					Prepare.Stop(byUser: true);
				}
				else
				{
					Prepare.Start(Plugin.PrepareModeSetting.Value, Plugin.VolumeChoice() << 20);
				}
			}, () => Stages.Open && (Object)(object)Player.m_localPlayer != (Object)null));
			Field(Plugin.T("Ход подготовки", "Progress"), Plugin.T("Сколько подготовлено из выбранного объёма и что сейчас готовится.", "How much of the chosen volume is prepared and what is being prepared now."), ProgressShort);
			float num = 0f;
			for (int num2 = 0; num2 < ((Transform)value5).childCount; num2++)
			{
				RectTransform val = (RectTransform)((Transform)value5).GetChild(num2);
				if (((Component)val).gameObject.activeSelf)
				{
					float num3 = num;
					Rect rect = val.rect;