Decompiled source of Ship Shuffle v1.0.0
BepInEx/plugins/ShipShuffle/ShipShuffle.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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