Decompiled source of ValheimEnhancedEdition v1.1.0

BepInEx/plugins/ValheimEnhancedEdition/ValheimEnhancedEdition.dll

Decompiled 3 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.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Jobs;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[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 PerfMod
{
	internal static class CreateDestroySkip
	{
		private static readonly FieldRef<ZNetScene, List<ZDO>> Near = AccessTools.FieldRefAccess<ZNetScene, List<ZDO>>("m_tempCurrentObjects");

		private static readonly FieldRef<ZNetScene, List<ZDO>> Distant = AccessTools.FieldRefAccess<ZNetScene, List<ZDO>>("m_tempCurrentDistantObjects");

		private static readonly FieldRef<ZNetScene, List<ZNetView>> Removed = AccessTools.FieldRefAccess<ZNetScene, List<ZNetView>>("m_tempRemoved");

		private static long s_version;

		private static long s_versionAtFull = -1L;

		private static Vector2s s_zoneAtFull;

		private static int s_nearDistAtFull = -1;

		private static int s_totalDistAtFull = -1;

		private static int s_createdThisCall;

		private static bool s_classicAtFull;

		private static bool s_lastFullIdle;

		private static bool s_skipped;

		private static float s_lastFullTime = -100f;

		internal static void Patch(Harmony h)
		{
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Expected O, but got Unknown
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Expected O, but got Unknown
			P(typeof(ZNetScene), "CreateDestroyObjects", "Pre", "Post");
			P(typeof(ZNetScene), "CreateObject", null, "Created");
			P(typeof(ZDOMan), "AddToSector", null, "Bump");
			P(typeof(ZDOMan), "InitialAddToSector", null, "Bump");
			P(typeof(ZDOMan), "RemoveFromSector", null, "Bump");
			P(typeof(ZDOMan), "AddIfPortal", null, "Bump");
			P(typeof(ZDOMan), "HandleDestroyedZDO", null, "Bump");
			P(typeof(ZDOMan), "DestroyZDO", null, "Bump");
			P(typeof(ZDOMan), "Load", null, "Bump");
			P(typeof(ZDOMan), "LoadChunks", null, "Bump");
			h.Patch((MethodBase)AccessTools.PropertySetter(typeof(ZDO), "Distant"), (HarmonyMethod)null, new HarmonyMethod(typeof(CreateDestroySkip), "Bump", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			P(typeof(ZNetScene), "AddInstance", null, "Bump");
			h.Patch((MethodBase)AccessTools.Method(typeof(ZNetScene), "Destroy", new Type[1] { typeof(GameObject) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CreateDestroySkip), "Bump", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			P(typeof(ZNetScene), "OnZDODestroyed", null, "Bump");
			void P(Type t, string m, string pre = null, string post = null)
			{
				//IL_0022: 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)
				h.Patch((MethodBase)AccessTools.Method(t, m, (Type[])null, (Type[])null), (pre == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(CreateDestroySkip), pre, (Type[])null), (post == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(CreateDestroySkip), post, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Bump()
		{
			s_version++;
		}

		private static void Created()
		{
			s_createdThisCall++;
		}

		private static bool Pre()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			s_createdThisCall = 0;
			s_skipped = false;
			if (!Plugin.CreateSkipOn.Value || (Object)(object)ZNet.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return true;
			}
			Vector2s zone = ZoneSystem.GetZone(ZNet.instance.GetReferencePosition());
			SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
			if (!s_lastFullIdle || s_version != s_versionAtFull || !(zone == s_zoneAtFull) || ((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance != s_nearDistAtFull || ((SimulationDistance)(ref syncedSimulationDistance)).TotalSimulationDistance != s_totalDistAtFull || ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic != s_classicAtFull || !(Time.time - s_lastFullTime < 1f))
			{
				return true;
			}
			s_skipped = true;
			return false;
		}

		private static void Post(ZNetScene __instance)
		{
			//IL_00b1: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			if (s_skipped)
			{
				s_skipped = false;
				return;
			}
			List<ZDO> list = Near.Invoke(__instance);
			List<ZDO> list2 = Distant.Invoke(__instance);
			bool flag = false;
			for (int i = 0; i < list.Count; i++)
			{
				if (flag)
				{
					break;
				}
				if (!list[i].Created)
				{
					flag = true;
				}
			}
			for (int j = 0; j < list2.Count; j++)
			{
				if (flag)
				{
					break;
				}
				if (!list2[j].Created)
				{
					flag = true;
				}
			}
			s_lastFullIdle = s_createdThisCall == 0 && Removed.Invoke(__instance).Count == 0 && !flag;
			s_versionAtFull = s_version;
			s_zoneAtFull = ZoneSystem.GetZone(ZNet.instance.GetReferencePosition());
			SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
			s_nearDistAtFull = ((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance;
			s_totalDistAtFull = ((SimulationDistance)(ref syncedSimulationDistance)).TotalSimulationDistance;
			s_classicAtFull = ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic;
			s_lastFullTime = Time.time;
		}
	}
	internal static class FarLandsLink
	{
		internal const string Guid = "ru.ivest.farlands";

		private static bool s_looked;

		private static bool s_present;

		private static bool s_told;

		private static Func<bool> s_active;

		private static Func<float> s_size;

		private static ConfigEntry<bool> s_enabled;

		private static ConfigEntry<float> s_terrain;

		private static ConfigEntry<float> s_forest;

		private static ConfigEntry<bool> s_bEnabled;

		private static ConfigEntry<float> s_bDistance;

		private static ConfigEntry<float> s_bHandoff;

		private static long s_warnedWorld = -1L;

		private static float s_next = -1f;

		private static bool s_drawing;

		private static float s_reach;

		private static float s_forestRange;

		private static bool s_loggedDrawing;

		private static float s_loggedReach;

		internal const string BuildingsWarning = "FarLands far buildings are on: set [Buildings] Enabled = false in ru.ivest.farlands.cfg, or bases are drawn twice.";

		internal static bool Present
		{
			get
			{
				Look();
				return s_present;
			}
		}

		internal static bool Drawing
		{
			get
			{
				Refresh();
				return s_drawing;
			}
		}

		internal static float Reach
		{
			get
			{
				Refresh();
				return s_reach;
			}
		}

		internal static float ForestRange
		{
			get
			{
				Refresh();
				return s_forestRange;
			}
		}

		internal static bool BuildingsOn
		{
			get
			{
				Look();
				if (s_present && s_bEnabled != null)
				{
					return s_bEnabled.Value;
				}
				return false;
			}
		}

		private static void Look()
		{
			if (s_looked)
			{
				return;
			}
			s_looked = true;
			try
			{
				if (Chainloader.PluginInfos.TryGetValue("ru.ivest.farlands", out var value) && !((Object)(object)value.Instance == (Object)null))
				{
					s_present = true;
					ConfigFile config = value.Instance.Config;
					config.TryGetEntry<bool>("General", "Enabled", ref s_enabled);
					config.TryGetEntry<float>("General", "TerrainDistance", ref s_terrain);
					config.TryGetEntry<float>("General", "ForestDistance", ref s_forest);
					config.TryGetEntry<bool>("Buildings", "Enabled", ref s_bEnabled);
					config.TryGetEntry<float>("Buildings", "Distance", ref s_bDistance);
					config.TryGetEntry<float>("Buildings", "HandoffDistance", ref s_bHandoff);
					Type? type = ((object)value.Instance).GetType().Assembly.GetType("FarLands.FarLandsReach", throwOnError: false);
					MethodInfo methodInfo = type?.GetProperty("Active", BindingFlags.Static | BindingFlags.Public)?.GetGetMethod();
					MethodInfo methodInfo2 = type?.GetProperty("Size", BindingFlags.Static | BindingFlags.Public)?.GetGetMethod();
					if (methodInfo != null && methodInfo2 != null)
					{
						s_active = (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), methodInfo);
						s_size = (Func<float>)Delegate.CreateDelegate(typeof(Func<float>), methodInfo2);
					}
					Plugin.Log.LogInfo((object)(string.Format("far builds: FarLands {0} found ({1}; land {2:0} m, trees {3:0} m; ", value.Metadata.Version, (s_active != null) ? "its live reach read" : "its settings only", F(s_terrain, 4000f), F(s_forest, 2500f)) + ((s_bEnabled == null) ? "no far buildings of its own)" : string.Format("its far buildings {0}, to {1:0} m, from {2:0} m)", s_bEnabled.Value ? "on" : "off", F(s_bDistance, 2500f), F(s_bHandoff, 250f)))));
				}
			}
			catch (Exception e)
			{
				Fail(e);
			}
		}

		private static void Fail(Exception e)
		{
			s_active = null;
			s_size = null;
			if (!s_told)
			{
				s_told = true;
				Plugin.Log.LogWarning((object)("far builds: FarLands could not be read (" + e.Message + "); going by its settings, or as if it were absent"));
			}
		}

		private static float F(ConfigEntry<float> e, float def)
		{
			return e?.Value ?? def;
		}

		private static void Refresh()
		{
			if (Time.unscaledTime < s_next && s_next >= 0f)
			{
				return;
			}
			s_next = Time.unscaledTime + 0.25f;
			Look();
			s_drawing = false;
			s_reach = 0f;
			s_forestRange = 0f;
			if (!s_present)
			{
				return;
			}
			float num = F(s_terrain, 4000f);
			if (s_active != null)
			{
				try
				{
					s_drawing = s_active();
					if (s_drawing)
					{
						num = s_size() * 0.5f;
					}
				}
				catch (Exception e)
				{
					Fail(e);
					s_drawing = s_enabled == null || s_enabled.Value;
				}
			}
			else
			{
				s_drawing = s_enabled == null || s_enabled.Value;
			}
			if (s_drawing)
			{
				s_reach = Mathf.Max(0f, num);
				s_forestRange = Mathf.Clamp(F(s_forest, 2500f), 0f, s_reach);
			}
			if (s_drawing != s_loggedDrawing || Mathf.Abs(s_reach - s_loggedReach) > 1f)
			{
				s_loggedDrawing = s_drawing;
				s_loggedReach = s_reach;
				Plugin.Log.LogInfo((object)(s_drawing ? $"far builds: FarLands drawing (land {s_reach:0} m, its trees {s_forestRange:0} m)" : "far builds: FarLands not drawing"));
			}
		}

		internal static void WarnBuildings()
		{
			if (!BuildingsOn)
			{
				return;
			}
			long num = ((ZNet.World != null) ? ZNet.World.m_uid : 0);
			if (num == s_warnedWorld)
			{
				return;
			}
			s_warnedWorld = num;
			Plugin.Log.LogWarning((object)"FarLands far buildings are on: set [Buildings] Enabled = false in ru.ivest.farlands.cfg, or bases are drawn twice.");
			try
			{
				Console instance = Console.instance;
				if (instance != null)
				{
					instance.Print("Valheim Enhanced Edition: FarLands far buildings are on: set [Buildings] Enabled = false in ru.ivest.farlands.cfg, or bases are drawn twice.");
				}
				MessageHud instance2 = MessageHud.instance;
				if (instance2 != null)
				{
					instance2.ShowMessage((MessageType)2, "FarLands far buildings are on: set [Buildings] Enabled = false in ru.ivest.farlands.cfg, or bases are drawn twice.", 0, (Sprite)null, false, true);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("far builds: the FarLands warning wasn't shown on screen: " + ex.Message));
			}
		}
	}
	internal struct FarEntry
	{
		public int Prefab;

		public float X;

		public float Y;

		public float Z;

		public float QX;

		public float QY;

		public float QZ;

		public float QW;

		public bool Scaled;

		public float SX;

		public float SY;

		public float SZ;

		public bool Snow;
	}
	internal static class FarManifest
	{
		internal const int Protocol = 1;

		internal const float Area = 64f;

		internal const float Zone = 64f;

		private const long TerrainMark = 9103745171752747008L;

		internal static long AreaOf(float x, float z)
		{
			return ((long)(int)Math.Floor(x / 64f) << 32) ^ (uint)(int)Math.Floor(z / 64f);
		}

		internal static long TerrainId(int zoneX, int zoneZ)
		{
			return (long)(0x7E57000000000000L | ((ulong)(ushort)(short)zoneX << 16) | (ushort)(short)zoneZ);
		}

		internal static bool IsTerrain(long id)
		{
			return (id & -281474976710656L) == 9103745171752747008L;
		}

		internal static void TerrainZone(long id, out int zoneX, out int zoneZ)
		{
			zoneX = (short)(ushort)(id >> 16);
			zoneZ = (short)(ushort)id;
		}

		internal static void TerrainOrigin(long id, out float x, out float z)
		{
			TerrainZone(id, out var zoneX, out var zoneZ);
			x = (float)zoneX * 64f - 32f;
			z = (float)zoneZ * 64f - 32f;
		}

		internal static uint HashBytes(byte[] b)
		{
			uint num = 2166136261u;
			foreach (byte b2 in b)
			{
				num ^= b2;
				num *= 16777619;
			}
			if (num != 0)
			{
				return num;
			}
			return 1u;
		}

		internal static void OriginOf(long id, out float x, out float z)
		{
			x = (float)(int)(id >> 32) * 64f;
			z = (float)(int)id * 64f;
		}

		private static ushort Local(float v, float origin)
		{
			return (ushort)Math.Max(0, Math.Min(65535, (int)Math.Round((v - origin) / 64f * 65535f)));
		}

		private static float Unlocal(ushort v, float origin)
		{
			return origin + (float)(int)v * 0.0009765774f;
		}

		private static sbyte Q(float v)
		{
			return (sbyte)Math.Max(-127, Math.Min(127, (int)Math.Round(v * 127f)));
		}

		internal static FarEntry Quantize(long id, FarEntry e)
		{
			OriginOf(id, out var x, out var z);
			FarEntry result = e;
			result.X = Unlocal(Local(e.X, x), x);
			result.Z = Unlocal(Local(e.Z, z), z);
			float num = Q(e.QX);
			float num2 = Q(e.QY);
			float num3 = Q(e.QZ);
			float num4 = Q(e.QW);
			float num5 = (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3 + num4 * num4);
			if (num5 < 1E-06f)
			{
				num4 = 1f;
				num5 = 1f;
			}
			result.QX = num / num5;
			result.QY = num2 / num5;
			result.QZ = num3 / num5;
			result.QW = num4 / num5;
			return result;
		}

		internal static void Write(BinaryWriter w, long id, List<FarEntry> list)
		{
			OriginOf(id, out var x, out var z);
			w.Write(id);
			w.Write(list.Count);
			foreach (FarEntry item in list)
			{
				w.Write(item.Prefab);
				w.Write(Local(item.X, x));
				w.Write(Local(item.Z, z));
				w.Write(item.Y);
				w.Write(Q(item.QX));
				w.Write(Q(item.QY));
				w.Write(Q(item.QZ));
				w.Write(Q(item.QW));
				w.Write((byte)((item.Scaled ? 1u : 0u) | (uint)(item.Snow ? 2 : 0)));
				if (item.Scaled)
				{
					w.Write(item.SX);
					w.Write(item.SY);
					w.Write(item.SZ);
				}
			}
		}

		internal static List<FarEntry> Read(BinaryReader r, out long id)
		{
			id = r.ReadInt64();
			return ReadBody(r, id);
		}

		internal static List<FarEntry> ReadBody(BinaryReader r, long id)
		{
			int num = r.ReadInt32();
			if (num < 0 || num > 1000000)
			{
				throw new InvalidDataException("bad piece count " + num);
			}
			OriginOf(id, out var x, out var z);
			List<FarEntry> list = new List<FarEntry>(num);
			for (int i = 0; i < num; i++)
			{
				FarEntry item = new FarEntry
				{
					Prefab = r.ReadInt32()
				};
				item.X = Unlocal(r.ReadUInt16(), x);
				item.Z = Unlocal(r.ReadUInt16(), z);
				item.Y = r.ReadSingle();
				float num2 = r.ReadSByte();
				float num3 = r.ReadSByte();
				float num4 = r.ReadSByte();
				float num5 = r.ReadSByte();
				float num6 = (float)Math.Sqrt(num2 * num2 + num3 * num3 + num4 * num4 + num5 * num5);
				if (num6 < 1E-06f)
				{
					num5 = 1f;
					num6 = 1f;
				}
				item.QX = num2 / num6;
				item.QY = num3 / num6;
				item.QZ = num4 / num6;
				item.QW = num5 / num6;
				byte b = r.ReadByte();
				item.Scaled = (b & 1) != 0;
				item.Snow = (b & 2) != 0;
				if (item.Scaled)
				{
					item.SX = r.ReadSingle();
					item.SY = r.ReadSingle();
					item.SZ = r.ReadSingle();
				}
				list.Add(item);
			}
			return list;
		}

		internal static uint Hash(long id, List<FarEntry> list)
		{
			MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter w = new BinaryWriter(memoryStream))
			{
				Write(w, id, Sorted(list));
			}
			byte[] array = memoryStream.ToArray();
			uint num = 2166136261u;
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				num ^= b;
				num *= 16777619;
			}
			if (num != 0)
			{
				return num;
			}
			return 1u;
		}

		internal static List<FarEntry> Sorted(List<FarEntry> list)
		{
			List<FarEntry> list2 = new List<FarEntry>(list);
			list2.Sort(delegate(FarEntry a, FarEntry b)
			{
				int num = a.Prefab.CompareTo(b.Prefab);
				if (num != 0)
				{
					return num;
				}
				num = a.X.CompareTo(b.X);
				if (num != 0)
				{
					return num;
				}
				num = a.Z.CompareTo(b.Z);
				return (num != 0) ? num : a.Y.CompareTo(b.Y);
			});
			return list2;
		}

		internal static byte[] WriteIndex(Dictionary<long, uint> index)
		{
			MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
			{
				binaryWriter.Write(1);
				binaryWriter.Write(index.Count);
				foreach (KeyValuePair<long, uint> item in index)
				{
					binaryWriter.Write(item.Key);
					binaryWriter.Write(item.Value);
				}
			}
			return memoryStream.ToArray();
		}

		internal static Dictionary<long, uint> ReadIndex(byte[] data)
		{
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(data));
			if (binaryReader.ReadInt32() != 1)
			{
				return null;
			}
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 10000000)
			{
				throw new InvalidDataException("bad index size " + num);
			}
			Dictionary<long, uint> dictionary = new Dictionary<long, uint>(num);
			for (int i = 0; i < num; i++)
			{
				dictionary[binaryReader.ReadInt64()] = binaryReader.ReadUInt32();
			}
			return dictionary;
		}
	}
	internal static class FarNet
	{
		private class HashJob
		{
			public volatile bool Done;

			public Dictionary<long, uint> Result;

			public Exception Error;
		}

		private const string Hello = "VEE_FarHello";

		private const string Index = "VEE_FarIndex";

		private const string Want = "VEE_FarWant";

		private const string Cells = "VEE_FarCells";

		private const string Changed = "VEE_FarChanged";

		private static ZRoutedRpc s_registeredOn;

		private static int s_errors;

		private static readonly HashSet<long> s_subs = new HashSet<long>();

		private static readonly Dictionary<long, uint> s_index = new Dictionary<long, uint>();

		private static readonly Dictionary<long, List<FarEntry>> s_lists = new Dictionary<long, List<FarEntry>>();

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

		private static List<ZDO> s_scan;

		private static int s_scanAt;

		private static long s_scanTicks;

		private static int s_scanFrames;

		private static float s_scanStarted;

		private static Dictionary<long, List<FarEntry>> s_scanLists;

		private static bool s_scanned;

		private static float s_nextScan;

		private static HashSet<int> s_pieceHashes;

		private static HashSet<int> s_syncScale;

		private static HashSet<int> s_terrainHashes;

		private static Dictionary<long, byte[]> s_scanTerrain;

		private static readonly Dictionary<long, byte[]> s_terrain = new Dictionary<long, byte[]>();

		private const float RescanSeconds = 20f;

		private static HashJob s_hashing;

		private const int ReplyBytes = 65536;

		private static float s_helloAt = -1f;

		private static Dictionary<long, uint> s_remote;

		private static bool s_inFlight;

		private static bool s_told;

		private static float s_sentAt;

		private static float s_nextWant;

		private static bool Serving
		{
			get
			{
				if (Plugin.FarServeOn != null)
				{
					return Plugin.FarServeOn.Value;
				}
				return true;
			}
		}

		internal static bool RemoteReady => s_remote != null;

		internal static bool NoServerMod
		{
			get
			{
				if (s_remote == null && s_helloAt >= 0f)
				{
					return Time.unscaledTime - s_helloAt > 20f;
				}
				return false;
			}
		}

		internal static void Tick()
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				if (s_registeredOn != null)
				{
					Reset();
				}
				return;
			}
			if (s_registeredOn != ZRoutedRpc.instance)
			{
				Reset();
				s_registeredOn = ZRoutedRpc.instance;
				ZRoutedRpc.instance.Register<ZPackage>("VEE_FarHello", (Action<long, ZPackage>)OnHello);
				ZRoutedRpc.instance.Register<ZPackage>("VEE_FarWant", (Action<long, ZPackage>)OnWant);
				ZRoutedRpc.instance.Register<ZPackage>("VEE_FarIndex", (Action<long, ZPackage>)OnIndex);
				ZRoutedRpc.instance.Register<ZPackage>("VEE_FarCells", (Action<long, ZPackage>)OnCells);
				ZRoutedRpc.instance.Register<ZPackage>("VEE_FarChanged", (Action<long, ZPackage>)OnChanged);
			}
			try
			{
				if (ZNet.instance.IsServer())
				{
					ServerTick();
				}
				else
				{
					ClientTick();
				}
			}
			catch (Exception ex)
			{
				if (s_errors++ < 5)
				{
					Plugin.Log.LogWarning((object)("far view on servers: " + ex));
				}
			}
		}

		private static void Reset()
		{
			s_registeredOn = null;
			s_subs.Clear();
			s_index.Clear();
			s_lists.Clear();
			s_terrain.Clear();
			s_scan = null;
			s_hashing = null;
			s_scanned = false;
			s_nextScan = 0f;
			s_pieceHashes = null;
			s_helloAt = -1f;
			s_remote = null;
			s_inFlight = false;
			s_told = false;
			MergeRender.RemoteReset();
		}

		private static byte[] Pack(byte[] raw)
		{
			return Utils.Compress(raw);
		}

		private static byte[] Unpack(byte[] data)
		{
			return Utils.Decompress(data);
		}

		private static void OnHello(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !Serving)
			{
				return;
			}
			int num = pkg.ReadInt();
			if (num != 1)
			{
				Plugin.Log.LogInfo((object)$"far view on servers: a client with protocol {num} (ours {1}) asked: not served");
				return;
			}
			bool flag = s_subs.Count == 0;
			s_subs.Add(sender);
			Plugin.Log.LogInfo((object)$"far view on servers: client {sender} subscribed ({s_subs.Count} now)");
			if (s_scanned)
			{
				SendIndex(sender);
			}
			else if (flag && s_scan == null)
			{
				s_nextScan = 0f;
			}
		}

		private static void SendIndex(long peer)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(Pack(FarManifest.WriteIndex(s_index)));
			ZRoutedRpc.instance.InvokeRoutedRPC(peer, "VEE_FarIndex", new object[1] { val });
		}

		private static void OnWant(long sender, ZPackage pkg)
		{
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Expected O, but got Unknown
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !Serving || !s_subs.Contains(sender))
			{
				return;
			}
			int num = Math.Min(pkg.ReadInt(), 64);
			MemoryStream memoryStream = new MemoryStream();
			int num2 = 0;
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
			{
				List<long> list = new List<long>();
				for (int i = 0; i < num; i++)
				{
					list.Add(pkg.ReadLong());
				}
				MemoryStream memoryStream2 = new MemoryStream();
				using (BinaryWriter binaryWriter2 = new BinaryWriter(memoryStream2))
				{
					foreach (long item in list)
					{
						if (memoryStream2.Length > 131072)
						{
							break;
						}
						uint value;
						uint num3 = (s_index.TryGetValue(item, out value) ? value : 0u);
						binaryWriter2.Write(num3);
						if (FarManifest.IsTerrain(item))
						{
							binaryWriter2.Write(item);
							byte[] value2;
							byte[] array = ((num3 != 0 && s_terrain.TryGetValue(item, out value2)) ? value2 : new byte[0]);
							binaryWriter2.Write(array.Length);
							binaryWriter2.Write(array);
						}
						else
						{
							FarManifest.Write(binaryWriter2, item, (num3 != 0 && s_lists.TryGetValue(item, out var value3)) ? value3 : new List<FarEntry>());
						}
						num2++;
					}
				}
				binaryWriter.Write(num2);
				binaryWriter.Write(memoryStream2.ToArray());
			}
			ZPackage val = new ZPackage();
			val.Write(Pack(memoryStream.ToArray()));
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "VEE_FarCells", new object[1] { val });
		}

		private static void ServerTick()
		{
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_031e: Unknown result type (might be due to invalid IL or missing references)
			//IL_080d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0814: Expected O, but got Unknown
			if (!Serving || s_subs.Count == 0)
			{
				return;
			}
			if (Time.frameCount % 120 == 0)
			{
				s_subs.RemoveWhere((long id) => ZNet.instance.GetPeer(id) == null);
			}
			if (s_subs.Count == 0)
			{
				return;
			}
			if (s_scan == null)
			{
				if (Time.unscaledTime < s_nextScan || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
				{
					return;
				}
				if (s_pieceHashes == null)
				{
					Pieces();
				}
				s_scan = new List<ZDO>(ZdosById.Invoke(ZDOMan.instance).Values);
				s_scanAt = 0;
				s_scanLists = new Dictionary<long, List<FarEntry>>();
				s_scanTerrain = new Dictionary<long, byte[]>();
				s_scanTicks = 0L;
				s_scanFrames = 0;
				s_scanStarted = Time.unscaledTime;
			}
			long timestamp = Stopwatch.GetTimestamp();
			long num = timestamp + (long)(2.0 / Clock.TickMs);
			if (s_scanAt < s_scan.Count)
			{
				s_scanFrames++;
			}
			while (s_scanAt < s_scan.Count)
			{
				if ((s_scanAt & 0xFF) == 0 && Stopwatch.GetTimestamp() > num)
				{
					s_scanTicks += Stopwatch.GetTimestamp() - timestamp;
					return;
				}
				ZDO val = s_scan[s_scanAt];
				if (val != null && val.IsValid())
				{
					if (s_terrainHashes.Contains(val.GetPrefab()))
					{
						byte[] byteArray = val.GetByteArray(ZDOVars.s_TCData, (byte[])null);
						if (byteArray != null && byteArray.Length != 0)
						{
							Vector2s zone = ZoneSystem.GetZone(val.GetPosition());
							s_scanTerrain[FarManifest.TerrainId(zone.x, zone.y)] = byteArray;
						}
					}
					else if (s_pieceHashes.Contains(val.GetPrefab()))
					{
						Vector3 position = val.GetPosition();
						Quaternion rotation = val.GetRotation();
						FarEntry item = new FarEntry
						{
							Prefab = val.GetPrefab(),
							X = position.x,
							Y = position.y,
							Z = position.z,
							QX = rotation.x,
							QY = rotation.y,
							QZ = rotation.z,
							QW = rotation.w,
							Snow = (val.GetFloat(ZDOVars.s_snow, 0f) > 0.25f)
						};
						if (s_syncScale.Contains(item.Prefab))
						{
							Vector3 vec = val.GetVec3(ZDOVars.s_scaleHash, Vector3.zero);
							if (vec == Vector3.zero)
							{
								float num2 = val.GetFloat(ZDOVars.s_scaleScalarHash, 0f);
								if (num2 > 0f)
								{
									((Vector3)(ref vec))..ctor(num2, num2, num2);
								}
							}
							if (vec != Vector3.zero)
							{
								item.Scaled = true;
								item.SX = vec.x;
								item.SY = vec.y;
								item.SZ = vec.z;
							}
						}
						long key = FarManifest.AreaOf(position.x, position.z);
						if (!s_scanLists.TryGetValue(key, out var value))
						{
							value = (s_scanLists[key] = new List<FarEntry>());
						}
						value.Add(item);
					}
				}
				s_scanAt++;
			}
			if (s_hashing == null)
			{
				s_scanTicks += Stopwatch.GetTimestamp() - timestamp;
				Dictionary<long, List<FarEntry>> lists = s_scanLists;
				Dictionary<long, byte[]> terrain = s_scanTerrain;
				HashJob job = (s_hashing = new HashJob());
				ThreadPool.QueueUserWorkItem(delegate
				{
					Dictionary<long, uint> dictionary = new Dictionary<long, uint>(lists.Count + terrain.Count);
					try
					{
						foreach (KeyValuePair<long, List<FarEntry>> item2 in lists)
						{
							dictionary[item2.Key] = FarManifest.Hash(item2.Key, item2.Value);
						}
						foreach (KeyValuePair<long, byte[]> item3 in terrain)
						{
							dictionary[item3.Key] = FarManifest.HashBytes(item3.Value);
						}
					}
					catch (Exception error)
					{
						job.Error = error;
					}
					job.Result = dictionary;
					job.Done = true;
				});
			}
			else
			{
				if (!s_hashing.Done)
				{
					return;
				}
				HashJob hashJob = s_hashing;
				s_hashing = null;
				if (hashJob.Error != null)
				{
					s_scan = null;
					s_scanLists = null;
					s_nextScan = Time.unscaledTime + 20f;
					throw hashJob.Error;
				}
				List<KeyValuePair<long, uint>> list2 = new List<KeyValuePair<long, uint>>();
				Dictionary<long, uint> result = hashJob.Result;
				if (s_scanned)
				{
					foreach (KeyValuePair<long, uint> item4 in result)
					{
						if (!s_index.TryGetValue(item4.Key, out var value2) || value2 != item4.Value)
						{
							list2.Add(item4);
						}
					}
					foreach (KeyValuePair<long, uint> item5 in s_index)
					{
						if (!result.ContainsKey(item5.Key))
						{
							list2.Add(new KeyValuePair<long, uint>(item5.Key, 0u));
						}
					}
				}
				int num3 = s_scanLists.Values.Sum((List<FarEntry> l) => l.Count);
				s_index.Clear();
				foreach (KeyValuePair<long, uint> item6 in result)
				{
					s_index[item6.Key] = item6.Value;
				}
				s_lists.Clear();
				foreach (KeyValuePair<long, List<FarEntry>> s_scanList in s_scanLists)
				{
					s_lists[s_scanList.Key] = s_scanList.Value;
				}
				s_terrain.Clear();
				foreach (KeyValuePair<long, byte[]> item7 in s_scanTerrain)
				{
					s_terrain[item7.Key] = item7.Value;
				}
				int num4 = s_scanTerrain.Values.Sum((byte[] b) => b.Length);
				string text = $"scan of {s_scan.Count} objects: {(double)s_scanTicks * Clock.TickMs:0.0} ms main thread over {s_scanFrames} frames, {Time.unscaledTime - s_scanStarted:0.00} s with the hashing";
				s_scan = null;
				s_scanLists = null;
				s_scanTerrain = null;
				s_nextScan = Time.unscaledTime + 20f;
				if (!s_scanned)
				{
					s_scanned = true;
					Plugin.Log.LogInfo((object)$"far view on servers: {num3} pieces in {s_lists.Count} areas, terrain edits in {s_terrain.Count} zones ({num4 / 1024} KB); sending the index to {s_subs.Count} clients; {text}");
					{
						foreach (long s_sub in s_subs)
						{
							SendIndex(s_sub);
						}
						return;
					}
				}
				if (list2.Count <= 0)
				{
					return;
				}
				MemoryStream memoryStream = new MemoryStream();
				using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
				{
					binaryWriter.Write(list2.Count);
					foreach (KeyValuePair<long, uint> item8 in list2)
					{
						binaryWriter.Write(item8.Key);
						binaryWriter.Write(item8.Value);
					}
				}
				byte[] array = Pack(memoryStream.ToArray());
				foreach (long s_sub2 in s_subs)
				{
					ZPackage val2 = new ZPackage();
					val2.Write(array);
					ZRoutedRpc.instance.InvokeRoutedRPC(s_sub2, "VEE_FarChanged", new object[1] { val2 });
				}
				Plugin.Log.LogInfo((object)$"far view on servers: {list2.Count} areas changed; {text}");
			}
		}

		private static void Pieces()
		{
			s_pieceHashes = new HashSet<int>();
			s_syncScale = new HashSet<int>();
			s_terrainHashes = new HashSet<int>();
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<TerrainComp>() != (Object)null)
				{
					s_terrainHashes.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name));
				}
				else if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<WearNTear>() == (Object)null) && !((Object)(object)prefab.GetComponentInChildren<Rigidbody>(true) != (Object)null))
				{
					int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name);
					s_pieceHashes.Add(stableHashCode);
					ZNetView component = prefab.GetComponent<ZNetView>();
					if ((Object)(object)component != (Object)null && component.m_syncInitialScale)
					{
						s_syncScale.Add(stableHashCode);
					}
				}
			}
		}

		private static void ClientTick()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			if (Plugin.FarOn == null || !Plugin.FarOn.Value || (Object)(object)Player.m_localPlayer == (Object)null || ZNet.World == null)
			{
				return;
			}
			if (s_helloAt < 0f)
			{
				s_helloAt = Time.unscaledTime;
				ZPackage val = new ZPackage();
				val.Write(1);
				ZRoutedRpc.instance.InvokeRoutedRPC("VEE_FarHello", new object[1] { val });
			}
			if (NoServerMod && !s_told)
			{
				s_told = true;
				Plugin.Log.LogInfo((object)("far view on servers: this server doesn't serve far view (no Valheim Enhanced Edition there)" + ((Plugin.FarVisitedOn != null && Plugin.FarVisitedOn.Value) ? "; far copies of the bases you visit are kept" : "; far view stays off ([Far builds] VisitedOnServers keeps copies of the bases you visit)")));
			}
			if (s_remote == null)
			{
				return;
			}
			if (s_inFlight && Time.unscaledTime - s_sentAt > 15f)
			{
				s_inFlight = false;
			}
			if (s_inFlight || Time.unscaledTime < s_nextWant)
			{
				return;
			}
			List<long> list = MergeRender.RemoteWanted(s_remote, 24);
			if (list.Count == 0)
			{
				s_nextWant = Time.unscaledTime + 1f;
				return;
			}
			ZPackage val2 = new ZPackage();
			val2.Write(list.Count);
			foreach (long item in list)
			{
				val2.Write(item);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC("VEE_FarWant", new object[1] { val2 });
			s_inFlight = true;
			s_sentAt = Time.unscaledTime;
			s_nextWant = Time.unscaledTime + 1f;
		}

		private static void OnIndex(long sender, ZPackage pkg)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
			{
				Dictionary<long, uint> dictionary = FarManifest.ReadIndex(Unpack(pkg.ReadByteArray()));
				if (dictionary == null)
				{
					Plugin.Log.LogInfo((object)"far view on servers: the server's protocol differs: far view stays off here");
					return;
				}
				s_remote = dictionary;
				Plugin.Log.LogInfo((object)$"far view on servers: the server serves far view ({dictionary.Count} areas with pieces)");
				MergeRender.RemoteIndex(dictionary);
			}
		}

		private static void OnCells(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || s_remote == null)
			{
				return;
			}
			s_inFlight = false;
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(Unpack(pkg.ReadByteArray())));
			int num = binaryReader.ReadInt32();
			using BinaryReader binaryReader2 = new BinaryReader(new MemoryStream(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))));
			for (int i = 0; i < num; i++)
			{
				uint num2 = binaryReader2.ReadUInt32();
				long num3 = binaryReader2.ReadInt64();
				if (num2 == 0)
				{
					s_remote.Remove(num3);
				}
				else
				{
					s_remote[num3] = num2;
				}
				if (FarManifest.IsTerrain(num3))
				{
					int count = binaryReader2.ReadInt32();
					MergeRender.RemoteTerrain(num3, num2, binaryReader2.ReadBytes(count));
				}
				else
				{
					MergeRender.RemoteArea(num3, num2, FarManifest.ReadBody(binaryReader2, num3));
				}
			}
		}

		private static void OnChanged(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || s_remote == null)
			{
				return;
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(Unpack(pkg.ReadByteArray())));
			int num = binaryReader.ReadInt32();
			for (int i = 0; i < num; i++)
			{
				long num2 = binaryReader.ReadInt64();
				uint num3 = binaryReader.ReadUInt32();
				if (num3 == 0)
				{
					s_remote.Remove(num2);
				}
				else
				{
					s_remote[num2] = num3;
				}
				MergeRender.RemoteChanged(num2, num3);
			}
		}
	}
	internal static class FarQuadric
	{
		internal struct Stats
		{
			public int Points;

			public int Passes;

			public long Free;

			public long Border;

			public long NonManifold;

			public long OddBorder;

			public long NoWedge;

			public long Flip;

			public long OverBound;
		}

		internal sealed class Scratch
		{
			internal readonly Run R = new Run();

			internal readonly Dictionary<Key, int> WeldHead = new Dictionary<Key, int>();

			internal int[] WeldNext;

			internal int[] Rep;

			internal float[] Pos;

			internal float[] Nrm;

			internal float[] Uv;

			internal readonly Dictionary<(int, int, int, int, int), int> SimKey = new Dictionary<(int, int, int, int, int), int>();

			internal readonly List<int> SimKeep = new List<int>();

			internal int[] SimMap;

			internal int[] SimTri;
		}

		internal struct Key : IEquatable<Key>
		{
			public float X;

			public float Y;

			public float Z;

			public bool Equals(Key o)
			{
				if (X == o.X && Y == o.Y)
				{
					return Z == o.Z;
				}
				return false;
			}

			public override bool Equals(object o)
			{
				if (o is Key o2)
				{
					return Equals(o2);
				}
				return false;
			}

			public override int GetHashCode()
			{
				int num = (X.GetHashCode() * 73856093) ^ (Y.GetHashCode() * 19349663) ^ (Z.GetHashCode() * 83492791);
				return num ^ (num >>> 15);
			}
		}

		internal sealed class Run
		{
			private int[] m_pt;

			private double[] m_px;

			private double[] m_py;

			private double[] m_pz;

			private double[] m_q;

			private double[] m_w;

			private int[] m_tri;

			private int m_tn;

			private int m_np;

			private readonly Dictionary<Key, int> m_map = new Dictionary<Key, int>();

			internal int Passes;

			internal long Free;

			internal long Border;

			internal long NonManifold;

			internal long OddBorder;

			internal long NoWedge;

			internal long Flip;

			internal long OverBound;

			private int[] m_off;

			private int[] m_adj;

			private byte[] m_kind;

			private bool[] m_locked;

			private int[] m_remap;

			private readonly List<int> m_moved = new List<int>();

			private readonly List<int> m_mapFrom = new List<int>(8);

			private readonly List<int> m_mapTo = new List<int>(8);

			private int[] m_fill;

			private int[] m_candU;

			private int[] m_candV;

			private int[] m_order;

			private double[] m_keys;

			private double[] m_candCost;

			internal int TriCount => m_tn;

			internal int PointCount => m_np;

			internal void Init(float[] pos, int n, int[] tri, int[] weld)
			{
				Passes = 0;
				m_tn = 0;
				double num = 0.0;
				double num2 = 0.0;
				double num3 = 0.0;
				for (int i = 0; i < n; i++)
				{
					num += (double)pos[i * 3];
					num2 += (double)pos[i * 3 + 1];
					num3 += (double)pos[i * 3 + 2];
				}
				if (n > 0)
				{
					num /= (double)n;
					num2 /= (double)n;
					num3 /= (double)n;
				}
				Grow(ref m_pt, n);
				Grow(ref m_px, n);
				Grow(ref m_py, n);
				Grow(ref m_pz, n);
				m_map.Clear();
				m_np = 0;
				for (int j = 0; j < n; j++)
				{
					Key key = new Key
					{
						X = pos[j * 3] + 0f,
						Y = pos[j * 3 + 1] + 0f,
						Z = pos[j * 3 + 2] + 0f
					};
					if (!m_map.TryGetValue(key, out var value))
					{
						value = m_np++;
						m_map[key] = value;
						m_px[value] = (double)key.X - num;
						m_py[value] = (double)key.Y - num2;
						m_pz[value] = (double)key.Z - num3;
					}
					m_pt[j] = value;
				}
				Grow(ref m_q, m_np * 10);
				Grow(ref m_w, m_np);
				Array.Clear(m_q, 0, m_np * 10);
				Array.Clear(m_w, 0, m_np);
				Grow(ref m_off, m_np + 1);
				Grow(ref m_adj, tri.Length);
				Grow(ref m_kind, m_np);
				Grow(ref m_locked, m_np);
				Grow(ref m_fill, m_np);
				Grow(ref m_remap, n);
				for (int k = 0; k < n; k++)
				{
					m_remap[k] = k;
				}
				Grow(ref m_tri, tri.Length);
				for (int l = 0; l + 2 < tri.Length; l += 3)
				{
					int num4 = tri[l];
					int num5 = tri[l + 1];
					int num6 = tri[l + 2];
					if (weld != null)
					{
						num4 = weld[num4];
						num5 = weld[num5];
						num6 = weld[num6];
					}
					int num7 = m_pt[num4];
					int num8 = m_pt[num5];
					int num9 = m_pt[num6];
					if (num7 != num8 && num8 != num9 && num7 != num9)
					{
						m_tri[m_tn * 3] = num4;
						m_tri[m_tn * 3 + 1] = num5;
						m_tri[m_tn * 3 + 2] = num6;
						m_tn++;
					}
				}
				InitQuadrics();
			}

			internal int[] Faces()
			{
				int[] array = new int[m_tn * 3];
				Array.Copy(m_tri, array, array.Length);
				return array;
			}

			private int P(int t, int k)
			{
				return m_pt[m_tri[t * 3 + k]];
			}

			private void BuildAdjacency()
			{
				Array.Clear(m_off, 0, m_np + 1);
				for (int i = 0; i < m_tn * 3; i++)
				{
					m_off[m_pt[m_tri[i]] + 1]++;
				}
				for (int j = 0; j < m_np; j++)
				{
					m_off[j + 1] += m_off[j];
				}
				int[] fill = m_fill;
				Array.Copy(m_off, fill, m_np);
				for (int k = 0; k < m_tn; k++)
				{
					for (int l = 0; l < 3; l++)
					{
						m_adj[fill[P(k, l)]++] = k;
					}
				}
			}

			private int EdgeCount(int a, int b)
			{
				int num = 0;
				for (int i = m_off[a]; i < m_off[a + 1]; i++)
				{
					int t = m_adj[i];
					int num2 = P(t, 0);
					int num3 = P(t, 1);
					int num4 = P(t, 2);
					if ((num2 == a && num3 == b) || (num3 == a && num4 == b) || (num4 == a && num2 == b))
					{
						num++;
					}
				}
				return num;
			}

			private int Opposite(int a, int b)
			{
				for (int i = m_off[b]; i < m_off[b + 1]; i++)
				{
					int num = m_adj[i];
					int num2 = P(num, 0);
					int num3 = P(num, 1);
					int num4 = P(num, 2);
					if ((num2 == b && num3 == a) || (num3 == b && num4 == a) || (num4 == b && num2 == a))
					{
						return num;
					}
				}
				return -1;
			}

			private int CornerOf(int t, int p)
			{
				if (P(t, 0) != p)
				{
					if (P(t, 1) != p)
					{
						return 2;
					}
					return 1;
				}
				return 0;
			}

			private void AddPlane(int p, double a, double b, double c, double d, double w)
			{
				int num = p * 10;
				m_q[num] += w * a * a;
				m_q[num + 1] += w * b * b;
				m_q[num + 2] += w * c * c;
				m_q[num + 3] += w * a * b;
				m_q[num + 4] += w * a * c;
				m_q[num + 5] += w * b * c;
				m_q[num + 6] += w * a * d;
				m_q[num + 7] += w * b * d;
				m_q[num + 8] += w * c * d;
				m_q[num + 9] += w * d * d;
			}

			private bool FaceNormal(int t, out double nx, out double ny, out double nz, out double len)
			{
				int num = P(t, 0);
				int num2 = P(t, 1);
				int num3 = P(t, 2);
				double num4 = m_px[num2] - m_px[num];
				double num5 = m_py[num2] - m_py[num];
				double num6 = m_pz[num2] - m_pz[num];
				double num7 = m_px[num3] - m_px[num];
				double num8 = m_py[num3] - m_py[num];
				double num9 = m_pz[num3] - m_pz[num];
				nx = num5 * num9 - num6 * num8;
				ny = num6 * num7 - num4 * num9;
				nz = num4 * num8 - num5 * num7;
				len = Math.Sqrt(nx * nx + ny * ny + nz * nz);
				if (len <= 1E-12)
				{
					return false;
				}
				nx /= len;
				ny /= len;
				nz /= len;
				return true;
			}

			private void InitQuadrics()
			{
				BuildAdjacency();
				for (int i = 0; i < m_tn; i++)
				{
					if (!FaceNormal(i, out var nx, out var ny, out var nz, out var len))
					{
						continue;
					}
					double num = len * 0.5;
					int num2 = P(i, 0);
					double d = 0.0 - (nx * m_px[num2] + ny * m_py[num2] + nz * m_pz[num2]);
					for (int j = 0; j < 3; j++)
					{
						int num3 = P(i, j);
						AddPlane(num3, nx, ny, nz, d, num);
						m_w[num3] += num;
					}
					for (int k = 0; k < 3; k++)
					{
						int num4 = P(i, k);
						int num5 = P(i, (k + 1) % 3);
						int num6 = Opposite(num4, num5);
						double num7;
						if (num6 < 0)
						{
							num7 = 10.0;
						}
						else
						{
							int num8 = m_tri[i * 3 + k];
							int num9 = m_tri[i * 3 + (k + 1) % 3];
							int num10 = m_tri[num6 * 3 + CornerOf(num6, num4)];
							int num11 = m_tri[num6 * 3 + CornerOf(num6, num5)];
							if (num8 == num10 && num9 == num11)
							{
								continue;
							}
							num7 = 1.0;
						}
						double num12 = m_px[num5] - m_px[num4];
						double num13 = m_py[num5] - m_py[num4];
						double num14 = m_pz[num5] - m_pz[num4];
						double num15 = num12 * num12 + num13 * num13 + num14 * num14;
						if (!(num15 <= 1E-12))
						{
							double num16 = num13 * nz - num14 * ny;
							double num17 = num14 * nx - num12 * nz;
							double num18 = num12 * ny - num13 * nx;
							double num19 = Math.Sqrt(num16 * num16 + num17 * num17 + num18 * num18);
							if (!(num19 <= 1E-12))
							{
								num16 /= num19;
								num17 /= num19;
								num18 /= num19;
								double d2 = 0.0 - (num16 * m_px[num4] + num17 * m_py[num4] + num18 * m_pz[num4]);
								AddPlane(num4, num16, num17, num18, d2, num15 * num7);
								AddPlane(num5, num16, num17, num18, d2, num15 * num7);
							}
						}
					}
				}
			}

			private double Cost(int p, int v)
			{
				int num = p * 10;
				double num2 = m_px[v];
				double num3 = m_py[v];
				double num4 = m_pz[v];
				double val = m_q[num] * num2 * num2 + m_q[num + 1] * num3 * num3 + m_q[num + 2] * num4 * num4 + 2.0 * (m_q[num + 3] * num2 * num3 + m_q[num + 4] * num2 * num4 + m_q[num + 5] * num3 * num4) + 2.0 * (m_q[num + 6] * num2 + m_q[num + 7] * num3 + m_q[num + 8] * num4) + m_q[num + 9];
				return Math.Max(0.0, val) / Math.Max(m_w[p], 1E-12);
			}

			private void Classify()
			{
				for (int i = 0; i < m_np; i++)
				{
					int num = 0;
					int num2 = 0;
					bool flag = false;
					for (int j = m_off[i]; j < m_off[i + 1]; j++)
					{
						if (flag)
						{
							break;
						}
						int t = m_adj[j];
						int num3 = CornerOf(t, i);
						int b = P(t, (num3 + 1) % 3);
						int a = P(t, (num3 + 2) % 3);
						if (EdgeCount(i, b) > 1 || EdgeCount(a, i) > 1)
						{
							flag = true;
							break;
						}
						if (Opposite(i, b) < 0)
						{
							num++;
						}
						if (Opposite(a, i) < 0)
						{
							num2++;
						}
					}
					m_kind[i] = (byte)(flag ? 2 : ((num != 0 || num2 != 0) ? ((num == 1 && num2 == 1) ? 1 : 2) : 0));
					if (m_off[i] != m_off[i + 1])
					{
						if (flag)
						{
							NonManifold++;
						}
						else if (m_kind[i] == 2)
						{
							OddBorder++;
						}
						else if (m_kind[i] == 1)
						{
							Border++;
						}
						else
						{
							Free++;
						}
					}
				}
			}

			private bool Open(int a, int b)
			{
				if (Opposite(a, b) >= 0 || EdgeCount(a, b) <= 0)
				{
					if (Opposite(b, a) < 0)
					{
						return EdgeCount(b, a) > 0;
					}
					return false;
				}
				return true;
			}

			private bool WedgeMap(int u, int v)
			{
				m_mapFrom.Clear();
				m_mapTo.Clear();
				for (int i = m_off[u]; i < m_off[u + 1]; i++)
				{
					int num = m_adj[i];
					int item = m_tri[num * 3 + CornerOf(num, u)];
					int num2 = m_mapFrom.IndexOf(item);
					if (num2 < 0)
					{
						m_mapFrom.Add(item);
						m_mapTo.Add(-1);
						num2 = m_mapFrom.Count - 1;
					}
					for (int j = 0; j < 3; j++)
					{
						if (P(num, j) == v)
						{
							int num3 = m_tri[num * 3 + j];
							if (m_mapTo[num2] < 0)
							{
								m_mapTo[num2] = num3;
							}
							else if (m_mapTo[num2] != num3)
							{
								return false;
							}
						}
					}
				}
				for (int k = 0; k < m_mapTo.Count; k++)
				{
					if (m_mapTo[k] < 0)
					{
						return false;
					}
				}
				return true;
			}

			private bool Flips(int u, int v)
			{
				if (KeepParts)
				{
					bool flag = false;
					for (int i = m_off[u]; i < m_off[u + 1]; i++)
					{
						if (flag)
						{
							break;
						}
						int t = m_adj[i];
						flag = P(t, 0) != v && P(t, 1) != v && P(t, 2) != v;
					}
					if (!flag)
					{
						return true;
					}
				}
				for (int j = m_off[u]; j < m_off[u + 1]; j++)
				{
					int t2 = m_adj[j];
					int num = CornerOf(t2, u);
					int num2 = P(t2, (num + 1) % 3);
					int num3 = P(t2, (num + 2) % 3);
					if (num2 != v && num3 != v)
					{
						double bx = m_px[num2];
						double num4 = m_py[num2];
						double bz = m_pz[num2];
						double cx = m_px[num3];
						double cy = m_py[num3];
						double cz = m_pz[num3];
						Cross(m_px[u], m_py[u], m_pz[u], bx, num4, bz, cx, cy, cz, out var nx, out var ny, out var nz);
						Cross(m_px[v], m_py[v], m_pz[v], bx, num4, bz, cx, cy, cz, out var nx2, out var ny2, out var nz2);
						if (nx2 * nx2 + ny2 * ny2 + nz2 * nz2 <= 1E-24)
						{
							return true;
						}
						if (nx * nx2 + ny * ny2 + nz * nz2 <= 0.0)
						{
							return true;
						}
					}
				}
				return false;
			}

			private static void Cross(double ax, double ay, double az, double bx, double by, double bz, double cx, double cy, double cz, out double nx, out double ny, out double nz)
			{
				double num = bx - ax;
				double num2 = by - ay;
				double num3 = bz - az;
				double num4 = cx - ax;
				double num5 = cy - ay;
				double num6 = cz - az;
				nx = num2 * num6 - num3 * num5;
				ny = num3 * num4 - num * num6;
				nz = num * num5 - num2 * num4;
			}

			internal void Pass(double bound2)
			{
				Passes++;
				Free = (Border = (NonManifold = (OddBorder = (NoWedge = (Flip = (OverBound = 0L))))));
				BuildAdjacency();
				Classify();
				Grow(ref m_candU, m_np);
				Grow(ref m_candV, m_np);
				Grow(ref m_candCost, m_np);
				int num = 0;
				for (int i = 0; i < m_np; i++)
				{
					if (m_kind[i] == 2 || m_off[i] == m_off[i + 1])
					{
						continue;
					}
					double num2 = bound2;
					int num3 = -1;
					bool flag = false;
					bool flag2 = false;
					for (int j = m_off[i]; j < m_off[i + 1]; j++)
					{
						int t = m_adj[j];
						for (int k = 0; k < 3; k++)
						{
							int num4 = P(t, k);
							if (num4 == i || (m_kind[i] == 1 && (m_kind[num4] != 1 || !Open(i, num4))))
							{
								continue;
							}
							double num5 = Cost(i, num4);
							if (num5 > bound2)
							{
								flag = true;
							}
							else if (!(num5 > num2) && (num5 != num2 || num3 < 0))
							{
								if (!WedgeMap(i, num4))
								{
									flag2 = true;
									continue;
								}
								num2 = num5;
								num3 = num4;
							}
						}
					}
					if (num3 < 0)
					{
						if (flag2)
						{
							NoWedge++;
						}
						else if (flag)
						{
							OverBound++;
						}
					}
					else
					{
						m_candU[num] = i;
						m_candV[num] = num3;
						m_candCost[num] = num2;
						num++;
					}
				}
				int[] array = Grow(ref m_order, num);
				for (int l = 0; l < num; l++)
				{
					array[l] = l;
				}
				double[] array2 = Grow(ref m_keys, num);
				Array.Copy(m_candCost, array2, num);
				Array.Sort(array2, array, 0, num);
				Array.Clear(m_locked, 0, m_np);
				for (int m = 0; m < num; m++)
				{
					int num6 = array[m];
					int num7 = m_candU[num6];
					int num8 = m_candV[num6];
					if (m_locked[num7] || m_locked[num8])
					{
						continue;
					}
					if (Flips(num7, num8))
					{
						Flip++;
					}
					else if (WedgeMap(num7, num8))
					{
						for (int n = 0; n < m_mapFrom.Count; n++)
						{
							m_remap[m_mapFrom[n]] = m_mapTo[n];
							m_moved.Add(m_mapFrom[n]);
						}
						for (int num9 = m_off[num7]; num9 < m_off[num7 + 1]; num9++)
						{
							int t2 = m_adj[num9];
							bool[] locked = m_locked;
							int num10 = P(t2, 0);
							bool flag3;
							m_locked[P(t2, 1)] = (flag3 = (m_locked[P(t2, 2)] = true));
							locked[num10] = flag3;
						}
						int num11 = num7 * 10;
						int num12 = num8 * 10;
						for (int num13 = 0; num13 < 10; num13++)
						{
							m_q[num12 + num13] += m_q[num11 + num13];
						}
						m_w[num8] += m_w[num7];
					}
				}
				int num14 = 0;
				for (int num15 = 0; num15 < m_tn; num15++)
				{
					int num16 = m_remap[m_tri[num15 * 3]];
					int num17 = m_remap[m_tri[num15 * 3 + 1]];
					int num18 = m_remap[m_tri[num15 * 3 + 2]];
					int num19 = m_pt[num16];
					int num20 = m_pt[num17];
					int num21 = m_pt[num18];
					if (num19 != num20 && num20 != num21 && num19 != num21)
					{
						m_tri[num14 * 3] = num16;
						m_tri[num14 * 3 + 1] = num17;
						m_tri[num14 * 3 + 2] = num18;
						num14++;
					}
				}
				m_tn = num14;
				foreach (int item in m_moved)
				{
					m_remap[item] = item;
				}
				m_moved.Clear();
			}
		}

		private const double BorderWeight = 10.0;

		private const double SeamWeight = 1.0;

		internal static bool KeepParts = true;

		private const int MaxPassesPerLevel = 64;

		private static readonly ConcurrentBag<Scratch> s_pool = new ConcurrentBag<Scratch>();

		internal static bool Pooled => !s_pool.IsEmpty;

		internal static int[][] Levels(float[] pos, int vertexCount, int[] tri, float[] bounds, out Stats stats, int[] weld = null, Scratch scratch = null)
		{
			Scratch scratch2 = scratch ?? Rent();
			Run r = scratch2.R;
			r.Init(pos, vertexCount, tri, weld);
			int[][] array = new int[bounds.Length][];
			for (int i = 0; i < bounds.Length; i++)
			{
				double bound = (double)bounds[i] * (double)bounds[i];
				for (int j = 0; j < 64; j++)
				{
					int triCount = r.TriCount;
					r.Pass(bound);
					if (triCount - r.TriCount < Math.Max(1, triCount / 100))
					{
						break;
					}
				}
				array[i] = r.Faces();
			}
			stats = new Stats
			{
				Points = r.PointCount,
				Passes = r.Passes,
				Free = r.Free,
				Border = r.Border,
				NonManifold = r.NonManifold,
				OddBorder = r.OddBorder,
				NoWedge = r.NoWedge,
				Flip = r.Flip,
				OverBound = r.OverBound
			};
			if (scratch == null)
			{
				Return(scratch2);
			}
			return array;
		}

		internal static Scratch Rent()
		{
			if (!s_pool.TryTake(out var result))
			{
				return new Scratch();
			}
			return result;
		}

		internal static void Return(Scratch s)
		{
			s_pool.Add(s);
		}

		internal static void ReleaseAll()
		{
			Scratch result;
			while (s_pool.TryTake(out result))
			{
			}
		}

		internal static int[] WeldExact(float[] attrs, int stride, int n)
		{
			int[] array = new int[n];
			Dictionary<ulong, int> dictionary = new Dictionary<ulong, int>(n);
			int[] array2 = new int[n];
			for (int i = 0; i < n; i++)
			{
				ulong num = 14695981039346656037uL;
				for (int j = 0; j < stride; j++)
				{
					num ^= (uint)(attrs[i * stride + j] + 0f).GetHashCode();
					num *= 1099511628211L;
				}
				array2[i] = -1;
				if (!dictionary.TryGetValue(num, out var value))
				{
					dictionary[num] = i;
					array[i] = i;
					continue;
				}
				int num2 = value;
				while (value >= 0)
				{
					bool flag = true;
					for (int k = 0; k < stride && flag; k++)
					{
						flag = attrs[i * stride + k] + 0f == attrs[value * stride + k] + 0f;
					}
					if (flag)
					{
						break;
					}
					num2 = value;
					value = array2[value];
				}
				if (value >= 0)
				{
					array[i] = value;
					continue;
				}
				array[i] = i;
				array2[num2] = i;
			}
			return array;
		}

		internal static int[] WeldSoft(float[] pos, float[] nrm, float[] uv, int n, float cosAngle, float uvTol, int[] rep = null, Scratch scratch = null)
		{
			if (rep == null || rep.Length < n)
			{
				rep = new int[n];
			}
			Scratch obj = scratch ?? new Scratch();
			Dictionary<Key, int> weldHead = obj.WeldHead;
			weldHead.Clear();
			int[] array = Grow(ref obj.WeldNext, n);
			for (int i = 0; i < n; i++)
			{
				Key key = new Key
				{
					X = pos[i * 3] + 0f,
					Y = pos[i * 3 + 1] + 0f,
					Z = pos[i * 3 + 2] + 0f
				};
				rep[i] = i;
				array[i] = -1;
				if (!weldHead.TryGetValue(key, out var value))
				{
					weldHead[key] = i;
					continue;
				}
				int num = value;
				while (value >= 0)
				{
					if (!(nrm[i * 3] * nrm[value * 3] + nrm[i * 3 + 1] * nrm[value * 3 + 1] + nrm[i * 3 + 2] * nrm[value * 3 + 2] < cosAngle) && (uv == null || (!(Math.Abs(uv[i * 2] - uv[value * 2]) > uvTol) && !(Math.Abs(uv[i * 2 + 1] - uv[value * 2 + 1]) > uvTol))))
					{
						rep[i] = value;
						break;
					}
					num = value;
					value = array[value];
				}
				if (rep[i] == i)
				{
					array[num] = i;
				}
			}
			weldHead.Clear();
			return rep;
		}

		internal static T[] Grow<T>(ref T[] a, int n)
		{
			if (a == null || a.Length < n)
			{
				long val = 2147483647L;
				T[] obj = a;
				a = new T[Math.Max(n, (int)Math.Min(val, (long)((obj != null) ? obj.Length : 0) * 5L / 4))];
			}
			return a;
		}
	}
	internal static class FarTerrain
	{
		private class ZoneEdits
		{
			public Vector3 Centre;

			public int Width;

			public float Scale;

			public float[] Delta;

			public Color[] Paint;
		}

		private static readonly FieldRef<Heightmap, List<float>> Heights = AccessTools.FieldRefAccess<Heightmap, List<float>>("m_heights");

		private static readonly FieldRef<Heightmap, Texture2D> PaintMask = AccessTools.FieldRefAccess<Heightmap, Texture2D>("m_paintMask");

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

		private static readonly Dictionary<Vector2s, ZoneEdits> s_zones = new Dictionary<Vector2s, ZoneEdits>();

		private static ZDOMan s_loadedFor;

		private static bool? s_renderHeim;

		internal static int ZoneCount => s_zones.Count;

		private static bool On
		{
			get
			{
				if (Plugin.FarOn != null && Plugin.FarOn.Value && Plugin.FarTerrainOn != null && Plugin.FarTerrainOn.Value && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null)
				{
					bool? flag = s_renderHeim;
					bool num;
					if (!flag.HasValue)
					{
						bool? flag2 = (s_renderHeim = Chainloader.PluginInfos.ContainsKey("com.renderheim"));
						num = flag2.Value;
					}
					else
					{
						num = flag == true;
					}
					if (!num)
					{
						return !FarLandsLink.Drawing;
					}
				}
				return false;
			}
		}

		private static bool OnHere
		{
			get
			{
				if (!On)
				{
					if (Plugin.FarOn != null && Plugin.FarOn.Value && Plugin.FarTerrainOn != null && Plugin.FarTerrainOn.Value && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && FarNet.RemoteReady)
					{
						bool? flag = s_renderHeim;
						bool num;
						if (!flag.HasValue)
						{
							bool? flag2 = (s_renderHeim = Chainloader.PluginInfos.ContainsKey("com.renderheim"));
							num = flag2.Value;
						}
						else
						{
							num = flag == true;
						}
						if (!num)
						{
							return !FarLandsLink.Drawing;
						}
					}
					return false;
				}
				return true;
			}
		}

		internal static bool RemoteWanted
		{
			get
			{
				if (OnHere && (Object)(object)ZNet.instance != (Object)null)
				{
					return !ZNet.instance.IsServer();
				}
				return false;
			}
		}

		internal static void Patch(Harmony h)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			h.Patch((MethodBase)AccessTools.Method(typeof(Heightmap), "ApplyModifiers", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(FarTerrain), "Post", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		internal static void RemoteZone(long id, byte[] data)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: 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)
			FarManifest.TerrainZone(id, out var zoneX, out var zoneZ);
			Vector2s val = default(Vector2s);
			((Vector2s)(ref val))..ctor(zoneX, zoneZ);
			if (data == null)
			{
				s_zones.Remove(val);
				return;
			}
			try
			{
				ZoneEdits zoneEdits = Parse(data);
				if (zoneEdits == null)
				{
					s_zones.Remove(val);
					return;
				}
				zoneEdits.Centre = ZoneSystem.GetZonePos(val);
				s_zones[val] = zoneEdits;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"far terrain: zone {zoneX},{zoneZ} from the server unreadable: {ex.Message}");
			}
		}

		internal static void RemoteClear()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				s_zones.Clear();
			}
		}

		private static void Post(Heightmap __instance)
		{
			if (!__instance.IsDistantLod || !OnHere)
			{
				return;
			}
			try
			{
				Apply(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("far terrain: " + ex.Message));
			}
		}

		private static void Load()
		{
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: 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_010a: 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_0119: Unknown result type (might be due to invalid IL or missing references)
			if (!ZNet.instance.IsServer() || s_loadedFor == ZDOMan.instance)
			{
				return;
			}
			s_loadedFor = ZDOMan.instance;
			s_zones.Clear();
			HashSet<int> hashSet = new HashSet<int>();
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<TerrainComp>() != (Object)null)
				{
					hashSet.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name));
				}
			}
			int num = 0;
			int num2 = 0;
			foreach (ZDO value in ZdosById.Invoke(ZDOMan.instance).Values)
			{
				if (value == null || !hashSet.Contains(value.GetPrefab()))
				{
					continue;
				}
				byte[] byteArray = value.GetByteArray(ZDOVars.s_TCData, (byte[])null);
				if (byteArray != null)
				{
					ZoneEdits zoneEdits = Parse(byteArray);
					if (zoneEdits == null)
					{
						num2++;
						continue;
					}
					Vector2s zone = ZoneSystem.GetZone(value.GetPosition());
					zoneEdits.Centre = ZoneSystem.GetZonePos(zone);
					s_zones[zone] = zoneEdits;
					num++;
				}
			}
			Plugin.Log.LogInfo((object)string.Format("far terrain: edits of {0} zones read{1}", num, (num2 > 0) ? $" ({num2} unreadable)" : ""));
		}

		private static ZoneEdits Parse(byte[] bytes)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: 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)
			ZPackage val = new ZPackage(Utils.Decompress(bytes));
			val.ReadInt();
			val.ReadInt();
			val.ReadVector3();
			val.ReadSingle();
			int num = val.ReadInt();
			int num2 = Mathf.RoundToInt(Mathf.Sqrt((float)num));
			if (num2 * num2 != num || num2 < 2)
			{
				return null;
			}
			ZoneEdits zoneEdits = new ZoneEdits
			{
				Width = num2 - 1,
				Scale = 64f / (float)(num2 - 1),
				Delta = new float[num],
				Paint = (Color[])(object)new Color[num]
			};
			for (int i = 0; i < num; i++)
			{
				if (val.ReadBool())
				{
					zoneEdits.Delta[i] = Mathf.Clamp(val.ReadSingle() + val.ReadSingle(), -8f, 8f);
				}
			}
			for (int j = 0; j < num; j++)
			{
				zoneEdits.Paint[j] = new Color(0f, 0f, 0f, -1f);
			}
			int num3 = val.ReadInt();
			if (num3 == num)
			{
				for (int k = 0; k < num3; k++)
				{
					if (val.ReadBool())
					{
						zoneEdits.Paint[k] = new Color(val.ReadSingle(), val.ReadSingle(), val.ReadSingle(), val.ReadSingle());
					}
				}
			}
			return zoneEdits;
		}

		private static void Apply(Heightmap hm)
		{
			//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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			Load();
			if (s_zones.Count == 0)
			{
				return;
			}
			List<float> list = Heights.Invoke(hm);
			Texture2D val = PaintMask.Invoke(hm);
			int width = hm.m_width;
			int num = width + 1;
			float scale = hm.m_scale;
			Vector3 position = ((Component)hm).transform.position;
			bool flag = false;
			Vector2s zone = ZoneSystem.GetZone(position);
			int num2;
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				num2 = 6;
			}
			else
			{
				SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
				num2 = ((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance;
			}
			int num3 = num2 + 5;
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					float num4 = position.x + ((float)j - (float)width * 0.5f) * scale;
					float num5 = position.z + ((float)i - (float)width * 0.5f) * scale;
					Vector2s zone2 = ZoneSystem.GetZone(new Vector3(num4, 0f, num5));
					if (!s_zones.TryGetValue(zone2, out var value))
					{
						continue;
					}
					float num6 = (num4 - value.Centre.x) / value.Scale + (float)value.Width * 0.5f;
					float num7 = (num5 - value.Centre.z) / value.Scale + (float)value.Width * 0.5f;
					int num8 = Mathf.Clamp(Mathf.FloorToInt(num6), 0, value.Width - 1);
					int num9 = Mathf.Clamp(Mathf.FloorToInt(num7), 0, value.Width - 1);
					float num10 = Mathf.Clamp01(num6 - (float)num8);
					float num11 = Mathf.Clamp01(num7 - (float)num9);
					int num12 = value.Width + 1;
					float num13 = Mathf.Lerp(Mathf.Lerp(value.Delta[num9 * num12 + num8], value.Delta[num9 * num12 + num8 + 1], num10), Mathf.Lerp(value.Delta[(num9 + 1) * num12 + num8], value.Delta[(num9 + 1) * num12 + num8 + 1], num10), num11);
					int num14 = i * num + j;
					bool flag2 = Mathf.Abs(zone2.x - zone.x) <= num3 && Mathf.Abs(zone2.y - zone.y) <= num3;
					if (num13 > 0f && flag2)
					{
						num13 = 0f;
					}
					if (num13 != 0f && num14 < list.Count)
					{
						list[num14] += num13;
					}
					if ((Object)(object)val != (Object)null)
					{
						Color val2 = value.Paint[Mathf.Clamp(Mathf.RoundToInt(num7), 0, value.Width) * num12 + Mathf.Clamp(Mathf.RoundToInt(num6), 0, value.Width)];
						if (val2.a >= 0f && j < ((Texture)val).width && i < ((Texture)val).height)
						{
							val.SetPixel(j, i, val2);
							flag = true;
						}
					}
				}
			}
			if (flag)
			{
				val.Apply();
			}
		}
	}
	internal static class Floaters
	{
		private static readonly FieldRef<WaterVolume, List<IWaterInteractable>> InWater = AccessTools.FieldRefAccess<WaterVolume, List<IWaterInteractable>>("m_inWater");

		private static readonly FieldRef<WaterVolume, Collider> VCollider = AccessTools.FieldRefAccess<WaterVolume, Collider>("m_collider");

		private static readonly FieldRef<WaterVolume, float> OneDepth = AccessTools.FieldRefAccess<WaterVolume, float>("m_oneDepth");

		private static readonly FieldRef<WaterVolume, float[]> NormDepth = AccessTools.FieldRefAccess<WaterVolume, float[]>("m_normalizedDepth");

		private static readonly FieldRef<float> DayTime = AccessTools.StaticFieldRefAccess<float>(AccessTools.Field(typeof(WaterVolume), "s_wrappedDayTimeSeconds"));

		private static readonly FieldRef<Vector4> Wind1 = AccessTools.StaticFieldRefAccess<Vector4>(AccessTools.Field(typeof(WaterVolume), "s_globalWind1"));

		private static readonly FieldRef<Vector4> Wind2 = AccessTools.StaticFieldRefAccess<Vector4>(AccessTools.Field(typeof(WaterVolume), "s_globalWind2"));

		private static readonly FieldRef<float> WindAlpha = AccessTools.StaticFieldRefAccess<float>(AccessTools.Field(typeof(WaterVolume), "s_globalWindAlpha"));

		private static readonly FieldRef<Vector2[]> Directions = AccessTools.StaticFieldRefAccess<Vector2[]>(AccessTools.Field(typeof(WaterVolume), "s_createWaveDirections"));

		private static readonly FieldRef<Vector2[]> Tangents = AccessTools.StaticFieldRefAccess<Vector2[]>(AccessTools.Field(typeof(WaterVolume), "s_createWaveTangents"));

		private static readonly List<int> s_remove = new List<int>();

		private static readonly Vector2[] s_dir = (Vector2[])(object)new Vector2[10];

		private static readonly Vector2[] s_tan = (Vector2[])(object)new Vector2[10];

		private static Vector2 s_dir1;

		private static Vector2 s_tan1;

		private static Vector2 s_dir2;

		private static Vector2 s_tan2;

		private static Vector4 s_w1 = new Vector4(float.NaN, 0f, 0f, 0f);

		private static Vector4 s_w2 = new Vector4(float.NaN, 0f, 0f, 0f);

		internal static bool Verify;

		internal static long Checked;

		internal static long Mismatched;

		internal static float MaxDiff;

		internal static void Patch(Harmony h)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			h.Patch((MethodBase)AccessTools.Method(typeof(WaterVolume), "UpdateFloaters", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Floaters), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static bool Prefix(WaterVolume __instance)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: 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_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: 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_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: 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_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_039f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.FloatersOn.Value)
			{
				return true;
			}
			List<IWaterInteractable> list = InWater.Invoke(__instance);
			int count = list.Count;
			if (count == 0)
			{
				return false;
			}
			Vector2[] array = Directions.Invoke();
			Vector2[] array2 = Tangents.Invoke();
			if (array == null || array2 == null)
			{
				return true;
			}
			for (int i = 1; i < 10; i++)
			{
				s_dir[i] = array[i];
				s_tan[i] = array2[i];
			}
			Transform transform = ((Component)__instance).transform;
			float y = transform.position.y;
			float surfaceOffset = __instance.m_surfaceOffset;
			float num = OneDepth.Invoke(__instance);
			bool useGlobalWind = __instance.m_useGlobalWind;
			bool flag = __instance.m_forceDepth < 0f;
			Vector3 size = default(Vector3);
			float[] nd = NormDepth.Invoke(__instance);
			if (useGlobalWind && num <= -1f)
			{
				Bounds bounds = VCollider.Invoke(__instance).bounds;
				size = ((Bounds)(ref bounds)).size;
			}
			float waterTime = DayTime.Invoke();
			float num2 = WindAlpha.Invoke();
			Vector4 val = Wind1.Invoke();
			Vector4 val2 = Wind2.Invoke();
			if (val != s_w1 || float.IsNaN(s_w1.x))
			{
				s_w1 = val;
				WindDir(val, out s_dir1, out s_tan1);
			}
			if (num2 != 0f && (val2 != s_w2 || float.IsNaN(s_w2.x)))
			{
				s_w2 = val2;
				WindDir(val2, out s_dir2, out s_tan2);
			}
			bool flag2 = false;
			s_remove.Clear();
			for (int j = 0; j < count; j++)
			{
				IWaterInteractable val3 = list[j];
				if (val3 == null)
				{
					s_remove.Add(j);
					continue;
				}
				Transform transform2 = val3.GetTransform();
				if (!Object.op_Implicit((Object)(object)transform2))
				{
					s_remove.Add(j);
					continue;
				}
				Vector3 position = transform2.position;
				float num3 = 0f;
				if (useGlobalWind)
				{
					float num4 = ((num > -1f) ? num : Depth(transform, position, size, nd));
					float big = 1f - (float)WorldGenerator.DeepNorthWaveFade(position.x, position.z);
					if (num4 != 0f)
					{
						flag2 = true;
						num3 = ((num2 == 0f) ? Wave(position, num4, val.w, s_dir1, s_tan1, waterTime, big) : Mathf.LerpUnclamped(Wave(position, num4, val.w, s_dir1, s_tan1, waterTime, big), Wave(position, num4, val2.w, s_dir2, s_tan2, waterTime, big), num2));
					}
				}
				float num5 = y + num3 + surfaceOffset;
				if (flag && Utils.LengthXZ(position) > 10500f)
				{
					num5 -= 100f;
				}
				if (Verify)
				{
					Checked++;
					float waterSurface = __instance.GetWaterSurface(position, 1f);
					if (num5 != waterSurface)
					{
						Mismatched++;
						MaxDiff = Mathf.Max(MaxDiff, Mathf.Abs(num5 - waterSurface));
					}
				}
				val3.SetLiquidLevel(num5, (LiquidType)0, (Component)(object)__instance);
			}
			for (int num6 = s_remove.Count - 1; num6 >= 0; num6--)
			{
				list.RemoveAt(s_remove[num6]);
			}
			if (flag2)
			{
				array[0] = ((num2 == 0f) ? s_dir1 : s_dir2);
				array2[0] = ((num2 == 0f) ? s_tan1 : s_tan2);
			}
			return false;
		}

		private static void WindDir(Vector4 wind, out Vector2 dir, out Vector2 tan)
		{
			//IL_0001: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			dir = new Vector2(wind.x, wind.z);
			((Vector2)(ref dir)).Normalize();
			tan = new Vector2(0f - dir.y, dir.x);
		}

		private static float Depth(Transform vt, Vector3 point, Vector3 size, float[] nd)
		{
			//IL_0001: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = vt.InverseTransformPoint(point);
			float num = (val.x + size.x / 2f) / size.x;
			float num2 = (val.z + size.z / 2f) / size.z;
			float num3 = Mathf.Lerp(nd[3], nd[2], num);
			float num4 = Mathf.Lerp(nd[0], nd[1], num);
			return Mathf.Lerp(num3, num4, num2);
		}

		private static float Troch(float x, float k)
		{
			return Mathf.Sin(x - Mathf.Cos(x) * k) * 0.5f + 0.5f;
		}

		private static float One(Vector3 p, float time, float speed, float length, float height, Vector2 dir, Vector2 tan, float sharp)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = -(p.z * dir + p.x * tan);
			float num = time * speed;
			return (Troch(num + val.y * length, sharp) * Troch(num * 0.123f + val.x * 0.13123f * length, sharp) - 0.2f) * height;
		}

		private static float Wave(Vector3 p, float depth, float windW, Vector2 d0, Vector2 t0, float waterTime, float big)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: 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_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Lerp(0f, windW, depth);
			float time = waterTime / 20f;
			Vector2[] array = s_dir;
			Vector2[] array2 = s_tan;
			float num2 = One(p, time, 10f, 0.04f, 8f * big, d0, t0, 0.5f);
			float num3 = One(p, time, 14.123f, 0.08f, 6f * big, array[1], array2[1], 0.5f);
			float num4 = One(p, time, 22.312f, 0.1f, 4f * big, array[2], array2[2], 0.5f);
			float num5 = One(p, time, 31.42f, 0.2f, 2f * big, array[3], array2[3], 0.5f);
			float num6 = One(p, time, 35.42f, 0.4f, 1f * big, array[4], array2[4], 0.5f);
			float num7 = One(p, time, 38.1223f, 1f, 0.8f * big, array[5], array2[5], 0.7f);
			float num8 = One(p, time, 41.1223f, 1.2f, 0.6f, array[6], array2[6], 0.8f);
			float num9 = One(p, time, 51.5123f, 1.3f, 0.4f, array[7], array2[7], 0.9f);
			float num10 = One(p, time, 54.2f, 1.3f, 0.3f, array[8], array2[8], 0.9f);
			float num11 = One(p, time, 56.123f, 1.5f, 0.2f, array[9], array2[9], 0.9f);
			return (num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 + num10 + num11) * num;
		}
	}
	internal class FpsReadout : MonoBehaviour
	{
		internal static bool Shown;

		internal static bool Big;

		private const int Size = 4096;

		private readonly float[] m_ms = new float[4096];

		private readonly float[] m_sorted = new float[4096];

		private int m_next;

		private int m_count;

		private float m_nextText;

		private string m_text = "";

		private GUIStyle m_style;

		private GUIStyle m_bigStyle;

		private string m_big = "";

		private ProfilerRecorder m_draws;

		private ProfilerRecorder m_batches;

		private ProfilerRecorder m_setPass;

		private ProfilerRecorder m_tris;

		private bool m_recording;

		private GUIStyle m_legendStyle;

		private void Update()
		{
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			MergeRender.VizTick();
			if (Big != m_recording)
			{
				Record(Big);
			}
			if (!Shown)
			{
				m_count = 0;
				return;
			}
			m_ms[m_next] = Time.unscaledDeltaTime * 1000f;
			m_next = (m_next + 1) % 4096;
			if (m_count < 4096)
			{
				m_count++;
			}
			if (Time.unscaledTime < m_nextText)
			{
				return;
			}
			m_nextText = Time.unscaledTime + 0.5f;
			float num = 0f;
			int num2 = 0;
			for (int i = 1; i <= m_count; i++)
			{
				float num3 = m_ms[(m_next - i + 4096) % 4096];
				if (num + num3 > 10000f)
				{
					break;
				}
				m_sorted[num2++] = num3;
				num += num3;
			}
			if (num2 == 0)
			{
				return;
			}
			Array.Sort(m_sorted, 0, num2);
			float num4 = num / (float)num2;
			float num5 = m_sorted[Mathf.Clamp(num2 - 1 - num2 / 100, 0, num2 - 1)];
			m_text = $"{1000f / num4:0} fps   1% low {1000f / num5:0}   {num4:0.0} ms   (last {num / 1000f:0} s)";
			if (!Big)
			{
				return;
			}
			float num6 = 0f;
			int num7 = 0;
			for (int j = 1; j <= m_count; j++)
			{
				if (!(num6 < 500f))
				{
					break;
				}
				num6 += m_ms[(m_next - j + 4096) % 4096];
				num7++;
			}
			float num8 = num6 / (float)Mathf.Max(num7, 1);
			bool flag = Plugin.MergeOn.Value && MergeRender.Active;
			bool flag2 = MergeRender.Viz != MergeRender.VizMode.Off;
			m_big = (flag2 ? "Frame rate: not meaningful in a colour view\n" : $"{1000f / num8:0} FPS    {num8:0.0} ms\n1% low {1000f / num5:0} FPS\n") + "Draw calls " + Stat(m_draws) + "\nBatches " + Stat(m_batches) + "    SetPass " + Stat(m_setPass) + "\nTriangles " + Tris(m_tris) + "\n" + (flag ? string.Format("{0}: on, {1:N0} pieces in {2:N0} merged cells", "Valheim Enhanced Edition", MergeRender.PieceCount, MergeRender.CellCount) : "Valheim Enhanced Edition: off (vanilla)") + (Plugin.FarOn.Value ? $"\nFar view: on, {MergeRender.FarCount:N0} far cells" : "") + ((MergeRender.Viz != MergeRender.VizMode.Off) ? ("\nView: " + MergeRender.Viz.ToString().ToLowerInvariant() + ": " + MergeRender.VizLine) : "");
		}

		private static string Stat(ProfilerRecorder r)
		{
			if (!((ProfilerRecorder)(ref r)).Valid)
			{
				return "n/a";
			}
			return ((ProfilerRecorder)(ref r)).LastValue.ToString("N0");
		}

		private static string Tris(ProfilerRecorder r)
		{
			if (((ProfilerRecorder)(ref r)).Valid)
			{
				if (((ProfilerRecorder)(ref r)).LastValue < 1000000)
				{
					return $"{(double)((ProfilerRecorder)(ref r)).LastValue / 1000.0:0} k";
				}
				return $"{(double)((ProfilerRecorder)(ref r)).LastValue / 1000000.0:0.0} M";
			}
			return "n/a";
		}

		private void Record(bool on)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			m_recording = on;
			if (on)
			{
				m_draws = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count", 1, (ProfilerRecorderOptions)24);
				m_batches = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count", 1, (ProfilerRecorderOptions)24);
				m_setPass = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count", 1, (ProfilerRecorderOptions)24);
				m_tris = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Triangles Count", 1, (ProfilerRecorderOptions)24);
			}
			else
			{
				((ProfilerRecorder)(ref m_draws)).Dispose();
				((ProfilerRecorder)(ref m_batches)).Dispose();
				((ProfilerRecorder)(ref m_setPass)).Dispose();
				((ProfilerRecorder)(ref m_tris)).Dispose();
			}
		}

		private void OnDestroy()
		{
			if (m_recording)
			{
				Record(on: false);
			}
		}

		private void OnGUI()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			if (MergeRender.Viz != MergeRender.VizMode.Off)
			{
				DrawLegend();
			}
			if (!Shown || m_text.Length == 0)
			{
				return;
			}
			if (Big)
			{
				DrawBig();
				return;
			}
			if (m_style == null)
			{
				m_style = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1
				};
			}
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(12f, 8f, 600f, 24f);
			m_style.normal.textColor = Color.black;
			GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), m_text, m_style);
			m_style.normal.textColor = Color.yellow;
			GUI.Label(val, m_text, m_style);
		}

		private void DrawLegend()
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Expected O, but got Unknown
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			List<(Color, string)> list = MergeRender.VizLegend();
			float num = (float)Screen.height / 1080f;
			if (m_legendStyle == null)
			{
				m_legendStyle = new GUIStyle(GUI.skin.label)
				{
					fontStyle = (FontStyle)1,
					wordWrap = false
				};
			}
			m_legendStyle.fontSize = Mathf.RoundToInt(17f * num);
			float num2 = 24f * num;
			float num3 = 16f * num;
			float num4 = 10f * num;
			string text = "View: " + MergeRender.Viz.ToString().ToLowerInvariant() + "   " + MergeRender.VizLine;
			float num5 = m_legendStyle.CalcSize(new GUIContent(text)).x;
			foreach (var item in list)
			{
				num5 = Mathf.Max(num5, num3 + 8f * num + m_legendStyle.CalcSize(new GUIContent(item.Item2)).x);
			}
			float num6 = num2 * (float)(list.Count + 1);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(num4 * 2f, (float)Screen.height - num6 - num4 * 6f, num5 + num4 * 2f, num6 + num4);
			Color color = GUI.color;
			GUI.color = new Color(0f, 0f, 0f, 0.55f);
			GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture);
			m_legendStyle.normal.textColor = Color.white;
			GUI.color = color;
			GUI.Label(new Rect(((Rect)(ref val)).x + num4, ((Rect)(ref val)).y + num4 * 0.5f, num5, num2), text, m_legendStyle);
			for (int i = 0; i < list.Count; i++)
			{
				float num7 = ((Rect)(ref val)).y + num4 * 0.5f + num2 * (float)(i + 1);
				GUI.color = list[i].Item1;
				GUI.DrawTexture(new Rect(((Rect)(ref val)).x + num4, num7 + (num2 - num3) * 0.5f, num3, num3), (Texture)(object)Texture2D.whiteTexture);
				GUI.color = color;
				GUI.Label(new Rect(((Rect)(ref val)).x + num4 + num3 + 8f * num, num7, num5, num2), list[i].Item2, m_legendStyle);
			}
		}

		private void DrawBig()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: 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_002e: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			if (m_big.Length != 0)
			{
				float num = (float)Screen.height / 1080f;
				if (m_bigStyle == null)
				{
					m_bigStyle = new GUIStyle(GUI.skin.label)
					{
						fontStyle = (FontStyle)1,
						wordWrap = false,
						richText = false
					};
				}
				m_bigStyle.fontSize = Mathf.RoundToInt(26f * num);
				GUIContent val = new GUIContent(m_big);
				Vector2 val2 = m_bigStyle.CalcSize(val);
				Rect val3 = default(Rect);
				((Rect)(ref val3))..ctor(24f * num, 20f * num, val2.x + 4f, val2.y + 4f);
				Color color = GUI.color;
				GUI.color = new Color(0f, 0f, 0f, 0.45f);
				GUI.DrawTexture(new Rect(((Rect)(ref val3)).x - 12f * num, ((Rect)(ref val3)).y - 8f * num, ((Rect)(ref val3)).width + 24f * num, ((Rect)(ref val3)).height + 16f * num), (Texture)(object)Texture2D.whiteTexture);
				GUI.color = color;
				m_bigStyle.normal.textColor = Color.black;
				GUI.Label(new Rect(((Rect)(ref val3)).x + 2f, ((Rect)(ref val3)).y + 2f, ((Rect)(ref val3)).width, ((Rect)(ref val3)).height), val, m_bigStyle);
				m_bigStyle.normal.textColor = Color.white;
				GUI.Label(val3, val, m_bigStyle);
			}
		}
	}
	internal static class MergeRender
	{
		private class FarCell
		{
			public long Id;

			public Vector3 Origin;

			public GameObject Go;

			public readonly List<FarGroup> Groups = new List<FarGroup>();

			public float MissingSince = -1f;

			public float SeenSince = -1f;

			public bool Shown;

			public bool Foliage;