Decompiled source of TeleportBoats v0.1.3

TeleportBoats.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using LocalizationManager;
using Microsoft.CodeAnalysis;
using Odinplus.Shared.ConfigurationUi;
using Odinplus.Shared.Input;
using Odinplus.Shared.Timing;
using Odinplus.Shared.Unity;
using Odinplus.Shared.YamlEditor;
using ServerSync;
using TMPro;
using UnityEngine;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TeleportBoats")]
[assembly: AssemblyDescription("https://valheim.hexium.gg/mods/OdinPlus/TeleportBoats")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("odinplus")]
[assembly: AssemblyProduct("TeleportBoats")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyFileVersion("0.1.3")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.3.0")]
[module: UnverifiableCode]
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;
		}
	}
}
namespace TeleportBoats
{
	internal static class BoatConfigurationUiModel
	{
		internal static void Apply(ModuleConfigurationView view)
		{
			foreach (ConfigurationDescriptor descriptor in view.Descriptors)
			{
				switch (descriptor.StableId)
				{
				case "Boats.Enabled":
					descriptor.Label = ((descriptor.Scope == "Player") ? "Boat Transport" : "Allow Boat Transport");
					descriptor.Help = ((descriptor.Scope == "Player") ? "Your preference can turn boat transport off, subject to server rules." : "The server-wide setting for boat transport. Player preferences can only restrict it.");
					break;
				case "Boats.SearchRadius":
					descriptor.Label = "Boat Search Distance";
					descriptor.Help = "Maximum distance from you to find a boat.";
					break;
				case "Boats.Teleport.Portals.Enabled":
					descriptor.Label = ((descriptor.Scope == "Player") ? "" : "Allow ") + "Boat Transport Through Portals";
					descriptor.Help = ((descriptor.Scope == "Player") ? "Your preference for taking boats through portals, subject to server rules." : "Allows boats to travel through portals, subject to world rules.");
					break;
				case "Boats.Teleport.Dungeons.Enabled":
					descriptor.Label = ((descriptor.Scope == "Player") ? "" : "Allow ") + "Boat Transport Through Dungeons";
					descriptor.Help = ((descriptor.Scope == "Player") ? "Your preference for taking boats through dungeon entrances, subject to server rules." : "Allows boats to travel through dungeon entrances and exits, subject to world rules.");
					break;
				case "Placement.ForwardOffset":
					descriptor.Label = "Boat Landing Forward Offset";
					descriptor.Help = "Minimum distance ahead of your arrival point for boat placement.";
					break;
				case "Placement.Radius":
					descriptor.Label = "Maximum Boat Landing Distance";
					descriptor.Help = "Maximum distance from your arrival point to a boat landing candidate.";
					break;
				}
			}
			foreach (ConfigurationDescriptor item in view.Descriptors.Where((ConfigurationDescriptor r) => r.Kind == "Number"))
			{
				item.Minimum = 0.0;
				item.Maximum = ((item.StableId == "Boats.SearchRadius") ? 500 : 100);
				string text = ((item.StableId == "Placement.ForwardOffset") ? "Enter a number greater than zero and at most 100; it cannot exceed the placement radius." : ("Enter a number greater than zero and at most " + item.Maximum + "."));
				item.Help = (string.IsNullOrWhiteSpace(item.Help) ? text : (item.Help + " " + text));
			}
		}
	}
	public sealed class EffectiveBoatPolicy
	{
		public BoatServerPolicy ServerPolicy { get; }

		public PlayerBoatPreferences PlayerPreferences { get; }

		public float SearchRadius => ServerPolicy.Boats.SearchRadius;

		public float ForwardOffset => ServerPolicy.Placement.ForwardOffset;

		public float PlacementRadius => ServerPolicy.Placement.Radius;

		private EffectiveBoatPolicy(BoatServerPolicy serverPolicy, PlayerBoatPreferences playerPreferences)
		{
			ServerPolicy = serverPolicy;
			PlayerPreferences = playerPreferences;
		}

		public static EffectiveBoatPolicy Create(BoatServerPolicy server, PlayerBoatPreferences player)
		{
			BoatServerPolicyValidator.Validate(server);
			PlayerBoatPreferencesValidator.Validate(player);
			return new EffectiveBoatPolicy(server, player);
		}

		public bool IsTransitionEnabled(BoatTransitionKind kind)
		{
			if (ServerPolicy.Boats.Enabled && PlayerPreferences.Boats.Enabled && BoatTransitionPolicy.IsEnabled(ServerPolicy.Boats.Teleport, kind))
			{
				return BoatTransitionPolicy.IsEnabled(PlayerPreferences.Boats.Teleport, kind);
			}
			return false;
		}
	}
	internal enum BoatLeaveBehindReason
	{
		NoWater,
		NoSpace,
		BoatOccupied,
		BoatUnavailable
	}
	internal static class BoatLeaveBehindReasonPolicy
	{
		public static BoatLeaveBehindReason? Select(string outcome, bool missingRecord, bool ownershipFailure, bool occupied, bool writeUnverified)
		{
			if (!writeUnverified)
			{
				BoatLeaveBehindReason? boatLeaveBehindReason;
				BoatLeaveBehindReason? boatLeaveBehindReason2;
				switch (outcome)
				{
				case "snapback-or-unobserved":
					break;
				case "no-water":
					boatLeaveBehindReason = BoatLeaveBehindReason.NoWater;
					goto IL_0087;
				case "obstructed-landing":
				case "boat-obstructed-landing":
				case "outside-placement-radius":
				case "placement-budget-exhausted":
				case "hull-geometry-unavailable":
					boatLeaveBehindReason = BoatLeaveBehindReason.NoSpace;
					goto IL_0087;
				default:
					{
						boatLeaveBehindReason = null;
						goto IL_0087;
					}
					IL_0087:
					boatLeaveBehindReason2 = boatLeaveBehindReason;
					if (boatLeaveBehindReason2.HasValue)
					{
						return boatLeaveBehindReason2.Value;
					}
					if (missingRecord)
					{
						return BoatLeaveBehindReason.BoatUnavailable;
					}
					if (occupied)
					{
						return BoatLeaveBehindReason.BoatOccupied;
					}
					if (ownershipFailure)
					{
						return BoatLeaveBehindReason.BoatUnavailable;
					}
					return null;
				}
			}
			return null;
		}

		public static string TranslationKey(BoatLeaveBehindReason reason)
		{
			return reason switch
			{
				BoatLeaveBehindReason.NoWater => "$tboat_reason_no_water", 
				BoatLeaveBehindReason.NoSpace => "$tboat_reason_no_space", 
				BoatLeaveBehindReason.BoatOccupied => "$tboat_reason_boat_occupied", 
				_ => "$tboat_reason_boat_unavailable", 
			};
		}
	}
	internal readonly struct BoatPlacementPoint
	{
		public float X { get; }

		public float Z { get; }

		public BoatPlacementPoint(float x, float z)
		{
			X = x;
			Z = z;
		}
	}
	internal static class BoatPlacementSearch
	{
		private readonly struct YawCandidate
		{
			public float Yaw { get; }

			public double DistanceSquared { get; }

			public int Order { get; }

			public YawCandidate(float yaw, double distanceSquared, int order)
			{
				Yaw = yaw;
				DistanceSquared = distanceSquared;
				Order = order;
			}
		}

		private const float Step = 2f;

		private const int PositionsPerRing = 9;

		private const float ForwardHalfAngleDegrees = 45f;

		private const float MaximumFootprintHalfExtent = 32f;

		private const float MinimumPlayerHalfExtent = 0.5f;

		private const float PlayerHullGap = 0.75f;

		private const float WaterlineRockClearance = 0.25f;

		private const float InvalidHullClearanceDepth = 1.5f;

		private const float YawDeduplicationTolerance = 0.001f;

		private static readonly float[] SideSearchAngles = new float[4] { -60f, 60f, -75f, 75f };

		private static readonly float[] LadderYawAdjustments = new float[8] { 0f, -30f, 30f, -60f, 60f, -90f, 90f, 180f };

		public static IEnumerable<BoatPlacementPoint> Candidates(float x, float z, float forwardYawDegrees, float forwardOffset, float radius, float spacing = 2f)
		{
			yield return AtDistance(x, z, forwardYawDegrees, forwardOffset, 0f);
			if (radius <= 0f)
			{
				yield break;
			}
			float searchStep = ((spacing > 0f) ? spacing : 2f);
			int rings = (int)Math.Ceiling(radius / searchStep);
			for (int ring = 1; ring <= rings; ring++)
			{
				float distance = Math.Min((float)ring * searchStep, radius);
				for (int slot = 0; slot < 9; slot++)
				{
					float forwardSearchAngle = GetForwardSearchAngle(slot);
					yield return AtDistance(x, z, forwardYawDegrees, forwardOffset + distance, forwardSearchAngle);
				}
			}
		}

		public static IEnumerable<BoatPlacementPoint> SideCandidates(float x, float z, float forwardYawDegrees, float firstDistance, float maximumDistance, float spacing = 2f)
		{
			if (!IsFinite(firstDistance) || !IsFinite(maximumDistance) || firstDistance < 0f || maximumDistance < firstDistance || !IsFinite(forwardYawDegrees))
			{
				yield break;
			}
			float searchStep = ((IsFinite(spacing) && spacing > 0f) ? spacing : 2f);
			int rings = (int)Math.Ceiling((maximumDistance - firstDistance) / searchStep);
			for (int ring = 0; ring <= rings; ring++)
			{
				float distance = Math.Min(firstDistance + (float)ring * searchStep, maximumDistance);
				for (int angleIndex = 0; angleIndex < SideSearchAngles.Length; angleIndex++)
				{
					yield return AtDistance(x, z, forwardYawDegrees, distance, SideSearchAngles[angleIndex]);
				}
			}
		}

		public static bool IsAtPreferredPoint(BoatPlacementPoint point, float x, float z, float forwardYawDegrees, float forwardOffset)
		{
			BoatPlacementPoint boatPlacementPoint = AtDistance(x, z, forwardYawDegrees, forwardOffset, 0f);
			if (Math.Abs(point.X - boatPlacementPoint.X) < 0.0001f)
			{
				return Math.Abs(point.Z - boatPlacementPoint.Z) < 0.0001f;
			}
			return false;
		}

		public static float GetFallbackRadius(float configuredRadius, float configuredForwardOffset, float clearanceOffset)
		{
			if (!(configuredRadius <= 0f))
			{
				return configuredRadius + Math.Max(0f, clearanceOffset - configuredForwardOffset);
			}
			return 0f;
		}

		public static float GetForwardClearanceOffset(float configuredForwardOffset, float halfWidth, float halfLength)
		{
			float num = Math.Max(halfWidth, halfLength);
			if (!(num > configuredForwardOffset))
			{
				return configuredForwardOffset;
			}
			return configuredForwardOffset + num;
		}

		public static bool IsSupportedFootprint(float halfWidth, float halfLength)
		{
			if (!float.IsNaN(halfWidth) && !float.IsInfinity(halfWidth) && !float.IsNaN(halfLength) && !float.IsInfinity(halfLength) && halfWidth >= 0f && halfLength >= 0f && halfWidth <= 32f)
			{
				return halfLength <= 32f;
			}
			return false;
		}

		public static float GetHullClearanceBottomRelativeToWaterline(float hullCenterRelativeToWaterline, float hullHalfHeight)
		{
			if (!IsFinite(hullCenterRelativeToWaterline) || !IsFinite(hullHalfHeight) || hullHalfHeight < 0f)
			{
				return -1.5f;
			}
			return Math.Min(hullCenterRelativeToWaterline - hullHalfHeight, 0f) - 0.25f;
		}

		public static float GetLandingRootHeight(float destinationSurface, float? sourceWaterlineOffset)
		{
			float num = ((sourceWaterlineOffset.HasValue && IsFinite(sourceWaterlineOffset.Value) && sourceWaterlineOffset.Value <= 0f) ? sourceWaterlineOffset.Value : 0f);
			return destinationSurface + num;
		}

		public static float? GetSourceWaterlineOffset(float sourceRootY, float sourceSurfaceY, float hullCenterY, float hullHalfHeight)
		{
			if (!IsFinite(sourceRootY) || !IsFinite(sourceSurfaceY) || !IsFinite(hullCenterY) || !IsFinite(hullHalfHeight) || hullHalfHeight < 0f)
			{
				return null;
			}
			double num = (double)sourceRootY + (double)hullCenterY - (double)hullHalfHeight;
			double num2 = (double)sourceRootY + (double)hullCenterY + (double)hullHalfHeight;
			if ((double)sourceSurfaceY < num - 0.25 || (double)sourceSurfaceY > num2 + 0.25)
			{
				return null;
			}
			float num3 = sourceRootY - sourceSurfaceY;
			if (!IsFinite(num3) || !(num3 <= 0f))
			{
				return null;
			}
			return num3;
		}

		public static bool FootprintsOverlapOrTooClose(float firstX, float firstZ, float firstHalfWidth, float firstHalfLength, float firstYawDegrees, float secondX, float secondZ, float secondHalfWidth, float secondHalfLength, float secondYawDegrees, float safetyGap = 0.25f)
		{
			if (!IsFinite(firstX) || !IsFinite(firstZ) || !IsFinite(firstYawDegrees) || !IsFinite(secondX) || !IsFinite(secondZ) || !IsFinite(secondYawDegrees) || !IsSupportedFootprint(firstHalfWidth, firstHalfLength) || !IsSupportedFootprint(secondHalfWidth, secondHalfLength) || firstHalfWidth <= 0f || firstHalfLength <= 0f || secondHalfWidth <= 0f || secondHalfLength <= 0f || !IsFinite(safetyGap) || safetyGap < 0f)
			{
				return true;
			}
			double num = (double)firstYawDegrees * Math.PI / 180.0;
			double num2 = (double)secondYawDegrees * Math.PI / 180.0;
			double num3 = Math.Cos(num);
			double num4 = Math.Sin(num);
			double num5 = Math.Cos(num2);
			double num6 = Math.Sin(num2);
			double deltaX = (double)secondX - (double)firstX;
			double deltaZ = (double)secondZ - (double)firstZ;
			if (!IsSeparated(deltaX, deltaZ, firstHalfWidth, firstHalfLength, num3, 0.0 - num4, num4, num3, secondHalfWidth, secondHalfLength, num5, 0.0 - num6, num6, num5, num3, 0.0 - num4, safetyGap) && !IsSeparated(deltaX, deltaZ, firstHalfWidth, firstHalfLength, num3, 0.0 - num4, num4, num3, secondHalfWidth, secondHalfLength, num5, 0.0 - num6, num6, num5, num4, num3, safetyGap) && !IsSeparated(deltaX, deltaZ, firstHalfWidth, firstHalfLength, num3, 0.0 - num4, num4, num3, secondHalfWidth, secondHalfLength, num5, 0.0 - num6, num6, num5, num5, 0.0 - num6, safetyGap))
			{
				return !IsSeparated(deltaX, deltaZ, firstHalfWidth, firstHalfLength, num3, 0.0 - num4, num4, num3, secondHalfWidth, secondHalfLength, num5, 0.0 - num6, num6, num5, num6, num5, safetyGap);
			}
			return false;
		}

		public static IEnumerable<float> CandidateYaws(float yawDegrees)
		{
			yield return NormalizeYaw(yawDegrees);
			yield return NormalizeYaw(yawDegrees - 90f);
		}

		public static IEnumerable<float> CandidateYawsForLadders(float playerX, float playerZ, BoatPlacementPoint center, float playerYaw, IReadOnlyList<BoatPlacementPoint> localLadderOffsets)
		{
			IReadOnlyList<BoatPlacementPoint> readOnlyList = localLadderOffsets ?? Array.Empty<BoatPlacementPoint>();
			List<float> list = new List<float>();
			bool flag = false;
			for (int i = 0; i < readOnlyList.Count; i++)
			{
				BoatPlacementPoint boatPlacementPoint = readOnlyList[i];
				if (IsFinite(boatPlacementPoint.X) && IsFinite(boatPlacementPoint.Z))
				{
					double num = Math.Atan2(boatPlacementPoint.X, boatPlacementPoint.Z) * 180.0 / Math.PI;
					float num2 = NormalizeYaw((float)(Math.Atan2(playerX - center.X, playerZ - center.Z) * 180.0 / Math.PI - num));
					float[] ladderYawAdjustments = LadderYawAdjustments;
					foreach (float num3 in ladderYawAdjustments)
					{
						AddUniqueYaw(list, NormalizeYaw(num2 + num3));
					}
					flag = true;
				}
			}
			AddUniqueYaw(list, NormalizeYaw(playerYaw));
			AddUniqueYaw(list, NormalizeYaw(playerYaw - 90f));
			AddUniqueYaw(list, NormalizeYaw(playerYaw + 90f));
			AddUniqueYaw(list, NormalizeYaw(playerYaw + 180f));
			if (flag)
			{
				List<YawCandidate> list2 = new List<YawCandidate>(list.Count);
				for (int k = 0; k < list.Count; k++)
				{
					list2.Add(new YawCandidate(list[k], GetLadderDistanceSquared(playerX, playerZ, center, list[k], readOnlyList), k));
				}
				list2.Sort(delegate(YawCandidate left, YawCandidate right)
				{
					int num4 = left.DistanceSquared.CompareTo(right.DistanceSquared);
					return (num4 == 0) ? left.Order.CompareTo(right.Order) : num4;
				});
				foreach (YawCandidate item in list2)
				{
					yield return item.Yaw;
				}
				yield break;
			}
			foreach (float item2 in list)
			{
				yield return item2;
			}
		}

		public static float GetProjectedHullHalfExtent(float halfWidth, float halfLength, float candidateYaw, float playerYaw)
		{
			double num = (double)(candidateYaw - playerYaw) * Math.PI / 180.0;
			return (float)(Math.Abs(Math.Sin(num)) * (double)Math.Max(0f, halfWidth) + Math.Abs(Math.Cos(num)) * (double)Math.Max(0f, halfLength));
		}

		public static double GetLadderDistanceSquared(float playerX, float playerZ, BoatPlacementPoint center, float yawDegrees, IReadOnlyList<BoatPlacementPoint> localLadderOffsets)
		{
			double num = (double)yawDegrees * Math.PI / 180.0;
			double num2 = Math.Cos(num);
			double num3 = Math.Sin(num);
			double num4 = double.PositiveInfinity;
			for (int i = 0; i < localLadderOffsets.Count; i++)
			{
				BoatPlacementPoint boatPlacementPoint = localLadderOffsets[i];
				if (IsFinite(boatPlacementPoint.X) && IsFinite(boatPlacementPoint.Z))
				{
					double num5 = (double)center.X + num2 * (double)boatPlacementPoint.X + num3 * (double)boatPlacementPoint.Z;
					double num6 = (double)center.Z - num3 * (double)boatPlacementPoint.X + num2 * (double)boatPlacementPoint.Z;
					double num7 = (double)playerX - num5;
					double num8 = (double)playerZ - num6;
					double num9 = num7 * num7 + num8 * num8;
					if (num9 < num4)
					{
						num4 = num9;
					}
				}
			}
			return num4;
		}

		private static void AddUniqueYaw(List<float> yaws, float yaw)
		{
			foreach (float yaw2 in yaws)
			{
				float num = Math.Abs(NormalizeYaw(yaw2 - yaw));
				num = Math.Min(num, 360f - num);
				if (num < 0.001f)
				{
					return;
				}
			}
			yaws.Add(yaw);
		}

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

		private static bool IsSeparated(double deltaX, double deltaZ, double firstHalfWidth, double firstHalfLength, double firstXAxisX, double firstXAxisZ, double firstZAxisX, double firstZAxisZ, double secondHalfWidth, double secondHalfLength, double secondXAxisX, double secondXAxisZ, double secondZAxisX, double secondZAxisZ, double axisX, double axisZ, double safetyGap)
		{
			double num = Math.Abs(deltaX * axisX + deltaZ * axisZ);
			double num2 = firstHalfWidth * Math.Abs(firstXAxisX * axisX + firstXAxisZ * axisZ) + firstHalfLength * Math.Abs(firstZAxisX * axisX + firstZAxisZ * axisZ);
			double num3 = secondHalfWidth * Math.Abs(secondXAxisX * axisX + secondXAxisZ * axisZ) + secondHalfLength * Math.Abs(secondZAxisX * axisX + secondZAxisZ * axisZ);
			return num > num2 + num3 + safetyGap;
		}

		public static float GetPlayerProjectedHalfExtent(float halfExtentX, float halfExtentZ, float forwardYawDegrees)
		{
			if (float.IsNaN(halfExtentX) || float.IsInfinity(halfExtentX) || float.IsNaN(halfExtentZ) || float.IsInfinity(halfExtentZ))
			{
				return 0.5f;
			}
			double num = (double)forwardYawDegrees * Math.PI / 180.0;
			float val = (float)(Math.Abs(Math.Sin(num)) * (double)Math.Max(0f, halfExtentX) + Math.Abs(Math.Cos(num)) * (double)Math.Max(0f, halfExtentZ));
			return Math.Max(0.5f, val);
		}

		public static float GetPreferredCenterDistance(float configuredForwardOffset, float boatHalfExtent, float playerHalfExtent)
		{
			return Math.Max(configuredForwardOffset, Math.Max(0f, boatHalfExtent) + Math.Max(0.5f, playerHalfExtent) + 0.75f);
		}

		public static IEnumerable<(BoatPlacementPoint Point, float Yaw)> PreferredPoses(float x, float z, float forwardYawDegrees, float forwardOffset, float halfWidth, float halfLength, float playerExtent)
		{
			float item = NormalizeYaw(forwardYawDegrees);
			float preferredCenterDistance = GetPreferredCenterDistance(forwardOffset, halfLength, playerExtent);
			float counterClockwiseDistance = GetPreferredCenterDistance(forwardOffset, halfWidth, playerExtent);
			yield return (Point: AtDistance(x, z, forwardYawDegrees, preferredCenterDistance, 0f), Yaw: item);
			yield return (Point: AtDistance(x, z, forwardYawDegrees, counterClockwiseDistance, 0f), Yaw: NormalizeYaw(forwardYawDegrees - 90f));
		}

		public static IEnumerable<(BoatPlacementPoint Point, float Yaw)> FallbackPoses(float x, float z, float forwardYawDegrees, float headingMinimumDistance, float counterClockwiseMinimumDistance, float maximumDistance)
		{
			float num = Math.Min(headingMinimumDistance, counterClockwiseMinimumDistance);
			if (maximumDistance < num)
			{
				yield break;
			}
			float headingYaw = NormalizeYaw(forwardYawDegrees);
			float counterClockwiseYaw = NormalizeYaw(forwardYawDegrees - 90f);
			foreach (float distance in SearchDistances(num, headingMinimumDistance, counterClockwiseMinimumDistance, maximumDistance))
			{
				for (int slot = 0; slot < 9; slot++)
				{
					BoatPlacementPoint point = AtDistance(x, z, forwardYawDegrees, distance, GetForwardSearchAngle(slot));
					if (distance >= headingMinimumDistance && !IsPreferredPose(point, headingYaw, x, z, forwardYawDegrees, headingMinimumDistance, headingYaw))
					{
						yield return (Point: point, Yaw: headingYaw);
					}
					if (distance >= counterClockwiseMinimumDistance && !IsPreferredPose(point, counterClockwiseYaw, x, z, forwardYawDegrees, counterClockwiseMinimumDistance, counterClockwiseYaw))
					{
						yield return (Point: point, Yaw: counterClockwiseYaw);
					}
				}
			}
		}

		public static float GetMaximumSearchDistance(float configuredRadius, float clearanceOffset, float headingMinimumDistance, float counterClockwiseMinimumDistance)
		{
			return Math.Max(clearanceOffset, Math.Max(headingMinimumDistance, counterClockwiseMinimumDistance)) + Math.Max(0f, configuredRadius);
		}

		public static float NormalizeYaw(float yawDegrees)
		{
			yawDegrees %= 360f;
			if (!(yawDegrees < 0f))
			{
				return yawDegrees;
			}
			return yawDegrees + 360f;
		}

		public static bool TryFindFirst(float x, float z, float forwardYawDegrees, float forwardOffset, float radius, Func<BoatPlacementPoint, bool> isValid, out BoatPlacementPoint result)
		{
			foreach (BoatPlacementPoint item in Candidates(x, z, forwardYawDegrees, forwardOffset, radius))
			{
				if (isValid(item))
				{
					result = item;
					return true;
				}
			}
			result = default(BoatPlacementPoint);
			return false;
		}

		private static float GetForwardSearchAngle(int slot)
		{
			if (slot == 0)
			{
				return 0f;
			}
			int num = (slot + 1) / 2;
			return ((slot % 2 == 0) ? 1f : (-1f)) * 45f * (float)num / 4f;
		}

		private static IEnumerable<float> SearchDistances(float minimumDistance, float headingMinimumDistance, float counterClockwiseMinimumDistance, float maximumDistance)
		{
			List<float> list = new List<float> { minimumDistance, headingMinimumDistance, counterClockwiseMinimumDistance };
			for (float num = minimumDistance + 2f; num < maximumDistance; num += 2f)
			{
				list.Add(num);
			}
			list.Add(maximumDistance);
			list.Sort();
			float previous = float.NegativeInfinity;
			foreach (float item in list)
			{
				if (!(item - previous < 0.0001f))
				{
					previous = item;
					yield return item;
				}
			}
		}

		private static bool IsPreferredPose(BoatPlacementPoint point, float yaw, float x, float z, float forwardYawDegrees, float preferredDistance, float preferredYaw)
		{
			BoatPlacementPoint boatPlacementPoint = AtDistance(x, z, forwardYawDegrees, preferredDistance, 0f);
			if (Math.Abs(point.X - boatPlacementPoint.X) < 0.0001f && Math.Abs(point.Z - boatPlacementPoint.Z) < 0.0001f)
			{
				return Math.Abs(NormalizeYaw(yaw) - NormalizeYaw(preferredYaw)) < 0.0001f;
			}
			return false;
		}

		private static BoatPlacementPoint AtDistance(float x, float z, float yawDegrees, float distance, float angleDegrees)
		{
			double num = (double)(yawDegrees + angleDegrees) * Math.PI / 180.0;
			return new BoatPlacementPoint(x + (float)Math.Sin(num) * distance, z + (float)Math.Cos(num) * distance);
		}
	}
	internal sealed class BoatPlacementSnapshot
	{
		public float HalfWidth { get; }

		public float HalfLength { get; }

		public float HalfHeight { get; }

		public float HullCenterY { get; }

		public IReadOnlyList<BoatPlacementPoint> BoardingAnchors { get; }

		public string BoardingAnchorSource { get; }

		public float? SourceWaterlineOffset { get; }

		private BoatPlacementSnapshot(float halfWidth, float halfLength, float halfHeight, float hullCenterY, ReadOnlyCollection<BoatPlacementPoint> boardingAnchors, string boardingAnchorSource, float? sourceWaterlineOffset)
		{
			HalfWidth = halfWidth;
			HalfLength = halfLength;
			HalfHeight = halfHeight;
			HullCenterY = hullCenterY;
			BoardingAnchors = boardingAnchors;
			BoardingAnchorSource = boardingAnchorSource;
			SourceWaterlineOffset = sourceWaterlineOffset;
		}

		public static bool TryCreate(float halfWidth, float halfLength, float halfHeight, float hullCenterY, IEnumerable<BoatPlacementPoint>? localBoardingAnchors, string boardingAnchorSource, out BoatPlacementSnapshot? snapshot, float? sourceWaterlineOffset = null)
		{
			snapshot = null;
			if (!BoatPlacementSearch.IsSupportedFootprint(halfWidth, halfLength) || !IsFinite(halfHeight) || halfHeight <= 0f || halfHeight > 32f || !IsFinite(hullCenterY))
			{
				return false;
			}
			List<BoatPlacementPoint> list = new List<BoatPlacementPoint>();
			if (localBoardingAnchors != null)
			{
				list.AddRange(localBoardingAnchors);
			}
			if (sourceWaterlineOffset.HasValue && (!IsFinite(sourceWaterlineOffset.Value) || sourceWaterlineOffset.Value > 0f))
			{
				sourceWaterlineOffset = null;
			}
			snapshot = new BoatPlacementSnapshot(halfWidth, halfLength, halfHeight, hullCenterY, list.AsReadOnly(), boardingAnchorSource ?? string.Empty, sourceWaterlineOffset);
			return true;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class PlayerBoatPreferences
	{
		public int SchemaVersion { get; set; } = 1;

		public PlayerBoatFeaturePreferences Boats { get; set; } = new PlayerBoatFeaturePreferences();

		public static PlayerBoatPreferences CreateDefault()
		{
			return new PlayerBoatPreferences();
		}
	}
	public sealed class PlayerBoatFeaturePreferences
	{
		public bool Enabled { get; set; } = true;

		public PlayerBoatTeleportConfig Teleport { get; set; } = new PlayerBoatTeleportConfig();
	}
	public sealed class PlayerBoatTeleportConfig
	{
		public BoatPortalTransportConfig Portals { get; set; } = new BoatPortalTransportConfig();

		public BoatTransitionToggleConfig Dungeons { get; set; } = new BoatTransitionToggleConfig();
	}
	public static class PlayerBoatPreferencesValidator
	{
		public static void Validate(PlayerBoatPreferences preferences)
		{
			if (preferences == null)
			{
				throw new InvalidDataException("Player boat preferences cannot be null.");
			}
			if (preferences.SchemaVersion != 1)
			{
				throw new InvalidDataException("Only SchemaVersion 1 is supported.");
			}
			if (preferences.Boats == null || preferences.Boats.Teleport == null || preferences.Boats.Teleport.Portals == null || preferences.Boats.Teleport.Dungeons == null)
			{
				throw new InvalidDataException("Boats and Boats.Teleport sections are required.");
			}
		}
	}
	public static class PlayerBoatPreferenceSnapshot
	{
		public static bool TryReplace(PlayerBoatPreferences lastValid, string yaml, out PlayerBoatPreferences retained, out string error)
		{
			try
			{
				PlayerBoatPreferences playerBoatPreferences = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build().Deserialize<PlayerBoatPreferences>(yaml);
				PlayerBoatPreferencesValidator.Validate(playerBoatPreferences);
				retained = playerBoatPreferences;
				error = string.Empty;
				return true;
			}
			catch (Exception ex)
			{
				retained = lastValid;
				error = ex.Message;
				return false;
			}
		}
	}
	public sealed class BoatServerPolicy
	{
		public int SchemaVersion { get; set; } = 1;

		public BoatFeatureConfig Boats { get; set; } = new BoatFeatureConfig();

		public BoatPlacementConfig Placement { get; set; } = new BoatPlacementConfig();
	}
	public sealed class BoatFeatureConfig
	{
		public bool Enabled { get; set; } = true;

		public BoatTeleportConfig Teleport { get; set; } = new BoatTeleportConfig();

		public float SearchRadius { get; set; } = 40f;
	}
	public sealed class BoatTeleportConfig
	{
		public BoatPortalTransportConfig Portals { get; set; } = new BoatPortalTransportConfig();

		public BoatTransitionToggleConfig Dungeons { get; set; } = new BoatTransitionToggleConfig();
	}
	public sealed class BoatPortalTransportConfig
	{
		public bool Enabled { get; set; } = true;
	}
	public sealed class BoatTransitionToggleConfig
	{
		public bool Enabled { get; set; } = true;
	}
	public sealed class BoatPlacementConfig
	{
		public float ForwardOffset { get; set; } = 5f;

		public float Radius { get; set; } = 30f;
	}
	public static class BoatServerPolicyValidator
	{
		public static void Validate(BoatServerPolicy policy)
		{
			if (policy == null)
			{
				throw new InvalidDataException("Boat server policy cannot be null.");
			}
			if (policy.SchemaVersion != 1)
			{
				throw new InvalidDataException("Only SchemaVersion 1 is supported.");
			}
			if (policy.Boats == null || policy.Boats.Teleport == null || policy.Placement == null || policy.Boats.Teleport.Portals == null || policy.Boats.Teleport.Dungeons == null)
			{
				throw new InvalidDataException("Boats, Boats.Teleport, Boats.Teleport.Portals, Boats.Teleport.Dungeons, and Placement sections are required.");
			}
			if (float.IsNaN(policy.Boats.SearchRadius) || float.IsInfinity(policy.Boats.SearchRadius) || policy.Boats.SearchRadius <= 0f || policy.Boats.SearchRadius > 500f)
			{
				throw new InvalidDataException("Boats.SearchRadius must be between 0 and 500.");
			}
			if (float.IsNaN(policy.Placement.ForwardOffset) || float.IsInfinity(policy.Placement.ForwardOffset) || policy.Placement.ForwardOffset <= 0f || policy.Placement.ForwardOffset > 100f)
			{
				throw new InvalidDataException("Placement.ForwardOffset must be between 0 and 100.");
			}
			if (float.IsNaN(policy.Placement.Radius) || float.IsInfinity(policy.Placement.Radius) || policy.Placement.Radius <= 0f || policy.Placement.Radius > 100f)
			{
				throw new InvalidDataException("Placement.Radius must be greater than 0 and at most 100.");
			}
			if (policy.Placement.ForwardOffset > policy.Placement.Radius)
			{
				throw new InvalidDataException("Placement.ForwardOffset cannot exceed Placement.Radius.");
			}
		}
	}
	public enum BoatTransitionKind
	{
		Portal,
		Dungeon
	}
	internal static class BoatTransitionPolicy
	{
		public static bool IsEnabled(BoatTeleportConfig config, BoatTransitionKind kind)
		{
			if (kind != BoatTransitionKind.Portal)
			{
				return config.Dungeons.Enabled;
			}
			return config.Portals.Enabled;
		}

		public static bool IsEnabled(PlayerBoatTeleportConfig config, BoatTransitionKind kind)
		{
			if (kind != BoatTransitionKind.Portal)
			{
				return config.Dungeons.Enabled;
			}
			return config.Portals.Enabled;
		}
	}
	internal enum BoatTransportOutcome
	{
		NoAttempt,
		NativeNotStarted,
		NativeFailed,
		NoArrivalWater,
		NoOwnedNetworkState,
		MoveBoat
	}
	internal readonly struct BoatTransportAttempt
	{
		public static BoatTransportAttempt None { get; } = new BoatTransportAttempt(string.Empty, BoatTransitionKind.Portal, string.Empty, isStarted: false);

		public string AttemptId { get; }

		public BoatTransitionKind Transition { get; }

		public string BoatId { get; }

		public bool IsStarted { get; }

		private BoatTransportAttempt(string attemptId, BoatTransitionKind transition, string boatId, bool isStarted)
		{
			AttemptId = attemptId;
			Transition = transition;
			BoatId = boatId;
			IsStarted = isStarted;
		}

		public static BoatTransportAttempt Started(string attemptId, BoatTransitionKind transition, string boatId)
		{
			return new BoatTransportAttempt(attemptId, transition, boatId, isStarted: true);
		}

		public BoatTransportCompletion Complete(bool nativeStarted, bool nativeSucceeded, bool hasArrivalWater, bool hasOwnedNetworkState)
		{
			if (!IsStarted)
			{
				return new BoatTransportCompletion(BoatTransportOutcome.NoAttempt, shouldMoveBoat: false);
			}
			if (!nativeStarted)
			{
				return new BoatTransportCompletion(BoatTransportOutcome.NativeNotStarted, shouldMoveBoat: false);
			}
			if (!nativeSucceeded)
			{
				return new BoatTransportCompletion(BoatTransportOutcome.NativeFailed, shouldMoveBoat: false);
			}
			if (!hasArrivalWater)
			{
				return new BoatTransportCompletion(BoatTransportOutcome.NoArrivalWater, shouldMoveBoat: false);
			}
			if (!hasOwnedNetworkState)
			{
				return new BoatTransportCompletion(BoatTransportOutcome.NoOwnedNetworkState, shouldMoveBoat: false);
			}
			return new BoatTransportCompletion(BoatTransportOutcome.MoveBoat, shouldMoveBoat: true);
		}
	}
	internal readonly struct BoatTransportCompletion
	{
		public BoatTransportOutcome Outcome { get; }

		public bool ShouldMoveBoat { get; }

		public BoatTransportCompletion(BoatTransportOutcome outcome, bool shouldMoveBoat)
		{
			Outcome = outcome;
			ShouldMoveBoat = shouldMoveBoat;
		}
	}
	internal enum BoatArrivalAction
	{
		Skip,
		MoveLive,
		MoveOwnedRecord
	}
	internal static class BoatArrivalDecision
	{
		public static BoatArrivalAction Select(bool live, bool record, bool owned, bool sourceUnoccupied, bool arrivalUnoccupied)
		{
			if (record && owned && sourceUnoccupied && (!live || arrivalUnoccupied))
			{
				if (!live)
				{
					return BoatArrivalAction.MoveOwnedRecord;
				}
				return BoatArrivalAction.MoveLive;
			}
			return BoatArrivalAction.Skip;
		}
	}
	internal sealed class BoatTransportLifecycle
	{
		private bool _accepted;

		private bool _completed;

		private bool _finished;

		private float? _observeAt;

		private float _nativeAcceptedAt;

		public bool NativeStarted { get; private set; }

		public bool NativeSucceeded { get; private set; }

		public void RecordNativeResult(bool succeeded, float now = 0f)
		{
			if (!_finished)
			{
				NativeStarted = true;
				NativeSucceeded = succeeded;
				_accepted = succeeded;
				_nativeAcceptedAt = now;
			}
		}

		public bool CanComplete(bool arrived, bool areaReady)
		{
			return _accepted && !_completed && !_finished && arrived && areaReady;
		}

		public bool TryBeginCompletion()
		{
			if (!_accepted || _completed || _finished)
			{
				return false;
			}
			_completed = true;
			return true;
		}

		public void AwaitObservation(float now, float delay)
		{
			if (_completed && !_finished)
			{
				_observeAt = now + delay;
			}
		}

		public bool ShouldObserve(float now)
		{
			if (!_finished && _observeAt.HasValue)
			{
				return now >= _observeAt.Value;
			}
			return false;
		}

		public bool TryFinish()
		{
			if (_finished)
			{
				return false;
			}
			_finished = true;
			return true;
		}

		public void Cancel()
		{
			_finished = true;
		}

		public bool ShouldExpire(float now, float timeout)
		{
			if (_accepted && !_completed && !_finished)
			{
				return now - _nativeAcceptedAt >= timeout;
			}
			return false;
		}
	}
	internal static class BoatWaterSurfaceDecision
	{
		public static bool IsUsable(float surface, float ground, float referenceHeight, float maxVerticalDistance, float waterClearance, bool colliderContainsWaterPoint)
		{
			if (colliderContainsWaterPoint && !float.IsNaN(surface) && !float.IsInfinity(surface) && surface > ground + waterClearance)
			{
				return Math.Abs(surface - referenceHeight) <= maxVerticalDistance;
			}
			return false;
		}
	}
	internal static class BoatWorldGate
	{
		public static bool ShouldBlock(bool noPortals, bool noBossPortals, bool bossEventActive)
		{
			if (!noPortals)
			{
				return noBossPortals && bossEventActive;
			}
			return true;
		}
	}
	[BepInPlugin("odinplus.TeleportBoats", "TeleportBoats", "0.1.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class TeleportBoatsPlugin : BaseUnityPlugin
	{
		private enum Toggle
		{
			On = 1,
			Off = 0
		}

		private static readonly object UiGate = new object();

		private static bool _uiCommitting;

		private static bool _uiFaulted;

		private NativeConfigTransport? _uiTransport;

		private ModuleConfigurationService? _uiService;

		private string _uiObservedServer = "";

		private string _uiObservedPlayer = "";

		private string _uiObservedCfg = "";

		internal const string Author = "odinplus";

		internal const string ModGuid = "odinplus.TeleportBoats";

		internal const string ModName = "TeleportBoats";

		internal const string ModVersion = "0.1.3";

		private const string ConfigFileName = "odinplus.TeleportBoats.cfg";

		private static readonly ConfigSync Sync = new ConfigSync("odinplus.TeleportBoats")
		{
			DisplayName = "TeleportBoats",
			CurrentVersion = "0.1.3",
			MinimumRequiredVersion = "0.1.3"
		};

		private static readonly CustomSyncedValue<string> Snapshot = new CustomSyncedValue<string>(Sync, "YamlPolicy", string.Empty);

		private static ConfigEntry<Toggle>? _forceServerConfig;

		private static ConfigEntry<bool>? _enableMod;

		private static ConfigEntry<bool>? _enableDiagnostics;

		private static ConfigEntry<bool>? _logBoatDestinations;

		private static ConfigEntry<KeyboardShortcut>? _teleportBoatHotkey;

		private readonly Harmony _harmony = new Harmony("odinplus.TeleportBoats");

		private readonly <cba6ec40-2546-4a46-9577-18d27987fd9e>ISerializer _serializer = new <cba6ec40-2546-4a46-9577-18d27987fd9e>SerializerBuilder().Build();

		private FileSystemWatcher? _serverYamlWatcher;

		private FileSystemWatcher? _playerYamlWatcher;

		private FileSystemWatcher? _configWatcher;

		private string _serverYamlPath;

		private string _playerYamlPath;

		private GameObject? _yamlEditorHost;

		public static readonly ManualLogSource TeleportBoatsLogger = Logger.CreateLogSource("TeleportBoats");

		internal static BoatServerPolicy ServerPolicy { get; private set; } = new BoatServerPolicy();

		internal static PlayerBoatPreferences PlayerPreferences { get; private set; } = PlayerBoatPreferences.CreateDefault();

		internal static EffectiveBoatPolicy Policy { get; private set; } = EffectiveBoatPolicy.Create(ServerPolicy, PlayerPreferences);

		internal static ManualLogSource Log { get; private set; } = TeleportBoatsLogger;

		internal static bool IsEnabled => _enableMod?.Value ?? false;

		internal static bool DiagnosticsEnabled => _enableDiagnostics?.Value ?? false;

		internal static bool BoatDestinationLoggingEnabled => _logBoatDestinations?.Value ?? false;

		internal static KeyboardShortcut TeleportBoatHotkey => (KeyboardShortcut)(((??)_teleportBoatHotkey?.Value) ?? new KeyboardShortcut((KeyCode)98, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }));

		internal static bool IsHotkeyConfigured
		{
			get
			{
				//IL_000b: 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_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Invalid comparison between Unknown and I4
				ConfigEntry<KeyboardShortcut>? teleportBoatHotkey = _teleportBoatHotkey;
				if (teleportBoatHotkey == null)
				{
					return true;
				}
				KeyboardShortcut value = teleportBoatHotkey.Value;
				return (int)((KeyboardShortcut)(ref value)).MainKey > 0;
			}
		}

		internal static bool IsBlocked { get; private set; }

		public string HandleUiConfigRequestV1(string request)
		{
			EnsureUi();
			return _uiTransport.Exchange(request);
		}

		private void EnsureUi()
		{
			if (_uiService != null)
			{
				return;
			}
			try
			{
				ObserveUiFiles(reload: false, "Server");
				ObserveUiFiles(reload: false, "Player");
			}
			catch
			{
			}
			_uiService = new ModuleConfigurationService("odinplus.TeleportBoats", ReadUi, UiDefaults, ValidateUi, CommitUi, ReadUiCandidate);
			_uiTransport = new NativeConfigTransport("odinplus.TeleportBoats", delegate(UiConfigRequest request, string caller, bool admin)
			{
				lock (UiGate)
				{
					return _uiService.Handle(request, caller, admin, DateTime.UtcNow);
				}
			}, delegate
			{
				lock (UiGate)
				{
					_uiService.NewSession();
				}
			});
		}

		private void Update()
		{
			EnsureUi();
			_uiTransport.Tick();
		}

		private Dictionary<string, ConfigEntryBase> UiEntries(string scope)
		{
			Dictionary<string, ConfigEntryBase> entries = new Dictionary<string, ConfigEntryBase>();
			if (scope == "Server")
			{
				Add((ConfigEntryBase?)(object)_forceServerConfig);
				Add((ConfigEntryBase?)(object)_enableMod);
			}
			else
			{
				Add((ConfigEntryBase?)(object)_enableDiagnostics);
				Add((ConfigEntryBase?)(object)_logBoatDestinations);
				Add((ConfigEntryBase?)(object)_teleportBoatHotkey);
			}
			return entries;
			void Add(ConfigEntryBase? entry)
			{
				if (entry != null)
				{
					entries[entry.Definition.Section + "/" + entry.Definition.Key] = entry;
				}
			}
		}

		private object UiPolicy(string scope)
		{
			if (!(scope == "Server"))
			{
				return PlayerPreferences;
			}
			return ServerPolicy;
		}

		private object UiDefaultPolicy(string scope)
		{
			if (!(scope == "Server"))
			{
				return PlayerBoatPreferences.CreateDefault();
			}
			return new BoatServerPolicy();
		}

		private ModuleConfigurationCandidate UiDefaults(string scope)
		{
			return new ModuleConfigurationCandidate
			{
				Yaml = ModuleConfigProtocolV1.SerializePayload(UiDefaultPolicy(scope)),
				Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => TomlTypeConverter.ConvertToString(x.Value.DefaultValue, x.Value.SettingType))
			};
		}

		private void ObserveUiFiles(bool reload, string scope)
		{
			string path = ((scope == "Server") ? _serverYamlPath : _playerYamlPath);
			string text = (File.Exists(path) ? File.ReadAllText(path) : string.Empty);
			string text2 = (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) ? File.ReadAllText(((BaseUnityPlugin)this).Config.ConfigFilePath) : string.Empty);
			string text3 = ((scope == "Server") ? _uiObservedServer : _uiObservedPlayer);
			if (scope == "Server")
			{
				_uiObservedServer = text;
			}
			else
			{
				_uiObservedPlayer = text;
			}
			if (reload && text != text3)
			{
				if (scope == "Server")
				{
					ReloadServerYaml();
				}
				else
				{
					ReloadPlayerYaml();
				}
			}
			if (reload && text2 != _uiObservedCfg)
			{
				ReloadBepInExConfig(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)));
			}
			_uiObservedCfg = text2;
		}

		private ModuleConfigurationCandidate ReadUiCandidate(string scope)
		{
			ObserveUiFiles(reload: true, scope);
			return new ModuleConfigurationCandidate
			{
				Yaml = ModuleConfigProtocolV1.SerializePayload(UiPolicy(scope)),
				Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue())
			};
		}

		private ModuleConfigurationView ReadUi(string scope)
		{
			object obj = UiPolicy(scope);
			ModuleConfigurationView moduleConfigurationView = new ModuleConfigurationView
			{
				Version = "0.1.3",
				Candidate = new ModuleConfigurationCandidate
				{
					Yaml = ModuleConfigProtocolV1.SerializePayload(obj),
					Cfg = UiEntries(scope).ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue())
				},
				Descriptors = ConfigurationFields.Describe(obj, UiDefaultPolicy(scope), scope)
			};
			foreach (KeyValuePair<string, ConfigEntryBase> item in UiEntries(scope))
			{
				ConfigEntryBase value = item.Value;
				ConfigurationDescriptor configurationDescriptor = new ConfigurationDescriptor
				{
					StableId = "cfg:" + item.Key,
					Scope = scope,
					Group = value.Definition.Section,
					Label = value.Definition.Key,
					Help = value.Description.Description,
					ValueYaml = value.GetSerializedValue(),
					DefaultYaml = TomlTypeConverter.ConvertToString(value.DefaultValue, value.SettingType),
					Kind = ((value.SettingType == typeof(bool)) ? "Toggle" : ((value.SettingType == typeof(KeyboardShortcut)) ? "KeyboardShortcut" : (value.SettingType.IsEnum ? "Enum" : "Text")))
				};
				if (value.SettingType.IsEnum)
				{
					configurationDescriptor.AllowedValues = Enum.GetNames(value.SettingType).ToList();
				}
				if (value.Description.AcceptableValues is AcceptableValueList<string> val)
				{
					configurationDescriptor.Kind = "Enum";
					configurationDescriptor.AllowedValues = val.AcceptableValues.ToList();
				}
				moduleConfigurationView.Descriptors.Add(configurationDescriptor);
			}
			string[] array = new string[2] { "com.kpro.TeleportEverything", "zenox.teleporteverything" };
			foreach (string key in array)
			{
				if (Chainloader.PluginInfos.TryGetValue(key, out var value2))
				{
					moduleConfigurationView.CompatibilityWarnings.Add(new CompatibilityWarning
					{
						ModName = value2.Metadata.Name,
						DetectedVersion = value2.Metadata.Version.ToString(),
						Reason = "Overlapping transport rules.",
						SuggestedAction = "Remove it or disable overlapping features, then restart."
					});
				}
			}
			BoatConfigurationUiModel.Apply(moduleConfigurationView);
			if (scope == "Player")
			{
				PlayerDescriptorAvailability.Apply(moduleConfigurationView, IsEnabled, ServerPolicy);
			}
			return moduleConfigurationView;
		}

		private object ParseUi(string scope, string yaml)
		{
			if (scope == "Server")
			{
				BoatServerPolicy boatServerPolicy = ModuleConfigProtocolV1.DeserializePayload<BoatServerPolicy>(yaml);
				BoatServerPolicyValidator.Validate(boatServerPolicy);
				if (ConfigurationFields.HasNonFinite(boatServerPolicy))
				{
					throw new ArgumentException("Numbers must be finite.");
				}
				return boatServerPolicy;
			}
			PlayerBoatPreferences playerBoatPreferences = ModuleConfigProtocolV1.DeserializePayload<PlayerBoatPreferences>(yaml);
			PlayerBoatPreferencesValidator.Validate(playerBoatPreferences);
			if (ConfigurationFields.HasNonFinite(playerBoatPreferences))
			{
				throw new ArgumentException("Numbers must be finite.");
			}
			return playerBoatPreferences;
		}

		private string? ValidateUi(string scope, ModuleConfigurationCandidate candidate)
		{
			try
			{
				if (_uiFaulted)
				{
					return "Saving is blocked after a rollback failure. Restart after checking the module log and configuration files.";
				}
				ParseUi(scope, candidate.Yaml);
				Dictionary<string, ConfigEntryBase> entries = UiEntries(scope);
				if (candidate.Cfg == null || candidate.Cfg.Count != entries.Count || candidate.Cfg.Keys.Any((string key) => !entries.ContainsKey(key)))
				{
					return "The complete configuration must include only this scope's supported settings.";
				}
				foreach (KeyValuePair<string, string> item in candidate.Cfg)
				{
					ConfigEntryBase val = entries[item.Key];
					object obj = TomlTypeConverter.ConvertToValue(item.Value, val.SettingType);
					if (val.Description.AcceptableValues != null && !val.Description.AcceptableValues.IsValid(obj))
					{
						return "Invalid value for " + val.Definition.Key + ".";
					}
					if (val.SettingType.IsEnum && !Enum.IsDefined(val.SettingType, obj))
					{
						return "Unsupported option.";
					}
				}
				return null;
			}
			catch (ArgumentException ex)
			{
				return ex.Message;
			}
			catch (InvalidDataException ex2)
			{
				return ex2.Message;
			}
			catch
			{
				return "The draft contains malformed or unsupported configuration values.";
			}
		}

		private void CommitUi(string scope, ModuleConfigurationCandidate candidate)
		{
			object parsed = ParseUi(scope, candidate.Yaml);
			BoatServerPolicy oldServer = ServerPolicy;
			PlayerBoatPreferences oldPlayer = PlayerPreferences;
			string oldSnapshot = Snapshot.Value;
			Dictionary<string, ConfigEntryBase> entries = UiEntries(scope);
			Dictionary<string, string> oldCfg = entries.ToDictionary<KeyValuePair<string, ConfigEntryBase>, string, string>((KeyValuePair<string, ConfigEntryBase> x) => x.Key, (KeyValuePair<string, ConfigEntryBase> x) => x.Value.GetSerializedValue());
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			_uiCommitting = true;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				AtomicModuleCommit.Execute((scope == "Server") ? _serverYamlPath : _playerYamlPath, ModuleConfigProtocolV1.SerializePayload(parsed), ((BaseUnityPlugin)this).Config.ConfigFilePath, delegate
				{
					foreach (KeyValuePair<string, string> item in candidate.Cfg)
					{
						entries[item.Key].SetSerializedValue(item.Value);
					}
					if (scope == "Server")
					{
						ServerPolicy = (BoatServerPolicy)parsed;
					}
					else
					{
						PlayerPreferences = (PlayerBoatPreferences)parsed;
					}
					RebuildEffectivePolicy();
				}, delegate
				{
					((BaseUnityPlugin)this).Config.Save();
				}, delegate
				{
					if (scope == "Server")
					{
						Snapshot.AssignLocalValue(ModuleConfigProtocolV1.SerializePayload(ServerPolicy));
					}
				}, delegate
				{
					AtomicModuleCommit.RestoreAll(((IEnumerable<KeyValuePair<string, string>>)oldCfg).Select((Func<KeyValuePair<string, string>, Action>)((KeyValuePair<string, string> pair) => delegate
					{
						entries[pair.Key].SetSerializedValue(pair.Value);
					})).Concat(new Action[2]
					{
						delegate
						{
							ServerPolicy = oldServer;
							PlayerPreferences = oldPlayer;
							RebuildEffectivePolicy();
						},
						delegate
						{
							if (scope == "Server")
							{
								Snapshot.AssignLocalValue(oldSnapshot);
							}
						}
					}));
				}, delegate(Exception error)
				{
					_uiFaulted = true;
					Log.LogError((object)("UI configuration rollback failed: " + error.Message));
				}, (scope == "Server") ? _uiObservedServer : _uiObservedPlayer, _uiObservedCfg);
				try
				{
					ObserveUiFiles(reload: false, scope);
				}
				catch
				{
				}
				Log.LogInfo((object)("UI configuration saved: scope=" + scope + "."));
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
				_uiCommitting = false;
			}
		}

		private void Awake()
		{
			Log = TeleportBoatsLogger;
			FindIncompatibleMods();
			BindConfiguration();
			_serverYamlPath = Path.Combine(Paths.ConfigPath, "odinplus.TeleportBoats.server.yml");
			_playerYamlPath = Path.Combine(Paths.ConfigPath, "odinplus.TeleportBoats.yml");
			Snapshot.ValueChanged += ReadSnapshot;
			if (Sync.IsSourceOfTruth)
			{
				ReloadServerYaml();
				SetupServerYamlWatcher();
			}
			ReloadPlayerYaml();
			SetupPlayerYamlWatcher();
			SetupConfigWatcher();
			try
			{
				RegisterYamlEditor();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("YAML editor unavailable; boat transport will continue. " + ex));
			}
			Localizer.Load();
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
		}

		private void Start()
		{
			if (!IsBlocked)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} loaded. enabled={1}, yamlSource={2}.", "TeleportBoats", IsEnabled, Sync.IsSourceOfTruth));
			}
		}

		private void OnDestroy()
		{
			_uiTransport?.Dispose();
			if (!IsBlocked)
			{
				_serverYamlWatcher?.Dispose();
				_playerYamlWatcher?.Dispose();
				_configWatcher?.Dispose();
				if ((Object)(object)_yamlEditorHost != (Object)null)
				{
					Object.Destroy((Object)(object)_yamlEditorHost);
				}
				Snapshot.ValueChanged -= ReadSnapshot;
				BoatTransport.CancelPending();
				_harmony.UnpatchSelf();
				if (!_uiFaulted)
				{
					((BaseUnityPlugin)this).Config.Save();
				}
			}
		}

		private void BindConfiguration()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Expected O, but got Unknown
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				_forceServerConfig = BindSynced("General", "Force Server Config", Toggle.On, "When On, the server's synchronized TeleportBoats settings are enforced for connected clients.");
				Sync.AddLockingConfigEntry<Toggle>(_forceServerConfig);
				_enableMod = BindSynced("General", "Enable Mod", defaultValue: true, "Master switch for TeleportBoats boat transport.");
				_teleportBoatHotkey = BindLocal<KeyboardShortcut>("Controls", "Teleport Boat Hotkey", new KeyboardShortcut((KeyCode)304, (KeyCode[])(object)new KeyCode[1] { (KeyCode)98 }), "Hotkey to teleport the nearest unoccupied boat when hovering over a portal or dungeon entrance with water at the destination.");
				YamlEditorButton.Bind(((BaseUnityPlugin)this).Config, "Advanced Configuration", "Teleport Rules");
				_enableDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enable Boat Teleport Diagnostics", false, new ConfigDescription("Writes transition capture, water, and landing details to this machine's bepinex log.", (AcceptableValueBase)null, new object[1]
				{
					new Odinplus.Shared.YamlEditor.ConfigurationManagerAttributes
					{
						IsAdvanced = true
					}
				}));
				_logBoatDestinations = BindLocal("Diagnostics", "Log Boat Teleport Destination", defaultValue: false, "Writes one concise line with the boat prefab and landing coordinates when the destination is observed after teleport.");
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
				((BaseUnityPlugin)this).Config.Save();
			}
		}

		private ConfigEntry<T> BindSynced<T>(string group, string key, T defaultValue, string description)
		{
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(group, key, defaultValue, Describe(description, "Server synchronized"));
			Sync.AddConfigEntry<T>(val).SynchronizedConfig = true;
			return val;
		}

		private ConfigEntry<T> BindLocal<T>(string group, string key, T defaultValue, string description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(group, key, defaultValue, Describe(description, "Local only"));
		}

		private static ConfigDescription Describe(string description, string scope)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			return new ConfigDescription(description + " [" + scope + "]", (AcceptableValueBase)null, Array.Empty<object>());
		}

		private void SetupServerYamlWatcher()
		{
			_serverYamlWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(_serverYamlPath))
			{
				IncludeSubdirectories = false,
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime),
				SynchronizingObject = ThreadingHelper.SynchronizingObject,
				EnableRaisingEvents = true
			};
			_serverYamlWatcher.Changed += delegate
			{
				ReloadServerYaml();
			};
			_serverYamlWatcher.Created += delegate
			{
				ReloadServerYaml();
			};
			_serverYamlWatcher.Renamed += delegate
			{
				ReloadServerYaml();
			};
		}

		private void SetupPlayerYamlWatcher()
		{
			_playerYamlWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(_playerYamlPath))
			{
				IncludeSubdirectories = false,
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime),
				SynchronizingObject = ThreadingHelper.SynchronizingObject,
				EnableRaisingEvents = true
			};
			_playerYamlWatcher.Changed += delegate
			{
				ReloadPlayerYaml();
			};
			_playerYamlWatcher.Created += delegate
			{
				ReloadPlayerYaml();
			};
			_playerYamlWatcher.Renamed += delegate
			{
				ReloadPlayerYaml();
			};
		}

		private void SetupConfigWatcher()
		{
			_configWatcher = new FileSystemWatcher(Paths.ConfigPath, "odinplus.TeleportBoats.cfg")
			{
				IncludeSubdirectories = false,
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime),
				SynchronizingObject = ThreadingHelper.SynchronizingObject,
				EnableRaisingEvents = true
			};
			_configWatcher.Changed += ReloadBepInExConfig;
			_configWatcher.Created += ReloadBepInExConfig;
			_configWatcher.Renamed += ReloadBepInExConfig;
		}

		private void RegisterYamlEditor()
		{
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			YamlEditorRegistry.Initialize("odinplus.TeleportBoats", "TeleportBoats");
			YamlEditorRegistry.Register(new Odinplus.Shared.YamlEditor.YamlDocument("server", "Server policy (" + Path.GetFileName(_serverYamlPath) + ")", _serverYamlPath, YamlValidation.FromExceptional(delegate(string text)
			{
				BoatServerPolicyValidator.Validate(new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build().Deserialize<BoatServerPolicy>(text) ?? throw new InvalidDataException("The document is empty."));
			}), () => Sync.IsSourceOfTruth && !_uiFaulted, () => "only the server host can edit the server policy"));
			YamlEditorRegistry.Register(new Odinplus.Shared.YamlEditor.YamlDocument("player", "Player preferences (" + Path.GetFileName(_playerYamlPath) + ")", _playerYamlPath, YamlValidation.FromTryPattern((string text) => (!PlayerBoatPreferenceSnapshot.TryReplace(PlayerPreferences, text, out PlayerBoatPreferences _, out string error)) ? error : null), () => !_uiFaulted));
			_yamlEditorHost = new GameObject("TeleportBoatsYamlEditor");
			Object.DontDestroyOnLoad((Object)(object)_yamlEditorHost);
			_yamlEditorHost.AddComponent<YamlEditorBehaviour>();
		}

		private void ReloadBepInExConfig(object sender, FileSystemEventArgs eventArgs)
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadBepInExConfigCore(sender, eventArgs);
				}
			}
		}

		private void ReloadBepInExConfigCore(object sender, FileSystemEventArgs eventArgs)
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return;
			}
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not reload odinplus.TeleportBoats.cfg. Check its entries and format. " + ex.Message));
			}
		}

		private void ReloadServerYaml()
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadServerYamlCore();
				}
			}
		}

		private void ReloadServerYamlCore()
		{
			if (!Sync.IsSourceOfTruth)
			{
				return;
			}
			if (!File.Exists(_serverYamlPath))
			{
				File.WriteAllText(_serverYamlPath, DefaultServerYamlTemplate());
			}
			try
			{
				BoatServerPolicy boatServerPolicy = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build().Deserialize<BoatServerPolicy>(File.ReadAllText(_serverYamlPath));
				BoatServerPolicyValidator.Validate(boatServerPolicy);
				ServerPolicy = boatServerPolicy;
				RebuildEffectivePolicy();
				Snapshot.AssignLocalValue(_serializer.Serialize(boatServerPolicy));
				LogDiagnostic($"Accepted server YAML: enabled={boatServerPolicy.Boats.Enabled}, searchRadius={boatServerPolicy.Boats.SearchRadius:0.##}, forwardOffset={boatServerPolicy.Placement.ForwardOffset:0.##}.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("TeleportBoats retained the previous valid server YAML: " + ex.Message));
			}
		}

		private void ReloadPlayerYaml()
		{
			lock (UiGate)
			{
				if (!_uiCommitting && !_uiFaulted)
				{
					ReloadPlayerYamlCore();
				}
			}
		}

		private void ReloadPlayerYamlCore()
		{
			if (!File.Exists(_playerYamlPath))
			{
				File.WriteAllText(_playerYamlPath, DefaultPlayerYamlTemplate());
			}
			string yaml = File.ReadAllText(_playerYamlPath);
			if (PlayerBoatPreferenceSnapshot.TryReplace(PlayerPreferences, yaml, out PlayerBoatPreferences retained, out string error))
			{
				PlayerPreferences = retained;
				RebuildEffectivePolicy();
				LogDiagnostic($"Accepted local player YAML: enabled={retained.Boats.Enabled}.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("TeleportBoats retained the previous valid local player YAML: " + error));
			}
		}

		private static void ReadSnapshot()
		{
			if (Sync.IsSourceOfTruth || string.IsNullOrWhiteSpace(Snapshot.Value))
			{
				return;
			}
			try
			{
				BoatServerPolicy boatServerPolicy = new <cba6ec40-2546-4a46-9577-18d27987fd9e>DeserializerBuilder().Build().Deserialize<BoatServerPolicy>(Snapshot.Value);
				BoatServerPolicyValidator.Validate(boatServerPolicy);
				ServerPolicy = boatServerPolicy;
				RebuildEffectivePolicy();
				LogDiagnostic($"Accepted server YAML snapshot: enabled={boatServerPolicy.Boats.Enabled}, searchRadius={boatServerPolicy.Boats.SearchRadius:0.##}.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Rejected TeleportBoats server snapshot: " + ex.Message));
			}
		}

		private static void RebuildEffectivePolicy()
		{
			Policy = EffectiveBoatPolicy.Create(ServerPolicy, PlayerPreferences);
		}

		private void FindIncompatibleMods()
		{
			string[] array = new string[2] { "com.kpro.TeleportEverything", "zenox.teleporteverything" };
			foreach (string text in array)
			{
				if (Chainloader.PluginInfos.TryGetValue(text, out var value))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)($"Incompatible mod detected: {value.Metadata.Name} ({value.Metadata.Version}), GUID: {text}.\n" + "This mod can conflict with TeleportBoats.\nTo avoid issues, please either:\n- Uninstall the detected mod, or\n- Disable the conflicting features in one of the mods."));
				}
			}
		}

		internal static void LogDiagnostic(string message)
		{
			if (DiagnosticsEnabled)
			{
				Log.LogInfo((object)("TeleportBoats: " + message));
			}
		}

		internal static void LogBoatDestination(string message)
		{
			if (BoatDestinationLoggingEnabled)
			{
				Log.LogInfo((object)("TeleportBoats: " + message));
			}
		}

		private static string DefaultServerYamlTemplate()
		{
			return "# Server policy. This file is synchronized to clients.\n# The server owns transport availability and placement numbers. A player file can only narrow them.\n# Boats are only teleported to destinations with water; a dry landing is never attempted.\nSchemaVersion: 1\n\nBoats:\n  # Disable boat transport for every player.\n  Enabled: true\n  # Boat detection radius around the teleporting player.\n  SearchRadius: 40\n\n  Teleport:\n    Portals:\n      # Disable boat transport through normal and stone portals.\n      Enabled: true\n    Dungeons:\n      # Covers both dungeon entrances and exits. Boats still require water at the destination.\n      Enabled: true\n\nPlacement:\n  # Check for water this far ahead of the player's arrival point.\n  ForwardOffset: 5\n  # Maximum distance from the arriving player to a boat landing candidate.\n  Radius: 30\n";
		}

		private static string DefaultPlayerYamlTemplate()
		{
			return "# Local player preference. This file is never synchronized and can only restrict the server policy.\n# Boats are only teleported to destinations with water; a dry landing is never attempted.\nSchemaVersion: 1\n\nBoats:\n  # Disable boat transport only for this player.\n  Enabled: true\n\n  Teleport:\n    Portals:\n      # Disable boat transport through portals only for this player.\n      Enabled: true\n    Dungeons:\n      # Disable dungeon entrance and exit boat transport only for this player.\n      Enabled: true\n";
		}
	}
	internal static class PlayerDescriptorAvailability
	{
		internal static void Apply(ModuleConfigurationView view, bool moduleEnabled, BoatServerPolicy server)
		{
			if (!moduleEnabled || !server.Boats.Enabled)
			{
				Disable(view, (ConfigurationDescriptor _) => true, (!moduleEnabled) ? "The boat module is off. Saved preferences are retained." : "Boat transport is off. Saved preferences are retained.");
				return;
			}
			if (!server.Boats.Teleport.Portals.Enabled)
			{
				Disable(view, (ConfigurationDescriptor row) => Under(row, "Boats.Teleport.Portals"), "Portal boat transport is off. Saved preferences are retained.");
			}
			if (!server.Boats.Teleport.Dungeons.Enabled)
			{
				Disable(view, (ConfigurationDescriptor row) => Under(row, "Boats.Teleport.Dungeons"), "Dungeon boat transport is off. Saved preferences are retained.");
			}
		}

		private static bool Under(ConfigurationDescriptor row, string path)
		{
			return row.StableId.StartsWith(path + ".", StringComparison.Ordinal);
		}

		private static void Disable(ModuleConfigurationView view, Func<ConfigurationDescriptor, bool> matches, string reason)
		{
			foreach (ConfigurationDescriptor item in view.Descriptors.Where(matches))
			{
				item.Enabled = false;
				item.DisabledReason = "Disabled by server: " + reason;
			}
		}
	}
	internal static class BoatTransportDiagnostics
	{
		public static string Rejection(string attemptId, BoatTransitionKind transition, Player player, Ship boat, Vector3 sourceInteractionPosition, string reason)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			return $"boat-transition attempt={attemptId} phase=summary outcome={reason} transition={transition}" + " | identity: player=" + SafePlayer(player) + " boat=" + SafeBoat(boat) + " boatId=" + SafeBoatId(boat) + " | source: portal=" + FormatPosition(sourceInteractionPosition) + " | native: started=False succeeded=False";
		}

		public static string Event(Player player, BoatTransitionKind transition, string outcome, Ship? boat = null, Vector3? position = null)
		{
			return $"boat-transition outcome={outcome} transition={transition} | identity: player={SafePlayer(player)}" + (((Object)(object)boat == (Object)null) ? string.Empty : (" boat=" + SafeBoat(boat) + " boatId=" + SafeBoatId(boat))) + (position.HasValue ? (" | position: " + FormatPosition(position)) : string.Empty);
		}

		public static string Summary(BoatTransport.PendingBoatTransport pending, string outcome, Player? player)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d5: Unknown result type (might be due to invalid IL or missing references)
			return $"boat-transition attempt={pending.Attempt.AttemptId} phase=summary outcome={outcome} transition={pending.Attempt.Transition}" + $" | identity: player={SafePlayer(player)} boat={pending.PrefabName} boatId={pending.BoatId}" + " | route: sourcePortal=" + FormatPosition(pending.SourcePortal) + " playerSource=" + FormatPosition(pending.PlayerSourcePosition) + " nativeTarget=" + FormatPosition(pending.NativeTarget) + " arrival=" + FormatPosition(pending.Arrival) + " landing=" + FormatPosition(pending.Landing) + $" | authority: nativeStarted={pending.Lifecycle.NativeStarted} nativeSucceeded={pending.Lifecycle.NativeSucceeded} sourceOwnedBeforeClaim={pending.SourceOwnedBeforeClaim} sourceOwnedAfterClaim={pending.SourceOwnedAfterClaim} arrivalOwned={pending.ArrivalOwnership}" + $" | placement: water={pending.HasWater} tries={pending.PlacementTries} wideFanTries={pending.PlacementProbe.WideFanTries} sourceWaterlineOffset={FormatHeight(pending.SourceWaterlineOffset)} landingWaterSurface={FormatHeight(pending.LandingWaterSurface)} rejectedWater={pending.PlacementProbe.NoWater} waterNoGround={pending.PlacementProbe.WaterNoGround} waterNoVolume={pending.PlacementProbe.WaterNoVolume} waterOutsideCollider={pending.PlacementProbe.WaterOutsideCollider} waterGroundTooHigh={pending.PlacementProbe.WaterGroundTooHigh} waterSurfaceTooFar={pending.PlacementProbe.WaterSurfaceTooFar} waterInvalidSurface={pending.PlacementProbe.WaterInvalidSurface} rejectedBoatFootprint={pending.PlacementProbe.BoatFootprint} rejectedBoatCollider={pending.PlacementProbe.BoatCollider} rejectedSolid={pending.PlacementProbe.Solid} firstBlocker={pending.PlacementProbe.FirstBlockerKind}:{pending.PlacementProbe.FirstBlockerName} blockerAt={FormatPosition(pending.PlacementProbe.FirstBlockerPosition)} blockedPose={FormatPosition(pending.PlacementProbe.FirstBlockedPose)} blockedYaw={FormatAngle(pending.PlacementProbe.FirstBlockedYaw)} boardingAnchors={pending.BoardingAnchorCount} boardingAnchorSource={pending.BoardingAnchorSource} arrivalYaw={FormatAngle(pending.ArrivalYaw)} landingYaw={FormatAngle(pending.LandingYaw)} movePath={pending.MovePath} beforeTransform={FormatPosition(pending.SourcePosition)} beforeZdo={FormatPosition(pending.SourceZdoPosition)}" + $" | verification: writtenTransform={FormatPosition(pending.WrittenTransform)} writtenZdo={FormatPosition(pending.WrittenZdo)} writeVerified={pending.WriteVerified} observedTransform={FormatPosition(pending.ObservedTransform)} observedZdo={FormatPosition(pending.ObservedZdo)} visibilityCheckpoints={pending.ObservationCount} destinationInstanceVisibleAtFollowup={FormatVisibility(pending.DestinationInstanceVisibleAtFollowup)} destinationZdoAtTargetAtFollowup={FormatVisibility(pending.DestinationZdoAtTargetAtFollowup)}" + " | followupPhysics: owner=" + FormatVisibility(pending.FollowupBodyOwner) + " body=" + FormatVisibility(pending.FollowupBodyPresent) + " isKinematic=" + FormatVisibility(pending.FollowupBodyIsKinematic) + " sleepingBeforeWake=" + FormatVisibility(pending.FollowupBodyWasSleeping) + " wakeUpRequested=" + FormatVisibility(pending.FollowupWakeUpRequested) + " sleepingAfterWake=" + FormatVisibility(pending.FollowupBodyIsSleepingAfterWake) + " | timing: firstDestinationInstanceVisibleAfterWrite=" + FormatElapsed(pending.BoatWriteAt, pending.FirstDestinationInstanceVisibleAt) + " firstDestinationZdoAtTargetAfterWrite=" + FormatElapsed(pending.BoatWriteAt, pending.FirstDestinationZdoAtTargetAt) + " nativeResultToArrival=" + FormatElapsed(pending.NativeResultAt, pending.ArrivalAt) + " arrivalToBoatWrite=" + FormatElapsed(pending.ArrivalAt, pending.BoatWriteAt) + " arrivalToDestinationInstanceVisible=" + FormatElapsed(pending.ArrivalAt, pending.FirstDestinationInstanceVisibleAt) + " visibilityRole=local-observer playerRole=" + ObserverRole() + " displacement=" + FormatDistance(pending.SourcePosition, pending.MovedTo);
		}

		public static string BoatDestination(BoatTransport.PendingBoatTransport pending)
		{
			return $"Boat observed at destination: prefab={pending.PrefabName} landing={FormatPosition(pending.MovedTo)} tries={pending.PlacementTries} transition={pending.Attempt.Transition} attempt={pending.Attempt.AttemptId}";
		}

		public static string Checkpoint(BoatTransport.PendingBoatTransport pending, string phase, Player player, Ship? ship, ZDO? record)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			return "boat-transition attempt=" + pending.Attempt.AttemptId + " phase=" + phase + " observerRole=" + ObserverRole() + $" | identity: player={SafePlayer(player)} boat={pending.PrefabName} boatId={pending.BoatId}" + " | timing: sinceNativeResult=" + FormatElapsed(pending.NativeResultAt, Time.time) + " sinceArrival=" + FormatElapsed(pending.ArrivalAt, Time.time) + " sinceWrite=" + FormatElapsed(pending.BoatWriteAt, Time.time) + $" | visibility: boatInstancePresent={(Object)(object)ship != (Object)null} boatZdoPresent={record != null} destinationInstanceVisible={pending.LastDestinationInstanceVisible} destinationZdoAtTarget={pending.LastDestinationZdoAtTarget}" + " | observed: boatTransform=" + FormatPosition(((Object)(object)ship != (Object)null) ? new Vector3?(((Component)ship).transform.position) : ((Vector3?)null)) + " boatZdo=" + FormatPosition((record != null) ? new Vector3?(record.GetPosition()) : ((Vector3?)null));
		}

		private static string ObserverRole()
		{
			if (!((Object)(object)ZNet.instance == (Object)null))
			{
				if (!ZNet.instance.IsServer())
				{
					return "client";
				}
				return "host-or-server";
			}
			return "unknown";
		}

		private static string FormatAngle(float angle)
		{
			return angle.ToString("0.#", CultureInfo.InvariantCulture);
		}

		private static string FormatAngle(float? angle)
		{
			if (!angle.HasValue)
			{
				return "<none>";
			}
			return FormatAngle(angle.Value);
		}

		private static string FormatHeight(float? height)
		{
			if (!height.HasValue)
			{
				return "<unavailable>";
			}
			return height.Value.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string FormatElapsed(float? start, float? end)
		{
			if (!start.HasValue || !end.HasValue)
			{
				return "<unavailable>";
			}
			return Math.Max(0f, end.Value - start.Value).ToString("0.000", CultureInfo.InvariantCulture) + "s";
		}

		private static string FormatElapsed(float? start, float now)
		{
			if (!start.HasValue)
			{
				return "<unavailable>";
			}
			return Math.Max(0f, now - start.Value).ToString("0.000", CultureInfo.InvariantCulture) + "s";
		}

		private static string FormatVisibility(bool? visible)
		{
			if (!visible.HasValue)
			{
				return "<not-checked>";
			}
			return visible.Value.ToString();
		}

		private static string SafePlayer(Player? player)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return "<unknown> playerId=<unknown>";
			}
			try
			{
				return $"{player.GetPlayerName()} playerId={((Character)player).GetZDOID()}";
			}
			catch
			{
				return "<unknown> playerId=<unknown>";
			}
		}

		private static string SafeBoat(Ship boat)
		{
			try
			{
				ZNetView nview = boat.m_nview;
				return ((nview != null) ? nview.GetPrefabName() : null) ?? "<unknown>";
			}
			catch
			{
				return "<unknown>";
			}
		}

		private static string SafeBoatId(Ship boat)
		{
			try
			{
				ZNetView nview = boat.m_nview;
				return ((nview == null) ? null : ((object)Unsafe.As<ZDOID, ZDOID>(ref nview.GetZDO()?.m_uid)/*cast due to .constrained prefix*/).ToString()) ?? "<unknown>";
			}
			catch
			{
				return "<unknown>";
			}
		}

		private static string FormatPosition(Vector3? position)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (!position.HasValue)
			{
				return "<none>";
			}
			return FormatPosition(position.Value);
		}

		private static string FormatPosition(Vector3 position)
		{
			return "(x=" + position.x.ToString("0.##", CultureInfo.InvariantCulture) + ", y=" + position.y.ToString("0.##", CultureInfo.InvariantCulture) + ", z=" + position.z.ToString("0.##", CultureInfo.InvariantCulture) + ")";
		}

		private static string FormatDistance(Vector3 from, Vector3? to)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (!to.HasValue)
			{
				return "0";
			}
			return Vector3.Distance(from, to.Value).ToString("0.##", CultureInfo.InvariantCulture);
		}
	}
	internal static class BoatShortcutInput
	{
		private enum TargetPortalPrefixResult
		{
			Unavailable,
			PickerOpened,
			AllowVanilla
		}

		private enum ShortcutTargetKind
		{
			None,
			Portal,
			Dungeon
		}

		private readonly struct ShortcutContext
		{
			public ShortcutTargetKind Kind { get; }

			public Player? Player { get; }

			public Ship? Boat { get; }

			public TeleportWorld? Portal { get; }

			public Teleport? Dungeon { get; }

			public bool Eligible
			{
				get
				{
					if (Kind != ShortcutTargetKind.None && (Object)(object)Player != (Object)null)
					{
						return (Object)(object)Boat != (Object)null;
					}
					return false;
				}
			}

			public ShortcutContext(ShortcutTargetKind kind, Player? player, Ship? boat, TeleportWorld? portal, Teleport? dungeon)
			{
				Kind = kind;
				Player = player;
				Boat = boat;
				Portal = portal;
				Dungeon = dungeon;
			}
		}

		private static readonly ShortcutConsumptionState Consumption = new ShortcutConsumptionState();

		private static readonly ShortcutPressGate PressGate = new ShortcutPressGate();

		private static int _armedFrame = -1;

		private static int _executedFrame = -1;

		private static ShortcutContext _armedContext;

		private static string? _targetPortalAttemptId;

		private static FieldInfo? _targetPortalTeleportingField;

		public static bool TrySuppress(string buttonName, ButtonQueryKind kind)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: 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_00bc: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut teleportBoatHotkey = TeleportBoatsPlugin.TeleportBoatHotkey;
			if ((int)((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey == 0)
			{
				Consumption.Reset();
				PressGate.Reset();
				return false;
			}
			int frameCount = Time.frameCount;
			bool keyDown = Input.GetKeyDown(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey);
			bool key = Input.GetKey(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey);
			bool keyUp = Input.GetKeyUp(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey);
			bool flag = kind == ButtonQueryKind.Down && keyDown;
			bool flag2 = Consumption.RequiresInspectionForFrame(frameCount);
			bool isConsuming = Consumption.IsConsuming;
			if (!flag && !isConsuming && !flag2)
			{
				return false;
			}
			if (!ShortcutInputWorkGate.ShouldInspectBindings(flag && !isConsuming && CanStartShortcut(), flag, isConsuming, flag2))
			{
				return false;
			}
			if (!keyDown && !key && !keyUp && isConsuming)
			{
				Consumption.ShouldSuppress(new ShortcutButtonQuery(buttonName, ((object)((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey/*cast due to .constrained prefix*/).ToString(), kind, frameCount, contextEligible: false, actionMatchesShortcut: false, mainKeyPressed: false, mainKeyHeld: false, mainKeyReleased: false));
				if (!Consumption.RequiresInspectionForFrame(frameCount))
				{
					return false;
				}
			}
			IReadOnlyList<string> effectiveControlPaths = ZInputShortcutBindings.GetEffectiveControlPaths(GetButtonDef, buttonName);
			IReadOnlyList<string> activeControlPaths = ZInputShortcutBindings.GetActiveControlPaths(GetButtonDef, buttonName);
			bool actionMatchesShortcut = ShortcutBindingMatcher.MatchesKeyboardShortcut(effectiveControlPaths, activeControlPaths, ((object)((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey/*cast due to .constrained prefix*/).ToString(), keyDown || key || keyUp, (kind == ButtonQueryKind.Down) ? ModifiersHeld(teleportBoatHotkey) : NoModifiersRequired(), kind != ButtonQueryKind.Down);
			ShortcutContext context = ((kind == ButtonQueryKind.Down && ShortcutInputWorkGate.ShouldCaptureContext(actionMatchesShortcut, keyDown, Consumption.IsConsuming)) ? GetContext(frameCount) : default(ShortcutContext));
			bool num = Consumption.ShouldSuppress(new ShortcutButtonQuery(buttonName, ((object)((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey/*cast due to .constrained prefix*/).ToString(), kind, frameCount, context.Eligible, actionMatchesShortcut, keyDown, key, keyUp));
			if (num && kind == ButtonQueryKind.Down && PressGate.ShouldStart(keyDown, key) && context.Eligible && _armedFrame != frameCount && _executedFrame != frameCount)
			{
				Arm(frameCount, context);
			}
			return num;
		}

		public static void ProcessPlayerUpdate(Player player)
		{
			//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_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			if (_targetPortalAttemptId != null && !IsTargetPortalMapTeleporting())
			{
				BoatTransport.ClearUncompletedAttempt(_targetPortalAttemptId);
				_targetPortalAttemptId = null;
			}
			int frameCount = Time.frameCount;
			KeyboardShortcut teleportBoatHotkey = TeleportBoatsPlugin.TeleportBoatHotkey;
			if ((int)((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey == 0 || !Input.GetKey(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey))
			{
				PressGate.Reset();
			}
			if (_armedFrame != frameCount && _executedFrame != frameCount && IsRawShortcutDown(teleportBoatHotkey) && PressGate.ShouldStart(Input.GetKeyDown(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey), Input.GetKey(((KeyboardShortcut)(ref teleportBoatHotkey)).MainKey)))
			{
				ShortcutContext context = GetContext(frameCount);
				if (context.Eligible)
				{
					Arm(frameCount, context);
				}
			}
			ExecuteArmed(player);
		}

		private static void ExecuteArmed(Player player)
		{
			int frameCount = Time.frameCount;
			if (_armedFrame != frameCount || (Object)(object)_armedContext.Player != (Object)(object)player || _executedFrame == frameCount)
			{
				return;
			}
			ShortcutContext armedContext = _armedContext;
			_armedFrame = -1;
			_armedContext = default(ShortcutContext);
			_executedFrame = frameCount;
			if (armedContext.Eligible && !((Object)(object)armedContext.Boat == (Object)null))
			{
				if (armedContext.Kind == ShortcutTargetKind.Portal && (Object)(object)armedContext.Portal != (Object)null)
				{
					ExecutePortal(player, armedContext.Boat, armedContext.Portal);
				}
				else if (armedContext.Kind == ShortcutTargetKind.Dungeon && (Object)(object)armedContext.Dungeon != (Object)null)
				{
					ExecuteDungeon(player, armedContext.Boat, armedContext.Dungeon);
				}
			}
		}

		private static void ExecutePortal(Player player, Ship boat, TeleportWorld portal)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			if (!BoatTransport.IsCurrentShortcutTarget(player, boat, portal, null, BoatTransitionKind.Portal))
			{
				return;
			}
			if (!BoatTransport.BoatIsTeleportable(boat, portal.m_allowAllItems))
			{
				BoatTransport.RejectAttempt(player, boat, BoatTransitionKind.Portal, ((Component)portal).transform.position, "hotkey-blocked-cargo");
				TeleportBoatsPlugin.LogDiagnostic(BoatTransportDiagnostics.Event(player, BoatTransitionKind.Portal, "hotkey-blocked-cargo", boat));
			}
			else if (!TeleportBoatsPlugin.Policy.IsTransitionEnabled(BoatTransitionKind.Portal))
			{
				BoatTransport.RejectAttempt(player, boat, BoatTransitionKind.Portal, ((Component)portal).transform.position, "hotkey-skipped-policy");
				TeleportBoatsPlugin.LogDiagnostic(BoatTransportDiagnostics.Event(player, BoatTransitionKind.Portal, "hotkey-skipped-policy", boat));
			}
			else if (BoatTransport.IsWorldTeleportBlocked())
			{
				BoatTransport.RejectAttempt(player, boat, BoatTransitionKind.Portal, ((Component)portal).transform.position, "hotkey-blocked-world");
				ShowCenterMessage("$tboat_blocked_world");
			}
			else if (IsTargetPortalMapTeleporting())
			{
				if (BoatTransport.IsCurrentShortcutTarget(player, boat, portal, null, BoatTransitionKind.Portal))
				{
					_targetPortalAttemptId = BoatTransport.BeginExplicitAttempt(player, boat, BoatTransitionKind.Portal, ((Component)portal).transform.position);
				}
			}
			else
			{
				if (!BoatTransport.IsCurrentShortcutTarget(player, boat, portal, null, BoatTransitionKind.Portal))
				{
					return;
				}
				string text = BoatTransport.BeginExplicitAttempt(player, boat, BoatTransitionKind.Portal, ((Component)portal).transform.position);
				if (InvokeTargetPortalPrefix(player, portal) != TargetPortalPrefixResult.PickerOpened)
				{
					try
					{
						portal.Teleport(player);
						return;
					}
					finally
					{
						BoatTransport.ClearUncompletedAttempt(text);
					}
				}
				_targetPortalAttemptId = text;
			}
		}

		private static void ExecuteDungeon(Player player, Ship boat, Teleport dungeon)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			if (!BoatTransport.IsCurrentShortcutTarget(player, boat, null, dungeon, BoatTransitionKind.Dungeon))
			{
				return;
			}
			if (!TeleportBoatsPlugin.Policy.IsTransitionEnabled(BoatTransitionKind.Dungeon))
			{
				BoatTransport.RejectAttempt(player, boat, BoatTransitionKind.Dungeon, ((Component)dungeon.m_targetPoint).transform.position, "hotkey-skipped-policy");
				TeleportBoatsPlugin.LogDiagnostic(BoatTransportDiagnostics.Event(player, BoatTransitionKind.Dungeon, "hotkey-skipped-policy", boat));
			}
			else if (BoatTransport.IsWorldTeleportBlocked())
			{
				BoatTransport.RejectAttempt(player, boat, BoatTransitionKind.Dungeon, ((Component)dungeon.m_targetPoint).transform.position, "hotkey-blocked-world");
				ShowCenterMessage("$tboat_blocked_world");
			}
			else if (BoatTransport.IsCurrentShortcutTarget(player, boat, null, dungeon, BoatTransitionKind.Dungeon))
			{
				string attemptId = BoatTransport.BeginExplicitAttempt(player, boat, BoatTransitionKind.Dungeon, ((Component)dungeon.m_targetPoint).transform.position);
				try
				{
					dungeon.Interact((Humanoid)(object)player, false, false);
				}
				finally
				{
					BoatTransport.ClearUncompletedAttempt(attemptId);
				}
			}
		}

		private static ShortcutContext GetContext(int frame)
		{
			return CaptureContext();
		}

		private static ShortcutContext CaptureContext()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || !TeleportBoatsPlugin.IsEnabled || !TeleportBoatsPlugin.IsHotkeyConfigured)
			{
				return default(ShortcutContext);
			}
			BoatTransitionKind kind;
			bool flag = BoatTransport.TryGetHotkeyTransition(localPlayer, out kind);
			bool transitionEnabled = flag && TeleportBoatsPlugin.Policy.IsTransitionEnabled(kind);
			if (!ShortcutInputWorkGate.ShouldDiscoverTransport(playerAlive: true, moduleEnabled: true, hotkeyConfigured: true, transitionEnabled, flag, uiAllowsInput: true) || IsUiBlocking())
			{
				return default(ShortcutContext);
			}
			if (!BoatTransport.TryGetNearestBoat(localPlayer, TeleportBoatsPlugin.Policy, out Ship boat))
			{
				return default(ShortcutContext);
			}
			GameObject hovering = localPlayer.m_hovering;
			if ((Object)(object)hovering == (Object)null)
			{
				return default(ShortcutContext);
			}
			TeleportWorld componentInParent = hovering.GetComponentInParent<TeleportWorld>();
			if ((Object)(object)componentInParent != (Object)null && componentInParent.TargetFound())
			{
				return new ShortcutContext(ShortcutTargetKind.Portal, localPlayer, boat, componentInParent, null);
			}
			Teleport componentInParent2 = hovering.GetComponentInParent<Teleport>();
			if (!((Object)(object)componentInParent2 != (Object)null) || !((Object)(object)componentInParent2.m_targetPoint != (Object)null))
			{
				return default(ShortcutContext);
			}
			return new ShortcutContext(ShortcutTargetKind.Dungeon, localPlayer, boat, null, componentInParent2);
		}

		private static bool CanStartShortcut()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || !TeleportBoatsPlugin.IsEnabled || !TeleportBoatsPlugin.IsHotkeyConfigured)
			{
				return false;
			}
			if (!BoatTransport.TryGetHotkeyTransition(localPlayer, out var kind))
			{
				return false;
			}
			bool transitionEnabled = TeleportBoatsPlugin.Policy.IsTransitionEnabled(kind);
			if (ShortcutInputWorkGate.ShouldDiscoverTransport(playerAlive: true, moduleEnabled: true, hotkeyConfigured: true, transitionEnabled, usableHoverTarget: true, uiAllowsInput: true))
			{
				return !IsUiBlocking();
			}
			return false;
		}

		private static object? GetButtonDef(string buttonName)
		{
			if (ZInput.instance == null)
			{
				return null;
			}
			return ZInput.instance.GetButtonDef(buttonName);
		}

		private static bool IsTargetPortalMapTeleporting()
		{
			if ((object)_targetPortalTeleportingField == null)
			{
				_targetPortalTeleportingField = FindTargetPortalTeleportingField();
			}
			object obj = _targetPortalTeleportingField?.GetValue(null);
			if (obj is bool)
			{
				return (bool)obj;
			}
			return false;
		}

		private static TargetPortalPrefixResult InvokeTargetPortalPrefix(Player player, TeleportWorld portal)
		{
			if (!Chainloader.PluginInfos.ContainsKey("org.bepinex.plugins.targetportal"))
			{
				return TargetPortalPrefixResult.Unavailable;
			}
			Type? type = FindTargetPortalMapType();
			MethodInfo methodInfo = FindTargetPortalPrefix(type);
			if (type == null || methodInfo == null || !TryGetTargetPortalTriggerContext(player, portal, out TeleportWorldTrigger trigger, out Collider playerCollider))
			{
				return TargetPortalPrefixResult.Unavailable;
			}
			try
			{
				object obj = methodInfo.Invoke(null, new object[2] { trigger, playerCollider });
				if (obj is bool && (bool)obj)
				{
					return TargetPortalPrefixResult.AllowVanilla;
				}
				return IsTargetPortalMapTeleporting() ? TargetPortalPrefixResult.PickerOpened : TargetPortalPrefixResult.Unavailable;
			}
			catch (TargetInvocationException ex)
			{
				TeleportBoatsPlugin.Log.LogWarning((object)$"Could not open TargetPortal's picker from the boat hotkey: {ex.InnerException ?? ex}");
				return IsTargetPortalMapTeleporting() ? TargetPortalPrefixResult.PickerOpened : TargetPortalPrefixResult.Unavailable;
			}
		}

		private static Type? FindTargetPortalMapType()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				Type type = assemblies[i].GetType("TargetPortal.Map", throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private static MethodInfo? FindTargetPortalPrefix(Type? mapType)
		{
			if (mapType == null)
			{
				return null;
			}
			Type[] nestedTypes = mapType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
			foreach (Type type in nestedTypes)
			{
				if (!(type.Name != "OpenMapOnPortalEnter"))
				{
					MethodInfo method = type.GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					ParameterInfo[] array = method?.GetParameters();
					if (method?.ReturnType == typeof(bool) && array != null && array.Length == 2 && array[0].ParameterType == typeof(TeleportWorldTrigger) && array[1].ParameterType == typeof(Collider))
					{
						return method;
					}
				}
			}
			return null;
		}

		private static bool TryGetTargetPortalTriggerContext(Player player, TeleportWorld portal, out TeleportWorldTrigger? trigger, out Collider? playerCollider)
		{
			trigger = null;
			playerCollider = null;
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || (Object)(object)portal == (Object)null || !((Component)portal).gameObject.activeInHierarchy)
			{
				return false;
			}
			Collider[] componentsInChildren = ((Component)player).GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && val.enabled && ((Component)val).gameObject.activeInHierarchy && (Object)(object)((Component)val).GetComponent<Player>() == (Object)(object)player)
				{
					playerCollider = val;
					break;
				}
			}
			if ((Object)(object)playerCollider == (Object)null)
			{
				return false;
			}
			TeleportWorldTrigger[] componentsInChildren2 = ((Component)portal).GetComponentsInChildren<TeleportWorldTrigger>(true);
			foreach (TeleportWorldTrigger val2 in componentsInChildren2)
			{
				Collider val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<Collider>() : null);
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null) && val3.enabled && val3.isTrigger && ((Component)val2).gameObject.activeInHierarchy && !((Object)(object)((Component)val2).transform.parent == (Object)null) && !((Object)(object)((Component)val2).GetComponentInParent<TeleportWorld>() != (Object)(object)portal))
				{
					trigger = val2;
					return true;
				}
			}
			return false;
		}

		private static FieldInfo? FindTargetPortalTeleportingField()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				FieldInfo fieldInfo = assemblies[i].GetType("TargetPortal.Map", throwOnError: false)?.GetField("Teleporting", BindingFlags.Static | BindingFlags.Public);
				if (fieldInfo?.FieldType == typeof(bool))
				{
					return fieldInfo;
				}
			}
			return null;
		}

		private static IEnumerable<bool> ModifiersHeld(KeyboardShortcut shortcut)
		{
			//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)
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				yield return Input.GetKey(modifier);
			}
		}

		private static IEnumerable<bool> NoModifiersRequired()
		{
			yield break;
		}

		private static bool IsRawShortcutDown(KeyboardShortcut shortcut)
		{
			//IL_0002: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!Input.GetKey(modifier))
				{
					return false;
				}
			}
			return true;
		}

		private static void Arm(int frame, ShortcutContext context)
		{
			_armedFrame = frame;
			_armedContext = context;
		}

		internal static bool IsUiBlocking()
		{
			if (!WrapperUiInput.IsBlocking && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Menu.IsVisible() && !SingletonBool("InventoryGui", "instance", "IsVisible") && !SingletonBool("Console", "instance", "IsVisible") && !SingletonBool("TextInput", "instance", "IsVisible") && !SingletonBool("TextInput", "instance", "HasFocus"))
			{
				return SingletonBool("Odinplus.Shared.YamlEditor.YamlEditorRegistry", null, "IsOpen");
			}
			return true;
		}

		private static bool SingletonBool(string typeName, string? instanceName, string memberName)
		{
			Type type = typeof(Player).Assembly.GetType(typeName) ?? typeof(ShortcutConsumptionState).Assembly.GetType(typeName);
			if (type == null)
			{
				return false;
			}
			object obj = null;
			if (instanceName != null)
			{
				obj = type.GetProperty(instanceName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) ?? type.GetField(instanceName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
			}
			MethodInfo method = type.GetMethod(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method?.ReturnType == typeof(bool))
			{
				return (bool)method.Invoke(obj, null);
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property?.PropertyType == typeof(bool))
			{
				return (bool)property.GetValue(obj);
			}
			FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field?.FieldType == typeof(bool))
			{
				return (bool)field.GetValue(obj);
			}
			return false;
		}

		private static void ShowCenterMessage(string key)
		{
			if ((Object)(object)MessageHud.instance != (Object)null && Localization.instance != null)
			{
				MessageHud.instance.ShowMessage((MessageType)2, Localization.instance.Localize(key), 0, (Sprite)null, false, true);
			}
		}
	}
	internal static class BoatTransport
	{
		private readonly struct BoatHoverContextKey : IEquatable<BoatHoverContextKey>
		{
			private readonly Player _player;

			private readonly object? _session;

			private readonly object _hover;

			private readonly BoatTransitionKind _transition;

			private readonly EffectiveBoatPolicy _policy;

			public BoatHoverContextKey(Player player, object? session, object hover, BoatTransitionKind transition, EffectiveBoatPolicy policy)
			{
				_player = player;
				_session = session;
				_hover = hover;
				_transition = transition;
				_policy = policy;
			}

			public bool Equals(BoatHoverContextKey other)
			{
				if (_player == other._player && _session == other._session && _hover == other._hover && _transition == other._transition)
				{
					return _policy == other._policy;
				}
				return false;
			}
		}

		internal enum WaterRejection
		{
			None,
			NoGround,
			NoVolume,
			InvalidSurface,
			OutsideCollider,
			GroundTooHigh,
			SurfaceTooFar
		}

		internal sealed class BoatPlacementProbe
		{
			public int NoWater { get; set; }

			public int WaterNoGround { get; private set; }

			public int WaterNoVolume { get; private set; }

			public int WaterInvalidSurface { get; private set; }

			public int WaterOutsideCollider { get; private set; }

			public int WaterGroundTooHigh { get; private set; }

			public int WaterSurfaceTooFar { get; private set; }

			pub