Decompiled source of Ship Shuffle v1.0.0

BepInEx/plugins/ShipShuffle/ShipShuffle.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.1", FrameworkDisplayName = ".NET Framework 4.7.1")]
[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 ShipShuffle
{
	internal enum RecordKind
	{
		Moored,
		Held
	}
	internal sealed class AssignmentRecord
	{
		internal RecordKind Kind;

		internal int BoatIndex;

		internal string BoatName;

		internal int PortIndex;

		internal string PortName;

		internal int Tier = 1;

		internal string FrontCleat;

		internal string BackCleat;

		internal int Side;

		internal float LocalX;

		internal float LocalZ;

		internal float YawDegrees;

		internal string Describe()
		{
			return "boat " + BoatIndex + "|" + BoatName + " -> port " + PortIndex + "|" + PortName + ((Kind == RecordKind.Moored) ? (" tier " + Tier + " (front " + FrontCleat + ", back " + BackCleat + ((Side != 0) ? (", side " + Side) : "") + ") at") : " station-kept at") + " island-local (" + LocalX.ToString("F2", CultureInfo.InvariantCulture) + ", " + LocalZ.ToString("F2", CultureInfo.InvariantCulture) + ") yaw " + YawDegrees.ToString("F1", CultureInfo.InvariantCulture);
		}
	}
	internal sealed class AssignmentDocument
	{
		internal int Seed;

		internal readonly List<AssignmentRecord> Records = new List<AssignmentRecord>();
	}
	internal static class AssignmentStore
	{
		internal const string Key = "ShipShuffle";

		private const string Version = "1";

		private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture;

		internal static string Format(AssignmentDocument document)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(Join("1", "seed", document.Seed.ToString(Invariant)));
			foreach (AssignmentRecord record in document.Records)
			{
				stringBuilder.Append('\n');
				if (record.Kind == RecordKind.Held)
				{
					stringBuilder.Append(Join("held", record.BoatIndex.ToString(Invariant), record.BoatName, record.PortIndex.ToString(Invariant), record.PortName, R(record.LocalX), R(record.LocalZ), R(record.YawDegrees)));
				}
				else
				{
					stringBuilder.Append(Join("moored", record.BoatIndex.ToString(Invariant), record.BoatName, record.PortIndex.ToString(Invariant), record.PortName, record.Tier.ToString(Invariant), record.FrontCleat, record.BackCleat, ((record.Tier == 3) ? record.Side : 0).ToString(Invariant), R(record.LocalX), R(record.LocalZ), R(record.YawDegrees)));
				}
			}
			return stringBuilder.ToString();
		}

		internal static AssignmentDocument Parse(string text, List<string> errors)
		{
			if (string.IsNullOrEmpty(text))
			{
				errors.Add("Stored assignment text is empty.");
				return null;
			}
			string[] array = text.Split(new char[1] { '\n' }, StringSplitOptions.None);
			string[] array2 = Split(array[0].TrimEnd(new char[1] { '\r' }));
			if (array2 == null || array2.Length != 3 || array2[0] != "1" || array2[1] != "seed" || !int.TryParse(array2[2], NumberStyles.Integer, Invariant, out var result))
			{
				errors.Add("Stored assignment header is not '1|seed|<int>'.");
				return null;
			}
			AssignmentDocument assignmentDocument = new AssignmentDocument
			{
				Seed = result
			};
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			for (int i = 1; i < array.Length; i++)
			{
				string text2 = array[i].TrimEnd(new char[1] { '\r' });
				if (text2.Length != 0)
				{
					AssignmentRecord assignmentRecord = ParseRecord(Split(text2));
					if (assignmentRecord == null)
					{
						errors.Add("Stored assignment line " + (i + 1) + " is malformed and was ignored.");
						continue;
					}
					dictionary[assignmentRecord.BoatIndex] = ((!dictionary.TryGetValue(assignmentRecord.BoatIndex, out var value)) ? 1 : (value + 1));
					assignmentDocument.Records.Add(assignmentRecord);
				}
			}
			foreach (KeyValuePair<int, int> pair in dictionary)
			{
				if (pair.Value > 1)
				{
					errors.Add("Boat " + pair.Key + " has " + pair.Value + " stored assignments; all were ignored.");
					assignmentDocument.Records.RemoveAll((AssignmentRecord record) => record.BoatIndex == pair.Key);
				}
			}
			return assignmentDocument;
		}

		private static AssignmentRecord ParseRecord(string[] fields)
		{
			if (fields == null || fields.Length < 5 || !int.TryParse(fields[1], NumberStyles.Integer, Invariant, out var result) || !int.TryParse(fields[3], NumberStyles.Integer, Invariant, out var result2) || result < 0 || result2 < 0 || string.IsNullOrEmpty(fields[2]) || string.IsNullOrEmpty(fields[4]))
			{
				return null;
			}
			AssignmentRecord assignmentRecord = new AssignmentRecord
			{
				BoatIndex = result,
				BoatName = fields[2],
				PortIndex = result2,
				PortName = fields[4]
			};
			string text = fields[0];
			if (!(text == "moored"))
			{
				if (text == "held")
				{
					if (fields.Length != 8 || !TryFloat(fields[5], out assignmentRecord.LocalX) || !TryFloat(fields[6], out assignmentRecord.LocalZ) || !TryFloat(fields[7], out assignmentRecord.YawDegrees))
					{
						return null;
					}
					assignmentRecord.Kind = RecordKind.Held;
					assignmentRecord.Tier = 4;
					return assignmentRecord;
				}
				return null;
			}
			if (fields.Length == 12 && int.TryParse(fields[5], NumberStyles.Integer, Invariant, out var result3) && result3 >= 1 && result3 <= 3 && int.TryParse(fields[8], NumberStyles.Integer, Invariant, out assignmentRecord.Side))
			{
				bool num;
				if (result3 != 3)
				{
					num = assignmentRecord.Side != 0;
				}
				else
				{
					if (assignmentRecord.Side == 1)
					{
						goto IL_00fd;
					}
					num = assignmentRecord.Side != -1;
				}
				if (!num)
				{
					goto IL_00fd;
				}
			}
			goto IL_0130;
			IL_00fd:
			if (TryFloat(fields[9], out assignmentRecord.LocalX) && TryFloat(fields[10], out assignmentRecord.LocalZ) && TryFloat(fields[11], out assignmentRecord.YawDegrees))
			{
				assignmentRecord.Tier = result3;
				assignmentRecord.FrontCleat = fields[6];
				assignmentRecord.BackCleat = fields[7];
				if (!ValidCleats(assignmentRecord))
				{
					return null;
				}
				return assignmentRecord;
			}
			goto IL_0130;
			IL_0130:
			return null;
		}

		private static bool ValidCleats(AssignmentRecord record)
		{
			if (!string.IsNullOrEmpty(record.FrontCleat) && !string.IsNullOrEmpty(record.BackCleat) && record.FrontCleat != record.BackCleat && !record.FrontCleat.StartsWith("@", StringComparison.Ordinal))
			{
				return !record.BackCleat.StartsWith("@", StringComparison.Ordinal);
			}
			return false;
		}

		private static string R(float value)
		{
			return value.ToString("R", Invariant);
		}

		private static bool TryFloat(string text, out float value)
		{
			if (float.TryParse(text, NumberStyles.Float, Invariant, out value) && !float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static string Join(params string[] fields)
		{
			string[] array = new string[fields.Length];
			for (int i = 0; i < fields.Length; i++)
			{
				array[i] = Uri.EscapeDataString(fields[i] ?? "");
			}
			return string.Join("|", array);
		}

		private static string[] Split(string line)
		{
			try
			{
				string[] array = line.Split(new char[1] { '|' });
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = Uri.UnescapeDataString(array[i]);
				}
				return array;
			}
			catch (Exception)
			{
				return null;
			}
		}
	}
	internal sealed class DockSite
	{
		internal PortEntry Port;

		internal GPButtonDockMooring[] IslandCleats;

		internal string[] IslandIds;

		internal int R1;

		internal int R2;

		internal GPButtonDockMooring[] Cleats;

		internal string[] CleatIds;

		internal DockChain Chain;

		internal Vec2 BoatPos;

		internal Vec2 BoatForward;

		internal Vec2 AlternateOffset;

		internal bool NoRecovery;

		internal bool MarkerReference;

		internal bool MarkerGoLeft;

		internal Vector3?[] VirtualLocal;

		internal Transform Marker;

		internal Terrain[] Terrains = (Terrain[])(object)new Terrain[0];

		internal int IslandCleatCount => IslandCleats.Length;

		internal Transform Island => ((Component)Port.Island).transform;

		internal Vec2 Position(int index)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)IslandCleats[index] != (Object)null))
			{
				return HullFootprint.Flat(Marker.TransformPoint(VirtualLocal[index].Value));
			}
			return HullFootprint.Flat(((Component)IslandCleats[index]).transform.position);
		}

		internal int IndexOf(string identity)
		{
			return Array.IndexOf(IslandIds, identity);
		}

		internal int IndexOf(Transform cleat)
		{
			return Array.FindIndex(IslandCleats, (GPButtonDockMooring item) => (Object)(object)item != (Object)null && (Object)(object)((Component)item).transform == (Object)(object)cleat);
		}
	}
	internal sealed class BerthPlan
	{
		internal DockSite Site;

		internal BoatEntry Boat;

		internal Candidate Candidate;

		internal GPButtonDockMooring Front;

		internal GPButtonDockMooring Back;

		internal string FrontId;

		internal string BackId;

		internal bool Restored;

		internal Vector3 Position;

		internal Quaternion Rotation;

		internal Vector3 LocalPosition;

		internal float LocalYaw;

		internal int Tier => Candidate.Tier;

		internal bool Held => Candidate.Tier == 4;

		internal string Describe()
		{
			return "tier " + Tier + (Held ? (" station-kept spot " + DockGeometry.F(Candidate.Outward) + " m outward") : (" " + FrontId + " / " + BackId + ((Candidate.Side != 0) ? (" side " + Candidate.Side) : "") + ((Candidate.SlotOwner != null) ? (" (vacated berth of " + Candidate.SlotOwner + ")") : "") + ((Candidate.TiedOff > 0f) ? (", tied off the dock (+" + DockGeometry.F(Candidate.TiedOff) + " m)") : ""))) + (Candidate.Estimated ? ", estimated" : "");
		}
	}
	internal sealed class PlannedBerth
	{
		internal BoatEntry Boat;

		internal Candidate Candidate;
	}
	internal static class BerthResolver
	{
		private sealed class TerrainPatch
		{
			internal TerrainData Data;

			internal float X;

			internal float Z;

			internal float SizeX;

			internal float SizeZ;

			internal float Y;
		}

		private static readonly FieldInfo IslandHeightField = AccessTools.Field(typeof(IslandHorizon), "initialHeight");

		internal static bool TryBuildSite(PortEntry port, out DockSite site, out string reason)
		{
			if (!TryBuildDocks(port, out site, out reason))
			{
				return false;
			}
			site.Terrains = (from item in ((Component)port.Island).GetComponentsInChildren<Terrain>(true)
				where (Object)(object)item != (Object)null && (Object)(object)item.terrainData != (Object)null
				select item).ToArray();
			if (site.Terrains.Length == 0)
			{
				Plugin.Instance?.DebugLog(port.Label + ": no terrain to check; berths here are not vetted against the sea floor.");
			}
			else
			{
				Plugin.Instance?.DebugLog(port.Label + ": " + site.Terrains.Length + " terrain(s) vet berths against the sea floor.");
			}
			return true;
		}

		private static bool TryBuildDocks(PortEntry port, out DockSite site, out string reason)
		{
			//IL_03aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03da: Unknown result type (might be due to invalid IL or missing references)
			//IL_041f: Unknown result type (might be due to invalid IL or missing references)
			site = null;
			reason = null;
			IslandHorizon island = port.Island;
			RecoveryPort val = port.PlacementReference ?? port.Recovery;
			if ((Object)(object)island == (Object)null)
			{
				reason = "Port island is missing.";
				return false;
			}
			GPButtonDockMooring[] cleats = (from item in ((Component)island).GetComponentsInChildren<GPButtonDockMooring>(true)
				where (Object)(object)item != (Object)null && (Object)(object)item.spring != (Object)null && (Object)(object)((Component)item).GetComponent<SpringJoint>() == (Object)(object)item.spring && (Object)(object)PortCatalog.FindIsland(((Component)item).transform) == (Object)(object)island
				select item).Distinct().ToArray();
			List<string> ids = cleats.Select((GPButtonDockMooring item) => CleatIdentity.Build(((Component)island).transform, ((Component)item).transform)).ToList();
			if (ids.Any((string item) => item == null) || ids.Distinct<string>(StringComparer.Ordinal).Count() != ids.Count)
			{
				reason = "Dock cleat identities on " + ((Object)island).name + " are missing or not unique.";
				return false;
			}
			if ((Object)(object)val == (Object)null)
			{
				return TryBuildNoRecoverySite(port, cleats, ids.ToArray(), out site, out reason);
			}
			if ((Object)(object)val.parentPort != (Object)(object)port.Port || (Object)(object)val.boatPos == (Object)null || (!port.NoRecovery && ((Object)(object)val.mooringFront == (Object)null || (Object)(object)val.mooringBack == (Object)null)))
			{
				reason = "Recovery berth is incomplete.";
				return false;
			}
			List<GPButtonDockMooring> list = cleats.ToList();
			Vector3?[] virtualLocal = null;
			int r = -1;
			int r2 = -1;
			if (port.NoRecovery)
			{
				if (!Synthesize(port, list, ids, "(dormant authored pose; no native recovery berth)", out r, out r2, out virtualLocal, out reason))
				{
					return false;
				}
			}
			else
			{
				r = RecoveryIndex(val.mooringFront, list, ids, "@recovery-front", out reason);
				r2 = ((r < 0) ? (-1) : RecoveryIndex(val.mooringBack, list, ids, "@recovery-back", out reason));
			}
			if (!port.NoRecovery && (r < 0 || r2 < 0 || r == r2))
			{
				string text = "Recovery cleats " + ((Object)val.mooringFront).name + " / " + ((Object)val.mooringBack).name + " of " + ((Object)island).name + " are unusable: " + (reason ?? "both are the same cleat");
				ids = ids.Take(cleats.Length).ToList();
				list = cleats.ToList();
				if (!Synthesize(port, list, ids, "(unresolved: " + (reason ?? "same cleat") + ")", out r, out r2, out virtualLocal, out reason))
				{
					reason = text + "; " + reason;
					return false;
				}
			}
			else if (!port.NoRecovery)
			{
				Vec2 b = HullFootprint.Flat(val.boatPos.position);
				float num = Math.Max(Vec2.Distance(HullFootprint.Flat(((Component)list[r]).transform.position), b), Vec2.Distance(HullFootprint.Flat(((Component)list[r2]).transform.position), b));
				if (!(num <= 40f))
				{
					ids = ids.Take(cleats.Length).ToList();
					list = cleats.ToList();
					if (!Synthesize(port, list, ids, "(" + DockGeometry.F(num) + " m)", out r, out r2, out virtualLocal, out reason))
					{
						return false;
					}
				}
			}
			cleats = list.ToArray();
			Transform boatPos = val.boatPos;
			Vec2 vec = HullFootprint.Flat(boatPos.forward);
			Vec2 vec2 = HullFootprint.Flat(boatPos.TransformDirection(val.goLeft ? Vector3.left : Vector3.right));
			if (!HullFootprint.Finite(boatPos.position) || !vec.IsFinite || vec.Length < 0.5f || !vec2.IsFinite || vec2.Length < 0.5f)
			{
				reason = "Recovery marker orientation is not usable in the horizontal plane.";
				return false;
			}
			Vec2 boatPos2 = HullFootprint.Flat(boatPos.position);
			DockSite dockSite = new DockSite
			{
				Port = port,
				IslandCleats = cleats,
				IslandIds = ids.ToArray(),
				R1 = r,
				R2 = r2,
				Marker = boatPos,
				VirtualLocal = (virtualLocal ?? new Vector3?[cleats.Length]),
				NoRecovery = port.NoRecovery,
				MarkerReference = port.NoRecovery,
				MarkerGoLeft = val.goLeft,
				BoatPos = boatPos2,
				BoatForward = vec * (1f / vec.Length),
				AlternateOffset = vec2 * (12f / vec2.Length)
			};
			DockChain dockChain = DockGeometry.BuildChain(Enumerable.Range(0, cleats.Length).Select(dockSite.Position).ToArray(), r, r2, boatPos2);
			if (!dockChain.Ok)
			{
				reason = dockChain.Reason;
				return false;
			}
			dockSite.Chain = dockChain;
			dockSite.Cleats = dockChain.Order.Select((int index) => cleats[index]).ToArray();
			dockSite.CleatIds = dockChain.Order.Select((int index) => ids[index]).ToArray();
			site = dockSite;
			return true;
		}

		private static bool Synthesize(PortEntry port, List<GPButtonDockMooring> cleats, List<string> ids, string vanilla, out int r1, out int r2, out Vector3?[] virtualLocal, out string reason)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: 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_0200: Unknown result type (might be due to invalid IL or missing references)
			virtualLocal = null;
			RecoveryPort val = port.PlacementReference ?? port.Recovery;
			Transform boatPos = val.boatPos;
			Vec2 boatPos2 = HullFootprint.Flat(boatPos.position);
			Vec2 vec = HullFootprint.Flat(boatPos.forward);
			Vec2[] array = cleats.Select((GPButtonDockMooring item) => HullFootprint.Flat(((Component)item).transform.position)).ToArray();
			if (BerthTiers.TrySyntheticRecovery(array, array.Length, boatPos2, vec, out r1, out r2, out var reason2))
			{
				Plugin.Instance?.DebugLog(port.Label + ": vanilla recovery cleats point elsewhere " + vanilla + "; using island cleats " + ((Object)cleats[r1]).name + "/" + ((Object)cleats[r2]).name + " as the recovery reference.");
				if (!BerthTiers.AgreesWithMarker(array[r1], array[r2], boatPos2, vec, val.goLeft))
				{
					Plugin.Instance?.DebugLog(port.Label + ": the island pair puts the dock on the opposite side from the recovery marker frame (goLeft " + val.goLeft + "); keeping the island pair.");
				}
				reason = null;
				return true;
			}
			if (!(vec.Length >= 0.5f) || !boatPos2.IsFinite)
			{
				reason = "vanilla recovery cleats point elsewhere " + vanilla + ", no island pair (" + reason2 + ") and no usable marker";
				return false;
			}
			BerthTiers.MarkerReference(boatPos2, vec, val.goLeft, out var front, out var back);
			float y = boatPos.position.y;
			virtualLocal = new Vector3?[cleats.Count + 2];
			virtualLocal[cleats.Count] = boatPos.InverseTransformPoint(new Vector3(front.X, y, front.Z));
			virtualLocal[cleats.Count + 1] = boatPos.InverseTransformPoint(new Vector3(back.X, y, back.Z));
			r1 = cleats.Count;
			r2 = cleats.Count + 1;
			cleats.Add(null);
			cleats.Add(null);
			ids.Add("@marker-front");
			ids.Add("@marker-back");
			Plugin.Instance?.DebugLog(port.Label + ": vanilla recovery cleats point elsewhere " + vanilla + " and no island pair fits (" + reason2 + "); using the authored marker frame (dock on its " + (val.goLeft ? "right" : "left") + ") as the placement reference.");
			reason = null;
			return true;
		}

		private static bool TryBuildNoRecoverySite(PortEntry port, GPButtonDockMooring[] cleats, string[] ids, out DockSite site, out string reason)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			site = new DockSite
			{
				Port = port,
				IslandCleats = cleats,
				IslandIds = ids,
				R1 = -1,
				R2 = -1,
				NoRecovery = true,
				Cleats = (GPButtonDockMooring[])(object)new GPButtonDockMooring[0],
				CleatIds = new string[0],
				VirtualLocal = new Vector3?[cleats.Length]
			};
			Vec2 islandCentre = HullFootprint.Flat(port.Island.GetPosition());
			PortFrame portFrame = BerthTiers.Frame(new PortInput
			{
				Cleats = cleats.Select((GPButtonDockMooring item) => HullFootprint.Flat(((Component)item).transform.position)).ToArray(),
				Names = ids,
				NoRecovery = true,
				IslandCentre = islandCentre,
				HasIslandCentre = islandCentre.IsFinite
			});
			if (!portFrame.Ok)
			{
				site = null;
				reason = portFrame.Reason;
				return false;
			}
			site.BoatPos = portFrame.Base.BoatPos;
			site.BoatForward = portFrame.Base.BoatForward;
			reason = null;
			Plugin.Instance?.DebugLog(port.Label + ": no recovery berth; reserves not applied (reference pier " + string.Join(">", portFrame.Reference.Order.Select((int index) => ((Object)cleats[index]).name).ToArray()) + ").");
			return true;
		}

		private static int RecoveryIndex(Transform mooring, List<GPButtonDockMooring> cleats, List<string> ids, string placeholder, out string reason)
		{
			//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_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			reason = null;
			GPButtonDockMooring val = (((Object)(object)mooring != (Object)null) ? ((Component)mooring).GetComponent<GPButtonDockMooring>() : null);
			int num = (((Object)(object)val != (Object)null) ? cleats.IndexOf(val) : (-1));
			if (num >= 0)
			{
				return num;
			}
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.spring == (Object)null) && !((Object)(object)((Component)val).GetComponent<SpringJoint>() != (Object)(object)val.spring))
			{
				Scene scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).IsValid())
				{
					scene = ((Component)val).gameObject.scene;
					if (((Scene)(ref scene)).isLoaded)
					{
						cleats.Add(val);
						ids.Add(placeholder);
						return cleats.Count - 1;
					}
				}
			}
			reason = (((Object)(object)mooring != (Object)null) ? ((Object)mooring).name : "missing cleat") + " is not an initialized dock cleat in the loaded scene";
			return -1;
		}

		internal static Dictionary<Transform, string> AuthoredClaims(BoatMooringRopes except)
		{
			//IL_0035: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<Transform, string> dictionary = new Dictionary<Transform, string>();
			BoatMooringRopes[] array = Resources.FindObjectsOfTypeAll<BoatMooringRopes>();
			foreach (BoatMooringRopes val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)except)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).isLoaded || !((Component)val).gameObject.activeInHierarchy)
				{
					continue;
				}
				Transform[] array2 = (Transform[])(object)new Transform[2] { val.mooringFront, val.mooringBack };
				foreach (Transform val2 in array2)
				{
					if ((Object)(object)val2 != (Object)null && !dictionary.ContainsKey(val2))
					{
						dictionary.Add(val2, ((Object)((Component)val).gameObject).name);
					}
				}
			}
			return dictionary;
		}

		internal static PortInput MakePortInput(DockSite site, BoatEntry boat, Occupancy occupancy, IEnumerable<BoatEntry> saleBoats, IEnumerable<PlannedBerth> planned = null)
		{
			//IL_01ee: 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_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_0387: Unknown result type (might be due to invalid IL or missing references)
			//IL_0393: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			int num = site.IslandCleats.Length;
			PortInput portInput = new PortInput
			{
				Cleats = Enumerable.Range(0, num).Select(site.Position).ToArray(),
				Names = site.IslandIds,
				Free = new bool[num],
				Unavailable = new string[num],
				Claimed = new bool[num],
				R1 = site.R1,
				R2 = site.R2,
				NoRecovery = site.NoRecovery,
				MarkerReference = site.MarkerReference,
				NoDock = site.IslandCleats.All((GPButtonDockMooring item) => (Object)(object)item == (Object)null),
				OffshoreOnly = KnownIdentities.OffshoreOnly(site.Port.Identity),
				OasisNativeSaleHeading = KnownIdentities.NativeSaleHeading(site.Port.Identity),
				StationPolicy = KnownIdentities.StationPolicyFor(site.Port.Identity),
				StationRegion = KnownIdentities.StationRegionFor(site.Port.Identity),
				BoatPos = site.BoatPos,
				BoatForward = site.BoatForward,
				AlternateOffset = site.AlternateOffset,
				Obstacles = occupancy.ObstaclesFor(boat.Body)
			};
			if ((Object)(object)site.Marker != (Object)null)
			{
				portInput.BoatPos = HullFootprint.Flat(site.Marker.position);
				Vec2 vec = HullFootprint.Flat(site.Marker.forward);
				portInput.BoatForward = vec * (1f / vec.Length);
				Vec2 vec2 = HullFootprint.Flat(site.Marker.TransformDirection(site.MarkerGoLeft ? Vector3.left : Vector3.right));
				portInput.AlternateOffset = vec2 * (12f / vec2.Length);
			}
			Vec2 vec3 = (portInput.IslandCentre = HullFootprint.Flat(site.Port.Island.GetPosition()));
			portInput.HasIslandCentre = vec3.IsFinite;
			for (int num2 = 0; num2 < num; num2++)
			{
				GPButtonDockMooring val = site.IslandCleats[num2];
				if ((Object)(object)val == (Object)null && site.VirtualLocal[num2].HasValue)
				{
					portInput.Unavailable[num2] = "virtual recovery reference";
				}
				else
				{
					portInput.Unavailable[num2] = occupancy.CleatProblem(val, boat.Body, out portInput.Claimed[num2]);
				}
				portInput.Free[num2] = portInput.Unavailable[num2] == null;
			}
			List<BerthSlot> list = new List<BerthSlot>();
			foreach (BoatEntry item in saleBoats ?? new BoatEntry[0])
			{
				if (item == null || item.Purchased || (Object)(object)item.Ropes == (Object)null || (Object)(object)item.Body == (Object)null || ((Object)(object)item.Body != (Object)(object)boat.Body && !occupancy.Moving.Contains(item.Body)))
				{
					continue;
				}
				int num3 = site.IndexOf(item.Ropes.mooringFront);
				int num4 = site.IndexOf(item.Ropes.mooringBack);
				if (num3 >= 0 && num4 >= 0 && num3 != num4)
				{
					try
					{
						Capsule2 capsule = HullFootprint.MakeFootprint(((Component)item.Body).transform.position, ((Component)item.Body).transform.rotation, ((Component)item.Body).transform.lossyScale, item.Hull.center, item.Hull.radius, item.Hull.height, item.Hull.direction);
						list.Add(new BerthSlot
						{
							Owner = item.Label,
							Center = capsule.Center,
							Forward = HullFootprint.Flat(((Component)item.Body).transform.forward),
							Length = Vec2.Distance(capsule.A, capsule.B) + 2f * capsule.Radius,
							Radius = capsule.Radius,
							FrontCleat = num3,
							BackCleat = num4
						});
					}
					catch (InvalidOperationException)
					{
					}
				}
			}
			portInput.Slots = list;
			portInput.Ground = MakeGround(site);
			foreach (PlannedBerth item2 in planned ?? new PlannedBerth[0])
			{
				if (item2 != null && item2.Boat != boat)
				{
					BerthTiers.AddPlanned(portInput, item2.Boat.Label, item2.Candidate);
				}
			}
			return portInput;
		}

		internal static Func<Vec2, float?> MakeGround(DockSite site)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			if (site.Terrains == null || site.Terrains.Length == 0 || !TryHorizonOffset(site.Port.Island, out var offset))
			{
				return null;
			}
			List<TerrainPatch> patches = new List<TerrainPatch>();
			Terrain[] terrains = site.Terrains;
			foreach (Terrain val in terrains)
			{
				TerrainData val2 = (((Object)(object)val != (Object)null) ? val.terrainData : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					Vector3 size = val2.size;
					Vector3 position = ((Component)val).transform.position;
					if (size.x > 0f && size.z > 0f && HullFootprint.Finite(position))
					{
						patches.Add(new TerrainPatch
						{
							Data = val2,
							X = position.x,
							Z = position.z,
							SizeX = size.x,
							SizeZ = size.z,
							Y = position.y - offset.y
						});
					}
				}
			}
			if (patches.Count == 0)
			{
				return null;
			}
			return delegate(Vec2 point)
			{
				float? result = null;
				foreach (TerrainPatch item in patches)
				{
					float num = (point.X - item.X) / item.SizeX;
					float num2 = (point.Z - item.Z) / item.SizeZ;
					if (num >= 0f && num <= 1f && num2 >= 0f && num2 <= 1f)
					{
						float num3 = item.Y + item.Data.GetInterpolatedHeight(num, num2);
						if (!result.HasValue || num3 > result.Value)
						{
							result = num3;
						}
					}
				}
				return result;
			};
		}

		internal static TierEvaluation Evaluate(DockSite site, BoatEntry boat, Occupancy occupancy, IEnumerable<BoatEntry> saleBoats, FleetSettings settings, IEnumerable<PlannedBerth> planned = null)
		{
			PortInput port = MakePortInput(site, boat, occupancy, saleBoats, planned);
			TierEvaluation tierEvaluation = BerthTiers.Evaluate(port, boat.Spec);
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance == (Object)null || !instance.DebugEnabled)
			{
				return tierEvaluation;
			}
			IGrouping<string, Candidate> grouping = (from item in tierEvaluation.Candidates
				where !item.Accepted && item.Reason != null
				group item by Regex.Replace(item.Reason, "[-\\d.]+", "#") into @group
				orderby @group.Count() descending
				select @group).FirstOrDefault();
			instance.DebugLog("  " + boat.Label + " at " + site.Port.Label + ": " + tierEvaluation.Candidates.Count((Candidate item) => item.Accepted) + " of " + tierEvaluation.Candidates.Count + " candidates valid" + ((tierEvaluation.Best != null) ? ("; best " + Describe(port, tierEvaluation.Best)) : ((grouping != null) ? ("; most common rejection (" + grouping.Count() + "x): " + grouping.First().Reason) : ((tierEvaluation.Reason != null) ? ("; " + tierEvaluation.Reason) : ""))));
			return tierEvaluation;
		}

		internal static BerthPlan MakePlan(DockSite site, BoatEntry boat, Candidate candidate)
		{
			//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_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: 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_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(candidate.Forward.X, 0f, candidate.Forward.Z);
			BerthPlan berthPlan = new BerthPlan
			{
				Site = site,
				Boat = boat,
				Candidate = candidate,
				Position = new Vector3(candidate.Root.X, SpawnHeight.For(boat), candidate.Root.Z),
				Rotation = Quaternion.LookRotation(val, Vector3.up)
			};
			if (candidate.Tier != 4)
			{
				if (candidate.FrontCleat == site.R1 || candidate.FrontCleat == site.R2 || candidate.BackCleat == site.R1 || candidate.BackCleat == site.R2 || CleatIdentity.IsPlaceholder(site.IslandIds[candidate.FrontCleat]) || CleatIdentity.IsPlaceholder(site.IslandIds[candidate.BackCleat]))
				{
					throw new InvalidOperationException("A berth may never use the recovery cleats.");
				}
				berthPlan.Front = site.IslandCleats[candidate.FrontCleat];
				berthPlan.Back = site.IslandCleats[candidate.BackCleat];
				berthPlan.FrontId = site.IslandIds[candidate.FrontCleat];
				berthPlan.BackId = site.IslandIds[candidate.BackCleat];
			}
			berthPlan.LocalPosition = site.Island.InverseTransformPoint(berthPlan.Position);
			berthPlan.LocalYaw = Mathf.DeltaAngle(site.Island.eulerAngles.y, ((Quaternion)(ref berthPlan.Rotation)).eulerAngles.y);
			return berthPlan;
		}

		internal static DockSite IslandSite(PortEntry port)
		{
			if (!((Object)(object)port?.Island == (Object)null))
			{
				return new DockSite
				{
					Port = port,
					IslandCleats = (GPButtonDockMooring[])(object)new GPButtonDockMooring[0],
					IslandIds = new string[0],
					R1 = -1,
					R2 = -1,
					Cleats = (GPButtonDockMooring[])(object)new GPButtonDockMooring[0],
					CleatIds = new string[0],
					VirtualLocal = new Vector3?[0]
				};
			}
			return null;
		}

		internal static string StoredCleat(DockSite site, string identity, Occupancy occupancy, Rigidbody boat, out GPButtonDockMooring cleat, out Vec2 position)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			cleat = null;
			position = default(Vec2);
			if (!CleatIdentity.TryResolve(site.Island, identity, out var cleat2, out var reason))
			{
				return "stored cleat " + identity + " did not resolve: " + reason;
			}
			cleat = ((Component)cleat2).GetComponent<GPButtonDockMooring>();
			if ((Object)(object)cleat == (Object)null || (Object)(object)cleat.spring == (Object)null || (Object)(object)((Component)cleat).GetComponent<SpringJoint>() != (Object)(object)cleat.spring)
			{
				return "stored cleat " + identity + " has no initialized spring";
			}
			position = HullFootprint.Flat(((Component)cleat).transform.position);
			Rigidbody connectedBody = ((Joint)cleat.spring).connectedBody;
			if (!((Object)(object)connectedBody != (Object)null) || !((Object)(object)connectedBody != (Object)(object)boat) || occupancy.Moving.Contains(connectedBody))
			{
				return null;
			}
			return "stored cleat " + identity + " is held by " + ((Object)connectedBody).name;
		}

		internal static BerthPlan MakeStoredPlan(DockSite site, BoatEntry boat, Candidate candidate, GPButtonDockMooring front, GPButtonDockMooring back, string frontId, string backId, Vector3 localPosition, float localYaw)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0096: Unknown result type (might be due to invalid IL or missing references)
			StationPose(site, localPosition, localYaw, out var position, out var rotation);
			BerthPlan berthPlan = new BerthPlan
			{
				Site = site,
				Boat = boat,
				Candidate = candidate,
				Restored = true,
				Position = new Vector3(position.x, SpawnHeight.For(boat), position.z),
				Rotation = rotation,
				LocalYaw = localYaw
			};
			if (candidate.Tier != 4)
			{
				berthPlan.Front = front;
				berthPlan.Back = back;
				berthPlan.FrontId = frontId;
				berthPlan.BackId = backId;
			}
			berthPlan.LocalPosition = site.Island.InverseTransformPoint(berthPlan.Position);
			return berthPlan;
		}

		internal static string Overlaps(Occupancy occupancy, BoatEntry boat, Candidate candidate)
		{
			string[] array = (from obstacle in occupancy.ObstaclesFor(boat.Body)
				where obstacle != null && obstacle.Measurable && obstacle.Hull.IsValid && DockGeometry.CapsuleGap(candidate.Hull, obstacle.Hull) < 0f
				select obstacle.Name).ToArray();
			if (array.Length != 0)
			{
				return string.Join(", ", array);
			}
			return null;
		}

		internal static void StationPose(DockSite site, Vector3 localPosition, float localYaw, out Vector3 position, out Quaternion rotation)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			position = site.Island.TransformPoint(localPosition);
			rotation = Quaternion.Euler(0f, site.Island.eulerAngles.y + localYaw, 0f);
		}

		internal static bool TryHorizonOffset(IslandHorizon island, out Vector3 offset)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: 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_0091: 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)
			offset = Vector3.zero;
			if ((Object)(object)island == (Object)null || IslandHeightField == null || !(IslandHeightField.GetValue(island) is float num) || !DockGeometry.Finite(num))
			{
				return false;
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(0f, ((Component)island).transform.localPosition.y - num, 0f);
			offset = (((Object)(object)((Component)island).transform.parent != (Object)null) ? ((Component)island).transform.parent.TransformVector(val) : val);
			return HullFootprint.Finite(offset);
		}

		internal static string Describe(PortInput port, Candidate candidate)
		{
			string text = "T" + candidate.Tier + (candidate.Estimated ? " (estimated)" : "") + " ";
			text = ((candidate.Tier != 4) ? (text + ((candidate.FrontCleat >= 0) ? port.Name(candidate.FrontCleat) : "?") + " / " + ((candidate.BackCleat >= 0) ? port.Name(candidate.BackCleat) : "?") + ((candidate.Side != 0) ? (" side " + candidate.Side) : "") + ((candidate.SlotOwner != null) ? (" vacated by " + candidate.SlotOwner) : "") + ": span " + DockGeometry.F(candidate.Span) + " m") : (text + "station spot " + DockGeometry.F(candidate.Outward) + " m out, shift " + DockGeometry.F(candidate.Shift) + " m"));
			if (!float.IsNaN(candidate.DockClearance))
			{
				text = text + ", dock clearance " + DockGeometry.F(candidate.DockClearance) + " m";
			}
			if (candidate.Blocker != null)
			{
				text = text + ", nearest gap " + DockGeometry.F(candidate.MinimumGap) + " m (" + candidate.Blocker + ")";
			}
			if (candidate.TiedOff > 0f)
			{
				text = text + ", tied off the dock (+" + DockGeometry.F(candidate.TiedOff) + " m)";
			}
			if (candidate.Accepted)
			{
				string[] obj = new string[6]
				{
					text,
					(candidate.Tier != 4) ? (", lines " + DockGeometry.F(candidate.FrontLine) + " / " + DockGeometry.F(candidate.BackLine) + " m") : "",
					", distance from recovery ",
					DockGeometry.F(candidate.Distance),
					" m, root ",
					null
				};
				Vec2 root = candidate.Root;
				obj[5] = root.ToString();
				text = string.Concat(obj);
			}
			return text + " -> " + (candidate.Accepted ? "ACCEPT" : ("reject: " + candidate.Reason));
		}
	}
	internal sealed class BerthSlot
	{
		internal string Owner;

		internal Vec2 Center;

		internal Vec2 Forward;

		internal float Length;

		internal float Radius;

		internal int FrontCleat;

		internal int BackCleat;
	}
	internal sealed class Pier
	{
		internal int[] Order;

		internal Vec2[] Points;

		internal int Side;

		internal string SideReason;

		internal int[] Cluster = new int[0];

		internal float Extent;

		internal float Length
		{
			get
			{
				if (Points.Length >= 2)
				{
					return Vec2.Distance(Points[0], Points[Points.Length - 1]);
				}
				return 0f;
			}
		}
	}
	internal sealed class PortInput
	{
		internal Vec2[] Cleats = new Vec2[0];

		internal string[] Names;

		internal bool[] Free;

		internal string[] Unavailable;

		internal bool[] Claimed;

		internal int R1 = -1;

		internal int R2 = -1;

		internal bool NoRecovery;

		internal bool MarkerReference;

		internal bool NoDock;

		internal bool OffshoreOnly;

		internal bool OasisNativeSaleHeading;

		internal StationPolicy StationPolicy;

		internal StationRegion? StationRegion;

		internal Vec2 BoatPos;

		internal Vec2 BoatForward;

		internal Vec2 AlternateOffset;

		internal Vec2 IslandCentre;

		internal bool HasIslandCentre;

		internal IList<Obstacle> Obstacles = new Obstacle[0];

		internal IList<BerthSlot> Slots = new BerthSlot[0];

		internal Func<Vec2, float?> Ground;

		internal string Name(int cleat)
		{
			if (Names == null || cleat < 0 || cleat >= Names.Length)
			{
				return "cleat " + cleat;
			}
			return Names[cleat];
		}
	}
	internal sealed class PortFrame
	{
		internal bool Ok;

		internal string Reason;

		internal bool NoRecovery;

		internal bool RadialSearch;

		internal bool OffshoreOnly;

		internal StationPolicy StationPolicy;

		internal StationRegion? StationRegion;

		internal DockChain Recovery;

		internal BerthInput Base;

		internal List<Pier> Piers = new List<Pier>();

		internal float Standoff;

		internal Vec2 StationOrigin;

		internal Vec2 StationForward;

		internal Vec2[] StationNormals = new Vec2[0];

		internal float StationStart;

		internal Pier Reference;

		internal List<Vec2[]> DockSegments = new List<Vec2[]>();

		internal List<int[]> StructureChains = new List<int[]>();

		internal List<Vec2[]> CornerLinks = new List<Vec2[]>();
	}
	internal sealed class TierEvaluation
	{
		internal DockChain Recovery;

		internal PortFrame Frame;

		internal readonly List<Pier> Piers = new List<Pier>();

		internal readonly List<Candidate> Candidates = new List<Candidate>();

		internal Candidate Best;

		internal string Reason;
	}
	internal static class BerthTiers
	{
		internal const float TwinMinSeparation = 3f;

		internal const float TwinMaxSeparation = 20f;

		internal const float CentreSideCosine = 0.5f;

		internal const float SlotLengthAllowance = 1f;

		internal const float SlotDockTolerance = 0.25f;

		internal const float StationStep = 2f;

		internal const float StationRange = 40f;

		internal const float OffshoreRange = 60f;

		internal const int OffshoreBearings = 16;

		internal const float OpenWaterHeadingToleranceDegrees = 1f;

		private static readonly float OpenWaterHeadingCos = (float)Math.Cos(Math.PI / 180.0);

		internal const float StationShoreClearance = 6f;

		internal const float NoRecoveryStandoff = 4.5f;

		internal const float MarkerHalfSpan = 8f;

		internal const float SyntheticRange = 25f;

		internal const float MinPierLengthFactor = 0.6f;

		internal const float SpaceCap = 8f;

		internal const float SpaceBucket = 1f;

		internal const float ComfortableLine = 8f;

		internal const float OwnEdgeRange = 4f;

		private static readonly float CosTwin = (float)Math.Cos(Math.PI / 12.0);

		internal static bool TrySyntheticRecovery(IList<Vec2> cleats, int count, Vec2 boatPos, Vec2 boatForward, out int r1, out int r2, out string reason)
		{
			r1 = (r2 = -1);
			reason = null;
			if (cleats == null || !boatPos.IsFinite)
			{
				reason = "no island cleats or marker";
				return false;
			}
			int[] array = (from i in Enumerable.Range(0, Math.Min(count, cleats.Count))
				where cleats[i].IsFinite
				orderby Vec2.Distance(cleats[i], boatPos)
				select i).Take(2).ToArray();
			if (array.Length < 2)
			{
				reason = "fewer than two island cleats";
				return false;
			}
			Vec2 vec = cleats[array[0]];
			Vec2 vec2 = cleats[array[1]];
			float num = Vec2.Distance(vec2, boatPos);
			if (!(num <= 25f))
			{
				reason = "the second nearest island cleat is " + DockGeometry.F(num) + " m from the marker (at most " + DockGeometry.F(25f) + " m)";
				return false;
			}
			float num2 = Vec2.Distance(vec, vec2);
			if (!(num2 >= 4f) || !(num2 <= 25f))
			{
				reason = "the nearest island cleats are " + DockGeometry.F(num2) + " m apart (4-25 m required)";
				return false;
			}
			float num3 = Math.Abs(Vec2.Cross((vec2 - vec) * (1f / num2), boatPos - vec));
			if (!(num3 >= 1f))
			{
				reason = "the marker is " + DockGeometry.F(num3) + " m off the island cleats' line (at least 1 m required)";
				return false;
			}
			bool flag = boatForward.IsFinite && Vec2.Dot(vec2 - vec, boatForward) > 0f;
			r1 = (flag ? array[1] : array[0]);
			r2 = (flag ? array[0] : array[1]);
			return true;
		}

		internal static Vec2 MarkerDockSide(Vec2 forward, bool goLeft)
		{
			Vec2 rightOfForward = (forward * (1f / forward.Length)).RightOfForward;
			if (!goLeft)
			{
				return -rightOfForward;
			}
			return rightOfForward;
		}

		internal static void MarkerReference(Vec2 boatPos, Vec2 forward, bool goLeft, out Vec2 front, out Vec2 back)
		{
			Vec2 vec = forward * (1f / forward.Length);
			Vec2 vec2 = boatPos + MarkerDockSide(forward, goLeft) * 4.5f;
			front = vec2 + vec * 8f;
			back = vec2 - vec * 8f;
		}

		internal static bool AgreesWithMarker(Vec2 r1, Vec2 r2, Vec2 boatPos, Vec2 forward, bool goLeft)
		{
			Vec2 vec = r2 - r1;
			float num = ((Vec2.Cross(vec, boatPos - r1) > 0f) ? 1f : (-1f));
			return Vec2.Dot(-(vec * (1f / vec.Length)).Perp * num, MarkerDockSide(forward, goLeft)) > 0f;
		}

		internal static void AddPlanned(PortInput port, string owner, Candidate planned)
		{
			if (port == null || planned == null || !planned.Accepted)
			{
				return;
			}
			List<Obstacle> obstacles = new List<Obstacle>(port.Obstacles ?? new Obstacle[0])
			{
				new Obstacle
				{
					Name = "planned berth of " + owner,
					Root = planned.Root,
					Measurable = true,
					Hull = planned.Hull
				}
			};
			port.Obstacles = obstacles;
			if (planned.Tier == 4 || port.Free == null)
			{
				return;
			}
			int[] array = new int[2] { planned.FrontCleat, planned.BackCleat };
			foreach (int num in array)
			{
				if (num >= 0 && num < port.Free.Length)
				{
					port.Free[num] = false;
					if (port.Unavailable != null && num < port.Unavailable.Length)
					{
						port.Unavailable[num] = "planned berth of " + owner;
					}
				}
			}
		}

		internal static PortFrame Frame(PortInput port)
		{
			PortFrame portFrame = new PortFrame
			{
				NoRecovery = port.NoRecovery,
				RadialSearch = (port.MarkerReference && port.NoDock),
				OffshoreOnly = port.OffshoreOnly,
				StationPolicy = port.StationPolicy,
				StationRegion = port.StationRegion
			};
			if (!port.NoRecovery || port.MarkerReference)
			{
				portFrame.Recovery = DockGeometry.BuildChain(port.Cleats, port.R1, port.R2, port.BoatPos);
				if (!portFrame.Recovery.Ok)
				{
					portFrame.Reason = portFrame.Recovery.Reason;
					return portFrame;
				}
				portFrame.Base = RecoveryInput(port, portFrame.Recovery);
				portFrame.Base.NoRecovery = port.NoRecovery;
				portFrame.Base.Ground = port.Ground;
				portFrame.Piers = BuildPiers(port, portFrame.Recovery);
				FillStructure(portFrame, port);
				portFrame.Base.OtherPiers = portFrame.DockSegments;
				portFrame.Standoff = portFrame.Recovery.Standoff;
				Vec2 vec = portFrame.Recovery.Points[portFrame.Recovery.R2] - portFrame.Recovery.Points[portFrame.Recovery.R1];
				portFrame.StationOrigin = port.BoatPos;
				portFrame.StationForward = port.BoatForward * (1f / port.BoatForward.Length);
				portFrame.StationNormals = new Vec2[1] { portFrame.Recovery.Normal(vec * (1f / vec.Length)) };
				portFrame.StationStart = 4.5f;
				portFrame.Ok = true;
				return portFrame;
			}
			portFrame.Recovery = new DockChain
			{
				Ok = true,
				Order = new int[0],
				Points = new Vec2[0],
				R1 = -1,
				R2 = -1
			};
			portFrame.Piers = BuildPiers(port, portFrame.Recovery);
			FillStructure(portFrame, port);
			portFrame.Reference = portFrame.Piers.OrderByDescending((Pier pier) => pier.Length).FirstOrDefault();
			if (portFrame.Reference == null || portFrame.Reference.Length < 4f)
			{
				portFrame.Reason = "no recovery berth and no pier to take a reference from";
				return portFrame;
			}
			Pier reference = portFrame.Reference;
			Vec2 vec2 = (reference.Points[reference.Points.Length - 1] - reference.Points[0]) * (1f / reference.Length);
			portFrame.Base = new BerthInput
			{
				BoatPos = (reference.Points[0] + reference.Points[reference.Points.Length - 1]) * 0.5f,
				BoatForward = vec2,
				AlternateOffset = new Vec2(0f, 0f),
				Obstacles = port.Obstacles,
				NoRecovery = true,
				OtherPiers = portFrame.DockSegments,
				Ground = port.Ground
			};
			portFrame.Standoff = 4.5f;
			portFrame.StationOrigin = portFrame.Base.BoatPos;
			portFrame.StationForward = vec2;
			portFrame.StationNormals = ((reference.Side == 0) ? new Vec2[2]
			{
				vec2.Perp,
				-vec2.Perp
			} : new Vec2[1] { vec2.Perp * reference.Side });
			portFrame.StationStart = 4.5f;
			portFrame.Ok = true;
			return portFrame;
		}

		internal static TierEvaluation Evaluate(PortInput port, HullSpec hull)
		{
			PortFrame portFrame = Frame(port);
			TierEvaluation tierEvaluation = new TierEvaluation
			{
				Frame = portFrame,
				Recovery = portFrame.Recovery
			};
			if (!portFrame.Ok)
			{
				tierEvaluation.Reason = portFrame.Reason;
				return tierEvaluation;
			}
			if (!ValidHull(hull, out var reason))
			{
				tierEvaluation.Reason = reason;
				return tierEvaluation;
			}
			tierEvaluation.Piers.AddRange(portFrame.Piers);
			if (!portFrame.NoRecovery && !port.OffshoreOnly && !port.StationRegion.HasValue)
			{
				List<Candidate> candidates = DockGeometry.EvaluateAll(portFrame.Base, hull).Candidates;
				foreach (BerthSlot slot in port.Slots)
				{
					candidates.Add(EvaluateSlot(port, portFrame.Base, hull, slot));
				}
				tierEvaluation.Candidates.AddRange(candidates);
				tierEvaluation.Best = Rank(portFrame, port, candidates, byDistance: true);
				if (tierEvaluation.Best != null)
				{
					return tierEvaluation;
				}
			}
			if (!port.OffshoreOnly && !port.StationRegion.HasValue)
			{
				foreach (Pier pier in portFrame.Piers)
				{
					int[] array = ((pier.Side == 0) ? new int[2] { 1, -1 } : new int[1] { pier.Side });
					foreach (int side in array)
					{
						tierEvaluation.Candidates.AddRange(EvaluatePier(port, portFrame, hull, pier, side).Candidates);
					}
				}
				tierEvaluation.Best = Rank(portFrame, port, tierEvaluation.Candidates.Where((Candidate item) => item.Tier == 3), !portFrame.NoRecovery);
				if (tierEvaluation.Best != null)
				{
					return tierEvaluation;
				}
			}
			Candidate candidate = FindStation(portFrame, hull, 0f);
			tierEvaluation.Candidates.Add(candidate);
			if (candidate.Accepted)
			{
				tierEvaluation.Best = candidate;
			}
			if (tierEvaluation.Best == null)
			{
				tierEvaluation.Reason = "No tier offered a valid berth.";
			}
			return tierEvaluation;
		}

		private static bool ValidHull(HullSpec hull, out string reason)
		{
			reason = ((hull == null || !DockGeometry.Finite(hull.Length) || !DockGeometry.Finite(hull.Radius) || !DockGeometry.Finite(hull.Extra) || !DockGeometry.Finite(hull.CenterRight) || !DockGeometry.Finite(hull.CenterForward) || hull.Radius <= 0f || hull.Length < 2f * hull.Radius || hull.Extra < 0f) ? "Hull measurements are invalid." : null);
			return reason == null;
		}

		internal static Candidate Rank(PortFrame frame, PortInput port, IEnumerable<Candidate> candidates, bool byDistance)
		{
			return Ordered(frame, port, candidates, byDistance).FirstOrDefault();
		}

		internal static List<Candidate> Ordered(PortFrame frame, PortInput port, IEnumerable<Candidate> candidates, bool byDistance)
		{
			List<Candidate> list = candidates.Where((Candidate item) => item.Accepted).ToList();
			foreach (Candidate item in list)
			{
				item.Space = Space(frame, port, item);
			}
			IOrderedEnumerable<Candidate> source = from item in list
				orderby Math.Floor(item.Space / 1f) descending, (!(Longest(item) <= 8f)) ? Longest(item) : 0f
				select item;
			return (byDistance ? source.ThenByDescending((Candidate item) => item.Distance) : source.ThenByDescending((Candidate item) => item.PierLength).ThenBy(Longest)).ToList();
		}

		private static float Longest(Candidate candidate)
		{
			return Math.Max(candidate.FrontLine, candidate.BackLine);
		}

		internal static float Space(PortFrame frame, PortInput port, Candidate candidate)
		{
			float val = 8f;
			if (candidate.Blocker != null && DockGeometry.Finite(candidate.MinimumGap))
			{
				val = Math.Min(val, candidate.MinimumGap);
			}
			int[] array = null;
			if (candidate.FrontCleat >= 0 && candidate.BackCleat >= 0)
			{
				array = frame.StructureChains.FirstOrDefault((int[] chain) => chain.Contains(candidate.FrontCleat) && chain.Contains(candidate.BackCleat)) ?? frame.StructureChains.FirstOrDefault((int[] chain) => chain.Contains(candidate.FrontCleat) || chain.Contains(candidate.BackCleat));
			}
			Vec2[] array2 = array?.Select((int index) => port.Cleats[index]).ToArray();
			foreach (int[] structureChain in frame.StructureChains)
			{
				if (structureChain != array)
				{
					val = Math.Min(val, DockGeometry.ChainClearance(candidate.Hull, structureChain.Select((int index) => port.Cleats[index]).ToArray()));
				}
			}
			foreach (Vec2[] cornerLink in frame.CornerLinks)
			{
				val = Math.Min(val, DockGeometry.ChainClearance(candidate.Hull, cornerLink));
			}
			for (int num = 0; num < port.Cleats.Length; num++)
			{
				if (candidate.FrontCleat != num && candidate.BackCleat != num)
				{
					Vec2 vec = port.Cleats[num];
					if (array2 == null || !(((array2.Length == 1) ? Vec2.Distance(vec, array2[0]) : DockGeometry.ChainClearance(new Capsule2(vec, vec, 0f), array2)) <= 4f))
					{
						val = Math.Min(val, DockGeometry.PointSegmentDistance(vec, candidate.Hull.A, candidate.Hull.B) - candidate.Hull.Radius);
					}
				}
			}
			return Math.Max(val, float.MinValue);
		}

		private static void FillStructure(PortFrame frame, PortInput port)
		{
			frame.DockSegments = DockSegments(port, frame.Recovery, out var chainsOut, out var linksOut);
			frame.StructureChains = chainsOut;
			frame.CornerLinks = linksOut;
		}

		internal static List<Vec2[]> DockSegments(PortInput port, DockChain recovery, out List<int[]> chainsOut, out List<Vec2[]> linksOut)
		{
			PortInput port2 = new PortInput
			{
				Cleats = port.Cleats,
				Names = port.Names,
				R1 = port.R1,
				R2 = port.R2,
				NoRecovery = port.NoRecovery,
				MarkerReference = port.MarkerReference,
				IslandCentre = port.IslandCentre,
				HasIslandCentre = port.HasIslandCentre
			};
			List<Vec2[]> list = new List<Vec2[]>();
			List<int[]> list2 = new List<int[]>();
			if (recovery != null && recovery.Points.Length >= 2)
			{
				list.Add(recovery.Points);
				list2.Add(recovery.Order);
			}
			foreach (Pier item in BuildPiers(port2, recovery ?? new DockChain
			{
				Order = new int[0],
				Points = new Vec2[0]
			}))
			{
				list.Add(item.Points);
				list2.Add(item.Order);
			}
			HashSet<long> hashSet = new HashSet<long>();
			List<Vec2[]> list3 = new List<Vec2[]>();
			foreach (int[] item2 in list2)
			{
				int[] array = new int[2]
				{
					item2[0],
					item2[^1]
				};
				foreach (int num in array)
				{
					int num2 = -1;
					float num3 = 25f;
					for (int j = 0; j < port.Cleats.Length; j++)
					{
						if (!item2.Contains(j))
						{
							float num4 = Vec2.Distance(port.Cleats[num], port.Cleats[j]);
							if (num4 > 0.01f && num4 <= num3)
							{
								num3 = num4;
								num2 = j;
							}
						}
					}
					if (num2 >= 0 && hashSet.Add((long)Math.Min(num, num2) * 100000L + Math.Max(num, num2)))
					{
						list.Add(new Vec2[2]
						{
							port.Cleats[num],
							port.Cleats[num2]
						});
						list3.Add(new Vec2[2]
						{
							port.Cleats[num],
							port.Cleats[num2]
						});
					}
				}
			}
			list.AddRange(port.Cleats.Select((Vec2 point) => new Vec2[2] { point, point }));
			chainsOut = list2;
			linksOut = list3;
			return list;
		}

		internal static BerthInput RecoveryInput(PortInput port, DockChain chain)
		{
			int num = chain.Order.Length;
			BerthInput berthInput = new BerthInput
			{
				Chain = chain,
				Available = new bool[num],
				Unavailable = new string[num],
				Names = new string[num],
				BoatPos = port.BoatPos,
				BoatForward = port.BoatForward,
				AlternateOffset = port.AlternateOffset,
				Obstacles = port.Obstacles
			};
			for (int i = 0; i < num; i++)
			{
				int cleat = chain.Order[i];
				berthInput.Names[i] = port.Name(cleat);
				berthInput.Unavailable[i] = ((i == chain.R1 || i == chain.R2) ? "recovery berth cleat" : Unavailable(port, cleat));
				berthInput.Available[i] = berthInput.Unavailable[i] == null;
			}
			return berthInput;
		}

		private static string Unavailable(PortInput port, int cleat)
		{
			if (cleat != port.R1 && cleat != port.R2)
			{
				if (port.Free != null && cleat >= 0 && cleat < port.Free.Length && !port.Free[cleat])
				{
					if (port.Unavailable == null || port.Unavailable[cleat] == null)
					{
						return "claimed";
					}
					return port.Unavailable[cleat];
				}
				return null;
			}
			return "recovery berth cleat";
		}

		internal static Candidate EvaluateSlot(PortInput port, BerthInput input, HullSpec hull, BerthSlot slot)
		{
			Candidate candidate = null;
			float[] array = new float[2] { 1f, -1f };
			foreach (float num in array)
			{
				Candidate candidate2 = new Candidate
				{
					Tier = 2,
					SlotOwner = slot.Owner,
					I = -1,
					J = -1
				};
				string text = SlotProblem(port, slot);
				if (text != null)
				{
					return DockGeometry.Reject(candidate2, text);
				}
				Vec2 vec = slot.Forward * (num / slot.Forward.Length);
				int num2 = OasisSaleFront(port, slot);
				if (num2 >= 0)
				{
					int num3 = ((num2 == slot.FrontCleat) ? slot.BackCleat : slot.FrontCleat);
					if (Vec2.Dot(vec, port.Cleats[num2] - port.Cleats[num3]) <= 0f)
					{
						continue;
					}
				}
				float num4 = Math.Max(0f, hull.Length * 0.5f - hull.Radius);
				candidate2.Forward = vec;
				candidate2.Direction = vec;
				candidate2.Hull = new Capsule2(slot.Center + vec * num4, slot.Center - vec * num4, hull.Radius);
				int num5 = ((Vec2.Dot(port.Cleats[slot.FrontCleat] - slot.Center, vec) >= Vec2.Dot(port.Cleats[slot.BackCleat] - slot.Center, vec)) ? slot.FrontCleat : slot.BackCleat);
				int num6 = ((num5 == slot.FrontCleat) ? slot.BackCleat : slot.FrontCleat);
				candidate2.FrontCleat = num5;
				candidate2.BackCleat = num6;
				candidate2.Span = Vec2.Distance(port.Cleats[num5], port.Cleats[num6]);
				bool flag = hull.Length <= slot.Length + 1f;
				float num7 = float.PositiveInfinity;
				float num8 = float.PositiveInfinity;
				float num9 = Math.Max(0f, slot.Length * 0.5f - slot.Radius);
				Capsule2 hull2 = new Capsule2(slot.Center + vec * num9, slot.Center - vec * num9, Math.Max(slot.Radius, 0.01f));
				if (input.OtherPiers != null)
				{
					foreach (Vec2[] otherPier in input.OtherPiers)
					{
						num7 = Math.Min(num7, DockGeometry.ChainClearance(candidate2.Hull, otherPier));
						if (slot.Radius > 0f)
						{
							num8 = Math.Min(num8, DockGeometry.ChainClearance(hull2, otherPier));
						}
					}
				}
				candidate2.DockClearance = num7;
				float num10 = Math.Min(0.5f, num8 - 0.25f);
				if (!(num7 >= num10))
				{
					DockGeometry.Reject(candidate2, "Hull is " + DockGeometry.F(num7) + " m from the island's docks; at least " + DockGeometry.F(num10) + " m is required.");
				}
				else if (DockGeometry.CheckClearances(candidate2, input, hull, flag))
				{
					DockGeometry.SetRoot(candidate2, input, hull);
					string text2 = DockGeometry.GroundProblem(candidate2.Hull, input.Ground);
					if (text2 != null)
					{
						DockGeometry.Reject(candidate2, Rejection.ShallowGround, text2);
					}
					else if (DockGeometry.CheckLines(candidate2, hull, port.Cleats[num5], port.Cleats[num6]))
					{
						candidate2.Accepted = true;
						candidate2.Reason = "accepted" + (flag ? " (recovery gaps waived)" : "");
					}
				}
				else
				{
					Vec2 vec2 = vec.Perp;
					if (Vec2.Dot(vec2, slot.Center - (port.Cleats[num5] + port.Cleats[num6]) * 0.5f) < 0f)
					{
						vec2 = -vec2;
					}
					DockGeometry.TieOff(candidate2, input, hull, vec2, port.Cleats[num5], port.Cleats[num6], input.OtherPiers);
				}
				if (candidate == null || (candidate2.Accepted && (!candidate.Accepted || Math.Max(candidate2.FrontLine, candidate2.BackLine) < Math.Max(candidate.FrontLine, candidate.BackLine))))
				{
					candidate = candidate2;
				}
			}
			return candidate ?? DockGeometry.Reject(new Candidate
			{
				Tier = 2,
				SlotOwner = slot.Owner
			}, "Vacated Oasis sale berth has no heading toward its departing cleat.");
		}

		private static int OasisSaleFront(PortInput port, BerthSlot slot)
		{
			if (!port.OasisNativeSaleHeading || port.Names == null)
			{
				return -1;
			}
			string text = port.Name(slot.FrontCleat);
			string text2 = port.Name(slot.BackCleat);
			int num = ((text == "dock_mooring (5)#1" && text2 == "dock_mooring (7)#1") ? slot.FrontCleat : ((text == "dock_mooring (7)#1" && text2 == "dock_mooring (5)#1") ? slot.BackCleat : (-1)));
			if (num < 0)
			{
				return -1;
			}
			int num2 = ((num == slot.FrontCleat) ? slot.BackCleat : slot.FrontCleat);
			Vec2 a = port.Cleats[num2] - port.Cleats[num];
			float num3 = Vec2.Cross(a, slot.Center - port.Cleats[num]);
			float num4 = Vec2.Cross(a, port.BoatPos - port.Cleats[num]);
			if (!DockGeometry.Finite(num3) || !DockGeometry.Finite(num4) || !(num3 * num4 < 0f))
			{
				return -1;
			}
			return num;
		}

		private static string SlotProblem(PortInput port, BerthSlot slot)
		{
			if (slot == null || slot.FrontCleat < 0 || slot.BackCleat < 0 || slot.FrontCleat >= port.Cleats.Length || slot.BackCleat >= port.Cleats.Length || slot.FrontCleat == slot.BackCleat)
			{
				return "Vacated berth cleats are not island cleats of this port.";
			}
			if (!slot.Center.IsFinite || !slot.Forward.IsFinite || slot.Forward.Length < 0.5f || !DockGeometry.Finite(slot.Length))
			{
				return "Vacated berth pose is invalid.";
			}
			if (slot.FrontCleat == port.R1 || slot.FrontCleat == port.R2 || slot.BackCleat == port.R1 || slot.BackCleat == port.R2)
			{
				return "Vacated berth uses the recovery cleats.";
			}
			string text = Unavailable(port, slot.FrontCleat) ?? Unavailable(port, slot.BackCleat);
			if (text == null)
			{
				return null;
			}
			return "Vacated berth cleat is unavailable: " + text;
		}

		internal static List<Pier> BuildPiers(PortInput port, DockChain recovery)
		{
			bool[] array = new bool[port.Cleats.Length];
			int[] order = recovery.Order;
			foreach (int num in order)
			{
				array[num] = true;
			}
			if (port.R1 >= 0 && port.R1 < array.Length)
			{
				array[port.R1] = true;
			}
			if (port.R2 >= 0 && port.R2 < array.Length)
			{
				array[port.R2] = true;
			}
			if (port.Claimed != null)
			{
				for (int j = 0; j < array.Length && j < port.Claimed.Length; j++)
				{
					ref bool reference = ref array[j];
					reference |= port.Claimed[j];
				}
			}
			if (recovery.Points.Length >= 2)
			{
				for (int k = 0; k < array.Length; k++)
				{
					if (!array[k] && DockGeometry.ChainClearance(new Capsule2(port.Cleats[k], port.Cleats[k], 0f), recovery.Points) <= 1.5f)
					{
						array[k] = true;
					}
				}
			}
			List<Pier> list = new List<Pier>();
			while (true)
			{
				int num2 = -1;
				int num3 = -1;
				float num4 = float.PositiveInfinity;
				for (int l = 0; l < port.Cleats.Length; l++)
				{
					if (array[l])
					{
						continue;
					}
					for (int m = l + 1; m < port.Cleats.Length; m++)
					{
						if (!array[m])
						{
							float num5 = Vec2.Distance(port.Cleats[l], port.Cleats[m]);
							if (num5 >= 4f && num5 <= 25f && num5 < num4)
							{
								num4 = num5;
								num2 = l;
								num3 = m;
							}
						}
					}
				}
				if (num2 < 0)
				{
					break;
				}
				array[num2] = (array[num3] = true);
				Vec2 vec = (port.Cleats[num3] - port.Cleats[num2]) * (1f / num4);
				List<int> list2 = DockGeometry.Extend(port.Cleats, array, port.Cleats[num2], -vec);
				List<int> collection = DockGeometry.Extend(port.Cleats, array, port.Cleats[num3], vec);
				List<int> order2 = new List<int>();
				for (int num6 = list2.Count - 1; num6 >= 0; num6--)
				{
					order2.Add(list2[num6]);
				}
				order2.Add(num2);
				order2.Add(num3);
				order2.AddRange(collection);
				List<int> list3 = new List<int>();
				foreach (int item in order2)
				{
					for (int n = 0; n < array.Length; n++)
					{
						if (!array[n] && Vec2.Distance(port.Cleats[n], port.Cleats[item]) < 4f)
						{
							array[n] = true;
							list3.Add(n);
						}
					}
				}
				Vec2 pierAxis = port.Cleats[order2[order2.Count - 1]] - port.Cleats[order2[0]];
				pierAxis *= 1f / Math.Max(pierAxis.Length, 0.001f);
				float[] source = (from index in order2.Concat(list3)
					select Vec2.Dot(port.Cleats[index] - port.Cleats[order2[0]], pierAxis)).ToArray();
				float extent = source.Max() - source.Min();
				list.Add(new Pier
				{
					Order = order2.ToArray(),
					Points = order2.Select((int index) => port.Cleats[index]).ToArray(),
					Cluster = list3.ToArray(),
					Extent = extent
				});
			}
			List<Vec2[]> list4 = new List<Vec2[]> { recovery.Points };
			list4.AddRange(list.Select((Pier pier2) => pier2.Points));
			foreach (Pier pier in list)
			{
				pier.Side = WaterSide(pier.Points, list4.Where((Vec2[] points) => points != pier.Points).ToList(), port.HasIslandCentre ? new Vec2?(port.IslandCentre) : ((Vec2?)null), out var reason);
				pier.SideReason = reason;
			}
			return list;
		}

		internal static int WaterSide(Vec2[] points, IList<Vec2[]> others, Vec2? islandCentre, out string reason)
		{
			reason = null;
			if (points == null || points.Length < 2)
			{
				reason = "single cleat";
				return 0;
			}
			Vec2 vec = points[0];
			Vec2 vec2 = points[^1] - vec;
			float length = vec2.Length;
			if (!(length > 0.5f))
			{
				reason = "degenerate pier";
				return 0;
			}
			Vec2 vec3 = vec2 * (1f / length);
			HashSet<int> hashSet = new HashSet<int>();
			foreach (Vec2[] item in others ?? new Vec2[0][])
			{
				if (item == null || item.Length < 2)
				{
					continue;
				}
				Vec2 vec4 = item[^1] - item[0];
				float length2 = vec4.Length;
				if (!(length2 > 0.5f) || Math.Abs(Vec2.Dot(vec3, vec4 * (1f / length2))) < CosTwin - 1E-06f)
				{
					continue;
				}
				Vec2 vec5 = Centroid(item);
				float num = Vec2.Cross(vec3, vec5 - vec);
				if (!(Math.Abs(num) < 3f) && !(Math.Abs(num) > 20f))
				{
					float val = Vec2.Dot(item[0] - vec, vec3);
					float val2 = Vec2.Dot(item[^1] - vec, vec3);
					if (!(Math.Max(val, val2) < 0f) && !(Math.Min(val, val2) > length))
					{
						hashSet.Add((num > 0f) ? 1 : (-1));
					}
				}
			}
			if (hashSet.Count == 1)
			{
				reason = "away from a parallel pier";
				return -hashSet.First();
			}
			if (hashSet.Count == 2)
			{
				reason = "parallel piers on both sides";
				return 0;
			}
			if (islandCentre.HasValue && islandCentre.Value.IsFinite)
			{
				Vec2 vec6 = islandCentre.Value - Centroid(points);
				float length3 = vec6.Length;
				if (length3 > 0.5f)
				{
					float num2 = Vec2.Dot(vec3.Perp, vec6 * (1f / length3));
					if (Math.Abs(num2) >= 0.5f)
					{
						reason = "away from the island centre";
						if (!(num2 > 0f))
						{
							return 1;
						}
						return -1;
					}
				}
				reason = "pier runs toward the island centre";
				return 0;
			}
			reason = "no island centre";
			return 0;
		}

		private static Vec2 Centroid(Vec2[] points)
		{
			Vec2 vec = new Vec2(0f, 0f);
			foreach (Vec2 vec2 in points)
			{
				vec += vec2;
			}
			return vec * (1f / (float)points.Length);
		}

		internal static Evaluation EvaluatePier(PortInput port, PortFrame frame, HullSpec hull, Pier pier, int side)
		{
			if (pier.Extent < 0.6f * hull.Length)
			{
				Evaluation evaluation = new Evaluation
				{
					Reason = "Pier too small"
				};
				evaluation.Candidates.Add(DockGeometry.Reject(new Candidate
				{
					Tier = 3,
					Estimated = true,
					Side = side,
					I = -1,
					J = -1,
					PierLength = pier.Extent,
					FrontCleat = pier.Order[0],
					BackCleat = pier.Order[pier.Order.Length - 1]
				}, "Pier " + string.Join(">", pier.Order.Select(port.Name).ToArray()) + " spans " + DockGeometry.F(pier.Extent) + " m with its clustered cleats; this hull needs " + DockGeometry.F(0.6f * hull.Length) + " m."));
				return evaluation;
			}
			DockChain chain = new DockChain
			{
				Ok = true,
				Order = pier.Order,
				Points = pier.Points,
				R1 = -1,
				R2 = -1,
				Side = side,
				Standoff = frame.Standoff
			};
			int num = pier.Order.Length;
			List<Vec2[]> dockSegments = frame.DockSegments;
			BerthInput berthInput = new BerthInput
			{
				Chain = chain,
				Available = new bool[num],
				Unavailable = new string[num],
				Names = new string[num],
				BoatPos = frame.Base.BoatPos,
				BoatForward = frame.Base.BoatForward,
				AlternateOffset = frame.Base.AlternateOffset,
				Obstacles = frame.Base.Obstacles,
				NoRecovery = frame.NoRecovery,
				OtherPiers = dockSegments,
				Ground = frame.Base.Ground
			};
			for (int i = 0; i < num; i++)
			{
				berthInput.Names[i] = port.Name(pier.Order[i]);
				berthInput.Unavailable[i] = Unavailable(port, pier.Order[i]);
				berthInput.Available[i] = berthInput.Unavailable[i] == null;
			}
			Evaluation evaluation2 = DockGeometry.EvaluateAll(berthInput, hull);
			foreach (Candidate candidate in evaluation2.Candidates)
			{
				candidate.Tier = 3;
				candidate.Estimated = true;
				candidate.Side = side;
				candidate.PierLength = pier.Extent;
			}
			return evaluation2;
		}

		internal static Candidate FindStation(PortFrame frame, HullSpec hull, float minimumOutward)
		{
			Candidate candidate = null;
			foreach (Candidate item in StationSpots(frame, hull, minimumOutward))
			{
				if (item.Accepted)
				{
					return item;
				}
				candidate = item;
			}
			if (candidate == null)
			{
				candidate = new Candidate
				{
					Tier = 4,
					Estimated = true
				};
			}
			if (frame.StationRegion.HasValue)
			{
				return DockGeometry.Reject(candidate, "No compatible station-kept spot in the port's offshore gap (last: " + candidate.Reason + ")");
			}
			if (frame.RadialSearch)
			{
				return DockGeometry.Reject(candidate, "No station-kept spot within " + DockGeometry.F(60f) + " m of the authored marker (last: " + candidate.Reason + ")");
			}
			return DockGeometry.Reject(candidate, "No station-kept spot within " + DockGeometry.F(40f) + " m outward of the " + (frame.NoRecovery ? "reference pier" : "recovery berth") + " (last: " + candidate.Reason + ")");
		}

		internal static IEnumerable<Candidate> StationSpots(PortFrame frame, HullSpec hull, float minimumOutward)
		{
			if (frame.StationRegion.HasValue)
			{
				foreach (Candidate item in RegionSpots(frame, hull))
				{
					yield return item;
				}
				yield break;
			}
			if (frame.RadialSearch)
			{
				foreach (Candidate item2 in OffshoreSpots(frame, hull, minimumOutward))
				{
					yield return item2;
				}
				yield break;
			}
			List<Vec2[]> segments = frame.DockSegments;
			float start = Math.Max(minimumOutward, StationStart(frame, hull));
			if (!DockGeometry.Finite(start) || start < 0f)
			{
				yield break;
			}
			float[] shifts = new float[5] { 0f, 0.5f, -0.5f, 1f, -1f };
			int steps = 0;
			float offset = start;
			while ((float)steps <= 20f && offset <= start + 40f + 0.001f)
			{
				Vec2[] stationNormals = frame.StationNormals;
				foreach (Vec2 normal in stationNormals)
				{
					float[] array = shifts;
					foreach (float num in array)
					{
						Vec2 center = frame.StationOrigin + normal * offset + frame.StationForward * (num * hull.Length);
						Vec2 forward = (frame.OffshoreOnly ? normal : frame.StationForward);
						if (StationPolicyProblem(frame, hull, center, translated: false) != null)
						{
							continue;
						}
						if (frame.StationPolicy.LeftHullWidths > 0f || frame.StationPolicy.BackHullLengths > 0f)
						{
							if (!EvaluateStation(frame.Base, hull, center, forward, segments).Accepted)
							{
								continue;
							}
							center += frame.StationForward.Perp * (frame.StationPolicy.LeftHullWidths * 2f * hull.Radius);
							if (frame.StationPolicy.BackHullLengths > 0f)
							{
								if (!EvaluateStation(frame.Base, hull, center, forward, segments).Accepted)
								{
									continue;
								}
								center -= frame.StationForward * (frame.StationPolicy.BackHullLengths * hull.Length);
							}
							if (StationPolicyProblem(frame, hull, center) != null)
							{
								continue;
							}
						}
						Candidate candidate = EvaluateStation(frame.Base, hull, center, forward, segments);
						candidate.Outward = offset;
						candidate.Shift = num * hull.Length;
						yield return candidate;
					}
				}
				offset += 2f;
				int num2 = steps + 1;
				steps = num2;
			}
		}

		private static float StationStart(PortFrame frame, HullSpec hull)
		{
			return (frame.NoRecovery ? frame.StationStart : (hull.Radius + 4f + 2f)) + frame.StationPolicy.OutwardHullLengths * hull.Length;
		}

		private static Vec2 StationOrigin(PortFrame frame, HullSpec hull)
		{
			return frame.StationOrigin + frame.StationForward.Perp * (frame.StationPolicy.LeftHullWidths * 2f * hull.Radius) - frame.StationForward * (frame.StationPolicy.BackHullLengths * hull.Length);
		}

		private static string StationPolicyProblem(PortFrame frame, HullSpec hull, Vec2 center, bool translated = true)
		{
			if (frame.StationRegion.HasValue)
			{
				StationRegion value = frame.StationRegion.Value;
				if (!ValidRegion(value))
				{
					return "Station search region is invalid.";
				}
				Vec2 a = center - frame.StationOrigin;
				float num = Vec2.Dot(a, frame.StationForward.RightOfForward);
				float num2 = Vec2.Dot(a, frame.StationForward);
				if (!center.IsFinite || num < value.MinimumRight - 0.05f || num > value.MaximumRight + 0.05f || num2 < value.MinimumForward - 0.05f || num2 > value.MaximumForward + 0.05f)
				{
					return "Station spot is outside the port's offshore gap.";
				}
			}
			StationPolicy stationPolicy = frame.StationPolicy;
			if (!DockGeometry.Finite(stationPolicy.OutwardHullLengths) || stationPolicy.OutwardHullLengths < 0f || !DockGeometry.Finite(stationPolicy.LeftHullWidths) || stationPolicy.LeftHullWidths < 0f || !DockGeometry.Finite(stationPolicy.BackHullLengths) || stationPolicy.BackHullLengths < 0f)
			{
				return "Station departure policy is invalid.";
			}
			Vec2 delta = center - (translated ? StationOrigin(frame, hull) : frame.StationOrigin);
			if (stationPolicy.ForwardOnly && Vec2.Dot(delta, frame.StationForward) < -0.05f)
			{
				return "Station spot is behind the port's departure reference.";
			}
			if (stationPolicy.OutwardHullLengths > 0f && !frame.StationNormals.Any(delegate(Vec2 normal)
			{
				float num3 = normal.X * frame.StationForward.Z - normal.Z * frame.StationForward.X;
				return !(Math.Abs(num3) < 0.0001f) && (delta.X * frame.StationForward.Z - delta.Z * frame.StationForward.X) / num3 >= StationStart(frame, hull) - 0.05f;
			}))
			{
				return "Station spot leaves too little room outside the port's departure lane.";
			}
			return null;
		}

		private static bool ValidRegion(StationRegion region)
		{
			if (DockGeometry.Finite(region.MinimumRight) && DockGeometry.Finite(region.MaximumRight) && DockGeometry.Finite(region.MinimumForward) && DockGeometry.Finite(region.MaximumForward) && DockGeometry.Finite(region.TargetRight) && DockGeometry.Finite(region.TargetForward) && region.MinimumRight <= region.TargetRight && region.TargetRight <= region.MaximumRight && region.MinimumForward <= region.TargetForward)
			{
				return region.TargetForward <= region.MaximumForward;
			}
			return false;
		}

		private static IEnumerable<Candidate> RegionSpots(PortFrame frame, HullSpec hull)
		{
			StationRegion region = frame.StationRegion.Value;
			if (!ValidRegion(region))
			{
				yield break;
			}
			List<Vec2> list = new List<Vec2>();
			for (float num = region.MinimumRight; num <= region.MaximumRight; num += 2f)
			{
				for (float num2 = region.MinimumForward; num2 <= region.MaximumForward; num2 += 2f)
				{
					list.Add(new Vec2(num, num2));
				}
			}
			foreach (Vec2 item in list.OrderBy((Vec2 point) => (point.X - region.TargetRight) * (point.X - region.TargetRight) + (point.Z - region.TargetForward) * (point.Z - region.TargetForward)))
			{
				Vec2 center = frame.StationOrigin + frame.StationForward.RightOfForward * item.X + frame.StationForward * item.Z;
				if (StationPolicyProblem(frame, hull, center) == null)
				{
					Candidate candidate = EvaluateStation(frame.Base, hull, center, frame.StationForward, frame.DockSegments);
					candidate.Outward = item.X;
					candidate.Shift = item.Z;
					yield return candidate;
				}
			}
		}

		private static IEnumerable<Candidate> OffshoreSpots(PortFrame frame, HullSpec hull, float minimumOutward)
		{
			float num = Math.Max(minimumOutward, 4.5f);
			for (float radius = num; radius <= 60.001f; radius += 2f)
			{
				int bearing = 0;
				while (bearing < 16)
				{
					double num2 = Math.PI * 2.0 * (double)bearing / 16.0;
					Vec2 vec = frame.StationForward * (float)Math.Cos(num2) + frame.StationForward.Perp * (float)Math.Sin(num2);
					Vec2 center = frame.StationOrigin + vec * radius;
					Candidate candidate = EvaluateStation(frame.Base, hull, center, vec.Perp, frame.DockSegments);
					candidate.Outward = radius;
					yield return candidate;
					int num3 = bearing + 1;
					bearing = num3;
				}
			}
		}

		internal static Candidate EvaluateStation(BerthInput input, HullSpec hull, Vec2 center, Vec2 forward, IList<Vec2[]> segments)
		{
			Candidate candidate = new Candidate
			{
				Tier = 4,
				Estimated = true,
				I = -1,
				J = -1
			};
			if (!center.IsFinite || !forward.IsFinite || forward.Length < 0.5f)
			{
				return DockGeometry.Reject(candidate, "Station pose is invalid.");
			}
			forward *= 1f / forward.Length;
			float num = Math.Max(0f, hull.Length * 0.5f - hull.Radius);
			candidate.Forward = forward;
			candidate.Direction = forward;
			candidate.Hull = new Capsule2(center + forward * num, center - forward * num, hull.Radius);
			float num2 = float.PositiveInfinity;
			foreach (Vec2[] item in segments ?? new Vec2[0][])
			{
				num2 = Math.Min(num2, DockGeometry.ChainClearance(candidate.Hull, (item.Length == 1) ? new Vec2[2]
				{
					item[0],
					item[0]
				} : item));
			}
			candidate.DockClearance = num2;
			if (!(num2 >= 6f))
			{
				return DockGeometry.Reject(candidate, "Hull is " + DockGeometry.F(num2) + " m from island cleats; at least " + DockGeometry.F(6f) + " m is required.");
			}
			if (!DockGeometry.CheckClearances(candidate, input, hull, waiveRecovery: false))
			{
				return candidate;
			}
			DockGeometry.SetRoot(candidate, input, hull);
			if (!candidate.Root.IsFinite)
			{
				return DockGeometry.Reject(candidate, "Station pose produced a non-finite position.");
			}
			string text = DockGeometry.GroundProblem(candidate.Hull, input.Ground);
			if (text != null)
			{
				return DockGeometry.Reject(candidate, Rejection.ShallowGround, text);
			}
			candidate.Accepted = true;
			candidate.Reason = "accepted";
			return candidate;
		}

		internal static Candidate EvaluateMoored(PortInput port, HullSpec hull, int tier, int frontCleat, int backCleat, int side)
		{
			if (port.OffshoreOnly || port.StationRegion.HasValue)
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = tier,
					FrontCleat = frontCleat,
					BackCleat = backCleat
				}, "This port requires an offshore station-kept berth.");
			}
			PortFrame portFrame = Frame(port);
			Candidate candidate = new Candidate
			{
				Tier = tier,
				FrontCleat = frontCleat,
				BackCleat = backCleat
			};
			if (!portFrame.Ok)
			{
				return DockGeometry.Reject(candidate, portFrame.Reason);
			}
			if (!ValidHull(hull, out var reason))
			{
				return DockGeometry.Reject(candidate, reason);
			}
			switch (tier)
			{
			case 1:
			case 2:
			{
				if (portFrame.NoRecovery)
				{
					return DockGeometry.Reject(candidate, "Tier " + tier + " needs a recovery berth.");
				}
				if (tier == 1)
				{
					int num2 = Array.IndexOf(portFrame.Recovery.Order, frontCleat);
					int num3 = Array.IndexOf(portFrame.Recovery.Order, backCleat);
					if (num2 < 0 || num3 < 0 || num2 == num3)
					{
						return DockGeometry.Reject(candidate, "Planned cleats are not both on the recovery dock line.");
					}
					return Matches(DockGeometry.EvaluatePair(portFrame.Base, hull, Math.Min(num2, num3), Math.Max(num2, num3)), frontCleat, backCleat);
				}
				BerthSlot berthSlot = port.Slots.FirstOrDefault((BerthSlot item) => (item.FrontCleat == frontCleat && item.BackCleat == backCleat) || (item.FrontCleat == backCleat && item.BackCleat == frontCleat));
				if (berthSlot == null)
				{
					return DockGeometry.Reject(candidate, "No moving boat owns the planned vacated berth.");
				}
				return Matches(EvaluateSlot(port, portFrame.Base, hull, berthSlot), frontCleat, backCleat);
			}
			case 3:
			{
				Pier pier = portFrame.Piers.FirstOrDefault((Pier item) => item.Order.Contains(frontCleat) && item.Order.Contains(backCleat));
				if (pier == null)
				{
					return DockGeometry.Reject(candidate, "Planned cleats are no longer on one pier.");
				}
				int i = Array.IndexOf(pier.Order, frontCleat);
				int j = Array.IndexOf(pier.Order, backCleat);
				Candidate candidate2 = null;
				int[] array = ((side == 0) ? ((pier.Side == 0) ? new int[2] { 1, -1 } : new int[1] { pier.Side }) : new int[1] { side });
				foreach (int side2 in array)
				{
					Candidate candidate3 = EvaluatePier(port, portFrame, hull, pier, side2).Candidates.First((Candidate item) => item.I == Math.Min(i, j) && item.J == Math.Max(i, j));
					if (candidate2 == null || (candidate3.Accepted && !candidate2.Accepted))
					{
						candidate2 = candidate3;
					}
				}
				return Matches(candidate2, frontCleat, backCleat);
			}
			default:
				return DockGeometry.Reject(candidate, "Unknown moored tier " + tier + ".");
			}
		}

		private static Candidate Matches(Candidate candidate, int front, int back)
		{
			if (candidate.Accepted && (candidate.FrontCleat != front || candidate.BackCleat != back) && (candidate.FrontCleat != back || candidate.BackCleat != front))
			{
				return DockGeometry.Reject(candidate, "Planned cleats resolved to a different pair.");
			}
			return candidate;
		}

		internal static Candidate EvaluateStationAt(PortInput port, HullSpec hull, Vec2 center, Vec2 forward)
		{
			PortFrame portFrame = Frame(port);
			if (!portFrame.Ok)
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = 4,
					Estimated = true
				}, portFrame.Reason);
			}
			if (!ValidHull(hull, out var reason))
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = 4,
					Estimated = true
				}, reason);
			}
			string text = StationPolicyProblem(portFrame, hull, center);
			if (text != null)
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = 4,
					Estimated = true
				}, text);
			}
			if (portFrame.StationRegion.HasValue && (!forward.IsFinite || forward.Length < 0.5f || Vec2.Dot(portFrame.StationForward, forward * (1f / forward.Length)) < OpenWaterHeadingCos))
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = 4,
					Estimated = true
				}, "Planned station heading does not follow the port's offshore gap.");
			}
			if (portFrame.OffshoreOnly && (!forward.IsFinite || forward.Length < 0.5f || !portFrame.StationNormals.Any((Vec2 normal) => Vec2.Dot(normal, forward * (1f / forward.Length)) >= OpenWaterHeadingCos)))
			{
				return DockGeometry.Reject(new Candidate
				{
					Tier = 4,
					Estimated = true
				}, "Planned station heading does not face open water.");
			}
			return EvaluateStation(portFrame.Base, hull, center, forward, portFrame.DockSegments);
		}

		internal static Candidate StoredBerth(HullSpec hull, Vec2 root, Vec2 forward, int tier, int side, Vec2? frontCleat, Vec2? backCleat, out string fallback)
		{
			fallback = null;
			if (forward.IsFinite && forward.Length > 0f)
			{
				forward *= 1f / forward.Length;
			}
			Vec2 vec = CenterFromRoot(hull, root, forward);
			float num = Math.Max(0f, hull.Length * 0.5f - hull.Radius);
			Candidate candidate = new Candidate
			{
				Tier = tier,
				Side = ((tier == 3) ? side : 0),
				Root = root,
				Forward = forward,
				Direction = forward,
				Center = vec,
				Hull = new Capsule2(vec + forward * num, vec - forward * num, hull.Radius),
				I = -1,
				J = -1,
				Accepted = true,
				Reason = "stored pose"
			};
			if (tier != 4)
			{
				if (!frontCleat.HasValue || !backCleat.HasValue)
				{
					fallback = "stored cleats are unusable";
				}
				else if (!DockGeometry.AssignRopes(candidate, hull, frontCleat.Value, backCleat.Value))
				{
					fallback = "no distinct mooring ropes at opposite ends";
				}
				else
				{
					if (Math.Max(candidate.FrontLine, candidate.BackLine) <= 30f)
					{
						return candidate;
					}
					fallback = "mooring lines " + DockGeometry.F(candidate.FrontLine) + " / " + DockGeometry.F(candidate.BackLine) + " m; a native rope reaches " + DockGeometry.F(30f) + " m";
				}
			}
			candidate.Tier = 4;
			candidate.Side = 0;
			candidate.Estimated = true;
			candidate.FrontRope = (candidate.BackRope = -1);
			candidate.FrontLine = (candidate.BackLine = float.NaN);
			return candidate;
		}

		internal static Vec2 CenterFromRoot(HullSpec hull, Vec2 root, Vec2 forward)
		{
			forward *= 1f / forward.Length;
			return root + forward.RightOfForward * hull.CenterRight + forward * hull.CenterForward;
		}
	}
	internal enum CleatBlock
	{
		None,
		SpringHeld,
		Claimed
	}
	internal static class CleatAccess
	{
		internal static CleatBlock Check<TBody, TClaim>(TBody holder, TBody boat, ICollection<TBody> moving, IEnumerable<TClaim> claimants, Func<TClaim, TBody> bodyOf, out TBody heldBy, out TClaim claimedBy) where TBody : class where TClaim : class
		{
			heldBy = null;
			claimedBy = null;
			if (holder != null && holder != boat && (moving == null || !moving.Contains(holder)))
			{
				heldBy = holder;
				return CleatBlock.SpringHeld;
			}
			foreach (TClaim item in claimants ?? new TClaim[0])
			{
				if (item != null && bodyOf(item) != boat)
				{
					claimedBy = item;
					return CleatBlock.Claimed;
				}
			}
			return CleatBlock.None;
		}
	}
	internal sealed class BoatEntry
	{
		internal PurchasableBoat Boat;

		internal SaveableObject Saveable;

		internal Rigidbody Body;

		internal BoatMooringRopes Ropes;

		internal CapsuleCollider Hull;

		internal BoatSize? Size;

		internal bool Vanilla;

		internal HullSpec Spec;

		internal string DisplayName;

		internal string Origin;

		internal int Index => Saveable.sceneIndex;

		internal Identity Identity => new Identity(Index, IdentityName);

		internal string IdentityName => ((Object)((Component)Boat).gameObject).name;

		internal bool Purchased => Boat.isPurchased();

		internal string Label => DisplayName + " [" + Index + "|" + IdentityName + "]";
	}
	internal sealed class UnusableBoat
	{
		internal PurchasableBoat Boat;

		internal SaveableObject Saveable;

		internal string Reason;

		internal bool IntentionalExclusion;

		internal int Index
		{
			get
			{
				if (!((Object)(object)Saveable != (Object)null))
				{
					return -1;
				}
				return Saveable.sceneIndex;
			}
		}

		internal string Label => ((Object)((Component)Boat).gameObject).name + " (" + Index + ")";
	}
	internal sealed class BoatCatalog
	{
		internal readonly List<BoatEntry> Boats = new List<BoatEntry>();

		internal readonly List<string> Rejections = new List<string>();

		internal readonly List<UnusableBoat> Unusable = new List<UnusableBoat>();

		private static readonly FieldInfo SaveableField = AccessTools.Field(typeof(PurchasableBoat), "saveable");

		private static readonly FieldInfo PurchaseUiField = AccessTools.Field(typeof(PurchasableBoat), "purchaseUI");

		internal static bool IsBoatLike(SaveableObject saveable)
		{
			if ((Object)(object)saveable != (Object)null)
			{
				if (!((Object)(object)((Component)saveable).GetComponent<Rigidbody>() != (Object)null) && !((Object)(object)((Component)saveable).GetComponent<BoatDamage>() != (Object)null) && !((Object)(object)((Component)saveable).GetComponent("BoatProbes") != (Object)null))
				{
					return (Object)(object)((Component)saveable).GetComponent<BoatMooringRopes>() != (Object)null;
				}
				return true;
			}
			return false;
		}

		private void Reject(PurchasableBoat boat, SaveableObject saveable, string reason, bool intentionalExclusion = false)
		{
			if ((Object)(object)boat != (Object)null && (Object)(object)saveable != (Object)null && !boat.isPurchased() && !Unusable.Any((UnusableBoat item) => (Object)(object)item.Boat == (Object)(object)boat))
			{
				Unusable.Add(new UnusableBoat
				{
					Boat = boat,
					Saveable = saveable,
					Reason = reason,
					IntentionalExclusion = intentionalExclusion
				});
			}
		}

		internal static BoatCatalog Discover(bool includeInactive = false)
		{
			//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_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			BoatCatalog boatCatalog = new BoatCatalog();
			if (SaveableField == null || PurchaseUiField == null)
			{
				boatCatalog.Rejections.Add("PurchasableBoat ownership fields were not found in this game build.");
				return boatCatalog;
			}
			HashSet<int> hashSet = new HashSet<int>();
			HashSet<int> hashSet2 = new HashSet<int>();
			PurchasableBoat[] array = Resources.FindObjectsOfTypeAll<PurchasableBoat>();
			foreach (PurchasableBoat val in array)
			{
				try
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Scene scene = ((Component)val).gameObject.scene;
					if (!((Scene)(ref scene)).IsValid())
					{
						continue;
					}
					scene = ((Component)val).gameObject.scene;
					if (!((Scene)(ref scene)).isLoaded || (!includeInactive && (!((Component)val).gameObject.activeInHierarchy || !((Behaviour)val).enabled)) || val.isHouse)
					{
						continue;
					}
					object? value = SaveableField.GetValue(val);
					SaveableObject val2 = (SaveableObject)((value is SaveableObject) ? value : null);
					if (!IsBoatLike(val2))
					{
						continue;
					}
					int index = val2.sceneIndex;
					if (index >= 0 && !hashSet.Add(index))
					{
						if (hashSet2.Add(index))
						{
							boatCatalog.Rejections.Add("Boat scene index " + index + " is used by more than one active boat.");
						}
						BoatEntry[] array2 = boatCatalog.Boats.Where((BoatEntry item) => item.Index == index).ToArray();
						foreach (BoatEntry boatEntry in array2)
						{
							boatCatalog.Reject(boatEntry.Boat, boatEntry.Saveable, "scene index " + index + " is ambiguous");
						}
						boatCatalog.Boats.RemoveAll((BoatEntry item) => item.Index == index);
						boatCatalog.Reject(val, val2, "scene index " + index + " is ambiguous");
						continue;
					}
					string text = ((Object)((Component)val).gameObject).name + " (" + index + ")";
					if (index < 0 || (Object)(object)((Component)val2).gameObject != (Object)(object)((Component)val).gameObject || (Object)/*isinst with value type is only supported in some contexts*/ == (Object)null)
					{
						boatCatalog.Rejections.Add("Boat " + text + " lacks normal purchase state.");
						boatCatalog.Reject(val, val2, "lacks normal purchase state");
						continue;
					}
					if (KnownIdentities.LeopardCompanion.Equals(new Identity(index, ((Object)((Component)val).gameObject).name)))
					{
						boatCatalog.Rejections.Add("Boat " + text + ": Leopard's companion is not a separate sale boat.");
						boatCatalog.Reject(val, val2, "Leopard's companion is not a separate sale boat", intentionalExclusion: true);
						continue;
					}
					Rigidbody body = ((Component)val2).GetComponent<Rigidbody>();
					BoatMooringRopes component = ((Component)val2).GetComponent<BoatMooringRopes>();
					List<string> list = new List<string>();
					if ((Object)(object)body == (Object)null)
					{
						list.Add("Rigidbody");
					}
					if (!ProbeVelocityGuard.CanGuard(val2))
					{
						list.Add("compatible BoatProbes velocity guard");
					}
					if ((Object)(object)((Component)val2).GetComponent<BoatDamage>() == (Object)null)
					{
						list.Add("BoatDamage");
					}
					if ((Object)(object)((Component)val2).GetComponent<BoatLocalItems>() == (Object)null)
					{
						list.Add("BoatLocalItems");
					}
					if ((Object)(object)component == (Object)null)
					{
						list.Add("BoatMooringRopes");
					}
					else
					{
						if (component.ropes == null || component.ropes.Length < 2 || component.ropes.Any((PickupableBoatMooringRope item) => (Object)(object)item == (Object)null))
						{
							list.Add("at least two initialized mooring ropes");
						}
						if ((Object)(object)component.GetAnchorController() == (Object)null)
						{
							list.Add("initialized native equipment controller");
						}
					}
					if (list.Count > 0)
					{
						string text2 = "missing " + string.Join(", ", list.ToArray());
						boatCatalog.Rejections.Add("Boat " + text + ": " + text2 + ".");
						boatCatalog.Reject(val, val2, text2);
						continue;
					}
					CapsuleCollider[] array3 = (from item in ((Component)body).GetComponents<CapsuleCollider>()
						where (Object)(object)item != (Object)null && !((Collider)item).isTrigger && (includeInactive || (((Collider)item).enabled && (Object)(object)((Collider)item).attachedRigidbody == (Object)(object)body))
						select item).ToArray();
					if (array3.Length != 1 || array3[0].direction != 2)
					{
						boatCatalog.Rejections.Add("Boat " + text + " has " + array3.Length + (includeInactive ? " root hull capsules" : " enabled attached root hull capsules") + "; exactly one along Z is required.");
						boatCatalog.Reject(val, val2, includeInactive ? "no single root hull capsule along Z" : "no single enabled attached root hull capsule along Z");
						continue;
					}
					VanillaBoat vanillaBoat = KnownIdentities.VanillaBoat(index, ((Object)((Component)val).gameObject).name);
					BoatEntry boatEntry2 = new BoatEntry
					{
						Boat = val,
						Saveable = val2,
						Body = body,
						Ropes = component,
						Hull = array3[0],
						Vanilla = (vanillaBoat != null),
						DisplayName = ((vanillaBoat != null) ? vanillaBoat.DisplayName : ((Object)((Component)val).gameObject).name),
						Origin = ((vanillaBoat != null) ? "Sailwind" : "Mod boat")
					};
					try
					{
						boatEntry2.Spec = HullFootprint.MakeSpec(boatEntry2);
						boatEntry2.Size = vanillaBoat?.Size ?? BoatSizing.FromHull(boatEntry2.Spec.Length, boatEntry2.Spec.Radius);
						boatEntry2.Spec.Extra = ((boatEntry2.Size == BoatSize.Large) ? 1f : 0f);
					}
					catch (Exception ex)
					{
						boatCatalog.Rejections.Add("Boat " + text + ": " + ex.Message);
						boatCatalog.Reject(val, val2, ex.Message);
						goto end_IL_0061;
					}
					boatCatalog.Boats.Add(boatEntry2);
					end_IL_0061:;
				}
				catch (Exception ex2)
				{
					boatCatalog.Rejections.Add("A purchasable boat could not be inspected: " + ex2.GetType().Name + ": " + ex2.Message);
					try
					{
						object? value2 = SaveableField.GetValue(val);
						SaveableObject saveable = (SaveableObject)((value2 is SaveableObject) ? value2 : null);
						if (!val.isHouse && IsBoatLike(saveable))
						{
							boatCatalog.Reject(val, saveable, "could not be inspected: " + ex2.Message);
						}
					}
					catch (Exception)
					{
					}
				}
			}
			boatCatalog.Boats.Sort((BoatEntry left, BoatEntry right) => left.Index.CompareTo(right.Index));
			return boatCatalog;
		}
	}
	internal sealed class ProbeVelocityGuard
	{
		private readonly Component probes;

		private readonly FieldInfo field;

		private readonly bool previous;

		private bool restored;

		internal static bool CanGuard(SaveableObject boat)
		{
			Component val = (((Object)(object)boat != (Object)null) ? ((Component)boat).GetComponent("BoatProbes") : null);
			FieldInfo fieldInfo = (((Object)(object)val != (Object)null) ? AccessTools.Field(((object)val).GetType(), "dontUpdateVelocity") : null);
			if (fieldInfo != null)
			{
				return fieldInfo.FieldType == typeof(bool);
			}
			return false;
		}

		internal ProbeVelocityGuard(SaveableObject boat)
		{
			probes = ((Component)boat).GetComponent("BoatProbes");
			field = (((Object)(object)probes != (Object)null) ? AccessTools.Field(((object)probes).GetType(), "dontUpdateVelocity") : null);
			if (field == null || field.FieldType != typeof(bool))
			{
				throw new InvalidOperationException("Boat has no compatible BoatProbes velocity guard.");
			}
			previous = (bool)field.GetValue(probes);
			field.SetValue(probes, true);
		}

		internal void Restore()
		{
			if (restored)
			{
				return;
			}
			try
			{
				if ((Object)(object)probes != (Object)null)
				{
					field.SetValue(probes, previous);
				}
				restored = true;
			}
			catch (Exception exception)
			{
				Plugin.Instance?.Error("Could not restore a boat's velocity guard.", exception);
			}
		}
	}
	internal static class BoatRelocator
	{
		private sealed class RopePosition
		{
			public PickupableBoatMooringRope Rope;

			public Transform Parent;

			public Vector3 Position;

			public Quaternion Rotation;

			public GPButtonDockMooring Mooring;

			public bool RestoreBlocked;
		}

		private sealed class Entry
		{
			public FleetMove Move;

			public BerthPlan Plan;

			public Vector3 OldPosition;

			public Quaternion OldRotation;

			public Vector3 OldBodyPosition;

			public Quaternion OldBodyRotation;

			public Vector3 OldVelocity;

			public Vector3 OldAngularVelocity;

			public Anchor Anchor;

			public Rigidbody AnchorBody;

			public Vector3 OldAnchorPosition;

			public float OldAnchorLength;

			public RopePosition[] Ropes;

			public ProbeVelocityGuard Guard;

			public bool Released;

			public bool AnchorReset;

			public bool Moved;

			public bool Placed;

			public bool Restored;
		}

		private static readonly List<ProbeVelocityGuard> ActiveGuards = new List<ProbeVelocityGuard>();

		internal static List<BerthPlan> ApplyFleet(IList<FleetMove> moves, ICollection<string> stayingHomes, ICollection<string> stayingClaims, Occupancy o