Decompiled source of ValheimHelmsman v0.2.42

BepInEx/plugins/ValheimHelmsman/ValheimHelmsman.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Helmsman;
using Helmsman.Core;
using Helmsman.Structures;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using MagicaCloth2;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using VikingBirds;
using VikingGull;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValheimHelmsman")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.42.0")]
[assembly: AssemblyInformationalVersion("0.2.42+36c9918e3a0f86f4012d19cdddf84217608f7fa4")]
[assembly: AssemblyProduct("ValheimHelmsman")]
[assembly: AssemblyTitle("ValheimHelmsman")]
[assembly: AssemblyVersion("0.2.42.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 VikingGull
{
	public sealed class GullHelmet : MonoBehaviour
	{
		private Material template;

		private readonly List<Mesh> meshes = new List<Mesh>();

		private readonly List<Material> materials = new List<Material>();

		internal static readonly Vector3 Crown = new Vector3(0f, 1.216f, -0.345f);

		internal static GullHelmet Create(Transform parent, Vector3 position, Material? template = null)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Viking gull helmet");
			val.transform.SetParent(parent, false);
			val.transform.localPosition = position;
			GullHelmet gullHelmet = val.AddComponent<GullHelmet>();
			Renderer component = ((Component)parent).GetComponent<Renderer>();
			gullHelmet.template = (Material)(Object.op_Implicit((Object)(object)template) ? ((object)template) : ((object)(Object.op_Implicit((Object)(object)component) ? component.sharedMaterial : null)));
			Material material = gullHelmet.Material(new Color(0.27f, 0.3f, 0.32f), 0.65f);
			Material material2 = gullHelmet.Material(new Color(0.57f, 0.35f, 0.13f), 0.55f);
			Material material3 = gullHelmet.Material(new Color(0.82f, 0.75f, 0.54f), 0.05f);
			gullHelmet.Surface("Iron cap", material, 32, 12, delegate(float u, float v)
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				float num4 = u * (float)Math.PI * 2f;
				float num5 = v * (float)Math.PI * 0.5f;
				return new Vector3(Mathf.Cos(num4) * 0.079f * Mathf.Cos(num5), 0.094f * Mathf.Sin(num5), Mathf.Sin(num4) * 0.103f * Mathf.Cos(num5));
			});
			gullHelmet.Surface("Bronze brow band", material2, 32, 2, delegate(float u, float v)
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				float num4 = u * (float)Math.PI * 2f;
				return new Vector3(Mathf.Cos(num4) * 0.082f, -0.006f + v * 0.024f, Mathf.Sin(num4) * 0.106f);
			});
			for (int num = -1; num <= 1; num += 2)
			{
				int s = num;
				gullHelmet.Surface("Curved horn", material3, 16, 14, delegate(float u, float v)
				{
					//IL_0076: Unknown result type (might be due to invalid IL or missing references)
					float num4 = u * (float)Math.PI * 2f;
					float num5 = 0.031f * (1f - v);
					return new Vector3((float)s * (0.067f + 0.143f * v) + num5 * Mathf.Cos(num4) * 0.5f, 0.046f + 0.032f * v + 0.135f * v * v + num5 * Mathf.Sin(num4), 0.008f + 0.042f * v + num5 * Mathf.Cos(num4));
				});
			}
			gullHelmet.Surface("Rolled helmet rim", material2, 40, 8, delegate(float u, float v)
			{
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				float num4 = u * (float)Math.PI * 2f;
				float num5 = v * (float)Math.PI * 2f;
				return new Vector3(Mathf.Cos(num4) * (0.082f + 0.003f * Mathf.Cos(num5)), 0.006f + 0.003f * Mathf.Sin(num5), Mathf.Sin(num4) * (0.106f + 0.003f * Mathf.Cos(num5)));
			});
			gullHelmet.Surface("Crown reinforcing strip", material2, 4, 24, delegate(float u, float v)
			{
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				float num4 = v * (float)Math.PI;
				return new Vector3((u - 0.5f) * 0.011f, 0.096f * Mathf.Sin(num4), 0.105f * Mathf.Cos(num4));
			});
			for (int num2 = 0; num2 < 10; num2++)
			{
				float num3 = (float)num2 * (float)Math.PI * 2f / 10f;
				Vector3 center = new Vector3(0.085f * Mathf.Cos(num3), 0.011f, 0.109f * Mathf.Sin(num3));
				gullHelmet.Surface("Brow rivet", material2, 8, 6, delegate(float u, float v)
				{
					//IL_0017: 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_0046: 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)
					float num4 = u * (float)Math.PI * 2f;
					float num5 = v * (float)Math.PI;
					return center + new Vector3(Mathf.Cos(num4) * Mathf.Sin(num5), Mathf.Cos(num5), Mathf.Sin(num4) * Mathf.Sin(num5)) * 0.0038f;
				});
			}
			return gullHelmet;
		}

		private Material Material(Color color, float metallic)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Material val = GullMaterials.Create(template, color, metallic);
			if (val.HasProperty("_MainTex"))
			{
				val.SetTexture("_MainTex", (Texture)(object)Texture2D.whiteTexture);
			}
			materials.Add(val);
			return val;
		}

		private void Surface(string label, Material material, int sides, int rings, Func<float, float, Vector3> point)
		{
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			List<Vector3> list = new List<Vector3>();
			List<int> list2 = new List<int>();
			int num = (sides + 1) * (rings + 1);
			for (int i = 0; i < 2; i++)
			{
				for (int j = 0; j <= rings; j++)
				{
					for (int k = 0; k <= sides; k++)
					{
						list.Add(point((float)k / (float)sides, (float)j / (float)rings));
					}
				}
			}
			for (int l = 0; l < 2; l++)
			{
				for (int m = 0; m < rings; m++)
				{
					for (int n = 0; n < sides; n++)
					{
						int num2 = l * num + m * (sides + 1) + n;
						int num3 = num2 + 1;
						int num4 = num2 + sides + 1;
						int num5 = num4 + 1;
						if (l == 0)
						{
							list2.AddRange(new int[6] { num2, num4, num3, num3, num4, num5 });
						}
						else
						{
							list2.AddRange(new int[6] { num2, num3, num4, num3, num5, num4 });
						}
					}
				}
			}
			Mesh val = new Mesh
			{
				name = label
			};
			val.SetVertices(list);
			val.SetTriangles(list2, 0);
			val.RecalculateNormals();
			val.RecalculateBounds();
			meshes.Add(val);
			GameObject val2 = new GameObject(label);
			val2.transform.SetParent(((Component)this).transform, false);
			val2.AddComponent<MeshFilter>().sharedMesh = val;
			((Renderer)val2.AddComponent<MeshRenderer>()).sharedMaterial = material;
		}

		internal void FollowStanding(Quaternion head, Quaternion body, Vector3 neck, Vector3 hips, float crouch)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0024: 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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = neck + head * (Crown - neck);
			((Component)this).transform.localPosition = hips + body * (val - hips) - Vector3.up * crouch;
			((Component)this).transform.localRotation = body * head;
		}

		internal static void FitFlying(GameObject model, Transform actor)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			SkinnedMeshRenderer componentInChildren = model.GetComponentInChildren<SkinnedMeshRenderer>();
			if (!Object.op_Implicit((Object)(object)componentInChildren))
			{
				return;
			}
			Transform val = ((Component)componentInChildren).transform;
			bool flag = false;
			Transform[] bones = componentInChildren.bones;
			foreach (Transform val2 in bones)
			{
				if (Object.op_Implicit((Object)(object)val2) && ((Object)val2).name == "Bone")
				{
					val = val2;
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return;
			}
			Mesh val3 = new Mesh();
			try
			{
				componentInChildren.BakeMesh(val3);
				Vector3[] vertices = val3.vertices;
				float num = float.NegativeInfinity;
				float num2 = float.PositiveInfinity;
				for (int j = 0; j < vertices.Length; j++)
				{
					vertices[j] = actor.InverseTransformPoint(((Component)componentInChildren).transform.TransformPoint(vertices[j]));
					num = Mathf.Max(num, vertices[j].z);
					num2 = Mathf.Min(num2, vertices[j].z);
				}
				Vector3 val4 = default(Vector3);
				((Vector3)(ref val4))..ctor(0f, float.NegativeInfinity, 0f);
				Vector3[] array = vertices;
				foreach (Vector3 val5 in array)
				{
					if (Mathf.Abs(val5.x) < 0.09f && val5.z > Mathf.Lerp(num2, num, 0.68f) && val5.y > val4.y)
					{
						val4 = val5;
					}
				}
				if (!float.IsInfinity(val4.y))
				{
					GullHelmet gullHelmet = Create(actor, new Vector3(0f, val4.y - 0.02f * model.transform.localScale.y, val4.z), ((Renderer)componentInChildren).sharedMaterial);
					((Component)gullHelmet).transform.localScale = model.transform.localScale;
					((Component)gullHelmet).transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
					((Component)gullHelmet).transform.SetParent(val, true);
					if (Object.op_Implicit((Object)(object)componentInChildren.sharedMesh) && componentInChildren.sharedMesh.isReadable)
					{
						Mesh item = (componentInChildren.sharedMesh = GullSurfaceRefinement.Create(componentInChildren.sharedMesh, perched: false));
						gullHelmet.meshes.Add(item);
					}
				}
			}
			finally
			{
				Object.Destroy((Object)(object)val3);
			}
		}

		private void OnDestroy()
		{
			foreach (Mesh mesh in meshes)
			{
				if (Object.op_Implicit((Object)(object)mesh))
				{
					Object.Destroy((Object)(object)mesh);
				}
			}
			foreach (Material material in materials)
			{
				if (Object.op_Implicit((Object)(object)material))
				{
					Object.Destroy((Object)(object)material);
				}
			}
		}
	}
}
namespace Helmsman
{
	public sealed class BirdInteraction : MonoBehaviour, Interactable, Hoverable
	{
		internal Func<string> Label = () => "Bird";

		internal Func<string> Hint = () => "";

		internal Func<Humanoid, bool>? Use;

		public string GetHoverName()
		{
			return Label();
		}

		public float GetHoverOffset()
		{
			return 0.15f;
		}

		public string GetHoverText()
		{
			return Hint();
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			if (!hold && (Object)(object)user == (Object)(object)Player.m_localPlayer)
			{
				return Use?.Invoke(user) ?? false;
			}
			return false;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}
	}
	internal static class BoatyardModels
	{
		private static readonly List<Sprite> icons = new List<Sprite>();

		internal static void RefreshIcon(GameObject prefab)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				return;
			}
			try
			{
				Sprite val = RenderManager.Instance.Render(new RenderRequest(prefab)
				{
					Rotation = RenderManager.IsometricRotation,
					Width = 128,
					Height = 128,
					UseCache = true,
					TargetPlugin = ((BaseUnityPlugin)Plugin.Instance).Info.Metadata,
					ParticleSimulationTime = -1f
				});
				if (Object.op_Implicit((Object)(object)val))
				{
					icons.Add(val);
					ItemDrop component = prefab.GetComponent<ItemDrop>();
					if (Object.op_Implicit((Object)(object)component))
					{
						component.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val };
					}
					Piece component2 = prefab.GetComponent<Piece>();
					if (Object.op_Implicit((Object)(object)component2))
					{
						component2.m_icon = val;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Record("Boatyard icon unavailable for " + ((Object)prefab).name + ": " + ex.Message);
			}
		}

		internal static void Release()
		{
			foreach (Sprite icon in icons)
			{
				if (Object.op_Implicit((Object)(object)icon))
				{
					if (Object.op_Implicit((Object)(object)icon.texture))
					{
						Object.Destroy((Object)(object)icon.texture);
					}
					Object.Destroy((Object)(object)icon);
				}
			}
			icons.Clear();
		}
	}
	internal static class BuildMenuCategory
	{
		internal const string Token = "$helmsman_build_category";

		private static readonly HashSet<string> Prefabs = new HashSet<string>(HarborCatalog.Pieces.Select((HarborEntry p) => p.Prefab).Concat(ShipConstruction.Blueprints.Select((ShipBlueprint b) => b.Prefab)).Concat(new string[7] { "helmsman_dock_ward", "CarpentersTable", "HelmsmanSlipway", "HelmsmanToolRack", "HelmsmanCaulkingStation", "HelmsmanRiggingRack", "HelmsmanPaintStand" }), StringComparer.Ordinal);

		internal static bool Contains(Piece piece)
		{
			if (Object.op_Implicit((Object)(object)piece))
			{
				return Prefabs.Contains(Utils.GetPrefabName(((Component)piece).gameObject));
			}
			return false;
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class AddHelmsmanUsageCategory
	{
		[HarmonyPriority(0)]
		private static void Postfix(ref UsageTagFlags[] ___m_usageTags, ref string[] ___m_usageTagDisplayNames)
		{
			if (!___m_usageTagDisplayNames.Contains("$helmsman_build_category"))
			{
				___m_usageTags = ___m_usageTags.Concat((IEnumerable<UsageTagFlags>)(object)new UsageTagFlags[1]).ToArray();
				___m_usageTagDisplayNames = ___m_usageTagDisplayNames.Concat(new string[1] { "$helmsman_build_category" }).ToArray();
			}
		}
	}
	[HarmonyPatch(typeof(ByUsagePieceList), "GetTagDisplayName")]
	internal static class HelmsmanUsageLabel
	{
		private static void Postfix(ref string __result)
		{
			if (__result == "$helmsman_build_category")
			{
				__result = "Helmsman";
			}
		}
	}
	[HarmonyPatch(typeof(ByUsagePieceList), "UpdateAvailableTags")]
	internal static class HideUnrelatedHelmsmanCategory
	{
		private static void Postfix(PieceTable pieceTable, string[] ___m_usageTagDisplayNames, List<int> ___m_availableTags)
		{
			int num = Array.IndexOf(___m_usageTagDisplayNames, "$helmsman_build_category");
			if (num >= 0 && !pieceTable.m_availablePieces.Any(BuildMenuCategory.Contains))
			{
				___m_availableTags.Remove(num);
			}
		}
	}
	[HarmonyPatch(typeof(ByUsagePieceList), "GetAvailablePiecesWithTag")]
	internal static class FilterHelmsmanUsageCategory
	{
		[HarmonyPriority(800)]
		private static bool Prefix(int tagId, PieceTable pieceTable, IList<Piece> resultOut, string[] ___m_usageTagDisplayNames)
		{
			if (tagId < 0 || tagId >= ___m_usageTagDisplayNames.Length || ___m_usageTagDisplayNames[tagId] != "$helmsman_build_category")
			{
				return true;
			}
			foreach (Piece availablePiece in pieceTable.m_availablePieces)
			{
				if (BuildMenuCategory.Contains(availablePiece) || availablePiece.m_repairPiece || availablePiece.m_removePiece)
				{
					resultOut.Add(availablePiece);
				}
			}
			return false;
		}
	}
	public sealed class CargoOrder : MonoBehaviour
	{
		private Ship ship;

		private GullGuide gull;

		private Player requester;

		private object request;

		private bool ended;

		internal string Status { get; private set; } = "";

		internal static bool Start(Ship ship, GullGuide gull, out string reason)
		{
			reason = "Speak to the landed gull aboard a stopped boat with Quartermaster in range.";
			if (!Plugin.LocalSession || !Object.op_Implicit((Object)(object)gull) || !gull.ReadyOn(ship) || !QuartermasterBridge.Available)
			{
				return false;
			}
			if (Object.op_Implicit((Object)(object)Plugin.Instance.Cargo))
			{
				reason = "Finish or stop the current cargo request first.";
				return false;
			}
			try
			{
				reason = QuartermasterBridge.Check(ship);
				if (reason.Length > 0)
				{
					return false;
				}
				object obj = QuartermasterBridge.BeginUnload(ship);
				CargoOrder cargoOrder = ((Component)ship).gameObject.AddComponent<CargoOrder>();
				cargoOrder.ship = ship;
				cargoOrder.gull = gull;
				cargoOrder.requester = Player.m_localPlayer;
				cargoOrder.request = obj;
				Plugin.Instance.Cargo = cargoOrder;
				cargoOrder.Status = "Unloading cargo into base storage…";
				reason = cargoOrder.Status;
				return true;
			}
			catch (Exception ex)
			{
				reason = ex.GetBaseException().Message;
				return false;
			}
		}

		private void Update()
		{
			if (ended)
			{
				return;
			}
			if (!Plugin.LocalSession || !QuartermasterBridge.Available || !Object.op_Implicit((Object)(object)ship) || !Object.op_Implicit((Object)(object)gull) || !gull.ReadyOn(ship) || !Object.op_Implicit((Object)(object)requester) || (Object)(object)requester != (Object)(object)Player.m_localPlayer || ((Character)requester).IsDead() || !ship.IsPlayerInBoat(requester))
			{
				Stop("Cargo request ended.");
				return;
			}
			try
			{
				string text = QuartermasterBridge.Check(ship);
				if (text.Length > 0)
				{
					Stop(text);
				}
				else
				{
					if (gull.SortingCargo)
					{
						return;
					}
					if (QuartermasterBridge.Finished(request))
					{
						Stop(QuartermasterBridge.Status(request), completed: true);
						return;
					}
					ItemData val = QuartermasterBridge.UnloadNext(request);
					Status = QuartermasterBridge.Status(request);
					if (val != null)
					{
						gull.SortCargo(val);
					}
					else if (QuartermasterBridge.Finished(request))
					{
						Stop(Status, completed: true);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
				Stop("Cargo handling stopped: " + ex.GetBaseException().Message);
			}
		}

		internal void Stop(string reason, bool completed = false)
		{
			if (!ended)
			{
				ended = true;
				if (request != null && QuartermasterBridge.Available)
				{
					QuartermasterBridge.Cancel(request);
				}
				if (Object.op_Implicit((Object)(object)gull))
				{
					gull.EndCargoSort(!completed);
				}
				if (Object.op_Implicit((Object)(object)Plugin.Instance) && (Object)(object)Plugin.Instance.Cargo == (Object)(object)this)
				{
					Plugin.Instance.Cargo = null;
				}
				Status = reason;
				if (completed && Object.op_Implicit((Object)(object)gull))
				{
					gull.Speak(reason, important: true);
				}
				Plugin.Message(reason);
				Object.Destroy((Object)(object)this);
			}
		}

		private void OnDestroy()
		{
			if (!ended)
			{
				Stop("Cargo request ended.");
			}
		}
	}
	internal static class CarpenterRecipes
	{
		private static readonly HashSet<string> Items = new HashSet<string>(from item in HarborCatalog.Items
			where item.Recipe.Length > 0
			select item.Prefab, StringComparer.Ordinal);

		internal static bool IsTable(CraftingStation station)
		{
			if (Object.op_Implicit((Object)(object)station))
			{
				return Utils.GetPrefabName(((Component)station).gameObject) == "CarpentersTable";
			}
			return false;
		}

		internal static bool Allows(Recipe recipe)
		{
			if (Object.op_Implicit((Object)(object)recipe) && Object.op_Implicit((Object)(object)recipe.m_item) && IsTable(recipe.m_craftingStation))
			{
				return Items.Contains(Utils.GetPrefabName(((Component)recipe.m_item).gameObject));
			}
			return false;
		}

		internal static void Configure(CraftingStation station)
		{
			station.m_name = "Carpenter's Table";
			station.m_showBasicRecipies = false;
			station.m_upgrader = false;
		}
	}
	[HarmonyPatch(typeof(Player), "GetAvailableRecipes")]
	internal static class CarpenterRecipeList
	{
		[HarmonyPriority(0)]
		private static void Postfix(Player __instance, ref List<Recipe> available)
		{
			if (CarpenterRecipes.IsTable(__instance.GetCurrentCraftingStation()))
			{
				available.RemoveAll((Recipe recipe) => !CarpenterRecipes.Allows(recipe));
			}
		}
	}
	[Serializable]
	public sealed class ConstructionOrder
	{
		public string blueprint = "";

		public string recipe = "";

		public string name = "";

		public long started;

		public long creator;

		public float duration;

		public bool freeBuild;

		public Vector3 position;

		public float heading;

		internal bool Valid
		{
			get
			{
				if (ShipConstruction.Find(blueprint) != null && ShipConstruction.ValidDuration((double)duration) && started >= 0 && Finite(position.x) && Finite(position.y) && Finite(position.z) && Finite(heading) && name != null && name.Length <= 48)
				{
					return ShipConstruction.ValidRecipe(blueprint, recipe);
				}
				return false;
			}
		}

		private static bool Finite(float n)
		{
			if (!float.IsInfinity(n))
			{
				return !float.IsNaN(n);
			}
			return false;
		}
	}
	internal static class ConstructionSite
	{
		internal static bool Ready(Player player, Vector3 bench, Vector3 site, out string reason)
		{
			//IL_0021: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0062: 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)
			if (!Object.op_Implicit((Object)(object)player) || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || !WorkshopRange.Connected(player, bench, site, ((Component)player).transform.position))
			{
				reason = "Construction paused: return to the workshop area to resume.";
				return false;
			}
			if (!Object.op_Implicit((Object)(object)ZNetScene.instance) || !ZNetScene.instance.IsAreaReady(bench) || !ZNetScene.instance.IsAreaReady(site))
			{
				reason = "Construction paused: waiting for the work area to finish loading.";
				return false;
			}
			reason = "";
			return true;
		}
	}
	[Serializable]
	public sealed class Berth
	{
		public int version = 1;

		public string name = "Unnamed dock";

		public Vector3 position;

		public float heading;

		public bool reverseDeparture;

		public float reverseDistance = 30f;

		public bool configured;

		public string shipType = "Karve";

		public Vector3 Forward => Quaternion.Euler(0f, heading, 0f) * Vector3.forward;

		public Vector3 Approach => position - Forward * 35f;

		public Vector3 Exit => position + Forward * (reverseDeparture ? (0f - reverseDistance) : 35f);

		public bool ValidData
		{
			get
			{
				if (version == 1 && !string.IsNullOrWhiteSpace(shipType) && !string.IsNullOrWhiteSpace(name) && name.Length <= 48 && Finite(position.x) && Finite(position.y) && Finite(position.z) && Finite(heading) && Finite(reverseDistance) && reverseDistance >= 16f)
				{
					return reverseDistance <= 80f;
				}
				return false;
			}
		}

		public Berth Copy()
		{
			return JsonUtility.FromJson<Berth>(JsonUtility.ToJson((object)this));
		}

		private static bool Finite(float v)
		{
			if (!float.IsNaN(v))
			{
				return !float.IsInfinity(v);
			}
			return false;
		}
	}
	public sealed class DockRecord
	{
		internal bool Temporary;

		public ZDOID Id;

		public Berth Berth;

		public Vector3 MarkerPosition;
	}
	public sealed class DockDirectory : MonoBehaviour
	{
		internal static readonly int DataKey = StringExtensionMethods.GetStableHashCode("helmsman_berth_v1");

		private long world;

		internal List<DockRecord> Records { get; private set; } = new List<DockRecord>();

		private IEnumerator Start()
		{
			while (true)
			{
				if (!Object.op_Implicit((Object)(object)ZNet.instance) || ZDOMan.instance == null || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					Records.Clear();
					world = 0L;
					yield return (object)new WaitForSeconds(1f);
					continue;
				}
				long worldUID = ZNet.instance.GetWorldUID();
				if (worldUID != world)
				{
					Records.Clear();
					world = worldUID;
				}
				long scanWorld = world;
				List<ZDO> zdos = new List<ZDO>();
				int cursor = 0;
				while (ZDOMan.instance != null && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.GetWorldUID() == world && !ZDOMan.instance.GetAllZDOsWithPrefabIterative("helmsman_dock_ward", zdos, ref cursor))
				{
					yield return null;
				}
				if (!Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.GetWorldUID() != scanWorld || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					Records.Clear();
					continue;
				}
				List<DockRecord> list = new List<DockRecord>();
				HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
				foreach (ZDO item in zdos)
				{
					if (hashSet.Add(item.m_uid))
					{
						Berth berth = Read(item);
						if (berth != null && berth.configured)
						{
							list.Add(new DockRecord
							{
								Id = item.m_uid,
								Berth = berth,
								MarkerPosition = item.GetPosition()
							});
						}
					}
				}
				Records = list;
				yield return (object)new WaitForSeconds(3f);
			}
		}

		internal static Berth? Read(ZDO? zdo)
		{
			if (zdo == null || !zdo.IsValid())
			{
				return null;
			}
			string text = zdo.GetString(DataKey, "");
			if (text.Length == 0 || text.Length > 4096)
			{
				return null;
			}
			try
			{
				Berth berth = JsonUtility.FromJson<Berth>(text);
				return (berth != null && berth.ValidData) ? berth : null;
			}
			catch (ArgumentException)
			{
				return null;
			}
		}

		internal static DockRecord? Resolve(ZDOID id)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			ZDOMan instance = ZDOMan.instance;
			ZDO val = ((instance != null) ? instance.GetZDO(id) : null);
			if (val == null || val.GetPrefab() != StringExtensionMethods.GetStableHashCode("helmsman_dock_ward"))
			{
				return null;
			}
			Berth berth = Read(val);
			if (berth == null || !berth.configured)
			{
				return null;
			}
			return new DockRecord
			{
				Id = id,
				Berth = berth,
				MarkerPosition = val.GetPosition()
			};
		}
	}
	public sealed class DockMarker : MonoBehaviour, Interactable, Hoverable
	{
		private ZNetView view;

		private GullGuide? gull;

		internal ZDOID Id => view.GetZDO().m_uid;

		internal Berth Settings => DockDirectory.Read(Object.op_Implicit((Object)(object)view) ? view.GetZDO() : null) ?? new Berth
		{
			position = ((Component)this).transform.position + ((Component)this).transform.forward * 12f,
			heading = ((Component)this).transform.eulerAngles.y
		};

		internal bool Ready
		{
			get
			{
				if (Object.op_Implicit((Object)(object)view))
				{
					return view.IsValid();
				}
				return false;
			}
		}

		private void Awake()
		{
			view = ((Component)this).GetComponent<ZNetView>();
		}

		private IEnumerator Start()
		{
			while (!Ready || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				yield return (object)new WaitForSeconds(0.5f);
			}
			if (!Object.op_Implicit((Object)(object)((Component)this).GetComponent<SummonBeacon>()))
			{
				((Component)this).gameObject.AddComponent<SummonBeacon>();
			}
			while (Ready)
			{
				if (ScoutGullPresentation.BusyDock(Id))
				{
					yield return (object)new WaitForSeconds(1f);
					continue;
				}
				if (!Object.op_Implicit((Object)(object)gull))
				{
					gull = GullGuide.Traveller(Id);
				}
				if (!Object.op_Implicit((Object)(object)gull))
				{
					gull = GullGuide.Create(((Component)this).transform.position + Vector3.up * 2f, this, null);
				}
				yield return (object)new WaitForSeconds(1f);
			}
		}

		internal void SendScout()
		{
			if (Object.op_Implicit((Object)(object)gull) && !gull.IsTravelling)
			{
				gull.FlyAway();
			}
		}

		internal GullGuide CallGuide(Ship ship)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)gull))
			{
				gull = GullGuide.Create(((Component)this).transform.position + Vector3.up * 2f, this, null);
			}
			gull.ReserveDock(Id);
			gull.Visit(ship);
			return gull;
		}

		internal GullGuide FetchGuide(Vector3 target)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)gull))
			{
				gull = GullGuide.Create(((Component)this).transform.position + Vector3.up * 2f, this, null);
			}
			gull.Fetch(target);
			return gull;
		}

		internal GullGuide BoardGuide(Voyage voyage)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)gull))
			{
				gull = GullGuide.Create(((Component)this).transform.position + Vector3.up * 2f, this, null);
			}
			gull.Board(voyage);
			return gull;
		}

		private void OnDestroy()
		{
			if (Object.op_Implicit((Object)(object)gull) && !gull.IsTravelling)
			{
				Object.Destroy((Object)(object)((Component)gull).gameObject);
			}
		}

		public string GetHoverName()
		{
			return "Dock Ward — " + Settings.name;
		}

		public float GetHoverOffset()
		{
			return 0f;
		}

		public string GetHoverText()
		{
			return Localization.instance.Localize(GetHoverName() + "\n[<color=yellow><b>$KEY_Use</b></color>] Configure berth");
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (hold || !Ready || (Object)(object)user != (Object)(object)Player.m_localPlayer || !PrivateArea.CheckAccess(((Component)this).transform.position, 0f, false, true))
			{
				return false;
			}
			if (!Plugin.LocalSession)
			{
				Plugin.Message("Join a world before configuring a dock.");
				return false;
			}
			Plugin.Instance.UI.OpenDock(this);
			return true;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}

		internal bool Save(Berth berth)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.LocalSession || !Ready || !berth.ValidData)
			{
				return false;
			}
			if (Vector3.Distance(((Component)Player.m_localPlayer).transform.position, ((Component)this).transform.position) > 10f)
			{
				return false;
			}
			Berth copy = berth.Copy();
			WorkstationLease lease = ((Component)this).GetComponent<WorkstationLease>();
			Plugin.Message(lease.Run(delegate
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				if (!lease.Held || !PrivateArea.CheckAccess(((Component)this).transform.position, 0f, false, true))
				{
					return "Dock access changed; settings were not saved.";
				}
				view.GetZDO().Set(DockDirectory.DataKey, JsonUtility.ToJson((object)copy));
				return "Berth saved.";
			}));
			return true;
		}
	}
	internal static class FinalFleetModels
	{
		private sealed class Part
		{
			internal string Group = "";

			internal Mesh Mesh;

			internal Material Material;
		}

		private sealed class Model
		{
			internal Spec Spec;

			internal readonly List<Part> Parts = new List<Part>();
		}

		[Serializable]
		internal sealed class Point
		{
			public string kind = "";

			public float[] position = Array.Empty<float>();

			public float[] exit = Array.Empty<float>();

			public float[] facing = Array.Empty<float>();

			public float[] size = Array.Empty<float>();
		}

		[Serializable]
		internal sealed class Solid
		{
			public float[] position = Array.Empty<float>();

			public float[] size = Array.Empty<float>();
		}

		[Serializable]
		internal sealed class HullSolid
		{
			public float[] vertices = Array.Empty<float>();

			public int[] triangles = Array.Empty<int>();
		}

		[Serializable]
		internal sealed class Sheet
		{
			public float[] head = Array.Empty<float>();

			public float[] foot = Array.Empty<float>();
		}

		[Serializable]
		internal sealed class Spec
		{
			public string prefab = "";

			public string name = "";

			public float length;

			public float beam;

			public float walkHeight;

			public float waterline;

			public float airHeight;

			public float cargoHeight;

			public float[] sailPivot = Array.Empty<float>();

			public float[] rudderPivot = Array.Empty<float>();

			public HullSolid[] hullSolids = Array.Empty<HullSolid>();

			public HullSolid[] cargoSolids = Array.Empty<HullSolid>();

			public float[][] waterMask = Array.Empty<float[]>();

			public Point[] points = Array.Empty<Point>();

			public Solid[] colliders = Array.Empty<Solid>();

			public Sheet[] sheets = Array.Empty<Sheet>();
		}

		private static readonly Dictionary<string, Model> cache = new Dictionary<string, Model>();

		private static readonly List<Object> owned = new List<Object>();

		internal static bool Has(string name)
		{
			return new string[9] { "MercantShip", "BigCargoShip", "WarShip", "HerculeShip", "LittleBoat", "HelmsmanCurrach", "HelmsmanDugout", "HelmsmanFinewoodKayak", "HelmsmanTandemKayak" }.Contains(name);
		}

		internal static bool PaddleCraft(string name)
		{
			switch (name)
			{
			case "HelmsmanDugout":
			case "HelmsmanFinewoodKayak":
			case "HelmsmanTandemKayak":
				return true;
			default:
				return false;
			}
		}

		private static Vector3 V(float[] values)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (values.Length != 3 || values.Any((float v) => float.IsNaN(v) || float.IsInfinity(v)))
			{
				throw new InvalidDataException("Invalid final fleet vector");
			}
			return new Vector3(values[0], values[1], values[2]);
		}

		private static int Count(BinaryReader r, int max)
		{
			int num = r.ReadInt32();
			if (num < 0 || num > max)
			{
				throw new InvalidDataException("Invalid final fleet count");
			}
			return num;
		}

		private static byte[] Bytes(BinaryReader r, int max)
		{
			int num = Count(r, max);
			byte[] array = r.ReadBytes(num);
			if (array.Length != num)
			{
				throw new EndOfStreamException();
			}
			return array;
		}

		private static string Text(BinaryReader r, int max = 1024)
		{
			return Encoding.UTF8.GetString(Bytes(r, max));
		}

		private static float Number(BinaryReader r)
		{
			float num = r.ReadSingle();
			if (float.IsNaN(num) || float.IsInfinity(num) || Math.Abs(num) > 100000f)
			{
				throw new InvalidDataException("Invalid final fleet coordinate");
			}
			return num;
		}

		private static Model Load(string name)
		{
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Expected O, but got Unknown
			if (cache.TryGetValue(name, out Model value))
			{
				return value;
			}
			using Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("Helmsman.Ships.final." + name + ".bin.gz") ?? throw new InvalidDataException("Missing final ship " + name);
			using GZipStream input = new GZipStream(stream, CompressionMode.Decompress);
			using BinaryReader binaryReader = new BinaryReader(input);
			if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "HMF1")
			{
				throw new InvalidDataException("Unknown final fleet format");
			}
			Model model = new Model
			{
				Spec = ReadSpecification(Text(binaryReader, 1000000), name)
			};
			int num = Count(binaryReader, 128);
			Material[] array = (Material[])(object)new Material[num];
			string[] array2 = new string[num];
			bool flag = SmallCraftFinish.Applies(name);
			Color authored = default(Color);
			for (int i = 0; i < num; i++)
			{
				string text = Text(binaryReader);
				Bytes(binaryReader, 1048576);
				((Color)(ref authored))..ctor(Number(binaryReader), Number(binaryReader), Number(binaryReader), Number(binaryReader));
				Material val = ImportedShipMaterials.FleetMaterial(text, authored, flag);
				owned.Add((Object)(object)val);
				array[i] = val;
				array2[i] = text;
			}
			int num2 = Count(binaryReader, 256);
			for (int j = 0; j < num2; j++)
			{
				string text2 = Text(binaryReader);
				int num3 = Count(binaryReader, num - 1);
				int num4 = Count(binaryReader, 1000000);
				Vector3[] array3 = (Vector3[])(object)new Vector3[num4];
				Vector3[] array4 = (Vector3[])(object)new Vector3[num4];
				Vector2[] array5 = (Vector2[])(object)new Vector2[num4];
				for (int k = 0; k < num4; k++)
				{
					array3[k] = new Vector3(Number(binaryReader), Number(binaryReader), Number(binaryReader));
					array4[k] = new Vector3(Number(binaryReader), Number(binaryReader), Number(binaryReader));
					array5[k] = new Vector2(Number(binaryReader), Number(binaryReader));
				}
				int num5 = Count(binaryReader, 6000000);
				if (num5 % 3 != 0)
				{
					throw new InvalidDataException("Invalid final ship triangles");
				}
				int[] array6 = new int[num5];
				for (int l = 0; l < num5; l++)
				{
					array6[l] = binaryReader.ReadInt32();
					if (array6[l] < 0 || array6[l] >= num4)
					{
						throw new InvalidDataException("Invalid final ship index");
					}
				}
				Mesh val2 = new Mesh
				{
					name = "Helmsman " + name + " " + text2,
					indexFormat = (IndexFormat)(num4 > 65535)
				};
				owned.Add((Object)(object)val2);
				if (flag)
				{
					SmallCraftFinish.Map(text2, array2[num3], array3, array5);
				}
				val2.vertices = array3;
				val2.normals = array4;
				val2.uv = array5;
				val2.triangles = array6;
				val2.RecalculateTangents();
				val2.RecalculateBounds();
				model.Parts.Add(new Part
				{
					Group = text2,
					Mesh = val2,
					Material = array[num3]
				});
			}
			if (binaryReader.BaseStream.ReadByte() != -1)
			{
				throw new InvalidDataException("Trailing final ship data");
			}
			cache.Add(name, model);
			return model;
		}

		private static Transform Node(string name, Transform parent, Vector3 position)
		{
			//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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name)
			{
				layer = ((Component)parent).gameObject.layer
			};
			val.transform.SetParent(parent, false);
			val.transform.localPosition = position;
			return val.transform;
		}

		private static void Rehome(Transform node, Transform root, Vector3 position)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			node.SetParent(root, false);
			node.localPosition = position;
			node.localRotation = Quaternion.identity;
			node.localScale = Vector3.one;
		}

		internal static void Apply(GameObject prefab, string name)
		{
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			//IL_0325: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0479: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0620: Unknown result type (might be due to invalid IL or missing references)
			//IL_07a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_084b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0856: Unknown result type (might be due to invalid IL or missing references)
			//IL_0872: Unknown result type (might be due to invalid IL or missing references)
			//IL_08a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_08d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0706: Expected O, but got Unknown
			//IL_0736: Unknown result type (might be due to invalid IL or missing references)
			//IL_0946: Unknown result type (might be due to invalid IL or missing references)
			//IL_094b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0955: Unknown result type (might be due to invalid IL or missing references)
			//IL_097b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0980: Unknown result type (might be due to invalid IL or missing references)
			//IL_0992: Unknown result type (might be due to invalid IL or missing references)
			//IL_0994: Unknown result type (might be due to invalid IL or missing references)
			//IL_09bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b30: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b46: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b52: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b6f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ed6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f1a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f46: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a52: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a57: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a78: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f84: Unknown result type (might be due to invalid IL or missing references)
			//IL_0fa2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aaa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0abb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0abd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ac2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bfd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c02: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ff9: Unknown result type (might be due to invalid IL or missing references)
			//IL_1027: Unknown result type (might be due to invalid IL or missing references)
			//IL_1066: Unknown result type (might be due to invalid IL or missing references)
			//IL_10b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c4c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c36: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c7a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c84: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c69: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c73: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e2a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e72: Unknown result type (might be due to invalid IL or missing references)
			Model model = Load(name);
			Spec spec = model.Spec;
			Ship component = prefab.GetComponent<Ship>();
			Transform transform = prefab.transform;
			Material material = ImportedShipMaterials.WaterMask();
			ShipControlls shipControlls = component.m_shipControlls;
			Container[] componentsInChildren = prefab.GetComponentsInChildren<Container>(true);
			Renderer[] componentsInChildren2 = prefab.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer obj in componentsInChildren2)
			{
				obj.enabled = false;
				obj.forceRenderingOff = true;
			}
			LODGroup[] componentsInChildren3 = prefab.GetComponentsInChildren<LODGroup>(true);
			for (int i = 0; i < componentsInChildren3.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren3[i]);
			}
			Animator[] componentsInChildren4 = prefab.GetComponentsInChildren<Animator>(true);
			for (int i = 0; i < componentsInChildren4.Length; i++)
			{
				((Behaviour)componentsInChildren4[i]).enabled = false;
			}
			Cloth[] componentsInChildren5 = prefab.GetComponentsInChildren<Cloth>(true);
			foreach (Cloth obj2 in componentsInChildren5)
			{
				obj2.enabled = false;
				GlobalWind component2 = ((Component)obj2).GetComponent<GlobalWind>();
				if (Object.op_Implicit((Object)(object)component2))
				{
					((MonoBehaviour)component2).CancelInvoke();
					((Behaviour)component2).enabled = false;
				}
			}
			Light[] componentsInChildren6 = prefab.GetComponentsInChildren<Light>(true);
			for (int i = 0; i < componentsInChildren6.Length; i++)
			{
				((Behaviour)componentsInChildren6[i]).enabled = false;
			}
			EffectArea[] componentsInChildren7 = prefab.GetComponentsInChildren<EffectArea>(true);
			for (int i = 0; i < componentsInChildren7.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren7[i]);
			}
			Collider[] componentsInChildren8 = prefab.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren8)
			{
				if ((Object)(object)val != (Object)(object)component.m_floatCollider && ((Object)val).name != "OnboardTrigger")
				{
					Object.DestroyImmediate((Object)(object)val);
				}
			}
			Chair[] componentsInChildren9 = prefab.GetComponentsInChildren<Chair>(true);
			for (int i = 0; i < componentsInChildren9.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren9[i]);
			}
			Ladder[] componentsInChildren10 = prefab.GetComponentsInChildren<Ladder>(true);
			for (int i = 0; i < componentsInChildren10.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren10[i]);
			}
			Transform val2 = Node("Final fleet", transform, Vector3.zero);
			Transform val3 = Node("Rig", val2, Vector3.zero);
			Transform val4 = Node("Sail", val3, V(spec.sailPivot));
			Transform val5 = Node("Steering rudder", val2, V(spec.rudderPivot));
			Transform val6 = Node("Paddle template", val2, Vector3.zero);
			component.m_mastObject = ((Component)val3).gameObject;
			component.m_sailObject = ((Component)val4).gameObject;
			component.m_rudderObject = null;
			foreach (Part part in model.Parts)
			{
				Transform val7 = ((part.Group == "paddle") ? val6 : ((part.Group == "sail") ? val4 : ((part.Group == "rudder") ? val5 : val2)));
				Transform obj3 = Node(part.Group + " " + val7.childCount, val7, (part.Group == "sail") ? (-V(spec.sailPivot)) : ((part.Group == "rudder") ? (-V(spec.rudderPivot)) : Vector3.zero));
				((Component)obj3).gameObject.AddComponent<MeshFilter>().sharedMesh = part.Mesh;
				MeshRenderer obj4 = ((Component)obj3).gameObject.AddComponent<MeshRenderer>();
				((Renderer)obj4).sharedMaterial = part.Material;
				((Renderer)obj4).shadowCastingMode = (ShadowCastingMode)1;
				((Renderer)obj4).receiveShadows = true;
			}
			((Component)val6).gameObject.SetActive(false);
			if (PaddleCraft(name))
			{
				PaddleCraftRig paddleCraftRig = prefab.AddComponent<PaddleCraftRig>();
				paddleCraftRig.PaddleTemplate = ((Component)val6).gameObject;
				paddleCraftRig.DoubleBlade = name != "HelmsmanDugout";
			}
			FinalShipPresentation finalShipPresentation = prefab.AddComponent<FinalShipPresentation>();
			finalShipPresentation.Sail = val4;
			finalShipPresentation.Rudder = val5;
			finalShipPresentation.Pivot = V(spec.sailPivot);
			finalShipPresentation.SheetHeads = spec.sheets.Select((Sheet s) => V(s.head)).ToArray();
			finalShipPresentation.SheetFeet = spec.sheets.Select((Sheet s) => V(s.foot)).ToArray();
			finalShipPresentation.RopeMaterial = ImportedShipMaterials.BoatyardMaterial(new Color(0.38f, 0.29f, 0.16f), timber: false);
			owned.Add((Object)(object)finalShipPresentation.RopeMaterial);
			finalShipPresentation.HullRenderers = (from r in ((Component)val2).GetComponentsInChildren<MeshRenderer>(true)
				where ((Object)r).name.StartsWith("hull ")
				select r).ToArray();
			finalShipPresentation.Decorations = (from r in ((Component)val2).GetComponentsInChildren<MeshRenderer>(true)
				where ((Object)r).name.StartsWith("decoration ")
				select ((Component)r).gameObject).ToArray();
			finalShipPresentation.SailRenderers = ((Component)val4).GetComponentsInChildren<MeshRenderer>(true);
			finalShipPresentation.AirHeight = spec.airHeight;
			FleetSailMotion fleetSailMotion = prefab.AddComponent<FleetSailMotion>();
			fleetSailMotion.Kind = spec.name;
			fleetSailMotion.Sail = val4;
			fleetSailMotion.Top = spec.sailPivot[1];
			fleetSailMotion.AuthoredMeshes = (from f in ((Component)val4).GetComponentsInChildren<MeshFilter>(true)
				select f.sharedMesh).ToArray();
			finalShipPresentation.Motion = fleetSailMotion;
			finalShipPresentation.Sculling = spec.name == "freighter";
			Solid[] colliders = spec.colliders;
			foreach (Solid solid in colliders)
			{
				Transform obj5 = Node("Hull solid", transform, V(solid.position));
				((Component)obj5).gameObject.layer = LayerMask.NameToLayer("vehicle");
				((Component)obj5).gameObject.AddComponent<BoxCollider>().size = V(solid.size);
			}
			foreach (HullSolid item in spec.hullSolids.Concat(spec.cargoSolids ?? Array.Empty<HullSolid>()))
			{
				if ((item.vertices.Length != 24 || item.triangles.Length != 36) && (item.vertices.Length != 18 || item.triangles.Length != 24))
				{
					throw new InvalidDataException("Invalid convex ship section");
				}
				Vector3[] array = (Vector3[])(object)new Vector3[item.vertices.Length / 3];
				for (int num = 0; num < array.Length; num++)
				{
					array[num] = V(item.vertices.Skip(num * 3).Take(3).ToArray());
				}
				Mesh val8 = new Mesh
				{
					name = "Fitted ship section"
				};
				owned.Add((Object)(object)val8);
				val8.vertices = array;
				val8.triangles = item.triangles;
				val8.RecalculateBounds();
				Transform obj6 = Node("Keel or cargo solid", transform, Vector3.zero);
				((Component)obj6).gameObject.layer = LayerMask.NameToLayer("vehicle");
				MeshCollider obj7 = ((Component)obj6).gameObject.AddComponent<MeshCollider>();
				obj7.sharedMesh = val8;
				obj7.convex = true;
			}
			Rehome(((Component)component.m_floatCollider).transform, transform, new Vector3(0f, spec.waterline, 0f));
			component.m_floatCollider.center = Vector3.zero;
			component.m_floatCollider.size = new Vector3(spec.beam, 0.5f, spec.length);
			((Collider)component.m_floatCollider).isTrigger = true;
			BoxCollider? obj8 = ((IEnumerable<BoxCollider>)prefab.GetComponentsInChildren<BoxCollider>(true)).FirstOrDefault((Func<BoxCollider, bool>)((BoxCollider c) => ((Object)c).name == "OnboardTrigger"));
			if (!Object.op_Implicit((Object)(object)obj8))
			{
				throw new InvalidDataException("Native onboard trigger missing for " + name);
			}
			Rehome(((Component)obj8).transform, transform, new Vector3(0f, spec.walkHeight + 0.8f, 0f));
			obj8.center = Vector3.zero;
			obj8.size = new Vector3(spec.beam, 3.4f, spec.length);
			((Collider)obj8).isTrigger = true;
			AddWaterMask(transform, spec, material);
			prefab.GetComponent<Rigidbody>().centerOfMass = new Vector3(0f, 0.25f, 0f);
			component.m_stearForceOffset = (0f - spec.length) * 0.35f;
			component.m_forceDistance = Mathf.Min(2f, spec.length * 0.16f);
			float num2 = Mathf.Abs(Physics.gravity.y) * component.m_forceDistance / (50f * Mathf.Max(0.01f, component.m_force));
			component.m_waterLevelOffset = 0.25f - spec.waterline + num2;
			component.m_disableLevel = -0.02f;
			Point[] points = spec.points;
			foreach (Point point in points)
			{
				Vector3 val9 = V(point.position);
				Transform val10 = Node(point.kind, transform, val9);
				((Component)val10).gameObject.layer = LayerMask.NameToLayer("piece_nonsolid");
				Vector3 val11 = V(point.facing);
				if (((Vector3)(ref val11)).sqrMagnitude > 0.1f)
				{
					val10.localRotation = Quaternion.LookRotation(val11);
				}
				if (point.kind == "helm")
				{
					Rehome(((Component)shipControlls).transform, transform, val9);
					((Component)shipControlls).gameObject.SetActive(true);
					((Component)shipControlls).gameObject.layer = LayerMask.NameToLayer("piece_nonsolid");
					((Behaviour)shipControlls).enabled = true;
					shipControlls.m_attachPoint = val10;
					shipControlls.m_attachAnimation = ((spec.name == "freighter") ? "attach_mast" : ((spec.name == "snekkja") ? "sit" : "attach_sitship"));
					shipControlls.m_maxUseRange = 2.5f;
					shipControlls.m_detachOffset = new Vector3(0f, 0.1f, 0.7f);
					BoxCollider obj9 = ((Component)shipControlls).gameObject.AddComponent<BoxCollider>();
					obj9.size = new Vector3(0.7f, 0.65f, 0.55f);
					obj9.center = new Vector3(0f, (spec.name == "freighter") ? 0.85f : 0.25f, 0f);
					component.m_controlGuiPos = Node("Helm UI", transform, val9 + Vector3.up);
					if (PaddleCraft(name))
					{
						shipControlls.m_attachAnimation = "sit";
					}
				}
				else if (point.kind == "ladder")
				{
					Ladder obj10 = ((Component)val10).gameObject.AddComponent<Ladder>();
					obj10.m_name = "$piece_ship_ladder";
					obj10.m_useDistance = 3f;
					obj10.m_targetPos = Node("Boarding exit", transform, V(point.exit));
					obj10.m_targetPos.localRotation = val10.localRotation;
					val10.localRotation = Quaternion.identity;
					((Component)val10).gameObject.AddComponent<BoxCollider>().size = V(point.size);
				}
				else
				{
					Chair obj11 = ((Component)val10).gameObject.AddComponent<Chair>();
					obj11.m_name = ((point.kind == "mast") ? "Mast holdfast" : "Seat");
					obj11.m_inShip = true;
					obj11.m_attachPoint = val10;
					obj11.m_attachAnimation = ((point.kind == "mast") ? "attach_mast" : "sit");
					obj11.m_useDistance = 2.5f;
					obj11.m_detachOffset = new Vector3(0f, 0.12f, 0.6f);
					BoxCollider obj12 = ((Component)val10).gameObject.AddComponent<BoxCollider>();
					obj12.size = ((point.kind == "mast") ? new Vector3(0.24f, 1.2f, 0.24f) : new Vector3(0.45f, 0.12f, 0.25f));
					obj12.center = ((point.kind == "mast") ? (Vector3.up * 0.65f) : (Vector3.down * 0.08f));
				}
			}
			Vector3 position = default(Vector3);
			for (int num3 = 0; num3 < componentsInChildren.Length; num3++)
			{
				Container val12 = componentsInChildren[num3];
				((Vector3)(ref position))..ctor((float)((num3 % 2 == 0) ? 1 : (-1)) * spec.beam * 0.17f, (spec.cargoHeight > 0f) ? spec.cargoHeight : (spec.walkHeight + 0.06f), (0f - spec.length) * 0.13f + (float)(num3 / 2) * 0.85f);
				if (PaddleCraft(name))
				{
					((Vector3)(ref position))..ctor(0f, (spec.name == "dugout") ? 0.415f : 0.68f, (spec.name == "tandem") ? 0f : (spec.length * 0.29f));
				}
				if (spec.name == "ceol")
				{
					((Vector3)(ref position))..ctor(-0.27f, 0.65f, -1.28f);
				}
				if (spec.name == "currach")
				{
					((Vector3)(ref position))..ctor(0.32f, 0.5f, -0.8f);
				}
				if (spec.name == "snekkja")
				{
					((Vector3)(ref position))..ctor((num3 % 2 == 0) ? (-0.3f) : 0.3f, 1.04f, (num3 < 2) ? (-6.3f) : 6.3f);
				}
				if (spec.name == "falkusa")
				{
					((Vector3)(ref position))..ctor(0.35f, 0.67f, (num3 == 0) ? 0.55f : (-0.6f));
				}
				Rehome(((Component)val12).transform, transform, position);
				((Component)val12).gameObject.layer = LayerMask.NameToLayer("piece_nonsolid");
				val12.m_open = null;
				val12.m_closed = null;
				((Component)val12).gameObject.AddComponent<BoxCollider>().size = new Vector3(0.5f, 0.1f, 0.5f);
				if (name == "HelmsmanCurrach")
				{
					val12.m_width = 3;
					val12.m_height = 2;
				}
			}
			if (name == "HerculeShip")
			{
				Transform val13 = Node("Fishing net", transform, new Vector3(spec.beam * 0.57f, 0f, 0f));
				((Component)val13).gameObject.layer = LayerMask.NameToLayer("character_trigger");
				BoxCollider obj13 = ((Component)val13).gameObject.AddComponent<BoxCollider>();
				((Collider)obj13).isTrigger = true;
				obj13.size = new Vector3(0.6f, 1.3f, 2.2f);
				ShipFishingNet shipFishingNet = ((Component)val13).gameObject.AddComponent<ShipFishingNet>();
				shipFishingNet.Chest = componentsInChildren[0];
				shipFishingNet.Visual = ((Component)Node("Deployed net", val13, Vector3.zero)).gameObject;
				for (int num4 = 0; num4 < 12; num4++)
				{
					AddNetLine(shipFishingNet.Visual.transform, new Vector3(0f, 0.55f, -1f + (float)num4 * 0.18f), new Vector3(0.25f, -0.6f, -1f + (float)num4 * 0.18f), finalShipPresentation.RopeMaterial);
				}
				for (int num5 = 0; num5 < 7; num5++)
				{
					AddNetLine(shipFishingNet.Visual.transform, new Vector3((float)num5 * 0.25f / 6f, 0.55f - (float)num5 * 1.15f / 6f, -1f), new Vector3((float)num5 * 0.25f / 6f, 0.55f - (float)num5 * 1.15f / 6f, 1f), finalShipPresentation.RopeMaterial);
				}
				Transform obj14 = Node("Net handle", transform, new Vector3(spec.beam * 0.36f, spec.walkHeight + 0.3f, 0f));
				((Component)obj14).gameObject.layer = LayerMask.NameToLayer("piece_nonsolid");
				((Component)obj14).gameObject.AddComponent<NetInteraction>().Net = shipFishingNet;
				((Component)obj14).gameObject.AddComponent<BoxCollider>().size = new Vector3(0.22f, 0.3f, 0.3f);
			}
		}

		private static void AddWaterMask(Transform root, Spec spec, Material material)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (spec.waterMask.Length < 4 || spec.waterMask.Length % 2 != 0)
			{
				throw new InvalidDataException("Missing fitted water mask");
			}
			Vector3[] array = spec.waterMask.Select(V).ToArray();
			List<int> list = new List<int>();
			for (int i = 2; i < array.Length; i += 2)
			{
				int num = i - 2;
				list.AddRange(new int[6]
				{
					num,
					num + 2,
					num + 1,
					num + 1,
					num + 2,
					num + 3
				});
			}
			Mesh val = new Mesh
			{
				name = "Fitted interior water mask"
			};
			owned.Add((Object)(object)val);
			val.vertices = array;
			val.SetTriangles(list, 0);
			val.RecalculateNormals();
			val.RecalculateBounds();
			Transform obj = Node("Interior water mask", root, Vector3.zero);
			((Component)obj).gameObject.AddComponent<MeshFilter>().sharedMesh = val;
			MeshRenderer obj2 = ((Component)obj).gameObject.AddComponent<MeshRenderer>();
			((Renderer)obj2).sharedMaterial = material;
			((Renderer)obj2).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)obj2).receiveShadows = false;
		}

		private static void AddNetLine(Transform parent, Vector3 a, Vector3 b, Material material)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			LineRenderer obj = ((Component)Node("Net cord", parent, Vector3.zero)).gameObject.AddComponent<LineRenderer>();
			obj.useWorldSpace = false;
			obj.positionCount = 2;
			obj.SetPosition(0, a);
			obj.SetPosition(1, b);
			float startWidth = (obj.endWidth = 0.012f);
			obj.startWidth = startWidth;
			((Renderer)obj).sharedMaterial = material;
		}

		internal static void ConfigureStyles(GameObject prefab, ShipCosmetics cosmetics)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)prefab.GetComponent<FinalShipPresentation>()))
			{
				cosmetics.Binding = new ShipCosmeticBinding
				{
					prefab = ((Object)prefab).name
				};
				cosmetics.HullStyles = (Material[])(object)new Material[5]
				{
					Choice("Weathered timber", Color.white),
					Choice("Red ochre", new Color(0.65f, 0.25f, 0.14f)),
					Choice("Deep blue", new Color(0.21f, 0.39f, 0.57f)),
					Choice("Ochre", new Color(0.82f, 0.62f, 0.27f)),
					Choice("Carved timber", Color.white)
				};
				if (((Object)prefab).name != "MercantShip" && ((Object)prefab).name != "BigCargoShip")
				{
					cosmetics.HullStyles = cosmetics.HullStyles.Take(4).ToArray();
				}
				cosmetics.SailStyles = (Material[])(PaddleCraft(((Object)prefab).name) ? ((Array)Array.Empty<Material>()) : ((Array)new Material[4]
				{
					Choice("Original cloth", Color.white),
					Choice("Unbleached linen", new Color(1f, 0.95f, 0.8f)),
					Choice("Red wool", new Color(0.65f, 0.23f, 0.16f)),
					Choice("Blue wool", new Color(0.37f, 0.54f, 0.7f))
				}));
			}
			static Material Choice(string name, Color color)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				Material val = ImportedShipMaterials.BoatyardMaterial(color, timber: false);
				((Object)val).name = name;
				owned.Add((Object)(object)val);
				return val;
			}
		}

		internal static void Release()
		{
			foreach (Object item in owned)
			{
				if (Object.op_Implicit(item))
				{
					Object.Destroy(item);
				}
			}
			owned.Clear();
			cache.Clear();
		}

		internal static Spec ReadSpecification(string json, string expected)
		{
			Spec spec = JsonConvert.DeserializeObject<Spec>(json) ?? throw new InvalidDataException("Missing final ship specification: " + expected);
			int num = spec.points?.Count((Point p) => p != null && p.kind == "helm") ?? 0;
			if (spec.prefab != expected || !Finite(spec.length) || spec.length < 4f || spec.length > 30f || !Finite(spec.beam) || spec.beam < 1f || spec.beam > 10f || num != 1)
			{
				throw new InvalidDataException($"Invalid final ship specification for {expected}: prefab={spec.prefab}, length={spec.length}, beam={spec.beam}, helms={num}");
			}
			Spec spec2 = spec;
			if (spec2.cargoSolids == null)
			{
				spec2.cargoSolids = Array.Empty<HullSolid>();
			}
			if (spec.hullSolids == null || spec.colliders == null || spec.sheets == null)
			{
				throw new InvalidDataException("Missing final ship geometry: " + expected);
			}
			return spec;
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class FinalShipPresentation : MonoBehaviour
	{
		public Transform Sail;

		public Transform Rudder;

		public GameObject[] Decorations = Array.Empty<GameObject>();

		public Vector3 Pivot;

		public Vector3[] SheetHeads = Array.Empty<Vector3>();

		public Vector3[] SheetFeet = Array.Empty<Vector3>();

		public Material RopeMaterial;

		public MeshRenderer[] HullRenderers = Array.Empty<MeshRenderer>();

		public MeshRenderer[] SailRenderers = Array.Empty<MeshRenderer>();

		public float AirHeight;

		public FleetSailMotion? Motion;

		public bool Sculling;

		private LineRenderer[] sheets = Array.Empty<LineRenderer>();

		private readonly Dictionary<Renderer, Material> originals = new Dictionary<Renderer, Material>();

		private readonly List<Material> painted = new List<Material>();

		private void Start()
		{
			//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)
			if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated())
			{
				sheets = (LineRenderer[])(object)new LineRenderer[SheetHeads.Length];
				for (int i = 0; i < sheets.Length; i++)
				{
					GameObject val = new GameObject("Working sheet");
					val.transform.SetParent(((Component)this).transform, false);
					LineRenderer val2 = val.AddComponent<LineRenderer>();
					val2.useWorldSpace = false;
					val2.positionCount = 9;
					float startWidth = (val2.endWidth = 0.018f);
					val2.startWidth = startWidth;
					((Renderer)val2).sharedMaterial = RopeMaterial;
					sheets[i] = val2;
				}
			}
		}

		private void LateUpdate()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: 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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: 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: Invalid comparison between Unknown and I4
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)Sail))
			{
				return;
			}
			Ship component = ((Component)this).GetComponent<Ship>();
			if (Object.op_Implicit((Object)(object)Rudder) && Object.op_Implicit((Object)(object)component))
			{
				float num = ((Sculling && ((int)component.GetSpeedSetting() == 2 || (int)component.GetSpeedSetting() == 1)) ? (Mathf.Sin(Time.time * 2.4f) * 5f) : 0f);
				Rudder.localRotation = Quaternion.Euler(0f, Mathf.Clamp(-8f * component.GetRudderValue() + num, -8f, 8f), 0f);
			}
			for (int i = 0; i < sheets.Length; i++)
			{
				Vector3 val = (Object.op_Implicit((Object)(object)Motion) ? Motion.Furl(SheetHeads[i]) : (Pivot + Vector3.Scale(SheetHeads[i] - Pivot, Sail.localScale)));
				for (int j = 0; j < 9; j++)
				{
					float num2 = (float)j / 8f;
					Vector3 val2 = Vector3.Lerp(val, SheetFeet[i], num2);
					val2.y -= Mathf.Sin(num2 * (float)Math.PI) * 0.09f;
					sheets[i].SetPosition(j, val2);
				}
			}
		}

		internal void Paint(int hull, int sail, Material[] hullStyles, Material[] sailStyles)
		{
			foreach (Material item in painted)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					Object.Destroy((Object)(object)item);
				}
			}
			painted.Clear();
			GameObject[] decorations = Decorations;
			foreach (GameObject val in decorations)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					val.SetActive(hull == 4);
				}
			}
			Apply(HullRenderers, hull, hullStyles);
			Apply(SailRenderers, sail, sailStyles);
			void Apply(MeshRenderer[] renderers, int choice, Material[] choices)
			{
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: Expected O, but got Unknown
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				foreach (MeshRenderer val2 in renderers)
				{
					if (!originals.TryGetValue((Renderer)(object)val2, out Material value))
					{
						value = ((Renderer)val2).sharedMaterial;
						originals[(Renderer)(object)val2] = value;
					}
					if (choice <= 0 || choice >= choices.Length)
					{
						((Renderer)val2).sharedMaterial = value;
					}
					else
					{
						Material val3 = new Material(value);
						val3.color = choices[choice].color;
						((Renderer)val2).sharedMaterial = val3;
						painted.Add(val3);
					}
				}
			}
		}

		private void OnDestroy()
		{
			foreach (Material item in painted)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					Object.Destroy((Object)(object)item);
				}
			}
		}
	}
	public sealed class FishCollisionPass : MonoBehaviour
	{
		private Ship ship;

		private float nextScan;

		private readonly HashSet<(Collider Boat, Collider Fish)> changed = new HashSet<(Collider, Collider)>();

		internal static void UpdateFor(Ship ship, bool active)
		{
			if (!Object.op_Implicit((Object)(object)ship))
			{
				return;
			}
			FishCollisionPass component = ((Component)ship).GetComponent<FishCollisionPass>();
			if (!active)
			{
				if (Object.op_Implicit((Object)(object)component))
				{
					component.Restore();
					Object.Destroy((Object)(object)component);
				}
			}
			else if (!Object.op_Implicit((Object)(object)component))
			{
				component = ((Component)ship).gameObject.AddComponent<FishCollisionPass>();
				component.ship = ship;
			}
		}

		private void Update()
		{
			Voyage voyage = Plugin.Instance.Voyage;
			if (!Object.op_Implicit((Object)(object)ship) || !Plugin.Instance.FishPassThrough.Value || !Object.op_Implicit((Object)(object)voyage) || (Object)(object)voyage.Ship != (Object)(object)ship || !voyage.CanControl || ship.m_shipControlls.HaveValidUser())
			{
				Restore();
				Object.Destroy((Object)(object)this);
			}
			else if (!(Time.time < nextScan))
			{
				nextScan = Time.time + 0.1f;
				Refresh();
			}
		}

		internal void Refresh()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)ship.m_floatCollider))
			{
				return;
			}
			Bounds bounds = ((Collider)ship.m_floatCollider).bounds;
			((Bounds)(ref bounds)).Expand(6f);
			HashSet<Fish> hashSet = new HashSet<Fish>();
			Collider[] array = Physics.OverlapBox(((Bounds)(ref bounds)).center, ((Bounds)(ref bounds)).extents, Quaternion.identity, -1, (QueryTriggerInteraction)1);
			for (int i = 0; i < array.Length; i++)
			{
				Fish componentInParent = ((Component)array[i]).GetComponentInParent<Fish>();
				if (Object.op_Implicit((Object)(object)componentInParent))
				{
					hashSet.Add(componentInParent);
				}
			}
			HashSet<(Collider, Collider)> hashSet2 = new HashSet<(Collider, Collider)>();
			array = ((Component)ship).GetComponentsInChildren<Collider>();
			foreach (Collider val in array)
			{
				if (!val.enabled || val.isTrigger || (Object)(object)((Component)val).GetComponentInParent<Ship>() != (Object)(object)ship)
				{
					continue;
				}
				foreach (Fish item2 in hashSet)
				{
					Collider[] componentsInChildren = ((Component)item2).GetComponentsInChildren<Collider>();
					foreach (Collider val2 in componentsInChildren)
					{
						if (val2.enabled && !val2.isTrigger)
						{
							(Collider, Collider) item = (val, val2);
							hashSet2.Add(item);
							if (!changed.Contains(item) && !Physics.GetIgnoreCollision(val, val2))
							{
								Physics.IgnoreCollision(val, val2, true);
								changed.Add(item);
							}
						}
					}
				}
			}
			foreach (var item3 in new List<(Collider, Collider)>(changed))
			{
				if (!hashSet2.Contains(item3))
				{
					if (Object.op_Implicit((Object)(object)item3.Item1) && Object.op_Implicit((Object)(object)item3.Item2))
					{
						Physics.IgnoreCollision(item3.Item1, item3.Item2, false);
					}
					changed.Remove(item3);
				}
			}
		}

		internal void Restore()
		{
			foreach (var item in changed)
			{
				if (Object.op_Implicit((Object)(object)item.Boat) && Object.op_Implicit((Object)(object)item.Fish))
				{
					Physics.IgnoreCollision(item.Boat, item.Fish, false);
				}
			}
			changed.Clear();
		}

		private void OnDisable()
		{
			Restore();
		}

		private void OnDestroy()
		{
			Restore();
		}
	}
	public sealed class FishingDock : MonoBehaviour
	{
		public Container Chest;

		public Transform WorkerAnchor;

		public string Catch = "Fish1";

		private static ConfigEntry<float> seconds = null;

		private static ConfigEntry<int> limit = null;

		private static readonly int Progress = StringExtensionMethods.GetStableHashCode("helmsman_fishing_progress_v1");

		private ZNetView view;

		private PerchedBird? bird;

		private double previous;

		private float next;

		private Vector3 perch;

		private float idleOffset;

		internal static void Configure(ConfigFile config)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			seconds = config.Bind<float>("Fishing Dock", "SecondsPerCatch", 20f, new ConfigDescription("Daylight work seconds per fish.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 3600f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			limit = config.Bind<int>("Fishing Dock", "CatchLimit", 50, new ConfigDescription("Pause when the dock chest contains this many fish.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
		}

		private void Awake()
		{
			view = ((Component)this).GetComponent<ZNetView>();
		}

		private IEnumerator Start()
		{
			while (!Object.op_Implicit((Object)(object)view) || !view.IsValid() || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				yield return (object)new WaitForSeconds(1f);
			}
			Material val = ((Component)this).GetComponentsInChildren<MeshRenderer>(true).SelectMany((MeshRenderer r) => ((Renderer)r).sharedMaterials).FirstOrDefault((Func<Material, bool>)((Material m) => Object.op_Implicit((Object)(object)m) && Object.op_Implicit((Object)(object)m.shader) && ((Object)m.shader).name == "Custom/Piece"));
			if (!Object.op_Implicit((Object)(object)val))
			{
				yield break;
			}
			bird = new PerchedBird(null, val, burrowingOwl: false, fishingPelican: true);
			bird.Root.transform.localScale = Vector3.one * 0.8f;
			bird.Probes(((Component)this).transform);
			idleOffset = Random.Range(0f, 23f);
			perch = (Object.op_Implicit((Object)(object)WorkerAnchor) ? ((Component)this).transform.InverseTransformPoint(WorkerAnchor.position) : Vector3.zero);
			Ray val2 = default(Ray);
			((Ray)(ref val2))..ctor(((Component)this).transform.TransformPoint(perch) + ((Component)this).transform.up * 4f, -((Component)this).transform.up);
			float num = float.PositiveInfinity;
			Collider[] componentsInChildren = ((Component)this).GetComponentsInChildren<Collider>(true);
			RaycastHit val4 = default(RaycastHit);
			foreach (Collider val3 in componentsInChildren)
			{
				if (val3.enabled && !val3.isTrigger && ((Component)val3).gameObject.activeInHierarchy && val3.Raycast(val2, ref val4, 6f) && ((RaycastHit)(ref val4)).distance < num)
				{
					num = ((RaycastHit)(ref val4)).distance;
					perch = ((Component)this).transform.InverseTransformPoint(((RaycastHit)(ref val4)).point);
				}
			}
			SphereCollider obj = bird.Root.AddComponent<SphereCollider>();
			obj.center = new Vector3(0f, 0.7f, 0f);
			obj.radius = 0.35f;
			BirdInteraction birdInteraction = bird.Root.AddComponent<BirdInteraction>();
			birdInteraction.Label = () => "Pelican fisherman";
			birdInteraction.Hint = () => Localization.instance.Localize("Pelican fisherman\n[<color=yellow><b>$KEY_Use</b></color>] Catch report");
			birdInteraction.Use = delegate
			{
				Plugin.Message((!EnvMan.IsDaylight()) ? "Fishing resumes at dawn." : (CanFish(out ItemDrop _) ? ("Fishing. Next catch in " + Shipyard.FormatDuration(Math.Max(0f, seconds.Value - view.GetZDO().GetFloat(Progress, 0f))) + ".") : "Waiting for space in the station chest."));
				return true;
			};
		}

		private bool CanFish(out ItemDrop? item)
		{
			object obj;
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				obj = null;
			}
			else
			{
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(Catch);
				obj = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
			}
			item = (ItemDrop?)obj;
			if (!Object.op_Implicit((Object)(object)item) || !Object.op_Implicit((Object)(object)Chest) || Chest.GetInventory() == null || Chest.IsInUse() || view.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0 || !Object.op_Implicit((Object)(object)EnvMan.instance) || !EnvMan.IsDaylight())
			{
				return false;
			}
			Inventory inventory = Chest.GetInventory();
			if (inventory.CountItems(item.m_itemData.m_shared.m_name, -1, true) < limit.Value)
			{
				return inventory.CanAddItem(item.m_itemData, 1);
			}
			return false;
		}

		private void Update()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)view) || !view.IsValid() || !Object.op_Implicit((Object)(object)ZNet.instance))
			{
				return;
			}
			ItemDrop item;
			bool flag = CanFish(out item);
			if (bird != null)
			{
				bird.Root.transform.position = ((Component)this).transform.TransformPoint(perch);
				bird.Fishing(flag);
				bird.Root.transform.rotation = (Object.op_Implicit((Object)(object)WorkerAnchor) ? WorkerAnchor.rotation : ((Component)this).transform.rotation);
				float num = Mathf.Sin(Time.time * 0.75f);
				bird.Pose(flag ? (12f + 6f * num) : 0f, flag ? (10f * num) : 0f, 0f, flag ? 4 : 0, 0f, flag ? (8f + 4f * num) : 0f);
				int num2;
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					Vector3 val = ((Component)Player.m_localPlayer).transform.position - bird.Root.transform.position;
					num2 = ((((Vector3)(ref val)).sqrMagnitude < 9f) ? 1 : 0);
				}
				else
				{
					num2 = 0;
				}
				bool flag2 = (byte)num2 != 0;
				bool flag3 = !flag && Object.op_Implicit((Object)(object)EnvMan.instance) && !EnvMan.IsDaylight() && !flag2;
				if (!flag3)
				{
					bird.Idle(Time.time + idleOffset, flag);
				}
				bird.Rest(flag3, Time.time + idleOffset, Time.deltaTime);
			}
			if (Time.time < next)
			{
				return;
			}
			next = Time.time + 1f;
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			double num3 = ((previous > 0.0) ? Math.Max(0.0, Math.Min(2.0, timeSeconds - previous)) : 0.0);
			previous = timeSeconds;
			if (!view.IsOwner() || !flag)
			{
				return;
			}
			float num4 = Mathf.Clamp(view.GetZDO().GetFloat(Progress, 0f), 0f, seconds.Value) + (float)num3;
			if (num4 >= seconds.Value)
			{
				ItemData val2 = item.m_itemData.Clone();
				val2.m_stack = 1;
				val2.m_dropPrefab = ObjectDB.instance.GetItemPrefab(Catch);
				if (Chest.GetInventory().AddItem(val2))
				{
					num4 -= seconds.Value;
				}
			}
			view.GetZDO().Set(Progress, Mathf.Min(num4, seconds.Value));
		}

		private void OnDestroy()
		{
			bird?.Dispose();
		}
	}
	internal static class FleetHandling
	{
		internal static void Apply(Ship ship, string model)
		{
			(float, float, float, float, float) tuple = model switch
			{
				"HelmsmanDugout" => (1f, 1f, 1.15f, 1.1f, 1.15f), 
				"HelmsmanFinewoodKayak" => (1f, 1.1f, 1.25f, 1.2f, 1.35f), 
				"HelmsmanTandemKayak" => (1f, 1.1f, 1.2f, 1.15f, 1.3f), 
				"LittleBoat" => (1.1f, 1.15f, 1.25f, 1.2f, 1.25f), 
				"HelmsmanCurrach" => (1.08f, 1.15f, 1.25f, 1.2f, 1.25f), 
				"HerculeShip" => (1.12f, 1.15f, 1.2f, 1.15f, 1.2f), 
				"MercantShip" => (1.1f, 1.1f, 1.15f, 1.1f, 1.15f), 
				"BigCargoShip" => (1.05f, 1.1f, 1.1f, 1.05f, 1.1f), 
				"WarShip" => (1.18f, 1.2f, 1.3f, 1.25f, 1.3f), 
				_ => (1f, 1f, 1f, 1f, 1f), 
			};
			ship.m_sailForceFactor *= tuple.Item1;
			ship.m_backwardForce *= tuple.Item2;
			ship.m_stearForce *= tuple.Item3;
			ship.m_stearVelForceFactor *= tuple.Item4;
			ship.m_rudderSpeed *= tuple.Item5;
		}
	}
	public sealed class FleetSailMotion : MonoBehaviour
	{
		private sealed class Cloth
		{
			internal Mesh Mesh;

			internal Vector3[] Rest;

			internal Vector3[] Live;
		}

		public string Kind = "";

		public Transform Sail;

		public float Top;

		public Mesh[] AuthoredMeshes = Array.Empty<Mesh>();

		private ImportedSailAnimation? animationState;

		private readonly List<Cloth> cloth = new List<Cloth>();

		private float nextTick;

		private float halfWidth = 1f;

		private float bottom;

		private bool frozenPreview;

		internal void FreezePreview()
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: 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)
			if (frozenPreview)
			{
				return;
			}
			frozenPreview = true;
			((Behaviour)this).enabled = false;
			FinalShipPresentation component = ((Component)this).GetComponent<FinalShipPresentation>();
			if (Object.op_Implicit((Object)(object)component))
			{
				((Behaviour)component).enabled = false;
			}
			int num = 0;
			MeshFilter[] componentsInChildren = ((Component)Sail).GetComponentsInChildren<MeshFilter>(true);
			foreach (MeshFilter val in componentsInChildren)
			{
				Mesh val2 = (Mesh)((num < AuthoredMeshes.Length) ? ((object)AuthoredMeshes[num++]) : ((object)val.sharedMesh));
				if (Object.op_Implicit((Object)(object)val2))
				{
					Mesh val3 = Object.Instantiate<Mesh>(val2);
					Vector3[] vertices = val2.vertices;
					for (int j = 0; j < vertices.Length; j++)
					{
						vertices[j] = Vector3.Lerp(FleetSailShape.Anchor(Kind, vertices[j], Top), vertices[j], 0.1f);
					}
					val3.vertices = vertices;
					val3.RecalculateNormals();
					val3.RecalculateBounds();
					val.sharedMesh = val3;
					cloth.Add(new Cloth
					{
						Mesh = val3
					});
				}
			}
		}

		private void Start()
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsDedicated())
			{
				return;
			}
			animationState = ((Component)this).GetComponent<ImportedSailAnimation>();
			bottom = Top;
			int num = 0;
			MeshFilter[] componentsInChildren = ((Component)Sail).GetComponentsInChildren<MeshFilter>(true);
			foreach (MeshFilter val in componentsInChildren)
			{
				Mesh val2 = (Mesh)((num < AuthoredMeshes.Length) ? ((object)AuthoredMeshes[num++]) : ((object)val.sharedMesh));
				if (Object.op_Implicit((Object)(object)val2))
				{
					Mesh val3 = Object.Instantiate<Mesh>(val2);
					val3.MarkDynamic();
					val.sharedMesh = val3;
					Vector3[] vertices = val3.vertices;
					Vector3[] array = vertices;
					foreach (Vector3 val4 in array)
					{
						halfWidth = Mathf.Max(halfWidth, Mathf.Abs(val4.x));
						bottom = Mathf.Min(bottom, val4.y);
					}
					cloth.Add(new Cloth
					{
						Mesh = val3,
						Rest = vertices,
						Live = (Vector3[])(object)new Vector3[vertices.Length]
					});
				}
			}
		}

		public Vector3 Furl(Vector3 p)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			float num = (Object.op_Implicit((Object)(object)animationState) ? animationState.FurlAmount : 1f);
			return Vector3.Lerp(FleetSailShape.Anchor(Kind, p, Top), p, num);
		}

		private void LateUpdate()
		{
			//IL_008e: 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_0096: 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)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < nextTick)
			{
				return;
			}
			nextTick = Time.time + 0.05f;
			float time = Time.time;
			float amount = (Object.op_Implicit((Object)(object)animationState) ? animationState.FurlAmount : 1f);
			float wind = (Object.op_Implicit((Object)(object)EnvMan.instance) ? EnvMan.instance.GetWindIntensity() : 0f);
			foreach (Cloth item in cloth)
			{
				for (int i = 0; i < item.Rest.Length; i++)
				{
					Vector3 p = item.Rest[i];
					Vector3 val = Furl(p);
					float num = FleetSailShape.Flutter(Kind, p, Top, bottom, halfWidth, time, wind, amount);
					if (Kind == "falkusa" || Kind == "currach")
					{
						val.x += num;
					}
					else
					{
						val.z += num;
					}
					item.Live[i] = val;
				}
				item.Mesh.vertices = item.Live;
				item.Mesh.RecalculateNormals();
				item.Mesh.RecalculateBounds();
			}
		}

		private void OnDestroy()
		{
			foreach (Cloth item in cloth)
			{
				if (Object.op_Implicit((Object)(object)item.Mesh))
				{
					Object.Destroy((Object)(object)item.Mesh);
				}
			}
			cloth.Clear();
		}
	}
	internal static class FleetSailShape
	{
		internal static Vector3 Anchor(string kind, Vector3 p, float top)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (kind == "falkusa")
			{
				if (!(p.x > 0f))
				{
					return Project(p, new Vector3(-0.25f, 1.48f, 5.45f), new Vector3(-0.22f, 6f, -0.8f));
				}
				return Project(p, new Vector3(0.45f, 1.95f, 3.4f), new Vector3(0.45f, 8.65f, -3.25f));
			}
			if (kind == "currach")
			{
				return Project(p, new Vector3(0.125f, 4.4f, 1.12f), new Vector3(0.135f, 0.95f, 1.12f));
			}
			return new Vector3(p.x, top, p.z);
		}

		private static Vector3 Project(Vector3 p, Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//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_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: 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)
			Vector3 val = b - a;
			return a + val * Mathf.Clamp01(Vector3.Dot(p - a, val) / ((Vector3)(ref val)).sqrMagnitude);
		}

		internal static float Weight(string kind, Vector3 p, float top, float bottom, float halfWidth)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: 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_

BepInEx/plugins/ValheimHelmsman/Helmsman.Core.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Helmsman.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.42.0")]
[assembly: AssemblyInformationalVersion("0.2.42+36c9918e3a0f86f4012d19cdddf84217608f7fa4")]
[assembly: AssemblyProduct("Helmsman.Core")]
[assembly: AssemblyTitle("Helmsman.Core")]
[assembly: AssemblyVersion("0.2.42.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Helmsman.Core
{
	public sealed class AdaptiveRouteSearch
	{
		private RouteSearch search;

		private readonly Point start;

		private readonly Point goal;

		private readonly Func<Point, Point, bool> clear;

		private int regionalExpanded;

		public bool Fine { get; private set; }

		public SearchState State => search.State;

		public List<Point> Route => search.Route;

		public int Expanded => regionalExpanded + search.Expanded;

		public AdaptiveRouteSearch(Point start, Point goal, Func<Point, Point, bool> clear, Func<Point, Point, double>? weight = null)
		{
			this.start = start;
			this.goal = goal;
			this.clear = clear;
			search = new RouteSearch(start, goal, clear, 24.0, 20000, weight);
		}

		public void Step(int budget = 8)
		{
			search.Step(budget);
			if (!Fine && search.State == SearchState.NoRoute)
			{
				regionalExpanded = search.Expanded;
				Fine = true;
				search = new RouteSearch(start, goal, clear, 6.0);
			}
		}
	}
	public enum BoardingState
	{
		GracePeriod,
		WaitingForPlayer,
		FinalCountdown,
		Ready
	}
	public sealed class BoardingGate
	{
		private readonly double graceEnd;

		private readonly double finalSeconds;

		private double? finalEnd;

		private bool waitedForPlayer;

		public BoardingState State { get; private set; }

		public double Remaining { get; private set; }

		public BoardingGate(double now, double graceSeconds = 10.0, double finalSeconds = 3.0)
		{
			graceEnd = now + Math.Max(0.0, graceSeconds);
			this.finalSeconds = Math.Max(0.0, finalSeconds);
		}

		public bool Update(double now, bool aboard)
		{
			Remaining = Math.Max(0.0, graceEnd - now);
			if (now < graceEnd)
			{
				State = BoardingState.GracePeriod;
				return false;
			}
			if (!aboard)
			{
				waitedForPlayer = true;
				finalEnd = null;
				State = BoardingState.WaitingForPlayer;
				return false;
			}
			if (waitedForPlayer)
			{
				if (!finalEnd.HasValue)
				{
					finalEnd = now + finalSeconds;
				}
				Remaining = Math.Max(0.0, finalEnd.Value - now);
				if (Remaining > 0.0)
				{
					State = BoardingState.FinalCountdown;
					return false;
				}
			}
			State = BoardingState.Ready;
			return true;
		}
	}
	public static class CommissionCosts
	{
		public static string Commit(bool freeBuild, Func<string> payMaterials, Action commit, Action<Exception> reportError)
		{
			if (!freeBuild)
			{
				return payMaterials();
			}
			try
			{
				commit();
				return "";
			}
			catch (Exception obj)
			{
				reportError(obj);
				return "The free build order could not be started.";
			}
		}

		public static IReadOnlyDictionary<string, int> Refund(string recipe, bool freeBuild)
		{
			if (!freeBuild)
			{
				return ShipwrightRules.Costs(recipe);
			}
			return new Dictionary<string, int>();
		}
	}
	public static class ConstructionClock
	{
		public static void Advance(ref long seen, ref long excluded, ref bool paused, long now, bool ready, bool continuing)
		{
			if (seen > 0)
			{
				long num = Math.Max(0L, now - seen);
				if ((!ready | paused) || !continuing || num > 50000000)
				{
					excluded += num;
				}
			}
			seen = Math.Max(seen, now);
			paused = !ready;
		}

		public static long Time(long now, long seen, long excluded, bool paused)
		{
			if (seen != 0L)
			{
				return Math.Max(0L, Math.Min(now, seen + (paused ? 0 : 10000000)) - excluded);
			}
			return now;
		}
	}
	public static class ConstructionFunding
	{
		public static Dictionary<string, int> Supplied(string supplied)
		{
			if (!string.IsNullOrEmpty(supplied))
			{
				return ShipwrightRules.Costs(supplied);
			}
			return new Dictionary<string, int>(StringComparer.Ordinal);
		}

		public static bool Valid(string recipe, string supplied)
		{
			try
			{
				Dictionary<string, int> costs = ShipwrightRules.Costs(recipe);
				int value;
				return Supplied(supplied).All<KeyValuePair<string, int>>((KeyValuePair<string, int> p) => costs.TryGetValue(p.Key, out value) && p.Value <= value);
			}
			catch (ArgumentException)
			{
				return false;
			}
			catch (OverflowException)
			{
				return false;
			}
		}

		public static int Remaining(string recipe, string supplied, string item)
		{
			Dictionary<string, int> dictionary = ShipwrightRules.Costs(recipe);
			Dictionary<string, int> dictionary2 = Supplied(supplied);
			if (!dictionary.TryGetValue(item, out var value))
			{
				return 0;
			}
			int value2;
			return Math.Max(0, value - (dictionary2.TryGetValue(item, out value2) ? value2 : 0));
		}

		public static string Credit(string recipe, string supplied, string item, int actual)
		{
			if (!Valid(recipe, supplied) || actual < 0 || actual > Remaining(recipe, supplied, item))
			{
				throw new ArgumentException("Invalid construction material receipt");
			}
			Dictionary<string, int> dictionary = Supplied(supplied);
			if (actual > 0)
			{
				dictionary[item] = (dictionary.TryGetValue(item, out var value) ? value : 0) + actual;
			}
			return string.Join(",", from p in dictionary.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.Ordinal)
				select p.Key + ":" + p.Value);
		}

		public static bool Complete(string recipe, string supplied)
		{
			if (Valid(recipe, supplied))
			{
				return ShipwrightRules.Costs(recipe).All<KeyValuePair<string, int>>((KeyValuePair<string, int> p) => Remaining(recipe, supplied, p.Key) == 0);
			}
			return false;
		}

		public static IReadOnlyDictionary<string, int> Refund(string recipe, string supplied, bool waiting, bool free)
		{
			if (!free)
			{
				if (!waiting)
				{
					return ShipwrightRules.Costs(recipe);
				}
				return Supplied(supplied);
			}
			return new Dictionary<string, int>();
		}
	}
	public static class DockApproach
	{
		public static bool TrySelect(Point berth, Point forward, double shipLength, Func<Point, Point, bool> corridorClear, Func<Point, Point, bool> leadClear, out Point approach, out Point lead)
		{
			approach = (lead = berth);
			double num = Math.Min(35.0, Math.Max(10.0, shipLength * 1.25));
			double num2 = Math.Min(20.0, Math.Max(6.0, shipLength * 0.75));
			double num3 = 35.0;
			while (true)
			{
				Point point = new Point(berth.X - forward.X * num3, berth.Y - forward.Y * num3);
				if (corridorClear(point, berth))
				{
					double num4 = 20.0;
					while (true)
					{
						Point point2 = new Point(point.X - forward.X * num4, point.Y - forward.Y * num4);
						if (leadClear(point2, point))
						{
							approach = point;
							lead = point2;
							return true;
						}
						if (num4 <= num2)
						{
							break;
						}
						num4 = Math.Max(num2, num4 - 6.0);
					}
				}
				if (num3 <= num)
				{
					break;
				}
				num3 = Math.Max(num, num3 - 6.0);
			}
			return false;
		}
	}
	public static class GuardedSteps
	{
		public static IEnumerator Run(IEnumerator steps, Action<Exception> failed)
		{
			try
			{
				while (true)
				{
					object current;
					try
					{
						if (!steps.MoveNext())
						{
							break;
						}
						current = steps.Current;
					}
					catch (Exception obj)
					{
						failed(obj);
						break;
					}
					yield return current;
				}
			}
			finally
			{
				(steps as IDisposable)?.Dispose();
			}
		}
	}
	public static class GullcallModel
	{
		public sealed class Part
		{
			public string Name = "";

			public float[] Color = Array.Empty<float>();

			public float[] Vertices = Array.Empty<float>();

			public int[] Triangles = Array.Empty<int>();
		}

		public static List<Part> Read(Stream stream)
		{
			using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
			if (binaryReader.ReadUInt32() != 827086151)
			{
				throw new InvalidDataException("Invalid Gullcall model header.");
			}
			int num = binaryReader.ReadInt32();
			if (num < 1 || num > 32)
			{
				throw new InvalidDataException("Invalid part count.");
			}
			List<Part> list = new List<Part>();
			for (int i = 0; i < num; i++)
			{
				int num2 = binaryReader.ReadUInt16();
				if (num2 == 0 || num2 > 256)
				{
					throw new InvalidDataException("Invalid part name.");
				}
				byte[] array = binaryReader.ReadBytes(num2);
				if (array.Length != num2)
				{
					throw new EndOfStreamException();
				}
				Part part = new Part
				{
					Name = Encoding.UTF8.GetString(array),
					Color = new float[4]
				};
				for (int j = 0; j < 4; j++)
				{
					part.Color[j] = Finite(binaryReader.ReadSingle());
				}
				int num3 = binaryReader.ReadInt32();
				if (num3 < 3 || num3 > 65535)
				{
					throw new InvalidDataException("Invalid vertex count.");
				}
				part.Vertices = new float[num3 * 3];
				for (int k = 0; k < part.Vertices.Length; k++)
				{
					part.Vertices[k] = Finite(binaryReader.ReadSingle());
				}
				int num4 = binaryReader.ReadInt32();
				if (num4 < 3 || num4 > 200000 || num4 % 3 != 0)
				{
					throw new InvalidDataException("Invalid triangle count.");
				}
				part.Triangles = new int[num4];
				for (int l = 0; l < num4; l++)
				{
					int num5 = binaryReader.ReadInt32();
					if (num5 < 0 || num5 >= num3)
					{
						throw new InvalidDataException("Invalid triangle index.");
					}
					part.Triangles[l] = num5;
				}
				list.Add(part);
			}
			if (stream.Position != stream.Length)
			{
				throw new InvalidDataException("Unexpected trailing model data.");
			}
			return list;
		}

		private static float Finite(float value)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				throw new InvalidDataException("Invalid model coordinate.");
			}
			return value;
		}
	}
	public enum GullMood
	{
		Calm,
		RoughSeas,
		Storm,
		Fog,
		Combat
	}
	public sealed class GullMoodState
	{
		private GullMood candidate;

		private double candidateSince;

		private double combatUntil = double.NegativeInfinity;

		public GullMood Current { get; private set; }

		public GullMood Update(double now, bool combat, bool storm, bool fog, bool rough)
		{
			if (combat)
			{
				combatUntil = now + 6.0;
			}
			if (now < combatUntil)
			{
				Current = GullMood.Combat;
				candidate = GullMood.Combat;
				candidateSince = now;
				return Current;
			}
			GullMood gullMood = (storm ? GullMood.Storm : (fog ? GullMood.Fog : (rough ? GullMood.RoughSeas : GullMood.Calm)));
			if (gullMood != candidate)
			{
				candidate = gullMood;
				candidateSince = now;
			}
			if (now - candidateSince >= 3.0)
			{
				Current = candidate;
			}
			return Current;
		}
	}
	public struct GullPose
	{
		public double HeadPitch;

		public double HeadYaw;

		public double HeadRoll;

		public double BodyPitch;

		public double BodyYaw;

		public double BodyRoll;

		public double Crouch;

		public double TailYaw;

		public double TailFan;

		public double Hop;
	}
	public static class GullPerformance
	{
		public static GullPose Blend(GullPose a, GullPose b, double amount)
		{
			double t = Clamp(amount, 0.0, 1.0);
			return new GullPose
			{
				HeadPitch = L(a.HeadPitch, b.HeadPitch),
				HeadYaw = L(a.HeadYaw, b.HeadYaw),
				HeadRoll = L(a.HeadRoll, b.HeadRoll),
				BodyPitch = L(a.BodyPitch, b.BodyPitch),
				BodyYaw = L(a.BodyYaw, b.BodyYaw),
				BodyRoll = L(a.BodyRoll, b.BodyRoll),
				Crouch = L(a.Crouch, b.Crouch),
				TailYaw = L(a.TailYaw, b.TailYaw),
				TailFan = L(a.TailFan, b.TailFan),
				Hop = b.Hop
			};
			double L(double x, double y)
			{
				return x + (y - x) * t;
			}
		}

		public static GullPose Sample(GullMood mood, double elapsed, double threatYaw = 0.0, double windYaw = 0.0, double shipRoll = 0.0, double shipPitch = 0.0)
		{
			GullPose result = default(GullPose);
			double num = Math.Max(0.0, elapsed);
			switch (mood)
			{
			case GullMood.Calm:
				num %= 14.0;
				result.HeadYaw = 28.0 * Window(num, 0.7, 3.0) - 32.0 * Window(num, 5.5, 7.3) + 78.0 * Window(num, 9.0, 11.8);
				result.HeadPitch = 38.0 * (Pulse(num, 3.5, 0.65) + Pulse(num, 4.35, 0.55)) + 24.0 * Window(num, 9.5, 11.5);
				result.HeadRoll = 12.0 * Window(num, 1.5, 2.6) - 8.0 * Window(num, 6.0, 7.0);
				result.BodyPitch = 6.0 * (Pulse(num, 3.5, 0.65) + Pulse(num, 4.35, 0.55));
				result.Crouch = 0.012 * (1.0 + Math.Sin(num * Math.PI * 2.0 / 3.5));
				result.TailYaw = 13.0 * Math.Sin(num * 19.0) * Window(num, 8.0, 8.8);
				result.TailFan = 0.18 * Window(num, 10.0, 11.5);
				break;
			case GullMood.RoughSeas:
				num %= 7.0;
				result.BodyRoll = Clamp((0.0 - shipRoll) * 0.7, -14.0, 14.0) + 4.0 * Math.Sin(num * Math.PI * 2.0 / 7.0);
				result.BodyPitch = 5.0 + Clamp((0.0 - shipPitch) * 0.5, -8.0, 8.0);
				result.Crouch = 0.06 + 0.025 * (1.0 + Math.Sin(num * Math.PI * 4.0 / 7.0));
				result.HeadRoll = (0.0 - result.BodyRoll) * 0.7;
				result.HeadPitch = (0.0 - result.BodyPitch) * 0.5;
				result.HeadYaw = 24.0 * Window(num, 1.2, 2.7) - 24.0 * Window(num, 4.0, 5.7);
				result.TailFan = 0.3;
				result.TailYaw = 0.0 - result.BodyRoll;
				result.Hop = 0.12 * Pulse(num, 5.8, 0.65);
				break;
			case GullMood.Storm:
			{
				num %= 10.0;
				result.BodyYaw = Clamp(windYaw, -65.0, 65.0);
				result.BodyPitch = 13.0 + 3.0 * Math.Sin(num * Math.PI * 4.0 / 10.0);
				result.Crouch = 0.14;
				result.HeadPitch = 24.0 - 32.0 * Window(num, 6.6, 8.1);
				result.HeadYaw = -20.0 * Window(num, 6.7, 7.5) + 20.0 * Window(num, 7.5, 8.3);
				double num2 = Window(num, 2.5, 3.7);
				result.BodyRoll = 7.0 * Math.Sin(num * 32.0) * num2;
				result.HeadRoll = -10.0 * Math.Sin(num * 32.0) * num2;
				result.TailYaw = 18.0 * Math.Sin(num * 26.0) * num2;
				result.TailFan = 0.25 * num2;
				break;
			}
			case GullMood.Fog:
				num %= 12.0;
				result.BodyPitch = -5.0;
				result.Crouch = -0.025;
				result.HeadPitch = -12.0 + 22.0 * Window(num, 8.5, 10.0);
				result.HeadYaw = -55.0 * Window(num, 0.7, 3.2) + 55.0 * Window(num, 4.2, 7.4);
				result.HeadRoll = 18.0 * Window(num, 2.0, 3.0) - 18.0 * Window(num, 5.8, 7.0);
				result.BodyYaw = result.HeadYaw * 0.2;
				result.TailYaw = 12.0 * Window(num, 9.0, 10.8);
				result.Hop = 0.16 * Pulse(num, 10.4, 0.8);
				break;
			case GullMood.Combat:
				num %= 6.0;
				result.BodyYaw = Clamp(threatYaw, -80.0, 80.0) * Window(num, 0.0, 5.8, 0.15);
				result.HeadYaw = Clamp(threatYaw - result.BodyYaw, -55.0, 55.0) + 9.0 * Math.Sin(num * 22.0) * Window(num, 1.5, 2.2);
				result.HeadPitch = -18.0 + 30.0 * Pulse(num, 2.5, 0.35) + 30.0 * Pulse(num, 2.95, 0.35);
				result.BodyPitch = -7.0 + 14.0 * Window(num, 2.4, 3.4);
				result.Crouch = 0.09 * Pulse(num, 0.0, 0.28) + 0.05 * Window(num, 2.3, 3.5);
				result.Hop = 0.4 * Pulse(num, 0.28, 1.05) + 0.25 * Pulse(num, 4.15, 0.8);
				result.TailFan = 0.6;
				result.TailYaw = 15.0 * Math.Sin(num * 16.0) * Window(num, 1.4, 2.2);
				break;
			}
			return result;
		}

		private static double Clamp(double x, double a, double b)
		{
			return Math.Max(a, Math.Min(b, x));
		}

		private static double Smooth(double x)
		{
			x = Clamp(x, 0.0, 1.0);
			return x * x * (3.0 - 2.0 * x);
		}

		private static double Window(double t, double start, double end, double edge = 0.35)
		{
			return Smooth((t - start) / edge) * Smooth((end - t) / edge);
		}

		private static double Pulse(double t, double start, double duration)
		{
			if (t <= start || t >= start + duration)
			{
				return 0.0;
			}
			double num = Math.Sin((t - start) / duration * Math.PI);
			return num * num;
		}
	}
	public sealed class HarborEntry
	{
		public readonly string Prefab;

		public readonly string Name;

		public readonly string Recipe;

		public readonly int Amount;

		public HarborEntry(string prefab, string name, string recipe = "", int amount = 1)
		{
			Prefab = prefab;
			Name = name;
			Recipe = recipe;
			Amount = amount;
		}
	}
	public static class HarborCatalog
	{
		public static readonly IReadOnlyList<HarborEntry> Pieces = Array.AsReadOnly(new HarborEntry[9]
		{
			new HarborEntry("ShipConstruction", "Keel cradle", "FineWood:20,IronNails:10,ElderBark:10"),
			new HarborEntry("ShipConstruction1", "Launching rollers", "FineWood:20,IronNails:10,ElderBark:10"),
			new HarborEntry("ShipConstruction2", "Ship framing gantry", "FineWood:20,IronNails:10,ElderBark:10"),
			new HarborEntry("PierCrane1", "Timber pier crane", "Chain:4,Wood:40,RoundLog:20,IronNails:20"),
			new HarborEntry("PierCrane2", "Braced pier crane", "Iron:4,Wood:40,RoundLog:20,IronNails:20"),
			new HarborEntry("PulleyCobia", "Single block", "Wood:10,LeatherScraps:4"),
			new HarborEntry("PulleyElephantSeal", "Double purchase", "Wood:10,LeatherScraps:4"),
			new HarborEntry("PulleyMarlin", "Heavy purchase", "Wood:10,LeatherScraps:4"),
			new HarborEntry("FishingDock", "Pelican fishing station", "BronzeNails:20,Wood:20,RoundLog:4,Coins:250")
		});

		public static readonly IReadOnlyList<HarborEntry> Items = Array.AsReadOnly(new HarborEntry[8]
		{
			new HarborEntry("ResinWood", "Resin wood", "", 10),
			new HarborEntry("CaulkedWood", "Caulked wood", "", 10),
			new HarborEntry("ClothShip", "Sail canvas"),
			new HarborEntry("ShipRope", "Marine rope"),
			new HarborEntry("WindBelt", "Wind belt", "Ruby:1,AmberPearl:4,DeerHide:5,Blueberries:2"),
			new HarborEntry("FishExtract", "Fish extract"),
			new HarborEntry("FishExtract2", "Fish extract"),
			new HarborEntry("DriedFishBasket", "Dried fish basket")
		});

		public const string TableRecipe = "Bronze:1,Wood:40,BronzeNails:20";
	}
	public static class HarborMotion
	{
		public const double Duration = 12.0;

		public static double Elapsed(long started, long now)
		{
			if (started > 0 && now > started)
			{
				return Math.Min(12.0, ((double)now - (double)started) / 10000000.0);
			}
			return 0.0;
		}

		public static float Travel(double seconds)
		{
			if (double.IsNaN(seconds) || seconds <= 0.0 || seconds >= 10.0)
			{
				return 0f;
			}
			double num = ((seconds < 4.0) ? (seconds / 4.0) : ((seconds <= 6.0) ? 1.0 : ((10.0 - seconds) / 4.0)));
			return (float)(num * num * (3.0 - 2.0 * num));
		}

		public static bool Busy(long started, long now)
		{
			if (started > 0 && now >= started)
			{
				return ((double)now - (double)started) / 10000000.0 < 12.0;
			}
			return false;
		}
	}
	public static class HelmMath
	{
		public static bool NeedsSlowTurn(double headingError, bool alreadySlowing)
		{
			return Math.Abs(headingError) > (double)(alreadySlowing ? 55 : 80);
		}

		public static double StoppingDistance(double speed, double deceleration, double reactionSeconds, double margin)
		{
			if (deceleration <= 0.0)
			{
				throw new ArgumentOutOfRangeException("deceleration");
			}
			speed = Math.Abs(speed);
			return speed * reactionSeconds + speed * speed / (2.0 * deceleration) + margin;
		}

		public static double Rudder(double headingError, bool reverse)
		{
			return Math.Max(-0.65, Math.Min(0.65, headingError / 55.0)) * (double)((!reverse) ? 1 : (-1));
		}
	}
	public readonly struct IslandCell : IEquatable<IslandCell>
	{
		public readonly int X;

		public readonly int Z;

		public IslandCell(int x, int z)
		{
			X = x;
			Z = z;
		}

		public bool Equals(IslandCell other)
		{
			if (X == other.X)
			{
				return Z == other.Z;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is IslandCell other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (X * 397) ^ Z;
		}
	}
	public enum SurveyState
	{
		SearchingShore,
		Surveying,
		Complete,
		Failed
	}
	public sealed class IslandSurvey
	{
		public const int CellSize = 8;

		public const int WorldRadius = 9800;

		public const int MaximumCells = 1000000;

		private readonly Func<float, float, float> height;

		private readonly float sea;

		private readonly int maximum;

		private readonly Queue<IslandCell> seeds;

		private readonly Queue<IslandCell> frontier = new Queue<IslandCell>();

		private readonly HashSet<IslandCell> examined = new HashSet<IslandCell>();

		private readonly HashSet<IslandCell> land = new HashSet<IslandCell>();

		private IslandCell? expanding;

		private int side;

		private static readonly int[] Dx = new int[4] { -1, 1, 0, 0 };

		private static readonly int[] Dz = new int[4] { 0, 0, -1, 1 };

		public SurveyState State { get; private set; }

		public string Error { get; private set; } = "";

		public IReadOnlyCollection<IslandCell> Cells => land;

		public IslandSurvey(float originX, float originZ, float sea, Func<float, float, float> height, int maximum = 1000000)
		{
			if (!Finite(originX) || !Finite(originZ) || !Finite(sea) || maximum < 1)
			{
				throw new ArgumentException("Invalid survey origin or budget.");
			}
			this.height = height;
			this.sea = sea;
			this.maximum = maximum;
			int num = (int)Math.Round(originX / 8f);
			int num2 = (int)Math.Round(originZ / 8f);
			List<IslandCell> list = new List<IslandCell>();
			for (int i = -16; i <= 16; i++)
			{
				for (int j = -16; j <= 16; j++)
				{
					if (i * i + j * j <= 256)
					{
						list.Add(new IslandCell(num + i, num2 + j));
					}
				}
			}
			seeds = new Queue<IslandCell>(list.OrderBy((IslandCell c) => Distance(c, originX, originZ)));
		}

		private static double Distance(IslandCell c, float x, float z)
		{
			return Math.Pow((float)(c.X * 8) - x, 2.0) + Math.Pow((float)(c.Z * 8) - z, 2.0);
		}

		private bool IsLand(float x, float z)
		{
			float num = height(x, z);
			if (!Finite(num))
			{
				throw new InvalidOperationException("Terrain height is unavailable.");
			}
			return num > sea + 0.05f;
		}

		private static bool Finite(float n)
		{
			if (!float.IsNaN(n))
			{
				return !float.IsInfinity(n);
			}
			return false;
		}

		private bool Inside(IslandCell c)
		{
			return (double)c.X * (double)c.X + (double)c.Z * (double)c.Z < 1500625.0;
		}

		private void Fail(string reason)
		{
			Error = reason;
			State = SurveyState.Failed;
			frontier.Clear();
		}

		public void Step(int budget)
		{
			if (budget < 1)
			{
				return;
			}
			try
			{
				while (budget-- > 0 && State != SurveyState.Complete && State != SurveyState.Failed)
				{
					if (State == SurveyState.SearchingShore)
					{
						if (seeds.Count == 0)
						{
							Fail("No island shoreline within 128 m of this dock.");
							break;
						}
						IslandCell islandCell = seeds.Dequeue();
						if (Inside(islandCell) && IsLand(islandCell.X * 8, islandCell.Z * 8))
						{
							land.Add(islandCell);
							examined.Add(islandCell);
							frontier.Enqueue(islandCell);
							State = SurveyState.Surveying;
						}
						continue;
					}
					if (!expanding.HasValue)
					{
						if (frontier.Count == 0)
						{
							State = SurveyState.Complete;
							break;
						}
						expanding = frontier.Dequeue();
						side = 0;
					}
					IslandCell value = expanding.Value;
					IslandCell islandCell2 = new IslandCell(value.X + Dx[side], value.Z + Dz[side]);
					if (++side == 4)
					{
						expanding = null;
					}
					if (land.Contains(islandCell2))
					{
						continue;
					}
					if (!Inside(islandCell2))
					{
						Fail("This landmass reaches the edge of the surveyable world.");
						break;
					}
					if (examined.Contains(islandCell2))
					{
						continue;
					}
					if (!IsLand(islandCell2.X * 8, islandCell2.Z * 8))
					{
						examined.Add(islandCell2);
						continue;
					}
					bool flag = true;
					for (int i = 1; i <= 3; i++)
					{
						if (!IsLand(((float)value.X + (float)((islandCell2.X - value.X) * i) * 0.25f) * 8f, ((float)value.Z + (float)((islandCell2.Z - value.Z) * i) * 0.25f) * 8f))
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						if (land.Count >= maximum)
						{
							Fail("This landmass is too large to survey in one report.");
							break;
						}
						land.Add(islandCell2);
						frontier.Enqueue(islandCell2);
					}
				}
			}
			catch (Exception ex)
			{
				Fail("Survey paused: " + ex.Message);
			}
		}

		public bool Contains(float x, float z)
		{
			return land.Contains(new IslandCell((int)Math.Round(x / 8f), (int)Math.Round(z / 8f)));
		}
	}
	public static class ScoutingPoi
	{
		public static string Label(string prefab)
		{
			string text = (prefab ?? "").ToLowerInvariant();
			if (text.Contains("hildir") || text.Contains("boss") || text.Contains("altar") || text.Contains("starttemple"))
			{
				return "";
			}
			if (text.Contains("sunkencrypt"))
			{
				return "Sunken crypt";
			}
			if (text.Contains("crypt"))
			{
				return "Burial chamber";
			}
			if (text.Contains("trollcave"))
			{
				return "Troll cave";
			}
			if (text.Contains("mountaincave"))
			{
				return "Frost cave";
			}
			if (text.Contains("dvergrtownentrance") || text.Contains("infestedmine"))
			{
				return "Infested mine";
			}
			if (text.Contains("goblincamp"))
			{
				return "Fuling village";
			}
			if (text.Contains("draugrvillage"))
			{
				return "Draugr village";
			}
			if (text.Contains("charredfortress") || text.Contains("fortress"))
			{
				return "Fortress";
			}
			if (text.Contains("dvergr") && (text.Contains("tower") || text.Contains("harbour") || text.Contains("town")))
			{
				return "Dvergr outpost";
			}
			if (text.Contains("ruin") || text.Contains("stonetower") || text.Contains("woodhouse"))
			{
				return "Ruins";
			}
			return "";
		}
	}
	public sealed class LocalDetour
	{
		public readonly int RejoinIndex;

		public readonly RouteSearch Search;

		private LocalDetour(int index, RouteSearch search)
		{
			RejoinIndex = index;
			Search = search;
		}

		public static LocalDetour? Create(Point start, IReadOnlyList<Point> route, int waypoint, Func<Point, Point, bool> clear)
		{
			if (waypoint < 0 || waypoint >= route.Count)
			{
				return null;
			}
			int i;
			for (i = waypoint; i < route.Count - 1 && start.Distance(route[i]) < 48.0; i++)
			{
			}
			if (start.Distance(route[i]) > 160.0)
			{
				return null;
			}
			return new LocalDetour(i, new RouteSearch(start, route[i], (Point a, Point b) => start.Distance(a) <= 192.0 && start.Distance(b) <= 192.0 && clear(a, b), 6.0, 4096));
		}

		public List<Point> Splice(IReadOnlyList<Point> previous)
		{
			if (Search.State != SearchState.Found)
			{
				throw new InvalidOperationException("No clear local detour.");
			}
			List<Point> list = new List<Point>(Search.Route);
			for (int i = RejoinIndex + 1; i < previous.Count; i++)
			{
				list.Add(previous[i]);
			}
			return list;
		}
	}
	public readonly struct PaddleStroke
	{
		public readonly double X;

		public readonly double Y;

		public readonly double Z;

		public readonly double AxisX;

		public readonly double AxisY;

		public readonly double AxisZ;

		public readonly double Twist;

		public readonly double Lean;

		private PaddleStroke(double x, double y, double z, double ax, double ay, double az, double twist, double lean)
		{
			X = x;
			Y = y;
			Z = z;
			double num = Math.Sqrt(ax * ax + ay * ay + az * az);
			AxisX = ax / num;
			AxisY = ay / num;
			AxisZ = az / num;
			Twist = twist;
			Lean = lean;
		}

		public static PaddleStroke Sample(double seconds, bool doubleBlade, bool reverse)
		{
			if (double.IsNaN(seconds) || double.IsInfinity(seconds))
			{
				seconds = 0.0;
			}
			double num = seconds % (doubleBlade ? 1.8 : 1.65) / (doubleBlade ? 1.8 : 1.65) * Math.PI * 2.0;
			if (reverse)
			{
				num = 0.0 - num;
			}
			double num2 = Math.Sin(num);
			double num3 = Math.Cos(num);
			if (doubleBlade)
			{
				return new PaddleStroke(0.06 * num2, 0.805 + 0.055 * Math.Cos(2.0 * num), 0.3 + 0.055 * Math.Cos(2.0 * num), 1.0, -0.76 * num2, 0.46 * num3, 12.0 * num3, 3.0 + 5.0 * Math.Cos(2.0 * num));
			}
			double num4 = (1.0 - num3) * 0.5;
			return new PaddleStroke(-0.22, 0.67 + 0.35 * num4, 0.2 + 0.15 * num2, 0.64, 0.95 - 0.35 * num4, 0.08 * num2, 9.0 * num2, 6.0 * num3);
		}
	}
	public enum PuffinCrewJob
	{
		Hammer,
		Shave,
		Haul,
		Timber,
		Supplies
	}
	public static class PuffinCrewPlan
	{
		public const int Count = 6;

		public const int UnobservedArrivalSeconds = 20;

		public static bool WaitingForCrew(long called, long begun)
		{
			if (called > 0)
			{
				return begun == 0;
			}
			return false;
		}

		public static bool CanBeginConstruction(long called, long begun, long now, bool observed, bool assembled)
		{
			if (WaitingForCrew(called, begun) && now >= called)
			{
				if (!observed)
				{
					return (double)now - (double)called >= 200000000.0;
				}
				return assembled;
			}
			return false;
		}

		public static double ConstructionElapsed(long called, long begun, long commissioned, long now, double duration)
		{
			if (!WaitingForCrew(called, begun))
			{
				return ShipConstruction.Elapsed((called > 0) ? begun : commissioned, now, duration);
			}
			return 0.0;
		}

		public static PuffinCrewJob Job(int index)
		{
			switch (index)
			{
			default:
				return PuffinCrewJob.Supplies;
			case 4:
				return PuffinCrewJob.Timber;
			case 2:
				return PuffinCrewJob.Haul;
			case 1:
				return PuffinCrewJob.Shave;
			case 0:
			case 3:
				return PuffinCrewJob.Hammer;
			}
		}

		public static float Variation(long seed, int index)
		{
			int num = (int)seed ^ (int)(seed >> 32) ^ ((index + 1) * -1640531535);
			int num2 = (num ^ (num >>> 16)) * -2048144777;
			return (float)(uint)((num2 ^ (num2 >>> 13)) & 0xFFFFFF) / 16777216f;
		}

		public static float ArrivalDelay(long seed, int index)
		{
			return (float)index * 0.65f + Variation(seed, index) * 0.8f;
		}

		public static PuffinPoint Departure(long seed, int index)
		{
			double num = ((double)index + ((double)Variation(seed, index) - 0.5) * 0.55) * Math.PI * 2.0 / 6.0 + (double)Variation(seed, 17) * Math.PI * 2.0;
			return new PuffinPoint((float)Math.Cos(num), 0.32f + Variation(seed, index + 31) * 0.2f, (float)Math.Sin(num));
		}

		public static PuffinWorkPose Work(PuffinCrewJob job, double seconds)
		{
			if (job == PuffinCrewJob.Hammer)
			{
				PuffinWorkPose result = PuffinPerformance.Station(1, seconds);
				result.Pitch *= 0.65f;
				result.BodyPitch *= 0.25f;
				result.Crouch *= 0.2f;
				return result;
			}
			float num = (float)(Math.Sin(seconds * Math.PI * 2.0 / ((job == PuffinCrewJob.Shave) ? 2.2 : 2.7)) * 0.5 + 0.5);
			return job switch
			{
				PuffinCrewJob.Shave => new PuffinWorkPose
				{
					Tool = 5,
					Pitch = 42f - num * 18f,
					BodyPitch = 5f + num * 10f,
					Wing = 18f
				}, 
				PuffinCrewJob.Haul => new PuffinWorkPose
				{
					Pitch = 10f - num * 17f,
					BodyPitch = (0f - num) * 17f,
					Wing = 25f,
					Crouch = num * 0.045f
				}, 
				_ => new PuffinWorkPose
				{
					Tool = ((job == PuffinCrewJob.Timber) ? 6 : 7),
					Pitch = -8f,
					BodyPitch = 5f,
					Wing = 22f
				}, 
			};
		}
	}
	public static class PuffinCrewSpace
	{
		public static bool Clear(PuffinPoint a, PuffinPoint b, PuffinPoint min, PuffinPoint max, float padding = 0.2f)
		{
			float enter = 0f;
			float exit = 1f;
			if (Slab(a.X, b.X, min.X, max.X) && Slab(a.Y, b.Y, min.Y, max.Y))
			{
				return !Slab(a.Z, b.Z, min.Z, max.Z);
			}
			return true;
			bool Slab(float p, float q, float lo, float hi)
			{
				lo -= padding;
				hi += padding;
				float num = q - p;
				if (Math.Abs(num) < 1E-06f)
				{
					if (p >= lo)
					{
						return p <= hi;
					}
					return false;
				}
				float num2 = (lo - p) / num;
				float num3 = (hi - p) / num;
				if (num2 > num3)
				{
					float num4 = num2;
					num2 = num3;
					num3 = num4;
				}
				enter = Math.Max(enter, num2);
				exit = Math.Min(exit, num3);
				return enter <= exit;
			}
		}
	}
	public struct PuffinPoint
	{
		public float X;

		public float Y;

		public float Z;

		public PuffinPoint(float x, float y, float z)
		{
			X = x;
			Y = y;
			Z = z;
		}

		public static float Distance(PuffinPoint a, PuffinPoint b)
		{
			float num = a.X - b.X;
			float num2 = a.Y - b.Y;
			float num3 = a.Z - b.Z;
			return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3);
		}
	}
	public sealed class PuffinFlightPath
	{
		public enum Result
		{
			Searching,
			Found,
			Unreachable
		}

		private sealed class Node
		{
			public int X;

			public int Y;

			public int Z;

			public float Cost = float.MaxValue;

			public Node Parent;

			public bool Closed;
		}

		public readonly List<PuffinPoint> Points = new List<PuffinPoint>();

		private readonly float Cell;

		private const int Limit = 4096;

		private readonly PuffinPoint start;

		private readonly PuffinPoint goal;

		private readonly Func<PuffinPoint, PuffinPoint, bool> clear;

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

		private readonly List<(Node node, float score)> heap = new List<(Node, float)>();

		public Result State { get; private set; }

		public int Expanded { get; private set; }

		public PuffinFlightPath(PuffinPoint start, PuffinPoint goal, Func<PuffinPoint, PuffinPoint, bool> clear, float resolution = 0.75f)
		{
			this.start = start;
			this.goal = goal;
			this.clear = clear;
			Cell = Math.Max(0.35f, Math.Min(1.5f, resolution));
			if (!Finite(start) || !Finite(goal) || PuffinPoint.Distance(start, goal) > 210f || !clear(start, start) || !clear(goal, goal))
			{
				State = Result.Unreachable;
			}
			else if (clear(start, goal))
			{
				Points.Add(start);
				Points.Add(goal);
				State = Result.Found;
			}
			else
			{
				Node node = new Node
				{
					Cost = 0f
				};
				nodes.Add((0, 0, 0), node);
				Push(node, PuffinPoint.Distance(start, goal));
			}
		}

		private static bool Finite(PuffinPoint p)
		{
			if (!float.IsNaN(p.X + p.Y + p.Z))
			{
				return !float.IsInfinity(p.X + p.Y + p.Z);
			}
			return false;
		}

		private PuffinPoint Position(Node n)
		{
			return new PuffinPoint(start.X + (float)n.X * Cell, start.Y + (float)n.Y * Cell, start.Z + (float)n.Z * Cell);
		}

		public void Step(int budget)
		{
			if (State != Result.Searching)
			{
				return;
			}
			for (int i = 0; i < budget; i++)
			{
				if (heap.Count <= 0)
				{
					break;
				}
				Node node = Pop();
				if (node.Closed)
				{
					continue;
				}
				node.Closed = true;
				Expanded++;
				PuffinPoint puffinPoint = Position(node);
				if (PuffinPoint.Distance(puffinPoint, goal) < Cell * 2.5f && clear(puffinPoint, goal))
				{
					Finish(node);
					return;
				}
				if (Expanded >= 4096)
				{
					State = Result.Unreachable;
					return;
				}
				for (int j = 0; j < 3; j++)
				{
					for (int k = -1; k <= 1; k += 2)
					{
						int num = node.X + ((j == 0) ? k : 0);
						int num2 = node.Y + ((j == 1) ? k : 0);
						int num3 = node.Z + ((j == 2) ? k : 0);
						PuffinPoint puffinPoint2 = new PuffinPoint(start.X + (float)num * Cell, start.Y + (float)num2 * Cell, start.Z + (float)num3 * Cell);
						if (puffinPoint2.X < Math.Min(start.X, goal.X) - 12f || puffinPoint2.X > Math.Max(start.X, goal.X) + 12f || puffinPoint2.Z < Math.Min(start.Z, goal.Z) - 12f || puffinPoint2.Z > Math.Max(start.Z, goal.Z) + 12f || puffinPoint2.Y < Math.Min(start.Y, goal.Y) - 6f || puffinPoint2.Y > Math.Max(start.Y, goal.Y) + 9f)
						{
							continue;
						}
						(int, int, int) key = (num, num2, num3);
						if (nodes.TryGetValue(key, out var value) && value.Closed)
						{
							continue;
						}
						float num4 = node.Cost + Cell * ((j == 1) ? 1.2f : 1f);
						if ((value != null && num4 >= value.Cost) || !clear(puffinPoint, puffinPoint2))
						{
							continue;
						}
						if (value == null)
						{
							if (nodes.Count >= 8192)
							{
								State = Result.Unreachable;
								return;
							}
							value = new Node
							{
								X = num,
								Y = num2,
								Z = num3
							};
							nodes.Add(key, value);
						}
						value.Cost = num4;
						value.Parent = node;
						Push(value, num4 + PuffinPoint.Distance(puffinPoint2, goal));
					}
				}
			}
			if (heap.Count == 0)
			{
				State = Result.Unreachable;
			}
		}

		private void Finish(Node n)
		{
			List<PuffinPoint> list = new List<PuffinPoint> { goal };
			while (n != null)
			{
				list.Add(Position(n));
				n = n.Parent;
			}
			list.Reverse();
			Points.Add(list[0]);
			int num = 0;
			while (num < list.Count - 1)
			{
				int num2 = Math.Min(num + 8, list.Count - 1);
				while (num2 > num + 1 && !clear(list[num], list[num2]))
				{
					num2--;
				}
				Points.Add(list[num2]);
				num = num2;
			}
			State = Result.Found;
		}

		private void Push(Node n, float score)
		{
			heap.Add((n, score));
			int num = heap.Count - 1;
			while (num > 0)
			{
				int num2 = (num - 1) / 2;
				if (heap[num2].score <= score)
				{
					break;
				}
				heap[num] = heap[num2];
				num = num2;
			}
			heap[num] = (n, score);
		}

		private Node Pop()
		{
			Node item = heap[0].node;
			(Node, float) value = heap[heap.Count - 1];
			heap.RemoveAt(heap.Count - 1);
			if (heap.Count == 0)
			{
				return item;
			}
			int num = 0;
			while (num * 2 + 1 < heap.Count)
			{
				int num2 = num * 2 + 1;
				if (num2 + 1 < heap.Count && heap[num2 + 1].score < heap[num2].score)
				{
					num2++;
				}
				if (heap[num2].score >= value.Item2)
				{
					break;
				}
				heap[num] = heap[num2];
				num = num2;
			}
			heap[num] = value;
			return item;
		}
	}
	public sealed class PuffinGroundPath
	{
		private sealed class Node
		{
			public PuffinPoint Point;

			public float Cost;

			public float Score;

			public Node Parent;

			public bool Closed;
		}

		public readonly List<PuffinPoint> Points = new List<PuffinPoint>();

		private readonly PuffinPoint start;

		private readonly PuffinPoint goal;

		private readonly Func<PuffinPoint, PuffinPoint?> floor;

		private readonly Func<PuffinPoint, PuffinPoint, bool> clear;

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

		private readonly List<Node> open = new List<Node>();

		private const float Cell = 0.6f;

		public PuffinFlightPath.Result State { get; private set; }

		public int Expanded { get; private set; }

		public PuffinGroundPath(PuffinPoint start, PuffinPoint goal, Func<PuffinPoint, PuffinPoint?> floor, Func<PuffinPoint, PuffinPoint, bool> clear)
		{
			this.start = start;
			this.goal = goal;
			this.floor = floor;
			this.clear = clear;
			if (float.IsNaN(start.X + start.Y + start.Z + goal.X + goal.Y + goal.Z) || float.IsInfinity(start.X + start.Y + start.Z + goal.X + goal.Y + goal.Z) || PuffinPoint.Distance(start, goal) > 210f || !clear(start, start) || !clear(goal, goal))
			{
				State = PuffinFlightPath.Result.Unreachable;
			}
			else if (clear(start, goal))
			{
				Points.Add(start);
				Points.Add(goal);
				State = PuffinFlightPath.Result.Found;
			}
			else
			{
				Node node = new Node
				{
					Point = start,
					Score = PuffinPoint.Distance(start, goal)
				};
				nodes[Key(start)] = node;
				open.Add(node);
			}
		}

		private (int, int, int) Key(PuffinPoint p)
		{
			return ((int)Math.Round((p.X - start.X) / 0.6f), (int)Math.Round(p.Y / 0.25f), (int)Math.Round((p.Z - start.Z) / 0.6f));
		}

		public void Step(int budget)
		{
			if (State != PuffinFlightPath.Result.Searching)
			{
				return;
			}
			for (int i = 0; i < budget; i++)
			{
				if (open.Count <= 0)
				{
					break;
				}
				int index = 0;
				for (int j = 1; j < open.Count; j++)
				{
					if (open[j].Score < open[index].Score)
					{
						index = j;
					}
				}
				Node node = open[index];
				open.RemoveAt(index);
				node.Closed = true;
				Expanded++;
				if (PuffinPoint.Distance(node.Point, goal) < 1.2f && clear(node.Point, goal))
				{
					Points.Add(goal);
					for (Node node2 = node; node2 != null; node2 = node2.Parent)
					{
						Points.Add(node2.Point);
					}
					Points.Reverse();
					State = PuffinFlightPath.Result.Found;
					return;
				}
				if (Expanded >= 1024)
				{
					State = PuffinFlightPath.Result.Unreachable;
					return;
				}
				for (int k = -1; k <= 1; k++)
				{
					for (int l = -1; l <= 1; l++)
					{
						if (k == 0 && l == 0)
						{
							continue;
						}
						PuffinPoint arg = new PuffinPoint(node.Point.X + (float)k * 0.6f, node.Point.Y, node.Point.Z + (float)l * 0.6f);
						if (arg.X < Math.Min(start.X, goal.X) - 9f || arg.X > Math.Max(start.X, goal.X) + 9f || arg.Z < Math.Min(start.Z, goal.Z) - 9f || arg.Z > Math.Max(start.Z, goal.Z) + 9f)
						{
							continue;
						}
						PuffinPoint? puffinPoint = floor(arg);
						if (!puffinPoint.HasValue)
						{
							continue;
						}
						PuffinPoint value = puffinPoint.Value;
						if (!clear(node.Point, value))
						{
							continue;
						}
						(int, int, int) key = Key(value);
						nodes.TryGetValue(key, out var value2);
						float num = node.Cost + PuffinPoint.Distance(node.Point, value);
						if (value2 == null || (!value2.Closed && !(num >= value2.Cost)))
						{
							if (value2 == null)
							{
								value2 = new Node
								{
									Point = value
								};
								nodes.Add(key, value2);
								open.Add(value2);
							}
							value2.Cost = num;
							value2.Score = num + PuffinPoint.Distance(value, goal);
							value2.Parent = node;
						}
					}
				}
			}
			if (open.Count == 0)
			{
				State = PuffinFlightPath.Result.Unreachable;
			}
		}
	}
	public struct PuffinWorkPose
	{
		public int Tool;

		public float X;

		public float Z;

		public float Hop;

		public float Yaw;

		public float Pitch;

		public float HeadYaw;

		public float Roll;

		public float BodyPitch;

		public float Crouch;

		public float Wing;
	}
	public static class PuffinPerformance
	{
		public static PuffinWorkPose Sample(bool hasSail, double elapsed)
		{
			double num = Math.Max(0.0, elapsed) % 36.0;
			int num2 = (int)(num / 12.0);
			num %= 12.0;
			int num3 = ((num2 == 0) ? 1 : ((num2 == 1 || !hasSail) ? 2 : 3));
			double num4 = Smooth((num - 0.45) / 0.65) * (1.0 - Smooth((num - 9.85) / 0.65));
			float num5 = (float)(Arc(num, 0.45, 0.65) + Arc(num, 9.85, 0.65)) * 0.22f;
			PuffinWorkPose result = new PuffinWorkPose
			{
				X = (float)num4 * num3 switch
				{
					2 => -0.48f, 
					1 => -0.22f, 
					_ => -0.12f, 
				},
				Z = (float)num4 * ((num3 == 2) ? 0.09f : (-0.03f)),
				Hop = num5,
				Yaw = (float)num4 * (float)(num3 switch
				{
					2 => 18, 
					1 => -24, 
					_ => -12, 
				}),
				Crouch = (float)(Pulse(num, 0.05, 0.4) * 0.075 + Pulse(num, 1.1, 0.4) * 0.05 + Pulse(num, 9.45, 0.4) * 0.075 + Pulse(num, 10.5, 0.4) * 0.05),
				Wing = num5 * 125f,
				Pitch = (0f - num5) * 35f,
				Roll = (float)(Math.Sin(num * Math.PI / 6.0) * 3.0)
			};
			if (num >= 1.5 && num < 9.4)
			{
				result.Tool = num3;
				double num6 = Smooth((num - 1.5) / 0.35) * Smooth((9.4 - num) / 0.35);
				switch (num3)
				{
				case 1:
				{
					double t2 = (num - 1.5) % 1.45;
					double num9 = Pulse(t2, 0.05, 0.52);
					double num10 = Pulse(t2, 0.62, 0.24);
					double num11 = Pulse(t2, 0.86, 0.35);
					result.Pitch += (float)((-24.0 * num9 + 43.0 * num10 - 9.0 * num11) * num6);
					result.BodyPitch = (float)((8.0 - 9.0 * num9 + 44.0 * num10) * num6);
					result.Crouch += (float)(0.065 * num10 * num6);
					result.Wing += (float)(12.0 * num6);
					break;
				}
				case 2:
				{
					double t = (num - 1.5) % 2.1;
					double num8 = Pulse(t, 0.25, 0.26) + Pulse(t, 0.72, 0.24) + Pulse(t, 1.1, 0.22);
					result.Pitch += (float)((24.0 + 16.0 * num8) * num6);
					result.BodyPitch = (float)((38.0 + 8.0 * num8) * num6);
					result.HeadYaw = (float)(8.0 * Math.Sin(num * 1.8) * num6);
					result.Crouch += (float)(0.025 * num8 * num6);
					result.Wing += (float)(10.0 * num6);
					break;
				}
				default:
				{
					double num7 = (num - 1.5) % 1.8 / 1.8;
					result.Pitch += (float)((23.0 + 12.0 * Math.Sin(num7 * Math.PI * 2.0)) * num6);
					result.HeadYaw = (float)(22.0 * Math.Sin(num7 * Math.PI * 2.0) * num6);
					result.BodyPitch = (float)(10.0 * num6);
					result.Wing += (float)((20.0 + 6.0 * Math.Cos(num7 * Math.PI * 2.0)) * num6);
					break;
				}
				}
			}
			else
			{
				result.HeadYaw = (float)(25.0 * Pulse(num, 11.0, 0.8));
			}
			return result;
		}

		public static PuffinWorkPose Station(int tool, double seconds)
		{
			double num = Smooth(seconds / 0.35);
			double num2 = Math.Max(0.0, seconds) % tool switch
			{
				3 => 1.8, 
				2 => 2.1, 
				1 => 1.45, 
				_ => 2.6, 
			};
			PuffinWorkPose result = new PuffinWorkPose
			{
				Tool = tool
			};
			switch (tool)
			{
			case 1:
			{
				double num4 = Pulse(num2, 0.05, 0.52);
				double num5 = Pulse(num2, 0.62, 0.24);
				double num6 = Pulse(num2, 0.86, 0.35);
				result.Pitch = (float)(-24.0 * num4 + 43.0 * num5 - 9.0 * num6);
				result.BodyPitch = (float)(8.0 - 9.0 * num4 + 44.0 * num5);
				result.Crouch = (float)(0.065 * num5);
				result.Wing = 12f;
				break;
			}
			case 2:
			{
				double num3 = Pulse(num2, 0.62, 0.24);
				result.Pitch = (float)(24.0 + 16.0 * num3);
				result.BodyPitch = (float)(38.0 + 8.0 * num3);
				result.HeadYaw = (float)Math.Sin(seconds * 1.8) * 8f;
				result.Wing = 10f;
				break;
			}
			case 3:
				result.Pitch = 23f + 12f * (float)Math.Sin(num2 * Math.PI * 2.0 / 1.8);
				result.HeadYaw = 22f * (float)Math.Sin(num2 * Math.PI * 2.0 / 1.8);
				result.BodyPitch = 10f;
				result.Wing = 20f + 6f * (float)Math.Cos(num2 * Math.PI * 2.0 / 1.8);
				break;
			default:
				result.Pitch = 26f + (float)Math.Sin(seconds * 2.4) * 9f;
				result.HeadYaw = (float)Math.Sin(seconds * 2.4) * 22f;
				result.BodyPitch = 16f;
				result.Wing = 12f;
				break;
			}
			result.Pitch *= (float)num;
			result.BodyPitch *= (float)num;
			result.Crouch *= (float)num;
			result.HeadYaw *= (float)num;
			result.Wing *= (float)num;
			return result;
		}

		public static bool Strike(int tool, double before, double after)
		{
			double num = tool switch
			{
				3 => 1.8, 
				2 => 2.1, 
				1 => 1.45, 
				_ => 2.6, 
			};
			if (Math.Floor((before - 0.74) / num) != Math.Floor((after - 0.74) / num))
			{
				return after >= 0.74;
			}
			return false;
		}

		private static double Smooth(double t)
		{
			t = Math.Max(0.0, Math.Min(1.0, t));
			return t * t * (3.0 - 2.0 * t);
		}

		private static double Pulse(double t, double start, double duration)
		{
			if (t <= start || t >= start + duration)
			{
				return 0.0;
			}
			double num = Math.Sin((t - start) / duration * Math.PI);
			return num * num;
		}

		private static double Arc(double t, double start, double duration)
		{
			if (t <= start || t >= start + duration)
			{
				return 0.0;
			}
			double num = (t - start) / duration;
			return 4.0 * num * (1.0 - num);
		}
	}
	public static class RoutePreference
	{
		public static bool Shortcut(IReadOnlyList<Point> route, int from, int to, Func<Point, Point, double> weight)
		{
			double num = 0.0;
			for (int i = from; i < to; i++)
			{
				num += route[i].Distance(route[i + 1]) * weight(route[i], route[i + 1]);
			}
			return route[from].Distance(route[to]) * weight(route[from], route[to]) <= num + 1E-06;
		}
	}
	public readonly struct Point
	{
		public readonly double X;

		public readonly double Y;

		public Point(double x, double y)
		{
			X = x;
			Y = y;
		}

		public double Distance(Point b)
		{
			return Math.Sqrt((X - b.X) * (X - b.X) + (Y - b.Y) * (Y - b.Y));
		}
	}
	public enum SearchState
	{
		Searching,
		Found,
		NoRoute,
		BudgetExceeded
	}
	public sealed class RouteSearch
	{
		private sealed class Node
		{
			public int X;

			public int Y;

			public double Cost;

			public Node? Parent;

			public bool Closed;
		}

		private readonly struct Entry
		{
			public readonly Node Node;

			public readonly double Priority;

			public readonly double Cost;

			public Entry(Node node, double priority)
			{
				Node = node;
				Priority = priority;
				Cost = node.Cost;
			}
		}

		private readonly List<Entry> heap = new List<Entry>();

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

		private readonly Point start;

		private readonly Point goal;

		private readonly double spacing;

		private readonly int maxExpanded;

		private readonly Func<Point, Point, bool> clear;

		private readonly Func<Point, Point, double>? weight;

		public SearchState State { get; private set; }

		public List<Point> Route { get; } = new List<Point>();

		public int Expanded { get; private set; }

		public RouteSearch(Point start, Point goal, Func<Point, Point, bool> clear, double spacing = 24.0, int maxExpanded = 20000, Func<Point, Point, double>? weight = null)
		{
			if (spacing <= 0.0 || maxExpanded <= 0)
			{
				throw new ArgumentOutOfRangeException();
			}
			this.start = start;
			this.goal = goal;
			this.clear = clear;
			this.weight = weight;
			this.spacing = spacing;
			this.maxExpanded = maxExpanded;
			Node node = new Node();
			nodes[(0, 0)] = node;
			Push(node, start.Distance(goal));
		}

		public void Step(int budget = 8)
		{
			while (State == SearchState.Searching && budget-- > 0)
			{
				if (heap.Count == 0)
				{
					State = SearchState.NoRoute;
					break;
				}
				Entry entry = Pop();
				Node node = entry.Node;
				if (node.Closed || entry.Cost != node.Cost)
				{
					continue;
				}
				if (++Expanded > maxExpanded)
				{
					State = SearchState.BudgetExceeded;
					break;
				}
				node.Closed = true;
				Point arg = Position(node);
				if (arg.Distance(goal) <= spacing * 1.5 && clear(arg, goal))
				{
					Route.Add(goal);
					for (Node node2 = node; node2 != null; node2 = node2.Parent)
					{
						Route.Add(Position(node2));
					}
					Route.Reverse();
					State = SearchState.Found;
					break;
				}
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						if (i == 0 && j == 0)
						{
							continue;
						}
						(int, int) key = (node.X + i, node.Y + j);
						if (nodes.TryGetValue(key, out Node value) && value.Closed)
						{
							continue;
						}
						Point point = new Point(start.X + (double)key.Item1 * spacing, start.Y + (double)key.Item2 * spacing);
						if (!clear(arg, point))
						{
							continue;
						}
						double num = weight?.Invoke(arg, point) ?? 1.0;
						if (double.IsNaN(num) || double.IsInfinity(num) || num < 1.0)
						{
							num = 1.0;
						}
						double num2 = node.Cost + arg.Distance(point) * num;
						if (value == null || !(num2 >= value.Cost))
						{
							if (value == null)
							{
								value = new Node
								{
									X = key.Item1,
									Y = key.Item2
								};
								nodes[key] = value;
							}
							value.Cost = num2;
							value.Parent = node;
							Push(value, num2 + point.Distance(goal));
						}
					}
				}
			}
		}

		private Point Position(Node n)
		{
			return new Point(start.X + (double)n.X * spacing, start.Y + (double)n.Y * spacing);
		}

		private void Push(Node node, double priority)
		{
			Entry entry = new Entry(node, priority);
			int num = heap.Count;
			heap.Add(entry);
			while (num > 0)
			{
				int num2 = (num - 1) / 2;
				if (heap[num2].Priority <= priority)
				{
					break;
				}
				heap[num] = heap[num2];
				num = num2;
			}
			heap[num] = entry;
		}

		private Entry Pop()
		{
			Entry result = heap[0];
			Entry value = heap[heap.Count - 1];
			heap.RemoveAt(heap.Count - 1);
			if (heap.Count == 0)
			{
				return result;
			}
			int num = 0;
			while (num * 2 + 1 < heap.Count)
			{
				int num2 = num * 2 + 1;
				if (num2 + 1 < heap.Count && heap[num2 + 1].Priority < heap[num2].Priority)
				{
					num2++;
				}
				if (value.Priority <= heap[num2].Priority)
				{
					break;
				}
				heap[num] = heap[num2];
				num = num2;
			}
			heap[num] = value;
			return result;
		}
	}
	public sealed class ScoutPoint
	{
		public readonly float X;

		public readonly float Z;

		public readonly string Name;

		public ScoutPoint(float x, float z, string name)
		{
			X = x;
			Z = z;
			Name = name;
		}
	}
	public sealed class ScoutReport
	{
		public readonly HashSet<IslandCell> Cells;

		public readonly List<ScoutPoint> Points;

		public ScoutReport(IEnumerable<IslandCell> cells, IEnumerable<ScoutPoint> points)
		{
			Cells = new HashSet<IslandCell>(cells);
			Points = new List<ScoutPoint>(points);
		}

		public bool Contains(float x, float z)
		{
			return Cells.Contains(new IslandCell((int)Math.Round(x / 8f), (int)Math.Round(z / 8f)));
		}

		public byte[] Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (GZipStream output = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true))
			{
				using BinaryWriter binaryWriter = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
				binaryWriter.Write(1);
				binaryWriter.Write(Cells.Count);
				foreach (IslandCell cell in Cells)
				{
					binaryWriter.Write(cell.X);
					binaryWriter.Write(cell.Z);
				}
				binaryWriter.Write(Points.Count);
				foreach (ScoutPoint point in Points)
				{
					binaryWriter.Write(point.X);
					binaryWriter.Write(point.Z);
					byte[] bytes = Encoding.UTF8.GetBytes(point.Name);
					if (bytes.Length > 128)
					{
						throw new InvalidDataException("POI label is too long.");
					}
					binaryWriter.Write(bytes.Length);
					binaryWriter.Write(bytes);
				}
			}
			return memoryStream.ToArray();
		}

		public static ScoutReport Decode(byte[] data)
		{
			if (data.Length > 16777216)
			{
				throw new InvalidDataException("Report is too large.");
			}
			using MemoryStream stream = new MemoryStream(data);
			using GZipStream input = new GZipStream(stream, CompressionMode.Decompress);
			using BinaryReader binaryReader = new BinaryReader(input);
			if (binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unknown survey report version.");
			}
			int num = binaryReader.ReadInt32();
			if (num < 1 || num > 1000000)
			{
				throw new InvalidDataException("Invalid survey size.");
			}
			HashSet<IslandCell> hashSet = new HashSet<IslandCell>();
			for (int i = 0; i < num; i++)
			{
				int num2 = binaryReader.ReadInt32();
				int num3 = binaryReader.ReadInt32();
				if (Math.Abs((long)num2) > 1225 || Math.Abs((long)num3) > 1225 || !hashSet.Add(new IslandCell(num2, num3)))
				{
					throw new InvalidDataException("Invalid survey cell.");
				}
			}
			num = binaryReader.ReadInt32();
			if (num < 0 || num > 10000)
			{
				throw new InvalidDataException("Invalid POI count.");
			}
			List<ScoutPoint> list = new List<ScoutPoint>();
			for (int j = 0; j < num; j++)
			{
				float num4 = binaryReader.ReadSingle();
				float num5 = binaryReader.ReadSingle();
				int num6 = binaryReader.ReadInt32();
				if (float.IsNaN(num4) || float.IsNaN(num5) || float.IsInfinity(num4) || float.IsInfinity(num5) || Math.Abs(num4) > 9800f || Math.Abs(num5) > 9800f || num6 < 1 || num6 > 128)
				{
					throw new InvalidDataException("Invalid POI.");
				}
				byte[] array = binaryReader.ReadBytes(num6);
				if (array.Length != num6)
				{
					throw new EndOfStreamException();
				}
				list.Add(new ScoutPoint(num4, num5, Encoding.UTF8.GetString(array)));
			}
			if (binaryReader.BaseStream.ReadByte() != -1)
			{
				throw new InvalidDataException("Trailing survey data.");
			}
			return new ScoutReport(hashSet, list);
		}
	}
	public sealed class ShipBlueprint
	{
		public readonly string Id;

		public readonly string Name;

		public readonly string Source;

		public readonly string Recipe;

		public readonly int BuildSeconds;

		public readonly bool HasSail;

		public bool UsesSlipway
		{
			get
			{
				switch (Id)
				{
				case "hercule":
				case "merchant":
				case "longship":
				case "big_cargo":
				case "warship":
					return true;
				default:
					return false;
				}
			}
		}

		public string Prefab => Source;

		public ShipBlueprint(string id, string name, string source, string recipe, int seconds, bool sails = true)
		{
			Id = id;
			Name = name;
			Source = source;
			Recipe = recipe;
			BuildSeconds = seconds;
			HasSail = sails;
		}
	}
	public enum ShipwrightTask
	{
		Idle,
		Hammer,
		Chisel,
		Stitch,
		AwaitingLaunch
	}
	public static class ShipConstruction
	{
		public static readonly IReadOnlyList<ShipBlueprint> Blueprints = Array.AsReadOnly(new ShipBlueprint[10]
		{
			new ShipBlueprint("dugout", "Dugout", "HelmsmanDugout", "Wood:12", 120, sails: false),
			new ShipBlueprint("kayak", "Finewood Kayak", "HelmsmanFinewoodKayak", "FineWood:10,Wood:12,Resin:8", 150, sails: false),
			new ShipBlueprint("tandem_kayak", "Tandem Finewood Kayak", "HelmsmanTandemKayak", "FineWood:16,Wood:16,Resin:10", 180, sails: false),
			new ShipBlueprint("little_boat", "Ceol", "LittleBoat", "LeatherScraps:8,Wood:40", 240),
			new ShipBlueprint("currach", "Currach", "HelmsmanCurrach", "DeerHide:10,Wood:25,Resin:10", 180),
			new ShipBlueprint("hercule", "Falkuša fishing boat", "HerculeShip", "DeerHide:10,RoundLog:30,Resin:30,BronzeNails:100,LeatherScraps:10", 360),
			new ShipBlueprint("merchant", "Ottar", "MercantShip", "DeerHide:20,RoundLog:40,Resin:40,IronNails:100,LeatherScraps:10", 480),
			new ShipBlueprint("longship", "Longship", "VikingShip", ShipwrightRules.Hull.Recipe, 720),
			new ShipBlueprint("big_cargo", "Big cargo ship", "BigCargoShip", "DeerHide:20,FineWood:50,Resin:50,Coal:50,IronNails:150,LeatherScraps:20", 900),
			new ShipBlueprint("warship", "Snekkja", "WarShip", "DeerHide:20,FineWood:50,Resin:50,Coal:50,IronNails:120,LeatherScraps:10", 1080)
		});

		public static ShipBlueprint? Find(string id)
		{
			return Blueprints.FirstOrDefault((ShipBlueprint b) => b.Id == id);
		}

		public static bool ValidRecipe(string id, string recipe)
		{
			ShipBlueprint shipBlueprint = Find(id);
			if (shipBlueprint == null)
			{
				return false;
			}
			if (recipe == shipBlueprint.Recipe)
			{
				return true;
			}
			return id switch
			{
				"hercule" => recipe == "ClothShip:2,ResinWood:30,BronzeNails:100,ShipRope:2", 
				"merchant" => recipe == "ClothShip:4,ResinWood:40,IronNails:100,ShipRope:2", 
				"big_cargo" => recipe == "ClothShip:4,CaulkedWood:50,IronNails:150,ShipRope:4", 
				"warship" => recipe == "ClothShip:4,CaulkedWood:50,IronNails:120,ShipRope:2", 
				_ => false, 
			};
		}

		public static bool ValidDuration(double seconds)
		{
			if (!double.IsNaN(seconds) && !double.IsInfinity(seconds) && seconds >= 30.0)
			{
				return seconds <= 86400.0;
			}
			return false;
		}

		public static double Elapsed(long started, long now, double duration)
		{
			if (started < 0 || now <= started || !ValidDuration(duration))
			{
				return 0.0;
			}
			return Math.Min(duration, ((double)now - (double)started) / 10000000.0);
		}

		public static ShipwrightTask Task(bool hasSail, double elapsed, double duration)
		{
			if (!ValidDuration(duration) || double.IsNaN(elapsed) || elapsed < 0.0)
			{
				return ShipwrightTask.Idle;
			}
			if (elapsed >= duration)
			{
				return ShipwrightTask.AwaitingLaunch;
			}
			int num = (int)(elapsed % 36.0 / 12.0);
			if (num != 0)
			{
				if (num != 1 && hasSail)
				{
					return ShipwrightTask.Stitch;
				}
				return ShipwrightTask.Chisel;
			}
			return ShipwrightTask.Hammer;
		}
	}
	public static class ShipRules
	{
		public static bool CanSail(string prefabName, bool hasSail, float sailForce, bool hasClothSail = false)
		{
			string text = prefabName.Replace("_", "").Replace("-", "").Replace(" ", "")
				.ToLowerInvariant();
			if ((hasSail || hasClothSail) && sailForce > 0.0001f && !text.Contains("rowboat") && !text.Contains("rowing"))
			{
				return !text.Contains("canoe");
			}
			return false;
		}

		public static bool IsMast(string animation)
		{
			return string.Equals(animation, "attach_mast", StringComparison.OrdinalIgnoreCase);
		}

		public static bool IsSeat(string animation)
		{
			if (!string.IsNullOrEmpty(animation))
			{
				if (animation.IndexOf("sit", StringComparison.OrdinalIgnoreCase) < 0 && animation.IndexOf("chair", StringComparison.OrdinalIgnoreCase) < 0)
				{
					return animation.Equals("attach_lox", StringComparison.OrdinalIgnoreCase);
				}
				return true;
			}
			return false;
		}

		public static bool Eligible(bool canSail, int seats)
		{
			if (!canSail)
			{
				return seats >= 1;
			}
			return true;
		}
	}
	public static class ShipText
	{
		public static string CleanName(string text)
		{
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			string text2 = text ?? "";
			foreach (char c in text2)
			{
				switch (c)
				{
				case '<':
					flag = true;
					continue;
				case '>':
					flag = false;
					continue;
				}
				if (!flag && !char.IsControl(c) && stringBuilder.Length < 48)
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString().Trim();
		}

		public static string Bearing(double east, double north)
		{
			if (Math.Abs(east) + Math.Abs(north) < 0.01)
			{
				return "Here";
			}
			string[] obj = new string[8] { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
			double num = (Math.Atan2(east, north) * 180.0 / Math.PI + 360.0) % 360.0;
			return obj[(int)Math.Floor((num + 22.5) / 45.0) % 8];
		}
	}
	public sealed class ShipUpgrade
	{
		public readonly string Id;

		public readonly string Name;

		public readonly string Recipe;

		public readonly string Station;

		public readonly string Requires;

		public readonly int Level;

		public ShipUpgrade(string id, string name, string recipe, string station, int level, string requires = "")
		{
			Id = id;
			Name = name;
			Recipe = recipe;
			Station = station;
			Level = level;
			Requires = requires;
		}
	}
	public static class ShipwrightRules
	{
		public static readonly ShipUpgrade Hull = new ShipUpgrade("longship", "Commission longship", "IronNails:100,DeerHide:10,FineWood:40,ElderBark:40", "$piece_workbench", 1);

		public static readonly ShipUpgrade[] Upgrades = new ShipUpgrade[6]
		{
			new ShipUpgrade("lantern", "Deck lantern", "SurtlingCore:3,BronzeNails:10,FineWood:6,Chain:1", "$piece_forge", 4),
			new ShipUpgrade("canopy", "Sheltering canopy", "JuteRed:2,LoxPelt:2,LinenThread:6,RoundLog:2", "$piece_workbench", 4),
			new ShipUpgrade("cargo1", "Cargo hold I — 7 × 3", "ElderBark:20,Silver:5,Obsidian:10", "$piece_workbench", 4),
			new ShipUpgrade("cargo2", "Cargo hold II — 7 × 4", "BlackMetal:20,YggdrasilWood:20,FineWood:20", "$piece_artisanstation", 1, "cargo1"),
			new ShipUpgrade("treatment", "Fire / Ashlands treatment", "CeramicPlate:20,Tar:20,YggdrasilWood:20,IronNails:50", "$piece_blackforge", 3),
			new ShipUpgrade("trophy", "Decorative trophy mount", "FineWood:4,BronzeNails:10", "$piece_workbench", 1)
		};

		public static Dictionary<string, int> Costs(string recipe)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			string[] array = recipe.Split(',');
			foreach (string text in array)
			{
				string[] array2 = text.Trim().Split(':');
				if (array2.Length != 2 || string.IsNullOrWhiteSpace(array2[0]) || !int.TryParse(array2[1], out var result) || result <= 0 || result > 100000)
				{
					throw new ArgumentException("Invalid material cost: " + text);
				}
				dictionary[array2[0]] = checked((dictionary.TryGetValue(array2[0], out var value) ? value : 0) + result);
			}
			if (dictionary.Count == 0)
			{
				throw new ArgumentException("A paid build needs materials.");
			}
			return dictionary;
		}

		public static string Check(ShipUpgrade upgrade, Func<string, bool> built)
		{
			if (built(upgrade.Id))
			{
				return "Already fitted.";
			}
			if (upgrade.Requires.Length > 0 && !built(upgrade.Requires))
			{
				return "Fit Cargo hold I first.";
			}
			return "";
		}

		public static (int Width, int Height) Cargo(bool first, bool second, int width, int height)
		{
			return (Width: Math.Max(width, (first || second) ? 7 : 6), Height: Math.Max(height, second ? 4 : 3));
		}

		public static bool CanPay(IReadOnlyDictionary<string, int> costs, Func<string, int> available)
		{
			return costs.All<KeyValuePair<string, int>>((KeyValuePair<string, int> pair) => available(pair.Key) >= pair.Value);
		}
	}
	public enum YardSound
	{
		PuffinMurmur,
		PuffinGreeting,
		Hammer,
		Scrape,
		Rope,
		Creak,
		Splash,
		Footstep,
		WoodSlide
	}
	public static class ShipyardAudioSynth
	{
		public const int Rate = 22050;

		public static float[] Create(YardSound sound, int variant = 0)
		{
			double num = sound switch
			{
				YardSound.Scrape => 0.65, 
				YardSound.Rope => 0.8, 
				YardSound.PuffinGreeting => 0.52, 
				YardSound.PuffinMurmur => 1.1, 
				YardSound.Creak => 2.4, 
				YardSound.Splash => 1.8, 
				YardSound.WoodSlide => 4.2, 
				_ => 0.19, 
			};
			float[] array = new float[(int)(22050.0 * num)];
			Random random = new Random(2311 + (int)sound * 911 + variant * 1777);
			double num2 = 0.0;
			double num3 = 0.0;
			double num4 = 0.0;
			double num5 = 0.0;
			for (int i = 0; i < array.Length; i++)
			{
				double num6 = (double)i / 22050.0;
				double num7 = num6 / num;
				double num8 = random.NextDouble() * 2.0 - 1.0;
				num3 = 0.86 * num3 + 0.14 * num8;
				double num9 = Math.Pow(Math.Sin(Math.PI * num7), 0.8);
				double num10 = 0.0;
				switch (sound)
				{
				case YardSound.PuffinMurmur:
				case YardSound.PuffinGreeting:
					num2 += Math.PI * 2.0 * ((sound == YardSound.PuffinGreeting) ? (260.0 - 75.0 * num7) : (150.0 + 22.0 * Math.Sin(num6 * 11.0))) / 22050.0;
					num10 = (Math.Sin(num2) * 0.2 + Math.Sin(num2 * 2.0) * 0.11 + num3 * 0.3) * (0.6 + 0.4 * Math.Sin(num6 * 37.0));
					break;
				case YardSound.Hammer:
					num10 = (Math.Sin(num6 * 2.0 * Math.PI * 190.0) * 0.5 + num8 * 0.17) * Math.Exp((0.0 - num6) * 25.0);
					num9 = Math.Min(1.0, num6 / 0.003);
					break;
				case YardSound.Footstep:
					num10 = num3 * 0.45 * Math.Exp((0.0 - num6) * 23.0);
					num9 = Math.Min(1.0, num6 / 0.004);
					break;
				case YardSound.Scrape:
					num10 = (num8 - num3) * 0.18 * (0.65 + 0.35 * Math.Sin(num6 * 83.0));
					break;
				case YardSound.Rope:
					num10 = num3 * 0.38 + num8 * 0.06 * (0.5 + 0.5 * Math.Sin(num6 * 49.0));
					break;
				case YardSound.Creak:
				{
					num4 = 0.992 * num4 + 0.008 * num8;
					double num13 = 0.35 + 0.65 * Math.Pow(Math.Sin(num6 * 5.2 + (double)variant), 2.0);
					double num14 = ((random.NextDouble() < 0.003 * num13) ? ((random.NextDouble() * 2.0 - 1.0) * 0.5) : 0.0);
					num5 = 0.91 * num5 + num14;
					num2 += Math.PI * 2.0 * (67.0 + num4 * 420.0 + 27.0 * Math.Sin(num6 * 3.8 + Math.Sin(num6 * 13.0))) / 22050.0;
					num10 = (num3 * 0.42 + num5 * 0.65 + Math.Sin(num2) * 0.075 + Math.Sin(num2 * 2.73) * 0.04) * num13;
					break;
				}
				case YardSound.WoodSlide:
				{
					num4 = 0.998 * num4 + 0.002 * num8;
					double num11 = 0.5 + 0.24 * Math.Sin(num6 * 19.0 + Math.Sin(num6 * 7.0)) + 0.16 * Math.Sin(num6 * 43.0);
					double num12 = Math.Pow(Math.Max(0.0, Math.Sin(num6 * 11.7 + 0.6 * Math.Sin(num6 * 3.0))), 9.0);
					num10 = num3 * (0.62 + 0.3 * num12) + (num8 - num3) * 0.08 * num11 + num4 * 0.28;
					num9 = Math.Min(1.0, num6 / 0.035) * Math.Min(1.0, (num - num6) / 0.035);
					break;
				}
				case YardSound.Splash:
					num10 = num3 * 0.65 + num8 * 0.16;
					num9 = Math.Pow(Math.Sin(Math.PI * num7), 0.55) * Math.Exp((0.0 - num7) * 1.8);
					break;
				}
				array[i] = (float)(num10 * num9 * Math.Min(1.0, (1.0 - num7) * 30.0));
			}
			return array;
		}
	}
	public readonly struct ShoreCandidate
	{
		public readonly Point Center;

		public readonly double Heading;

		public ShoreCandidate(Point center, double heading)
		{
			Center = center;
			Heading = heading;
		}
	}
	public static class ShorelineSearch
	{
		public const double MaximumRadius = 48.0;

		public const double MaximumSwimGap = 12.0;

		public static IEnumerable<ShoreCandidate> Candidates(Point caller, double length)
		{
			if (!Finite(caller.X) || !Finite(caller.Y) || !Finite(length) || length <= 0.0)
			{
				yield break;
			}
			for (double radius = Math.Max(8.0, length * 0.5 + 3.0); radius <= 48.0; radius += 4.0)
			{
				for (int angle = 0; angle < 24; angle++)
				{
					double num = (double)angle * Math.PI / 12.0;
					yield return new ShoreCandidate(new Point(caller.X + Math.Sin(num) * radius, caller.Y + Math.Cos(num) * radius), angle * 15 + 180);
				}
			}
		}

		public static bool ShoreAccess(Point caller, Point bow, double sea, Func<Point, double?> height)
		{
			if (!Finite(caller.X) || !Finite(caller.Y) || !Finite(bow.X) || !Finite(bow.Y) || !Finite(sea))
			{
				return false;
			}
			double num = caller.Distance(bow);
			if (num > 48.0 || num < 1.0)
			{
				return false;
			}
			int num2 = (int)Math.Ceiling(num);
			double num3 = 0.0;
			bool flag = false;
			for (int i = 0; i <= num2; i++)
			{
				double num4 = (double)i / (double)num2;
				double? num5 = height(new Point(caller.X + (bow.X - caller.X) * num4, caller.Y + (bow.Y - caller.Y) * num4));
				if (!num5.HasValue || !Finite(num5.Value) || num5.Value > sea + 8.0)
				{
					return false;
				}
				if (num5.Value < sea - 0.25)
				{
					flag = true;
					continue;
				}
				if (flag)
				{
					return false;
				}
				num3 = num * num4;
			}
			if (flag)
			{
				return num - num3 <= 12.0;
			}
			return false;
		}

		private static bool Finite(double value)
		{
			if (!double.IsNaN(value))
			{
				return !double.IsInfinity(value);
			}
			return false;
		}
	}
	public static class SlipwayMotion
	{
		public const float LaunchSeconds = 10f;

		public const float ResetSeconds = 6f;

		public static float Clamp(double t)
		{
			return (float)Math.Max(0.0, Math.Min(1.0, double.IsNaN(t) ? 0.0 : t));
		}

		public static float Ease(double t)
		{
			float num = Clamp(t);
			return num * num * (3f - 2f * num);
		}

		public static float Travel(double seconds)
		{
			float num = Clamp((seconds - 1.4) / 8.6);
			return num * num * (2f - num);
		}

		public static float Level(double seconds)
		{
			return Ease(((double)Travel(seconds) - 0.48) / 0.52);
		}

		public static float Cradle(double seconds, float launchDistance = 23f)
		{
			return Math.Min(11f, Travel(seconds) * Math.Max(0f, launchDistance));
		}

		public static float Reset(double seconds)
		{
			return 1f - Ease(seconds / 6.0);
		}

		public static double LaunchElapsed(long now, long started, long paused, long pauseTicks)
		{
			if (started > 0)
			{
				return Math.Max(0.0, ((double)((paused > 0) ? paused : now) - (double)started - (double)Math.Max(0L, pauseTicks)) / 10000000.0);
			}
			return 0.0;
		}

		public static float FloatOffset(float waterOffset, float massCenter, float forceDistance, float force, float gravity)
		{
			return waterOffset - massCenter - Math.Abs(gravity) * forceDistance / (50f * Math.Max(0.01f, force));
		}

		public static float PartStart(string name, float height)
		{
			name = (name ?? "").ToLowerInvariant();
			if (name.StartsWith("deck ") || name == "deck")
			{
				return 0.5f;
			}
			if (name.StartsWith("mast ") || name == "mast")
			{
				return 0.66f;
			}
			if (name.StartsWith("rigging ") || name == "rigging")
			{
				return 0.78f;
			}
			if (name.StartsWith("sail ") || name == "sail")
			{
				return 0.9f;
			}
			if (name.StartsWith("cargo ") || name == "cargo" || name.StartsWith("decoration "))
			{
				return 0.96f;
			}
			if (name.StartsWith("fixed ") || name == "fixed")
			{
				return 0.4f;
			}
			if (name.Contains("sail"))
			{
				return 0.9f;
			}
			if (name.Contains("rope") || name.Contains("rigging") || name.Contains("shroud"))
			{
				return 0.78f;
			}
			if (name.Contains("cargo") || name.Contains("barrel") || name.Contains("sack") || name.Contains("shield") || name.Contains("decoration"))
			{
				return 0.96f;
			}
			if (name.Contains("mast") || name.Contains("yard"))
			{
				return 0.66f;
			}
			if (name.Contains("deck") || name.Contains("bench") || name.Contains("seat"))
			{
				return 0.5f;
			}
			return 0.05f + 0.48f * Clamp(height);
		}
	}
	public static class VoyageWords
	{
		public static string Obstruction(string reason)
		{
			string text = reason.ToLowerInvariant();
			if (text.Contains("water depth"))
			{
				return "Too shallow ahead, Viking. " + reason;
			}
			if (text.Contains("not available") || text.Contains("not loaded"))
			{
				return "I can't check the waters ahead yet, Viking. " + reason;
			}
			if (text.Contains("blocked") || text.Contains("no clear"))
			{
				return "Our way is blocked, Viking. " + reason;
			}
			if (text.Contains("no progress"))
			{
				return "We're making no headway, Viking. I'm checking another course.";
			}
			return "Holding here, Viking. " + reason;
		}
	}
	public static class WorkshopCoverage
	{
		public static bool Contains(float radius, float dx, float dy, float dz)
		{
			if (radius > 0f && !float.IsNaN(radius) && !float.IsInfinity(radius))
			{
				return (double)dx * (double)dx + (double)dy * (double)dy + (double)dz * (double)dz <= (double)radius * (double)radius;
			}
			return false;
		}
	}
}