Decompiled source of Straumr v0.1.2

Straumr.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Riverheim;
using Riverheim.Configuration;
using Riverheim.Plugin;
using Riverheim.Plugin.Rendering;
using Riverheim.Rendering;
using Riverheim.Util.Splines;
using Riverheim.World;
using Riverheim.World.Rivers;
using Straumr.Api;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("StraumrServerTools")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Straumr")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Straumr")]
[assembly: AssemblyTitle("Straumr")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Straumr
{
	internal static class Atlas
	{
		internal static ConfigEntry<int> Port;

		internal static ConfigEntry<string> Hidden;

		internal const string KeyHeader = "X-Straumr-Key";

		internal const string Here = "@here";

		internal const string PlainWorld = "~valheim";

		private static volatile object hereFor;

		private static volatile string hereName;

		private static WorldGenerator hereMade;

		private const double ByeWait = 5.0;

		private const double Idle = 90.0;

		private static HttpListener listener;

		private static volatile int listening;

		private static volatile string key = "";

		private static volatile bool closing;

		private static readonly ConcurrentQueue<Action> mainThread = new ConcurrentQueue<Action>();

		private static readonly object gate = new object();

		private static readonly Stopwatch clock = new Stopwatch();

		private static readonly Stopwatch seen = new Stopwatch();

		private static string seed = "";

		private static string state = "idle";

		private static string step = "";

		private static string error;

		private static string preset = "";

		private static string json;

		private static CancellationTokenSource making;

		private static string KeyFile => Path.Combine(Paths.ConfigPath, "Straumr-atlas-key.txt");

		internal static void Bind(ConfigFile config)
		{
			Port = config.Bind<int>("Atlas", "Port", 8765, "The port on this computer the atlas serves its page on (http://localhost:<port>).");
			Hidden = config.Bind<string>("Atlas", "HiddenSeeds", "", "Seeds the atlas won't show (comma-separated): a world you'd rather keep a surprise. Write each as # and the first 16 characters of its SHA-256 (lowercase hex) so this file doesn't give the seed away; a plain seed works too.");
			DropKeyFile();
		}

		internal static string Start()
		{
			if (listener == null)
			{
				HttpListener l = new HttpListener();
				l.Prefixes.Add($"http://localhost:{Port.Value}/");
				l.Prefixes.Add($"http://127.0.0.1:{Port.Value}/");
				l.Start();
				listener = l;
				listening = Port.Value;
				key = NewKey();
				closing = false;
				seen.Restart();
				if (Application.isBatchMode)
				{
					File.WriteAllText(KeyFile, key);
				}
				Task.Run(() => Serve(l));
				Status.Write($"atlas: open at http://localhost:{listening}/");
			}
			if (Application.isBatchMode)
			{
				return $"atlas open at http://localhost:{listening}/ (no browser here: its key is in {KeyFile} while it runs)";
			}
			Application.OpenURL($"http://localhost:{listening}/#k={key}");
			return $"atlas open at http://localhost:{listening}/: type a seed there (`straumr atlas stop` closes it)";
		}

		internal static string Stop(string why)
		{
			HttpListener httpListener = listener;
			listener = null;
			key = "";
			closing = false;
			httpListener?.Close();
			DropKeyFile();
			Action result;
			while (mainThread.TryDequeue(out result))
			{
			}
			hereName = null;
			hereMade = null;
			lock (gate)
			{
				making?.Cancel();
				making = null;
				seed = "";
				state = "idle";
				step = "";
				error = null;
				preset = "";
				json = null;
				clock.Reset();
				CurrentField.AtlasDrop();
			}
			if (httpListener == null)
			{
				return "the atlas isn't open";
			}
			Status.Write("atlas: closed (" + why + ")");
			return "atlas closed";
		}

		private static void DropKeyFile()
		{
			try
			{
				File.Delete(KeyFile);
			}
			catch (IOException)
			{
			}
		}

		internal static void AllowHere(bool allowed)
		{
			hereFor = ((allowed && (Object)(object)ZNet.instance != (Object)null) ? WorldGenerator.instance : null);
		}

		internal static void Forget()
		{
			hereFor = null;
			hereName = null;
			ForgetHere();
		}

		private static void ForgetHere()
		{
			lock (gate)
			{
				if (seed == "@here")
				{
					making?.Cancel();
					making = null;
					seed = "";
					state = "idle";
					step = "";
					error = null;
					json = null;
					CurrentField.AtlasDrop();
				}
			}
			hereMade = null;
		}

		internal static void Pump()
		{
			if (listener == null)
			{
				return;
			}
			double totalSeconds = seen.Elapsed.TotalSeconds;
			if (closing && totalSeconds > 5.0)
			{
				Stop("its page was closed");
				return;
			}
			if (totalSeconds > 90.0 && !Application.isBatchMode)
			{
				Stop($"no word from its page for {90.0:0} s");
				return;
			}
			WorldGenerator val = (((Object)(object)ZNet.instance != (Object)null) ? WorldGenerator.instance : null);
			hereName = ((val != null && hereFor == val && (Object)(object)Player.m_localPlayer != (Object)null) ? ZNet.instance.GetWorldName() : null);
			Action result;
			while (mainThread.TryDequeue(out result))
			{
				result();
			}
			if (hereMade != null && (hereMade != val || hereFor != val))
			{
				ForgetHere();
			}
		}

		private static async Task Serve(HttpListener l)
		{
			while (l.IsListening)
			{
				HttpListenerContext context;
				try
				{
					context = await l.GetContextAsync();
				}
				catch (Exception)
				{
					break;
				}
				Task.Run(delegate
				{
					Handle(context);
				});
			}
		}

		private static void Handle(HttpListenerContext context)
		{
			HttpListenerResponse response = context.Response;
			try
			{
				HttpListenerRequest request = context.Request;
				if (!FromOurPage(request))
				{
					Send(response, 403, "text/plain", "the atlas answers only its own page, opened on this computer");
					return;
				}
				string absolutePath = request.Url.AbsolutePath;
				if (absolutePath == "/" || absolutePath == "/index.html")
				{
					string path = Path.Combine(Path.GetDirectoryName(typeof(Atlas).Assembly.Location) ?? "", "flowmap-viewer.html");
					if (File.Exists(path))
					{
						string text = File.ReadAllText(path);
						if (!text.TrimStart(Array.Empty<char>()).StartsWith("<!doctype", StringComparison.OrdinalIgnoreCase))
						{
							text = "<!doctype html><html lang=\"en\">" + text;
						}
						Send(response, 200, "text/html; charset=utf-8", text);
					}
					else
					{
						Send(response, 404, "text/plain", "flowmap-viewer.html should be next to Straumr.dll");
					}
					return;
				}
				if (!SameKey(request.Headers["X-Straumr-Key"]))
				{
					Send(response, 403, "text/plain", "not this atlas's key: open the atlas again with `straumr atlas`");
					return;
				}
				seen.Restart();
				closing = absolutePath == "/bye";
				NameValueCollection queryString = request.QueryString;
				string wanted = (queryString["seed"] ?? "").Trim();
				switch (absolutePath)
				{
				case "/atlas":
					Send(response, 200, "application/json", Report());
					break;
				case "/bye":
					Send(response, 200, "text/plain", "bye");
					break;
				case "/world":
					World(response, wanted);
					break;
				case "/detail":
					Detail(response, wanted, queryString["wx"], queryString["wz"]);
					break;
				default:
					Send(response, 404, "text/plain", "nothing here");
					break;
				}
			}
			catch (Exception ex)
			{
				try
				{
					Send(response, 500, "text/plain", ex.Message);
				}
				catch (Exception)
				{
				}
			}
		}

		private static bool FromOurPage(HttpListenerRequest request)
		{
			IPAddress iPAddress = request.RemoteEndPoint?.Address;
			if (iPAddress == null)
			{
				return false;
			}
			if (iPAddress.IsIPv4MappedToIPv6)
			{
				iPAddress = iPAddress.MapToIPv4();
			}
			if (!IPAddress.IsLoopback(iPAddress))
			{
				return false;
			}
			string text = request.Headers["Host"];
			string text2 = request.Headers["Origin"];
			if (text == null || !Ours(text))
			{
				return false;
			}
			if (text2 != null)
			{
				if (text2.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
				{
					return Ours(text2.Substring("http://".Length));
				}
				return false;
			}
			return true;
		}

		private static bool Ours(string authority)
		{
			int num = listening;
			string[] array = new string[2] { "localhost", "127.0.0.1" };
			foreach (string text in array)
			{
				if (string.Equals(authority, $"{text}:{num}", StringComparison.OrdinalIgnoreCase) || (num == 80 && string.Equals(authority, text, StringComparison.OrdinalIgnoreCase)))
				{
					return true;
				}
			}
			return false;
		}

		private static string NewKey()
		{
			byte[] array = new byte[16];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			StringBuilder stringBuilder = new StringBuilder(32);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		private static bool SameKey(string given)
		{
			string text = key;
			if (given == null || text.Length == 0 || given.Length != text.Length)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < text.Length; i++)
			{
				num |= given[i] ^ text[i];
			}
			return num == 0;
		}

		private static void World(HttpListenerResponse response, string wanted)
		{
			bool flag = true;
			string text = wanted;
			foreach (char c in text)
			{
				flag &= !char.IsControl(c);
			}
			if (wanted.Length == 0 || wanted.Length > 20 || !flag)
			{
				Send(response, 400, "application/json", "{\"error\":\"a seed, please\"}");
				return;
			}
			string text2 = (wanted.EndsWith("~valheim", StringComparison.Ordinal) ? wanted.Substring(0, wanted.Length - "~valheim".Length) : wanted);
			string b = Fingerprint(text2);
			string[] array = Hidden.Value.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i].Trim();
				if (text3.Length > 0 && (text3 == text2 || (text3.StartsWith("#") && string.Equals(text3.Substring(1), b, StringComparison.OrdinalIgnoreCase))))
				{
					Send(response, 403, "application/json", "{\"error\":\"that seed is kept a surprise\"}");
					return;
				}
			}
			if (wanted == "@here" && HereWorld() == null)
			{
				Send(response, 403, "application/json", "{\"error\":\"not in a world, or this world's map isn't yours to see (devcommands, or a server's admins)\"}");
				return;
			}
			string text4 = null;
			lock (gate)
			{
				if (seed == wanted && state == "ready")
				{
					text4 = json;
				}
				else
				{
					if (state == "generating" && seed != wanted)
					{
						Send(response, 409, "application/json", "{\"error\":" + CurrentField.Quote("still making " + seed) + "}");
						return;
					}
					if (seed != wanted || state != "generating")
					{
						making?.Cancel();
						making = new CancellationTokenSource();
						CancellationToken cancel = making.Token;
						seed = wanted;
						state = "generating";
						step = "starting";
						error = null;
						json = null;
						preset = "";
						clock.Restart();
						CurrentField.AtlasDrop();
						if (wanted == "@here")
						{
							mainThread.Enqueue(delegate
							{
								BeginHere(cancel);
							});
						}
						else
						{
							mainThread.Enqueue(delegate
							{
								Begin(wanted, cancel);
							});
						}
					}
				}
			}
			if (text4 != null)
			{
				Send(response, 200, "application/json", text4);
			}
			else
			{
				Send(response, 202, "application/json", Report());
			}
		}

		internal static string Fingerprint(string seed)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(seed));
			StringBuilder stringBuilder = new StringBuilder(16);
			for (int i = 0; i < 8; i++)
			{
				stringBuilder.Append(array[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		private static void Detail(HttpListenerResponse response, string wanted, string wx, string wz)
		{
			if (!int.TryParse(wx, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(wz, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || Math.Abs(result) > 42 || Math.Abs(result2) > 42)
			{
				Send(response, 400, "application/json", "{\"error\":\"wx and wz, please\"}");
				return;
			}
			if (wanted == "@here" && HereWorld() == null)
			{
				Send(response, 403, "application/json", "{\"error\":\"not in that world now\"}");
				return;
			}
			lock (gate)
			{
				if (seed != wanted || state != "ready")
				{
					Send(response, 409, "application/json", "{\"error\":\"that seed isn't made\"}");
					return;
				}
			}
			string text = CurrentField.AtlasDetail(result, result2);
			if (text == null)
			{
				Send(response, 409, "application/json", "{\"error\":\"that seed isn't made\"}");
			}
			else
			{
				Send(response, 200, "application/json", text);
			}
		}

		private static void Begin(string wanted, CancellationToken cancel)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			if (cancel.IsCancellationRequested)
			{
				return;
			}
			try
			{
				bool flag = wanted.EndsWith("~valheim", StringComparison.Ordinal);
				bool riverheim = RiverSources.HaveRiverheim && !flag;
				string text = (flag ? wanted.Substring(0, wanted.Length - "~valheim".Length) : wanted);
				World val = new World("Atlas " + text, text)
				{
					m_menu = true
				};
				if (flag)
				{
					RiverSources.Plain(val);
				}
				WorldGenerator wg = (WorldGenerator)AccessTools.Constructor(typeof(WorldGenerator), new Type[1] { typeof(World) }, false).Invoke(new object[1] { val });
				val.m_menu = false;
				MethodInfo pregenerate = AccessTools.Method(typeof(WorldGenerator), "Pregenerate", (Type[])null, (Type[])null);
				string name = (riverheim ? RiverSources.Preset(val) : "");
				lock (gate)
				{
					if (seed == wanted)
					{
						preset = name;
					}
				}
				if (!riverheim)
				{
					Step(wanted, "placing Valheim's rivers");
					pregenerate.Invoke(wg, null);
				}
				Task.Run(delegate
				{
					Finish(wanted, wg, pregenerate, name, riverheim, cancel);
				});
			}
			catch (Exception e)
			{
				Fail(wanted, e);
			}
		}

		private static string HereWorld()
		{
			return hereName;
		}

		private static void BeginHere(CancellationToken cancel)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			if (cancel.IsCancellationRequested)
			{
				return;
			}
			try
			{
				WorldGenerator wg = WorldGenerator.instance;
				string world = ZNet.instance.GetWorldName();
				string name = RiverSources.Preset(wg.m_world);
				Channel[] rivers = RiverSources.Current();
				Vector3 you = ((Component)Player.m_localPlayer).transform.position;
				float ocean = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.m_waterLevel : 30f);
				hereMade = wg;
				lock (gate)
				{
					if (seed == "@here")
					{
						preset = name;
					}
				}
				Task.Run(delegate
				{
					Made("@here", () => CurrentField.AtlasPrepare(wg, rivers, ocean, world, "@here", name, RiverSources.Name, you, cancel), rivers.Length, cancel);
				});
			}
			catch (Exception e)
			{
				Fail("@here", e);
			}
		}

		private static void Finish(string wanted, WorldGenerator wg, MethodInfo pregenerate, string name, bool riverheim, CancellationToken cancel)
		{
			try
			{
				if (riverheim)
				{
					Step(wanted, "Riverheim is building the world");
					pregenerate.Invoke(wg, null);
				}
				cancel.ThrowIfCancellationRequested();
				Step(wanted, "tracing the rivers");
				Channel[] rivers = RiverSources.For(wg);
				string from = (RiverSources.MadeByRiverheim(wg) ? "Riverheim's" : "Valheim's");
				cancel.ThrowIfCancellationRequested();
				Made(wanted, () => CurrentField.AtlasPrepare(wg, rivers, 30f, wanted, wanted, name, from, null, cancel), rivers.Length, cancel);
			}
			catch (Exception e)
			{
				Fail(wanted, e);
			}
		}

		private static void Made(string wanted, Func<string> make, int rivers, CancellationToken cancel)
		{
			try
			{
				Step(wanted, "working out the rivers' flow, the sea and the tides");
				string text = make();
				lock (gate)
				{
					if (seed != wanted || cancel.IsCancellationRequested)
					{
						return;
					}
					json = text;
					state = "ready";
					step = "";
					clock.Stop();
				}
				Status.Write($"atlas: {wanted} made in {clock.Elapsed.TotalSeconds:0} s ({rivers} rivers, {text.Length / 1024} KB)");
			}
			catch (Exception e)
			{
				Fail(wanted, e);
			}
		}

		private static void Step(string wanted, string what)
		{
			lock (gate)
			{
				if (seed == wanted)
				{
					step = what;
				}
			}
		}

		private static void Fail(string wanted, Exception e)
		{
			Exception baseException = e.GetBaseException();
			if (baseException is OperationCanceledException)
			{
				return;
			}
			lock (gate)
			{
				if (seed == wanted)
				{
					state = "failed";
					error = baseException.Message;
					clock.Stop();
				}
			}
			Status.Write($"atlas: {wanted} failed: {baseException}");
		}

		private static string Report()
		{
			lock (gate)
			{
				string[] obj = new string[21]
				{
					"{\"atlas\":true,\"version\":",
					CurrentField.Quote("0.1.2"),
					",\"rivers_from\":",
					CurrentField.Quote(RiverSources.Name),
					",\"here\":",
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				string text = HereWorld();
				obj[5] = ((text != null) ? CurrentField.Quote(text) : "null");
				obj[6] = ",\"worlds\":";
				obj[7] = (RiverSources.HaveRiverheim ? "[\"riverheim\",\"valheim\"]" : "[\"valheim\"]");
				obj[8] = ",\"preset\":";
				obj[9] = CurrentField.Quote(preset);
				obj[10] = ",\"state\":";
				obj[11] = CurrentField.Quote(state);
				obj[12] = ",\"seed\":";
				obj[13] = CurrentField.Quote(seed);
				obj[14] = ",\"elapsed\":";
				obj[15] = clock.Elapsed.TotalSeconds.ToString("0.0", CultureInfo.InvariantCulture);
				obj[16] = ",\"step\":";
				obj[17] = CurrentField.Quote(step);
				obj[18] = ",\"error\":";
				obj[19] = ((error == null) ? "null" : CurrentField.Quote(error));
				obj[20] = "}";
				return string.Concat(obj);
			}
		}

		private static void Send(HttpListenerResponse response, int status, string type, string body)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(body);
			response.StatusCode = status;
			response.ContentType = type;
			response.ContentLength64 = bytes.Length;
			response.OutputStream.Write(bytes, 0, bytes.Length);
			response.OutputStream.Close();
		}
	}
	internal static class BoatAnchor
	{
		private sealed class Ride
		{
			public bool Held;

			public Vector3 At;
		}

		internal static ConfigEntry<bool> Enabled;

		private const float Swing = 2.5f;

		private const float Brake = 0.8f;

		private const float Draw = 0.8f;

		private const float Pull = 0.04f;

		private static readonly ConditionalWeakTable<Ship, Ride> rides = new ConditionalWeakTable<Ship, Ride>();

		internal static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Currents", "AnchorEmptyBoats", true, "A boat left empty on the water (not moored) rides as if at anchor: it stops where it was left, may swing a couple of metres either way, and in a river turns to lie along the current. Someone boards it, and it's free again.");
		}

		[HarmonyPatch(typeof(Ship), "CustomFixedUpdate")]
		[HarmonyPostfix]
		private static void Hold(Ship __instance, float fixedDeltaTime, Rigidbody ___m_body, List<Player> ___m_players)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: 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_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: 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_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: 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_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			if (!Enabled.Value)
			{
				return;
			}
			Ride orCreateValue = rides.GetOrCreateValue(__instance);
			BoxCollider floatCollider = __instance.m_floatCollider;
			if ((Object)(object)___m_body == (Object)null || (Object)(object)floatCollider == (Object)null || !__instance.IsOwner() || CurrentPush.Aboard(__instance, ___m_players))
			{
				orCreateValue.Held = false;
				return;
			}
			Vector3 worldCenterOfMass = ___m_body.worldCenterOfMass;
			if (worldCenterOfMass.y - Floating.GetLiquidLevel(worldCenterOfMass, 1f, (LiquidType)10) - __instance.m_waterLevelOffset > __instance.m_disableLevel || CurrentPush.Moored(__instance, ___m_body, floatCollider))
			{
				orCreateValue.Held = false;
				return;
			}
			if (!orCreateValue.Held)
			{
				orCreateValue.Held = true;
				orCreateValue.At = worldCenterOfMass;
			}
			Vector3 linearVelocity = ___m_body.linearVelocity;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(linearVelocity.x, linearVelocity.z);
			val *= Mathf.Exp(-0.8f * fixedDeltaTime);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(worldCenterOfMass.x - orCreateValue.At.x, worldCenterOfMass.z - orCreateValue.At.z);
			float num = ((Vector2)(ref val2)).magnitude - 2.5f;
			if (num > 0f)
			{
				val -= val2 / ((Vector2)(ref val2)).magnitude * (0.8f * num * fixedDeltaTime);
			}
			___m_body.linearVelocity = new Vector3(val.x, linearVelocity.y, val.y);
			Vector3 forward = ((Component)__instance).transform.forward;
			if (!(forward.x * forward.x + forward.z * forward.z < 0.01f) && CurrentPush.Flow(worldCenterOfMass, out var current) && !(((Vector2)(ref current)).sqrMagnitude < 0.01f))
			{
				float num2 = Mathf.DeltaAngle(Mathf.Atan2(current.x, current.y) * 57.29578f, Mathf.Atan2(forward.x, forward.z) * 57.29578f) * ((float)Math.PI / 180f);
				float num3 = 0.04f * Math.Min(((Vector2)(ref current)).magnitude, 2f) * (1f - CurrentField.OpenAt(worldCenterOfMass));
				float y = ___m_body.angularVelocity.y;
				___m_body.angularVelocity += new Vector3(0f, ((0f - num3) * Mathf.Sin(2f * num2) - Mathf.Sqrt(2f * num3) * y) * fixedDeltaTime, 0f);
			}
		}
	}
	internal static class BoatSpeed
	{
		internal static ConfigEntry<bool> Enabled;

		private const float Every = 0.25f;

		private const float Smoothing = 1f;

		private const float Jump = 40f;

		private const int Lines = 3;

		private static TMP_Text label;

		private static readonly Vector3[] corners = (Vector3[])(object)new Vector3[4];

		private static float next;

		private static Ship ship;

		private static Rigidbody body;

		private static Vector3 lastAt;

		private static float lastTime = -1f;

		private static bool known;

		private static Vector2 overGround;

		private static Vector2 water;

		internal static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Hud", "BoatSpeed", true, "Client: under the minimap, while you're aboard a boat, its speed through the water and over the ground, and how much the current adds to it or takes from it along its heading.");
		}

		internal static void Update(float now)
		{
			Player localPlayer = Player.m_localPlayer;
			Ship val = ((Enabled.Value && (Object)(object)localPlayer != (Object)null) ? Aboard(localPlayer) : null);
			if (val != ship)
			{
				Follow(val);
			}
			if ((Object)(object)val != (Object)null && now >= next)
			{
				next = now + 0.25f;
				Measure();
				if (known && ((Object)(object)label != (Object)null || Make()))
				{
					label.text = Line();
				}
			}
			if (!((Object)(object)label == (Object)null))
			{
				bool flag = (Object)(object)val != (Object)null && known;
				if (((Component)label).gameObject.activeSelf != flag)
				{
					((Component)label).gameObject.SetActive(flag);
				}
				if (flag)
				{
					Place();
				}
			}
		}

		private static Ship Aboard(Player player)
		{
			Ship controlledShip = player.GetControlledShip();
			if ((Object)(object)controlledShip != (Object)null)
			{
				return controlledShip;
			}
			Ship localShip = Ship.GetLocalShip();
			if (!((Object)(object)localShip != (Object)null) || !localShip.IsPlayerInBoat(player))
			{
				return null;
			}
			return localShip;
		}

		private static void Follow(Ship boat)
		{
			ship = boat;
			body = (((Object)(object)boat != (Object)null) ? ((Component)boat).GetComponent<Rigidbody>() : null);
			lastTime = -1f;
			known = false;
			next = 0f;
		}

		private static void Measure()
		{
			//IL_0023: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: 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_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = (((Object)(object)body != (Object)null) ? body.worldCenterOfMass : ((Component)ship).transform.position);
			float fixedTime = Time.fixedTime;
			float num = fixedTime - lastTime;
			if (!(lastTime < 0f) && !(num > 2f))
			{
				if (num > 0f)
				{
					Vector3 val2 = val - lastAt;
					if (((Vector3)(ref val2)).magnitude > 40f * num)
					{
						goto IL_0070;
					}
				}
				if (!(num <= 0f))
				{
					Vector3 val3 = val - lastAt;
					lastAt = val;
					lastTime = fixedTime;
					Vector2 val4 = new Vector2(val3.x, val3.z) / num;
					Vector2 current;
					Vector2 val5 = (CurrentPush.Flow(val, out current) ? (current * Carry()) : Vector2.zero);
					if (!known)
					{
						overGround = val4;
						water = val5;
						known = true;
					}
					else
					{
						float num2 = 1f - Mathf.Exp((0f - num) / 1f);
						overGround += (val4 - overGround) * num2;
						water += (val5 - water) * num2;
					}
				}
				return;
			}
			goto IL_0070;
			IL_0070:
			lastAt = val;
			lastTime = fixedTime;
		}

		private static float Carry()
		{
			float value = CurrentPush.Boats.Value;
			if (!float.IsNaN(value) && !(value <= 0f))
			{
				return Mathf.Min(value, 3f);
			}
			return 0f;
		}

		private static string Line()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_00e5: Unknown result type (might be due to invalid IL or missing references)
			Vector3 forward = ((Component)ship).transform.forward;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(forward.x, forward.z);
			float magnitude = ((Vector2)(ref val)).magnitude;
			val = ((magnitude > 0.1f) ? (val * ((((int)ship.GetSpeedSetting() == 1) ? (-1f) : 1f) / magnitude)) : Vector2.zero);
			Vector2 val2 = overGround - water;
			float magnitude2 = ((Vector2)(ref val2)).magnitude;
			float magnitude3 = ((Vector2)(ref overGround)).magnitude;
			float num = Vector2.Dot(water, val);
			return string.Format(arg2: (((Vector2)(ref water)).magnitude < 0.05f) ? "no current" : ((num >= 0.05f) ? $"current {num:+0.0} (helping), running {Commands.Compass(water)}" : ((!(num <= -0.05f)) ? $"current {((Vector2)(ref water)).magnitude:0.0} across, running {Commands.Compass(water)}" : $"current {num:0.0} (against), running {Commands.Compass(water)}")), format: "Boat {0:0.0} m/s through water\n{1:0.0} m/s over ground\n{2}", arg0: magnitude2, arg1: magnitude3);
		}

		private static bool Make()
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: 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_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			Minimap instance = Minimap.instance;
			TMP_Text val = (((Object)(object)instance != (Object)null) ? instance.m_biomeNameSmall : null);
			if ((Object)(object)val == (Object)null || (Object)(object)instance.m_smallRoot == (Object)null || (Object)(object)instance.m_mapImageSmall == (Object)null)
			{
				return false;
			}
			GameObject obj = Object.Instantiate<GameObject>(((Component)val).gameObject, instance.m_smallRoot.transform);
			((Object)obj).name = "Straumr_BoatSpeed";
			Component component = obj.GetComponent("Animator");
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			label = obj.GetComponent<TMP_Text>();
			label.fontSize = val.fontSize * 0.6f;
			label.textWrappingMode = (TextWrappingModes)0;
			label.overflowMode = (TextOverflowModes)0;
			label.alignment = (TextAlignmentOptions)260;
			RectTransform val2 = (RectTransform)obj.transform;
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(0.5f, 0.5f);
			val2.anchorMax = val3;
			val2.anchorMin = val3;
			val2.pivot = new Vector2(1f, 1f);
			obj.SetActive(false);
			return true;
		}

		private static void Place()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			RectTransform rectTransform = ((Graphic)Minimap.instance.m_mapImageSmall).rectTransform;
			RectTransform val = (RectTransform)label.transform;
			RectTransform shown = TideClock.Shown;
			Rect rect = rectTransform.rect;
			val.sizeDelta = new Vector2(((Rect)(ref rect)).width * 2f, label.fontSize * 1.4f * 3f);
			if ((Object)(object)shown != (Object)null)
			{
				shown.GetWorldCorners(corners);
				((Transform)val).position = corners[3];
			}
			else
			{
				rectTransform.GetWorldCorners(corners);
				((Transform)val).position = corners[3] - new Vector3(0f, (corners[1].y - corners[0].y) * 0.03f, 0f);
			}
		}
	}
	[HarmonyPatch]
	internal static class BoatYaw
	{
		private sealed class Tied
		{
			public bool Value;

			public float At = -1f;
		}

		internal static ConfigEntry<float> Strength;

		private const float Pull = 0.02f;

		private static readonly ConditionalWeakTable<Ship, Tied> tied = new ConditionalWeakTable<Ship, Tied>();

		internal static void Bind(ConfigFile config)
		{
			Strength = config.Bind<float>("Currents", "BoatYaw", 1f, "A boat drifting free (someone aboard, no one at the helm, sail furled and oars shipped, not moored) slowly swings round to lie along the current, bow or stern downstream, whichever is nearer: at 1, a boat lying across a 1 m/s current comes round in about half a minute to a minute, quicker in faster water. Steering, under sail or oars, or moored, it is left alone. 0 = never, up to 3.");
		}

		internal static bool Moored(Ship ship, Rigidbody body, BoxCollider hull)
		{
			Tied orCreateValue = tied.GetOrCreateValue(ship);
			if (orCreateValue.At != Time.fixedTime)
			{
				orCreateValue.At = Time.fixedTime;
				orCreateValue.Value = CurrentPush.Moored(ship, body, hull);
			}
			return orCreateValue.Value;
		}

		[HarmonyPatch(typeof(Ship), "CustomFixedUpdate")]
		[HarmonyPostfix]
		[HarmonyPriority(800)]
		private static void Swing(Ship __instance, float fixedDeltaTime, Rigidbody ___m_body, List<Player> ___m_players)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_00a5: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: 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_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: 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)
			float num = Mathf.Clamp(Strength.Value, 0f, 3f);
			BoxCollider floatCollider = __instance.m_floatCollider;
			if (num > 0f && !((Object)(object)___m_body == (Object)null) && !((Object)(object)floatCollider == (Object)null) && __instance.IsOwner() && CurrentPush.Aboard(__instance, ___m_players) && (int)__instance.GetSpeedSetting() == 0 && (!((Object)(object)__instance.m_shipControlls != (Object)null) || !__instance.m_shipControlls.HaveValidUser()))
			{
				Vector3 worldCenterOfMass = ___m_body.worldCenterOfMass;
				Vector3 forward = ((Component)__instance).transform.forward;
				if (!(forward.x * forward.x + forward.z * forward.z < 0.01f) && CurrentPush.Flow(worldCenterOfMass, out var current) && !(((Vector2)(ref current)).sqrMagnitude < 0.01f) && !(worldCenterOfMass.y - Floating.GetLiquidLevel(worldCenterOfMass, 1f, (LiquidType)10) - __instance.m_waterLevelOffset > __instance.m_disableLevel) && !Moored(__instance, ___m_body, floatCollider))
				{
					float num2 = Mathf.DeltaAngle(Mathf.Atan2(current.x, current.y) * 57.29578f, Mathf.Atan2(forward.x, forward.z) * 57.29578f) * ((float)Math.PI / 180f);
					float num3 = num * 0.02f * Math.Min(((Vector2)(ref current)).magnitude, 2f) * (1f - CurrentField.OpenAt(worldCenterOfMass));
					float y = ___m_body.angularVelocity.y;
					float num4 = ((0f - num3) * Mathf.Sin(2f * num2) - Mathf.Sqrt(2f * num3) * y) * fixedDeltaTime;
					___m_body.angularVelocity += new Vector3(0f, num4, 0f);
				}
			}
		}
	}
	internal static class Commands
	{
		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		internal static class Register
		{
			[CompilerGenerated]
			private static class <>O
			{
				public static ConsoleEvent <0>__Run;
			}

			private static void Postfix()
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Expected O, but got Unknown
				object obj = <>O.<0>__Run;
				if (obj == null)
				{
					ConsoleEvent val = Run;
					<>O.<0>__Run = val;
					obj = (object)val;
				}
				new ConsoleCommand("straumr", "[here | report | selftest | waterlevel | perf | overlay | tide ... | races | map | waterstep ... | atlas] Straumr's water, for testing", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
		}

		internal static float TideHold = float.NaN;

		internal static float StrengthHold = float.NaN;

		private const string Usage = "straumr here | report | selftest | waterlevel | perf | overlay | tide [flood|ebb|high|low|spring|neap|auto|-1..1] | races | map | waterstep [cm ...|off] | atlas [stop]";

		private static readonly ConcurrentQueue<string> replies = new ConcurrentQueue<string>();

		internal static void Flush()
		{
			string result;
			while (replies.TryDequeue(out result))
			{
				Console instance = Console.instance;
				if (instance != null)
				{
					((Terminal)instance).AddString("Straumr: " + result);
				}
			}
		}

		internal static void Reset()
		{
			TideHold = float.NaN;
			StrengthHold = float.NaN;
		}

		private static void Run(ConsoleEventArgs args)
		{
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
			if (text == "atlas")
			{
				Atlas.AllowHere(Allowed(args.Context));
				try
				{
					args.Context.AddString((args.Length > 2 && args[2].ToLowerInvariant() == "stop") ? Atlas.Stop("`straumr atlas stop`") : Atlas.Start());
					return;
				}
				catch (Exception ex)
				{
					args.Context.AddString("atlas: couldn't start (" + ex.GetBaseException().Message + ")");
					return;
				}
			}
			if ((Object)(object)localPlayer == (Object)null || text.Length == 0)
			{
				args.Context.AddString("straumr here | report | selftest | waterlevel | perf | overlay | tide [flood|ebb|high|low|spring|neap|auto|-1..1] | races | map | waterstep [cm ...|off] | atlas [stop]");
				return;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			switch (text)
			{
			case "here":
				args.Context.AddString(Diagnostics.Here(position));
				return;
			case "report":
				try
				{
					args.Context.AddString("report written: " + Diagnostics.Report(position, Allowed(args.Context)));
					return;
				}
				catch (Exception ex2)
				{
					args.Context.AddString("report: couldn't write it (" + ex2.GetBaseException().Message + ")");
					return;
				}
			case "selftest":
				args.Context.AddString(Diagnostics.SelfTest(position));
				return;
			case "waterlevel":
				args.Context.AddString(Diagnostics.WaterLevel(position));
				return;
			case "perf":
				args.Context.AddString(Diagnostics.Perf());
				return;
			case "overlay":
				args.Context.AddString(FlowOverlay.Toggle());
				return;
			}
			if (!Allowed(args.Context))
			{
				args.Context.AddString("straumr " + text + ": devcommands first (or, on a server, be one of its admins)");
				return;
			}
			switch (text)
			{
			case "tide":
				args.Context.AddString(Tide((args.Length > 2) ? args[2].ToLowerInvariant() : ""));
				break;
			case "races":
				args.Context.AddString(CurrentField.Races(position, 4000f));
				break;
			case "map":
				CurrentField.ExportFlowMap(RiverSources.Current(), ZoneSystem.instance.m_waterLevel, replies.Enqueue);
				args.Context.AddString("writing the flow map in the background (10-30 s): BepInEx/config/Straumr-flowmap.json");
				break;
			case "waterstep":
			{
				string[] array = new string[Math.Max(0, args.Length - 2)];
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = args[i + 2];
				}
				args.Context.AddString(Diagnostics.WaterStep(position, array));
				break;
			}
			default:
				args.Context.AddString("straumr: no '" + text + "' (straumr here | report | selftest | waterlevel | perf | overlay | tide [flood|ebb|high|low|spring|neap|auto|-1..1] | races | map | waterstep [cm ...|off] | atlas [stop])");
				break;
			}
		}

		private static bool Allowed(Terminal terminal)
		{
			if (!terminal.IsCheatsEnabled())
			{
				if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
				{
					return ZNet.instance.LocalPlayerIsAdminOrHost();
				}
				return false;
			}
			return true;
		}

		private static string Tide(string to)
		{
			switch (to)
			{
			case "flood":
				TideHold = (float)Math.PI / 2f;
				break;
			case "slack":
			case "high":
				TideHold = (float)Math.PI;
				break;
			case "ebb":
				TideHold = 4.712389f;
				break;
			case "low":
				TideHold = 0f;
				break;
			case "spring":
				StrengthHold = 1f + TideTime.SpringNeap.Value;
				break;
			case "neap":
				StrengthHold = 1f - TideTime.SpringNeap.Value;
				break;
			case "auto":
			case "off":
				TideHold = float.NaN;
				StrengthHold = float.NaN;
				break;
			default:
			{
				if (!float.TryParse(to, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return "straumr tide flood | ebb | high | low | spring | neap | auto | a number from -1 (full ebb) to 1 (full flood)";
				}
				TideHold = Mathf.Asin(Mathf.Clamp(result, -1f, 1f));
				break;
			}
			case "":
				break;
			}
			if (float.IsNaN(TideHold) && !float.IsNaN(StrengthHold))
			{
				return string.Format("{0} tides held (x{1:0.00}) in your game; `straumr tide auto` lets them follow the moon again", (StrengthHold > 1f) ? "spring" : "neap", StrengthHold);
			}
			if (!float.IsNaN(TideHold))
			{
				return $"tide held at {TideName(Mathf.Sin(TideHold))} (stream {Mathf.Sin(TideHold):+0.00;-0.00}, open-sea level {(0f - TideLevels.Range.Value) * Mathf.Cos(TideHold):+0.0;-0.0} m) in your game; `straumr tide auto` lets it follow the clock again";
			}
			if ((Object)(object)EnvMan.instance == (Object)null)
			{
				return "the tide follows the time of day";
			}
			double days = TideTime.Days;
			float num = TideTime.Angle(days);
			float num2 = Mathf.Sin(num);
			float num3 = TideTime.Strength(days);
			bool flag = num % (float)Math.PI < (float)Math.PI / 2f;
			string text = ((!(num2 > 0f)) ? (flag ? "ebb building" : "ebb easing") : (flag ? "rising toward full flood" : "flood easing"));
			string text2 = TideTime.Kind(num3);
			string arg = ((text2 != null) ? (text2 + " tides") : "between springs and neaps");
			return $"the tide follows the moon: {TideName(num2)} at {Math.Abs(num2) * 100f:0}% ({text}); it turns in {TideTime.MinutesToTurn(num):0.0} min. " + $"{arg} (x{num3:0.00}); next spring tides in {TideTime.DaysToSpring(days):0.0} game days";
		}

		internal static string TideName(float phase)
		{
			if (!(Math.Abs(phase) < 0.15f))
			{
				if (!(phase > 0f))
				{
					return "ebb";
				}
				return "flood";
			}
			return "slack";
		}

		internal static string Compass(Vector2 v)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			string[] obj = new string[8] { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
			float num = Mathf.Atan2(v.x, v.y) * 57.29578f;
			return obj[((int)Math.Round(num / 45f) % 8 + 8) % 8];
		}
	}
	internal static class ConfigSync
	{
		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		internal static class Listen
		{
			private static void Postfix(ZNetPeer peer)
			{
				peer.m_rpc.Register<ZPackage>("Straumr_Settings", (Action<ZRpc, ZPackage>)Receive);
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		internal static class Send
		{
			private static void Postfix(ZNet __instance, ZRpc rpc)
			{
				if (__instance.IsServer())
				{
					rpc.Invoke("Straumr_Settings", new object[1] { Package() });
				}
			}
		}

		private const string Rpc = "Straumr_Settings";

		private static ConfigFile config;

		private static Dictionary<ConfigDefinition, string> held;

		private static Dictionary<ConfigDefinition, string> own;

		internal static bool Following => held != null;

		internal static void Init(ConfigFile file)
		{
			config = file;
		}

		internal static bool Shared(ConfigDefinition d)
		{
			if (!(d.Section == "Currents") && !(d.Section == "Stage") && (!(d.Section == "Flecks") || !d.Key.StartsWith("White") || !(d.Key != "WhiteWater")))
			{
				if (d.Section == "World")
				{
					return d.Key == "StandingRuins";
				}
				return false;
			}
			return true;
		}

		internal static IEnumerable<string> SharedValues()
		{
			return from e in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config
				where Shared(e.Key)
				orderby e.Key.Section, e.Key.Key
				select "[" + e.Key.Section + "] " + e.Key.Key + " = " + e.Value.GetSerializedValue();
		}

		internal static void Broadcast()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				peer.m_rpc.Invoke("Straumr_Settings", new object[1] { Package() });
			}
		}

		private static ZPackage Package()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			List<KeyValuePair<ConfigDefinition, ConfigEntryBase>> list = ((IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config).Where((KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => Shared(e.Key)).ToList();
			val.Write(list.Count);
			foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in list)
			{
				val.Write(item.Key.Section);
				val.Write(item.Key.Key);
				val.Write(item.Value.GetSerializedValue());
			}
			return val;
		}

		private static void Receive(ZRpc rpc, ZPackage pkg)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>();
			int num = pkg.ReadInt();
			for (int i = 0; i < num; i++)
			{
				string text = pkg.ReadString();
				string text2 = pkg.ReadString();
				string value = pkg.ReadString();
				ConfigDefinition val = new ConfigDefinition(text, text2);
				if (Shared(val))
				{
					dictionary[val] = value;
				}
			}
			if (own == null)
			{
				own = ((IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config).Where((KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => Shared(e.Key)).ToDictionary((KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => e.Key, (KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => e.Value.GetSerializedValue());
			}
			held = dictionary;
			Apply();
			Status.Write($"settings: following the server's ({dictionary.Count} shared)");
		}

		private static void Apply()
		{
			if (held == null)
			{
				return;
			}
			config.SaveOnConfigSet = false;
			foreach (KeyValuePair<ConfigDefinition, string> item in held)
			{
				if (config.ContainsKey(item.Key))
				{
					config[item.Key].SetSerializedValue(item.Value);
				}
			}
		}

		internal static void Reloaded()
		{
			if (held != null)
			{
				own = ((IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)config).Where((KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => Shared(e.Key)).ToDictionary((KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => e.Key, (KeyValuePair<ConfigDefinition, ConfigEntryBase> e) => e.Value.GetSerializedValue());
				Apply();
			}
		}

		internal static void Check()
		{
			if (held == null || (Object)(object)ZNet.instance != (Object)null)
			{
				return;
			}
			foreach (KeyValuePair<ConfigDefinition, string> item in own)
			{
				if (config.ContainsKey(item.Key))
				{
					config[item.Key].SetSerializedValue(item.Value);
				}
			}
			held = null;
			own = null;
			config.SaveOnConfigSet = true;
			config.Save();
			Status.Write("settings: back to our own");
		}
	}
	internal static class CreatureWater
	{
		internal static ConfigEntry<bool> FollowTide;

		internal static int Followed = -1;

		internal static int Sites;

		private static readonly MethodInfo level = AccessTools.Method(typeof(CreatureWater), "Level", (Type[])null, (Type[])null);

		private static readonly HashSet<string> patched = new HashSet<string>();

		private static readonly HashSet<string> told = new HashSet<string>();

		private static readonly OpCode[] locals = new OpCode[6]
		{
			OpCodes.Ldloc_0,
			OpCodes.Ldloc_1,
			OpCodes.Ldloc_2,
			OpCodes.Ldloc_3,
			OpCodes.Ldloc_S,
			OpCodes.Ldloc
		};

		private static readonly OpCode[] addresses = new OpCode[2]
		{
			OpCodes.Ldloca_S,
			OpCodes.Ldloca
		};

		internal static void Bind(ConfigFile config)
		{
			FollowTide = config.Bind<bool>("Currents", "CreaturesFollowTide", true, "Creatures find the sea's edge where the tide has it now, not at the sea's usual level: at high tide land animals keep out of the flooded beach, at low tide they wander the bared sand, and sea creatures, swimmers' and fish's paths, birds' landing spots and where creatures spawn follow it too. Works where players' games run the creatures (a dedicated server, which runs only those round the world's centre, keeps the usual level). Off = Valheim's fixed sea level for these checks (swimming and floating follow the tide either way).");
		}

		internal static float Level(Vector3 at)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (FollowTide == null || !FollowTide.Value)
			{
				return 30f;
			}
			return TideLevels.SurfaceAt(at);
		}

		internal static void Patch(Harmony harmony)
		{
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Expected O, but got Unknown
			Type type = AccessTools.Inner(typeof(Pathfinding), "AgentSettings");
			Type type2 = typeof(Vector3).MakeByRefType();
			(Type, string, Type[], string)[] array = new(Type, string, Type[], string)[8]
			{
				(typeof(BaseAI), "MoveToWater", new Type[2]
				{
					typeof(float),
					typeof(float)
				}, "MoveToWater"),
				(typeof(BaseAI), "Flee", new Type[2]
				{
					typeof(float),
					typeof(Vector3)
				}, "Flee"),
				(typeof(BaseAI), "IsValidRandomMovePoint", new Type[1] { typeof(Vector3) }, "RandomMove"),
				(typeof(Pathfinding), "SnapToNavMesh", new Type[3]
				{
					type2,
					typeof(bool),
					type
				}, "SwimDepth"),
				(typeof(SpawnSystem), "IsSpawnPointGood", new Type[2]
				{
					typeof(SpawnData),
					type2
				}, "SpawnAltitude"),
				(typeof(Fish), "GetWaterLevel", new Type[1] { typeof(Vector3) }, "FishWater"),
				(typeof(RandomFlyingBird), "FindLandingPoint", new Type[1] { type2 }, "BirdLanding"),
				(typeof(Raven), "FindSpawnPoint", new Type[2]
				{
					type2,
					typeof(GameObject).MakeByRefType()
				}, "RavenLanding")
			};
			List<string> list = new List<string>();
			(Type, string, Type[], string)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(Type, string, Type[], string) tuple = array2[i];
				string text = tuple.Item1.Name + "." + tuple.Item2;
				try
				{
					MethodInfo methodInfo = (tuple.Item3.Contains(null) ? null : AccessTools.Method(tuple.Item1, tuple.Item2, tuple.Item3, (Type[])null));
					if (methodInfo == null)
					{
						Tell(text, "not found");
					}
					else
					{
						harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(CreatureWater), tuple.Item4, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
					}
				}
				catch (Exception ex)
				{
					Tell(text, ex.Message);
				}
				if (!patched.Contains(text))
				{
					list.Add(text);
				}
			}
			Followed = array.Length - list.Count;
			Sites = array.Length;
			Status.Write($"creatures and the tide: {array.Length - list.Count} of {array.Length} checks follow it" + ((list.Count > 0) ? (" (not " + string.Join(", ", list) + ")") : ""));
		}

		private static IEnumerable<CodeInstruction> MoveToWater(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "BaseAI.MoveToWater", (List<CodeInstruction> c, int i) => (i < 2 || !ReadsY(c[i - 1])) ? null : Value(c[i - 2]));
		}

		private static IEnumerable<CodeInstruction> Flee(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "BaseAI.Flee", (List<CodeInstruction> c, int i) => (CodeInstruction[])(object)((i >= 2 && Calls(c[i - 1], "GetSolidHeight") && ((MethodInfo)c[i - 1].operand).ReturnType == typeof(float) && Reads(c[i - 2], "m_fleeTarget")) ? new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldfld, c[i - 2].operand)
			} : null));
		}

		private static IEnumerable<CodeInstruction> RandomMove(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "BaseAI.IsValidRandomMovePoint", (List<CodeInstruction> c, int i) => (CodeInstruction[])(object)((i >= 1 && locals.Contains(c[i - 1].opcode)) ? new CodeInstruction[1]
			{
				new CodeInstruction(OpCodes.Ldarg_1, (object)null)
			} : null));
		}

		private static IEnumerable<CodeInstruction> SwimDepth(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "Pathfinding.SnapToNavMesh", (List<CodeInstruction> c, int i) => (CodeInstruction[])(object)((i >= 1 && i + 2 < c.Count && CodeInstructionExtensions.IsLdarg(c[i - 1], (int?)1) && Reads(c[i + 2], "m_swimDepth")) ? new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_1, (object)null),
				new CodeInstruction(OpCodes.Ldobj, (object)typeof(Vector3))
			} : null));
		}

		private static IEnumerable<CodeInstruction> SpawnAltitude(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "SpawnSystem.IsSpawnPointGood", (List<CodeInstruction> c, int i) => (CodeInstruction[])(object)((i >= 2 && i + 1 < c.Count && ReadsY(c[i - 1]) && CodeInstructionExtensions.IsLdarg(c[i - 2], (int?)2) && c[i + 1].opcode == OpCodes.Sub) ? new CodeInstruction[2]
			{
				new CodeInstruction(OpCodes.Ldarg_2, (object)null),
				new CodeInstruction(OpCodes.Ldobj, (object)typeof(Vector3))
			} : null));
		}

		private static IEnumerable<CodeInstruction> FishWater(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "Fish.GetWaterLevel", (List<CodeInstruction> c, int i) => (CodeInstruction[])(object)((i + 1 < c.Count && c[i + 1].opcode == OpCodes.Ret) ? new CodeInstruction[1]
			{
				new CodeInstruction(OpCodes.Ldarg_1, (object)null)
			} : null));
		}

		private static IEnumerable<CodeInstruction> BirdLanding(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "RandomFlyingBird.FindLandingPoint", Probed);
		}

		private static IEnumerable<CodeInstruction> RavenLanding(IEnumerable<CodeInstruction> code)
		{
			return Rewrite(code, "Raven.FindSpawnPoint", Probed);
		}

		private static CodeInstruction[] Probed(List<CodeInstruction> c, int i)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			if (i < 1 || !locals.Contains(c[i - 1].opcode))
			{
				return null;
			}
			for (int num = i - 1; num >= Math.Max(0, i - 4); num--)
			{
				if (Calls(c[num], "GetSolidHeight"))
				{
					for (int num2 = num - 1; num2 >= Math.Max(0, num - 6); num2--)
					{
						if (Calls(c[num2], "get_instance"))
						{
							if (!locals.Contains(c[num2 + 1].opcode))
							{
								return null;
							}
							return (CodeInstruction[])(object)new CodeInstruction[1]
							{
								new CodeInstruction(c[num2 + 1].opcode, c[num2 + 1].operand)
							};
						}
					}
					return null;
				}
			}
			return null;
		}

		private static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, string site, Func<List<CodeInstruction>, int, CodeInstruction[]> position)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			try
			{
				int[] array = (from i in Enumerable.Range(0, code.Count)
					where Sea(code[i])
					select i).ToArray();
				CodeInstruction[] array2 = ((array.Length == 1) ? position(code, array[0]) : null);
				if (array2 == null)
				{
					Tell(site, (array.Length == 1) ? "its sea-level check isn't the code expected" : $"{array.Length} uses of the sea's level where one was expected");
					return code;
				}
				int num = array[0];
				array2[0].labels.AddRange(code[num].labels);
				array2[0].blocks.AddRange(code[num].blocks);
				List<CodeInstruction> result = code.Take(num).Concat(array2).Append(new CodeInstruction(OpCodes.Call, (object)level))
					.Concat(code.Skip(num + 1))
					.ToList();
				patched.Add(site);
				return result;
			}
			catch (Exception ex)
			{
				Tell(site, ex.Message);
				return code;
			}
		}

		private static bool Sea(CodeInstruction c)
		{
			if (c.opcode == OpCodes.Ldc_R4 && c.operand is float num)
			{
				return num == 30f;
			}
			return false;
		}

		private static bool ReadsY(CodeInstruction c)
		{
			if (c.opcode == OpCodes.Ldfld && c.operand is FieldInfo fieldInfo && fieldInfo.DeclaringType == typeof(Vector3))
			{
				return fieldInfo.Name == "y";
			}
			return false;
		}

		private static bool Reads(CodeInstruction c, string field)
		{
			if (c.opcode == OpCodes.Ldfld && c.operand is FieldInfo fieldInfo)
			{
				return fieldInfo.Name == field;
			}
			return false;
		}

		private static bool Calls(CodeInstruction c, string method)
		{
			if ((c.opcode == OpCodes.Call || c.opcode == OpCodes.Callvirt) && c.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(ZoneSystem))
			{
				return methodInfo.Name == method;
			}
			return false;
		}

		private static CodeInstruction[] Value(CodeInstruction local)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			if (!locals.Contains(local.opcode))
			{
				if (!addresses.Contains(local.opcode))
				{
					return null;
				}
				return (CodeInstruction[])(object)new CodeInstruction[2]
				{
					new CodeInstruction(local.opcode, local.operand),
					new CodeInstruction(OpCodes.Ldobj, (object)typeof(Vector3))
				};
			}
			return (CodeInstruction[])(object)new CodeInstruction[1]
			{
				new CodeInstruction(local.opcode, local.operand)
			};
		}

		private static void Tell(string site, string why)
		{
			if (told.Add(site))
			{
				Status.Write("creatures and the tide: " + site + " left as Valheim has it (" + why + "); it keeps the sea's usual level");
			}
		}
	}
	internal static class CurrentField
	{
		private struct Cell
		{
			public Vector2 River;

			public Vector2 Sea;

			public Vector2 Bank;

			public float Open;

			public float Wind;

			public float Depth;

			public float Size;

			public float Melt;

			public float Estuary;

			public int Line;

			public bool Cliff;
		}

		private sealed class Seam
		{
			public float Head;

			public float Offset;

			public float Length;

			public float Shear;

			public float Speed;
		}

		private sealed class Jet
		{
			public float X;

			public float Z;

			public float DX;

			public float DZ;

			public float Half;

			public float Reach;

			public float Speed;
		}

		private struct Point
		{
			public float X;

			public float Z;

			public float DX;

			public float DZ;

			public float Along;

			public float HalfWidth;

			public float MeanDepth;

			public float Scale;

			public float Speed;

			public float Head;

			public float Curve;

			public float Phase;

			public int River;

			public int Next;

			public bool Dry;

			public bool Lake;

			public bool Lagoon;

			public bool Shadowed;

			public bool Seaward;

			public bool Turned;

			public bool Linked;

			public bool Fed;

			public float Drift;

			public float ByWater;
		}

		private sealed class Field
		{
			public Point[] Points;

			public Dictionary<long, List<int>> Grid;

			public float Ocean;

			public Seam[][] Seams = new Seam[0][];

			public Jet[] Jets = new Jet[0];

			public Dictionary<long, List<int>> JetGrid = new Dictionary<long, List<int>>();

			public float[] Melt = new float[0];

			public Func<float, float, float> Generated;

			public readonly ConcurrentDictionary<long, float[]> Edited = new ConcurrentDictionary<long, float[]>();

			public readonly Dictionary<long, int> EditsSeen = new Dictionary<long, int>();

			public readonly ConcurrentQueue<long> Dirty = new ConcurrentQueue<long>();

			public readonly ConcurrentDictionary<long, Cell[]> Tiles = new ConcurrentDictionary<long, Cell[]>();

			public volatile OceanCurrents Sea;

			public volatile Whirlpools Pools;

			public volatile bool SeaDone;

			public int Seed;

			public readonly BlockingCollection<long> Queue = new BlockingCollection<long>();

			public WorldGenerator World;

			public volatile bool Ready;

			public string Settings;

			public readonly Dictionary<long, Window> Windows = new Dictionary<long, Window>();

			public float Ground(float x, float z)
			{
				int num = (int)Math.Floor((x + 32f) / 64f);
				int num2 = (int)Math.Floor((z + 32f) / 64f);
				if (!Edited.TryGetValue(Key(num, num2), out var value))
				{
					return Generated(x, z);
				}
				float num3 = Mathf.Clamp(x - ((float)num * 64f - 32f), 0f, 63.999f);
				float num4 = Mathf.Clamp(z - ((float)num2 * 64f - 32f), 0f, 63.999f);
				int num5 = (int)num3;
				int num6 = (int)num4;
				float num7 = num3 - (float)num5;
				float num8 = num4 - (float)num6;
				float num9 = Mathf.Lerp(value[num6 * 65 + num5], value[num6 * 65 + num5 + 1], num7);
				float num10 = Mathf.Lerp(value[(num6 + 1) * 65 + num5], value[(num6 + 1) * 65 + num5 + 1], num7);
				return Mathf.Lerp(num9, num10, num8);
			}

			public float Lowest(float x, float z, float half)
			{
				int x2 = (int)Math.Floor((x + 32f) / 64f);
				int z2 = (int)Math.Floor((z + 32f) / 64f);
				if (Edited.IsEmpty || !Edited.ContainsKey(Key(x2, z2)))
				{
					return Ground(x, z);
				}
				float num = Ground(x, z);
				for (float num2 = 0.5f - half; num2 < half; num2 += 1f)
				{
					for (float num3 = 0.5f - half; num3 < half; num3 += 1f)
					{
						float num4 = Ground(x + num3, z + num2);
						if (num4 < num && num4 < Generated(x + num3, z + num2) - 0.2f)
						{
							num = num4;
						}
					}
				}
				return num;
			}
		}

		private sealed class Window
		{
			public int X0;

			public int Z0;

			public float[] Depth;

			public float[] Phi;

			public float[] Dist;

			public float[] T;

			public int[] Near;

			public bool[] Held;

			public float[] Shore;

			public long Used;
		}

		private sealed class SeaReach
		{
			private const float Cell = 64f;

			private const float Extent = 10560f;

			private readonly int n;

			private readonly float[] dist;

			private SeaReach(int n, float[] dist)
			{
				this.n = n;
				this.dist = dist;
			}

			internal static SeaReach Build(Field f)
			{
				int n = 330;
				bool[] wide = new bool[n * n];
				Parallel.For(0, n, delegate(int row)
				{
					for (int i = 0; i < n; i++)
					{
						float num20 = -10560f + ((float)i + 0.5f) * 64f;
						float num21 = -10560f + ((float)row + 0.5f) * 64f;
						float num22 = f.Ocean - 1f;
						wide[row * n + i] = num20 * num20 + num21 * num21 > 108160000f || (f.Generated(num20, num21) < num22 && f.Generated(num20 + 20f, num21) < num22 && f.Generated(num20 - 20f, num21) < num22 && f.Generated(num20, num21 + 20f) < num22 && f.Generated(num20, num21 - 20f) < num22);
					}
				});
				bool[] sea = new bool[n * n];
				Stack<int> stack = new Stack<int>();
				for (int num = 0; num < n; num++)
				{
					Reach(num);
					Reach((n - 1) * n + num);
					Reach(num * n);
					Reach(num * n + n - 1);
				}
				while (stack.Count > 0)
				{
					int num2 = stack.Pop();
					int num3 = num2 % n;
					int num4 = num2 / n;
					if (num3 > 0)
					{
						Reach(num2 - 1);
					}
					if (num3 < n - 1)
					{
						Reach(num2 + 1);
					}
					if (num4 > 0)
					{
						Reach(num2 - n);
					}
					if (num4 < n - 1)
					{
						Reach(num2 + n);
					}
				}
				float[] array = new float[n * n];
				Heap heap = new Heap();
				for (int num5 = 0; num5 < n * n; num5++)
				{
					array[num5] = float.PositiveInfinity;
					if (!sea[num5])
					{
						continue;
					}
					int num6 = num5 % n;
					int num7 = num5 / n;
					bool flag = true;
					for (int num8 = -2; num8 <= 2 && flag; num8++)
					{
						for (int num9 = -2; num9 <= 2 && flag; num9++)
						{
							int num10 = num6 + num9;
							int num11 = num7 + num8;
							flag = num10 < 0 || num11 < 0 || num10 >= n || num11 >= n || sea[num11 * n + num10];
						}
					}
					if (flag)
					{
						array[num5] = 0f;
						heap.Push(0f, num5);
					}
				}
				while (heap.Count > 0)
				{
					float key;
					int num12 = heap.Pop(out key);
					if (key > array[num12])
					{
						continue;
					}
					int num13 = num12 % n;
					int num14 = num12 / n;
					for (int num15 = -1; num15 <= 1; num15++)
					{
						for (int num16 = -1; num16 <= 1; num16++)
						{
							int num17 = num13 + num16;
							int num18 = num14 + num15;
							if ((num16 != 0 || num15 != 0) && num17 >= 0 && num18 >= 0 && num17 < n && num18 < n && sea[num18 * n + num17])
							{
								float num19 = key + ((num16 != 0 && num15 != 0) ? 1.4142f : 1f) * 64f;
								if (num19 < array[num18 * n + num17])
								{
									array[num18 * n + num17] = num19;
									heap.Push(num19, num18 * n + num17);
								}
							}
						}
					}
				}
				return new SeaReach(n, array);
				void Reach(int c)
				{
					if (wide[c] && !sea[c])
					{
						sea[c] = true;
						stack.Push(c);
					}
				}
			}

			internal bool Open(float x, float z)
			{
				int num = (int)Math.Floor((x + 10560f) / 64f);
				int num2 = (int)Math.Floor((z + 10560f) / 64f);
				if (num >= 0 && num2 >= 0 && num < n && num2 < n)
				{
					return dist[num2 * n + num] == 0f;
				}
				return false;
			}

			internal float At(float x, float z)
			{
				int num = (int)Math.Floor((x + 10560f) / 64f);
				int num2 = (int)Math.Floor((z + 10560f) / 64f);
				float num3 = float.PositiveInfinity;
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						int num4 = num + j;
						int num5 = num2 + i;
						if (num4 >= 0 && num5 >= 0 && num4 < n && num5 < n && !float.IsInfinity(dist[num5 * n + num4]))
						{
							float num6 = -10560f + ((float)num4 + 0.5f) * 64f - x;
							float num7 = -10560f + ((float)num5 + 0.5f) * 64f - z;
							num3 = Math.Min(num3, dist[num5 * n + num4] + Mathf.Sqrt(num6 * num6 + num7 * num7));
						}
					}
				}
				return num3;
			}
		}

		private sealed class WaterReach
		{
			private const float Cell = 16f;

			private const float Extent = 10560f;

			private readonly int n;

			private readonly float[] dist;

			private WaterReach(int n, float[] dist)
			{
				this.n = n;
				this.dist = dist;
			}

			internal static WaterReach Build(Field f, SeaReach sea)
			{
				int n = 1320;
				bool[] wet = new bool[n * n];
				Parallel.For(0, n, delegate(int row)
				{
					for (int i = 0; i < n; i++)
					{
						float num10 = -10560f + ((float)i + 0.5f) * 16f;
						float num11 = -10560f + ((float)row + 0.5f) * 16f;
						wet[row * n + i] = num10 * num10 + num11 * num11 > 108160000f || f.Generated(num10, num11) < f.Ocean - 0.2f;
					}
				});
				float[] array = new float[n * n];
				Heap heap = new Heap();
				for (int num = 0; num < n * n; num++)
				{
					array[num] = float.PositiveInfinity;
					if (wet[num] && sea.Open(-10560f + ((float)(num % n) + 0.5f) * 16f, -10560f + ((float)(num / n) + 0.5f) * 16f))
					{
						array[num] = 0f;
						heap.Push(0f, num);
					}
				}
				while (heap.Count > 0)
				{
					float key;
					int num2 = heap.Pop(out key);
					if (key > array[num2])
					{
						continue;
					}
					int num3 = num2 % n;
					int num4 = num2 / n;
					for (int num5 = -1; num5 <= 1; num5++)
					{
						for (int num6 = -1; num6 <= 1; num6++)
						{
							int num7 = num3 + num6;
							int num8 = num4 + num5;
							if ((num6 != 0 || num5 != 0) && num7 >= 0 && num8 >= 0 && num7 < n && num8 < n && wet[num8 * n + num7] && (num6 == 0 || num5 == 0 || wet[num4 * n + num7] || wet[num8 * n + num3]))
							{
								float num9 = key + ((num6 != 0 && num5 != 0) ? 1.4142f : 1f) * 16f;
								if (num9 < array[num8 * n + num7])
								{
									array[num8 * n + num7] = num9;
									heap.Push(num9, num8 * n + num7);
								}
							}
						}
					}
				}
				return new WaterReach(n, array);
			}

			internal float At(float x, float z)
			{
				int num = (int)Math.Floor((x + 10560f) / 16f);
				int num2 = (int)Math.Floor((z + 10560f) / 16f);
				float num3 = float.PositiveInfinity;
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						int num4 = num + j;
						int num5 = num2 + i;
						if (num4 >= 0 && num5 >= 0 && num4 < n && num5 < n && !float.IsInfinity(dist[num5 * n + num4]))
						{
							float num6 = -10560f + ((float)num4 + 0.5f) * 16f - x;
							float num7 = -10560f + ((float)num5 + 0.5f) * 16f - z;
							num3 = Math.Min(num3, dist[num5 * n + num4] + Mathf.Sqrt(num6 * num6 + num7 * num7));
						}
					}
				}
				return num3;
			}
		}

		private sealed class Heap
		{
			private float[] keys = new float[64];

			private int[] values = new int[64];

			public int Count;

			public void Push(float key, int value)
			{
				if (Count == keys.Length)
				{
					Array.Resize(ref keys, Count * 2);
					Array.Resize(ref values, Count * 2);
				}
				int num = Count++;
				while (num > 0)
				{
					int num2 = num - 1 >> 1;
					if (keys[num2] <= key)
					{
						break;
					}
					keys[num] = keys[num2];
					values[num] = values[num2];
					num = num2;
				}
				keys[num] = key;
				values[num] = value;
			}

			public int Pop(out float key)
			{
				key = keys[0];
				int result = values[0];
				if (--Count > 0)
				{
					float num = keys[Count];
					int num2 = values[Count];
					int num3 = 0;
					while (true)
					{
						int num4 = 2 * num3 + 1;
						if (num4 >= Count)
						{
							break;
						}
						if (num4 + 1 < Count && keys[num4 + 1] < keys[num4])
						{
							num4++;
						}
						if (keys[num4] >= num)
						{
							break;
						}
						keys[num3] = keys[num4];
						values[num3] = values[num4];
						num3 = num4;
					}
					keys[num3] = num;
					values[num3] = num2;
				}
				return result;
			}
		}

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> Speed;

		internal static ConfigEntry<float> MaxSpeed;

		internal static ConfigEntry<float> OceanSpeed;

		internal static ConfigEntry<float> WindDrift;

		internal static ConfigEntry<float> StormDrift;

		internal static ConfigEntry<float> Tides;

		internal static ConfigEntry<float> Rips;

		internal static ConfigEntry<float> Longshore;

		internal static ConfigEntry<float> VanillaSpeed;

		internal static ConfigEntry<bool> Bends;

		internal static ConfigEntry<bool> Seams;

		internal static ConfigEntry<bool> Riffles;

		internal static ConfigEntry<bool> Boils;

		internal static ConfigEntry<bool> Windrows;

		private const float TileSize = 64f;

		private const float CellSize = 4f;

		private const float GridCell = 64f;

		internal const float EstuaryShare = 0.6f;

		private const float SectionStep = 2f;

		private const float SectionReach = 80f;

		private const float JoinDistance = 12f;

		private const float NearReach = 200f;

		private const float LineHold = 3f;

		private const float Against = 2f;

		private const float HandOffReach = 600f;

		private const float HandOffCell = 12f;

		private const float FarSpeed = 0.3f;

		private const float DriftShare = 0.3f;

		private const int Cells = 16;

		private const int WindowTiles = 4;

		private const int Margin = 40;

		private const int WindowCells = 144;

		private static Field field;

		internal static volatile float Clock;

		internal static volatile float TidePhase;

		internal static volatile float TideAngle;

		internal static volatile float TideStrength = 1f;

		private static volatile string settings;

		private const long Invalidate = long.MinValue;

		private static readonly ConcurrentDictionary<long, float> groundReadAt = new ConcurrentDictionary<long, float>();

		private static readonly FieldRef<TerrainComp, int> editCount = AccessTools.FieldRefAccess<TerrainComp, int>("m_operations");

		private static Whirlpools spotsOf;

		private static Vector3[] spots;

		private const int Band = 16;

		private const int CoreCells = 64;

		private const float MapOrigin = -10500f;

		private static readonly string[] BiomeNames = new string[9] { "Ocean", "Meadows", "Black Forest", "Swamp", "Mountain", "Plains", "Mistlands", "Ashlands", "Deep North" };

		private static Field atlas;

		private static readonly object atlasBuild = new object();

		private static Field Live
		{
			get
			{
				Field field = CurrentField.field;
				if (field == null || field.World != WorldGenerator.instance)
				{
					return null;
				}
				return field;
			}
		}

		internal static string Tag => Settings();

		internal static bool SeaPending
		{
			get
			{
				Field live = Live;
				if (live != null)
				{
					return !live.SeaDone;
				}
				return false;
			}
		}

		internal static OceanCurrents SeaNow
		{
			get
			{
				Field live = Live;
				if (live == null)
				{
					return null;
				}
				return live.Sea;
			}
		}

		internal static object Token => Live;

		internal static bool Ready
		{
			get
			{
				Field live = Live;
				if (live == null)
				{
					return false;
				}
				return live.Ready;
			}
		}

		internal static string Baked => Live?.Settings ?? Settings();

		private static string Settings()
		{
			return settings ?? (settings = $"physics-36|{SeaWanted()}|{OceanSpeed.Value}|{Bends.Value}|{Riffles.Value}|{VanillaSpeed.Value}");
		}

		internal static bool For(WorldGenerator world)
		{
			if (field != null && field.World == world)
			{
				return field.Settings == Settings();
			}
			return false;
		}

		internal static void Bind(ConfigFile config)
		{
			config.SettingChanged += delegate
			{
				settings = null;
			};
			config.ConfigReloaded += delegate
			{
				settings = null;
			};
			Enabled = config.Bind<bool>("Currents", "Enabled", true, "Straumr's currents, on any world: the rivers' flow, the sea's gyres and tidal streams, whirlpools, surf and wind drift, and carrying swimmers, boats and floating things with them. Off, that water lies still (the tide still rises and falls, see TideRange; Rivers and Lakes' lakes keep the streams pouring in and the wind's drift).");
			Bends = config.Bind<bool>("Currents", "Bends", true, "Round a bend the surface water drifts to the outside bank (helical flow): floating things and swimmers are carried outward.");
			Seams = config.Bind<bool>("Currents", "Seams", true, "Where a side river joins, the seam between the two waters rolls into drifting swirls and gathers foam along it.");
			VanillaSpeed = config.Bind<float>("Currents", "VanillaSpeed", 0.7f, "On worlds without Riverheim: how fast Valheim's own rivers run, times the usual (they're lazy channels between lakes).");
			Riffles = config.Bind<bool>("Currents", "Riffles", true, "Flowing rivers alternate shallow, fast riffles (whiter) and deep, slow pools, a riffle every six widths.");
			Boils = config.Bind<bool>("Currents", "Boils", true, "Big, deep, fast rivers well up in boils now and then: domes of water spreading outward from a spot.");
			Windrows = config.Bind<bool>("Currents", "Windrows", true, "In a steady wind, foam on open water gathers in lines along the wind (Langmuir circulation).");
			Rips = config.Bind<float>("Currents", "Rips", 0.8f, "Rip currents off open beaches when the wind blows onshore: narrow jets out to sea that come and go (m/s in a gale; 0 = none). Swim along the beach to get out of one.");
			Longshore = config.Bind<float>("Currents", "Longshore", 0.5f, "The surf's current along open beaches when waves come in at an angle (a share of the full surf-zone formula; 0 = none).");
			Tides = config.Bind<float>("Currents", "Tides", 1.5f, "Tidal streams: the narrow sea channels flood and ebb twice a tidal day (a little later each day; stronger at spring tides, see TidalMonth), fastest where a narrow mouth feeds a big bay (m/s at the strongest; real straits run 1-3, Saltstraumen 10; 0 = none).");
			MaxSpeed = config.Bind<float>("Currents", "MaxSpeed", 3f, "Rivers never run faster than this, however narrow (m/s; real rapids are about 3).");
			OceanSpeed = config.Bind<float>("Currents", "OceanSpeed", 0.8f, "The typical open-sea current (m/s): wind-driven gyres, seeded from the world, along the coasts and through straits. 0 = none.");
			StormDrift = config.Bind<float>("Currents", "StormDrift", 0.3f, "Extra surface drift in a gale (m/s at full wind), on the same water as WindDrift but growing with the wind squared: a breeze barely adds any, a storm pushes hard (0.8 m/s with WindDrift at full wind). 0 = none.");
			WindDrift = config.Bind<float>("Currents", "WindDrift", 0.5f, "Surface drift downwind at full wind (m/s), on roomy slack water: the sea, a lake's middle, a broad slow reach (not a running channel). 0 = none.");
			Speed = config.Bind<float>("Currents", "Speed", 1.2f, "The typical river's mean current (m/s; real lowland rivers run about 1, and you swim about 2). Great rivers run a little faster than creeks, narrows faster and broad reaches and lakes slower; deep water mid-channel runs faster than the mean, the banks slower.");
		}

		internal static long Key(int x, int z)
		{
			return ((long)x << 32) | (uint)z;
		}

		private static bool SeaWanted()
		{
			if (!(OceanSpeed.Value > 0f) && !(Tides.Value > 0f))
			{
				return TideLevels.Range.Value > 0f;
			}
			return true;
		}

		internal static void Start(Channel[] rivers, float oceanLevel)
		{
			field?.Queue.CompleteAdding();
			WorldGenerator wg = WorldGenerator.instance;
			Field f = new Field
			{
				Ocean = oceanLevel,
				Generated = (float x, float z) => wg.GetHeight(x, z),
				World = wg,
				Settings = Settings(),
				Seed = wg.GetSeed()
			};
			field = f;
			Thread thread = new Thread((ThreadStart)delegate
			{
				Run(f, rivers);
			});
			thread.IsBackground = true;
			thread.Name = "Straumr currents";
			thread.Start();
		}

		internal static void Stop()
		{
			Field obj = field;
			field = null;
			obj?.Queue.CompleteAdding();
			spotsOf = null;
			spots = null;
			TideAngle = 0f;
			TidePhase = 0f;
			TideStrength = 1f;
		}

		internal static void Survey(Channel[] rivers, float oceanLevel)
		{
			WorldGenerator wg = WorldGenerator.instance;
			Field f = new Field
			{
				Ocean = oceanLevel,
				Generated = (float x, float z) => wg.GetHeight(x, z),
				World = wg,
				Settings = Settings(),
				Seed = wg.GetSeed()
			};
			Task.Run((Action)delegate
			{
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
				//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					Status.Write("currents survey: " + BuildPoints(f, rivers));
					Stopwatch stopwatch = Stopwatch.StartNew();
					OceanCurrents oceanCurrents = OceanCurrents.Solve(f.Generated, f.Ocean, 10500f, f.Seed);
					long elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
					List<(float, float, float)> list = new List<(float, float, float)>();
					int num = 0;
					int num2 = 0;
					Vector2 val;
					for (float num3 = -10000f; num3 <= 10000f; num3 += 96f)
					{
						for (float num4 = -10000f; num4 <= 10000f; num4 += 96f)
						{
							if (!(num3 * num3 + num4 * num4 > 100000000f) && !(f.Generated(num3, num4) > f.Ocean - 1f))
							{
								num2++;
								val = oceanCurrents.TideAt(num3, num4);
								float num5 = ((Vector2)(ref val)).magnitude * Tides.Value;
								if (num5 > 0.2f)
								{
									num++;
								}
								list.Add((num5, num3, num4));
							}
						}
					}
					List<(float, float, float)> source = list.OrderByDescending<(float, float, float), float>(((float v, float x, float z) r) => r.v).Aggregate<(float, float, float), List<(float, float, float)>>(new List<(float, float, float)>(), delegate(List<(float v, float x, float z)> kept, (float v, float x, float z) r)
					{
						if (kept.Count < 5 && kept.All(((float v, float x, float z) k) => (k.x - r.x) * (k.x - r.x) + (k.z - r.z) * (k.z - r.z) > 640000f))
						{
							kept.Add(r);
						}
						return kept;
					});
					Whirlpools whirlpools = Whirlpools.Find(oceanCurrents, f.Generated, f.Ocean);
					Status.Write($"currents survey: tide levels (TideRange {TideLevels.Range.Value} m): {oceanCurrents.LevelSummary}");
					Status.Write($"currents survey: tides (Tides {Tides.Value} m/s) solved with the sea in {elapsedMilliseconds} ms; {whirlpools.Count} whirlpools; {num * 100 / Math.Max(1, num2)}% of the sea runs over 0.2 m/s at full flood; " + "strongest races: " + string.Join(", ", source.Select<(float, float, float), string>(((float v, float x, float z) r) => $"({r.x:0}, {r.z:0}) {r.v:0.00}")));
					List<float> speeds = new List<float>();
					int num6 = 0;
					int num7 = 0;
					for (float num8 = -10000f; num8 <= 10000f; num8 += 96f)
					{
						for (float num9 = -10000f; num9 <= 10000f; num9 += 96f)
						{
							if (!(num8 * num8 + num9 * num9 > 100000000f) && !(f.Generated(num8, num9) > f.Ocean - 1f))
							{
								num6++;
								val = oceanCurrents.At(num8, num9);
								float num10 = ((Vector2)(ref val)).magnitude * oceanCurrents.Fetch(num8, num9) * OceanSpeed.Value;
								if (num10 < 0.05f)
								{
									num7++;
								}
								else
								{
									speeds.Add(num10);
								}
							}
						}
					}
					speeds.Sort();
					string text = $"currents survey: sea (OceanSpeed {OceanSpeed.Value}): {num7 * 100 / Math.Max(1, num6)}% of the sea still (sheltered), ";
					object[] obj = new object[5]
					{
						Pick(0.1f),
						Pick(0.5f),
						Pick(0.9f),
						null,
						null
					};
					val = oceanCurrents.At(1169f, 3908f);
					obj[3] = ((Vector2)(ref val)).magnitude;
					obj[4] = oceanCurrents.Fetch(1169f, 3908f);
					Status.Write(text + string.Format("elsewhere p10 {0} / median {1} / p90 {2} m/s; at (1169, 3908): gyre {3:0.00} x openness {4:0.00}", obj));
					string Pick(float q)
					{
						if (speeds.Count <= 0)
						{
							return "-";
						}
						return speeds[(int)(q * (float)(speeds.Count - 1))].ToString("0.00");
					}
				}
				catch (Exception arg)
				{
					Status.Write($"currents survey failed: {arg}");
				}
			});
		}

		private static void Run(Field f, Channel[] rivers)
		{
			try
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				string arg = BuildPoints(f, rivers);
				f.Ready = true;
				Status.Write($"currents: {arg}; {stopwatch.ElapsedMilliseconds} ms");
				if (SeaWanted())
				{
					Task.Run(delegate
					{
						try
						{
							Stopwatch stopwatch2 = Stopwatch.StartNew();
							OceanCurrents sea = OceanCurrents.Solve(f.Generated, f.Ocean, 10500f, f.Seed);
							f.Pools = Whirlpools.Find(sea, f.Generated, f.Ocean);
							f.Sea = sea;
							Status.Write($"currents: sea solved in {stopwatch2.ElapsedMilliseconds} ms ({f.Pools.Count} whirlpools)");
							foreach (long key in f.Tiles.Keys)
							{
								f.Queue.Add(key);
							}
						}
						catch (InvalidOperationException)
						{
						}
						catch (Exception arg3)
						{
							Status.Write($"currents: sea failed: {arg3}");
						}
						finally
						{
							f.SeaDone = true;
						}
					});
				}
				else
				{
					f.SeaDone = true;
				}
				foreach (long item in f.Queue.GetConsumingEnumerable())
				{
					if (!f.Queue.IsAddingCompleted)
					{
						if (item == long.MinValue)
						{
							Rebuild(f);
						}
						else
						{
							f.Tiles[item] = Build(f, item);
						}
						continue;
					}
					break;
				}
			}
			catch (Exception arg2)
			{
				Status.Write($"currents failed: {arg2}");
			}
		}

		private static string BuildPoints(Field f, Channel[] rivers)
		{
			//IL_244c: Unknown result type (might be due to invalid IL or missing references)
			//IL_2454: Unknown result type (might be due to invalid IL or missing references)
			//IL_2aae: Unknown result type (might be due to invalid IL or missing references)
			//IL_2ab3: Unknown result type (might be due to invalid IL or missing references)
			//IL_2ab5: Unknown result type (might be due to invalid IL or missing references)
			//IL_2ab8: Invalid comparison between Unknown and I4
			//IL_2aba: Unknown result type (might be due to invalid IL or missing references)
			//IL_2abe: Invalid comparison between Unknown and I4
			int num = rivers.Length;
			List<Point> list = new List<Point>();
			int[] first = new int[num];
			int[] count = new int[num];
			for (int i = 0; i < num; i++)
			{
				first[i] = list.Count;
				Channel channel = rivers[i];
				int num2 = -1;
				float num3 = 0f;
				float num4 = 0f;
				float num5 = 0f;
				for (int j = 0; j < channel.Count; j++)
				{
					float x = channel.At[j].x;
					float y = channel.At[j].y;
					if (num2 >= 0)
					{
						num3 += (float)Math.Sqrt((x - num4) * (x - num4) + (y - num5) * (y - num5));
					}
					num4 = x;
					num5 = y;
					list.Add(new Point
					{
						X = x,
						Z = y,
						DX = 0f - channel.Up[j].x,
						DZ = 0f - channel.Up[j].y,
						Along = num3,
						River = i,
						Next = num2
					});
					num2 = list.Count - 1;
				}
				count[i] = list.Count - first[i];
			}
			Point[] points = list.ToArray();
			int n = points.Length;
			Dictionary<long, List<int>> grid = new Dictionary<long, List<int>>();
			for (int k = 0; k < n; k++)
			{
				long key = Key((int)Math.Floor(points[k].X / 64f), (int)Math.Floor(points[k].Z / 64f));
				if (!grid.TryGetValue(key, out var value))
				{
					value = (grid[key] = new List<int>());
				}
				value.Add(k);
			}
			float[] area = new float[n];
			float ocean = f.Ocean;
			Parallel.For(0, n, delegate(int num140)
			{
				Point point6 = points[num140];
				float num141 = 0f - point6.DZ;
				float dX = point6.DX;
				float num142 = 0f;
				float num143 = 0f;
				float num144 = ocean - f.Ground(point6.X, point6.Z);
				points[num140].Dry = num144 <= 0.05f;
				if (num144 > 0f)
				{
					num142 += num144 * 2f;
					num143 += 2f;
				}
				for (int l = -1; l <= 1; l += 2)
				{
					for (float num145 = 2f; num145 <= 80f; num145 += 2f)
					{
						float num146 = ocean - f.Ground(point6.X + (float)l * num141 * num145, point6.Z + (float)l * dX * num145);
						if (num146 <= 0f)
						{
							break;
						}
						num142 += num146 * 2f;
						num143 += 2f;
					}
				}
				points[num140].HalfWidth = Math.Max(1f, num143 * 0.5f);
				points[num140].MeanDepth = ((num143 > 0f) ? Math.Max(0.2f, num142 / num143) : 0.2f);
				area[num140] = Math.Max(1f, num142);
			});
			for (int num6 = 0; num6 < num; num6++)
			{
				FillDry(points, area, first[num6], count[num6]);
			}
			int part = points.Count((Point p) => p.Dry);
			int[] joins = new int[num];
			for (int num7 = 0; num7 < num; num7++)
			{
				joins[num7] = -1;
				if (count[num7] == 0)
				{
					continue;
				}
				Point point = points[first[num7]];
				float num8 = 144f;
				float num9 = num8;
				int num10 = -1;
				int num11 = -1;
				int num12 = (int)Math.Floor(point.X / 64f);
				int num13 = (int)Math.Floor(point.Z / 64f);
				for (int num14 = num12 - 1; num14 <= num12 + 1; num14++)
				{
					for (int num15 = num13 - 1; num15 <= num13 + 1; num15++)
					{
						if (!grid.TryGetValue(Key(num14, num15), out var value2))
						{
							continue;
						}
						foreach (int item8 in value2)
						{
							int river = points[item8].River;
							if (river != num7)
							{
								float num16 = (points[item8].X - point.X) * (points[item8].X - point.X) + (points[item8].Z - point.Z) * (points[item8].Z - point.Z);
								if (num16 < num9)
								{
									num9 = num16;
									num11 = item8;
								}
								if (item8 - first[river] >= 2 && num16 < num8)
								{
									num8 = num16;
									num10 = item8;
								}
							}
						}
					}
				}
				joins[num7] = ((num10 >= 0) ? num10 : num11);
			}
			int num17 = 0;
			int[] array = new int[num];
			for (int num18 = 0; num18 < num; num18++)
			{
				if (joins[num18] < 0)
				{
					continue;
				}
				int num19 = num18;
				int num20 = 0;
				array[num18] = num18 + 1;
				while (joins[num19] >= 0 && num20++ < num)
				{
					num19 = points[joins[num19]].River;
					if (num19 == num18)
					{
						joins[num18] = -1;
						num17++;
						break;
					}
					if (array[num19] == num18 + 1)
					{
						break;
					}
					array[num19] = num18 + 1;
				}
			}
			List<int>[] children = new List<int>[num];
			int num21 = 0;
			for (int num22 = 0; num22 < num; num22++)
			{
				if (joins[num22] >= 0)
				{
					int river2 = points[joins[num22]].River;
					(children[river2] ?? (children[river2] = new List<int>())).Add(num22);
					num21++;
				}
			}
			float[] logArea = area.Select((float v) => Mathf.Log(v)).ToArray();
			float[] size = new float[n];
			float[] reach = new float[n];
			Parallel.For(0, num, delegate(int r)
			{
				for (int l = first[r]; l < first[r] + count[r]; l++)
				{
					size[l] = MedianWet(logArea, r, l, 25);
					reach[l] = MedianWet(logArea, r, l, 75);
				}
			});
			int[] source = (from num140 in Enumerable.Range(0, n)
				where !points[num140].Dry
				select num140).ToArray();
			float num23 = Middle(source.Select((int num140) => size[num140]));
			float[] array2 = new float[n];
			float[] array3 = new float[n];
			for (int num24 = 0; num24 < n; num24++)
			{
				if (!points[num24].Dry)
				{
					array2[num24] = size[num24] - logArea[num24];
					array3[num24] = reach[num24] - logArea[num24];
				}
			}
			float[] squeezes = new float[n];
			float[] array4 = new float[n];
			float num25 = 0f;
			for (int num26 = 0; num26 < num; num26++)
			{
				for (int num27 = first[num26]; num27 < first[num26] + count[num26]; num27++)
				{
					array4[num27] = Mathf.Clamp(Mathf.Exp(0.5f * SmoothWet(array3, num26, num27, 5)), 0.25f, 1.8f);
					points[num27].Lake = !points[num27].Dry && array4[num27] <= 0.5f && points[num27].HalfWidth >= 20f;
				}
			}
			for (int num28 = 0; num28 < num; num28++)
			{
				int num29 = first[num28] + count[num28];
				for (int num30 = first[num28]; num30 < num29; num30++)
				{
					if (points[num30].Lake || points[num30].Dry || num30 == first[num28] || !points[num30 - 1].Lake)
					{
						continue;
					}
					int num31;
					for (num31 = num30; num31 < num29 && !points[num31].Lake && !points[num31].Dry && num31 - num30 < 6; num31++)
					{
					}
					if (num31 < num29 && num31 - num30 <= 5 && points[num31].Lake)
					{
						for (int num32 = num30; num32 < num31; num32++)
						{
							points[num32].Lake = true;
						}
					}
				}
				int num33 = first[num28];
				while (num33 < num29)
				{
					if (!points[num33].Lake)
					{
						num33++;
						continue;
					}
					int num34;
					for (num34 = num33; num34 < num29 && points[num34].Lake; num34++)
					{
					}
					if (num34 - num33 < 3)
					{
						for (int num35 = num33; num35 < num34; num35++)
						{
							points[num35].Lake = false;
						}
					}
					num33 = num34;
				}
				if (joins[num28] < 0)
				{
					for (int num36 = first[num28]; num36 < num29 && points[num36].Lake; num36++)
					{
						points[num36].Lagoon = true;
					}
				}
			}
			for (int num37 = 0; num37 < num; num37++)
			{
				for (int num38 = first[num37]; num38 < first[num37] + count[num37]; num38++)
				{
					float num39 = Mathf.Clamp(Mathf.Exp(0.5f * SmoothWet(array2, num37, num38, 5)), 0.25f, 1.8f);
					float num40 = size[num38];
					float num41 = array4[num38];
					if (points[num38].Lake)
					{
						num39 = Mathf.Sqrt(num41);
						num40 = reach[num