Decompiled source of FarLands v1.1.1

FarLands.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PerfGate.Shared;
using Unity.Collections;
using Unity.Profiling;
using Unity.Profiling.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.PostProcessing;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FarLands")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+b16c62b6984bcd59a8f70fe51881f64b24b9d1a7")]
[assembly: AssemblyProduct("FarLands")]
[assembly: AssemblyTitle("FarLands")]
[assembly: AssemblyVersion("1.1.1.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 PerfGate.Shared
{
	internal static class FrameCounters
	{
		private const string GpuName = "GPU Frame Time";

		private const string MainName = "CPU Main Thread Frame Time";

		private static ProfilerRecorder _gpu;

		private static ProfilerRecorder _main;

		private static bool _opened;

		public static double GpuMs => Read(ref _gpu);

		public static double MainMs => Read(ref _main);

		private static double Read(ref ProfilerRecorder r)
		{
			Open();
			if (!((ProfilerRecorder)(ref r)).Valid || ((ProfilerRecorder)(ref r)).LastValue <= 0)
			{
				return double.NaN;
			}
			return (double)((ProfilerRecorder)(ref r)).LastValue / 1000000.0;
		}

		private static void Open()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if (_opened)
			{
				return;
			}
			_opened = true;
			List<ProfilerRecorderHandle> list = new List<ProfilerRecorderHandle>();
			ProfilerRecorderHandle.GetAvailable(list);
			foreach (ProfilerRecorderHandle item in list)
			{
				ProfilerRecorderDescription description = ProfilerRecorderHandle.GetDescription(item);
				if (((ProfilerRecorderDescription)(ref description)).Name == "GPU Frame Time")
				{
					_gpu = ProfilerRecorder.StartNew(((ProfilerRecorderDescription)(ref description)).Category, "GPU Frame Time", 1, (ProfilerRecorderOptions)24);
				}
				else if (((ProfilerRecorderDescription)(ref description)).Name == "CPU Main Thread Frame Time")
				{
					_main = ProfilerRecorder.StartNew(((ProfilerRecorderDescription)(ref description)).Category, "CPU Main Thread Frame Time", 1, (ProfilerRecorderOptions)24);
				}
			}
		}
	}
}
namespace FarLands
{
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public string Category;

		public string DispName;

		public bool? Browsable;
	}
	internal static class ConfigLayout
	{
		private static readonly string[] Sections = new string[4] { "General", "Detail", "Buildings", "Air" };

		private static readonly Dictionary<string, int> Bound = new Dictionary<string, int>();

		internal static ConfigEntry<T> BindListed<T>(this ConfigFile cfg, string section, string key, T value, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return cfg.BindListed(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		internal static ConfigEntry<T> BindListed<T>(this ConfigFile cfg, string section, string key, T value, ConfigDescription description)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			return cfg.Bind<T>(section, key, value, new ConfigDescription(description.Description, description.AcceptableValues, description.Tags.Append(Attributes(section, key)).ToArray()));
		}

		private static ConfigurationManagerAttributes Attributes(string section, string key)
		{
			Bound.TryGetValue(section, out var value);
			Bound[section] = value + 1;
			return new ConfigurationManagerAttributes
			{
				Category = Category(section),
				Order = 1000 - value,
				DispName = Words(key)
			};
		}

		internal static string Category(string section)
		{
			int num = Array.IndexOf(Sections, section);
			if (num >= 0)
			{
				return num + 1 + " - " + Words(section);
			}
			return Words(section);
		}

		internal static string Words(string key)
		{
			if (key.Contains(" "))
			{
				return key;
			}
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < key.Length; i++)
			{
				char c = key[i];
				if (i > 0 && ((char.IsUpper(c) && !char.IsUpper(key[i - 1])) || (char.IsDigit(c) && !char.IsDigit(key[i - 1]))))
				{
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
				}
				stringBuilder.Append(c);
			}
			list.Add(stringBuilder.ToString());
			for (int j = 0; j < list.Count; j++)
			{
				if (j > 0 && list[j].Length > 1 && !list[j].All(char.IsUpper))
				{
					list[j] = char.ToLowerInvariant(list[j][0]) + list[j].Substring(1);
				}
			}
			return string.Join(" ", list);
		}
	}
	public static class AirDefaults
	{
		public const int SettingsVersion = 1;

		public const float NearHaze = 0.6f;

		public const float NearHazeDistance = 180f;

		public const float FarLandsHaze = 0.6f;

		public static readonly float[] FormerNearHazeDistances = new float[1] { 50f };

		public static readonly float[] FormerFarLandsHazes = new float[2] { 0.17f, 1f };

		public static float OnOldFile(float value, float current, float[] former)
		{
			foreach (float num in former)
			{
				if (Math.Abs(value - num) <= 0.0001f)
				{
					return current;
				}
			}
			return value;
		}
	}
	internal sealed class AirEase
	{
		public const float Seconds = 8f;

		private bool _have;

		public float View { get; private set; }

		public float Height { get; private set; }

		public float Mist { get; private set; }

		public float Sea { get; private set; }

		public void Step(float view, float height, float mist, float dt, float sea = 0f)
		{
			if (!float.IsNaN(view) && !float.IsNaN(height) && !float.IsNaN(mist) && !float.IsNaN(sea) && view > 0f)
			{
				if (!_have)
				{
					View = view;
					Height = height;
					Mist = mist;
					Sea = sea;
					_have = true;
				}
				else if (dt >= 0f)
				{
					float num = 1f - (float)Math.Exp((0f - Math.Min(dt, 10000f)) / 8f);
					View = (float)Math.Exp(Math.Log(View) + (Math.Log(view) - Math.Log(View)) * (double)num);
					Height += (height - Height) * num;
					Mist += (mist - Mist) * num;
					Sea += (sea - Sea) * num;
				}
			}
		}

		public void Reset()
		{
			_have = false;
		}
	}
	public sealed class BackgroundQueue<TJob, TResult> : IDisposable
	{
		private readonly Func<TJob, TResult> _work;

		private readonly object _lock = new object();

		private readonly Queue<TJob> _jobs = new Queue<TJob>();

		private readonly ConcurrentQueue<TResult> _results = new ConcurrentQueue<TResult>();

		private readonly Thread _thread;

		private volatile bool _stop;

		private volatile Exception _failure;

		public Exception Failure => _failure;

		public BackgroundQueue(Func<TJob, TResult> work, string name)
		{
			_work = work;
			_thread = new Thread(Run)
			{
				IsBackground = true,
				Priority = ThreadPriority.BelowNormal,
				Name = name
			};
			_thread.Start();
		}

		public void Post(TJob job)
		{
			lock (_lock)
			{
				_jobs.Enqueue(job);
				Monitor.Pulse(_lock);
			}
		}

		public bool TryTake(out TResult result)
		{
			return _results.TryDequeue(out result);
		}

		private void Run()
		{
			try
			{
				while (!_stop)
				{
					TJob arg;
					lock (_lock)
					{
						while (_jobs.Count == 0 && !_stop)
						{
							Monitor.Wait(_lock);
						}
						if (_stop)
						{
							break;
						}
						arg = _jobs.Dequeue();
					}
					TResult item = _work(arg);
					if (!_stop)
					{
						_results.Enqueue(item);
						continue;
					}
					break;
				}
			}
			catch (Exception failure)
			{
				_failure = failure;
			}
		}

		public void Dispose()
		{
			_stop = true;
			lock (_lock)
			{
				_jobs.Clear();
				Monitor.PulseAll(_lock);
			}
		}
	}
	public struct PlacedPiece
	{
		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 sealed class ZoneBuildings
	{
		public readonly int ZoneX;

		public readonly int ZoneZ;

		public readonly uint Print;

		public readonly PlacedPiece[] Pieces;

		public readonly ZoneEdit Edit;

		public readonly byte[][] Compilers;

		public bool IsEmpty
		{
			get
			{
				if (Pieces.Length == 0 && (Edit == null || Edit.IsEmpty))
				{
					return Compilers == null;
				}
				return false;
			}
		}

		public ZoneBuildings(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, ZoneEdit edit)
			: this(zoneX, zoneZ, print, pieces, edit, null)
		{
		}

		private ZoneBuildings(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, ZoneEdit edit, byte[][] compilers)
		{
			ZoneX = zoneX;
			ZoneZ = zoneZ;
			Print = print;
			Pieces = pieces;
			Edit = edit;
			Compilers = compilers;
		}

		public static ZoneBuildings Unparsed(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, byte[][] compilers)
		{
			return new ZoneBuildings(zoneX, zoneZ, print, pieces, null, (compilers != null && compilers.Length != 0) ? compilers : null);
		}

		public ZoneBuildings Parsed(out int bad)
		{
			bad = 0;
			if (Compilers == null)
			{
				return this;
			}
			List<ZoneEdit> list = new List<ZoneEdit>(Compilers.Length);
			byte[][] compilers = Compilers;
			foreach (byte[] compressed in compilers)
			{
				try
				{
					list.Add(TerrainCompData.Parse(ZoneX, ZoneZ, compressed));
				}
				catch (InvalidDataException)
				{
					bad++;
				}
			}
			ZoneEdit zoneEdit = TerrainCompData.Pick(list);
			return new ZoneBuildings(ZoneX, ZoneZ, Print, Pieces, (zoneEdit != null && !zoneEdit.IsEmpty) ? zoneEdit : null);
		}

		public static uint PrintOf(List<(long User, uint Id, PlacedPiece Piece)> pieces, List<(long User, uint Id, uint Revision)> compilers)
		{
			if (pieces.Count == 0 && compilers.Count == 0)
			{
				return 0u;
			}
			pieces.Sort(((long User, uint Id, PlacedPiece Piece) p, (long User, uint Id, PlacedPiece Piece) q) => (p.User == q.User) ? p.Id.CompareTo(q.Id) : p.User.CompareTo(q.User));
			compilers.Sort();
			uint h = 2166136261u;
			foreach (var piece in pieces)
			{
				h = Id(h, piece.User, piece.Id);
				PlacedPiece item = piece.Piece;
				h = Mix(h, (uint)item.Prefab);
				h = Mix(h, Bits(item.X));
				h = Mix(h, Bits(item.Y));
				h = Mix(h, Bits(item.Z));
				h = Mix(h, Bits(item.Qx));
				h = Mix(h, Bits(item.Qy));
				h = Mix(h, Bits(item.Qz));
				h = Mix(h, Bits(item.Qw));
			}
			h = Mix(h, 12648430u);
			foreach (var compiler in compilers)
			{
				h = Id(h, compiler.User, compiler.Id);
				h = Mix(h, compiler.Revision);
			}
			if (h != 0)
			{
				return h;
			}
			return 1u;
		}

		private static uint Id(uint h, long user, uint id)
		{
			return Mix(Mix(Mix(h, (uint)user), (uint)(user >> 32)), id);
		}

		private static uint Bits(float f)
		{
			return (uint)BitConverter.SingleToInt32Bits(f);
		}

		private static uint Mix(uint h, uint v)
		{
			for (int i = 0; i < 4; i++)
			{
				h = (h ^ ((v >> 8 * i) & 0xFF)) * 16777619;
			}
			return h;
		}
	}
	public sealed class BuildingCell
	{
		public readonly TileKey Key;

		public readonly IReadOnlyDictionary<(int X, int Z), ZoneBuildings> Zones;

		public IEnumerable<ZoneEdit> Edits => from z in Zones.Values
			where z.Edit != null && !z.Edit.IsEmpty
			select z.Edit;

		private BuildingCell(TileKey key, Dictionary<(int X, int Z), ZoneBuildings> zones)
		{
			Key = key;
			Zones = zones;
		}

		public static BuildingCell Empty(TileKey key)
		{
			return new BuildingCell(key, new Dictionary<(int, int), ZoneBuildings>());
		}

		public BuildingCell With(IEnumerable<ZoneBuildings> zones)
		{
			Dictionary<(int, int), ZoneBuildings> dictionary = new Dictionary<(int, int), ZoneBuildings>();
			foreach (KeyValuePair<(int, int), ZoneBuildings> zone in Zones)
			{
				dictionary[zone.Key] = zone.Value;
			}
			foreach (ZoneBuildings zone2 in zones)
			{
				if (!EditMap.CellOf(zone2.ZoneX, zone2.ZoneZ).Equals(Key))
				{
					throw new ArgumentException($"zone ({zone2.ZoneX}, {zone2.ZoneZ}) is not in cell {Key}");
				}
				if (zone2.Compilers != null)
				{
					throw new ArgumentException($"zone ({zone2.ZoneX}, {zone2.ZoneZ}) has its land compilers not parsed");
				}
				if (zone2.IsEmpty)
				{
					dictionary.Remove((zone2.ZoneX, zone2.ZoneZ));
				}
				else
				{
					dictionary[(zone2.ZoneX, zone2.ZoneZ)] = zone2;
				}
			}
			return new BuildingCell(Key, dictionary);
		}
	}
	public sealed class BuildingCodec : ITileCodec<BuildingCell>
	{
		public const int Version = 1;

		public static readonly BuildingCodec Instance = new BuildingCodec();

		public TileKey KeyOf(BuildingCell cell)
		{
			return cell.Key;
		}

		public void Encode(BuildingCell cell, Stream to)
		{
			using DeflateStream output = new DeflateStream(to, CompressionLevel.Fastest, leaveOpen: true);
			using BinaryWriter binaryWriter = new BinaryWriter(output);
			binaryWriter.Write(1);
			binaryWriter.Write(cell.Key.Level);
			binaryWriter.Write(cell.Key.X);
			binaryWriter.Write(cell.Key.Z);
			binaryWriter.Write(cell.Zones.Count);
			foreach (ZoneBuildings item in from v in cell.Zones.Values
				orderby v.ZoneZ, v.ZoneX
				select v)
			{
				binaryWriter.Write(item.ZoneX);
				binaryWriter.Write(item.ZoneZ);
				binaryWriter.Write(item.Print);
				binaryWriter.Write(item.Pieces.Length);
				PlacedPiece[] pieces = item.Pieces;
				for (int num = 0; num < pieces.Length; num++)
				{
					PlacedPiece placedPiece = pieces[num];
					binaryWriter.Write(placedPiece.Prefab);
					binaryWriter.Write(placedPiece.X);
					binaryWriter.Write(placedPiece.Y);
					binaryWriter.Write(placedPiece.Z);
					binaryWriter.Write(placedPiece.Qx);
					binaryWriter.Write(placedPiece.Qy);
					binaryWriter.Write(placedPiece.Qz);
					binaryWriter.Write(placedPiece.Qw);
				}
				ZoneEdit edit = item.Edit;
				binaryWriter.Write(edit != null);
				if (edit == null)
				{
					continue;
				}
				binaryWriter.Write(edit.Operations);
				int num2 = 0;
				int num3 = 0;
				for (int num4 = 0; num4 < edit.Delta.Length; num4++)
				{
					if (edit.Delta[num4] != 0f)
					{
						num2++;
					}
					if (edit.Painted[num4])
					{
						num3++;
					}
				}
				binaryWriter.Write(num2);
				for (int num5 = 0; num5 < edit.Delta.Length; num5++)
				{
					if (edit.Delta[num5] != 0f)
					{
						binaryWriter.Write((ushort)num5);
						binaryWriter.Write(edit.Delta[num5]);
					}
				}
				binaryWriter.Write(num3);
				for (int num6 = 0; num6 < edit.Painted.Length; num6++)
				{
					if (edit.Painted[num6])
					{
						binaryWriter.Write((ushort)num6);
						binaryWriter.Write(edit.Paint[num6]);
					}
				}
			}
		}

		public BuildingCell Decode(byte[] data, int length)
		{
			try
			{
				using DeflateStream input = new DeflateStream(new MemoryStream(data, 0, length, writable: false), CompressionMode.Decompress);
				using BinaryReader binaryReader = new BinaryReader(input);
				int num = binaryReader.ReadInt32();
				if (num != 1)
				{
					throw new InvalidDataException($"building cell format {num}, expected {1}");
				}
				TileKey key = new TileKey(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32());
				int num2 = binaryReader.ReadInt32();
				if (num2 < 0 || num2 > 64)
				{
					throw new InvalidDataException($"building cell of {num2} zones");
				}
				List<ZoneBuildings> list = new List<ZoneBuildings>(num2);
				int num3 = 4225;
				for (int i = 0; i < num2; i++)
				{
					int zoneX = binaryReader.ReadInt32();
					int zoneZ = binaryReader.ReadInt32();
					uint print = binaryReader.ReadUInt32();
					int num4 = binaryReader.ReadInt32();
					if (num4 < 0 || num4 > 1048576)
					{
						throw new InvalidDataException($"building zone of {num4} pieces");
					}
					PlacedPiece[] array = new PlacedPiece[num4];
					for (int j = 0; j < num4; j++)
					{
						array[j] = new PlacedPiece
						{
							Prefab = binaryReader.ReadInt32(),
							X = binaryReader.ReadSingle(),
							Y = binaryReader.ReadSingle(),
							Z = binaryReader.ReadSingle(),
							Qx = binaryReader.ReadSingle(),
							Qy = binaryReader.ReadSingle(),
							Qz = binaryReader.ReadSingle(),
							Qw = binaryReader.ReadSingle()
						};
					}
					ZoneEdit edit = null;
					if (binaryReader.ReadBoolean())
					{
						int operations = binaryReader.ReadInt32();
						float[] array2 = new float[num3];
						uint[] array3 = new uint[num3];
						bool[] array4 = new bool[num3];
						int num5 = binaryReader.ReadInt32();
						if (num5 < 0 || num5 > num3)
						{
							throw new InvalidDataException($"zone edit of {num5} deltas");
						}
						for (int k = 0; k < num5; k++)
						{
							int num6 = binaryReader.ReadUInt16();
							if (num6 >= num3)
							{
								throw new InvalidDataException("zone edit vertex out of range");
							}
							array2[num6] = binaryReader.ReadSingle();
						}
						int num7 = binaryReader.ReadInt32();
						if (num7 < 0 || num7 > num3)
						{
							throw new InvalidDataException($"zone edit of {num7} paints");
						}
						for (int l = 0; l < num7; l++)
						{
							int num8 = binaryReader.ReadUInt16();
							if (num8 >= num3)
							{
								throw new InvalidDataException("zone edit vertex out of range");
							}
							array4[num8] = true;
							array3[num8] = binaryReader.ReadUInt32();
						}
						edit = new ZoneEdit(zoneX, zoneZ, operations, array2, array3, array4);
					}
					list.Add(new ZoneBuildings(zoneX, zoneZ, print, array, edit));
				}
				return BuildingCell.Empty(key).With(list);
			}
			catch (InvalidDataException)
			{
				throw;
			}
			catch (Exception ex2) when (ex2 is EndOfStreamException || ex2 is IOException || ex2 is ArgumentException)
			{
				throw new InvalidDataException("damaged building cell: " + ex2.Message, ex2);
			}
		}
	}
	public static class BuildingLevels
	{
		public const float Hysteresis = 32f;

		public const float MinHandoff = 64f;

		public static bool Ours(bool wasOurs, float nearest, float handoff)
		{
			if (!(nearest >= handoff))
			{
				if (wasOurs)
				{
					return nearest >= handoff - 32f;
				}
				return false;
			}
			return true;
		}

		public static bool Ours(bool wasOurs, float nearest, float handoff, float pastLod)
		{
			if (!Ours(wasOurs, nearest, handoff))
			{
				if (Ours(wasOurs, nearest, 64f))
				{
					return Ours(wasOurs, pastLod, 0f);
				}
				return false;
			}
			return true;
		}

		public static float LodScale(float lodBias, float fovDegrees)
		{
			return lodBias / (2f * (float)Math.Tan((double)fovDegrees * 0.5 * Math.PI / 180.0));
		}

		public static float PastLod(float camX, float camY, float camZ, LodBox[] boxes, float scale)
		{
			float num = float.NegativeInfinity;
			if (boxes == null)
			{
				return num;
			}
			for (int i = 0; i < boxes.Length; i++)
			{
				LodBox lodBox = boxes[i];
				float num2 = Math.Max(camX - lodBox.MinX, lodBox.MaxX - camX);
				float num3 = Math.Max(camY - lodBox.MinY, lodBox.MaxY - camY);
				float num4 = Math.Max(camZ - lodBox.MinZ, lodBox.MaxZ - camZ);
				num = Math.Max(num, (float)Math.Sqrt(num2 * num2 + num3 * num3 + num4 * num4) - lodBox.LodSize * scale);
			}
			return num;
		}

		public static float Nearest(float camX, float camZ, float minX, float minZ, float maxX, float maxZ)
		{
			float num = Math.Max(Math.Max(minX - camX, camX - maxX), 0f);
			float num2 = Math.Max(Math.Max(minZ - camZ, camZ - maxZ), 0f);
			return (float)Math.Sqrt(num * num + num2 * num2);
		}

		public static bool CellLevel(float nearestOfCell, float cellDistance)
		{
			return nearestOfCell >= cellDistance;
		}

		public static float ZoneMergesFrom(float handoff, float areaEdge, float lead)
		{
			return Math.Min(handoff - 32f, areaEdge) - lead;
		}

		public static bool RemergeNow(bool hasMerge, bool gameDraws, float nearest, float handoff, float pastLod = float.NegativeInfinity)
		{
			if (hasMerge && gameDraws)
			{
				return Ours(wasOurs: true, nearest, handoff, pastLod);
			}
			return true;
		}
	}
	public struct LodBox
	{
		public float MinX;

		public float MinY;

		public float MinZ;

		public float MaxX;

		public float MaxY;

		public float MaxZ;

		public float LodSize;
	}
	public static class GameArea
	{
		public static bool Near(int dx, int dz, int near, bool classic)
		{
			int num = Math.Max(Math.Abs(dx), Math.Abs(dz));
			if (num == 0)
			{
				return true;
			}
			if (num > near)
			{
				return false;
			}
			if (!classic)
			{
				return 4 * ((long)dx * (long)dx + (long)dz * (long)dz) < (2L * (long)near + 1) * (2L * (long)near + 1);
			}
			return true;
		}

		public static float Edge(int near, bool classic)
		{
			near = Math.Max(0, near);
			float num = float.MaxValue;
			for (int i = 0; i <= near + 1; i++)
			{
				for (int j = 0; j <= near + 1; j++)
				{
					if (!Near(i, j, near, classic))
					{
						num = Math.Min(num, Gap(Math.Max(0, i - 1), Math.Max(0, j - 1)));
					}
				}
			}
			return num;
		}

		public static float Reach(int near, bool classic)
		{
			near = Math.Max(0, near);
			float num = 0f;
			for (int i = 0; i <= near; i++)
			{
				for (int j = 0; j <= near; j++)
				{
					if (Near(i, j, near, classic))
					{
						num = Math.Max(num, Gap(i, j));
					}
				}
			}
			return num;
		}

		private static float Gap(int zonesX, int zonesZ)
		{
			return (float)Math.Sqrt((double)zonesX * (double)zonesX + (double)zonesZ * (double)zonesZ) * 64f;
		}
	}
	public static class CanopyShade
	{
		public const float MinSunSine = 0.087f;

		public const double SkyFitA = 0.874;

		public const double SkyFitB = 0.921;

		public static float[] Silhouettes(byte[] mip, int frames)
		{
			int num = (int)Math.Round(Math.Sqrt(mip.Length));
			int num2 = num / frames;
			float[] array = new float[frames * frames * 2];
			if (num2 == 0 || num * num != mip.Length)
			{
				return array;
			}
			for (int i = 0; i < frames; i++)
			{
				for (int j = 0; j < frames; j++)
				{
					double num3 = 0.0;
					double num4 = 0.0;
					for (int k = 0; k < num2; k++)
					{
						double num5 = ((double)k + 0.5) / (double)num2 * 2.0 - 1.0;
						int num6 = (i * num2 + k) * num + j * num2;
						for (int l = 0; l < num2; l++)
						{
							byte b = mip[num6 + l];
							num3 += (double)(int)b;
							num4 += (double)(int)b * num5;
						}
					}
					int num7 = (i * frames + j) * 2;
					array[num7] = (float)(num3 / (255.0 * (double)num2 * (double)num2));
					array[num7 + 1] = ((num3 > 0.0) ? ((float)(num4 / num3)) : 0f);
				}
			}
			return array;
		}

		public static bool ForSun(float[] silhouettes, int frames, float centreY, float radius, float lx, float ly, float lz, out float area, out float offset, out float length)
		{
			area = (offset = (length = 0f));
			if (silhouettes == null || silhouettes.Length < frames * frames * 2 || radius <= 0f)
			{
				return false;
			}
			float num = Math.Max(ly, 0.087f);
			float num2 = (float)Math.Sqrt(lx * lx + lz * lz);
			float num3 = ((num2 > 1E-06f) ? (lx / num2) : 0f);
			float num4 = ((num2 > 1E-06f) ? (lz / num2) : 0f);
			float num5 = (float)Math.Sqrt(1.0 - (double)(num * num));
			ImpostorMath.Encode(num3 * num5, num, num4 * num5, out var u, out var v);
			float num6 = Clamp((u * 0.5f + 0.5f) * (float)frames - 0.5f, frames);
			float num7 = Clamp((v * 0.5f + 0.5f) * (float)frames - 0.5f, frames);
			int num8 = Math.Min(frames - 2, (int)num6);
			int num9 = Math.Min(frames - 2, (int)num7);
			float num10 = num6 - (float)num8;
			float num11 = num7 - (float)num9;
			float num12 = 0f;
			float num13 = 0f;
			for (int i = 0; i < 4; i++)
			{
				int num14 = num8 + (i & 1);
				int num15 = num9 + (i >> 1);
				float num16 = (((i & 1) != 0) ? num10 : (1f - num10)) * ((i >> 1 != 0) ? num11 : (1f - num11));
				float num17 = silhouettes[(num15 * frames + num14) * 2];
				num12 += num16 * num17;
				num13 += num16 * num17 * silhouettes[(num15 * frames + num14) * 2 + 1];
			}
			if (num12 <= 0f)
			{
				return false;
			}
			float num18 = num13 / num12 * radius;
			float num19 = num12 * 4f * radius * radius;
			area = num19 / num;
			offset = (centreY * num5 + num18) / num;
			length = (float)Math.Sqrt(num19) / num;
			return true;
		}

		public static float SkyThrough(float density)
		{
			if (density <= 0f)
			{
				return 1f;
			}
			return (float)(Math.Exp(-0.874 * (double)density) / (1.0 + 0.921 * (double)density));
		}

		private static float Clamp(float g, int frames)
		{
			return Math.Max(0f, Math.Min(frames - 1, g));
		}
	}
	public sealed class CoverageSet<T> where T : class
	{
		private Dictionary<TileKey, T> _shown = new Dictionary<TileKey, T>();

		private readonly Dictionary<TileKey, T> _arrived = new Dictionary<TileKey, T>();

		private HashSet<TileKey> _wanted;

		public IReadOnlyDictionary<TileKey, T> Shown => _shown;

		public bool HasWanted => _wanted != null;

		public IEnumerable<T> Views => _shown.Values.Concat(_arrived.Values);

		public bool Ready
		{
			get
			{
				if (_wanted == null)
				{
					return false;
				}
				foreach (TileKey item in _wanted)
				{
					if (!_shown.ContainsKey(item) && !_arrived.ContainsKey(item))
					{
						return false;
					}
				}
				return true;
			}
		}

		public IEnumerable<KeyValuePair<TileKey, T>> Coming
		{
			get
			{
				T value;
				if (_wanted != null)
				{
					return from k in _wanted
						where _arrived.ContainsKey(k) || _shown.ContainsKey(k)
						select new KeyValuePair<TileKey, T>(k, _arrived.TryGetValue(k, out value) ? value : _shown[k]);
				}
				return Enumerable.Empty<KeyValuePair<TileKey, T>>();
			}
		}

		public int Missing
		{
			get
			{
				if (_wanted == null)
				{
					return 0;
				}
				int num = 0;
				foreach (TileKey item in _wanted)
				{
					if (!_shown.ContainsKey(item) && !_arrived.ContainsKey(item))
					{
						num++;
					}
				}
				return num;
			}
		}

		public List<T> Want(IEnumerable<TileKey> keys)
		{
			_wanted = new HashSet<TileKey>(keys);
			List<T> list = new List<T>();
			foreach (TileKey item in _arrived.Keys.Where((TileKey k) => !_wanted.Contains(k)).ToList())
			{
				list.Add(_arrived[item]);
				_arrived.Remove(item);
			}
			return list;
		}

		public bool IsWanted(TileKey key)
		{
			if (_wanted != null)
			{
				return _wanted.Contains(key);
			}
			return false;
		}

		public bool TryGetComing(TileKey key, out T view)
		{
			view = null;
			if (IsWanted(key))
			{
				if (!_arrived.TryGetValue(key, out view))
				{
					return _shown.TryGetValue(key, out view);
				}
				return true;
			}
			return false;
		}

		public List<TileKey> ToBuild()
		{
			if (_wanted != null)
			{
				return _wanted.Where((TileKey k) => !_shown.ContainsKey(k) && !_arrived.ContainsKey(k)).ToList();
			}
			return new List<TileKey>();
		}

		public T Arrive(TileKey key, T view)
		{
			if (!IsWanted(key))
			{
				return view;
			}
			_arrived.TryGetValue(key, out var value);
			_arrived[key] = view;
			return value;
		}

		public T Replace(TileKey key, T view)
		{
			if (IsWanted(key))
			{
				return Arrive(key, view);
			}
			if (!_shown.TryGetValue(key, out var value))
			{
				return view;
			}
			_shown[key] = view;
			return value;
		}

		public bool TrySwap(out List<T> hidden, out List<T> shownNow)
		{
			hidden = null;
			shownNow = null;
			if (!Ready)
			{
				return false;
			}
			Dictionary<TileKey, T> dictionary = new Dictionary<TileKey, T>();
			shownNow = new List<T>();
			hidden = (from p in _shown
				where !_wanted.Contains(p.Key)
				select p.Value).ToList();
			foreach (TileKey item in _wanted)
			{
				if (_arrived.TryGetValue(item, out var value))
				{
					dictionary[item] = value;
					shownNow.Add(value);
					if (_shown.TryGetValue(item, out var value2))
					{
						hidden.Add(value2);
					}
				}
				else
				{
					dictionary[item] = _shown[item];
				}
			}
			_shown = dictionary;
			_arrived.Clear();
			_wanted = null;
			return true;
		}
	}
	public sealed class DetailGovernor
	{
		public const float Raise = 0.6f;

		public const float Max = 2.5f;

		public const double RoomBelow = 0.6;

		public const double BusyAbove = 0.85;

		public const double WindowSeconds = 10.0;

		private const int WindowsToChange = 2;

		private readonly List<double> _shares = new List<double>();

		private double _windowTime;

		private int _room;

		private int _busy;

		public bool Raised { get; private set; }

		public double LastShare { get; private set; } = double.NaN;

		public bool Frame(double gpuMs, double frameMs)
		{
			if (frameMs <= 0.0 || double.IsNaN(frameMs))
			{
				return false;
			}
			if (!double.IsNaN(gpuMs) && gpuMs > 0.0)
			{
				_shares.Add(gpuMs / frameMs);
			}
			_windowTime += frameMs / 1000.0;
			if (_windowTime < 10.0)
			{
				return false;
			}
			_windowTime = 0.0;
			if (_shares.Count < 30)
			{
				_shares.Clear();
				_room = (_busy = 0);
				return false;
			}
			_shares.Sort();
			LastShare = _shares[_shares.Count / 2];
			_shares.Clear();
			_room = ((LastShare < 0.6) ? (_room + 1) : 0);
			_busy = ((LastShare > 0.85) ? (_busy + 1) : 0);
			if (!Raised && _room >= 2)
			{
				Raised = true;
				_room = 0;
				return true;
			}
			if (Raised && _busy >= 2)
			{
				Raised = false;
				_busy = 0;
				return true;
			}
			return false;
		}

		public void Skip()
		{
			_shares.Clear();
			_windowTime = 0.0;
			_room = (_busy = 0);
		}

		public float Detail(float set)
		{
			if (!Raised)
			{
				return set;
			}
			return Math.Min(set + 0.6f, 2.5f);
		}

		public float Near(float set)
		{
			if (!Raised)
			{
				return set;
			}
			return Math.Max(2f, set * 0.5f);
		}
	}
	public sealed class EditMap
	{
		private sealed class Snapshot
		{
			public readonly Dictionary<(int X, int Z), ZoneEdit> Zones;

			public readonly HashSet<TileKey> Cells;

			public Snapshot()
			{
				Zones = new Dictionary<(int, int), ZoneEdit>();
				Cells = new HashSet<TileKey>();
			}

			public Snapshot(Snapshot from)
			{
				Zones = new Dictionary<(int, int), ZoneEdit>(from.Zones);
				Cells = new HashSet<TileKey>(from.Cells);
			}
		}

		private readonly object _write = new object();

		private volatile Snapshot _snap = new Snapshot();

		private readonly List<(int X, int Z)> _changed = new List<(int, int)>();

		private readonly HashSet<(int X, int Z)> _changedSet = new HashSet<(int, int)>();

		public static TileKey CellOf(int zoneX, int zoneZ)
		{
			return new TileKey(0, FloorDiv(zoneX), FloorDiv(zoneZ));
		}

		private static int FloorDiv(int zone)
		{
			return (int)Math.Floor((double)zone / 8.0);
		}

		public void SetCell(TileKey cell, IEnumerable<ZoneEdit> edits)
		{
			SetCells(new(TileKey, IEnumerable<ZoneEdit>)[1] { (cell, edits) });
		}

		public void SetCells(IEnumerable<(TileKey Cell, IEnumerable<ZoneEdit> Edits)> cells)
		{
			List<(TileKey, Dictionary<(int, int), ZoneEdit>)> list = new List<(TileKey, Dictionary<(int, int), ZoneEdit>)>();
			foreach (var cell in cells)
			{
				TileKey item = cell.Cell;
				IEnumerable<ZoneEdit> item2 = cell.Edits;
				Dictionary<(int, int), ZoneEdit> dictionary = new Dictionary<(int, int), ZoneEdit>();
				foreach (ZoneEdit item5 in item2)
				{
					if (!CellOf(item5.ZoneX, item5.ZoneZ).Equals(item))
					{
						throw new ArgumentException($"zone ({item5.ZoneX}, {item5.ZoneZ}) is not in cell {item}");
					}
					if (!item5.IsEmpty)
					{
						dictionary[(item5.ZoneX, item5.ZoneZ)] = item5;
					}
				}
				list.Add((item, dictionary));
			}
			if (list.Count == 0)
			{
				return;
			}
			lock (_write)
			{
				Snapshot snapshot = new Snapshot(_snap);
				foreach (var item6 in list)
				{
					TileKey item3 = item6.Item1;
					Dictionary<(int, int), ZoneEdit> item4 = item6.Item2;
					bool flag = !snapshot.Cells.Add(item3);
					for (int i = 0; i < 8; i++)
					{
						for (int j = 0; j < 8; j++)
						{
							(int, int) tuple = (item3.X * 8 + j, item3.Z * 8 + i);
							snapshot.Zones.TryGetValue(tuple, out var value);
							item4.TryGetValue(tuple, out var value2);
							if (!flag || (value?.Print ?? 0) != (value2?.Print ?? 0))
							{
								Changed(tuple);
							}
							if (value2 != null)
							{
								snapshot.Zones[tuple] = value2;
							}
							else if (value != null)
							{
								snapshot.Zones.Remove(tuple);
							}
						}
					}
				}
				_snap = snapshot;
			}
		}

		private void Changed((int X, int Z) zone)
		{
			if (_changedSet.Add(zone))
			{
				_changed.Add(zone);
			}
		}

		public void ForgetCell(TileKey cell)
		{
			lock (_write)
			{
				if (!_snap.Cells.Contains(cell))
				{
					return;
				}
				Snapshot snapshot = new Snapshot(_snap);
				snapshot.Cells.Remove(cell);
				for (int i = 0; i < 8; i++)
				{
					for (int j = 0; j < 8; j++)
					{
						(int, int) tuple = (cell.X * 8 + j, cell.Z * 8 + i);
						if (snapshot.Zones.Remove(tuple))
						{
							Changed(tuple);
						}
					}
				}
				_snap = snapshot;
			}
		}

		public bool Known(TileKey cell)
		{
			return _snap.Cells.Contains(cell);
		}

		public bool HasZone(int zoneX, int zoneZ)
		{
			return _snap.Zones.ContainsKey((zoneX, zoneZ));
		}

		public float Delta(float x, float z)
		{
			if (!_snap.Zones.TryGetValue((ForestCell.ZoneOf(x), ForestCell.ZoneOf(z)), out var value))
			{
				return 0f;
			}
			return value.DeltaAt(x, z);
		}

		public bool Paint(float x, float z, out uint paint)
		{
			if (_snap.Zones.TryGetValue((ForestCell.ZoneOf(x), ForestCell.ZoneOf(z)), out var value))
			{
				return value.PaintAt(x, z, out paint);
			}
			paint = 0u;
			return false;
		}

		public uint PrintOver(float minX, float minZ, float maxX, float maxZ)
		{
			Snapshot snap = _snap;
			uint num = 2166136261u;
			bool flag = false;
			int num2 = ForestCell.ZoneOf(minZ);
			int num3 = ForestCell.ZoneOf(maxZ);
			int num4 = ForestCell.ZoneOf(minX);
			int num5 = ForestCell.ZoneOf(maxX);
			for (int i = num2; i <= num3; i++)
			{
				for (int j = num4; j <= num5; j++)
				{
					if (snap.Zones.TryGetValue((j, i), out var value))
					{
						flag = true;
						num = (num ^ (uint)j) * 16777619;
						num = (num ^ (uint)i) * 16777619;
						num = (num ^ value.Print) * 16777619;
					}
				}
			}
			if (flag)
			{
				if (num != 0)
				{
					return num;
				}
				return 1u;
			}
			return 0u;
		}

		public List<(int X, int Z)> TakeChangedZones()
		{
			lock (_write)
			{
				List<(int, int)> result = new List<(int, int)>(_changed);
				_changed.Clear();
				_changedSet.Clear();
				return result;
			}
		}
	}
	public static class FarLandsLand
	{
		private sealed class Change
		{
			public string Owner;

			public Func<float, float, float, float> Delta;

			public Func<float, float, float, uint, uint> Paint;

			public Func<float, float, float, float, float, uint> Print;

			public Func<float, float, bool> Cleared;
		}

		internal sealed class ZoneFeed
		{
			private readonly Queue<(int X, int Z)> _queue = new Queue<(int, int)>();

			private readonly HashSet<(int X, int Z)> _set = new HashSet<(int, int)>();

			internal void Add((int X, int Z) zone)
			{
				if (_set.Add(zone))
				{
					_queue.Enqueue(zone);
				}
			}

			public void Take(List<(int X, int Z)> into, int most)
			{
				lock (Gate)
				{
					for (int i = 0; i < most; i++)
					{
						if (_queue.Count <= 0)
						{
							break;
						}
						(int, int) item = _queue.Dequeue();
						_set.Remove(item);
						into.Add(item);
					}
				}
			}
		}

		public const int ApiVersion = 1;

		internal const int ChangedPerFrame = 256;

		private static readonly object Gate = new object();

		private static volatile Change[] s_all = new Change[0];

		private static volatile int s_version;

		private static ZoneFeed[] s_feeds = new ZoneFeed[0];

		internal static volatile EditMap Edits;

		internal static Action<string> Warn;

		internal static int Version => s_version;

		public static object Add(string owner, Func<float, float, float, float> delta, Func<float, float, float, uint, uint> paint, Func<float, float, float, float, float, uint> print, Func<float, float, bool> cleared)
		{
			if (print == null)
			{
				throw new ArgumentNullException("print");
			}
			Change change = new Change
			{
				Owner = (owner ?? "?"),
				Delta = delta,
				Paint = paint,
				Print = print,
				Cleared = cleared
			};
			lock (Gate)
			{
				Change[] array = new Change[s_all.Length + 1];
				Array.Copy(s_all, array, s_all.Length);
				array[s_all.Length] = change;
				s_all = array;
				s_version++;
				return change;
			}
		}

		public static void Remove(object handle)
		{
			lock (Gate)
			{
				int num = Array.IndexOf(s_all, handle as Change);
				if (num >= 0)
				{
					Change[] array = new Change[s_all.Length - 1];
					Array.Copy(s_all, 0, array, 0, num);
					Array.Copy(s_all, num + 1, array, num, array.Length - num);
					s_all = array;
					s_version++;
				}
			}
		}

		public static void ZoneChanged(int zoneX, int zoneZ)
		{
			lock (Gate)
			{
				ZoneFeed[] array = s_feeds;
				for (int i = 0; i < array.Length; i++)
				{
					array[i].Add((X: zoneX, Z: zoneZ));
				}
			}
		}

		public static bool PlayerEdited(int zoneX, int zoneZ)
		{
			return Edits?.HasZone(zoneX, zoneZ) ?? false;
		}

		internal static ZoneFeed Follow()
		{
			ZoneFeed zoneFeed = new ZoneFeed();
			lock (Gate)
			{
				ZoneFeed[] array = new ZoneFeed[s_feeds.Length + 1];
				Array.Copy(s_feeds, array, s_feeds.Length);
				array[s_feeds.Length] = zoneFeed;
				s_feeds = array;
				return zoneFeed;
			}
		}

		internal static void Unfollow(ZoneFeed feed)
		{
			lock (Gate)
			{
				int num = Array.IndexOf(s_feeds, feed);
				if (num >= 0)
				{
					ZoneFeed[] array = new ZoneFeed[s_feeds.Length - 1];
					Array.Copy(s_feeds, 0, array, 0, num);
					Array.Copy(s_feeds, num + 1, array, num, array.Length - num);
					s_feeds = array;
				}
			}
		}

		internal static float Delta(float x, float z, float spacing)
		{
			Change[] array = s_all;
			float num = 0f;
			Change[] array2 = array;
			foreach (Change change in array2)
			{
				if (change.Delta != null)
				{
					try
					{
						num += change.Delta(x, z, spacing);
					}
					catch (Exception e)
					{
						Fail(change, e);
					}
				}
			}
			return num;
		}

		internal static uint Paint(float x, float z, float spacing, uint mask)
		{
			Change[] array = s_all;
			foreach (Change change in array)
			{
				if (change.Paint != null)
				{
					try
					{
						mask = change.Paint(x, z, spacing, mask);
					}
					catch (Exception e)
					{
						Fail(change, e);
					}
				}
			}
			return mask;
		}

		internal static uint Print(uint edits, float minX, float minZ, float maxX, float maxZ, float spacing)
		{
			Change[] array = s_all;
			uint num = edits;
			bool flag = false;
			Change[] array2 = array;
			foreach (Change change in array2)
			{
				uint num2;
				try
				{
					num2 = change.Print(minX, minZ, maxX, maxZ, spacing);
				}
				catch (Exception e)
				{
					Fail(change, e);
					continue;
				}
				if (num2 != 0)
				{
					if (!flag)
					{
						num = (0x811C9DC5u ^ edits) * 16777619;
						flag = true;
					}
					num = (num ^ num2) * 16777619;
				}
			}
			if (!flag || num != 0)
			{
				return num;
			}
			return 1u;
		}

		internal static bool Cleared(float x, float z)
		{
			Change[] array = s_all;
			foreach (Change change in array)
			{
				if (change.Cleared == null)
				{
					continue;
				}
				try
				{
					if (change.Cleared(x, z))
					{
						return true;
					}
				}
				catch (Exception e)
				{
					Fail(change, e);
				}
			}
			return false;
		}

		internal static uint ClearedPrint(float minX, float minZ, float maxX, float maxZ)
		{
			Change[] array = s_all;
			uint num = 2166136261u;
			bool flag = false;
			Change[] array2 = array;
			foreach (Change change in array2)
			{
				if (change.Cleared != null)
				{
					uint num2;
					try
					{
						num2 = change.Print(minX, minZ, maxX, maxZ, 0f);
					}
					catch (Exception e)
					{
						Fail(change, e);
						continue;
					}
					if (num2 != 0)
					{
						flag = true;
						num = (num ^ num2) * 16777619;
					}
				}
			}
			if (flag)
			{
				if (num != 0)
				{
					return num;
				}
				return 1u;
			}
			return 0u;
		}

		private static void Fail(Change c, Exception e)
		{
			lock (Gate)
			{
				if (Array.IndexOf(s_all, c) < 0)
				{
					return;
				}
				Remove(c);
			}
			Warn?.Invoke($"{c.Owner}'s change of the far land failed and is left out: {e}");
		}
	}
	public static class FarSink
	{
		public const float NoHiding = 30f;

		public static float UnderLoaded(float distance, float radius, float depth)
		{
			if (distance >= radius)
			{
				return 0f;
			}
			float num = radius - 30f;
			if (!(distance <= num))
			{
				return depth * (radius - distance) / 30f;
			}
			return depth;
		}

		public static float Depth(float distance, float hide, float height)
		{
			float num = 1f - distance / (hide - 30f);
			float num2 = 80f * (float.IsNaN(num) ? 0f : Clamp01(num));
			float num3 = Clamp01((distance - 100f) * 0.005f);
			float num4 = 2f * num3 * num3 * (3f - 2f * num3);
			if (!(30f < height))
			{
				return num2 + num4;
			}
			return num2 - num4;
		}

		private static float Clamp01(float v)
		{
			return Math.Max(0f, Math.Min(1f, v));
		}
	}
	public readonly struct FarSurface : IEquatable<FarSurface>
	{
		public readonly float Hide;

		public static FarSurface Tiles => new FarSurface(30f);

		public FarSurface(float hide)
		{
			Hide = hide;
		}

		public static FarSurface Game(float hide)
		{
			return new FarSurface(hide);
		}

		public float Depth(float distance, float height)
		{
			return FarSink.Depth(distance, Hide, height);
		}

		public bool Equals(FarSurface o)
		{
			return Hide == o.Hide;
		}

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

		public override int GetHashCode()
		{
			return Hide.GetHashCode();
		}
	}
	public sealed class Footprint
	{
		public struct Shape
		{
			public float X;

			public float Z;

			public float AxisX;

			public float AxisZ;

			public float HalfX;

			public float HalfZ;

			public bool Box;
		}

		public readonly Shape[] Shapes;

		public Footprint(Shape[] shapes)
		{
			Shapes = shapes;
		}

		public bool Covers(float dx, float dz, float yaw, float scale)
		{
			float num = (float)Math.Cos(yaw);
			float num2 = (float)Math.Sin(yaw);
			float num3 = (dx * num - dz * num2) / scale;
			float num4 = (dx * num2 + dz * num) / scale;
			Shape[] shapes = Shapes;
			for (int i = 0; i < shapes.Length; i++)
			{
				Shape shape = shapes[i];
				float num5 = num3 - shape.X;
				float num6 = num4 - shape.Z;
				float num7 = (num5 * shape.AxisX + num6 * shape.AxisZ) / shape.HalfX;
				float num8 = (num6 * shape.AxisX - num5 * shape.AxisZ) / shape.HalfZ;
				bool num9;
				if (!shape.Box)
				{
					num9 = num7 * num7 + num8 * num8 <= 1f;
				}
				else
				{
					if (!(Math.Abs(num7) <= 1f))
					{
						continue;
					}
					num9 = Math.Abs(num8) <= 1f;
				}
				if (num9)
				{
					return true;
				}
			}
			return false;
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Shape[] shapes = Shapes;
			for (int i = 0; i < shapes.Length; i++)
			{
				Shape shape = shapes[i];
				stringBuilder.Append(string.Join(",", F(shape.X), F(shape.Z), F(shape.AxisX), F(shape.AxisZ), F(shape.HalfX), F(shape.HalfZ), shape.Box)).Append(';');
			}
			return stringBuilder.ToString();
		}

		private static string F(float v)
		{
			return v.ToString("R", CultureInfo.InvariantCulture);
		}
	}
	public sealed class ForestCell
	{
		public const int ZonesPerSide = 8;

		public readonly TileKey Key;

		public readonly TreeInstance[] Trees;

		public readonly ulong Observed;

		public readonly uint Clearings;

		public ForestCell(TileKey key, TreeInstance[] trees, ulong observed, uint clearings = 0u)
		{
			if (key.Level != 0)
			{
				throw new ArgumentException($"forest cell {key}: cells are level-0 tiles");
			}
			Key = key;
			Trees = trees;
			Observed = observed;
			Clearings = clearings;
		}

		public static int ZoneOf(float coordinate)
		{
			return (int)Math.Floor((coordinate + 32f) / 64f);
		}

		public static TileKey CellOf(int zoneX, int zoneZ)
		{
			return new TileKey(0, (int)Math.Floor((double)zoneX / 8.0), (int)Math.Floor((double)zoneZ / 8.0));
		}

		public static void Bounds(TileKey key, out float minX, out float minZ, out float maxX, out float maxZ)
		{
			minX = (float)key.X * 512f - 32f;
			minZ = (float)key.Z * 512f - 32f;
			maxX = minX + 512f;
			maxZ = minZ + 512f;
		}

		public ForestCell Cleared(uint clearings, Func<float, float, bool> cleared)
		{
			List<TreeInstance> list = new List<TreeInstance>(Trees.Length);
			TreeInstance[] trees = Trees;
			for (int i = 0; i < trees.Length; i++)
			{
				TreeInstance item = trees[i];
				int num = ZoneOf(item.X) - Key.X * 8;
				int num2 = ZoneOf(item.Z) - Key.Z * 8;
				if ((num >= 0 && num2 >= 0 && num < 8 && num2 < 8 && (Observed & (ulong)(1L << num2 * 8 + num)) != 0) || !cleared(item.X, item.Z))
				{
					list.Add(item);
				}
			}
			return new ForestCell(Key, list.ToArray(), Observed, clearings);
		}

		public int ZoneBit(int zoneX, int zoneZ)
		{
			int num = zoneX - Key.X * 8;
			int num2 = zoneZ - Key.Z * 8;
			if (num < 0 || num2 < 0 || num >= 8 || num2 >= 8)
			{
				throw new ArgumentOutOfRangeException("zoneX", $"zone ({zoneX}, {zoneZ}) is not in cell {Key}");
			}
			return num2 * 8 + num;
		}

		public bool IsObserved(int zoneX, int zoneZ)
		{
			return (Observed & (ulong)(1L << ZoneBit(zoneX, zoneZ))) != 0;
		}

		public ForestCell WithZone(int zoneX, int zoneZ, IReadOnlyList<TreeInstance> seen)
		{
			return WithZones(new Dictionary<(int, int), List<TreeInstance>> { [(zoneX, zoneZ)] = seen.ToList() });
		}

		public ForestCell WithZones(IReadOnlyDictionary<(int X, int Z), List<TreeInstance>> seen)
		{
			ulong num = 0uL;
			bool flag = true;
			foreach (KeyValuePair<(int, int), List<TreeInstance>> zone in seen)
			{
				num |= (ulong)(1L << ZoneBit(zone.Key.Item1, zone.Key.Item2));
				flag = flag && IsObserved(zone.Key.Item1, zone.Key.Item2) && SameTrees(Trees.Where((TreeInstance t) => ZoneOf(t.X) == zone.Key.Item1 && ZoneOf(t.Z) == zone.Key.Item2), zone.Value);
			}
			if (flag)
			{
				return this;
			}
			TreeInstance[] trees = Trees.Where((TreeInstance t) => !seen.ContainsKey((ZoneOf(t.X), ZoneOf(t.Z)))).Concat(seen.Values.SelectMany((List<TreeInstance> v) => v)).ToArray();
			return new ForestCell(Key, trees, Observed | num);
		}

		private static bool SameTrees(IEnumerable<TreeInstance> a, IEnumerable<TreeInstance> b)
		{
			Func<TreeInstance, (byte, float, float, float, float, float, float, float)> selector = (TreeInstance t) => (t.Type, t.X, t.Z, t.Y, t.Yaw, t.Scale, t.TiltX, t.TiltZ);
			return (from k in a.Select(selector)
				orderby k
				select k).SequenceEqual(from k in b.Select(selector)
				orderby k
				select k);
		}
	}
	public sealed class ForestCodec : ITileCodec<ForestCell>
	{
		public const int Version = 2;

		public static readonly ForestCodec Instance = new ForestCodec();

		public TileKey KeyOf(ForestCell cell)
		{
			return cell.Key;
		}

		public void Encode(ForestCell cell, Stream to)
		{
			using DeflateStream output = new DeflateStream(to, CompressionLevel.Fastest, leaveOpen: true);
			using BinaryWriter binaryWriter = new BinaryWriter(output);
			binaryWriter.Write(2);
			binaryWriter.Write(cell.Key.Level);
			binaryWriter.Write(cell.Key.X);
			binaryWriter.Write(cell.Key.Z);
			binaryWriter.Write(cell.Observed);
			binaryWriter.Write(cell.Trees.Length);
			TreeInstance[] trees = cell.Trees;
			for (int i = 0; i < trees.Length; i++)
			{
				TreeInstance treeInstance = trees[i];
				binaryWriter.Write(treeInstance.Type);
				binaryWriter.Write(treeInstance.X);
				binaryWriter.Write(treeInstance.Y);
				binaryWriter.Write(treeInstance.Z);
				binaryWriter.Write(treeInstance.Yaw);
				binaryWriter.Write(treeInstance.Scale);
				binaryWriter.Write(treeInstance.TiltX);
				binaryWriter.Write(treeInstance.TiltZ);
			}
		}

		public ForestCell Decode(byte[] data, int length)
		{
			try
			{
				using DeflateStream input = new DeflateStream(new MemoryStream(data, 0, length, writable: false), CompressionMode.Decompress);
				using BinaryReader binaryReader = new BinaryReader(input);
				int num = binaryReader.ReadInt32();
				if (num != 2)
				{
					throw new InvalidDataException($"forest cell format {num}, expected {2}");
				}
				TileKey key = new TileKey(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32());
				ulong observed = binaryReader.ReadUInt64();
				int num2 = binaryReader.ReadInt32();
				if (num2 < 0 || num2 > 1048576)
				{
					throw new InvalidDataException($"forest cell of {num2} trees");
				}
				TreeInstance[] array = new TreeInstance[num2];
				for (int i = 0; i < num2; i++)
				{
					array[i] = new TreeInstance
					{
						Type = binaryReader.ReadByte(),
						X = binaryReader.ReadSingle(),
						Y = binaryReader.ReadSingle(),
						Z = binaryReader.ReadSingle(),
						Yaw = binaryReader.ReadSingle(),
						Scale = binaryReader.ReadSingle(),
						TiltX = binaryReader.ReadSingle(),
						TiltZ = binaryReader.ReadSingle()
					};
				}
				return new ForestCell(key, array, observed);
			}
			catch (InvalidDataException)
			{
				throw;
			}
			catch (Exception ex2) when (ex2 is EndOfStreamException || ex2 is IOException || ex2 is ArgumentException)
			{
				throw new InvalidDataException("damaged forest cell: " + ex2.Message, ex2);
			}
		}
	}
	public static class ForestGenerator
	{
		public const int Version = 8;

		public static ForestCell Generate(TileKey cell, IReadOnlyList<VegetationRule> rules, IVegetationWorld world, int worldSeed)
		{
			List<TreeInstance> list = new List<TreeInstance>();
			for (int i = 0; i < 8; i++)
			{
				for (int j = 0; j < 8; j++)
				{
					int num = cell.X * 8 + j;
					int num2 = cell.Z * 8 + i;
					float num3 = (float)num * 64f;
					float num4 = (float)num2 * 64f;
					if (!(Math.Sqrt(num3 * num3 + num4 * num4) > (double)TileGrid.WorldRadius))
					{
						VegetationRules.PlaceTrees(num, num2, rules, world, worldSeed, list);
					}
				}
			}
			return new ForestCell(cell, list.ToArray(), 0uL);
		}
	}
	public static class ForestSelection
	{
		public static List<TileKey> Cells(float cx, float cz, float distance)
		{
			List<TileKey> list = new List<TileKey>();
			if (distance <= 0f)
			{
				return list;
			}
			float num = TileGrid.Size(0);
			int num2 = (int)Math.Floor((cx - distance) / num);
			int num3 = (int)Math.Floor((cx + distance) / num);
			int num4 = (int)Math.Floor((cz - distance) / num);
			int num5 = (int)Math.Floor((cz + distance) / num);
			for (int i = num2; i <= num3; i++)
			{
				for (int j = num4; j <= num5; j++)
				{
					TileKey tileKey = new TileKey(0, i, j);
					if (TileGrid.Distance(tileKey, cx, cz) <= distance && TileGrid.Distance(tileKey, 0f, 0f) <= TileGrid.WorldRadius)
					{
						list.Add(tileKey);
					}
				}
			}
			return list.OrderBy((TileKey k) => TileGrid.Distance(k, cx, cz)).ToList();
		}
	}
	public sealed class FrameBudget
	{
		public static readonly FrameBudget Shared = new FrameBudget(2.0);

		public const double SharedMs = 2.0;

		private readonly double _ms;

		private int _frame = int.MinValue;

		private double _spent;

		private long _start;

		public FrameBudget(double ms)
		{
			_ms = ms;
		}

		public double Grant(int frame, double capMs)
		{
			if (frame != _frame)
			{
				_frame = frame;
				_spent = 0.0;
			}
			_start = Stopwatch.GetTimestamp();
			return Math.Max(0.0, Math.Min(capMs, _ms - _spent));
		}

		public void Done()
		{
			_spent += (double)(Stopwatch.GetTimestamp() - _start) * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
	public enum Haze
	{
		Clear,
		Light,
		Standard,
		Thick,
		Custom
	}
	public readonly struct HazeValues : IEquatable<HazeValues>
	{
		public readonly float NearHaze;

		public readonly float NearHazeDistance;

		public readonly float FarLandsHaze;

		public HazeValues(float nearHaze, float nearHazeDistance, float farLandsHaze)
		{
			NearHaze = nearHaze;
			NearHazeDistance = nearHazeDistance;
			FarLandsHaze = farLandsHaze;
		}

		public bool Equals(HazeValues o)
		{
			if (Math.Abs(NearHaze - o.NearHaze) < 0.005f && Math.Abs(NearHazeDistance - o.NearHazeDistance) < 0.5f)
			{
				return Math.Abs(FarLandsHaze - o.FarLandsHaze) < 0.005f;
			}
			return false;
		}

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

		public override int GetHashCode()
		{
			return (int)NearHazeDistance;
		}
	}
	public static class HazePresets
	{
		public static HazeValues Get(Haze h)
		{
			return h switch
			{
				Haze.Clear => new HazeValues(0.4f, 290f, 0.15f), 
				Haze.Light => new HazeValues(0.55f, 290f, 0.35f), 
				Haze.Thick => new HazeValues(0.8f, 250f, 0.85f), 
				_ => new HazeValues(0.6f, 180f, 0.6f), 
			};
		}

		public static Haze Match(HazeValues v)
		{
			Haze[] array = new Haze[4]
			{
				Haze.Clear,
				Haze.Light,
				Haze.Standard,
				Haze.Thick
			};
			foreach (Haze haze in array)
			{
				if (Get(haze).Equals(v))
				{
					return haze;
				}
			}
			return Haze.Custom;
		}

		public static Haze OnLoad(Haze saved, bool inFile, HazeValues current, out bool apply)
		{
			Haze haze = Match(current);
			apply = inFile && saved != Haze.Custom && haze != Haze.Custom && haze != saved;
			if (!apply)
			{
				return haze;
			}
			return saved;
		}
	}
	public sealed class HeightField
	{
		public const float None = -10000f;

		public readonly float OriginX;

		public readonly float OriginZ;

		public readonly float Texel;

		public readonly int Size;

		public readonly float[] Heights;

		public HeightField(float originX, float originZ, float texel, int size, float[] heights = null)
		{
			if (heights != null && heights.Length != size * size)
			{
				throw new ArgumentException($"{heights.Length} heights for a {size}x{size} field");
			}
			OriginX = originX;
			OriginZ = originZ;
			Texel = texel;
			Size = size;
			Heights = heights ?? new float[size * size];
		}

		public static HeightField Build(IEnumerable<TerrainTile> tiles, float cx, float cz, float reach, int size, float[] into = null)
		{
			float texel = 2f * reach / (float)size;
			HeightField heightField = new HeightField(cx - reach, cz - reach, texel, size, into);
			for (int i = 0; i < heightField.Heights.Length; i++)
			{
				heightField.Heights[i] = -10000f;
			}
			foreach (TerrainTile tile in tiles)
			{
				heightField.Paint(tile);
			}
			return heightField;
		}

		private void Paint(TerrainTile tile)
		{
			TileKey key = tile.Key;
			float num = TileGrid.Size(key.Level);
			float num2 = TileGrid.MinX(key);
			float num3 = TileGrid.MinZ(key);
			int num4 = Math.Max(0, (int)Math.Ceiling((num2 - OriginX) / Texel - 0.5f));
			int num5 = Math.Min(Size - 1, (int)Math.Floor((num2 + num - OriginX) / Texel - 0.5f));
			int num6 = Math.Max(0, (int)Math.Ceiling((num3 - OriginZ) / Texel - 0.5f));
			int num7 = Math.Min(Size - 1, (int)Math.Floor((num3 + num - OriginZ) / Texel - 0.5f));
			for (int i = num6; i <= num7; i++)
			{
				float z = OriginZ + ((float)i + 0.5f) * Texel;
				for (int j = num4; j <= num5; j++)
				{
					Heights[i * Size + j] = TileSurface.Height(tile, OriginX + ((float)j + 0.5f) * Texel, z);
				}
			}
		}

		public float Sample(float x, float z)
		{
			float num = Math.Max(0f, Math.Min(Size - 1, (x - OriginX) / Texel - 0.5f));
			float num2 = Math.Max(0f, Math.Min(Size - 1, (z - OriginZ) / Texel - 0.5f));
			int num3 = Math.Min((int)num, Size - 2);
			int num4 = Math.Min((int)num2, Size - 2);
			float num5 = num - (float)num3;
			float num6 = num2 - (float)num4;
			int num7 = num4 * Size + num3;
			float num8 = Heights[num7] + (Heights[num7 + 1] - Heights[num7]) * num5;
			float num9 = Heights[num7 + Size] + (Heights[num7 + Size + 1] - Heights[num7 + Size]) * num5;
			return num8 + (num9 - num8) * num6;
		}
	}
	internal struct AirLayer
	{
		public float Density;

		public float Height;

		public float At(float aboveSea)
		{
			return Density * (float)Math.Exp((0f - Math.Max(-200f, float.IsNaN(aboveSea) ? 0f : aboveSea)) / Math.Max(Height, 1f));
		}

		public float MeanToSea(float aboveSea)
		{
			float num = (float.IsNaN(aboveSea) ? 0f : aboveSea);
			float num2 = Math.Max(Height, 1f);
			if (num < 0.01f * num2)
			{
				return At(num);
			}
			return Density * (float)((double)num2 / (double)num * (1.0 - Math.Exp((double)(0f - num) / (double)num2)));
		}
	}
	internal static class HeightFog
	{
		public const float MistVisibility = 400f;

		public const float MistHeight = 25f;

		public const float NearHazeMaxGameFog = 0.006f;

		public const float FarRate = 0.17f;

		public const float MistMaxDepth = 0.8f;

		public const float SeaMistView = 1500f;

		public const float SeaMistLayerHeight = 150f;

		public const float SeaSampleNear = 60f;

		public const float SeaSampleFar = 150f;

		public const int SeaSamples = 16;

		public const float MoonGlowBelow = -0.15f;

		public static AirLayer SeaMist(float amount, float view = 1500f, float height = 150f)
		{
			return new AirLayer
			{
				Density = Visibility.Koschmieder(view) * Clamp01(amount),
				Height = height
			};
		}

		public static float SeaMistAmount(float waterShare)
		{
			return Smooth(0.4f, 0.85f, waterShare);
		}

		public static double MistDepth(double raw, float cap)
		{
			if (cap > 0f && raw > 0.0)
			{
				return (double)cap * (1.0 - Math.Exp((0.0 - raw) / (double)cap));
			}
			return Math.Max(raw, 0.0);
		}

		public static float FarRateFor(float rain, float snow, float fog, float rate = 0.17f)
		{
			float num = Math.Max(Visibility.DenseShare(fog), Math.Max(Clamp01(rain), Clamp01(snow)));
			rate = Clamp01(rate);
			return rate + (1f - rate) * num;
		}

		public static double NearOpticalDepth(float density, float reach, float distance)
		{
			if (!(density > 0f) || !(reach > 0f) || !(distance > 0f) || float.IsInfinity(density))
			{
				return 0.0;
			}
			if (float.IsInfinity(distance))
			{
				return (double)density * (double)reach;
			}
			return (double)density * (double)reach * (1.0 - Math.Exp((double)(0f - distance) / (double)reach));
		}

		public static double NearEquivalentDensity(float density, float reach)
		{
			if (!(reach > 0f))
			{
				return 0.0;
			}
			return NearOpticalDepth(density, reach, 2f * reach) / (2.0 * (double)reach);
		}

		public static AirLayer Haze(float view, float height)
		{
			return new AirLayer
			{
				Density = Visibility.Koschmieder(view),
				Height = height
			};
		}

		public static AirLayer Mist(float amount)
		{
			return new AirLayer
			{
				Density = Visibility.Koschmieder(400f) * amount,
				Height = 25f
			};
		}

		public static float BuiltInDensity(in AirLayer haze, in AirLayer mist, float near, float nearReach, float aboveSea, in AirLayer sea = default(AirLayer))
		{
			return haze.At(aboveSea) + mist.At(aboveSea) + sea.At(aboveSea) + (float)NearEquivalentDensity(near, nearReach);
		}

		public static float SeaViewDensity(in AirLayer haze, in AirLayer mist, in AirLayer sea, float near, float nearReach, float aboveSea)
		{
			return haze.MeanToSea(aboveSea) + mist.MeanToSea(aboveSea) + sea.MeanToSea(aboveSea) + (float)NearEquivalentDensity(near, nearReach);
		}

		public static void GlowDirection(float sx, float sy, float sz, out float x, out float y, out float z)
		{
			float num = (float)Math.Sqrt(sx * sx + sy * sy + sz * sz);
			if (!(num > 1E-05f))
			{
				x = 0f;
				y = 1f;
				z = 0f;
			}
			else
			{
				float num2 = ((sy / num < -0.15f) ? (-1f) : 1f);
				x = num2 * sx / num;
				y = num2 * sy / num;
				z = num2 * sz / num;
			}
		}

		public static float MistAmount(float dayFraction, float morningMist, float rain, float snow)
		{
			float num = Smooth(0.2f, 0.25f, dayFraction);
			float num2 = 1f - Smooth(0.32f, 0.42f, dayFraction);
			return Clamp01(morningMist) * num * num2 * (1f - Clamp01(rain)) * (1f - Clamp01(snow));
		}

		private static float Smooth(float a, float b, float x)
		{
			float num = Clamp01((x - a) / (b - a));
			return num * num * (3f - 2f * num);
		}

		private static float Clamp01(float v)
		{
			if (!float.IsNaN(v))
			{
				if (!(v < 0f))
				{
					if (!(v > 1f))
					{
						return v;
					}
					return 1f;
				}
				return 0f;
			}
			return 0f;
		}
	}
	public interface IHeightSource
	{
		int Biome(float x, float z);

		float BiomeHeight(int biome, float x, float z, out uint mask);

		uint SectorColor(float x, float z);
	}
	public static class ImpostorMath
	{
		public const int Frames = 8;

		public const int CutMips = 8;

		public const int FirstCutMip = 2;

		public static void Encode(float x, float y, float z, out float u, out float v)
		{
			float num = Math.Abs(x) + Math.Abs(y) + Math.Abs(z);
			x /= num;
			z /= num;
			u = x + z;
			v = x - z;
		}

		public static void Decode(float u, float v, out float x, out float y, out float z)
		{
			float num = (u + v) * 0.5f;
			float num2 = (u - v) * 0.5f;
			x = num;
			z = num2;
			y = 1f - Math.Abs(num) - Math.Abs(num2);
			float num3 = (float)Math.Sqrt(x * x + y * y + z * z);
			x /= num3;
			y /= num3;
			z /= num3;
		}

		public static void Frame(float u, float v, int frames, out int fx, out int fy)
		{
			fx = Math.Min(frames - 1, Math.Max(0, (int)Math.Floor((u * 0.5f + 0.5f) * (float)frames)));
			fy = Math.Min(frames - 1, Math.Max(0, (int)Math.Floor((v * 0.5f + 0.5f) * (float)frames)));
		}

		public static void FrameDirection(int fx, int fy, int frames, out float x, out float y, out float z)
		{
			Decode(((float)fx + 0.5f) / (float)frames * 2f - 1f, ((float)fy + 0.5f) / (float)frames * 2f - 1f, out x, out y, out z);
		}

		public static float[] CoverageCuts(IReadOnlyList<byte[]> alpha)
		{
			float[] array = new float[8];
			for (int i = 0; i < 8; i++)
			{
				array[i] = 0.5f;
			}
			if (alpha.Count == 0 || alpha[0].Length == 0)
			{
				return array;
			}
			double num = 0.0;
			byte[] array2 = alpha[0];
			foreach (byte b in array2)
			{
				num += (double)(int)b;
			}
			double num2 = num / (255.0 * (double)alpha[0].Length);
			int[] array3 = new int[256];
			for (int k = 0; k < alpha.Count && 2 + k < 8; k++)
			{
				byte[] array4 = alpha[k];
				if (array4.Length == 0)
				{
					break;
				}
				Array.Clear(array3, 0, 256);
				array2 = array4;
				foreach (byte b2 in array2)
				{
					array3[b2]++;
				}
				int num3 = 0;
				int num4 = 127;
				double num5 = double.MaxValue;
				for (int num6 = 255; num6 >= 0; num6--)
				{
					double num7 = Math.Abs((double)num3 / (double)array4.Length - num2);
					if (num7 < num5)
					{
						num5 = num7;
						num4 = num6;
					}
					num3 += array3[num6];
				}
				array[2 + k] = Math.Min(0.5f, ((float)num4 + 0.5f) / 255f);
			}
			for (int l = 2 + alpha.Count; l < 8; l++)
			{
				array[l] = array[Math.Max(0, l - 1)];
			}
			return array;
		}
	}
	public sealed class InstancePool
	{
		private readonly SortedDictionary<int, int> _free = new SortedDictionary<int, int>();

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

		public int Capacity { get; }

		public int Used { get; private set; }

		public int HighWater { get; private set; }

		public InstancePool(int capacity)
		{
			Capacity = capacity;
			_free[0] = capacity;
		}

		public int Allocate(int count)
		{
			if (count <= 0)
			{
				throw new ArgumentOutOfRangeException("count");
			}
			int num = -1;
			int num2 = 0;
			foreach (KeyValuePair<int, int> item in _free)
			{
				if (item.Value >= count)
				{
					num = item.Key;
					num2 = item.Value;
					break;
				}
			}
			if (num < 0)
			{
				return -1;
			}
			_free.Remove(num);
			if (num2 > count)
			{
				_free[num + count] = num2 - count;
			}
			_used[num] = count;
			Used += count;
			HighWater = Math.Max(HighWater, num + count);
			return num;
		}

		public int SizeOf(int offset)
		{
			return _used[offset];
		}

		public void Free(int offset)
		{
			int num = _used[offset];
			_used.Remove(offset);
			Used -= num;
			int num2 = offset;
			int num3 = offset + num;
			foreach (KeyValuePair<int, int> item in _free.ToList())
			{
				if (item.Key + item.Value == num2)
				{
					num2 = item.Key;
					_free.Remove(item.Key);
				}
				else if (item.Key == num3)
				{
					num3 = item.Key + item.Value;
					_free.Remove(item.Key);
				}
			}
			_free[num2] = num3 - num2;
			HighWater = ((_used.Count != 0) ? _used.Max((KeyValuePair<int, int> u) => u.Key + u.Value) : 0);
		}
	}
	public interface ITileCodec<T>
	{
		TileKey KeyOf(T value);

		void Encode(T value, Stream to);

		T Decode(byte[] data, int length);
	}
	public interface IVegetationWorld
	{
		int Biome(float x, float z);

		float BiomeHeight(int biome, float x, float z, out float vegetationMask);

		float ForestFactor(float x, float z);

		bool AltBiomeAllows(string vegetationName, string altBiomeParent, float x, float z);

		uint AltBiomes(float x, float z);
	}
	public interface IEasedTile
	{
		TileKey Key { get; }

		TerrainTile Tile { get; }

		bool Morphing { get; }

		float PrepareMorph(TerrainTile other, bool toOther);

		void ApplyMorphStart();

		void StartMorph(float now, float seconds);

		bool StepMorph(float now);
	}
	public interface IEasingStats
	{
		void Pop(int levelA, int levelB, float metres);

		void Started(int folds, int unfolds, double ms);

		void Stepped(double ms);
	}
	public sealed class LevelEasing<T> where T : class, IEasedTile
	{
		public const float FoldSeconds = 1.2f;

		public const float UnfoldSeconds = 1.2f;

		public const double BudgetMs = 0.5;

		private readonly IEasingStats _stats;

		private readonly Stopwatch _clock = new Stopwatch();

		private readonly List<T> _folding = new List<T>();

		private readonly List<T> _unfolding = new List<T>();

		private readonly List<(T View, TerrainTile Other)> _folds = new List<(T, TerrainTile)>();

		private readonly List<(T View, TerrainTile Other)> _unfolds = new List<(T, TerrainTile)>();

		private Task _preparing;

		private int _preloaded;

		private int _next;

		public bool Planned { get; private set; }

		public bool Easing => _folding.Count + _unfolding.Count > 0;

		public bool Unfolding => _unfolding.Count > 0;

		public LevelEasing(IEasingStats stats)
		{
			_stats = stats;
		}

		public void Plan(CoverageSet<T> coverage, bool ease, Func<T, bool> inView)
		{
			Planned = true;
			IReadOnlyDictionary<TileKey, T> old = coverage.Shown;
			if (old.Count == 0 || !ease)
			{
				return;
			}
			foreach (KeyValuePair<TileKey, T> item in coverage.Coming)
			{
				if (!old.ContainsKey(item.Key) && Above(item.Key, (TileKey k) => old.ContainsKey(k), out var found) && inView(item.Value))
				{
					_unfolds.Add((item.Value, old[found].Tile));
				}
			}
			foreach (KeyValuePair<TileKey, T> item2 in old)
			{
				if (!coverage.IsWanted(item2.Key) && Above(item2.Key, coverage.IsWanted, out var found2) && coverage.TryGetComing(found2, out var view) && inView(item2.Value))
				{
					_folds.Add((item2.Value, view.Tile));
				}
			}
			if (_folds.Count + _unfolds.Count == 0)
			{
				return;
			}
			(T View, TerrainTile Other)[] folds = _folds.ToArray();
			(T View, TerrainTile Other)[] unfolds = _unfolds.ToArray();
			IEasingStats stats = _stats;
			_preparing = Task.Run(delegate
			{
				(T, TerrainTile)[] array = folds;
				for (int i = 0; i < array.Length; i++)
				{
					(T, TerrainTile) tuple = array[i];
					float metres = tuple.Item1.PrepareMorph(tuple.Item2, toOther: true);
					stats?.Pop(tuple.Item1.Key.Level, tuple.Item2.Key.Level, metres);
				}
				array = unfolds;
				for (int i = 0; i < array.Length; i++)
				{
					(T, TerrainTile) tuple2 = array[i];
					float metres2 = tuple2.Item1.PrepareMorph(tuple2.Item2, toOther: false);
					stats?.Pop(tuple2.Item1.Key.Level, tuple2.Item2.Key.Level, metres2);
				}
			});
		}

		public bool ReadyToSwap(float now)
		{
			if (Prepared(now) && Preloaded())
			{
				return _folding.Count == 0;
			}
			return false;
		}

		public void Swapped(float now)
		{
			Planned = false;
			_clock.Restart();
			foreach (var unfold in _unfolds)
			{
				T item = unfold.View;
				item.StartMorph(now, 1.2f);
				_unfolding.Add(item);
			}
			int count = _unfolds.Count;
			_unfolds.Clear();
			_stats?.Started(0, count, _clock.Elapsed.TotalMilliseconds);
		}

		public void Step(float now)
		{
			int num = _folding.Count + _unfolding.Count;
			if (num == 0)
			{
				return;
			}
			_clock.Restart();
			for (int i = 0; i < num; i++)
			{
				if (!(_clock.Elapsed.TotalMilliseconds < 0.5))
				{
					break;
				}
				int num2 = _next++ % num;
				((num2 < _folding.Count) ? _folding[num2] : _unfolding[num2 - _folding.Count]).StepMorph(now);
			}
			_folding.RemoveAll((T v) => !v.Morphing);
			_unfolding.RemoveAll((T v) => !v.Morphing);
			_stats?.Stepped(_clock.Elapsed.TotalMilliseconds);
		}

		private bool Prepared(float now)
		{
			if (_preparing == null)
			{
				return true;
			}
			if (!_preparing.IsCompleted)
			{
				return false;
			}
			if (_preparing.IsFaulted)
			{
				throw new Exception("working out the level easings failed", _preparing.Exception);
			}
			_preparing = null;
			_clock.Restart();
			foreach (var fold in _folds)
			{
				T item = fold.View;
				item.StartMorph(now, 1.2f);
				_folding.Add(item);
			}
			int count = _folds.Count;
			_folds.Clear();
			_preloaded = 0;
			_stats?.Started(count, 0, _clock.Elapsed.TotalMilliseconds);
			return false;
		}

		private bool Preloaded()
		{
			if (_preloaded >= _unfolds.Count)
			{
				return true;
			}
			_clock.Restart();
			do
			{
				_unfolds[_preloaded++].View.ApplyMorphStart();
			}
			while (_preloaded < _unfolds.Count && _clock.Elapsed.TotalMilliseconds < 0.5);
			_stats?.Started(0, 0, _clock.Elapsed.TotalMilliseconds);
			return _preloaded >= _unfolds.Count;
		}

		public static bool Above(TileKey key, Func<TileKey, bool> has, out TileKey found)
		{
			for (int i = 1; key.Level + i <= 6; i++)
			{
				found = new TileKey(key.Level + i, key.X >> i, key.Z >> i);
				if (has(found))
				{
					return true;
				}
			}
			found = default(TileKey);
			return false;
		}
	}
	public static class ZoneBits
	{
		public const byte None = 0;

		public const byte Distant = 128;

		public const byte All = byte.MaxValue;

		public static bool Stands(byte zone, bool distant)
		{
			if (zone != byte.MaxValue)
			{
				if (distant)
				{
					return zone != 0;
				}
				return false;
			}
			return true;
		}
	}
	public static class MeshCoverage
	{
		public static void Compose(byte[] mask, bool ignoredMask, int cornerZoneX, int cornerZoneZ, int size, IEnumerable<TileKey> lateCells, byte[] coverage)
		{
			if (ignoredMask)
			{
				Array.Clear(coverage, 0, size * size);
			}
			else
			{
				Array.Copy(mask, coverage, size * size);
			}
			foreach (TileKey lateCell in lateCells)
			{
				for (int i = 0; i < 8; i++)
				{
					for (int j = 0; j < 8; j++)
					{
						int num = lateCell.X * 8 + j - cornerZoneX;
						int num2 = lateCell.Z * 8 + i - cornerZoneZ;
						if (num >= 0 && num2 >= 0 && num < size && num2 < size)
						{
							coverage[num2 * size + num] = byte.MaxValue;
						}
					}
				}
			}
		}

		public static bool ListsDraw(bool withinReach, bool hasMeshes, byte coverage, bool distant)
		{
			if (withinReach && hasMeshes)
			{
				return !ZoneBits.Stands(coverage, distant);
			}
			return false;
		}

		public static bool ImpostorDraws(bool withinReach, bool hasMeshes, byte coverage, byte maskNow, bool distant)
		{
			if (!ListsDraw(withinReach, hasMeshes, coverage, distant))
			{
				return !ZoneBits.Stands(maskNow, distant);
			}
			return false;
		}
	}
	public sealed class MergedGeometry
	{
		public static class Layout
		{
			public const int Stride = 28;

			public const int Position = 0;

			public const int Normal = 12;

			public const int Tangent = 16;

			public const int Color = 20;

			public const int TexCoord0 = 24;
		}

		public int VertexCount;

		public int[] Indices;

		public byte[] Vertices;

		public float MinX;

		public float MinY;

		public float MinZ;

		public float MaxX;

		public float MaxY;

		public float MaxZ;
	}
	public static class MeshMerge
	{
		public static MergedGeometry Merge(IReadOnlyList<(PieceGeometry Part, float[] Matrix, float Mark)> parts, float originX, float originY, float originZ)
		{
			int num = 0;
			int num2 = 0;
			foreach (var part in parts)
			{
				PieceGeometry item = part.Part;
				num += item.VertexCount;
				num2 += item.Indices.Length;
			}
			MergedGeometry mergedGeometry = new MergedGeometry
			{
				VertexCount = num,
				Indices = new int[num2],
				Vertices = new byte[num * 28]
			};
			float num3 = float.MaxValue;
			float num4 = float.MaxValue;
			float num5 = float.MaxValue;
			float num6 = float.MinValue;
			float num7 = float.MinValue;
			float num8 = float.MinValue;
			int num9 = 0;
			int num10 = 0;
			foreach (var part2 in parts)
			{
				PieceGeometry item2 = part2.Part;
				float[] item3 = part2.Matrix;
				float item4 = part2.Mark;
				for (int i = 0; i < item2.VertexCount; i++)
				{
					int num11 = (num9 + i) * 28;
					float num12 = item2.Positions[i * 3];
					float num13 = item2.Positions[i * 3 + 1];
					float num14 = item2.Positions[i * 3 + 2];
					float num15 = item3[0] * num12 + item3[4] * num13 + item3[8] * num14 + item3[12] - originX;
					float num16 = item3[1] * num12 + item3[5] * num13 + item3[9] * num14 + item3[13] - originY;
					float num17 = item3[2] * num12 + item3[6] * num13 + item3[10] * num14 + item3[14] - originZ;
					if (num15 < num3)
					{
						num3 = num15;
					}
					if (num15 > num6)
					{
						num6 = num15;
					}
					if (num16 < num4)
					{
						num4 = num16;
					}
					if (num16 > num7)
					{
						num7 = num16;
					}
					if (num17 < num5)
					{
						num5 = num17;
					}
					if (num17 > num8)
					{
						num8 = num17;
					}
					PutFloat(mergedGeometry.Vertices, num11, num15);
					PutFloat(mergedGeometry.Vertices, num11 + 4, num16);
					PutFloat(mergedGeometry.Vertices, num11 + 8, num17);
					PutTurned(mergedGeometry.Vertices, num11 + 12, item3, item2.Normals[i * 3], item2.Normals[i * 3 + 1], item2.Normals[i * 3 + 2], item4);
					PutTurned(mergedGeometry.Vertices, num11 + 16, item3, item2.Tangents[i * 4], item2.Tangents[i * 4 + 1], item2.Tangents[i * 4 + 2], item2.Tangents[i * 4 + 3]);
					uint num18 = ((item2.Colors == null) ? uint.MaxValue : item2.Colors[i]);
					for (int j = 0; j < 4; j++)
					{
						mergedGeometry.Vertices[num11 + 20 + j] = (byte)(num18 >> 8 * j);
					}
					PutHalf(mergedGeometry.Vertices, num11 + 24, item2.Uvs[i * 2]);
					PutHalf(mergedGeometry.Vertices, num11 + 24 + 2, item2.Uvs[i * 2 + 1]);
				}
				int[] indices = item2.Indices;
				foreach (int num19 in indices)
				{
					mergedGeometry.Indices[num10++] = num9 + num19;
				}
				num9 += item2.VertexCount;
			}
			if (num > 0)
			{
				mergedGeometry.MinX = num3;
				mergedGeometry.MinY = num4;
				mergedGeometry.MinZ = num5;
				mergedGeometry.MaxX = num6;
				mergedGeometry.MaxY = num7;
				mergedGeometry.MaxZ = num8;
			}
			return mergedGeometry;
		}

		public static float[] Place(PlacedPiece piece, float[] local)
		{
			float qx = piece.Qx;
			float qy = piece.Qy;
			float qz = piece.Qz;
			float qw = piece.Qw;
			float num = (float)Math.Sqrt(qx * qx + qy * qy + qz * qz + qw * qw);
			if (num > 1E-12f)
			{
				qx /= num;
				qy /= num;
				qz /= num;
				qw /= num;
			}
			else
			{
				qx = (qy = (qz = 0f));
				qw = 1f;
			}
			float num2 = qx * qx * 2f;
			float num3 = qy * qy * 2f;
			float num4 = qz * qz * 2f;
			float num5 = qx * qy * 2f;
			float num6 = qx * qz * 2f;
			float num7 = qy * qz * 2f;
			float num8 = qw * qx * 2f;
			float num9 = qw * qy * 2f;
			float num10 = qw * qz * 2f;
			float num11 = 1f - (num3 + num4);
			float num12 = num5 - num10;
			float num13 = num6 + num9;
			float num14 = num5 + num10;
			float num15 = 1f - (num2 + num4);
			float num16 = num7 - num8;
			float num17 = num6 - num9;
			float num18 = num7 + num8;
			float num19 = 1f - (num2 + num3);
			float[] array = new float[16];
			for (int i = 0; i < 4; i++)
			{
				float num20 = local[i * 4];
				float num21 = local[i * 4 + 1];
				float num22 = local[i * 4 + 2];
				float num23 = local[i * 4 + 3];
				array[i * 4] = num11 * num20 + num12 * num21 + num13 * num22 + piece.X * num23;
				array[i * 4 + 1] = num14 * num20 + num15 * num21 + num16 * num22 + piece.Y * num23;
				array[i * 4 + 2] = num17 * num20 + num18 * num21 + num19 * num22 + piece.Z * num23;
				array[i * 4 + 3] = num23;
			}
			return array;
		}

		private static void PutFloat(byte[] b, int at, float v)
		{
			int num = BitConverter.SingleToInt32Bits(v);
			for (int i = 0; i < 4; i++)
			{
				b[at + i] = (byte)(num >> 8 * i);
			}
		}

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

		private static void PutTurned(byte[] b, int at, float[] m, float x, float y, float z, float w)
		{
			float num = m[0] * x + m[4] * y + m[8] * z;
			float num2 = m[1] * x + m[5] * y + m[9] * z;
			float num3 = m[2] * x + m[6] * y + m[10] * z;
			float num4 = (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3);
			if (num4 > 1E-12f)
			{
				num /= num4;
				num2 /= num4;
				num3 /= num4;
			}
			b[at] = (byte)SNorm(num);
			b[at + 1] = (byte)SNorm(num2);
			b[at + 2] = (byte)SNorm(num3);
			b[at + 3] = (byte)SNorm(w);
		}

		private static void PutHalf(byte[] b, int at, float v)
		{
			ushort num = ToHalf(v);
			b[at] = (byte)num;
			b[at + 1] = (byte)(num >> 8);
		}

		private static ushort ToHalf(float v)
		{
			int num = BitConverter.SingleToInt32Bits(v);
			int num2 = (num >> 16) & 0x8000;
			int num3 = ((num >> 23) & 0xFF) - 127 + 15;
			int num4 = num & 0x7FFFFF;
			if (((num >> 23) & 0xFF) == 255 && num4 != 0)
			{
				return 0;
			}
			if (num3 >= 31)
			{
				return (ushort)(num2 | 0x7BFF);
			}
			if (num3 <= 0)
			{
				return (ushort)num2;
			}
			int num5 = (num3 << 10) | (num4 >> 13);
			int num6 = num4 & 0x1FFF;
			if (num6 > 4096 || (num6 == 4096 && (num5 & 1) != 0))
			{
				num5++;
			}
			if (num5 > 31743)
			{
				num5 = 31743;
			}
			return (ushort)(num2 | num5);
		}
	}
	internal static class MorningMist
	{
		public static float For(int worldSeed, int day, string environment)
		{
			uint h = 2166136261u;
			h = Mix(h, worldSeed);
			h = Mix(h, day);
			if (environment != null)
			{
				foreach (char c in environment)
				{
					h ^= c;
					h *= 16777619;
				}
			}
			uint s = h;
			for (int j = 0; j < 4; j++)
			{
				Next(ref s);
			}
			if (!(Next(ref s) < 0.4f))
			{
				return 0f;
			}
			return 1f;
		}

		private static uint Mix(uint h, int v)
		{
			for (int i = 0; i < 4; i++)
			{
				h ^= (uint)((v >> 8 * i) & 0xFF);
				h *= 16777619;
			}
			return h;
		}

		private static float Next(ref uint s)
		{
			s += 2654435769u;
			uint num = s;
			int num2 = (int)(num ^ (num >> 16)) * -2048144789;
			int num3 = (num2 ^ (num2 >>> 13)) * -1028477387;
			return (float)((uint)(num3 ^ (num3 >>> 16)) >> 8) / 16777216f;
		}
	}
	public sealed class PieceGeometry
	{
		public readonly float[] Positions;

		public readonly float[] Normals;

		public readonly float[] Tangents;

		public readonly float[] Uvs;

		public readonly uint[] Colors;

		public readonly int[] Indices;

		public readonly float Size;

		public int VertexCount => Positions.Length / 3;

		public PieceGeometry(float[] positions, float[] normals, float[] tangents, float[] uvs, uint[] colors, int[] indices)
		{
			Positions = positions;
			Normals = normals;
			Tangents = tangents;
			Uvs = uvs;
			Colors = colors;
			Indices = indices;
			int num = positions.Length / 3;
			if (positions.Length != num * 3 || normals.Length != num * 3 || tangents.Length != num * 4 || uvs.Length != num * 2 || (colors != null && colors.Length != num))
			{
				throw new ArgumentException("the vertex arrays disagree in length");
			}
			if (indices.Length % 3 != 0)
			{
				throw new ArgumentException("the index count is not a multiple of 3");
			}
			foreach (int num2 in indices)
			{
				if (num2 < 0 || num2 >= num)
				{
					throw new ArgumentException("an index lies outside the vertices");
				}
			}
			if (num == 0)
			{
				return;
			}
			float num3 = float.MaxValue;
			float num4 = float.MaxValue;
			float num5 = float.MaxValue;
			float num6 = float.MinValue;
			float num7 = float.MinValue;
			float num8 = float.MinValue;
			for (int j = 0; j < num; j++)
			{
				float num9 = positions[j * 3];
				float num10 = positions[j * 3 + 1];
				float num11 = positions[j * 3 + 2];
				if (num9 < num3)
				{
					num3 = num9;
				}
				if (num9 > num6)
				{
					num6 = num9;
				}
				if (num10 < num4)
				{
					num4 = num10;
				}
				if (num10 > num7)
				{
					num7 = num10;
				}
				if (num11 < num5)
				{
					num5 = num11;
				}
				if (num11 > num8)
				{
					num8 = num11;
				}
			}
			Size = Math.Max(num6 - num3, Math.Max(num7 - num4, num8 - num5));
		}
	}
	internal interface IFogSwitch
	{
		bool FogEnabled { get; set; }

		object Key { get; }
	}
	internal sealed class PostFogKeeper
	{
		private IFogSwitch _current;

		private bool _was;

		private bool _engaged;

		public bool Off
		{
			get
			{
				if (_engaged && _current != null)
				{
					return !_current.FogEnabled;
				}
				return false;
			}
		}

		public void Engage(IFogSwitch profile)
		{
			if (profile != _current)
			{
				bool flag = _engaged && _current != null && PostFogClaims.Found(_current.Key, _was);
				Release();
				_current = profile;
				if (profile == null)
				{
					return;
				}
				_was = PostFogClaims.Claim(profile.Key, profile.FogEnabled || flag);
				_engaged = true;
			}
			if (_current != null && _current.FogEnabled)
			{
				_current.FogEnabled = false;
			}
		}

		public void Release()
		{
			if (_engaged && _current != null && PostFogClaims.Release(_current.Key, _was, out var found))
			{
				_current.FogEnabled = found;
			}
			_engaged = false;
			_current = null;
		}
	}
	internal static class PostFogClaims
	{
		private const string Slot = "ValheimPostFog.Claims.v1";

		private static Hashtable Table
		{
			get
			{
				if (AppDomain.CurrentDomain.GetData("ValheimPostFog.Claims.v1") is Hashtable result)
				{
					return result;
				}
				Hashtable hashtable = new Hashtable();
				AppDomain.CurrentDomain.SetData("ValheimPostFog.Claims.v1", hashtable);
				return hashtable;
			}
		}

		private static object[] Entry(Hashtable table, object key, out int holders, out bool found)
		{
			holders = 0;
			found = false;
			if (!(table[key] is object[] array) || array.Length != 2 || !(array[0] is int num) || !(array[1] is bool flag))
			{
				return null;
			}
			holders = num;
			found = flag;
			return array;
		}

		public static bool Claim(object key, bool found)
		{
			Hashtable table = Table;
			int holders;
			bool found2;
			object[] array = Entry(table, key, out holders, out found2);
			if (array != null)
			{
				array[0] = holders + 1;
				return found2;
			}
			table[key] = new object[2] { 1, found };
			return found;
		}

		public static bool Found(object key, bool fallback)
		{
			if (Entry(Table, key, out var _, out var found) == null)
			{
				return fallback;
			}
			return found;
		}

		public static bool Release(object key, bool fallback, out bool found)
		{
			Hashtable table = Table;
			int holders;
			object[] array = Entry(table, key, out holders, out found);
			if (array == null)
			{
				found = fallback;
				return true;
			}
			if (holders > 1)
			{
				array[0] = holders - 1;
				return false;
			}
			table.Remove(key);
			return true;
		}
	}
	public enum Quality
	{
		Low,
		Medium,
		High,
		Custom
	}
	public readonly struct QualityValues : IEquatable<QualityValues>
	{
		public readonly float TerrainDistance;

		public readonly float ForestDistance;

		public readonly float TerrainDetail;

		public readonly float TreeModelDistance;

		public readonly int ImpostorResolution;

		public readonly int NearDetail;

		public readonly bool SmoothImpostors;

		public readonly float BuildingDistance;

		public QualityValues(float terrainDistance, float forestDistance, float terrainDetail, int impostorResolution, bool smoothImpostors, float treeModelDistance, int nearDetail = 4, float buildingDistance = 2500f)
		{
			TerrainDistance = terrainDistance;
			ForestDistance = forestDistance;
			TerrainDetail = terrainDetail;
			ImpostorResolution = impostorResolution;
			SmoothImpostors = smoothImpostors;
			TreeModelDistance = treeModelDistance;
			NearDetail = nearDetail;
			BuildingDistance = buildingDistance;
		}

		public QualityValues WithModels(float treeModelDistance)
		{
			return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, treeModelDistance, NearDetail, BuildingDistance);
		}

		public QualityValues WithNear(int nearDetail)
		{
			return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, TreeModelDistance, nearDetail, BuildingDistance);
		}

		public QualityValues WithBuildings(float buildingDistance)
		{
			return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, TreeModelDistance, NearDetail, buildingDistance);
		}

		public bool Equals(QualityValues o)
		{
			if (Math.Abs(TerrainDistance - o.TerrainDistance) < 0.5f && Math.Abs(ForestDistance - o.ForestDistance) < 0.5f && Math.Abs(TerrainDetail - o.TerrainDetail) < 0.005f && ImpostorResolution == o.ImpostorResolution && SmoothImpostors == o.SmoothImpostors && Math.Abs(TreeModelDistance - o.TreeModelDistance) < 0.5f && NearDetail == o.NearDetail)
			{
				return Math.Abs(BuildingDistance - o.BuildingDistance) < 0.5f;
			}
			return false;
		}

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

		public override int GetHashCode()
		{
			return ImpostorResolution;
		}
	}
	public static class QualityPresets
	{
		public const float LowModels = 800f;

		public const float MediumModels = 1200f;

		public const float HighModels = 1200f;

		public const int LowNear = 8;

		public const int MediumNear = 4;

		public const int HighNear = 4;

		public const float LowBuildings = 1500f;

		public const float MediumBuildings = 2500f;

		public const float HighBuildings = 3500f;

		private static readonly (Quality Quality, QualityValues Values)[] Retired = new(Quality, QualityValues)[1] { (Quality.High, new QualityValues(6000f, 3500f, 2f, 128, smoothImpostors: true, 1500f, 2, 3500f)) };

		public static QualityValues Get(Quality q)
		{
			return q switch
			{
				Quality.Low => new QualityValues(2500f, 1500f, 1f, 64, smoothImpostors: false, 800f, 8, 1500f), 
				Quality.High => new QualityValues(6000f, 3500f, 2f, 128, smoothImpostors: true, 1200f, 4, 3500f), 
				_ => new QualityValues(4000f, 2500f, 1.4f, 128, smoothImpostors: true, 1200f), 
			};
		}

		public static Quality Match(QualityValues s)
		{
			Quality[] array = new Quality[3]
			{
				Quality.Low,
				Quality.Medium,
				Quality.High
			};
			foreach (Quality quality in array)
			{
				if (Get(quality).Equals(s))
				{
					return quality;
				}
			}
			return Quality.Custom;
		}

		public static float ModelsForOldFile(Quality saved, float terrainDistance, float forestDistance, float terrainDetail, int impostorResolution, bool smoothImpostors)
		{
			return ForOldFile(saved, (QualityValues p) => new QualityValues(terrainDistance, forestDistance, terrainDetail, impostorResolution, smoothImpostors, p.TreeModelDistance, p.NearDetail, p.BuildingDistance)).TreeModelDistance;
		}

		public static QualityValues ForOldFile(Quality saved, Func<QualityValues, QualityValues> withMissingFrom)
		{
			Quality[] array = new Quality[3]
			{
				Quality.Low,
				Quality.Medium,
				Quality.High
			};
			for (int i = 0; i < array.Length; i++)
			{
				QualityValues qualityValues = Get(array[i]);
				if (qualityValues.Equals(withMissingFrom(qualityValues)))
				{
					return qualityValues;
				}
			}
			return Get(saved);
		}

		public static Quality OnLoad(Quality saved, QualityValues current, out bool apply)
		{
			Quality quality = Match(current);
			if (quality == Quality.Custom && saved != Quality.Custom)
			{
				(Quality, QualityValues)[] retired = Retired;
				for (int i = 0; i < retired.Length; i++)
				{
					(Quality, QualityValues) tuple = retired[i];
					if (tuple.Item2.Equals(current))
					{
						apply = true;
						return saved;
					}
				}
			}
			apply = saved != Quality.Custom && quality != Quality.Custom && quality != saved;
			if (!apply)
			{
				return quality;
			}
			return saved;
		}
	}
	public struct SeenTree
	{
		public TreeInstance Tree;

		public float Ground;
	}
	public static class SeenLand
	{
		public static List<TreeInstance> OnFarLand(IVegetationWorld world, int zoneX, int zoneZ, List<SeenTree> seen, EditMap edits)
		{
			List<TreeInstance> list = new List<TreeInstance>(seen.Count);
			ZoneLand zoneLand = null;
			foreach (SeenTree item in seen)
			{
				TreeInstance tree = item.Tree;
				if (!float.IsNaN(item.Ground))
				{
					tree.Y = tree.Y - item.Ground + (zoneLand ?? (zoneLand = new ZoneLand(world, zoneX, zoneZ))).Height(tree.X, tree.Z) + (edits?.Delta(tree.X, tree.Z) ?? 0f) + FarLandsLand.Delta(tree.X, tree.Z, 0f);
				}
				list.Add(tree);
			}
			return list;
		}

		public static Dictionary<(int X, int Z), List<TreeInstance>> OnFarLand(IVegetationWorld world, Dictionary<(int X, int Z), List<SeenTree>> zones, EditMap edits)
		{
			Dictionary<(int, int), List<TreeInstance>> dictionary = new Dictionary<(int, int), List<TreeInstance>>(zones.Count);
			foreach (KeyValuePair<(int, int), List<SeenTree>> zone in zones)
			{
				dictionary[zone.Key] = OnFarLand(world, zone.Key.Item1, zone.Key.Item2, zone.Value, edits);
			}
			return dictionary;
		}
	}
	public sealed class SettledZones
	{
		private readonly float _seconds;

		private readonly Dictionary<(int X, int Z), float> _since = new Dictionary<(int, int), float>();

		private readonly List<(int X, int Z)> _gone = new List<(int, int)>();

		public int Count => _since.Count;

		public SettledZones(float seconds)
		{
			_seconds = seconds;
		}

		public void Update(HashSet<(int X, int Z)> present, float now)
		{
			_gone.Clear();
			foreach (var key in _since.Keys)
			{
				if (!present.Contains(key))
				{
					_gone.Add(key);
				}
			}
			foreach (var item in _gone)
			{
				_since.Remove(item);
			}
			foreach (var item2 in present)
			{
				if (!_since.ContainsKey(item2))
				{
					_since[item2] = now;
				}
			}
		}

		public bool Settled((int X, int Z) zone, float now)
		{
			if (_since.TryGetValue(zone, out var value))
			{
				return now - value >= _seconds;
			}
			return false;
		}

		public void Clear()
		{
			_since.Clear();
		}
	}
	public static class ShadowMath
	{
		public const float FirstStep = 1.5f;

		public const float Growth = 1.08f;

		public const float MaxDistance = 4000f;

		public const float SunSpread = 0.0093f;

		public const float MinSoftness = 2f;

		public const float Bias = 1f;

		public const float BiasPerMetre = 0.001f;

		public static float ShadowHeight(HeightField field, float x, float z, float sunX, float sunY, float sunZ, out float occluder)
		{
			occluder = 0f;
			float num = (float)Math.Sqrt(sunX * sunX + sunZ * sunZ);
			if (num < 0.0001f)
			{
				return -10000f;
			}
			float num2 = sunY / num;
			float num3 = sunX / num;
			float num4 = sunZ / num;
			float num5 = -10000f;
			float originX = field.OriginX;
			float num6 = field.OriginX + (float)field.Size * field.Texel;
			float originZ = field.OriginZ;
			float num7 = field.OriginZ + (float)field.Size * field.Texel;
			for (float num8 = 1.5f * field.Texel; num8 <= 4000f; num8 = num8 * 1.08f + 0.5f * field.Texel)
			{
				float num9 = x + num3 * num8;
				float num10 = z + num4 * num8;
				if (num9 < originX || num9 > num6 || num10 < originZ || num10 > num7)
				{
					break;
				}
				float num11 = field.Sample(num9, num10);
				if (!(num11 <= -9999f))
				{
					float num12 = num11 - num8 * num2;
					if (num12 > num5)
					{
						num5 = num12;
						occluder = num8;
					}
				}
			}
			return num5;
		}

		public static float Lit(float y, float shadow, float occluder, float eyeDistance)
		{
			float num = Math.Max(2f, occluder * 0.0093f);
			float val = (y - shadow + 1f + 0.001f * eyeDistance + num) / num;
			val = Math.Max(0f, Math.Min(1f, val));
			return val * val * (3f - 2f * val);
		}
	}
	public static class StableHash
	{
		public static int Of(string s)
		{
			int num = 5381;
			int num2 = num;
			for (int i = 0; i < s.Length && s[i] != 0; i += 2)
			{
				num = ((num << 5) + num) ^ s[i];
				if (i == s.Length - 1 || s[i + 1] == '\0')
				{
					break;
				}
				num2 = ((num2 << 5) + num2) ^ s[i + 1];
			}
			return num + num2 * 1566083941;
		}
	}
	public static class TerrainCompData
	{
		private const int Vertices = 4225;

		private const int OldPaintWidth = 64;

		public static ZoneEdit Parse(int zoneX, int zoneZ, byte[] compressed)
		{
			try
			{
				using GZipStream gZipStream = new GZipStream(new MemoryStream(compressed, writable: false), CompressionMode.Decompress);
				using BinaryReader binaryReader = new BinaryReader(gZipStream);
				binaryReader.ReadInt32();
				int operations = binaryReader.ReadInt32();
				binaryReader.ReadSingle();
				binaryReader.ReadSingle();
				binaryReader.ReadSingle();
				binaryReader.ReadSingle();
				int num = binaryReader.ReadInt32();
				if (num != 4225)
				{
					throw new InvalidDataException($"terrain data holds {num} heights, expected {4225}");
				}
				float[] array = new float[4225];
				for (int i = 0; i < num; i++)
				{
					if (binaryReader.ReadBoolean())
					{
						float num2 = binaryReader.ReadSingle();
						float num3 = binaryReader.ReadSingle();
						float num4 = num2 + num3;
						array[i] = (float.IsNaN(num4) ? 0f : Math.Max(-8f, Math.Min(8f, num4)));
					}
				}
				int num5 = binaryReader.ReadInt32();
				if (num5 < 0 || num5 > 4225)
				{
					throw new InvalidDataException($"terrain data holds {num5} paint entries");
				}
				uint[] array2 = new uint[num5];
				bool[] array3 = new bool[num5];
				for (int j = 0; j < num5; j++)
				{
					if (binaryReader.ReadBoolean())
					{
						array3[j] = true;
						uint num6 = Byte(binaryReader.ReadSingle());
						uint num7 = Byte(binaryReader.ReadSingle());
						uint num8 = Byte(binaryReader.ReadSingle());
						uint num9 = Byte(binaryReader.ReadSingle());
						array2[j] = num6 | (num7 << 8) | (num8 << 16) | (num9 << 24);
					}
				}
				gZipStream.CopyTo(Stream.Null);
				switch (num5)
				{
				case 4096:
					Respread(ref array2, ref array3);
					break;
				default:
					Array.Resize(ref array2, 4225);
					Array.Resize(ref array3, 4225);
					break;
				case 4225:
					break;
				}
				return new ZoneEdit(zoneX, zoneZ, operations, array, array2, array3);
			}
			catch (InvalidDataException)
			{
				throw;
			}
			catch (Exception ex2) when (ex2 is IOException || ex2 is ArgumentException || ex2 is OverflowException)
			{
				throw new InvalidDataException("terrain data is damaged: " + ex2.Message, ex2);
			}
		}

		public static ZoneEdit Pick(IEnumerable<ZoneEdit> compilers)
		{
			ZoneEdit zoneEdit = null;
			foreach (ZoneEdit compiler in compilers)
			{
				if (zoneEdit == null || compiler.Operations > zoneEdit.Operations)
				{
					zoneEdit = compiler;
				}
			}
			return zoneEdit;
		}

		private static uint Byte(float c)
		{
			if (!float.IsNaN(c))
			{
				return (uint)Math.Max(0, Math.Min(255, (int)Math.Round(Math.Max(-1f, Math.Min(2f, c)) * 255f)));
			}
			return 0u;
		}

		private static void Respread(ref uint[] paint, ref bool[] painted)
		{
			uint[] array = new uint[4225];
			bool[] array2 = new bool[4225];
			int num = 65;
			for (int i = 0; i < 4225; i++)
			{
				int num2 = i / num;
				int num3 = (i + 1) / num;
				int num4 = i - num2;
				if (num2 == 64)
				{
					num4 -= 64;
				}
				if (i > 0 && (i - num2) % 64 == 0 && (i + 1 - num3) % 64 == 0)
				{
					num4--;
				}
				if (num4 >= 0 && num4 < paint.Length)
				{
					array[i] = paint[num4];
					array2[i] = painted[num4];
				}
			}
			paint = array;
			painted = array2;
		}
	}
	public sealed class TerrainTile
	{
		public readonly TileKey Key;

		public readonly float[] Heights;

		public readonly float[] Normals;

		public readonly uint[] Colors;

		public readonly uint[] Mask;

		public readonly uint Edits;

		public int ColourEpoch { get; private set; }

		public TerrainTile(TileKey key, float[] heights, float[] normals, uint[] colors, uint[] mask, uint edits = 0u)
		{
			int num = 4225;
			if (heights == null || heights.Length != num || normals?.Length != num * 3 || colors == null || colors.Length != num || mask == null || mask.Length != num)
			{
				throw new ArgumentException($"tile {key}: arrays do not match a {65}x{65} grid");
			}
			Key = key;
			Heights = heights;
			Normals = normals;
			Colors = colors;
			Mask = mask;
			Edits = edits;
		}

		public TerrainTile WithColours(uint[] colors, int epoch)
		{
			return new TerrainTile(Key, Heights, Normals, colors, Mask, Edits)
			{
				ColourEpoch = epoch
			};
		}
	}
	public sealed class TileBuilder<T> : IDisposable where T : class
	{
		private readonly Func<TileStore<T>> _openStore;

		private TileStore<T> _store;

		private readonly Func<TileKey, T> _generate;

		private readonly Func<T, bool> _fresh;

		private readonly Func<T, T> _deliver;

		private Dictionary<TileKey, T> _updated;

		private bool _readOnly;

		private readonly object _lock = new object();

		private List<TileKey> _pending = new List<TileKey>();

		private readonly ConcurrentQueue<T> _ready = new ConcurrentQueue<T>();

		private readonly ConcurrentQueue<(TileKey Key, Func<T, T> Change)> _updates = new ConcurrentQueue<(TileKey, Func<T, T>)>();

		private readonly ConcurrentQueue<T> _redeliveries = new ConcurrentQueue<T>();

		private readonly Thread _thread;

		private volatile bool _stop;

		private volatile Exception _failure;

		private volatile bool _cacheReady;

		private volatile bool _cacheMissing;

		private volatile string _cacheNote;

		private volatile bool _cacheNoteWarns;

		private int _cacheNews;

		private int _cacheNewsTaken;

		public int Generated;

		public int Loaded;

		public const int OpenRetryMs = 3000;

		public Exception Failure => _failure;

		public bool CacheReady => _cacheReady;

		public bool CacheMissing => _cacheMissing;

		public string CacheNote => _cacheNote;

		public bool CacheNoteWarns => _cacheNoteWarns;

		public TileBuilder(Func<TileStore<T>> openStore, Func<TileKey, T> generate, Func<T, bool> fresh = null, bool generateMakesUpdates = false, Func<T, T> deliver = null)
		{
			_openStore = openStore;
			_generate = generate;
			_fresh = fresh;
			_deliver = deliver;
			if (!generateMakesUpdates)
			{
				_updated = new Dictionary<TileKey, T>();
			}
			_thread = new Thread(Run)
			{
				IsBackground = true,
				Priority = ThreadPriority.BelowNormal,
				Name = "FarLands builder"
			};
			_thread.Start();
		}

		public void Want(IEnumerable<TileKey> keys, float cx, float cz)
		{
			List<TileKey> pending = keys.OrderBy((TileKey k) => TileGrid.Distance(k, cx, cz)).ToList();
			lock (_lock)
			{
				_pending = pending;
				Monitor.PulseAll(_lock);
			}
		}

		public void Update(TileKey key, Func<T, T> change)
		{
			_updates.Enqueue((key, change));
			lock (_lock)
			{
				Monitor.PulseAll(_lock);
			}
		}

		public void Redeliver(T value)
		{
			_redeliveries.Enqueue(value);
			lock (_lock)
			{
				Monitor.PulseAll(_lock);
			}
		}

		public bool TryTake(out T value)
		{
			return _ready.TryDequeue(out value);
		}

		private void Deliver(T value)
		{
			_ready.Enqueue((_deliver != null) ? _deliver(value) : value);
		}

		public bool TakeCacheNews(out string note, out bool missing, out bool warns)
		{
			note = _cacheNote;
			missing = _cacheMissing;
			warns = _cacheNoteWarns | missing;
			int num = Volatile.Read(in _cacheNews);
			if (num == _cacheNewsTaken)
			{
				return false;
			}
			_cacheNewsTaken = num;
			return true;
		}

		private void Run()
		{
			try
			{
				OpenStore();
				while (!_stop)
				{
					if (_