plugins/Tarn.dll

Decompiled 17 hours ago
using System;
using System.Collections;
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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Raven Iron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.2.0")]
[assembly: AssemblyInformationalVersion("0.4.2+13ac5df1643e3e2ff05c19195fc7003305c84d45")]
[assembly: AssemblyProduct("Tarn")]
[assembly: AssemblyTitle("Tarn")]
[assembly: AssemblyVersion("0.4.2.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 RavenIron.Tarn
{
	internal static class ClearBeds
	{
		private sealed class Job
		{
			public string Target;

			public bool Confirm;

			public long Requester;

			public List<Plugin.LakeRt> Lakes;

			public int Shaped;

			public int Scanned;

			public readonly HashSet<ZDO> Found = new HashSet<ZDO>();

			public readonly Dictionary<string, int> PerLake = new Dictionary<string, int>();

			public readonly List<string> Dry = new List<string>();

			public Dictionary<int, string> Vegetation;

			public readonly Stopwatch Started = Stopwatch.StartNew();
		}

		private const string RequestRpc = "com.raveniron.tarn.clearbeds";

		private const string ReplyRpc = "com.raveniron.tarn.clearbedsreply";

		private static Job _job;

		private static ZRoutedRpc _registeredOn;

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

		private static readonly SimulationDistance JustTheZone = new SimulationDistance(0, 0, true);

		internal static void Tick(float budgetMs)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredOn)
			{
				try
				{
					instance.Register<string, bool>("com.raveniron.tarn.clearbeds", (Action<long, string, bool>)RPC_Request);
					instance.Register<string>("com.raveniron.tarn.clearbedsreply", (Action<long, string>)RPC_Reply);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("clearbeds: register failed: " + ex.Message));
				}
				_registeredOn = instance;
				_job = null;
			}
			if (_job == null)
			{
				return;
			}
			try
			{
				Advance(budgetMs);
			}
			catch (Exception ex2)
			{
				Job job = _job;
				_job = null;
				Reply(job.Requester, "tarn clearbeds " + job.Target + ": failed (" + ex2.Message + "); nothing more was removed");
				Plugin.Log.LogError((object)$"clearbeds: {ex2}");
			}
		}

		internal static string Command(string target, bool confirm)
		{
			if (string.IsNullOrEmpty(target))
			{
				return "tarn clearbeds <lake|all> - count the trees, rocks, bushes and pickables standing on lake beds in explored ground\ntarn clearbeds <lake|all> confirm - remove them (admins only on a server; cannot be undone)";
			}
			if (!LakeSync.IsAuthority)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.GetServerPeer() == null)
				{
					return "tarn clearbeds: not connected";
				}
				instance.InvokeRoutedRPC("com.raveniron.tarn.clearbeds", new object[2] { target, confirm });
				return "tarn clearbeds: sent to the server (admins only); the answer arrives here";
			}
			return Start(target, confirm, 0L);
		}

		private static string Start(string target, bool confirm, long requester)
		{
			if (_job != null)
			{
				return "tarn clearbeds: busy with '" + _job.Target + "', try again in a moment";
			}
			if (WorldGenerator.instance == null || ZDOMan.instance == null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return "tarn clearbeds: no world loaded";
			}
			List<Plugin.LakeRt> list = (target.Equals("all", StringComparison.OrdinalIgnoreCase) ? Plugin.LakeList.ToList() : Plugin.LakeList.Where((Plugin.LakeRt r) => r.L.Name.Equals(target, StringComparison.OrdinalIgnoreCase)).ToList());
			if (list.Count == 0)
			{
				return "tarn clearbeds: no lake named '" + target + "' (tarn list shows the nearest)";
			}
			_job = new Job
			{
				Target = target,
				Confirm = confirm,
				Requester = requester,
				Lakes = list
			};
			int num = list.Count((Plugin.LakeRt r) => r.Shape == null || !r.Shape.Done);
			Plugin.Log.LogInfo((object)string.Format("clearbeds: {0} on {1} lake bed(s) for {2}", confirm ? "removing" : "counting", list.Count, (requester == 0L) ? "the local console" : ("peer " + requester)));
			return string.Format("tarn clearbeds {0}: {1} on {2} lake bed(s)", target, confirm ? "removing" : "counting", list.Count) + ((num > 0) ? $", shaping {num} first" : "") + "; the result follows in a moment";
		}

		private static void Advance(float budgetMs)
		{
			Job job = _job;
			WorldGenerator instance = WorldGenerator.instance;
			if (instance == null || ZDOMan.instance == null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				_job = null;
				Reply(job.Requester, "tarn clearbeds " + job.Target + ": the world went away; nothing was removed");
				return;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			while (job.Shaped < job.Lakes.Count)
			{
				Plugin.LakeRt lakeRt = job.Lakes[job.Shaped];
				LakeShape lakeShape = lakeRt.Shape ?? (lakeRt.Shape = new LakeShape(lakeRt.L));
				while (!lakeShape.Sampled)
				{
					if (stopwatch.Elapsed.TotalMilliseconds >= (double)budgetMs)
					{
						return;
					}
					lakeShape.SampleRow(instance);
				}
				if (!lakeShape.Done)
				{
					lakeShape.Flood();
				}
				job.Shaped++;
			}
			if (job.Vegetation == null)
			{
				job.Vegetation = VegetationPrefabs();
			}
			if (job.Scanned < job.Lakes.Count)
			{
				Scan(job, job.Lakes[job.Scanned++]);
				return;
			}
			_job = null;
			int num = 0;
			if (job.Confirm)
			{
				long sessionID = ZDOMan.GetSessionID();
				foreach (ZDO item in job.Found)
				{
					if (item.IsValid())
					{
						if (!item.IsOwner())
						{
							item.SetOwner(sessionID);
						}
						ZDOMan.instance.DestroyZDO(item);
						num++;
					}
				}
			}
			Reply(job.Requester, Report(job, num));
		}

		private static Dictionary<int, string> VegetationPrefabs()
		{
			Dictionary<int, string> dictionary = new Dictionary<int, string>();
			foreach (ZoneVegetation item in ZoneSystem.instance.m_vegetation)
			{
				if (item != null && (Object)(object)item.m_prefab != (Object)null)
				{
					dictionary[StringExtensionMethods.GetStableHashCode(((Object)item.m_prefab).name)] = ((Object)item.m_prefab).name;
				}
			}
			return dictionary;
		}

		private static void Scan(Job j, Plugin.LakeRt rt)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			LakeShape shape = rt.Shape;
			if (shape == null || shape.Failed != null)
			{
				j.Dry.Add(rt.L.Name);
				return;
			}
			float pad = LakeBeds.Pad;
			Vector2s zone = ZoneSystem.GetZone(new Vector3(shape.X0 - pad, 0f, shape.Z0 - pad));
			Vector2s zone2 = ZoneSystem.GetZone(new Vector3(shape.Cx(shape.Nx - 1) + pad, 0f, shape.Cz(shape.Nz - 1) + pad));
			int num = 0;
			for (int i = zone.y; i <= zone2.y; i++)
			{
				for (int k = zone.x; k <= zone2.x; k++)
				{
					_sector.Clear();
					ZDOMan.instance.FindSectorObjects(new Vector2s(k, i), JustTheZone, _sector, (List<ZDO>)null);
					foreach (ZDO item in _sector)
					{
						if (item != null && j.Vegetation.ContainsKey(item.GetPrefab()) && item.GetLong(ZDOVars.s_creator, 0L) == 0L)
						{
							Vector3 position = item.GetPosition();
							if (shape.InBed(position.x, position.z, pad) && j.Found.Add(item))
							{
								num++;
							}
						}
					}
				}
			}
			if (num > 0)
			{
				j.PerLake[rt.L.Name] = num;
			}
		}

		private static string Report(Job j, int removed)
		{
			string value;
			List<(string, int)> list = (from z in j.Found
				group z by z.GetPrefab() into g
				select (name: j.Vegetation.TryGetValue(g.Key, out value) ? value : g.Key.ToString(), count: g.Count()) into k
				orderby k.count descending
				select k).ToList();
			StringBuilder stringBuilder = new StringBuilder("tarn clearbeds " + j.Target + ": ");
			if (j.Found.Count == 0)
			{
				stringBuilder.Append($"nothing stands on {j.Lakes.Count - j.Dry.Count} lake bed(s) in explored ground");
			}
			else
			{
				stringBuilder.Append(j.Confirm ? $"removed {removed} object(s)" : $"{j.Found.Count} object(s) stand on the bed(s)");
				stringBuilder.Append(": ").Append(string.Join(", ", from k in list.Take(8)
					select $"{k.name} x{k.count}"));
				if (list.Count > 8)
				{
					stringBuilder.Append($", and {list.Count - 8} more kind(s)");
				}
				if (j.PerLake.Count > 1)
				{
					stringBuilder.Append("\n  by lake: ").Append(string.Join(", ", from kv in j.PerLake.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).Take(10)
						select $"{kv.Key} {kv.Value}")).Append((j.PerLake.Count > 10) ? $", and {j.PerLake.Count - 10} more" : "");
				}
				if (!j.Confirm)
				{
					stringBuilder.Append("\n  run `tarn clearbeds " + j.Target + " confirm` to remove them (cannot be undone)");
				}
			}
			if (j.Dry.Count > 0)
			{
				stringBuilder.Append(string.Format("\n  skipped {0} lake(s) whose point is dry: {1}", j.Dry.Count, string.Join(", ", j.Dry.Take(5))));
			}
			stringBuilder.Append($" ({j.Started.Elapsed.TotalSeconds:0.0} s)");
			return stringBuilder.ToString();
		}

		private static void Reply(long requester, string text)
		{
			Plugin.Log.LogInfo((object)text.Replace("\n", " |"));
			if (requester == 0L)
			{
				if ((Object)(object)Console.instance != (Object)null)
				{
					Console.instance.Print(text);
				}
				return;
			}
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(requester, "com.raveniron.tarn.clearbedsreply", new object[1] { text });
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"clearbeds: answering peer {requester} failed: {ex.Message}");
			}
		}

		private static void RPC_Request(long sender, string target, bool confirm)
		{
			if (!LakeSync.IsAuthority)
			{
				return;
			}
			string text;
			try
			{
				ZNetPeer peer = ZNet.instance.GetPeer(sender);
				if (peer == null || peer.m_socket == null || !ZNet.instance.IsAdmin(peer.m_socket.GetHostName()))
				{
					text = "tarn clearbeds: you are not on the server's adminlist";
					Plugin.Log.LogWarning((object)string.Format("clearbeds: refused peer {0} ({1}): not an admin", sender, (peer?.m_socket != null) ? peer.m_socket.GetHostName() : "unknown"));
				}
				else
				{
					text = Start(target ?? "", confirm, sender);
				}
			}
			catch (Exception ex)
			{
				text = "tarn clearbeds: the server failed (" + ex.Message + ")";
				Plugin.Log.LogWarning((object)$"clearbeds: {ex}");
			}
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "com.raveniron.tarn.clearbedsreply", new object[1] { text });
			}
			catch
			{
			}
		}

		private static void RPC_Reply(long sender, string text)
		{
			if (!LakeSync.IsAuthority)
			{
				Plugin.Log.LogInfo((object)text.Replace("\n", " |"));
				if ((Object)(object)Console.instance != (Object)null)
				{
					Console.instance.Print(text);
				}
			}
		}
	}
	internal static class Clearing
	{
		private static string _for;

		private static byte[] _bits;

		private static int _n;

		private static float _extent;

		private static bool _fromServer;

		private static ConstructorInfo _ctor;

		private static bool _ctorMissingLogged;

		internal static bool WaitingForServer
		{
			get
			{
				if (!LakeSync.IsAuthority && !_fromServer)
				{
					return !ClearSync.GaveUp;
				}
				return false;
			}
		}

		internal static string FileFor(string world)
		{
			return Path.Combine(Paths.ConfigPath, "Tarn", world + ".clear");
		}

		internal static void ResetClient()
		{
			_fromServer = false;
			_for = null;
			_bits = null;
		}

		internal static void SetFromServer(string world, int n, float extent, byte[] bits)
		{
			_fromServer = true;
			_for = world;
			_n = n;
			_extent = extent;
			_bits = bits;
			if (bits == null)
			{
				Plugin.Log.LogInfo((object)("clear sync: the server has no clearing mask for '" + world + "'"));
				return;
			}
			Plugin.Log.LogInfo((object)$"clear sync: the server's clearing mask for '{world}': {n} x {n} over +-{extent:0} m, {Km2():0.00} km2 kept free of vegetation");
		}

		internal static bool TryGetRaw(out int n, out float extent, out byte[] bits)
		{
			n = 0;
			extent = 0f;
			bits = null;
			if (!LakeSync.IsAuthority || !Ensure())
			{
				return false;
			}
			n = _n;
			extent = _extent;
			bits = _bits;
			return true;
		}

		private static double Km2()
		{
			long num = 0L;
			byte[] bits = _bits;
			foreach (byte b in bits)
			{
				for (int j = 0; j < 8; j++)
				{
					num += (b >> j) & 1;
				}
			}
			float num2 = 2f * _extent / (float)_n;
			return (double)((float)num * num2 * num2) / 1000000.0;
		}

		internal static bool Ensure()
		{
			if (!LakeSync.IsAuthority)
			{
				if (_fromServer)
				{
					return _bits != null;
				}
				return false;
			}
			string text = Plugin.WorldName();
			if (_for == text)
			{
				return _bits != null;
			}
			_for = text;
			_bits = null;
			string text2 = FileFor(text);
			if (!File.Exists(text2))
			{
				Plugin.Log.LogInfo((object)("no clearing mask for world '" + text + "' (" + text2 + "): vegetation is placed as vanilla places it"));
				return false;
			}
			try
			{
				using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(text2)))
				{
					if (new string(binaryReader.ReadChars(8)) != "TARNCLR1")
					{
						throw new InvalidDataException("not a TARNCLR1 file");
					}
					_n = binaryReader.ReadInt32();
					_extent = binaryReader.ReadSingle();
					int num = (int)(((long)_n * (long)_n + 7) / 8);
					_bits = binaryReader.ReadBytes(num);
					if (_n <= 0 || _extent <= 0f || _bits.Length != num)
					{
						throw new InvalidDataException($"size {_n}, extent {_extent}, {_bits.Length} of {num} bytes");
					}
				}
				Plugin.Log.LogInfo((object)$"clearing mask for world '{text}': {_n} x {_n} over +-{_extent:0} m, {Km2():0.00} km2 kept free of vegetation");
			}
			catch (Exception ex)
			{
				_bits = null;
				Plugin.Log.LogError((object)("clearing mask " + text2 + " could not be read (" + ex.Message + "): vegetation is placed as vanilla places it"));
			}
			return _bits != null;
		}

		internal static object NewArea(Vector3 p, float r)
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (_ctor == null)
			{
				Type type = AccessTools.Inner(typeof(ZoneSystem), "ClearArea");
				_ctor = ((type == null) ? null : AccessTools.Constructor(type, new Type[2]
				{
					typeof(Vector3),
					typeof(float)
				}, false));
				if (_ctor == null)
				{
					if (!_ctorMissingLogged)
					{
						_ctorMissingLogged = true;
						Plugin.Log.LogError((object)"ZoneSystem.ClearArea(Vector3, float) not found: Valheim's API moved, nothing is cleared");
					}
					return null;
				}
			}
			return _ctor.Invoke(new object[2] { p, r });
		}

		private static bool At(int r, int c)
		{
			long num = (long)r * (long)_n + c;
			return (_bits[num >> 3] & (128 >> (int)(num & 7))) != 0;
		}

		internal static List<object> AreasFor(Vector3 centre)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			List<object> list = new List<object>();
			float num = 2f * _extent / (float)_n;
			float num2 = 32f + num;
			int num3 = Mathf.Max(0, Mathf.FloorToInt((centre.x - num2 + _extent) / num));
			int num4 = Mathf.Min(_n - 1, Mathf.FloorToInt((centre.x + num2 + _extent) / num));
			int num5 = Mathf.Max(0, Mathf.FloorToInt((_extent - (centre.z + num2)) / num));
			int num6 = Mathf.Min(_n - 1, Mathf.FloorToInt((_extent - (centre.z - num2)) / num));
			float r = num * 0.5f + 0.25f;
			for (int i = num5; i <= num6; i++)
			{
				for (int j = num3; j <= num4; j++)
				{
					if (At(i, j))
					{
						object obj = NewArea(new Vector3(((float)j + 0.5f) * num - _extent, 0f, _extent - ((float)i + 0.5f) * num), r);
						if (obj == null)
						{
							return list;
						}
						list.Add(obj);
					}
				}
			}
			return list;
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "PlaceVegetation")]
	internal static class Patch_PlaceVegetation
	{
		private static bool _warned;

		private static bool _warnedWaiting;

		private static bool _provedMask;

		private static bool _provedBeds;

		[HarmonyPriority(200)]
		private static void Prefix(Vector3 zoneCenterPos, object clearAreas, bool __runOriginal, ref List<object> __state)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			__state = null;
			if (!__runOriginal || !(clearAreas is IList list))
			{
				return;
			}
			try
			{
				if ((Clearing.WaitingForServer || (!LakeSync.IsAuthority && !LakeSync.Answered && !LakeSync.GaveUp)) && !_warnedWaiting)
				{
					_warnedWaiting = true;
					Plugin.Log.LogWarning((object)$"clearing: the zone at ({zoneCenterPos.x:0}, {zoneCenterPos.z:0}) was generated before the server's clearing mask and lakes arrived - its vegetation is vanilla's (only the first such zone is named)");
				}
				List<object> list2 = (Clearing.Ensure() ? Clearing.AreasFor(zoneCenterPos) : new List<object>());
				int count = list2.Count;
				string firstLake = null;
				int num = (LakeSync.LakeBedsOn ? LakeBeds.AddFor(zoneCenterPos, list2, out firstLake) : 0);
				if (list2.Count == 0)
				{
					return;
				}
				__state = list2;
				foreach (object item in list2)
				{
					list.Add(item);
				}
				if (count > 0 && !_provedMask)
				{
					_provedMask = true;
					Plugin.Log.LogInfo((object)$"clearing: first zone with marked ground at ({zoneCenterPos.x:0}, {zoneCenterPos.z:0}): {count} clear area(s) added");
				}
				if (num > 0 && !_provedBeds)
				{
					_provedBeds = true;
					Plugin.Log.LogInfo((object)$"lake beds: first zone with a lake bed at ({zoneCenterPos.x:0}, {zoneCenterPos.z:0}), lake '{firstLake}': {num} clear area(s) added");
				}
			}
			catch (Exception arg)
			{
				__state = null;
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogError((object)$"clearing: {arg}");
				}
			}
		}

		private static void Finalizer(object clearAreas, List<object> __state)
		{
			if (__state == null || __state.Count == 0 || !(clearAreas is IList list))
			{
				return;
			}
			HashSet<object> hashSet = new HashSet<object>(__state);
			for (int num = list.Count - 1; num >= 0; num--)
			{
				if (hashSet.Contains(list[num]))
				{
					list.RemoveAt(num);
				}
			}
		}
	}
	internal static class ClearSync
	{
		internal const string RequestRpc = "com.raveniron.tarn.clearrequest";

		internal const string ChunkRpc = "com.raveniron.tarn.clearchunk";

		private const int Protocol = 1;

		private const int ChunkBytes = 49152;

		private const float AskEvery = 4f;

		private const int GiveUpAfter = 15;

		private static ZRoutedRpc _registeredOn;

		private static float _sinceAsk = 4f;

		private static int _asks;

		private static bool _answered;

		private static bool _gaveUp;

		private static byte[][] _parts;

		private static int _partsGot;

		private static string _partsKey;

		internal static bool GaveUp => _gaveUp;

		internal static void Tick(float dt)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				return;
			}
			if (instance != _registeredOn)
			{
				try
				{
					instance.Register("com.raveniron.tarn.clearrequest", (Action<long>)RPC_Request);
					instance.Register<ZPackage>("com.raveniron.tarn.clearchunk", (Action<long, ZPackage>)RPC_Chunk);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("clear sync: register failed: " + ex.Message));
				}
				_registeredOn = instance;
				_answered = (_gaveUp = false);
				_asks = 0;
				_sinceAsk = 4f;
				_parts = null;
				_partsGot = 0;
				_partsKey = null;
				Clearing.ResetClient();
			}
			if (LakeSync.IsAuthority || _answered || _gaveUp || ZNet.instance.GetServerPeer() == null)
			{
				return;
			}
			_sinceAsk += dt;
			if (_sinceAsk < 4f)
			{
				return;
			}
			_sinceAsk = 0f;
			if (++_asks > 15)
			{
				_gaveUp = true;
				Plugin.Log.LogWarning((object)"clear sync: the server never sent its clearing mask - is it running Tarn 0.3.0 or newer? Zones this client generates keep vanilla's vegetation.");
				return;
			}
			try
			{
				instance.InvokeRoutedRPC("com.raveniron.tarn.clearrequest", Array.Empty<object>());
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("clear sync: asking the server failed: " + ex2.Message));
			}
		}

		private static ZPackage Chunk(string world, int n, float extent, int rawLen, int packedLen, int index, int count, byte[] data)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write("0.4.2");
			val.Write(world);
			val.Write(n);
			val.Write(extent);
			val.Write(rawLen);
			val.Write(packedLen);
			val.Write(index);
			val.Write(count);
			val.Write(data ?? new byte[0]);
			return val;
		}

		private static void RPC_Request(long sender)
		{
			if (!LakeSync.IsAuthority)
			{
				return;
			}
			try
			{
				string world = Plugin.WorldName();
				if (!Clearing.TryGetRaw(out var n, out var extent, out var bits))
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(sender, "com.raveniron.tarn.clearchunk", new object[1] { Chunk(world, 0, 0f, 0, 0, 0, 0, null) });
					Plugin.Log.LogInfo((object)$"clear sync: told peer {sender} this world has no clearing mask");
					return;
				}
				byte[] array;
				using (MemoryStream memoryStream = new MemoryStream())
				{
					using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
					{
						deflateStream.Write(bits, 0, bits.Length);
					}
					array = memoryStream.ToArray();
				}
				int num = (array.Length + 49152 - 1) / 49152;
				for (int i = 0; i < num; i++)
				{
					int num2 = Math.Min(49152, array.Length - i * 49152);
					byte[] array2 = new byte[num2];
					Buffer.BlockCopy(array, i * 49152, array2, 0, num2);
					ZRoutedRpc.instance.InvokeRoutedRPC(sender, "com.raveniron.tarn.clearchunk", new object[1] { Chunk(world, n, extent, bits.Length, array.Length, i, num, array2) });
				}
				Plugin.Log.LogInfo((object)$"clear sync: sent the clearing mask to peer {sender} ({array.Length / 1024} KB deflated from {bits.Length / 1024} KB, {num} chunk(s))");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"clear sync: answering peer {sender} failed: {ex.Message}");
			}
		}

		private static void RPC_Chunk(long sender, ZPackage pkg)
		{
			if (LakeSync.IsAuthority || _answered)
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num != 1)
				{
					Plugin.Log.LogWarning((object)$"clear sync: the server speaks clearing protocol {num}, this Tarn {1} - update Tarn to match. No clearing.");
					_gaveUp = true;
					return;
				}
				pkg.ReadString();
				string text = pkg.ReadString();
				int num2 = pkg.ReadInt();
				float extent = pkg.ReadSingle();
				int num3 = pkg.ReadInt();
				int num4 = pkg.ReadInt();
				int num5 = pkg.ReadInt();
				int num6 = pkg.ReadInt();
				byte[] array = pkg.ReadByteArray();
				if (num6 == 0)
				{
					_answered = true;
					Clearing.SetFromServer(text, 0, 0f, null);
					return;
				}
				string text2 = $"{text}|{num2}|{num3}|{num4}|{num6}";
				if (text2 != _partsKey)
				{
					_partsKey = text2;
					_parts = new byte[num6][];
					_partsGot = 0;
				}
				if (num5 < 0 || num5 >= num6 || _parts[num5] != null)
				{
					return;
				}
				_parts[num5] = array;
				if (++_partsGot < num6)
				{
					return;
				}
				byte[] array2 = new byte[num4];
				int num7 = 0;
				byte[][] parts = _parts;
				foreach (byte[] array3 in parts)
				{
					Buffer.BlockCopy(array3, 0, array2, num7, array3.Length);
					num7 += array3.Length;
				}
				if (num7 != num4)
				{
					throw new InvalidDataException($"{num7} of {num4} bytes arrived");
				}
				byte[] array4 = new byte[num3];
				using (DeflateStream deflateStream = new DeflateStream(new MemoryStream(array2), CompressionMode.Decompress))
				{
					int j;
					int num8;
					for (j = 0; j < num3; j += num8)
					{
						if ((num8 = deflateStream.Read(array4, j, num3 - j)) <= 0)
						{
							break;
						}
					}
					if (j != num3)
					{
						throw new InvalidDataException($"{j} of {num3} bytes after inflating");
					}
				}
				if ((long)num2 * (long)num2 > (long)num3 * 8L)
				{
					throw new InvalidDataException($"a {num2} x {num2} mask needs more than {num3} bytes");
				}
				_answered = true;
				_parts = null;
				Clearing.SetFromServer(text, num2, extent, array4);
			}
			catch (Exception ex)
			{
				_parts = null;
				_partsKey = null;
				Plugin.Log.LogWarning((object)("clear sync: reading the server's clearing mask failed: " + ex.Message));
			}
		}
	}
	internal static class ConfigSync
	{
		private const string ConfigRpc = "com.raveniron.tarn.config";

		private const int Protocol = 1;

		private const float FileCheckEvery = 3f;

		private static ConfigFile _file;

		private static readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>();

		private static readonly Dictionary<ConfigEntryBase, object> _server = new Dictionary<ConfigEntryBase, object>();

		private static ZRoutedRpc _registeredOn;

		private static bool _dirty;

		private static float _sinceFileCheck;

		private static DateTime _fileTime;

		private static bool Connected
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return !LakeSync.IsAuthority;
				}
				return false;
			}
		}

		internal static void Init(ConfigFile file, params ConfigEntryBase[] entries)
		{
			_file = file;
			_entries.AddRange(entries);
			file.SettingChanged += delegate
			{
				_dirty = true;
			};
			_fileTime = FileTime();
		}

		internal static T Get<T>(ConfigEntry<T> e)
		{
			if (!Connected || !_server.TryGetValue((ConfigEntryBase)(object)e, out var value))
			{
				return e.Value;
			}
			return (T)value;
		}

		internal static bool HasServer(ConfigEntryBase e)
		{
			if (Connected)
			{
				return _server.ContainsKey(e);
			}
			return false;
		}

		internal static void Tick(float dt)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				return;
			}
			if (instance != _registeredOn)
			{
				try
				{
					instance.Register<ZPackage>("com.raveniron.tarn.config", (Action<long, ZPackage>)RPC_Config);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("config sync: register failed: " + ex.Message));
				}
				_registeredOn = instance;
				_server.Clear();
			}
			if (!LakeSync.IsAuthority)
			{
				return;
			}
			_sinceFileCheck += dt;
			if (_sinceFileCheck >= 3f)
			{
				_sinceFileCheck = 0f;
				DateTime dateTime = FileTime();
				if (dateTime != _fileTime)
				{
					_fileTime = dateTime;
					ReloadFile("its file changed");
				}
			}
			if (_dirty)
			{
				_dirty = false;
				Broadcast();
			}
		}

		internal static void ReloadFile(string why)
		{
			if (_file == null)
			{
				return;
			}
			try
			{
				Dictionary<ConfigEntryBase, string> dictionary = Snapshot();
				_file.Reload();
				_fileTime = FileTime();
				List<string> list = new List<string>();
				foreach (ConfigEntryBase entry in _entries)
				{
					if (dictionary[entry] != entry.GetSerializedValue())
					{
						list.Add(entry.Definition.Key + " " + dictionary[entry] + " -> " + entry.GetSerializedValue());
					}
				}
				if (list.Count > 0)
				{
					Plugin.Log.LogInfo((object)("config: re-read (" + why + "): " + string.Join(", ", list)));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("config: re-reading " + _file.ConfigFilePath + " failed: " + ex.Message));
			}
		}

		private static Dictionary<ConfigEntryBase, string> Snapshot()
		{
			Dictionary<ConfigEntryBase, string> dictionary = new Dictionary<ConfigEntryBase, string>();
			foreach (ConfigEntryBase entry in _entries)
			{
				dictionary[entry] = entry.GetSerializedValue();
			}
			return dictionary;
		}

		private static DateTime FileTime()
		{
			try
			{
				return (_file != null && File.Exists(_file.ConfigFilePath)) ? File.GetLastWriteTimeUtc(_file.ConfigFilePath) : default(DateTime);
			}
			catch
			{
				return default(DateTime);
			}
		}

		private static ZPackage Pack()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write("0.4.2");
			val.Write(_entries.Count);
			foreach (ConfigEntryBase entry in _entries)
			{
				val.Write(entry.Definition.Section);
				val.Write(entry.Definition.Key);
				val.Write(entry.GetSerializedValue());
			}
			return val;
		}

		internal static void SendTo(long peer)
		{
			if (!LakeSync.IsAuthority || ZRoutedRpc.instance == null)
			{
				return;
			}
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(peer, "com.raveniron.tarn.config", new object[1] { Pack() });
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"config sync: sending to peer {peer} failed: {ex.Message}");
			}
		}

		private static void Broadcast()
		{
			if (!LakeSync.IsAuthority || ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.GetConnectedPeers().Count == 0)
			{
				return;
			}
			try
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "com.raveniron.tarn.config", new object[1] { Pack() });
				Plugin.Log.LogInfo((object)$"config sync: sent the changed settings to {ZNet.instance.GetConnectedPeers().Count} player(s)");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("config sync: broadcast failed: " + ex.Message));
			}
		}

		private static void RPC_Config(long sender, ZPackage pkg)
		{
			if (LakeSync.IsAuthority)
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num != 1)
				{
					Plugin.Log.LogWarning((object)$"config sync: the server speaks config protocol {num}, this Tarn {1} - update Tarn to match. Using this machine's settings.");
					return;
				}
				pkg.ReadString();
				int num2 = pkg.ReadInt();
				bool flag = _server.Count == 0;
				List<string> list = new List<string>();
				float num3 = Get<float>(Plugin.DepthScale);
				for (int i = 0; i < num2; i++)
				{
					string section = pkg.ReadString();
					string key = pkg.ReadString();
					string text = pkg.ReadString();
					ConfigEntryBase val = _entries.Find((ConfigEntryBase x) => x.Definition.Section == section && x.Definition.Key == key);
					if (val != null)
					{
						object obj = TomlTypeConverter.ConvertToValue(text, val.SettingType);
						ConfigDescription description = val.Description;
						if (((description != null) ? description.AcceptableValues : null) != null)
						{
							obj = val.Description.AcceptableValues.Clamp(obj);
						}
						object value;
						bool num4 = _server.TryGetValue(val, out value);
						_server[val] = obj;
						if (!num4 || !object.Equals(value, obj))
						{
							list.Add(key + " " + TomlTypeConverter.ConvertToString(obj, val.SettingType));
						}
					}
				}
				if (list.Count > 0)
				{
					Plugin.Log.LogInfo((object)("config sync: " + (flag ? "the server's settings" : "the server changed") + ": " + string.Join(", ", list)));
				}
				if (!flag && list.Count > 0)
				{
					Plugin.Notify($"Tarn: the server changed {list.Count} setting(s)");
				}
				if (Get<float>(Plugin.DepthScale) != num3)
				{
					Plugin.Reapply();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("config sync: reading the server's settings failed: " + ex.Message));
			}
		}
	}
	internal static class LakeBeds
	{
		private const float Reach = 32f;

		private const float SlowMs = 20f;

		private const float WarmRange = 640f;

		private const float PickEvery = 0.5f;

		private static Plugin.LakeRt _warming;

		private static float _sincePick = 0.5f;

		private static readonly List<Vector3> _refs = new List<Vector3>();

		internal static float Pad => 2f + LakeSync.LakeBedShore;

		internal static void Warm(float dt, float budgetMs)
		{
			if (!LakeSync.LakeBedsOn)
			{
				_warming = null;
				return;
			}
			WorldGenerator instance = WorldGenerator.instance;
			if (instance == null)
			{
				return;
			}
			_sincePick += dt;
			if (_warming == null || _sincePick >= 0.5f)
			{
				_sincePick = 0f;
				_warming = Pick();
			}
			if (_warming != null)
			{
				LakeShape lakeShape = _warming.Shape ?? (_warming.Shape = new LakeShape(_warming.L));
				Stopwatch stopwatch = Stopwatch.StartNew();
				while (stopwatch.Elapsed.TotalMilliseconds < (double)budgetMs && lakeShape.SampleRow(instance))
				{
				}
				if (lakeShape.Sampled && !lakeShape.Done)
				{
					lakeShape.Flood();
				}
				if (lakeShape.Done)
				{
					_warming = null;
				}
			}
		}

		private static Plugin.LakeRt Pick()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			_refs.Clear();
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				_refs.Add(((Component)Player.m_localPlayer).transform.position);
			}
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					_refs.Add(peer.GetRefPos());
				}
			}
			if (_refs.Count == 0)
			{
				return null;
			}
			Plugin.LakeRt result = null;
			float num = 640f;
			foreach (Plugin.LakeRt lake in Plugin.LakeList)
			{
				if (lake.Shape != null && lake.Shape.Done)
				{
					continue;
				}
				foreach (Vector3 @ref in _refs)
				{
					float num2 = lake.DistanceTo(@ref);
					if (num2 < num)
					{
						num = num2;
						result = lake;
					}
				}
			}
			return result;
		}

		internal static void Reset()
		{
			_warming = null;
			_sincePick = 0.5f;
		}

		internal static int AddFor(Vector3 c, List<object> into, out string firstLake)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
			firstLake = null;
			WorldGenerator instance = WorldGenerator.instance;
			if (instance == null)
			{
				return 0;
			}
			int num = 0;
			float pad = Pad;
			foreach (Plugin.LakeRt lake in Plugin.LakeList)
			{
				Plugin.Lake l = lake.L;
				float num2 = 12f + pad;
				if (c.x + 32f < l.MinX - num2 || c.x - 32f > l.MaxX + num2 || c.z + 32f < l.MinZ - num2 || c.z - 32f > l.MaxZ + num2)
				{
					continue;
				}
				LakeShape lakeShape = lake.Shape ?? (lake.Shape = new LakeShape(l));
				if (!lakeShape.Done)
				{
					Stopwatch stopwatch = Stopwatch.StartNew();
					lakeShape.Complete(instance);
					if (stopwatch.Elapsed.TotalMilliseconds > 20.0)
					{
						Plugin.Log.LogInfo((object)$"lake beds: shaped lake '{l.Name}' for zone generation in {stopwatch.Elapsed.TotalMilliseconds:0} ms ({lakeShape.Nx} x {lakeShape.Nz} samples)");
					}
				}
				if (lakeShape.Failed != null)
				{
					continue;
				}
				int num3 = Mathf.Max(0, Mathf.CeilToInt((c.x - 32f - pad - lakeShape.X0) / 4f));
				int num4 = Mathf.Min(lakeShape.Nx - 1, Mathf.FloorToInt((c.x + 32f + pad - lakeShape.X0) / 4f));
				int num5 = Mathf.Max(0, Mathf.CeilToInt((c.z - 32f - pad - lakeShape.Z0) / 4f));
				int num6 = Mathf.Min(lakeShape.Nz - 1, Mathf.FloorToInt((c.z + 32f + pad - lakeShape.Z0) / 4f));
				if (num3 > num4 || num5 > num6)
				{
					continue;
				}
				int num7 = num4 - num3 + 1;
				int num8 = num6 - num5 + 1;
				bool[] array = new bool[num7 * num8];
				bool flag = false;
				for (int i = 0; i < num8; i++)
				{
					for (int j = 0; j < num7; j++)
					{
						flag |= (array[i * num7 + j] = lakeShape.Wet[(num5 + i) * lakeShape.Nx + num3 + j]);
					}
				}
				if (!flag)
				{
					continue;
				}
				int num9 = num;
				foreach (RectInt item in LakeBuilder.Rectangles(array, num7, num8))
				{
					RectInt current2 = item;
					int num10 = Squares(lakeShape.Cx(num3 + ((RectInt)(ref current2)).xMin) - pad, lakeShape.Cx(num3 + ((RectInt)(ref current2)).xMax - 1) + pad, lakeShape.Cz(num5 + ((RectInt)(ref current2)).yMin) - pad, lakeShape.Cz(num5 + ((RectInt)(ref current2)).yMax - 1) + pad, into);
					if (num10 < 0)
					{
						return num;
					}
					num += num10;
				}
				if (num > num9 && firstLake == null)
				{
					firstLake = l.Name;
				}
			}
			return num;
		}

		private static int Squares(float xa, float xb, float za, float zb, List<object> into)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			float num = xb - xa;
			float num2 = zb - za;
			float num3 = Mathf.Min(num, num2);
			float num4 = num3 / 2f;
			bool flag = num >= num2;
			float num5 = (flag ? xa : za);
			float num6 = (flag ? xb : zb);
			float num7 = (flag ? ((za + zb) / 2f) : ((xa + xb) / 2f));
			int num8 = 0;
			float num9 = num5 + num4;
			while (true)
			{
				float num10 = Mathf.Min(num9, num6 - num4);
				object obj = Clearing.NewArea(flag ? new Vector3(num10, 0f, num7) : new Vector3(num7, 0f, num10), num4);
				if (obj == null)
				{
					return -1;
				}
				into.Add(obj);
				num8++;
				if (num10 + num4 >= num6 - 0.01f)
				{
					break;
				}
				num9 += num3;
			}
			return num8;
		}
	}
	internal sealed class LakeBuilder
	{
		private const float Step_ = 4f;

		private const float TileSize = 64f;

		public readonly Plugin.LakeRt Rt;

		private readonly WaterVolume _tpl;

		private readonly LakeShape _s;

		private int _phase;

		private List<Vector2Int> _tiles;

		private int _tileIdx;

		private List<RectInt> _boxes;

		private int _boxIdx;

		private MeshRenderer _anySurface;

		private int _layerVolume;

		private float Level => Rt.L.Level;

		public LakeBuilder(Plugin.LakeRt rt, WaterVolume template)
		{
			Rt = rt;
			_tpl = template;
			if (rt.Shape == null)
			{
				rt.Shape = new LakeShape(rt.L);
			}
			_s = rt.Shape;
		}

		private float Cx(int i)
		{
			return _s.Cx(i);
		}

		private float Cz(int j)
		{
			return _s.Cz(j);
		}

		public bool Step(Stopwatch sw, float budgetMs)
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_tpl == (Object)null)
			{
				Fail("no zone water template");
				return true;
			}
			WorldGenerator instance = WorldGenerator.instance;
			if (instance == null)
			{
				return false;
			}
			while (sw.Elapsed.TotalMilliseconds < (double)budgetMs)
			{
				switch (_phase)
				{
				case 0:
					if (!_s.SampleRow(instance))
					{
						_phase = 1;
					}
					break;
				case 1:
					if (!Flood())
					{
						return true;
					}
					_phase = 2;
					break;
				case 2:
					if (_tileIdx >= _tiles.Count)
					{
						_phase = 3;
					}
					else
					{
						BuildTile(_tiles[_tileIdx++]);
					}
					break;
				case 3:
					if ((Object)(object)_anySurface == (Object)null)
					{
						Fail("no water surface was drawn");
						return true;
					}
					if (_boxIdx >= _boxes.Count)
					{
						Finish();
						return true;
					}
					BuildBox(_boxes[_boxIdx++]);
					break;
				}
			}
			return false;
		}

		private void Fail(string why)
		{
			Rt.Failed = why;
			Plugin.Drop(Rt);
			Rt.Built = true;
			Plugin.Log.LogWarning((object)("lake '" + Rt.L.Name + "': " + why));
		}

		private bool Flood()
		{
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (!_s.Done)
			{
				_s.Flood();
			}
			if (_s.Failed != null)
			{
				Fail(_s.Failed);
				return false;
			}
			if (_s.TouchedEdge)
			{
				Plugin.Log.LogWarning((object)("lake '" + Rt.L.Name + "': its water reaches the edge of its box - the basin is not closed there, so the shore is cut off at the box"));
			}
			Rt.WetCells = _s.WetCells;
			int nx = _s.Nx;
			int nz = _s.Nz;
			HashSet<Vector2Int> hashSet = new HashSet<Vector2Int>();
			for (int i = 0; i < nz; i++)
			{
				for (int j = 0; j < nx; j++)
				{
					if (_s.Mask[i * nx + j])
					{
						hashSet.Add(new Vector2Int(Mathf.FloorToInt((Cx(j) + 32f) / 64f), Mathf.FloorToInt((Cz(i) + 32f) / 64f)));
					}
				}
			}
			_tiles = new List<Vector2Int>(hashSet);
			_boxes = Rectangles(_s.Mask, nx, nz);
			_layerVolume = ((Component)_tpl).gameObject.layer;
			return true;
		}

		private bool InMask(float x, float z)
		{
			return _s.InMask(x, z);
		}

		private void BuildTile(Vector2Int t)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_049b: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)((Vector2Int)(ref t)).x * 64f;
			float num2 = (float)((Vector2Int)(ref t)).y * 64f;
			float num3 = 32f;
			GameObject val = Object.Instantiate<GameObject>(((Component)_tpl).gameObject);
			((Object)val).name = $"Tarn_{Rt.L.Name}_{((Vector2Int)(ref t)).x}_{((Vector2Int)(ref t)).y}";
			val.transform.rotation = ((Component)_tpl).transform.rotation;
			val.transform.localScale = ((Component)_tpl).transform.lossyScale;
			val.transform.position = new Vector3(num, Level - _tpl.m_surfaceOffset, num2);
			Collider[] componentsInChildren = val.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].enabled = false;
			}
			Rt.Owned.Add((Object)(object)val);
			WaterVolume component = val.GetComponent<WaterVolume>();
			Transform val2 = (((Object)(object)_tpl.m_waterSurface != (Object)null) ? ((Component)_tpl.m_waterSurface).transform : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"zone water has no surface renderer: lakes will be invisible");
				return;
			}
			GameObject val3 = Object.Instantiate<GameObject>(((Component)val2).gameObject);
			((Object)val3).name = ((Object)val).name + "_s";
			WaterVolume[] componentsInChildren2 = val3.GetComponentsInChildren<WaterVolume>(true);
			foreach (WaterVolume val4 in componentsInChildren2)
			{
				if ((Object)(object)((Component)val4).gameObject == (Object)(object)val3)
				{
					Object.DestroyImmediate((Object)(object)val4);
				}
				else
				{
					Object.DestroyImmediate((Object)(object)((Component)val4).gameObject);
				}
			}
			componentsInChildren = val3.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].enabled = false;
			}
			val3.transform.rotation = val2.rotation;
			val3.transform.localScale = val2.lossyScale;
			val3.transform.position = val.transform.position + (val2.position - ((Component)_tpl).transform.position);
			Rt.Owned.Add((Object)(object)val3);
			MeshFilter component2 = val3.GetComponent<MeshFilter>();
			MeshRenderer component3 = val3.GetComponent<MeshRenderer>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)component2.sharedMesh != (Object)null)
			{
				Mesh val5 = Object.Instantiate<Mesh>(component2.sharedMesh);
				((Object)val5).name = ((Object)val3).name;
				Vector3[] vertices = val5.vertices;
				bool[] array = new bool[vertices.Length];
				for (int j = 0; j < vertices.Length; j++)
				{
					Vector3 val6 = val3.transform.TransformPoint(vertices[j]);
					array[j] = InMask(val6.x, val6.z);
				}
				int num4 = 0;
				for (int k = 0; k < val5.subMeshCount; k++)
				{
					int[] triangles = val5.GetTriangles(k);
					List<int> list = new List<int>(triangles.Length);
					for (int l = 0; l + 2 < triangles.Length; l += 3)
					{
						if (array[triangles[l]] || array[triangles[l + 1]] || array[triangles[l + 2]])
						{
							list.Add(triangles[l]);
							list.Add(triangles[l + 1]);
							list.Add(triangles[l + 2]);
						}
					}
					val5.SetTriangles(list, k);
					num4 += list.Count / 3;
				}
				Rt.Owned.Add((Object)(object)val5);
				if (num4 == 0)
				{
					Object.Destroy((Object)(object)val3);
					Object.Destroy((Object)(object)val);
					return;
				}
				component2.sharedMesh = val5;
			}
			component.m_waterSurface = component3;
			component.m_heightmap = null;
			component.m_forceDepth = -1f;
			component.m_useGlobalWind = true;
			float[] depth = new float[4]
			{
				Mathf.Max(0f, Level - Plugin.GroundHeight(num - num3, num2 + num3)),
				Mathf.Max(0f, Level - Plugin.GroundHeight(num + num3, num2 + num3)),
				Mathf.Max(0f, Level - Plugin.GroundHeight(num + num3, num2 - num3)),
				Mathf.Max(0f, Level - Plugin.GroundHeight(num - num3, num2 - num3))
			};
			Plugin.Tiles[component] = new Plugin.Tile
			{
				Volume = component,
				Depth = depth,
				Centre = new Vector3(num, Level, num2),
				Visual = true
			};
			if ((Object)(object)_anySurface == (Object)null)
			{
				_anySurface = component3;
			}
			Rt.VisualTiles++;
		}

		private void BuildBox(RectInt r)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			float num = Cx(((RectInt)(ref r)).xMin) - 2f;
			float num2 = Cx(((RectInt)(ref r)).xMax - 1) + 2f;
			float num3 = Cz(((RectInt)(ref r)).yMin) - 2f;
			float num4 = Cz(((RectInt)(ref r)).yMax - 1) + 2f;
			float num5 = Level;
			float num6 = 0f;
			int num7 = 0;
			for (int i = ((RectInt)(ref r)).yMin; i < ((RectInt)(ref r)).yMax; i++)
			{
				for (int j = ((RectInt)(ref r)).xMin; j < ((RectInt)(ref r)).xMax; j++)
				{
					float num8 = _s.H[i * _s.Nx + j];
					num5 = Mathf.Min(num5, num8);
					num6 += Mathf.Max(0f, Level - num8);
					num7++;
				}
			}
			float num9 = num5 - 4f;
			float num10 = Level + 10f;
			GameObject val = new GameObject($"Tarn_{Rt.L.Name}_box{_boxIdx}");
			val.SetActive(false);
			val.layer = _layerVolume;
			val.transform.position = new Vector3((num + num2) / 2f, Level, (num3 + num4) / 2f);
			BoxCollider obj = val.AddComponent<BoxCollider>();
			((Collider)obj).isTrigger = true;
			obj.size = new Vector3(num2 - num, num10 - num9, num4 - num3);
			obj.center = new Vector3(0f, (num10 + num9) / 2f - Level, 0f);
			WaterVolume val2 = val.AddComponent<WaterVolume>();
			val2.m_waterSurface = _anySurface;
			val2.m_heightmap = null;
			val2.m_forceDepth = -1f;
			val2.m_useGlobalWind = true;
			val2.m_surfaceOffset = 0f;
			float num11 = ((num7 > 0) ? (num6 / (float)num7) : 0f);
			Plugin.Tiles[val2] = new Plugin.Tile
			{
				Volume = val2,
				Depth = new float[4] { num11, num11, num11, num11 },
				Centre = val.transform.position,
				Visual = false
			};
			WaterAccess.CollisionOnly.Add(val2);
			Rt.Owned.Add((Object)(object)val);
			val.SetActive(true);
			Rt.Boxes++;
		}

		private void Finish()
		{
			Plugin.Log.LogInfo((object)($"lake '{Rt.L.Name}' built at ({Rt.L.X:0}, {Rt.L.Z:0}): level {Level:0.00}, {(float)Rt.WetCells * 4f * 4f:0} m2 of water, " + $"{Rt.VisualTiles} tile(s), {Rt.Boxes} box(es)"));
		}

		internal static List<RectInt> Rectangles(bool[] mask, int nx, int nz)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			bool[] array = new bool[mask.Length];
			List<RectInt> list = new List<RectInt>();
			for (int i = 0; i < nz; i++)
			{
				for (int j = 0; j < nx; j++)
				{
					int num = i * nx + j;
					if (!mask[num] || array[num])
					{
						continue;
					}
					int k;
					for (k = j; k < nx && mask[i * nx + k] && !array[i * nx + k]; k++)
					{
					}
					int l;
					for (l = i + 1; l < nz; l++)
					{
						bool flag = true;
						for (int m = j; m < k && flag; m++)
						{
							flag = mask[l * nx + m] && !array[l * nx + m];
						}
						if (!flag)
						{
							break;
						}
					}
					for (int n = i; n < l; n++)
					{
						for (int num2 = j; num2 < k; num2++)
						{
							array[n * nx + num2] = true;
						}
					}
					list.Add(new RectInt(j, i, k - j, l - i));
				}
			}
			return list;
		}
	}
	internal sealed class LakeShape
	{
		internal const float Step = 4f;

		internal const float Edge = 12f;

		public readonly Plugin.Lake L;

		public readonly float X0;

		public readonly float Z0;

		public readonly int Nx;

		public readonly int Nz;

		public readonly float[] H;

		public bool[] Wet;

		public bool[] Mask;

		public int WetCells;

		public bool TouchedEdge;

		public string Failed;

		private int _row;

		public bool Sampled => _row >= Nz;

		public bool Done
		{
			get
			{
				if (Wet == null)
				{
					return Failed != null;
				}
				return true;
			}
		}

		public LakeShape(Plugin.Lake l)
		{
			L = l;
			X0 = l.MinX - 12f;
			Z0 = l.MinZ - 12f;
			Nx = Mathf.CeilToInt((l.MaxX + 12f - X0) / 4f) + 1;
			Nz = Mathf.CeilToInt((l.MaxZ + 12f - Z0) / 4f) + 1;
			H = new float[Nx * Nz];
		}

		public float Cx(int i)
		{
			return X0 + (float)i * 4f;
		}

		public float Cz(int j)
		{
			return Z0 + (float)j * 4f;
		}

		public bool SampleRow(WorldGenerator wg)
		{
			if (_row >= Nz)
			{
				return false;
			}
			for (int i = 0; i < Nx; i++)
			{
				H[_row * Nx + i] = wg.GetHeight(Cx(i), Cz(_row));
			}
			_row++;
			return true;
		}

		public void Complete(WorldGenerator wg)
		{
			while (SampleRow(wg))
			{
			}
			if (!Done)
			{
				Flood();
			}
		}

		public bool Flood()
		{
			float level = L.Level;
			int num = Mathf.Clamp(Mathf.RoundToInt((L.X - X0) / 4f), 0, Nx - 1);
			int num2 = Mathf.Clamp(Mathf.RoundToInt((L.Z - Z0) / 4f), 0, Nz - 1);
			if (!(H[num2 * Nx + num] < level))
			{
				float num3 = float.MaxValue;
				int num4 = num;
				int num5 = num2;
				for (int i = -3; i <= 3; i++)
				{
					for (int j = -3; j <= 3; j++)
					{
						int num6 = num + j;
						int num7 = num2 + i;
						if (num6 >= 0 && num7 >= 0 && num6 < Nx && num7 < Nz && H[num7 * Nx + num6] < num3)
						{
							num3 = H[num7 * Nx + num6];
							num4 = num6;
							num5 = num7;
						}
					}
				}
				num = num4;
				num2 = num5;
				if (!(num3 < level))
				{
					Failed = $"its point ({L.X:0}, {L.Z:0}) is dry: ground {num3:0.0} m, level {level:0.0} m";
					return false;
				}
			}
			bool[] wet = new bool[Nx * Nz];
			Stack<int> stack = new Stack<int>();
			stack.Push(num2 * Nx + num);
			wet[num2 * Nx + num] = true;
			int num8 = 0;
			bool touchedEdge = false;
			while (stack.Count > 0)
			{
				int num9 = stack.Pop();
				num8++;
				int num10 = num9 % Nx;
				int num11 = num9 / Nx;
				if (num10 == 0 || num11 == 0 || num10 == Nx - 1 || num11 == Nz - 1)
				{
					touchedEdge = true;
				}
				Visit(num10 + 1, num11);
				Visit(num10 - 1, num11);
				Visit(num10, num11 + 1);
				Visit(num10, num11 - 1);
			}
			bool[] array = new bool[Nx * Nz];
			for (int k = 0; k < Nz; k++)
			{
				for (int l = 0; l < Nx; l++)
				{
					if (!wet[k * Nx + l])
					{
						continue;
					}
					for (int m = -1; m <= 1; m++)
					{
						for (int n = -1; n <= 1; n++)
						{
							int num12 = l + n;
							int num13 = k + m;
							if (num12 >= 0 && num13 >= 0 && num12 < Nx && num13 < Nz)
							{
								array[num13 * Nx + num12] = true;
							}
						}
					}
				}
			}
			WetCells = num8;
			TouchedEdge = touchedEdge;
			Mask = array;
			Wet = wet;
			return true;
			void Visit(int num14, int num15)
			{
				if (num14 >= 0 && num15 >= 0 && num14 < Nx && num15 < Nz)
				{
					int num16 = num15 * Nx + num14;
					if (!wet[num16] && H[num16] < level)
					{
						wet[num16] = true;
						stack.Push(num16);
					}
				}
			}
		}

		public bool InBed(float x, float z, float pad)
		{
			int num = Mathf.Max(0, Mathf.FloorToInt((x - pad - X0) / 4f));
			int num2 = Mathf.Min(Nx - 1, Mathf.CeilToInt((x + pad - X0) / 4f));
			int num3 = Mathf.Max(0, Mathf.FloorToInt((z - pad - Z0) / 4f));
			int num4 = Mathf.Min(Nz - 1, Mathf.CeilToInt((z + pad - Z0) / 4f));
			for (int i = num3; i <= num4; i++)
			{
				for (int j = num; j <= num2; j++)
				{
					if (Wet[i * Nx + j] && Mathf.Abs(x - Cx(j)) < pad && Mathf.Abs(z - Cz(i)) < pad)
					{
						return true;
					}
				}
			}
			return false;
		}

		public bool InMask(float x, float z)
		{
			int num = Mathf.RoundToInt((x - X0) / 4f);
			int num2 = Mathf.RoundToInt((z - Z0) / 4f);
			if (num >= 0 && num2 >= 0 && num < Nx && num2 < Nz)
			{
				return Mask[num2 * Nx + num];
			}
			return false;
		}
	}
	internal static class LakeSync
	{
		internal const string RequestRpc = "com.raveniron.tarn.request";

		internal const string LakesRpc = "com.raveniron.tarn.lakes";

		private const int Protocol = 1;

		internal const float MaxShore = 10f;

		private const float AskEvery = 4f;

		private const int GiveUpAfter = 15;

		private static ZRoutedRpc _registeredOn;

		private static float _sinceAsk = 4f;

		private static int _asks;

		private static bool _answered;

		private static bool _gaveUp;

		private static bool _warnedVersion;

		private static bool _serverLakeBeds;

		private const float ShoreBefore041 = 2f;

		private static float _serverShore = 2f;

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

		internal static bool Answered => _answered;

		internal static bool GaveUp => _gaveUp;

		internal static bool LakeBedsOn
		{
			get
			{
				if (!IsAuthority)
				{
					if (!ConfigSync.HasServer((ConfigEntryBase)(object)Plugin.ClearLakeBeds))
					{
						if (_answered)
						{
							return _serverLakeBeds;
						}
						return false;
					}
					if (_answered)
					{
						return ConfigSync.Get<bool>(Plugin.ClearLakeBeds);
					}
					return false;
				}
				return Plugin.ClearLakeBeds.Value;
			}
		}

		internal static float LakeBedShore
		{
			get
			{
				if (!IsAuthority && !ConfigSync.HasServer((ConfigEntryBase)(object)Plugin.LakeBedShore))
				{
					return _serverShore;
				}
				return ConfigSync.Get<float>(Plugin.LakeBedShore);
			}
		}

		internal static void Tick(float dt)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				return;
			}
			if (instance != _registeredOn)
			{
				try
				{
					instance.Register("com.raveniron.tarn.request", (Action<long>)RPC_Request);
					instance.Register<ZPackage>("com.raveniron.tarn.lakes", (Action<long, ZPackage>)RPC_Lakes);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("lake sync: register failed: " + ex.Message));
				}
				_registeredOn = instance;
				_answered = (_gaveUp = (_warnedVersion = (_serverLakeBeds = false)));
				_serverShore = 2f;
				_asks = 0;
				_sinceAsk = 4f;
			}
			if (IsAuthority || _answered || _gaveUp || ZNet.instance.GetServerPeer() == null)
			{
				return;
			}
			_sinceAsk += dt;
			if (_sinceAsk < 4f)
			{
				return;
			}
			_sinceAsk = 0f;
			if (++_asks > 15)
			{
				_gaveUp = true;
				Plugin.Log.LogWarning((object)"lake sync: the server never sent its lakes - is Tarn installed on the server? No lakes this session.");
				return;
			}
			try
			{
				instance.InvokeRoutedRPC("com.raveniron.tarn.request", Array.Empty<object>());
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("lake sync: asking the server failed: " + ex2.Message));
			}
		}

		private static ZPackage Pack(IEnumerable<Plugin.Lake> lakes)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			List<string> list = new List<string>();
			foreach (Plugin.Lake lake in lakes)
			{
				list.Add(lake.ToLine());
			}
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write("0.4.2");
			val.Write(Plugin.WorldName());
			val.Write(list.Count);
			foreach (string item in list)
			{
				val.Write(item);
			}
			val.Write(Plugin.ClearLakeBeds.Value);
			val.Write(Plugin.LakeBedShore.Value);
			return val;
		}

		internal static void Broadcast(IEnumerable<Plugin.Lake> lakes)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (!IsAuthority || instance == null || ZNet.instance.GetConnectedPeers().Count == 0)
			{
				return;
			}
			try
			{
				instance.InvokeRoutedRPC(0L, "com.raveniron.tarn.lakes", new object[1] { Pack(lakes) });
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("lake sync: broadcast failed: " + ex.Message));
			}
		}

		private static void RPC_Request(long sender)
		{
			if (!IsAuthority)
			{
				return;
			}
			try
			{
				ConfigSync.SendTo(sender);
				List<Plugin.Lake> list = Plugin.CurrentLakes();
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "com.raveniron.tarn.lakes", new object[1] { Pack(list) });
				Plugin.Log.LogInfo((object)$"lake sync: sent {list.Count} lake(s) to peer {sender}");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"lake sync: answering peer {sender} failed: {ex.Message}");
			}
		}

		private static void RPC_Lakes(long sender, ZPackage pkg)
		{
			if (IsAuthority)
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num != 1)
				{
					Plugin.Log.LogWarning((object)$"lake sync: the server speaks lake protocol {num}, this Tarn {1} - update Tarn to match. No lakes.");
					_gaveUp = true;
					return;
				}
				string text = pkg.ReadString();
				string text2 = pkg.ReadString();
				int num2 = pkg.ReadInt();
				List<Plugin.Lake> list = new List<Plugin.Lake>(num2);
				for (int i = 0; i < num2; i++)
				{
					Plugin.Lake lake = Plugin.Lake.Parse(pkg.ReadString());
					if (lake != null)
					{
						list.Add(lake);
					}
				}
				bool flag = pkg.GetPos() < pkg.Size() && pkg.ReadBool();
				float num3 = ((pkg.GetPos() < pkg.Size()) ? pkg.ReadSingle() : 2f);
				if (text != "0.4.2" && !_warnedVersion)
				{
					_warnedVersion = true;
					Plugin.Log.LogWarning((object)("lake sync: the server runs Tarn " + text + ", this client 0.4.2. It works while the protocol matches; update to match."));
				}
				bool num4 = !_answered;
				_serverLakeBeds = flag;
				_serverShore = Mathf.Clamp(num3, 0f, 10f);
				_answered = true;
				Plugin.ReplaceLakes(list, "the server (" + text2 + "), lake beds " + (flag ? $"kept clear to {_serverShore:0.#} m past the water" : "not cleared"));
				if (!num4)
				{
					Plugin.Notify($"Tarn: the server changed its lakes ({list.Count})");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("lake sync: reading the server's lakes failed: " + ex.Message));
			}
		}
	}
	internal static class MapLakes
	{
		internal static bool Dirty;

		private static FieldRef<Minimap, Texture2D> _height;

		private static FieldRef<Minimap, Texture2D> _mask;

		private static bool _warned;

		private const float Sea = 30f;

		internal static void Paint(IEnumerable<Plugin.Lake> lakes)
		{
			//IL_0399: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			Minimap instance = Minimap.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				if (_height == null)
				{
					_height = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_heightTexture");
				}
				if (_mask == null)
				{
					_mask = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_forestMaskTexture");
				}
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogError((object)("Minimap.m_heightTexture / m_forestMaskTexture did not resolve (" + ex.Message + "): Valheim's API moved; lakes stay off the map"));
				}
				return;
			}
			Texture2D val = _height.Invoke(instance);
			Texture2D val2 = _mask.Invoke(instance);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				return;
			}
			int size = instance.m_textureSize;
			float px = instance.m_pixelSize;
			int half = size / 2;
			Color[] h = val.GetPixels();
			Color[] pixels = val2.GetPixels();
			if (h.Length != size * size || pixels.Length != size * size)
			{
				Plugin.Log.LogWarning((object)"map textures are not the size the map says: lakes stay off the map");
				return;
			}
			int num = 0;
			int num2 = 0;
			Stack<int> stack = new Stack<int>();
			foreach (Plugin.Lake lake in lakes)
			{
				Plugin.Lake l = lake;
				int j0 = Mathf.Max(0, Col(l.MinX) - 2);
				int j1 = Mathf.Min(size - 1, Col(l.MaxX) + 2);
				int i0 = Mathf.Max(0, Col(l.MinZ) - 2);
				int i1 = Mathf.Min(size - 1, Col(l.MaxZ) + 2);
				int num3 = Mathf.Clamp(Col(l.X), j0, j1);
				int num4 = Mathf.Clamp(Col(l.Z), i0, i1);
				float num5 = float.MaxValue;
				for (int k = -2; k <= 2; k++)
				{
					for (int m = -2; m <= 2; m++)
					{
						int num6 = num4 + k;
						int num7 = num3 + m;
						if (num6 >= i0 && num6 <= i1 && num7 >= j0 && num7 <= j1)
						{
							float r = h[num6 * size + num7].r;
							if (r < num5)
							{
								num5 = r;
								num4 = num6;
								num3 = num7;
							}
						}
					}
				}
				HashSet<int> seen;
				if (num5 < l.Level)
				{
					seen = new HashSet<int>();
					stack.Push(num4 * size + num3);
					seen.Add(num4 * size + num3);
					int num8 = 0;
					while (stack.Count > 0)
					{
						int num9 = stack.Pop();
						int num10 = num9 / size;
						int num11 = num9 % size;
						float num12 = Mathf.Max(1f, l.Level - h[num9].r);
						h[num9].r = 30f - num12;
						pixels[num9] = new Color(0f, 0f, 0f, 0f);
						num8++;
						Visit(num10 + 1, num11);
						Visit(num10 - 1, num11);
						Visit(num10, num11 + 1);
						Visit(num10, num11 - 1);
					}
					num += num8;
					num2++;
				}
				void Visit(int a, int b)
				{
					if (a >= i0 && a <= i1 && b >= j0 && b <= j1)
					{
						int num13 = a * size + b;
						if (!seen.Contains(num13) && h[num13].r < l.Level && !(h[num13].r < 30f))
						{
							seen.Add(num13);
							stack.Push(num13);
						}
					}
				}
			}
			val.SetPixels(h);
			val.Apply();
			val2.SetPixels(pixels);
			val2.Apply();
			Plugin.Log.LogInfo((object)$"map: {num2} lake(s) drawn as water, {num} pixel(s) of {px:0} m");
			int Col(float wx)
			{
				return Mathf.FloorToInt((wx - px / 2f) / px + (float)half + 0.5f);
			}
		}
	}
	[HarmonyPatch(typeof(Minimap), "GenerateWorldMap")]
	internal static class Patch_MapGenerated
	{
		private static void Postfix()
		{
			MapLakes.Dirty = true;
		}
	}
	[HarmonyPatch(typeof(Minimap), "TryLoadMinimapTextureData")]
	internal static class Patch_MapLoaded
	{
		private static void Postfix(bool __result)
		{
			if (__result)
			{
				MapLakes.Dirty = true;
			}
		}
	}
	[BepInPlugin("com.raveniron.tarn", "Tarn", "0.4.2")]
	public class Plugin : BaseUnityPlugin
	{
		internal sealed class Tile
		{
			public WaterVolume Volume;

			public float[] Depth;

			public Vector3 Centre;

			public bool Visual;
		}

		internal sealed class Lake
		{
			public string Name;

			public float X;

			public float Z;

			public float Level;

			public float MinX;

			public float MinZ;

			public float MaxX;

			public float MaxZ;

			public float Depth;

			public string ToLine()
			{
				return string.Join(";", Name, F(X), F(Z), F(Level), F(MinX), F(MinZ), F(MaxX), F(MaxZ), F(Depth));
			}

			private static string F(float v)
			{
				return v.ToString("0.###", C);
			}

			public static Lake Parse(string line)
			{
				string[] p = line.Split(new char[1] { ';' });
				if (p.Length < 9)
				{
					return null;
				}
				return new Lake
				{
					Name = p[0],
					X = G(1),
					Z = G(2),
					Level = G(3),
					MinX = G(4),
					MinZ = G(5),
					MaxX = G(6),
					MaxZ = G(7),
					Depth = G(8)
				};
				float G(int i)
				{
					return float.Parse(p[i], NumberStyles.Float, C);
				}
			}
		}

		internal sealed class LakeRt
		{
			public Lake L;

			public bool Built;

			public readonly List<Object> Owned = new List<Object>();

			public int WetCells;

			public int VisualTiles;

			public int Boxes;

			public string Failed;

			public LakeShape Shape;

			public float DistanceTo(Vector3 p)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: 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_004a: Unknown result type (might be due to invalid IL or missing references)
				float num = Mathf.Max(0f, Mathf.Max(L.MinX - p.x, p.x - L.MaxX));
				float num2 = Mathf.Max(0f, Mathf.Max(L.MinZ - p.z, p.z - L.MaxZ));
				return Mathf.Sqrt(num * num + num2 * num2);
			}
		}

		public const string Guid = "com.raveniron.tarn";

		public const string Version = "0.4.2";

		internal static ManualLogSource Log;

		private static readonly CultureInfo C = CultureInfo.InvariantCulture;

		internal static ConfigEntry<float> DepthScale;

		internal static ConfigEntry<float> SoundVolume;

		internal static ConfigEntry<float> BuildRange;

		internal static ConfigEntry<float> BudgetMs;

		internal static ConfigEntry<bool> ClearLakeBeds;

		internal static ConfigEntry<float> LakeBedShore;

		private static ZoneSystem _loadedFor;

		private static readonly List<LakeRt> Lakes = new List<LakeRt>();

		private static LakeBuilder _building;

		private static float _streamTimer;

		private static bool _loggedTemplate;

		internal static readonly Dictionary<WaterVolume, Tile> Tiles = new Dictionary<WaterVolume, Tile>();

		internal static List<LakeRt> LakeList => Lakes;

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Expected O, but got Unknown
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			DepthScale = ((BaseUnityPlugin)this).Config.Bind<float>("Look", "DepthScale", 0.5f, new ConfigDescription("How deep the game's water shader thinks a lake is, as a share of its real depth. Vanilla's colour, foam and wave size all follow depth (full colour and waves at 10 m), so lower is calmer and greener, higher bluer and rougher. Live: `tarn depth <value>`.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			SoundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Sound", "LakeVolume", 0.35f, new ConfigDescription("Volume of the water sound near a lake (vanilla's ocean loop, quieter). 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			BuildRange = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "BuildRange", 400f, new ConfigDescription("Lakes within this many metres of you are built; they are dropped again 150 m further out.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(150f, 2000f), Array.Empty<object>()));
			BudgetMs = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "BuildBudgetMs", 2f, new ConfigDescription("Milliseconds per frame spent building lakes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>()));
			ClearLakeBeds = ((BaseUnityPlugin)this).Config.Bind<bool>("Clearing", "LakeBeds", true, "Keep trees, rocks, bushes and pickable debris off every lake's bed (the ground under its water, to the shore), including lakes made with `tarn here`. Only zones generated after a lake exists are affected. In multiplayer the SERVER's setting applies to everyone (it is sent with the lakes).");
			LakeBedShore = ((BaseUnityPlugin)this).Config.Bind<float>("Clearing", "LakeBedShore", 2f, new ConfigDescription("How far past the water's edge lake-bed clearing reaches, in metres. A lake's shape is sampled every 4 m, so the edge it knows is good to about 2 m: at 0 a little may still stand in the shallows, at the default 2 nothing does. Also what `tarn clearbeds` removes. In multiplayer the SERVER's setting applies to everyone.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			ConfigSync.Init(((BaseUnityPlugin)this).Config, (ConfigEntryBase)DepthScale, (ConfigEntryBase)SoundVolume, (ConfigEntryBase)BuildRange, (ConfigEntryBase)BudgetMs, (ConfigEntryBase)ClearLakeBeds, (ConfigEntryBase)LakeBedShore);
			new Harmony("com.raveniron.tarn").PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)"Tarn 0.4.2 (prototype) loaded");
		}

		private void Update()
		{
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			LakeSound.Update(Time.deltaTime);
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				if ((Object)(object)_loadedFor != (Object)null)
				{
					Unload();
				}
				return;
			}
			if ((Object)(object)instance != (Object)(object)_loadedFor)
			{
				Unload();
				_loadedFor = instance;
				if (LakeSync.IsAuthority)
				{
					try
					{
						Lakes.AddRange(from l in LoadLakes()
							select new LakeRt
							{
								L = l
							});
						Log.LogInfo((object)(string.Format("world '{0}': {1} lake(s) from {2}, lake beds {3}", WorldName(), Lakes.Count, LakeFile(), ClearLakeBeds.Value ? $"kept clear to {LakeBedShore.Value:0.#} m past the water" : "not cleared") + (ZNet.instance.IsDedicated() ? " (dedicated server: sent to players, no water built here)" : "")));
					}
					catch (Exception arg)
					{
						Log.LogError((object)$"loading lakes failed: {arg}");
					}
				}
				else
				{
					Log.LogInfo((object)"joined a server: waiting for its lakes");
				}
			}
			ConfigSync.Tick(Time.deltaTime);
			LakeSync.Tick(Time.deltaTime);
			ClearSync.Tick(Time.deltaTime);
			ClearBeds.Tick(ConfigSync.Get<float>(BudgetMs));
			if (MapLakes.Dirty && (Object)(object)_loadedFor == (Object)(object)instance && (Object)(object)Minimap.instance != (Object)null)
			{
				MapLakes.Dirty = false;
				try
				{
					MapLakes.Paint(Lakes.Select((LakeRt r) => r.L));
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("drawing lakes on the map failed: " + ex.Message));
				}
			}
			if (_building != null)
			{
				Stopwatch sw = Stopwatch.StartNew();
				bool flag;
				try
				{
					flag = _building.Step(sw, ConfigSync.Get<float>(BudgetMs));
				}
				catch (Exception ex2)
				{
					Log.LogError((object)$"building lake '{_building.Rt.L.Name}' failed: {ex2}");
					_building.Rt.Failed = ex2.Message;
					Drop(_building.Rt);
					_building.Rt.Built = true;
					flag = true;
				}
				if (flag)
				{
					_building = null;
				}
			}
			else
			{
				try
				{
					LakeBeds.Warm(Time.deltaTime, ConfigSync.Get<float>(BudgetMs));
				}
				catch (Exception ex3)
				{
					Log.LogWarning((object)("lake beds: shaping ahead failed: " + ex3.Message));
					LakeBeds.Reset();
				}
			}
			_streamTimer += Time.deltaTime;
			if (_streamTimer < 0.5f)
			{
				return;
			}
			_streamTimer = 0f;
			if (!ListenerPosition(out var at))
			{
				return;
			}
			float build = ConfigSync.Get<float>(BuildRange);
			float num = build + 150f;
			foreach (LakeRt lake in Lakes)
			{
				if (lake.Built && lake.Failed == null && lake.DistanceTo(at) > num && _building?.Rt != lake)
				{
					Drop(lake);
				}
			}
			if (_building == null)
			{
				LakeRt lakeRt = (from r in Lakes
					where !r.Built && r.DistanceTo(at) < build
					orderby r.DistanceTo(at)
					select r).FirstOrDefault();
				if (lakeRt != null)
				{
					_building = new LakeBuilder(lakeRt, Template());
					lakeRt.Built = true;
				}
			}
		}

		internal static bool ListenerPosition(out Vector3 p)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				p = ((Component)Player.m_localPlayer).transform.position;
				return true;
			}
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null && (int)SystemInfo.graphicsDeviceType != 4)
			{
				p = ((Component)main).transform.position;
				return true;
			}
			p = default(Vector3);
			return false;
		}

		private static void Unload()
		{
			foreach (LakeRt lake in Lakes)
			{
				Drop(lake);
			}
			Lakes.Clear();
			Tiles.Clear();
			_building = null;
			_loadedFor = null;
			LakeBeds.Reset();
		}

		internal static void Drop(LakeRt rt)
		{
			foreach (Object item in rt.Owned)
			{
				GameObject val = (GameObject)(object)((item is GameObject) ? item : null);
				if (val != null && (Object)(object)val != (Object)null)
				{
					WaterVolume[] componentsInChildren = val.GetComponentsInChildren<WaterVolume>(true);
					foreach (WaterVolume val2 in componentsInChildren)
					{
						Tiles.Remove(val2);
						WaterAccess.CollisionOnly.Remove(val2);
					}
				}
				if (item != (Object)null)
				{
					Object.Destroy(item);
				}
			}
			rt.Owned.Clear();
			rt.Built = false;
			rt.WetCells = (rt.VisualTiles = (rt.Boxes = 0));
		}

		internal static string WorldName()
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return "unknown";
			}
			return ZNet.instance.GetWorldName();
		}

		internal static string LakeFile()
		{
			return Path.Combine(Paths.ConfigPath, "Tarn", WorldName() + ".txt");
		}

		private static IEnumerable<Lake> LoadLakes()
		{
			string path = LakeFile();
			if (!File.Exists(path))
			{
				yield break;
			}
			string[] array = File.ReadAllLines(path);
			foreach (string text in array)
			{
				if (!string.IsNullOrWhiteSpace(text) && !text.TrimStart(Array.Empty<char>()).StartsWith("#"))
				{
					Lake lake = null;
					try
					{
						lake = Lake.Parse(text.Trim());
					}
					catch (Exception ex)
					{
						Log.LogWarning((object)("bad lake line '" + text + "': " + ex.Message));
					}
					if (lake != null)
					{
						yield return lake;
					}
				}
			}
		}

		internal static void SaveLakes()
		{
			string path = LakeFile();
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			List<string> list = new List<string> { "# Tarn lakes: name;x;z;level;minX;minZ;maxX;maxZ;depth (world metres, absolute heights; x, z = a point in the water)" };
			list.AddRange(Lakes.Select((LakeRt l) => l.L.ToLine()));
			File.WriteAllLines(path, list.ToArray());
		}

		internal static WaterVolume Template()
		{
			ZoneSystem instance = ZoneSystem.instance;
			WaterVolume obj = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_zonePrefab != (Object)null) ? instance.m_zonePrefab.GetComponentInChildren<WaterVolume>(true) : null);
			if ((Object)(object)obj != (Object)null && !_loggedTemplate)
			{
				_loggedTemplate = true;
				Log.LogInfo((object)("zone water template:\n" + DescribeTemplate()));
			}
			return obj;
		}

		internal static string DescribeTemplate()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_0382: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			WaterVolume val = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_zonePrefab != (Object)null) ? instance.m_zonePrefab.GetComponentInChildren<WaterVolume>(true) : null);
			if ((Object)(object)val == (Object)null)
			{
				return "no zone water template found";
			}
			StringBuilder stringBuilder = new StringBuilder();
			Transform transform = instance.m_zonePrefab.transform;
			stringBuilder.Append($"zone prefab '{((Object)transform).name}' root pos {transform.position}\n");
			stringBuilder.Append(string.Format("water '{0}' pos {1} (parent '{2}') lossy scale {3} layer {4}\n", ((Object)val).name, ((Component)val).transform.position, Object.op_Implicit((Object)(object)((Component)val).transform.parent) ? ((Object)((Component)val).transform.parent).name : "-", ((Component)val).transform.lossyScale, LayerMask.LayerToName(((Component)val).gameObject.layer)));
			stringBuilder.Append($"  surfaceOffset {val.m_surfaceOffset} forceDepth {val.m_forceDepth} globalWind {val.m_useGlobalWind}\n");
			Collider[] components = ((Component)val).GetComponents<Collider>();
			foreach (Collider val2 in components)
			{
				string name = ((object)val2).GetType().Name;
				BoxCollider val3 = (BoxCollider)(object)((val2 is BoxCollider) ? val2 : null);
				stringBuilder.Append("  collider " + name + ((val3 != null) ? $" size {val3.size} centre {val3.center} trigger {((Collider)val3).isTrigger}" : "") + "\n");
			}
			if ((Object)(object)val.m_waterSurface != (Object)null)
			{
				Transform transform2 = ((Component)val.m_waterSurface).transform;
				MeshFilter component = ((Component)transform2).GetComponent<MeshFilter>();
				string[] obj = new string[5]
				{
					string.Format("surface '{0}' pos {1} (parent '{2}') lossy scale {3} layer {4}", ((Object)transform2).name, transform2.position, Object.op_Implicit((Object)(object)transform2.parent) ? ((Object)transform2.parent).name : "-", transform2.lossyScale, LayerMask.LayerToName(((Component)transform2).gameObject.layer)),
					null,
					null,
					null,
					null
				};
				object obj2;
				if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component.sharedMesh))
				{
					obj2 = "";
				}
				else
				{
					object[] array = new object[4];
					Bounds bounds = component.sharedMesh.bounds;
					array[0] = ((Bounds)(ref bounds)).center;
					bounds = component.sharedMesh.bounds;
					array[1] = ((Bounds)(ref bounds)).size;
					array[2] = component.sharedMesh.vertexCount;
					array[3] = component.sharedMesh.subMeshCount;
					obj2 = string.Format(" mesh centre {0} size {1}, {2} verts, {3} submesh(es)", array);
				}
				obj[1] = (string)obj2;
				obj[2] = "; components: ";
				obj[3] = string.Join(", ", from c in ((Component)transform2).GetComponents<Component>()
					select ((object)c).GetType().Name);
				obj[4] = "\n";
				stringBuilder.Append(string.Concat(obj));
				components = ((Component)transform2).GetComponents<Collider>();
				foreach (Collider val4 in components)
				{
					string name2 = ((object)val4).GetType().Name;
					BoxCollider val5 = (BoxCollider)(object)((val4 is BoxCollider) ? val4 : null);
					stringBuilder.Append("  surface collider " + name2 + ((val5 != null) ? $" size {val5.size} centre {val5.center} trigger {((Collider)val5).isTrigger}" : "") + "\n");
				}
			}
			return stringBuilder.ToString();
		}

		internal static float GroundHeight(float x, float z)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			float result = default(float);
			if (Heightmap.GetHeight(new Vector3(x, 0f, z), ref result))
			{
				return result;
			}
			if (WorldGenerator.instance == null)
			{
				return 0f;
			}
			return WorldGenerator.instance.GetHeight(x, z);
		}

		internal static int Reapply()
		{
			int num = 0;
			foreach (Tile item in Tiles.Values.ToList())
			{
				if (!((Object)(object)item.Volume == (Object)null))
				{
					if (!WaterAccess.Detect(item.Volume) || !WaterAccess.Setup(item.Volume))
					{
						break;
					}
					num++;
				}
			}
			return num;
		}

		internal static List<Lake> CurrentLakes()
		{
			return Lakes.Select((LakeRt r) => r.L).ToList();
		}

		private static Dictionary<string, LakeShape> KeptShapes()
		{
			Dictionary<string, LakeShape> dictionary = new Dictionary<string, LakeShape>();
			foreach (LakeRt lake in Lakes)
			{
				if (lake.Shape != null && lake.Shape.Done)
				{
					dictionary[lake.L.ToLine()] = lake.Shape;
				}
			}
			LakeBeds.Reset();
			return dictionary;
		}

		private static LakeShape KeptShape(Dictionary<string, LakeShape> kept, Lake l)
		{
			if (!kept.TryGetValue(l.ToLine(), out var value))
			{
				return null;
			}
			return value;
		}

		internal static void ReplaceLakes(List<Lake> lakes, string source)
		{
			Dictionary<string, LakeShape> shapes = KeptShapes();
			foreach (LakeRt lake in Lakes)
			{
				Drop(lake);
			}
			Lakes.Clear();
			_building = null;
			Lakes.AddRange(lakes.Select((Lake l) => new LakeRt
			{
				L = l,
				Shape = KeptShape(shapes, l)
			}));
			_streamTimer = 1f;
			MapLakes.Dirty = true;
			Log.LogInfo((object)$"{Lakes.Count} lake(s) from {source}");
		}

		private static void Changed()
		{
			SaveLakes();
			LakeSync.Broadcast(CurrentLakes());
			MapLakes.Dirty = true;
		}

		internal static void Notify(string text)
		{
			try
			{
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
				}
			}
			catch
			{
			}
		}

		internal static bool Forward(string line, out string reply)
		{
			reply = null;
			if (LakeSync.IsAuthority || (Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			ZNet.instance.RemoteCommand(line);
			reply = "tarn: sent '" + line + "' to the server (admins only); the new lakes arrive in a moment";
			return true;
		}

		internal static string Here(Vector3 p, float radius, float margin)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			if (Forward(string.Format(C, "tarn at {0:0.##} {1:0.##} {2:0.##} {3:0.###}", p.x, p.z, radius, margin), out var reply))
			{
				return reply;
			}
			Spill.Result result = Spill.Compute(GroundHeight, p.x, p.z, radius, 2f, margin);
			float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.m_waterLevel : 30f);
			if (result == null)
			{
				return "no spill found (unexpected)";
			}
			if (result.Level <= num + 0.2f)
			{
				return $"this hollow drains to the sea: it spills at {result.SpillHeight:0.0} m, the sea is {num:0.0} m";
			}
			if (result.WetCells == 0)
			{
				return $"you are not in a hollow: it would spill at {result.SpillHeight:0.0} m and the ground here is higher. Stand in the low part.";
			}
			int k = 1;
			while (Lakes.Any((LakeRt x) => x.L.Name == "lake" + k))
			{
				k++;
			}
			Lake lake = new Lake
			{
				Name = "lake" + k,
				X = p.x,
				Z = p.z,
				Level = result.Level,
				MinX = result.MinX,
				MinZ = result.MinZ,
				MaxX = result.MaxX,
				MaxZ = result.MaxZ,
				Depth = result.Depth
			};
			Lakes.Add(new LakeRt
			{
				L = lake
			});
			Changed();
			_streamTimer = 1f;
			return $"{lake.Name}: level {result.Level:0.00} m (spills at {result.SpillHeight:0.00}), deepest {result.Depth:0.0} m, " + $"{result.AreaM2:0} m2 wet. Saved to {LakeFile()}" + (ClearLakeBeds.Value ? ". Trees already standing here stay; zones generated from now on keep its bed clear" : "");
		}

		internal static string Remove(string name)
		{
			if (Forward("tarn remove " + name, out var reply))
			{
				return reply;
			}
			int num = 0;
			for (int num2 = Lakes.Count - 1; num2 >= 0; num2--)
			{
				if (!(name != "all") || !(Lakes[num2].L.Name != name))
				{
					if (_building?.Rt == Lakes[num2])
					{
						_building = null;
					}
					Drop(Lakes[num2]);
					Lakes.RemoveAt(num2);
					num++;
				}
			}
			Changed();
			return $"removed {num} lake(s)";
		}

		internal static string Reload()
		{
			if (Forward("tarn reload", out var reply))
			{
				return reply;
			}
			ConfigSync.ReloadFile("tarn reload");
			Dictionary<string, LakeShape> shapes = KeptShapes();
			foreach (LakeRt lake in Lakes)
			{
				Drop(lake);
			}
			Lakes.Clear();
			_building = null;
			Lakes.AddRange(from l in LoadLakes()
				select new LakeRt
				{
					L = l,
					Shape = KeptShape(shapes, l)
				});
			_streamTimer = 1f;
			MapLakes.Dirty = true;
			LakeSync.Broadcast(CurrentLakes());
			return $"{Lakes.Count} lake(s) read from {LakeFile()}; nearby ones rebuild now";
		}

		internal static string List(int max)
		{
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			if (Lakes.Count == 0)
			{
				return "no lakes in this world (" + LakeFile() + ")";
			}
			ListenerPosition(out var at);
			StringBuilder stringBuilder = new StringBuilder($"{Lakes.Count} lake(s), {Lakes.Count((LakeRt r) => r.Built && r.Failed == null)} built (nearest first):\n");
			foreach (LakeRt item in Lakes.OrderBy((LakeRt r) => r.DistanceTo(at)).Take(max))
			{
				stringBuilder.Append($"{item.L.Name}: ({item.L.X:0}, {item.L.Z:0}) level {item.L.Level:0.0}, {item.DistanceTo(at):0} m away, " + ((item.Failed != null) ? ("FAILED: " + item.Failed) : (item.Built ? ((_building?.Rt == item) ? "building" : $"built: {item.WetCells * 16} m2, {item.VisualTiles} tiles, {item.Boxes} boxes") : "not built")) + "\n");
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class Patch_Terminal
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			internal void <Postfix>b__0_0(ConsoleEventArgs args)
			{
				//IL_019a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0262: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help");
					CultureInfo invariantCulture = CultureInfo.InvariantCulture;
					switch (text)
					{
					case "at":
						if (args.Args.Length < 4)
						{
							args.Context.AddString("tarn at <x> <z> [radius] [margin]");
						}
						else
						{
							args.Context.AddString(Plugin.Here(new Vector3(float.Parse(args.Args[2], invariantCulture), 0f, float.Parse(args.Args[3], invariantCulture)), (args.Args.Length > 4) ? float.Parse(args.Args[4], invariantCulture) : 120f, (args.Args.Length > 5) ? float.Parse(args.Args[5], invariantCulture) : 0.3f));
						}
						break;
					case "here":
					{
						if ((Object)(object)Player.m_localPlayer == (Object)null)
						{
							args.Context.AddString("no local player");
							break;
						}
						float radius = ((args.Args.Length > 2) ? float.Parse(args.Args[2], invariantCulture) : 120f);
						float margin = ((args.Args.Length > 3) ? float.Parse(args.Args[3], invariantCulture) : 0.3f);
						args.Context.AddString(Plugin.Here(((Component)Player.m_localPlayer).transform.position, radius, margin));
						break;
					}
					case "depth":
						if (args.Args.Length > 2)
						{
							float num = float.Parse(args.Args[2], invariantCulture);
							if (Plugin.Forward(string.Format(invariantCulture, "tarn depth {0:0.###}", num), out var reply))
							{
								args.Context.AddString(reply);
								break;
							}
							Plugin.DepthScale.Value = num;
							int num2 = Plugin.Reapply();
							args.Context.AddString($"depth scale {Plugin.DepthScale.Value:0.##} (saved, sent to every player), {num2} lake volume(s) updated");
						}
						else
						{
							args.Context.AddString($"depth scale {ConfigSync.Get<float>(Plugin.DepthScale):0.##}" + (ConfigSync.HasServer((ConfigEntryBase)(object)Plugin.DepthScale) ? " (the server's)" : "") + ": the water shader sees each lake at that share of its real depth (colour, foam and waves reach full at 10 m). Try 0.2 (calm, green) to 1 (sea-like). tarn depth <value>");
						}
						break;
					case "list":
						args.Context.AddString(Plugin.List((args.Args.Length > 2) ? int.Parse(args.Args[2], invariantCulture) : 10));
						break;
					case "reload":
						args.Context.AddString(Plugin.Reload());
						break;
					case "remove":
						args.Context.AddString(Plugin.Remove((args.Args.Length > 2) ? args.Args[2] : ""));
						break;
					case "info":
						args.Context.AddString(Plugin.DescribeTemplate());
						break;
					case "clearbeds":
						args.Context.AddString(ClearBeds.Command((args.Args.Length > 2) ? args.Args[2] : "", args.Args.Length > 3 && args.Args[3].Equals("confirm", StringComparison.OrdinalIgnoreCase)));
						break;
					default:
						args.Context.AddString("tarn here [radius=120] [margin=0.3] - fill the hollow you stand in, up to just under its spill point\ntarn list [n] - the n nearest lakes (default 10) and whether they are built\ntarn depth [scale] - how deep lakes look (colour, foam, waves); live\ntarn reload - re-read this world's lake file (on the server)\ntarn at <x> <z> [radius] [margin] - tarn here at a point (a player's tarn here sends this to the server)\ntarn clearbeds <lake|all> [confirm] - count (then remove) trees, rocks and bushes already standing on lake beds\ntarn remove <name|all> | tarn info (the zone water template)");
						break;
					}
				}
				catch (Exception ex)
				{
					args.Context.AddString("tarn: " + ex.Message);
					Plugin.Log.LogWarning((object)ex.ToString());
				}
			}
		}

		private static void Postfix()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			try
			{
				object obj = <>c.<>9__0_0;
				if (obj == null)
				{
					ConsoleEvent val = delegate(ConsoleEventArgs args)
					{
						//IL_019a: Unknown result type (might be due to invalid IL or missing references)
						//IL_0262: Unknown result type (might be due to invalid IL or missing references)
						try
						{
							string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help");
							CultureInfo invariantCulture = CultureInfo.InvariantCulture;
							switch (text)
							{
							case "at":
								if (args.Args.Length < 4)
								{
									args.Context.AddString("tarn at <x> <z> [radius] [margin]");
								}
								else
								{
									args.Context.AddString(Plugin.Here(new Vector3(float.Parse(args.Args[2], invariantCulture), 0f, float.Parse(args.Args[3], invariantCulture)), (args.Args.Length > 4) ? float.Parse(args.Args[4], invariantCulture) : 120f, (args.Args.Length > 5) ? float.Parse(args.Args[5], invariantCulture) : 0.3f));
								}
								break;
							case "here":
								if ((Object)(object)Player.m_localPlayer == (Object)null)
								{
									args.Context.AddString("no local player");
								}
								else
								{
									float radius = ((args.Args.Length > 2) ? float.Parse(args.Args[2], invariantCulture) : 120f);
									float margin = ((args.Args.Length > 3) ? float.Parse(args.Args[3], invariantCulture) : 0.3f);
									args.Context.AddString(Plugin.Here(((Component)Player.m_localPlayer).transform.position, radius, margin));
								}
								break;
							case "depth":
								if (args.Args.Length > 2)
								{
									float num = float.Parse(args.Args[2], invariantCulture);
									if (Plugin.Forward(string.Format(invariantCulture, "tarn depth {0:0.###}", num), out var reply))
									{
										args.Context.AddString(reply);
									}
									else
									{
										Plugin.DepthScale.Value = num;
										int num2 = Plugin.Reapply();
										args.Context.AddString($"depth scale {Plugin.DepthScale.Value:0.##} (saved, sent to every player), {num2} lake volume(s) updated");
									}
								}
								else
								{
									args.Context.AddString($"depth scale {ConfigSync.Get<float>(Plugin.DepthScale):0.##}" + (ConfigSync.HasServer((ConfigEntryBase)(object)Plugin.DepthScale) ? " (the server's)" : "") + ": the water shader sees each lake at that share of its real depth (colour, foam and waves reach full at 10 m). Try 0.2 (calm, green) to 1 (sea-like). tarn depth <value>");
								}
								break;
							case "list":
								args.Context.AddString(Plugin.List((args.Args.Length > 2) ? int.Parse(args.Args[2], invariantCulture) : 10));
								break;
							case "reload":
								args.Context.AddString(Plugin.Reload());
								break;
							case "remove":
								args.Context.AddString(Plugin.Remove((args.Args.Length > 2) ? args.Args[2] : ""));
								break;
							case "info":
								args.Context.AddString(Plugin.DescribeTemplate());
								break;
							case "clearbeds":
								args.Context.AddString(ClearBeds.Command((args.Args.Length > 2) ? args.Args[2] : "", args.Args.Length > 3 && args.Args[3].Equals("confirm", StringComparison.OrdinalIgnoreCase)));
								break;
							default:
								args.Context.AddString("tarn here [radius=120] [margin=0.3] - fill the hollow you stand in, up to just under its spill point\ntarn list [n] - the n nearest lakes (default 10) and whether they are built\ntarn depth [scale] - how deep lakes look (colour, foam, waves); live\ntarn reload - re-read this world's lake file (on the server)\ntarn at <x> <z> [radius] [margin] - tarn here at a point (a player's tarn here sends this to the server)\ntarn clearbeds <lake|all> [confirm] - count (then remove) trees, rocks and bushes already standing on lake beds\ntarn remove <name|all> | tarn info (the zone water template)");
								break;
							}
						}
						catch (Exception ex2)
						{
							args.Context.AddString("tarn: " + ex2.Message);
							Plugin.Log.LogWarning((object)ex2.ToString());
						}
					};
					<>c.<>9__0_0 = val;
					obj = (object)val;
				}
				new ConsoleCommand("tarn", "Tarn lakes: tarn here | list [n] | depth [scale] | reload | remove <name|all> | clearbeds <lake|all> [confirm] | info", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("tarn consol