Decompiled source of Keel v0.1.15

plugins/Keel/Keel.Core.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using ExtraPotions.Keel.Catalog;
using ExtraPotions.Keel.Config;
using ExtraPotions.Keel.Conversion;
using ExtraPotions.Keel.Localization;
using ExtraPotions.Keel.Tuning;
using Microsoft.CodeAnalysis;

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

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ExtraPotions.Keel
{
	public static class KeelCore
	{
		public const string PluginGuid = "com.meadworks.keel";

		public const string ProductName = "Keel";

		public const string ProductVersion = "0.1.15";

		public const string Family = "Waystone";

		public const string TokenNamespace = "keel";

		public const string TokenPrefix = "meadworks_keel_";

		public const string ZdoPrefix = "keel.";

		public const string SupportedGameVersion = "1.0.17";
	}
}
namespace ExtraPotions.Keel.Tuning
{
	public static class PendingShipValues
	{
		public static string Encode(IEnumerable<(string Prefab, ShipField Field, string Value)> values)
		{
			if (values == null)
			{
				throw new ArgumentNullException("values");
			}
			return string.Join(";", values.Select<(string, ShipField, string), string>(((string Prefab, ShipField Field, string Value) v) => ShipFields.Key(v.Prefab, v.Field) + "=" + (v.Value ?? "").Trim()));
		}

		public static int Load(string? text, ShipRegistry registry)
		{
			if (registry == null)
			{
				throw new ArgumentNullException("registry");
			}
			int num = 0;
			string[] array = (text ?? "").Split(';');
			foreach (string text2 in array)
			{
				int num2 = text2.IndexOf('=');
				if (num2 <= 0)
				{
					continue;
				}
				string text3 = text2.Substring(0, num2).Trim();
				int num3 = text3.LastIndexOf('.');
				if (num3 > 0)
				{
					string prefab = text3.Substring(0, num3);
					string text4 = text3.Substring(num3 + 1);
					if (Enum.TryParse<ShipField>(text4, ignoreCase: false, out var result) && !(result.ToString() != text4))
					{
						registry.AddPending(prefab, result, text2.Substring(num2 + 1).Trim());
						num++;
					}
				}
			}
			return num;
		}
	}
	public enum ShipField
	{
		Health,
		SailForce,
		SailForceOffset,
		PaddleForce,
		RudderSpeed,
		TurnForceSailing,
		TurnForcePaddling,
		ForwardDrag,
		SidewaysDrag,
		AngularDamping,
		WaterImpactDamage,
		UpsideDownDamage,
		WeatherWear,
		DamageTaken,
		BuildCost
	}
	public sealed class ShipFieldSpec
	{
		private readonly Func<ShipDefaults, float> defaultFor;

		private readonly Func<ShipDefaults, float> minFor;

		private readonly Func<ShipDefaults, float> maxFor;

		public ShipField Field { get; }

		public string Key => Field.ToString();

		public bool IsToggle { get; }

		public string Description { get; }

		internal ShipFieldSpec(ShipField field, bool toggle, string description, Func<ShipDefaults, float> defaultFor, Func<ShipDefaults, float> minFor, Func<ShipDefaults, float> maxFor)
		{
			Field = field;
			IsToggle = toggle;
			Description = description;
			this.defaultFor = defaultFor;
			this.minFor = minFor;
			this.maxFor = maxFor;
		}

		public float DefaultFor(ShipDefaults defaults)
		{
			return defaultFor(defaults ?? throw new ArgumentNullException("defaults"));
		}

		public float MinimumFor(ShipDefaults defaults)
		{
			return minFor(defaults ?? throw new ArgumentNullException("defaults"));
		}

		public float MaximumFor(ShipDefaults defaults)
		{
			return maxFor(defaults ?? throw new ArgumentNullException("defaults"));
		}

		public float Normalize(ShipDefaults defaults, float value)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return DefaultFor(defaults);
			}
			return Math.Min(MaximumFor(defaults), Math.Max(MinimumFor(defaults), value));
		}
	}
	public static class ShipFields
	{
		public const string Section = "Ships";

		public const float RelativeRange = 10f;

		private static readonly ShipFieldSpec[] all = new ShipFieldSpec[15]
		{
			Absolute(ShipField.Health, (ShipDefaults d) => d.Health, 1f, 100000f, "Hull points of this ship."),
			Relative(ShipField.SailForce, (ShipDefaults d) => d.SailForce, "Thrust from the sail: top speed under sail, scaled by wind and sail size."),
			Absolute(ShipField.SailForceOffset, (ShipDefaults d) => d.SailForceOffset, -5f, 10f, "Height in meters above the center of mass where the sail pushes. Lower heels less and capsizes less at high sail force."),
			Relative(ShipField.PaddleForce, (ShipDefaults d) => d.PaddleForce, "Force at the oars, forward and backward."),
			Relative(ShipField.RudderSpeed, (ShipDefaults d) => d.RudderSpeed, "How fast the rudder swings to full lock while steering."),
			Relative(ShipField.TurnForceSailing, (ShipDefaults d) => d.TurnForceSailing, "Turning force under sail; grows with forward speed."),
			Relative(ShipField.TurnForcePaddling, (ShipDefaults d) => d.TurnForcePaddling, "Turning force at the oars."),
			Relative(ShipField.ForwardDrag, (ShipDefaults d) => d.ForwardDrag, "Forward water drag. Lower is faster and coasts longer; it caps the top speed."),
			Relative(ShipField.SidewaysDrag, (ShipDefaults d) => d.SidewaysDrag, "Sideways water drag. Lower lets the ship slide in a crosswind. Tune with care."),
			Relative(ShipField.AngularDamping, (ShipDefaults d) => d.AngularDamping, "Resistance to rolling and pitching. Higher is a steadier deck; too high and the ship stops turning."),
			Absolute(ShipField.WaterImpactDamage, (ShipDefaults d) => d.WaterImpactDamage, 0f, 1000f, "Damage from slamming into rough seas with players aboard. 0 turns it off."),
			Absolute(ShipField.UpsideDownDamage, (ShipDefaults d) => d.UpsideDownDamage, 0f, 1000f, "Damage per second while capsized. 0 turns it off."),
			new ShipFieldSpec(ShipField.WeatherWear, toggle: true, "On: the ship loses health in rain and with its hull under water, down to half health. Off by default (user decision 2026-10-07).", (ShipDefaults d) => 0f, (ShipDefaults d) => 0f, (ShipDefaults d) => 1f),
			Absolute(ShipField.DamageTaken, (ShipDefaults d) => 1f, 0f, 10f, "Factor on every hit this ship takes. 1 is vanilla, 0 means no damage."),
			Absolute(ShipField.BuildCost, (ShipDefaults d) => 1f, 0.1f, 10f, "Factor on the materials this ship costs. Each material is rounded and at least 1.")
		};

		public static IReadOnlyList<ShipFieldSpec> All { get; } = Array.AsReadOnly(all);

		public static ShipFieldSpec Get(ShipField field)
		{
			return all[(int)field];
		}

		public static IEnumerable<ShipFieldSpec> For(ShipDefaults defaults)
		{
			if (defaults == null)
			{
				throw new ArgumentNullException("defaults");
			}
			return all.Where((ShipFieldSpec f) => f.Field != ShipField.BuildCost || defaults.BuildCosts != null);
		}

		public static string Key(string prefab, ShipField field)
		{
			if (string.IsNullOrWhiteSpace(prefab) || prefab.Trim() != prefab)
			{
				throw new ArgumentException("Expected a trimmed prefab name.", "prefab");
			}
			return prefab + "." + field;
		}

		private static ShipFieldSpec Absolute(ShipField field, Func<ShipDefaults, float> value, float min, float max, string description)
		{
			return new ShipFieldSpec(field, toggle: false, description, value, (ShipDefaults d) => min, (ShipDefaults d) => max);
		}

		private static ShipFieldSpec Relative(ShipField field, Func<ShipDefaults, float> value, string description)
		{
			return new ShipFieldSpec(field, toggle: false, description + " Range 0 to 10 times this ship's vanilla value.", value, (ShipDefaults d) => 0f, (ShipDefaults d) => (!(value(d) > 0f)) ? 1f : (value(d) * 10f));
		}
	}
	public sealed class ShipRegistry
	{
		private readonly Dictionary<string, ShipDefaults> ships = new Dictionary<string, ShipDefaults>(StringComparer.Ordinal);

		private readonly List<string> order = new List<string>();

		private readonly Dictionary<string, Dictionary<ShipField, string>> pending = new Dictionary<string, Dictionary<ShipField, string>>(StringComparer.Ordinal);

		public IReadOnlyList<string> Names => order;

		public bool Register(string prefab, ShipDefaults defaults)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				throw new ArgumentException("Expected a prefab name.", "prefab");
			}
			if (defaults == null)
			{
				throw new ArgumentNullException("defaults");
			}
			if (ships.ContainsKey(prefab))
			{
				return false;
			}
			ships.Add(prefab, defaults);
			order.Add(prefab);
			return true;
		}

		public bool TryGet(string prefab, out ShipDefaults defaults)
		{
			return ships.TryGetValue(prefab ?? "", out defaults);
		}

		public void AddPending(string prefab, ShipField field, string value)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				throw new ArgumentException("Expected a prefab name.", "prefab");
			}
			if (!pending.TryGetValue(prefab, out Dictionary<ShipField, string> value2))
			{
				value2 = (pending[prefab] = new Dictionary<ShipField, string>());
			}
			value2[field] = value ?? "";
		}

		public IEnumerable<(string Prefab, ShipField Field, string Value)> Pending()
		{
			foreach (KeyValuePair<string, Dictionary<ShipField, string>> ship in pending)
			{
				foreach (KeyValuePair<ShipField, string> item in ship.Value)
				{
					yield return (Prefab: ship.Key, Field: item.Key, Value: item.Value);
				}
			}
		}

		public IReadOnlyDictionary<ShipField, string> TakePending(string prefab)
		{
			if (prefab == null || !pending.TryGetValue(prefab, out Dictionary<ShipField, string> value))
			{
				return new Dictionary<ShipField, string>();
			}
			pending.Remove(prefab);
			return value;
		}
	}
	public sealed class ShipDefaults
	{
		public float Health;

		public float SailForce;

		public float SailForceOffset;

		public float PaddleForce;

		public float RudderSpeed;

		public float TurnForceSailing;

		public float TurnForcePaddling;

		public float ForwardDrag;

		public float SidewaysDrag;

		public float AngularDamping;

		public float WaterImpactDamage;

		public float UpsideDownDamage;

		public bool WeatherWear;

		public bool AshlandsReady;

		public int[]? BuildCosts;
	}
	public sealed class ShipSettings
	{
		public float Health;

		public float SailForce;

		public float SailForceOffset;

		public float PaddleForce;

		public float RudderSpeed;

		public float TurnForceSailing;

		public float TurnForcePaddling;

		public float ForwardDrag;

		public float SidewaysDrag;

		public float AngularDamping;

		public float WaterImpactDamage;

		public float UpsideDownDamage;

		public bool WeatherWear;

		public float DamageTaken = 1f;

		public float BuildCost = 1f;

		public bool AshlandsReady;

		public static ShipSettings Vanilla(ShipDefaults d)
		{
			ShipSettings shipSettings = FromDefaults(d);
			shipSettings.WeatherWear = d.WeatherWear;
			return shipSettings;
		}

		public static ShipSettings FromDefaults(ShipDefaults d)
		{
			if (d == null)
			{
				throw new ArgumentNullException("d");
			}
			return new ShipSettings
			{
				Health = d.Health,
				SailForce = d.SailForce,
				SailForceOffset = d.SailForceOffset,
				PaddleForce = d.PaddleForce,
				RudderSpeed = d.RudderSpeed,
				TurnForceSailing = d.TurnForceSailing,
				TurnForcePaddling = d.TurnForcePaddling,
				ForwardDrag = d.ForwardDrag,
				SidewaysDrag = d.SidewaysDrag,
				AngularDamping = d.AngularDamping,
				WaterImpactDamage = d.WaterImpactDamage,
				UpsideDownDamage = d.UpsideDownDamage,
				AshlandsReady = d.AshlandsReady
			};
		}
	}
	public readonly struct GlobalMultipliers
	{
		public static GlobalMultipliers Vanilla => new GlobalMultipliers(1f, 1f, 1f, 1f, 1f, 1f);

		public float Health { get; }

		public float SailForce { get; }

		public float PaddleForce { get; }

		public float Turning { get; }

		public float DamageTaken { get; }

		public float BuildCost { get; }

		public GlobalMultipliers(float health, float sailForce, float paddleForce, float turning, float damageTaken, float buildCost)
		{
			Health = Positive(health);
			SailForce = Positive(sailForce);
			PaddleForce = Positive(paddleForce);
			Turning = Positive(turning);
			DamageTaken = Positive(damageTaken);
			BuildCost = Positive(buildCost);
		}

		public static GlobalMultipliers From(float health, float sailForce, float paddleForce, float turning, float damageTaken, float buildCost)
		{
			return new GlobalMultipliers(health, sailForce, paddleForce, turning, damageTaken, buildCost);
		}

		internal static float Positive(float value)
		{
			if (!float.IsNaN(value) && !(value < 0f))
			{
				return value;
			}
			return 0f;
		}
	}
	public readonly struct EffectiveShip
	{
		public float Health { get; }

		public float SailForce { get; }

		public float SailForceOffset { get; }

		public float PaddleForce { get; }

		public float RudderSpeed { get; }

		public float TurnForceSailing { get; }

		public float TurnForcePaddling { get; }

		public float ForwardDrag { get; }

		public float SidewaysDrag { get; }

		public float AngularDamping { get; }

		public float WaterImpactDamage { get; }

		public float UpsideDownDamage { get; }

		public float DamageTaken { get; }

		public float BuildCost { get; }

		public bool WeatherWear { get; }

		public bool AshlandsReady { get; }

		public bool Invulnerable { get; }

		public bool TakesNoDamage
		{
			get
			{
				if (!Invulnerable)
				{
					return DamageTaken <= 0f;
				}
				return true;
			}
		}

		public EffectiveShip(ShipSettings s, GlobalMultipliers m, bool invulnerable, bool ashlandsImmune)
		{
			Health = GlobalMultipliers.Positive(s.Health) * m.Health;
			SailForce = GlobalMultipliers.Positive(s.SailForce) * m.SailForce;
			SailForceOffset = s.SailForceOffset;
			PaddleForce = GlobalMultipliers.Positive(s.PaddleForce) * m.PaddleForce;
			RudderSpeed = GlobalMultipliers.Positive(s.RudderSpeed);
			TurnForceSailing = GlobalMultipliers.Positive(s.TurnForceSailing) * m.Turning;
			TurnForcePaddling = GlobalMultipliers.Positive(s.TurnForcePaddling) * m.Turning;
			ForwardDrag = GlobalMultipliers.Positive(s.ForwardDrag);
			SidewaysDrag = GlobalMultipliers.Positive(s.SidewaysDrag);
			AngularDamping = GlobalMultipliers.Positive(s.AngularDamping);
			WaterImpactDamage = GlobalMultipliers.Positive(s.WaterImpactDamage);
			UpsideDownDamage = GlobalMultipliers.Positive(s.UpsideDownDamage);
			DamageTaken = GlobalMultipliers.Positive(s.DamageTaken) * m.DamageTaken;
			BuildCost = GlobalMultipliers.Positive(s.BuildCost) * m.BuildCost;
			WeatherWear = s.WeatherWear;
			AshlandsReady = s.AshlandsReady || ashlandsImmune;
			Invulnerable = invulnerable;
		}
	}
	public static class ShipTuning
	{
		public static EffectiveShip Compute(ShipSettings settings, GlobalMultipliers multipliers, bool invulnerable, bool ashlandsImmune)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			return new EffectiveShip(settings, multipliers, invulnerable, ashlandsImmune);
		}

		public static int[]? BuildAmounts(int[]? originals, float effectiveCost)
		{
			if (originals == null)
			{
				return null;
			}
			int[] array = new int[originals.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = Math.Max(1, (int)Math.Round((float)originals[i] * effectiveCost));
			}
			return array;
		}
	}
}
namespace ExtraPotions.Keel.Sailing
{
	public enum AbilityRefusal
	{
		None,
		NeedsLevel,
		CoolingDown
	}
	public static class AbilityRules
	{
		public static bool Unlockable(bool sailingActive, float unlockLevel)
		{
			if (sailingActive)
			{
				return unlockLevel <= 100f;
			}
			return false;
		}

		public static bool Unlocked(bool sailingActive, float unlockLevel, float level)
		{
			if (Unlockable(sailingActive, unlockLevel))
			{
				return level >= unlockLevel;
			}
			return false;
		}

		public static AbilityRefusal Refuse(float level, float unlockLevel, AbilityCooldown cooldown, float now, out int secondsLeft)
		{
			if (cooldown == null)
			{
				throw new ArgumentNullException("cooldown");
			}
			secondsLeft = 0;
			if (level < unlockLevel)
			{
				return AbilityRefusal.NeedsLevel;
			}
			float num = cooldown.Left(now);
			if (num <= 0f)
			{
				return AbilityRefusal.None;
			}
			secondsLeft = (int)Math.Ceiling(num);
			return AbilityRefusal.CoolingDown;
		}
	}
	public sealed class AbilityCooldown
	{
		private float readyAt;

		public float Left(float now)
		{
			return Math.Max(0f, readyAt - now);
		}

		public void Start(float now, float seconds)
		{
			readyAt = now + Math.Max(0f, float.IsNaN(seconds) ? 0f : seconds);
		}
	}
	public static class WindCallState
	{
		private static readonly string[] points = new string[8] { "north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west" };

		public static IReadOnlyList<string> Points { get; } = Array.AsReadOnly(points);

		public static long Until(long nowTicks, float seconds)
		{
			return nowTicks + (long)(Math.Max(0f, seconds) * 10000000f);
		}

		public static bool Blows(long untilTicks, long nowTicks, float x, float z)
		{
			if (untilTicks > nowTicks)
			{
				return x * x + z * z > 0.5f;
			}
			return false;
		}

		public static bool Retarget(bool changing, float targetX, float targetZ, float calledX, float calledZ)
		{
			if (!changing || calledX * calledX + calledZ * calledZ <= 0.5f)
			{
				return false;
			}
			double num = Math.Sqrt(targetX * targetX + targetZ * targetZ);
			double num2 = Math.Sqrt(calledX * calledX + calledZ * calledZ);
			if (num < 1E-06)
			{
				return true;
			}
			return (double)(targetX * calledX + targetZ * calledZ) / (num * num2) < 0.999;
		}

		public static WindAnnouncement Announce(long caller, long local, bool callerAboard)
		{
			if (caller == local)
			{
				return WindAnnouncement.Self;
			}
			if (!callerAboard)
			{
				return WindAnnouncement.Everyone;
			}
			return WindAnnouncement.ByName;
		}

		public static string KeyOf(WindAnnouncement announcement)
		{
			return announcement switch
			{
				WindAnnouncement.Self => "wind_called_self", 
				WindAnnouncement.ByName => "wind_called_by", 
				_ => "wind_called", 
			};
		}

		public static string Compass(float x, float z)
		{
			double num = Math.Atan2(x, z) * 180.0 / Math.PI;
			num -= Math.Floor(num / 360.0) * 360.0;
			return points[(int)Math.Round(num / 45.0) % points.Length];
		}
	}
	public enum WindAnnouncement
	{
		Self,
		ByName,
		Everyone
	}
	public static class ShipCall
	{
		public static bool MayCallWind(long senderPlayerId, bool aboard, bool controlling)
		{
			return senderPlayerId != 0 && aboard && controlling;
		}

		public static bool MayPulseLookout(long senderPlayerId, bool aboard)
		{
			return senderPlayerId != 0 && aboard;
		}
	}
	public static class LookoutRules
	{
		public const float RingStartRadius = 3f;

		public const float RingSeconds = 2f;

		public static bool IsTarget(bool isPlayer, bool isDead, bool isBoss, bool hideHud, bool isEnemy, float dx, float dz, float radius)
		{
			if (isPlayer || isDead || isBoss || hideHud || !isEnemy)
			{
				return false;
			}
			return dx * dx + dz * dz <= radius * radius;
		}

		public static float RingRadius(float t, float radius)
		{
			float num = Math.Max(3f, radius);
			float num2 = Math.Min(1f, Math.Max(0f, float.IsNaN(t) ? 0f : t));
			float num3 = 1f - (1f - num2) * (1f - num2);
			return 3f + (num - 3f) * num3;
		}
	}
	public static class ExploreShare
	{
		public const float MaximumFactor = 16f;

		public const float GameRadius = 100f;

		public static float Factor(bool active, bool aboard, float explorationRadiusAt100, float level)
		{
			if (float.IsInfinity(explorationRadiusAt100))
			{
				return 1f;
			}
			float num = SailingRules.RadiusFactor(active, aboard, explorationRadiusAt100, level);
			if (float.IsNaN(num) || float.IsInfinity(num) || num <= 0f)
			{
				return 1f;
			}
			return Math.Min(16f, num);
		}

		public static bool ExploresThisFrame(float timer, float deltaTime, float interval)
		{
			return timer + deltaTime > interval;
		}

		public static float PanelRadius(bool hasCurrent, float current, float vanillaRadius, float factor)
		{
			if (hasCurrent)
			{
				return Math.Max(0f, current);
			}
			float num = ((float.IsNaN(vanillaRadius) || float.IsInfinity(vanillaRadius)) ? 100f : vanillaRadius);
			float num2 = ((float.IsNaN(factor) || float.IsInfinity(factor) || factor <= 0f) ? 1f : factor);
			return Math.Max(0f, num * num2);
		}
	}
	public static class PadChord
	{
		private const int CacheLimit = 64;

		private static readonly Dictionary<string, IReadOnlyList<string>> parsed = new Dictionary<string, IReadOnlyList<string>>(StringComparer.Ordinal);

		public static IReadOnlyList<string> Names(string? text)
		{
			return (from p in (text ?? "").Split('+')
				select p.Trim() into p
				where p.Length > 0
				select p).Distinct<string>(StringComparer.Ordinal).ToList();
		}

		public static IReadOnlyList<string> Cached(string? text)
		{
			string text2 = text ?? "";
			if (parsed.TryGetValue(text2, out IReadOnlyList<string> value))
			{
				return value;
			}
			if (parsed.Count >= 64)
			{
				parsed.Clear();
			}
			value = Names(text2);
			parsed[text2] = value;
			return value;
		}

		public static bool Pressed(IReadOnlyList<bool> held, IReadOnlyList<bool> down)
		{
			if (held == null || down == null || held.Count == 0 || held.Count != down.Count)
			{
				return false;
			}
			bool result = false;
			for (int i = 0; i < held.Count; i++)
			{
				if (!held[i])
				{
					return false;
				}
				if (down[i])
				{
					result = true;
				}
			}
			return result;
		}
	}
	public static class SailingRules
	{
		public const float MaxLevel = 100f;

		public static float Share(float percentAt100, float level)
		{
			if (float.IsNaN(level) || float.IsNaN(percentAt100))
			{
				return 0f;
			}
			return Math.Max(0f, percentAt100) / 100f * Math.Min(1f, Math.Max(0f, level / 100f));
		}

		public static float ShipwrightFactor(bool active, float shipHealthAt100, float builderLevel)
		{
			if (!active)
			{
				return 1f;
			}
			return 1f + Share(shipHealthAt100, builderLevel);
		}

		public static float HelmSpeedFactor(bool active, bool hasHelmsman, float shipSpeedAt100, float helmsmanLevel)
		{
			if (!(active && hasHelmsman))
			{
				return 1f;
			}
			return 1f + Share(shipSpeedAt100, helmsmanLevel);
		}

		public static float HelmForceFactor(bool isOwner, float speedFactor)
		{
			if (!isOwner || !(speedFactor > 1f))
			{
				return 1f;
			}
			return speedFactor * speedFactor;
		}

		public static float RadiusFactor(bool active, bool aboard, float explorationRadiusAt100, float level)
		{
			if (!(active && aboard))
			{
				return 1f;
			}
			return 1f + Share(explorationRadiusAt100, level);
		}
	}
	public static class SailingSkill
	{
		public const string Name = "Sailing";

		public const string Identity = "meadworks_sailing";

		public const int GameType = 1690349047;

		public const string ImportFrom = "grindstone_sailing";

		public const string PublishKey = "meadworks_sailing_level";

		public const string SourcePublishKey = "grindstone_sailing_level";

		public const string TextKey = "skill_sailing";

		public const string Description = "Health of the ships you build, speed of the ships you steer, and how far you explore at sea.";

		public const string IconFile = "assets/skill_sailing.png";

		public const string IconResource = "Keel.skill_sailing.png";

		public static int ImportType => SkillHash.CustomType("grindstone_sailing");
	}
	public static class SailingKeys
	{
		public const string ShipwrightLevel = "keel.shipwright";

		public const string WindCallDirection = "keel.wind_dir";

		public const string WindCallUntil = "keel.wind_until";

		public const string RpcWindCall = "keel_WindCall";

		public const string RpcLookout = "keel_Lookout";

		public const string SourceShipwrightLevel = "grindstone_shipwright";
	}
	public enum HelmRole
	{
		Nobody,
		Helmsman,
		Crew
	}
	public sealed class SailingXp
	{
		public const float Interval = 1f;

		public const float MaxStep = 60f;

		private float timer;

		private object? lastShip;

		private double lastX;

		private double lastZ;

		public float Step(float dt, object? ship, double x, double z, HelmRole role, bool active, float helmExperiencePerKilometre, float crewSharePercent)
		{
			if (!Advance(dt))
			{
				return 0f;
			}
			return Measure(ship, x, z, role, active, helmExperiencePerKilometre, crewSharePercent);
		}

		public bool Advance(float dt)
		{
			timer += (float.IsNaN(dt) ? 0f : Math.Max(0f, dt));
			if (timer < 1f)
			{
				return false;
			}
			timer = 0f;
			return true;
		}

		public float Measure(object? ship, double x, double z, HelmRole role, bool active, float helmExperiencePerKilometre, float crewSharePercent)
		{
			double num = ((ship != null && ship == lastShip) ? Flat(x - lastX, z - lastZ) : 0.0);
			lastShip = ship;
			lastX = ((ship != null) ? x : 0.0);
			lastZ = ((ship != null) ? z : 0.0);
			if (!active || num <= 0.0 || num > 60.0)
			{
				return 0f;
			}
			float num2 = Math.Max(0f, helmExperiencePerKilometre) * Share(role, crewSharePercent);
			if (!(num2 > 0f))
			{
				return 0f;
			}
			return (float)(num / 1000.0 * (double)num2);
		}

		public static float Share(HelmRole role, float crewSharePercent)
		{
			switch (role)
			{
			case HelmRole.Helmsman:
				return 1f;
			case HelmRole.Crew:
				if (!float.IsNaN(crewSharePercent))
				{
					return Math.Min(1f, Math.Max(0f, crewSharePercent / 100f));
				}
				return 0f;
			default:
				return 0f;
			}
		}

		private static double Flat(double dx, double dz)
		{
			return Math.Sqrt(dx * dx + dz * dz);
		}
	}
	public static class ShipCheats
	{
		public const float SpeedFactor = 2f;

		public static float SailForceFactor(bool on)
		{
			if (!on)
			{
				return 1f;
			}
			return 4f;
		}

		public static bool HasTailwind(float forwardX, float forwardZ)
		{
			return forwardX * forwardX + forwardZ * forwardZ > 1E-06f;
		}

		public static (float X, float Z) Tailwind(float forwardX, float forwardZ)
		{
			float num = (float)Math.Sqrt(forwardX * forwardX + forwardZ * forwardZ);
			if (!(num > 0.001f))
			{
				return (X: 0f, Z: 0f);
			}
			return (X: forwardX / num, Z: forwardZ / num);
		}
	}
	public static class SkillHash
	{
		public static int Of(string text)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			int num = 5381;
			int num2 = num;
			for (int i = 0; i < text.Length && text[i] != 0; i += 2)
			{
				num = ((num << 5) + num) ^ text[i];
				if (i == text.Length - 1 || text[i + 1] == '\0')
				{
					break;
				}
				num2 = ((num2 << 5) + num2) ^ text[i + 1];
			}
			return num + num2 * 1566083941;
		}

		public static int CustomType(string identity)
		{
			return Of(identity) & 0x7FFFFFFF;
		}
	}
}
namespace ExtraPotions.Keel.Performance
{
	public sealed class FrameMemo<T>
	{
		private readonly Func<T> compute;

		private bool has;

		private long frame;

		private long stamp;

		private T value;

		public FrameMemo(Func<T> compute)
		{
			this.compute = compute ?? throw new ArgumentNullException("compute");
		}

		public T Get(long currentFrame, long currentStamp)
		{
			if (has && frame == currentFrame && stamp == currentStamp)
			{
				return value;
			}
			T result = (value = compute());
			frame = currentFrame;
			stamp = currentStamp;
			has = true;
			return result;
		}

		public void Invalidate()
		{
			has = false;
		}
	}
	public sealed class RefreshGate
	{
		private readonly double interval;

		private bool started;

		private double next;

		public RefreshGate(double intervalSeconds)
		{
			interval = (double.IsNaN(intervalSeconds) ? 0.0 : Math.Max(0.0, intervalSeconds));
		}

		public bool Due(double now)
		{
			if (started && now >= next - interval && now < next)
			{
				return false;
			}
			started = true;
			next = now + interval;
			return true;
		}

		public void Reset()
		{
			started = false;
		}
	}
	public sealed class ChangeGate<T>
	{
		private bool has;

		private T last;

		public bool Changed(T value)
		{
			if (has && EqualityComparer<T>.Default.Equals(last, value))
			{
				return false;
			}
			last = value;
			has = true;
			return true;
		}

		public void Reset()
		{
			has = false;
		}
	}
}
namespace ExtraPotions.Keel.Localization
{
	public readonly struct TextKey
	{
		public string Key { get; }

		public int Arguments { get; }

		public TextKey(string key, int arguments)
		{
			Key = key;
			Arguments = arguments;
		}
	}
	public static class KeelTextKeys
	{
		public const string OffNoCrucible = "off_no_crucible";

		public const string OffNoKeel = "off_no_keel";

		public const string ImportDone = "import_done";

		public const string RecipeInvalid = "recipe_invalid";

		public const string RudderUse = "rudder_use";

		public const string StorageName = "storage_name";

		public const string GateOpen = "gate_open";

		public const string GateClose = "gate_close";

		public const string GateRefusedMoving = "gate_refused_moving";

		public const string AbilityWindCall = "ability_wind_call";

		public const string AbilityLookout = "ability_lookout";

		public const string AbilityNeedsLevel = "ability_needs_level";

		public const string AbilityCooldown = "ability_cooldown";

		public const string AbilityReady = "ability_ready";

		public const string WindCalled = "wind_called";

		public const string WindCalledSelf = "wind_called_self";

		public const string WindCalledBy = "wind_called_by";

		public const string LookoutSent = "lookout_sent";

		public const string PanelSpeed = "panel_speed";

		public const string PanelSpeedFactor = "panel_speed_factor";

		public const string PanelRadius = "panel_radius";

		public const string UnitMetres = "unit_meters";

		public const string SquareWind = "square_wind";

		public const string SquareWindText = "square_wind_text";

		public static IReadOnlyList<TextKey> All { get; } = Array.AsReadOnly(new TextKey[58]
		{
			new TextKey("step_aside_tuning", 1),
			new TextKey("step_aside_sailing", 1),
			new TextKey("step_aside_catalog", 1),
			new TextKey("off_no_crucible", 0),
			new TextKey("off_no_keel", 0),
			new TextKey("import_done", 2),
			new TextKey("recipe_invalid", 2),
			new TextKey("cheat_ashlands_immune_ships", 0),
			new TextKey("cheat_invulnerable_ships", 0),
			new TextKey("skill_sailing", 0),
			new TextKey("skill_sailing_description", 0),
			new TextKey("ship_fast_karve", 0),
			new TextKey("ship_fast_longship", 0),
			new TextKey("ship_knarr", 0),
			new TextKey("ship_busse", 0),
			new TextKey("ship_big_busse", 0),
			new TextKey("ship_small_byrding", 0),
			new TextKey("ship_byrding", 0),
			new TextKey("ship_greater_byrding", 0),
			new TextKey("ship_fast_karve_description", 0),
			new TextKey("ship_fast_longship_description", 0),
			new TextKey("ship_knarr_description", 0),
			new TextKey("ship_busse_description", 0),
			new TextKey("ship_big_busse_description", 0),
			new TextKey("ship_small_byrding_description", 0),
			new TextKey("ship_byrding_description", 0),
			new TextKey("ship_greater_byrding_description", 0),
			new TextKey("rudder_use", 1),
			new TextKey("storage_name", 1),
			new TextKey("gate_open", 0),
			new TextKey("gate_close", 0),
			new TextKey("gate_refused_moving", 0),
			new TextKey("ability_wind_call", 0),
			new TextKey("ability_lookout", 0),
			new TextKey("ability_needs_level", 2),
			new TextKey("ability_cooldown", 2),
			new TextKey("ability_ready", 0),
			new TextKey("wind_called", 2),
			new TextKey("wind_called_self", 2),
			new TextKey("wind_called_by", 3),
			new TextKey("lookout_sent", 1),
			new TextKey("compass_north", 0),
			new TextKey("compass_north_east", 0),
			new TextKey("compass_east", 0),
			new TextKey("compass_south_east", 0),
			new TextKey("compass_south", 0),
			new TextKey("compass_south_west", 0),
			new TextKey("compass_west", 0),
			new TextKey("compass_north_west", 0),
			new TextKey("panel_speed", 0),
			new TextKey("panel_speed_factor", 0),
			new TextKey("panel_radius", 0),
			new TextKey("square_wind", 0),
			new TextKey("square_wind_text", 1),
			new TextKey("unit_meters_per_second", 0),
			new TextKey("unit_knots", 0),
			new TextKey("unit_kilometers_per_hour", 0),
			new TextKey("unit_meters", 0)
		});

		public static string Compass(string point)
		{
			return "compass_" + (point ?? throw new ArgumentNullException("point")).Replace('-', '_');
		}

		public static string Unit(SpeedUnit unit)
		{
			return unit switch
			{
				SpeedUnit.MetersPerSecond => "unit_meters_per_second", 
				SpeedUnit.Knots => "unit_knots", 
				SpeedUnit.KilometersPerHour => "unit_kilometers_per_hour", 
				_ => throw new ArgumentException("Unknown unit.", "unit"), 
			};
		}
	}
}
namespace ExtraPotions.Keel.Integration
{
	public enum KeelModule
	{
		Tuning,
		Sailing,
		Catalog
	}
	public sealed class ModuleSource
	{
		public string Guid { get; }

		public string Name { get; }

		public KeelModule Module { get; }

		public string ModuleId => Module.ToString().ToLowerInvariant();

		internal ModuleSource(string guid, string name, KeelModule module)
		{
			Guid = guid;
			Name = name;
			Module = module;
		}
	}
	public sealed class SourceSwitches
	{
		public static SourceSwitches Unknown => new SourceSwitches();

		public bool? GsSailing { get; set; }

		public static SourceSwitches Read(string? grindstoneConfig)
		{
			SourceSwitches sourceSwitches = new SourceSwitches();
			if (grindstoneConfig != null)
			{
				SourceEntry sourceEntry = SourceConfig.Parse(grindstoneConfig).FirstOrDefault((SourceEntry e) => e.Section == "8 - Sailing" && e.Key == "Sailing Enabled");
				sourceSwitches.GsSailing = sourceEntry.Key == null || !string.Equals(sourceEntry.Value, "false", StringComparison.OrdinalIgnoreCase);
			}
			return sourceSwitches;
		}
	}
	public readonly struct StepAsideNotice
	{
		public KeelModule Module { get; }

		public string SourceName { get; }

		public string SourceGuid { get; }

		public string Key => KeyOf(Module);

		public StepAsideNotice(KeelModule module, string sourceName, string sourceGuid)
		{
			Module = module;
			SourceName = sourceName;
			SourceGuid = sourceGuid;
		}

		public static string KeyOf(KeelModule module)
		{
			return "step_aside_" + module.ToString().ToLowerInvariant();
		}
	}
	public sealed class ModuleStepAsideDecision
	{
		private readonly HashSet<KeelModule> aside;

		public IReadOnlyList<ModuleSource> Present { get; }

		public bool Any => aside.Count > 0;

		public IReadOnlyList<StepAsideNotice> Notices { get; }

		internal ModuleStepAsideDecision(IReadOnlyList<ModuleSource> present)
		{
			Present = present;
			aside = new HashSet<KeelModule>(present.Select((ModuleSource p) => p.Module));
			Notices = (from p in present
				orderby p.Module
				select new StepAsideNotice(p.Module, p.Name, p.Guid)).ToList();
		}

		public bool SteppedAside(KeelModule module)
		{
			return aside.Contains(module);
		}
	}
	public static class ModuleStepAside
	{
		public static readonly ModuleSource ShipConfig = new ModuleSource("com.ShipConfig", "ShipConfig", KeelModule.Tuning);

		public static readonly ModuleSource GrindstoneSkills = new ModuleSource("com.GrindstoneSkills", "GrindstoneSkills", KeelModule.Sailing);

		public static readonly ModuleSource TheGreatestShips = new ModuleSource("DeathMonger.TheGreatestShips", "TheGreatestShips", KeelModule.Catalog);

		public static IReadOnlyList<ModuleSource> Sources { get; } = Array.AsReadOnly(new ModuleSource[3] { ShipConfig, GrindstoneSkills, TheGreatestShips });

		public static ModuleStepAsideDecision Decide(IEnumerable<string> installedGuids, SourceSwitches switches)
		{
			if (installedGuids == null)
			{
				throw new ArgumentNullException("installedGuids");
			}
			SourceSwitches s = switches ?? SourceSwitches.Unknown;
			HashSet<string> installed = new HashSet<string>(installedGuids.Where((string g) => g != null), StringComparer.Ordinal);
			return new ModuleStepAsideDecision(Sources.Where((ModuleSource src) => installed.Contains(src.Guid) && Acts(src)).ToList());
			bool Acts(ModuleSource source)
			{
				if (source == GrindstoneSkills)
				{
					return s.GsSailing != false;
				}
				return true;
			}
		}
	}
}
namespace ExtraPotions.Keel.Display
{
	public static class HoldPanel
	{
		public const int GameColumns = 8;

		public const float ScreenMargin = 8f;

		public const float MinScale = 0.25f;

		public static float Width(float gameWidth, float step, int columns)
		{
			return Finite(gameWidth) + (float)Math.Max(0, columns - 8) * Math.Max(0f, Finite(step));
		}

		public static float CentreShift(float gameWidth, float width)
		{
			return (Finite(width) - Finite(gameWidth)) / 2f;
		}

		public static (float Scale, float Shift) Place(float left, float width, float screenLeft, float screenRight, float margin = 8f)
		{
			if (!(width > 0f) || float.IsInfinity(width) || float.IsNaN(left) || float.IsNaN(screenLeft) || float.IsNaN(screenRight))
			{
				return (Scale: 1f, Shift: 0f);
			}
			float num = screenRight - screenLeft - 2f * margin;
			float num2 = ((num > 0f) ? Math.Max(0.25f, Math.Min(1f, num / width)) : 0.25f);
			float num3 = width * num2;
			float val = screenLeft + margin;
			float num4 = Math.Min(Math.Max(val, screenRight - margin - num3), Math.Max(val, left));
			return (Scale: num2, Shift: num4 - left);
		}

		public static (float Left, float Right) GridSpan(float centre, float step, int columns)
		{
			float num = (float)Math.Max(0, columns) * step / 2f;
			return (Left: centre - num, Right: centre + num);
		}

		private static float Finite(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return value;
			}
			return 0f;
		}
	}
	public readonly struct PanelLine
	{
		public string Key { get; }

		public string Number { get; }

		public string UnitKey { get; }

		public PanelLine(string key, string number, string unitKey)
		{
			Key = key;
			Number = number;
			UnitKey = unitKey;
		}
	}
	public readonly struct AbilityMeter
	{
		public bool Visible { get; }

		public string Key { get; }

		public string Argument { get; }

		public float Fill { get; }

		public AbilityMeter(bool visible, string key, string argument, float fill)
		{
			Visible = visible;
			Key = key;
			Argument = argument;
			Fill = fill;
		}
	}
	public static class PanelModel
	{
		public const int Margin = 10;

		public const int Gap = 8;

		public const float MinimumBoatCamera = 8f;

		public const float MaximumBoatCamera = 40f;

		private const double MetresPerNauticalMile = 1852.0;

		public static double Speed(float metresPerSecond, SpeedUnit unit)
		{
			if (float.IsNaN(metresPerSecond) || float.IsInfinity(metresPerSecond))
			{
				return 0.0;
			}
			double num = Math.Abs((double)metresPerSecond);
			return unit switch
			{
				SpeedUnit.Knots => num * 3600.0 / 1852.0, 
				SpeedUnit.KilometersPerHour => num * 3.6, 
				_ => num, 
			};
		}

		public static double SpeedFactor(float sailForce, float vanillaSailForce, float helmFactor)
		{
			double num = ((float.IsNaN(helmFactor) || float.IsInfinity(helmFactor) || helmFactor <= 0f) ? 1.0 : ((double)helmFactor));
			if (float.IsNaN(sailForce) || float.IsInfinity(sailForce) || sailForce < 0f || float.IsNaN(vanillaSailForce) || float.IsInfinity(vanillaSailForce) || vanillaSailForce <= 0f)
			{
				return num;
			}
			return Math.Sqrt((double)sailForce / (double)vanillaSailForce) * num;
		}

		public static IEnumerable<PanelLine> Lines(float metresPerSecond, SpeedUnit unit, double speedFactor, float radius, CultureInfo culture)
		{
			CultureInfo c = culture ?? CultureInfo.InvariantCulture;
			yield return new PanelLine("panel_speed", Speed(metresPerSecond, unit).ToString("0.0", c), KeelTextKeys.Unit(unit));
			yield return new PanelLine("panel_speed_factor", "x" + (double.IsNaN(speedFactor) ? 1.0 : speedFactor).ToString("0.00", c), "");
			yield return new PanelLine("panel_radius", (float.IsNaN(radius) ? 0f : Math.Max(0f, radius)).ToString("0", c), "unit_meters");
		}

		public static AbilityMeter Ability(bool unlockable, float level, float unlockLevel, float cooldownLeft, float cooldownLength)
		{
			if (!unlockable)
			{
				return new AbilityMeter(visible: false, "", "", 0f);
			}
			if (level < unlockLevel)
			{
				return new AbilityMeter(visible: true, "ability_needs_level", Math.Ceiling(unlockLevel).ToString("0", CultureInfo.InvariantCulture), 0f);
			}
			float num = (float.IsNaN(cooldownLeft) ? 0f : Math.Max(0f, cooldownLeft));
			if (num <= 0f || !(cooldownLength > 0f))
			{
				return new AbilityMeter(visible: true, "ability_ready", "", 1f);
			}
			float fill = Math.Min(1f, Math.Max(0f, 1f - num / cooldownLength));
			return new AbilityMeter(visible: true, "ability_cooldown", Math.Ceiling(num).ToString("0", CultureInfo.InvariantCulture), fill);
		}

		public static bool Visible(bool setting, bool allowsUi, bool productActive, bool aboard, bool blocked)
		{
			if (setting && allowsUi && productActive && aboard)
			{
				return !blocked;
			}
			return false;
		}

		public static bool Visible(bool setting, bool allowsUi, bool productActive, bool aboard, Func<bool> blocked)
		{
			if (setting && allowsUi && productActive && aboard)
			{
				return !blocked();
			}
			return false;
		}

		public static (int X, int Y) Home(int parentWidth, int panelWidth, int minimapBottom)
		{
			return (X: parentWidth - panelWidth - 10, Y: Math.Max(0, minimapBottom) + 8);
		}

		public static float BoatCamera(bool active, float setting, float original)
		{
			if (!active || float.IsNaN(setting) || float.IsInfinity(setting))
			{
				return original;
			}
			return Math.Min(40f, Math.Max(8f, setting));
		}
	}
	public enum SailLook
	{
		Normal,
		Faded,
		Hidden
	}
	public static class SailOpacity
	{
		public const float HiddenBelow = 0.01f;

		public static float Alpha(float opacity, bool steering, bool aboard, bool onDeck, bool bothSides, bool viewerAstern)
		{
			float result = ((float.IsNaN(opacity) || float.IsInfinity(opacity)) ? 1f : Math.Min(1f, Math.Max(0f, opacity)));
			if (!steering && !(aboard && onDeck))
			{
				return 1f;
			}
			if (!bothSides && !viewerAstern)
			{
				return 1f;
			}
			return result;
		}

		public static SailLook Look(float alpha)
		{
			if (float.IsNaN(alpha) || alpha >= 1f)
			{
				return SailLook.Normal;
			}
			if (!(alpha <= 0.01f))
			{
				return SailLook.Faded;
			}
			return SailLook.Hidden;
		}

		public static bool Astern(float cameraForwardOffset)
		{
			return !(cameraForwardOffset > 0f);
		}
	}
	public static class WindSquare
	{
		public const int Rank = 600;

		public const string Id = "keel_wind";

		public static float RelativeAngle(float forwardX, float forwardZ, float windX, float windZ)
		{
			if (forwardX * forwardX + forwardZ * forwardZ < 1E-08f || windX * windX + windZ * windZ < 1E-08f)
			{
				return 0f;
			}
			double num = (Math.Atan2(windX, windZ) - Math.Atan2(forwardX, forwardZ)) * 180.0 / Math.PI;
			num = (num % 360.0 + 540.0) % 360.0 - 180.0;
			return (float)((num == -180.0) ? 180.0 : num);
		}

		public static int Percent(float intensity)
		{
			if (float.IsNaN(intensity) || float.IsInfinity(intensity))
			{
				intensity = 0f;
			}
			return (int)Math.Round(Math.Max(0f, Math.Min(1f, intensity)) * 100f, MidpointRounding.AwayFromZero);
		}

		public static string Strength(float intensity)
		{
			return Percent(intensity).ToString(CultureInfo.InvariantCulture) + "%";
		}
	}
}
namespace ExtraPotions.Keel.Conversion
{
	public enum ImportSource
	{
		ShipConfig,
		GrindstoneSkills,
		TheGreatestShips
	}
	public enum ShipImportUnit
	{
		Absolute,
		BaseHullSpeedRatio,
		BaseHullRudderRatio
	}
	public enum ImportNoteKind
	{
		Skipped,
		Clamped,
		Fallback,
		Cheat,
		OtherProduct
	}
	public sealed class ImportNote
	{
		public ImportNoteKind Kind { get; }

		public string Source { get; }

		public string Text { get; }

		internal ImportNote(ImportNoteKind kind, string source, string text)
		{
			Kind = kind;
			Source = source;
			Text = text;
		}

		public override string ToString()
		{
			return Kind.ToString() + " " + Source + ": " + Text;
		}
	}
	public sealed class ImportValue
	{
		public string SettingId { get; }

		public object Value { get; }

		public string Source { get; }

		internal ImportValue(string settingId, object value, string source)
		{
			SettingId = settingId;
			Value = value;
			Source = source;
		}
	}
	public sealed class ShipImportValue
	{
		public string Prefab { get; }

		public ShipField Field { get; }

		public float Value { get; }

		public string Source { get; }

		public ShipImportUnit Unit { get; }

		public string Text
		{
			get
			{
				if (!ShipFields.Get(Field).IsToggle)
				{
					return ImportPlan.Format(Value);
				}
				if (Value == 0f)
				{
					return "false";
				}
				return "true";
			}
		}

		internal ShipImportValue(string prefab, ShipField field, float value, string source, ShipImportUnit unit = ShipImportUnit.Absolute)
		{
			Prefab = prefab;
			Field = field;
			Value = value;
			Source = source;
			Unit = unit;
		}

		public float Resolve(float baseHullValue)
		{
			if (Unit == ShipImportUnit.Absolute)
			{
				return Value;
			}
			if (baseHullValue <= 0f || float.IsNaN(baseHullValue) || float.IsInfinity(baseHullValue))
			{
				throw new InvalidOperationException("Native base hull facts are not available; preview after vanilla prefabs load.");
			}
			float num = baseHullValue * Value * ((Unit == ShipImportUnit.BaseHullSpeedRatio) ? Value : 1f);
			if (float.IsNaN(num) || float.IsInfinity(num))
			{
				throw new InvalidOperationException("Source ship ratio exceeds native range.");
			}
			return num;
		}
	}
	public sealed class ImportPlan
	{
		public ImportSource Source { get; }

		public IReadOnlyList<ImportValue> Values { get; }

		public IReadOnlyList<ShipImportValue> ShipValues { get; }

		public IReadOnlyList<ImportNote> Notes { get; }

		internal ImportPlan(ImportSource source, IReadOnlyList<ImportValue> values, IReadOnlyList<ShipImportValue> shipValues, IReadOnlyList<ImportNote> notes)
		{
			Source = source;
			Values = values;
			ShipValues = shipValues;
			Notes = notes;
		}

		public string Report()
		{
			return string.Join("\n", new string[1] { "Source: " + Source }.Concat(Values.Select((ImportValue v) => "Set " + v.SettingId + " = " + Format(v.Value) + " (from " + v.Source + ")")).Concat(ShipValues.Select((ShipImportValue v) => "Set Ships." + ShipFields.Key(v.Prefab, v.Field) + " = " + v.Text + ((v.Unit == ShipImportUnit.Absolute) ? "" : (" " + v.Unit.ToString() + " (resolved against native hull during preview)")) + " (from " + v.Source + ")")).Concat(Notes.Select((ImportNote n) => n.ToString())));
		}

		internal static string Format(object value)
		{
			string text;
			if (!(value is float num))
			{
				if (value is bool)
				{
					if (!(bool)value)
					{
						return "false";
					}
					return "true";
				}
				text = Convert.ToString(value, CultureInfo.InvariantCulture);
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = num.ToString("R", CultureInfo.InvariantCulture);
			}
			return text;
		}
	}
	public readonly struct SourceEntry
	{
		public string Section { get; }

		public string Key { get; }

		public string Value { get; }

		public SourceEntry(string section, string key, string value)
		{
			Section = section;
			Key = key;
			Value = value;
		}
	}
	public static class SourceConfig
	{
		public static IReadOnlyList<SourceEntry> Parse(string? text)
		{
			List<SourceEntry> list = new List<SourceEntry>();
			string section = "";
			string[] array = (text ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2[0] == '#')
				{
					continue;
				}
				if (text2[0] == '[' && text2[text2.Length - 1] == ']')
				{
					section = text2.Substring(1, text2.Length - 2).Trim();
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string text3 = text2.Substring(0, num).Trim();
					if (text3.Length != 0)
					{
						list.Add(new SourceEntry(section, text3, text2.Substring(num + 1).Trim()));
					}
				}
			}
			return list;
		}
	}
	public static class SourceImport
	{
		public const string SkillImportMarker = "Keel.imported.sailing";

		public const string VanillaAshlandsShip = "VikingShip_Ashlands";

		private static readonly string[] GsSections = new string[3] { "8 - Sailing", "9 - Lookout", "35 - Wind Call" };

		private static readonly Dictionary<string, KeelSetting> Gs = new Dictionary<string, KeelSetting>(StringComparer.Ordinal)
		{
			{
				"8 - Sailing.Sailing Enabled",
				KeelSettings.Sailing.Enabled
			},
			{
				"8 - Sailing.Ship Health At 100",
				KeelSettings.Sailing.ShipHealthAt100
			},
			{
				"8 - Sailing.Ship Speed At 100",
				KeelSettings.Sailing.ShipSpeedAt100
			},
			{
				"8 - Sailing.Exploration Radius At 100",
				KeelSettings.Sailing.ExplorationRadiusAt100
			},
			{
				"8 - Sailing.Helm Experience Per Kilometre",
				KeelSettings.Sailing.HelmExperiencePerKilometre
			},
			{
				"8 - Sailing.Crew Experience Share",
				KeelSettings.Sailing.CrewExperienceShare
			},
			{
				"9 - Lookout.Lookout Level",
				KeelSettings.Lookout.Level
			},
			{
				"9 - Lookout.Lookout Radius",
				KeelSettings.Lookout.Radius
			},
			{
				"9 - Lookout.Lookout Duration",
				KeelSettings.Lookout.Duration
			},
			{
				"9 - Lookout.Lookout Cooldown",
				KeelSettings.Lookout.Cooldown
			},
			{
				"9 - Lookout.Lookout Key",
				KeelSettings.Lookout.Key
			},
			{
				"35 - Wind Call.Wind Call Level",
				KeelSettings.WindCall.Level
			},
			{
				"35 - Wind Call.Wind Call Duration",
				KeelSettings.WindCall.Duration
			},
			{
				"35 - Wind Call.Wind Call Cooldown",
				KeelSettings.WindCall.Cooldown
			},
			{
				"35 - Wind Call.Wind Call Key",
				KeelSettings.WindCall.Key
			}
		};

		private static readonly Dictionary<string, KeelSetting> Sc = new Dictionary<string, KeelSetting>(StringComparer.Ordinal)
		{
			{
				"Multipliers.Health Multiplier",
				KeelSettings.Multipliers.Health
			},
			{
				"Multipliers.Sail Force Multiplier",
				KeelSettings.Multipliers.SailForce
			},
			{
				"Multipliers.Paddle Force Multiplier",
				KeelSettings.Multipliers.PaddleForce
			},
			{
				"Multipliers.Turning Multiplier",
				KeelSettings.Multipliers.Turning
			},
			{
				"Multipliers.Damage Taken Multiplier",
				KeelSettings.Multipliers.DamageTaken
			},
			{
				"Multipliers.Build Cost Multiplier",
				KeelSettings.Multipliers.BuildCost
			},
			{
				"Display.Ship Panel",
				KeelSettings.Display.ShipPanel
			}
		};

		public static string FileName(ImportSource source)
		{
			return source switch
			{
				ImportSource.ShipConfig => "com.ShipConfig.cfg", 
				ImportSource.GrindstoneSkills => "com.GrindstoneSkills.cfg", 
				ImportSource.TheGreatestShips => "DeathMonger.TheGreatestShips.cfg", 
				_ => throw new ArgumentException("Unknown source.", "source"), 
			};
		}

		public static ImportPlan Plan(ImportSource source, string? text)
		{
			List<ImportValue> list = new List<ImportValue>();
			List<ShipImportValue> list2 = new List<ShipImportValue>();
			List<ImportNote> list3 = new List<ImportNote>();
			List<string> list4 = new List<string>();
			List<string> list5 = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (SourceEntry item in SourceConfig.Parse(text))
			{
				string text2 = item.Section + "." + item.Key;
				switch (source)
				{
				case ImportSource.TheGreatestShips:
					TgsImport.Entry(item, list, list2, list3);
					continue;
				case ImportSource.GrindstoneSkills:
					if (!GsSections.Contains(item.Section))
					{
						if (hashSet.Add(item.Section))
						{
							list3.Add(new ImportNote(ImportNoteKind.OtherProduct, item.Section, "imported by another Meadworks product"));
						}
						continue;
					}
					break;
				}
				KeelSetting target;
				if (source == ImportSource.ShipConfig && item.Section == "Ship")
				{
					PlanShip(item, text2, list2, list3, list4, list5);
				}
				else if (!((source == ImportSource.ShipConfig) ? Sc : Gs).TryGetValue(text2, out target))
				{
					list3.Add(new ImportNote(ImportNoteKind.Skipped, text2, "not imported"));
				}
				else if (!list.Any((ImportValue v) => v.SettingId == target.Id))
				{
					list.Add(new ImportValue(target.Id, Convert(target, item.Value, text2, list3), text2));
				}
			}
			AddCheat(list, list3, KeelSettings.Cheats.AshlandsImmuneShips, KeelSettings.Cheats.AshlandsImmuneList, list4, "AshlandsOceanDamage = false");
			AddCheat(list, list3, KeelSettings.Cheats.InvulnerableShips, KeelSettings.Cheats.InvulnerableList, list5, "Invulnerable = true");
			return new ImportPlan(source, list, list2, list3);
		}

		public static float ShipwrightLevel(float keelValue, float sourceValue)
		{
			if (!(keelValue >= 0f))
			{
				return sourceValue;
			}
			return keelValue;
		}

		private static void PlanShip(SourceEntry entry, string from, List<ShipImportValue> ships, List<ImportNote> notes, List<string> immune, List<string> invulnerable)
		{
			int num = entry.Key.LastIndexOf('.');
			string prefab = ((num > 0) ? entry.Key.Substring(0, num).Trim() : "");
			string text = ((num > 0) ? entry.Key.Substring(num + 1).Trim() : "");
			if (prefab.Length == 0 || text.Length == 0)
			{
				notes.Add(new ImportNote(ImportNoteKind.Skipped, from, "not Prefab.Field"));
				return;
			}
			if (text == "AshlandsOceanDamage" || text == "Invulnerable")
			{
				if (!TryBool(entry.Value, out var value))
				{
					notes.Add(new ImportNote(ImportNoteKind.Fallback, from, "unreadable \"" + entry.Value + "\"; not imported"));
					return;
				}
				if (text == "AshlandsOceanDamage" && !value)
				{
					if (prefab == "VikingShip_Ashlands")
					{
						notes.Add(new ImportNote(ImportNoteKind.Skipped, from, "the game already spares this ship"));
					}
					else if (!immune.Contains(prefab))
					{
						immune.Add(prefab);
					}
				}
				if (text == "Invulnerable" && value && !invulnerable.Contains(prefab))
				{
					invulnerable.Add(prefab);
				}
				return;
			}
			if (!Enum.TryParse<ShipField>(text, ignoreCase: false, out var field) || !Enum.IsDefined(typeof(ShipField), field) || text != field.ToString())
			{
				notes.Add(new ImportNote(ImportNoteKind.Skipped, from, "unknown ship field"));
				return;
			}
			float value3;
			if (ShipFields.Get(field).IsToggle)
			{
				if (!TryBool(entry.Value, out var value2))
				{
					notes.Add(new ImportNote(ImportNoteKind.Fallback, from, "unreadable \"" + entry.Value + "\"; not imported"));
					return;
				}
				value3 = (value2 ? 1f : 0f);
			}
			else if (!TryFloat(entry.Value, out value3))
			{
				notes.Add(new ImportNote(ImportNoteKind.Fallback, from, "unreadable \"" + entry.Value + "\"; not imported"));
				return;
			}
			ships.RemoveAll((ShipImportValue s) => s.Prefab == prefab && s.Field == field);
			ships.Add(new ShipImportValue(prefab, field, value3, from));
		}

		private static void AddCheat(List<ImportValue> values, List<ImportNote> notes, KeelSetting<bool> cheat, KeelSetting<string> list, List<string> ships, string why)
		{
			if (ships.Count != 0)
			{
				string text = list.Normalize(string.Join(",", ships));
				values.Add(new ImportValue(cheat.Id, true, "Ship.*." + why.Split(' ')[0]));
				values.Add(new ImportValue(list.Id, text, "Ship.*." + why.Split(' ')[0]));
				notes.Add(new ImportNote(ImportNoteKind.Cheat, cheat.Id, "turned on for " + text + " (ShipConfig " + why + ")"));
			}
		}

		internal static object Convert(KeelSetting target, string text, string from, List<ImportNote> notes)
		{
			if (!(target is KeelSetting<bool>))
			{
				if (!(target is KeelSetting<float>))
				{
					if (!(target is KeelSetting<int>))
					{
						if (target is KeelSetting<string>)
						{
							return Checked(text);
						}
						return Fallback("unsupported type");
					}
					if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						return Fallback("unreadable \"" + text + "\"");
					}
					return Checked(result);
				}
				if (!TryFloat(text, out var value))
				{
					return Fallback("unreadable \"" + text + "\"");
				}
				return Checked(value);
			}
			if (!TryBool(text, out var value2))
			{
				return Fallback("unreadable \"" + text + "\"");
			}
			return value2;
			object Checked(object obj2)
			{
				object obj = target.NormalizeBoxed(obj2);
				if (!object.Equals(obj, obj2))
				{
					notes.Add(new ImportNote(ImportNoteKind.Clamped, from, ImportPlan.Format(obj2) + " clamped to " + ImportPlan.Format(obj)));
				}
				return obj;
			}
			object Fallback(string why)
			{
				notes.Add(new ImportNote(ImportNoteKind.Fallback, from, why + "; Keel default " + ImportPlan.Format(target.DefaultValue)));
				return target.DefaultValue;
			}
		}

		private static bool TryBool(string text, out bool value)
		{
			if (string.Equals(text, "true", StringComparison.OrdinalIgnoreCase))
			{
				value = true;
				return true;
			}
			if (string.Equals(text, "false", StringComparison.OrdinalIgnoreCase))
			{
				value = false;
				return true;
			}
			value = false;
			return false;
		}

		private static bool TryFloat(string text, out float value)
		{
			if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value) && !float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal static class TgsImport
	{
		private static readonly Dictionary<string, KeelSetting> general = new Dictionary<string, KeelSetting>(StringComparer.Ordinal)
		{
			{
				"Boat Camera Max Distance",
				KeelSettings.Display.BoatCameraMaxDistance
			},
			{
				"Sail Opacity",
				KeelSettings.Display.SailOpacity
			},
			{
				"Sail Opacity Both Sides",
				KeelSettings.Display.SailOpacityBothSides
			},
			{
				"Sail Opacity On Deck",
				KeelSettings.Display.SailOpacityOnDeck
			},
			{
				"Name Vanilla Ships",
				KeelSettings.Display.NameVanillaShips
			}
		};

		internal static void Entry(SourceEntry entry, List<ImportValue> values, List<ShipImportValue> ships, List<ImportNote> notes)
		{
			string source = entry.Section + "." + entry.Key;
			if (entry.Section == "General" && general.TryGetValue(entry.Key, out KeelSetting value))
			{
				Add(value, entry, values, notes);
				return;
			}
			ShipType type = ShipCatalog.All.FirstOrDefault((ShipType t) => t.Name == entry.Section);
			if (type == null)
			{
				notes.Add(new ImportNote(ImportNoteKind.Skipped, source, "not a documented ship section or supported General key"));
				return;
			}
			CatalogSettings catalogSettings = KeelSettings.Catalog(type.Ship);
			KeelSetting keelSetting = null;
			switch (entry.Key)
			{
			case "Recipe":
				keelSetting = catalogSettings.Recipe;
				break;
			case "Hull Width":
				keelSetting = catalogSettings.Width;
				break;
			case "Hull Length":
				keelSetting = catalogSettings.Length;
				break;
			case "Hull Scale":
				keelSetting = catalogSettings.Scale;
				break;
			case "Sail Color":
				keelSetting = catalogSettings.SailColor;
				break;
			case "Hull Color":
				keelSetting = catalogSettings.HullColor;
				break;
			case "Hull Stripes":
				keelSetting = catalogSettings.HullStripes;
				break;
			}
			if (keelSetting != null)
			{
				Add(keelSetting, entry, values, notes);
				return;
			}
			ShipField field;
			ShipImportUnit unit;
			switch (entry.Key)
			{
			case "Health":
				field = ShipField.Health;
				unit = ShipImportUnit.Absolute;
				break;
			case "Top Speed Multiplier":
				field = ShipField.SailForce;
				unit = ShipImportUnit.BaseHullSpeedRatio;
				break;
			case "Rudder Speed":
				field = ShipField.RudderSpeed;
				unit = ShipImportUnit.BaseHullRudderRatio;
				break;
			default:
				notes.Add(new ImportNote(ImportNoteKind.Skipped, source, "key not documented at the pinned README"));
				return;
			}
			if (!float.TryParse(entry.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 0f || float.IsNaN(result) || float.IsInfinity(result))
			{
				notes.Add(new ImportNote(ImportNoteKind.Fallback, source, "invalid finite nonnegative ship value; not imported"));
				return;
			}
			ships.RemoveAll((ShipImportValue v) => v.Prefab == type.Prefab && v.Field == field);
			ships.Add(new ShipImportValue(type.Prefab, field, result, source, unit));
		}

		private static void Add(KeelSetting target, SourceEntry entry, List<ImportValue> values, List<ImportNote> notes)
		{
			string text = entry.Section + "." + entry.Key;
			values.RemoveAll((ImportValue v) => v.SettingId == target.Id);
			values.Add(new ImportValue(target.Id, SourceImport.Convert(target, entry.Value, text, notes), text));
		}
	}
}
namespace ExtraPotions.Keel.Config
{
	public static class KeelCheats
	{
		public static T Effective<T>(KeelSetting<T> setting, bool locked, T value)
		{
			if (setting == null)
			{
				throw new ArgumentNullException("setting");
			}
			if (setting.Scope != SettingScope.Cheat)
			{
				throw new ArgumentException(setting.Id + " is not a cheat.", "setting");
			}
			if (!locked)
			{
				return setting.Normalize(value);
			}
			return setting.Safe;
		}

		public static ShipList ShipsInEffect(KeelSetting<bool> cheat, bool locked, bool enabled, KeelSetting<string> list, string value)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (list.Scope != SettingScope.ServerAuthoritative)
			{
				throw new ArgumentException(list.Id + " must be a ServerAuthoritative ship list.", "list");
			}
			if (!Effective(cheat, locked, enabled))
			{
				return ShipList.Empty;
			}
			return ShipList.Parse(list.Normalize(value));
		}
	}
	public enum SettingScope
	{
		Local,
		ServerAuthoritative,
		Cheat
	}
	public abstract class KeelSetting
	{
		private static readonly IReadOnlyList<string> NoChoices = Array.Empty<string>();

		public string Section { get; }

		public string Key { get; }

		public string Id => Section + "." + Key;

		public SettingScope Scope { get; }

		public string Description { get; }

		public IReadOnlyList<string> Choices { get; }

		public bool Hidden { get; }

		public abstract Type ValueType { get; }

		public abstract object DefaultValue { get; }

		public abstract object? SafeValue { get; }

		public abstract object? Minimum { get; }

		public abstract object? Maximum { get; }

		private protected KeelSetting(string section, string key, SettingScope scope, string description, IReadOnlyList<string>? choices, bool hidden)
		{
			if (string.IsNullOrWhiteSpace(section) || section.Trim() != section)
			{
				throw new ArgumentException("Expected a trimmed section name.", "section");
			}
			if (string.IsNullOrWhiteSpace(key) || key.Trim() != key)
			{
				throw new ArgumentException("Expected a trimmed key.", "key");
			}
			if (!Enum.IsDefined(typeof(SettingScope), scope))
			{
				throw new ArgumentException("Unknown scope.", "scope");
			}
			if (string.IsNullOrWhiteSpace(description))
			{
				throw new ArgumentException("Expected a description.", "description");
			}
			if (hidden && scope == SettingScope.Cheat)
			{
				throw new ArgumentException(section + "." + key + ": a cheat cannot be hidden; cheats stay visible for review.", "hidden");
			}
			Section = section;
			Key = key;
			Scope = scope;
			Description = description;
			Choices = choices ?? NoChoices;
			Hidden = hidden;
		}

		public abstract object NormalizeBoxed(object? value);

		public override string ToString()
		{
			return Id + " (" + Scope.ToString() + (Hidden ? ", hidden" : "") + ")";
		}

		public static KeelSetting<bool> Toggle(string section, string key, bool defaultValue, SettingScope scope, string description, bool? safe = null, bool hidden = false)
		{
			return new KeelSetting<bool>(section, key, scope, description, defaultValue, safe, null, (bool v) => v, null, hidden);
		}

		public static KeelSetting<int> Integer(string section, string key, int defaultValue, int min, int max, SettingScope scope, string description)
		{
			if (min > max)
			{
				throw new ArgumentException("Minimum above maximum.", "min");
			}
			return new KeelSetting<int>(section, key, scope, description, defaultValue, null, (min, max), (int v) => Math.Min(max, Math.Max(min, v)));
		}

		public static KeelSetting<float> Number(string section, string key, float defaultValue, float min, float max, SettingScope scope, string description)
		{
			if (!(min <= max) || float.IsInfinity(min) || float.IsInfinity(max))
			{
				throw new ArgumentException("Expected a finite range.", "min");
			}
			return new KeelSetting<float>(section, key, scope, description, defaultValue, null, (min, max), (float v) => (!float.IsNaN(v) && !float.IsInfinity(v)) ? Math.Min(max, Math.Max(min, v)) : defaultValue);
		}

		public static KeelSetting<TEnum> Choice<TEnum>(string section, string key, TEnum defaultValue, SettingScope scope, string description) where TEnum : struct, Enum
		{
			return new KeelSetting<TEnum>(section, key, scope, description, defaultValue, null, null, (TEnum v) => (!Enum.IsDefined(typeof(TEnum), v)) ? defaultValue : v, Enum.GetNames(typeof(TEnum)));
		}

		public static KeelSetting<string> Text(string section, string key, string defaultValue, SettingScope scope, string description, Func<string, string> normalize, bool hidden = false)
		{
			if (normalize == null)
			{
				throw new ArgumentNullException("normalize");
			}
			return new KeelSetting<string>(section, key, scope, description, defaultValue, null, null, normalize, null, hidden);
		}
	}
	public sealed class KeelSetting<T> : KeelSetting
	{
		private readonly Func<T, T> normalize;

		private readonly bool hasSafe;

		private readonly (T Min, T Max)? range;

		public T Default { get; }

		public T Safe { get; }

		public override Type ValueType => typeof(T);

		public override object DefaultValue => Default;

		public override object? SafeValue
		{
			get
			{
				if (!hasSafe)
				{
					return null;
				}
				return Safe;
			}
		}

		public override object? Minimum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Min;
			}
		}

		public override object? Maximum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Max;
			}
		}

		internal KeelSetting(string section, string key, SettingScope scope, string description, T defaultValue, object? safe, (T, T)? range, Func<T, T> normalize, IReadOnlyList<string>? choices = null, bool hidden = false)
			: base(section, key, scope, description, choices, hidden)
		{
			this.normalize = normalize ?? throw new ArgumentNullException("normalize");
			this.range = range;
			if (!EqualityComparer<T>.Default.Equals(normalize(defaultValue), defaultValue))
			{
				throw new ArgumentException(base.Id + ": the default is outside its own rule.", "defaultValue");
			}
			Default = defaultValue;
			if (scope == SettingScope.Cheat)
			{
				if (safe == null)
				{
					throw new ArgumentException(base.Id + ": a cheat needs a safe value.", "safe");
				}
				if (!EqualityComparer<T>.Default.Equals(defaultValue, (T)safe))
				{
					throw new ArgumentException(base.Id + ": a cheat must default to its safe value.", "defaultValue");
				}
				Safe = (T)safe;
				hasSafe = true;
			}
			else
			{
				if (safe != null)
				{
					throw new ArgumentException(base.Id + ": only cheats have a safe value.", "safe");
				}
				Safe = defaultValue;
			}
		}

		public T Normalize(T value)
		{
			return normalize(value);
		}

		public override object NormalizeBoxed(object? value)
		{
			return (value is T value2) ? Normalize(value2) : Default;
		}
	}
	public sealed class CatalogSettings
	{
		public ShipType Type { get; }

		public KeelSetting<bool> Enabled { get; }

		public KeelSetting<string> Recipe { get; }

		public KeelSetting<float> Width { get; }

		public KeelSetting<float> Length { get; }

		public KeelSetting<float> Scale { get; }

		public KeelSetting<int> CargoColumns { get; }

		public KeelSetting<int> CargoRows { get; }

		public KeelSetting<string> SailColor { get; }

		public KeelSetting<string> HullColor { get; }

		public KeelSetting<int> HullStripes { get; }

		public KeelSetting<int>? PenCapacity { get; }

		internal CatalogSettings(ShipType type)
		{
			Type = type;
			string name = type.Name;
			Enabled = KeelSetting.Toggle(name, "Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Off hides the " + type.Name + " from the hammer. Ships already built stay.");
			Recipe = KeelSetting.Text(name, "Recipe", type.Recipe, SettingScope.ServerAuthoritative, "Up to five Prefab:Amount pairs separated by commas. An invalid recipe or an unknown item keeps the default and logs why.", (string v) => (v ?? "").Trim());
			Width = Shape(name, "Width", type.Width, "Hull width relative to the vanilla " + HullName(type) + ".");
			Length = Shape(name, "Length", type.Length, "Bow-to-stern length relative to the vanilla " + HullName(type) + ".");
			Scale = Shape(name, "Scale", type.Scale, "Size of the whole ship in all three dimensions; Width and Length multiply on top.");
			CargoColumns = KeelSetting.Integer(name, "Cargo Columns", type.CargoColumns, 1, 12, SettingScope.ServerAuthoritative, "Columns of the ship's hold. A smaller hold never deletes items: a non-empty hold is not shrunk.");
			CargoRows = KeelSetting.Integer(name, "Cargo Rows", type.CargoRows, 1, 10, SettingScope.ServerAuthoritative, "Number of storage rows in this ship's hold.");
			SailColor = KeelSetting.Text(name, "Sail Color", type.SailColor, SettingScope.ServerAuthoritative, "Hex RRGGBB multiplied into the sail; FFFFFF leaves it as it is. Loaded ships change at once.", (string v) => HexColor.Normalize(v, type.SailColor, allowEmpty: false));
			HullColor = KeelSetting.Text(name, "Hull Color", type.HullColor, SettingScope.ServerAuthoritative, "Hex RRGGBB paint multiplied into the hull planks (not the mast or rudder); empty keeps the vanilla planks.", (string v) => HexColor.Normalize(v, type.HullColor, allowEmpty: true));
			HullStripes = KeelSetting.Integer(name, "Hull Stripes", type.HullStripes, 0, 8, SettingScope.ServerAuthoritative, "Painted bands on the hull; 0 paints it solid.");
			if (type.HasPen)
			{
				PenCapacity = KeelSetting.Integer(name, "Pen Capacity", type.PenCapacity, 0, 40, SettingScope.ServerAuthoritative, "Animals the deck pen is meant for (about one per 2 square meters); Shepherd uses it as the herd limit. 0: no limit.");
			}
		}

		public IEnumerable<KeelSetting> Entries()
		{
			yield return Enabled;
			yield return Recipe;
			yield return Width;
			yield return Length;
			yield return Scale;
			yield return CargoColumns;
			yield return CargoRows;
			yield return SailColor;
			yield return HullColor;
			yield return HullStripes;
			if (PenCapacity != null)
			{
				yield return PenCapacity;
			}
		}

		private static string HullName(ShipType type)
		{
			if (!(type.BaseHull == "Karve"))
			{
				return "Longship";
			}
			return "Karve";
		}

		private static KeelSetting<float> Shape(string section, string key, float value, string description)
		{
			return KeelSetting.Number(section, key, value, 0.5f, 2f, SettingScope.ServerAuthoritative, description);
		}
	}
	public enum SpeedUnit
	{
		MetersPerSecond,
		Knots,
		KilometersPerHour
	}
	public static class KeelSettings
	{
		public static class Multipliers
		{
			public const string Section = "Multipliers";

			public static readonly KeelSetting<float> Health = Factor("Health Multiplier", 0.1f, "the health of every ship");

			public static readonly KeelSetting<float> SailForce = Factor("Sail Force Multiplier", 0.1f, "the sail force of every ship");

			public static readonly KeelSetting<float> PaddleForce = Factor("Paddle Force Multiplier", 0.1f, "the paddle force of every ship");

			public static readonly KeelSetting<float> Turning = Factor("Turning Multiplier", 0.1f, "the turning force of every ship, under sail and at the oars");

			public static readonly KeelSetting<float> DamageTaken = Factor("Damage Taken Multiplier", 0f, "the damage every ship takes; 0 means no damage");

			public static readonly KeelSetting<float> BuildCost = Factor("Build Cost Multiplier", 0.1f, "the materials every ship costs (each material rounded, at least 1; free ships come from Timber's Free Build cheat)");

			private static KeelSetting<float> Factor(string key, float min, string subject)
			{
				return KeelSetting.Number("Multipliers", key, 1f, min, 10f, SettingScope.ServerAuthoritative, "Multiplier on " + subject + ", on top of the per-ship value. 1 is vanilla.");
			}
		}

		public static class Pens
		{
			public const string Section = "Pens";

			public static readonly KeelSetting<float> ContainmentSpeed = KeelSetting.Number("Pens", "Containment Speed", 0.5f, 0f, 5f, SettingScope.ServerAuthoritative, "Ship speed in m/s above which the invisible lip along a deck pen's rails keeps jostled animals aboard. At rest the lip is gone so animals can leave.");

			public static readonly KeelSetting<bool> GatesOpenAtRestOnly = KeelSetting.Toggle("Pens", "Gates Open At Rest Only", defaultValue: false, SettingScope.ServerAuthoritative, "On: a pen gate refuses to open while the ship moves faster than Containment Speed. Closing always works.");
		}

		public static class Sailing
		{
			public const string Section = "Sailing";

			public static readonly KeelSetting<bool> Enabled = KeelSetting.Toggle("Sailing", "Sailing Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Every Sailing perk, Wind Call, Lookout and Sailing experience. Off keeps everyone's Sailing level.");

			public static readonly KeelSetting<float> ShipHealthAt100 = KeelSetting.Number("Sailing", "Ship Health At 100", 50f, 0f, 500f, SettingScope.ServerAuthoritative, "Percent more health for a ship built by a level 100 sailor, fixed by the builder's level when the ship is placed.");

			public static readonly KeelSetting<float> ShipSpeedAt100 = KeelSetting.Number("Sailing", "Ship Speed At 100", 20f, 0f, 200f, SettingScope.ServerAuthoritative, "Percent more top speed, under sail and at the oars, for a ship a level 100 sailor steers.");

			public static readonly KeelSetting<float> ExplorationRadiusAt100 = KeelSetting.Number("Sailing", "Exploration Radius At 100", 100f, 0f, 500f, SettingScope.ServerAuthoritative, "Percent larger map exploration radius while a level 100 sailor is aboard a ship (the game's radius is 100 m).");

			public static readonly KeelSetting<float> HelmExperiencePerKilometre = KeelSetting.Number("Sailing", "Helm Experience Per Kilometer", 40f, 0f, 1000f, SettingScope.ServerAuthoritative, "Sailing experience per kilometer a ship travels while you steer it. 0 turns it off.");

			public static readonly KeelSetting<float> CrewExperienceShare = KeelSetting.Number("Sailing", "Crew Experience Share", 25f, 0f, 100f, SettingScope.ServerAuthoritative, "Percent of the helmsman's experience everyone else aboard earns while someone steers. 0 turns it off.");
		}

		public static class WindCall
		{
			public const string Section = "Wind Call";

			public static readonly KeelSetting<float> Level = KeelSetting.Number("Wind Call", "Wind Call Level", 25f, 0f, 101f, SettingScope.ServerAuthoritative, "Sailing level that unlocks Wind Call. 0 gives it to everyone; above 100 turns it off.");

			public static readonly KeelSetting<float> Duration = KeelSetting.Number("Wind Call", "Wind Call Duration", 60f, 5f, 600f, SettingScope.ServerAuthoritative, "Seconds the called wind blows for everyone aboard that ship.");

			public static readonly KeelSetting<float> Cooldown = KeelSetting.Number("Wind Call", "Wind Call Cooldown", 180f, 0f, 3600f, SettingScope.ServerAuthoritative, "Seconds before the same player can call the wind again.");

			public static readonly KeelSetting<string> Key = Shortcut("Wind Call", "Wind Call Key", "H", "Turns the wind to blow the way you look while you steer a ship. Your own key.");

			public static readonly KeelSetting<string> Pad = Shortcut("Wind Call", "Gamepad Wind Call", "", "Gamepad chord for Wind Call (Crucible pad buttons joined by +). Empty: none, so nothing collides with steering.");
		}

		public static class Lookout
		{
			public const string Section = "Lookout";

			public static readonly KeelSetting<float> Level = KeelSetting.Number("Lookout", "Lookout Level", 50f, 0f, 101f, SettingScope.ServerAuthoritative, "Sailing level that unlocks Lookout. 0 gives it to everyone; above 100 turns it off.");

			public static readonly KeelSetting<float> Radius = KeelSetting.Number("Lookout", "Lookout Radius", 100f, 10f, 300f, SettingScope.ServerAuthoritative, "How far from the ship the Lookout pulse reaches, in meters.");

			public static readonly KeelSetting<float> Duration = KeelSetting.Number("Lookout", "Lookout Duration", 30f, 1f, 300f, SettingScope.ServerAuthoritative, "Seconds the enemies the pulse reached keep their name tags for everyone aboard.");

			public static readonly KeelSetting<float> Cooldown = KeelSetting.Number("Lookout", "Lookout Cooldown", 60f, 0f, 3600f, SettingScope.ServerAuthoritative, "Seconds before the same player can send the next pulse.");

			public static readonly KeelSetting<string> Key = Shortcut("Lookout", "Lookout Key", "O", "Sends the Lookout pulse while you are aboard a ship. Your own key.");

			public static readonly KeelSetting<string> Pad = Shortcut("Lookout", "Gamepad Lookout", "", "Gamepad chord for Lookout (Crucible pad buttons joined by +). Empty: none.");
		}

		public static class Display
		{
			public const string Section = "Display";

			public static readonly KeelSetting<bool> ShipPanel = KeelSetting.Toggle("Display", "Ship Panel", defaultValue: true, SettingScope.Local, "A panel under the minimap while you are aboard: speed, speed against vanilla, exploration radius and your Sailing abilities.");

			public static readonly KeelSetting<SpeedUnit> SpeedUnit = KeelSetting.Choice("Display", "Speed Unit", ExtraPotions.Keel.Config.SpeedUnit.KilometersPerHour, SettingScope.Local, "How the ship panel shows speed: meters per second (MetersPerSecond), knots (Knots) or kilometers per hour (KilometersPerHour).");

			public static readonly KeelSetting<float> BoatCameraMaxDistance = KeelSetting.Number("Display", "Boat Camera Max Distance", 16f, 8f, 40f, SettingScope.Local, "How far the camera may zoom out while you steer, in meters. 16 is the game's own limit; try 24 for the biggest ships.");

			public static readonly KeelSetting<float> SailOpacity = KeelSetting.Number("Display", "Sail Opacity", 1f, 0f, 1f, SettingScope.Local, "Sail opacity of the ship you steer: 1 as the game draws it, lower is see-through, 0 hides it. Other ships are unchanged.");

			public static readonly KeelSetting<bool> SailOpacityBothSides = KeelSetting.Toggle("Display", "Sail Opacity Both Sides", defaultValue: false, SettingScope.Local, "Off: only the side of the sail you see from the rudder is see-through. On: both sides.");

			public static readonly KeelSetting<bool> SailOpacityOnDeck = KeelSetting.Toggle("Display", "Sail Opacity On Deck", defaultValue: false, SettingScope.Local, "Sail Opacity always applies at the rudder. On: also anywhere on the deck.");

			public static readonly KeelSetting<bool> NameVanillaShips = KeelSetting.Toggle("Display", "Name Vanilla Ships", defaultValue: true, SettingScope.Local, "Vanilla rudders and holds name their ship too: \"Use rudder (Karve)\", \"Karve Storage\".");
		}

		public static class Cheats
		{
			public const string Section = "Cheats";

			public static readonly KeelSetting<bool> AshlandsImmuneShips = KeelSetting.Toggle("Cheats", "Ashlands Immune Ships", defaultValue: false, SettingScope.Cheat, "Cheat. The ships on Ashlands Immune List sail the burning Ashlands ocean unharmed. This removes the progression gate that makes the Drakkar necessary, with the ocean effects and world key it triggers.", false);

			public static readonly KeelSetting<string> AshlandsImmuneList = KeelSetting.Text("Cheats", "Ashlands Immune List", "*", SettingScope.ServerAuthoritative, "Ship prefab names separated by commas, or * for every ship. Acts only while Ashlands Immune Ships is on.", ShipList.Normalize);

			public static readonly KeelSetting<bool> InvulnerableShips = KeelSetting.Toggle("Cheats", "Invulnerable Ships", defaultValue: false, SettingScope.Cheat, "Cheat. The ships on Invulnerable List take no damage of any kind: hits, collisions, weather or the Ashlands ocean.", false);

			public static readonly KeelSetting<string> InvulnerableList = KeelSetting.Text("Cheats", "Invulnerable List", "*", SettingScope.ServerAuthoritative, "Ship prefab names separated by commas, or * for every ship. Acts only while Invulnerable Ships is on.", ShipList.Normalize);

			public static readonly KeelSetting<bool> Tailwind = KeelSetting.Toggle("Cheats", "Tailwind", defaultValue: false, SettingScope.Cheat, "Cheat. The wind always blows from behind the ship you steer, for you alone: your sail fills whichever way you head. Other players' wind is unchanged.", false);

			public static readonly KeelSetting<bool> FastSailing = KeelSetting.Toggle("Cheats", "Fast Sailing", defaultValue: false, SettingScope.Cheat, "Cheat. The ship you steer sails at up to twice its top speed. Acts on the machine that moves the ship, which is usually the helmsman's.", false);
		}

		private static readonly Lazy<KeelSetting[]> general = new Lazy<KeelSetting[]>(() => new KeelSetting[38]
		{
			Multipliers.Health,
			Multipliers.SailForce,
			Multipliers.PaddleForce,
			Multipliers.Turning,
			Multipliers.DamageTaken,
			Multipliers.BuildCost,
			Pens.ContainmentSpeed,
			Pens.GatesOpenAtRestOnly,
			Sailing.Enabled,
			Sailing.ShipHealthAt100,
			Sailing.ShipSpeedAt100,
			Sailing.ExplorationRadiusAt100,
			Sailing.HelmExperiencePerKilometre,
			Sailing.CrewExperienceShare,
			WindCall.Level,
			WindCall.Duration,
			WindCall.Cooldown,
			WindCall.Key,
			WindCall.Pad,
			Lookout.Level,
			Lookout.Radius,
			Lookout.Duration,
			Lookout.Cooldown,
			Lookout.Key,
			Lookout.Pad,
			Display.ShipPanel,
			Display.SpeedUnit,
			Display.BoatCameraMaxDistance,
			Display.SailOpacity,
			Display.SailOpacityBothSides,
			Display.SailOpacityOnDeck,
			Display.NameVanillaShips,
			Cheats.AshlandsImmuneShips,
			Cheats.AshlandsImmuneList,
			Cheats.InvulnerableShips,
			Cheats.InvulnerableList,
			Cheats.Tailwind,
			Cheats.FastSailing
		});

		private static readonly Lazy<CatalogSettings[]> catalog = new Lazy<CatalogSettings[]>(() => ShipCatalog.All.Select((ShipType t) => new CatalogSettings(t)).ToArray());

		private static readonly Lazy<KeelSetting[]> all = new Lazy<KeelSetting[]>(() => general.Value.Concat(catalog.Value.SelectMany((CatalogSettings c) => c.Entries())).ToArray());

		private static readonly Lazy<Dictionary<string, KeelSetting>> byId = new Lazy<Dictionary<string, KeelSetting>>(() => all.Value.ToDictionary<KeelSetting, string>((KeelSetting s) => s.Id, StringComparer.OrdinalIgnoreCase));

		public static IReadOnlyList<KeelSetting> General => general.Value;

		public static IReadOnlyList<KeelSetting> All => all.Value;

		public static IReadOnlyList<KeelSetting<string>> KeyboardShortcuts { get; } = Array.AsReadOnly(new KeelSetting<string>[2]
		{
			WindCall.Key,
			Lookout.Key
		});

		private static KeelSetting<string> Shortcut(string section, string key, string defaultValue, string description)
		{
			return KeelSetting.Text(section, key, defaultValue, SettingScope.Local, description, (string v) => (v ?? "").Trim());
		}

		public static CatalogSettings Catalog(KeelShip ship)
		{
			return catalog.Value[(int)ship];
		}

		public static KeelSetting? Find(string? id)
		{
			if (id == null || !byId.Value.TryGetValue(id.Trim(), out KeelSetting value))
			{
				return null;
			}
			return value;
		}
	}
	public readonly struct LockTarget
	{
		public string Id { get; }

		public bool Lockable { get; }

		public LockTarget(string id, bool lockable)
		{
			Id = id;
			Lockable = lockable;
		}
	}
	public sealed class LockList
	{
		private readonly HashSet<string> locked;

		public static LockList Empty { get; } = new LockList(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());

		public IReadOnlyList<string> Locked { get; }

		public IReadOnlyList<string> Unknown { get; }

		public IReadOnlyList<string> NotLockable { get; }

		private LockList(IReadOnlyList<string> lockedIds, IReadOnlyList<string> unknown, IReadOnlyList<string> notLockable)
		{
			Locked = lockedIds;
			Unknown = unknown;
			NotLockable = notLockable;
			locked = new HashSet<string>(lockedIds, StringComparer.OrdinalIgnoreCase);
		}

		public static LockList Parse(string? text, Func<string, LockTarget?> find)
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			string[] array = (text ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					LockTarget? lockTarget = find(text2);
					List<string> list4 = ((!lockTarget.HasValue) ? list2 : (lockTarget.Value.Lockable ? list : list3));
					string text3 = lockTarget?.Id ?? text2;
					if (!list4.Contains<string>(text3, StringComparer.OrdinalIgnoreCase))
					{
						list4.Add(text3);
					}
				}
			}
			if (list.Count + list2.Count + list3.Count != 0)
			{
				return new LockList(list, list2, list3);
			}
			return Empty;
		}

		public bool IsLocked(string? id)
		{
			if (id != null)
			{
				return locked.Contains(id.Trim());
			}
			return false;
		}

		public string Describe()
		{
			string text = Locked.Count + " locked";
			if (Unknown.Count > 0)
			{
				text = text + "; unknown: " + string.Join(", ", Unknown);
			}
			if (NotLockable.Count > 0)
			{
				text = text + "; not lockable: " + string.Join(", ", NotLockable);
			}
			return text;
		}
	}
	public static class LockedValues
	{
		public static string Encode(IEnumerable<KeyValuePair<string, string>> values)
		{
			if (values == null)
			{
				throw new ArgumentNullException("values");
			}
			return string.Join(";", values.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> v) => Escape(v.Key) + "=" + Escape(v.Value ?? "")));
		}

		public static IReadOnlyDictionary<string, string> Decode(string? text)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrEmpty(text))
			{
				return dictionary;
			}
			foreach (string item in Split(text, ';'))
			{
				List<string> list = Split(item, '=').ToList();
				if (list.Count == 2)
				{
					string text2 = Unescape(list[0]);
					string text3 = Unescape(list[1]);
					if (!string.IsNullOrWhiteSpace(text2) && text3 != null && !dictionary.ContainsKey(text2.Trim()))
					{
						dictionary.Add(text2.Trim(), text3);
					}
				}
			}
			return dictionary;
		}

		public static string Compose(LockList list, Func<string, string?> ownText)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (ownText == null)
			{
				throw new ArgumentNullException("ownText");
			}
			return Encode(from id in list.Locked
				select new KeyValuePair<string, string>(id, ownText(id)) into p
				where p.Value != null
				select new KeyValuePair<string, string>(p.Key, p.Value));
		}

		private static string Escape(string text)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			foreach (char c in text)
			{
				if (c == '\\' || c == ';' || c == '=')
				{
					stringBuilder.Append('\\');
				}
				stringBuilder.Append(c);
			}
			return stringBuilder.ToString();
		}

		private static IEnumerable<string> Split(string text, char separator)
		{
			int num = 0;
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\\')
				{
					i++;
				}
				else if (text[i] == separator)
				{
					yield return text.Substring(num, i - num);
					num = i + 1;
				}
			}
			yield return text.Substring(num);
		}

		private static string? Unescape(string text)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\\' && ++i >= text.Length)
				{
					return null;
				}
				stringBuilder.Append(text[i]);
			}
			return stringBuilder.ToString();
		}
	}
	public readonly struct PlayerLock
	{
		public bool Locked { get; }

		public string? ServerText { get; }

		public PlayerLock(bool locked, string? serverText)
		{
			Locked = locked;
			ServerText = (locked ? serverText : null);
		}

		public override string ToString()
		{
			if (!Locked)
			{
				return "Own";
			}
			return "Locked (" + ServerText + ")";
		}
	}
	public static class PlayerLocks
	{
		public static PlayerLock Evaluate(string id, bool serverBinds, LockList list, IReadOnlyDictionary<string, string> values)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (values == null)
			{
				throw new ArgumentNullException("values");
			}
			if (!serverBinds || !list.IsLocked(id) || !values.TryGetValue(id.Trim(), out string value))
			{
				return new PlayerLock(locked: false, null);
			}
			return new PlayerLock(locked: true, value);
		}
	}
	public enum SessionKind
	{
		SinglePlayer,
		Host,
		DedicatedServer,
		RemoteClient
	}
	public enum ServerKind
	{
		Unknown,
		Vanilla,
		CrucibleWithoutKeel,
		Keel
	}
	public enum BlockReason
	{
		None,
		SetByServer,
		VanillaServer,
		ProductDisabled,
		NotAdmin,
		AuthorityPending
	}
	public readonly struct SessionFacts
	{
		public static SessionFacts SinglePlayer { get; } = new SessionFacts(SessionKind.SinglePlayer, ServerKind.Keel, admin: true);

		public SessionKind Kind { get; }

		public ServerKind Server { get; }

		public bool Admin { get; }

		public bool IsAuthority => Kind != SessionKind.RemoteClient;

		public SessionFacts(SessionKind kind, ServerKind server, bool admin)
		{
			Kind = kind;
			Server = server;
			Admin = admin;
		}
	}
	public static class SettingAccess
	{
		public static BlockReason Decide(SettingScope scope, SessionFacts facts)
		{
			if (facts.IsAuthority || scope == SettingScope.Local)
			{
				return BlockReason.None;
			}
			switch (facts.Server)
			{
			case ServerKind.Unknown:
				return BlockReason.AuthorityPending;
			case ServerKind.Vanilla:
				return BlockReason.VanillaServer;
			case ServerKind.CrucibleWithoutKeel:
				return BlockReason.ProductDisabled;
			default:
				if (facts.Admin)
				{
					return BlockReason.None;
				}
				if (scope != SettingScope.Cheat)
				{
					return BlockReason.SetByServer;
				}
				return BlockReason.NotAdmin;
			}
		}

		public static bool CheatLocked(SessionFacts facts)
		{
			if (!facts.IsAuthority)
			{
				return facts.Server != ServerKind.Keel;
			}
			return false;
		}
	}
	public sealed class ShipList
	{
		private readonly HashSet<string> names;

		public static ShipList Empty { get; } = new ShipList(all: false, Array.Empty<string>());

		public static ShipList Every { get; } = new ShipList(all: true, Array.Empty<string>());

		public bool All { get; }

		public IReadOnlyList<string> Names { get; }

		public bool IsEmpty
		{
			get
			{
				if (!All)
				{
					return Names.Count == 0;
				}
				return false;
			}
		}

		private ShipList(bool all, IReadOnlyList<string> ordered)
		{
			All = all;
			Names = ordered;
			names = new HashSet<string>(ordered, StringComparer.Ordinal);
		}

		public static ShipList Parse(string? text)
		{
			List<string> list = new List<string>();
			string[] array = (text ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					if (text2 == "*")
					{
						return Every;
					}
					if (!list.Contains<string>(text2, StringComparer.Ordinal))
					{
						list.Add(text2);
					}
				}
			}
			if (list.Count != 0)
			{
				return new ShipList(all: false, list);
			}
			return Empty;
		}

		public bool Matches(string? prefab)
		{
			if (!string.IsNullOrEmpty(prefab))
			{
				if (!All)
				{
					return names.Contains(prefab);
				}
				return true;
			}
			return false;
		}

		public override string ToString()
		{
			if (!All)
			{
				return string.Join(", ", Names);
			}
			return "*";
		}

		public static string Normalize(string? text)
		{
			return Parse(text).ToString();
		}
	}
}
namespace ExtraPotions.Keel.Catalog
{
	public static class ShipShape
	{
		public const float Minimum = 0.5f;

		public const float Maximum = 2f;

		public static (float X, float Y, float Z) LocalScale(float width, float length, float scale)
		{
			float num = Clamp(width);
			float num2 = Clamp(length);
			float num3 = Clamp(scale);
			return (X: num * num3, Y: num3, Z: num2 * num3);
		}

		public static (float SteerForceOffset, float SailForceOffset, float WaterLevelOffset) Offsets(float steerForceOffset, float sailForceOffset, float waterLevelOffset, float width, float length, float scale)
		{
			(float, float, float) tuple = LocalScale(width, length, scale);
			return (SteerForceOffset: steerForceOffset * tuple.Item3, SailForceOffset: sailForceOffset * tuple.Item2, WaterLevelOffset: waterLevelOffset * tuple.Item2);
		}

		private static float Clamp(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Min(2f, Math.Max(0.5f, value));
			}
			return 1f;
		}
	}
	public static class HoldSize
	{
		public const int MaxColumns = 12;

		public const int MaxRows = 10;

		public const string ColumnsKey = "keel.hold_columns";

		public const string RowsKey = "keel.hold_rows";

		public static (int Columns, int Rows) Resolve(int columns, int rows, int neededColumns, int neededRows)
		{
			return (Columns: Math.Max(Math.Min(12, Math.Max(1, columns)), neededColumns), Rows: Math.Max(Math.Min(10, Math.Max(1, rows)), neededRows));
		}

		public static (int Columns, int Rows) Needed(IEnumerable<(int X, int Y)> occupied)
		{
			int num = 0;
			int num2 = 0;
			foreach (var item3 in occupied ?? Enumerable.Empty<(int, int)>())
			{
				int item = item3.X;
				int item2 = item3.Y;
				num = Math.Max(num, item + 1);
				num2 = Math.Max(num2, item2 + 1);
			}
			return (Columns: num, Rows: num2);
		}

		public static bool HasItems(int byteLength)
		{
			return byteLength > 8;
		}

		public static (int Columns, int Rows) NeededAtLoad(bool hasItems, int storedColumns, int storedRows, int defaultColumns, int defaultRows)
		{
			if (!hasItems)
			{
				return (Columns: 0, Rows: 0);
			}
			if (storedColumns <= 0 || storedRows <= 0)
			{
				return (Columns: defaultColumns, Rows: defaultRows);
			}
			return (Columns: storedColumns, Rows: storedRows);
		}
	}
	public static class HullPaint
	{
		public static bool Painted(float v, int stripes)
		{
			if (stripes <= 0)
			{
				return true;
			}
			float num = (float.IsNaN(v) ? 0f : Math.Min(1f, Math.Max(0f, v)));
			int num2 = 2 * stripes + 1;
			return Math.Min(num2 - 1, (int)(num * (float)num2)) % 2 == 1;
		}

		public static int Bands(IEnumerable<bool> column)
		{
			int num = 0;
			bool flag = false;
			foreach (bool item in column)
			{
				if (item && !flag)
				{
					num++;
				}
				flag = item;
			}
			return num;
		}
	}
	public static class HammerOrder
	{
		public static IReadOnlyList<string> Arrange(IEnumerable<string> pieces, IEnumerable<ShipType> catalog)
		{
			List<string> list = (pieces ?? Enumerable.Empty<string>()).ToList();
			List<ShipType> source = (catalog ?? Enumerable.Empty<ShipType>()).Where((ShipType t) => list.Contains(t.Prefab)).ToList();
			HashSet<string> keel = new HashSet<string>(source.Select((ShipType t) => t.Prefab), StringComparer.Ordinal);
			List<string> list2 = list.Where((string p) => !keel.Contains(p)).ToList();
			List<string> list3 = new List<string>();
			foreach (string hull in source.Select((ShipType t) => t.BaseHull).Distinct())
			{
				int num = list2.IndexOf(hull);
				List<string> collection = (from t in source
					where t.BaseHull == hull
					select t.Prefab).ToList();
				if (num < 0)
				{
					list3.AddRange(collection);
				}
				else
				{
					list2.InsertRange(num + 1, collection);
				}
			}
			list2.AddRange(source.Select((ShipType t) => t.Prefab).Where(list3.Contains));
			return list2;
		}
	}
	public enum GateDecision
	{
		Accepted,
		RefusedMoving,
		InvalidGate
	}
	public readonly struct RampPose
	{
		public float HingeOut { get; }

		public float HingeHeight { get; }

		public float Length { get; }

		public float BoardingStart { get; }

		public float Floor { get; }

		public float EndOut => HingeOut + Length * (float)Math.Cos(Math.PI / 6.0);

		public float EndHeight => HingeHeight - Length * (float)Math.Sin(Math.PI / 6.0);

		public float BoardingRun => HingeOut - BoardingStart;

		public float BoardingRise => HingeHeight - Floor;

		public float BoardingLength => (float)Math.Sqrt(BoardingRun * BoardingRun + BoardingRise * BoardingRise);

		public float BoardingSlope
		{
			get
			{
				if (!(BoardingRise <= 0f))
				{
					if (!(BoardingRun <= 0f))
					{
						return (float)(Math.Atan2(BoardingRise, BoardingRun) * 180.0 / Math.PI);
					}
					return 90f;
				}
				return 0f;
			}
		}

		public bool Walkable => BoardingSlope <= 35f;

		public RampPose(float hingeOut, float hingeHeight, float length, float boardingStart, float floor)
		{
			HingeOut = hingeOut;
			HingeHeight = hingeHeight;
			Length = length;
			BoardingStart = boardingStart;
			Floor = floor;
		}
	}
	public static class PenRules
	{
		public const int GatesPerPen = 2;

		public const float RampSlope = 30f;

		public const float MaxWalkableSlope = 35f;

		public const float RampEndBelowDeck = 0.25f;

		public const float MaxRampStretch = 3f;

		public const float RailClearance = 0.05f;

		public const float MinBoardingStart = 0.2f;

		public const float FloorThickness = 0.1f;

		public const float FloorDepth = 1f;

		public const float AtRestSpeed = 0.1f;

		public const string Capability = "meadworks.livestock.pens";

		public const int CapabilityMajor = 1;

		private static readonly string[] GateKeys = BuildGateKeys();

		public static RampPose OpenRamp(float penSide, float floor, float gateLength, IEnumerable<(float Out, float Height)> rail)
		{
			if (rail == null)
			{
				throw new ArgumentNullException("rail");
			}
			float num = (float)Math.Tan(Math.PI / 6.0);
			float num2 = (float)Math.Sin(Math.PI / 6.0);
			float num3 = penSide;
			List<(float, float)> list = new List<(float, float)>();
			foreach (var item in rail)
			{
				if (!float.IsNaN(item.Out) && !float.IsNaN(item.Height) && !float.IsInfinity(item.Out) && !float.IsInfinity(item.Height))
				{
					list.Add(item);
					num3 = Math.Max(num3, item.Out);
				}
			}
			float num4 = floor;
			foreach (var item2 in list)
			{
				if (item2.Item2 > floor)
				{
					num4 = Math.Max(num4, item2.Item2 + 0.05f + (num3 - item2.Item1) * num);
				}
			}
			float boardingStart = ((num4 > floor) ? Math.Max(0.2f, num3 - (num4 - floor) / num) : num3);
			float length = Math.Min(gateLength * 3f, Math.Max(gateLength, (num4 - floor + 0.25f) / num2));
			return new RampPose(num3, num4, length, boardingStart, floor);
		}

		public static float FloorReach(float penSide, RampPose pose)
		{
			return Math.Max(penSide, pose.BoardingStart);
		}

		public static (float Centre, float Height) FloorSolid(float deck)
		{
			return (Centre: deck - 0.5f, Height: 1f);
		}

		public static float GateAngle(int gate, bool open)
		{
			if (!open)
			{
				return 0f;
			}
			return Side(gate) * 120f;
		}

		public static float BoardingAngle(int gate, RampPose pose)
		{
			return Side(gate) * (90f - pose.BoardingSlope);
		}

		private static float Side(int gate)
		{
			if (gate != 0)
			{
				return -1f;
			}
			return 1f;
		}

		public static float RestSpeed(float x, float y, float z)
		{
			return (float)Math.Sqrt(x * x + z * z);
		}

		public static bool LipActive(float speed, float containmentSpeed, bool everyGateClosed)
		{
			if (float.IsNaN(speed))
			{
				return false;
			}
			float num = Math.Abs(speed);
			if (num <= 0.1f)
			{
				return false;
			}
			return num > Math.Max(0f, containmentSpeed) || everyGateClosed;
		}

		public static bool EveryGateClosed(bool[] gateOpen)
		{
			if (gateOpen == null)
			{
				throw new ArgumentNull

plugins/Keel/Keel.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExtraPotions.Keel.Catalog;
using ExtraPotions.Keel.Config;
using ExtraPotions.Keel.Conversion;
using ExtraPotions.Keel.Display;
using ExtraPotions.Keel.Integration;
using ExtraPotions.Keel.Localization;
using ExtraPotions.Keel.Performance;
using ExtraPotions.Keel.Sailing;
using ExtraPotions.Keel.Tuning;
using ExtraPotions.Meadworks.Contracts;
using ExtraPotions.Meadworks.Crucible;
using ExtraPotions.Meadworks.Crucible.UI;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ExtraPotions")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")]
[assembly: AssemblyFileVersion("0.1.15.0")]
[assembly: AssemblyInformationalVersion("0.1.15")]
[assembly: AssemblyProduct("Keel")]
[assembly: AssemblyTitle("Keel")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.15.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ExtraPotions.Keel.Runtime
{
	public static class KeelCatalog
	{
		internal sealed class Built
		{
			public ShipType Type { get; }

			public GameObject Prefab { get; }

			public float SteerForceOffset { get; }

			public float WaterLevelOffset { get; }

			public CatalogSettings Settings => KeelSettings.Catalog(Type.Ship);

			public string AppliedRecipe { get; set; } = "";

			public string RefusedReason { get; set; } = "";

			public bool PenReady { get; set; }

			public Built(ShipType type, GameObject prefab, float steerForceOffset, float waterLevelOffset)
			{
				Type = type;
				Prefab = prefab;
				SteerForceOffset = steerForceOffset;
				WaterLevelOffset = waterLevelOffset;
			}
		}

		private const string Hammer = "Hammer";

		private const string Workbench = "piece_workbench";

		private static readonly string[] VanillaShips = new string[4] { "Raft", "Karve", "VikingShip", "VikingShip_Ashlands" };

		private static readonly Dictionary<KeelShip, Built> built = new Dictionary<KeelShip, Built>();

		private static readonly Dictionary<string, Built> byPrefab = new Dictionary<string, Built>(StringComparer.Ordinal);

		private static readonly Dictionary<string, float> vanillaSailForce = new Dictionary<string, float>(StringComparer.Ordinal);

		private static readonly Dictionary<string, float> vanillaRudderSpeed = new Dictionary<string, float>(StringComparer.Ordinal);

		private static readonly Dictionary<ConfigEntryBase, KeelShip> watched = new Dictionary<ConfigEntryBase, KeelShip>();

		private static readonly HashSet<KeelShip> dirty = new HashSet<KeelShip>();

		private static MeadworksLog? log;

		private static ConfigFile? file;

		private static bool created;

		private static bool hammerDirty;

		private static bool? lastActive;

		private static long lastGeneration = -1L;

		public static bool PensReady => built.Values.Count((Built b) => b.Type.HasPen && b.PenReady) == 3;

		public static int Count => built.Count;

		public static bool Active => KeelModules.Active((KeelModule)2);

		private static bool SteppedAside => KeelModules.StepAside.SteppedAside((KeelModule)2);

		public static void Initialize(MeadworksLog logger)
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			log = logger;
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabs;
			if (SteppedAside)
			{
				return;
			}
			PieceManager.OnPiecesRegistered += OnPiecesRegistered;
			KeelConfig settings = Plugin.Settings;
			if (settings == null)
			{
				return;
			}
			file = settings.File;
			file.SettingChanged += OnSettingChanged;
			file.ConfigReloaded += OnReloaded;
			SynchronizationManager.OnConfigurationSynchronized += OnSynchronized;
			foreach (ShipType item in ShipCatalog.All)
			{
				foreach (KeelSetting item2 in KeelSettings.Catalog(item.Ship).Entries())
				{
					watched[settings.Entry(item2)] = item.Ship;
				}
			}
		}

		public static void Shutdown()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabs;
			PieceManager.OnPiecesRegistered -= OnPiecesRegistered;
			if (file != null)
			{
				file.SettingChanged -= OnSettingChanged;
				file.ConfigReloaded -= OnReloaded;
				SynchronizationManager.OnConfigurationSynchronized -= OnSynchronized;
				file = null;
			}
			watched.Clear();
			dirty.Clear();
			KeelPaint.Dispose();
			lastActive = null;
			lastGeneration = -1L;
			log = null;
		}

		internal static bool TryGet(string prefab, out Built ship)
		{
			return byPrefab.TryGetValue(prefab ?? "", out ship);
		}

		public static bool TryVanillaSailForce(string prefab, out float sailForce)
		{
			if (byPrefab.TryGetValue(prefab ?? "", out Built value))
			{
				prefab = value.Type.BaseHull;
			}
			return vanillaSailForce.TryGetValue(prefab ?? "", out sailForce);
		}

		internal static bool TryVanillaRudderSpeed(string prefab, out float speed)
		{
			return vanillaRudderSpeed.TryGetValue(prefab, out speed);
		}

		public static void Tick()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (!created || SteppedAside)
			{
				return;
			}
			bool active = Active;
			long generation = Plugin.Session.Generation;
			if (lastActive != active || generation != lastGeneration)
			{
				lastActive = active;
				lastGeneration = generation;
				RequestAll();
			}
			if (dirty.Count > 0)
			{
				foreach (KeelShip item in dirty.ToList())
				{
					if (built.TryGetValue(item, out Built b))
					{
						Guard("apply " + b.Type.Prefab, delegate
						{
							Apply(b, scene: false);
						});
					}
				}
				dirty.Clear();
			}
			if (hammerDirty)
			{
				hammerDirty = false;
				Guard("hammer", RefreshHammer);
			}
		}

		private static void RequestAll()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			foreach (KeelShip key in built.Keys)
			{
				dirty.Add(key);
			}
			hammerDirty = true;
		}

		private static void OnVanillaPrefabs()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabs;
			string[] vanillaShips = VanillaShips;
			foreach (string text in vanillaShips)
			{
				GameObject prefab = PrefabManager.Instance.GetPrefab(text);
				Ship val = ((prefab != null) ? prefab.GetComponent<Ship>() : null);
				if ((Object)(object)val != (Object)null)
				{
					vanillaSailForce[text] = val.m_sailForceFactor;
					vanillaRudderSpeed[text] = val.m_rudderSpeed;
				}
			}
			if (SteppedAside || Plugin.Settings == null)
			{
				return;
			}
			foreach (ShipType type in ShipCatalog.All)
			{
				Guard("clone " + type.Prefab, delegate
				{
					Create(type);
				});
			}
			created = built.Count > 0;
			MeadworksLog? obj = log;
			if (obj != null)
			{
				obj.Info("Keel ships built from the game's hulls: " + built.Count + " of " + ShipCatalog.All.Count + ".");
			}
		}

		private static void Create(ShipType type)
		{
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Expected O, but got Unknown
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Expected O, but got Unknown
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			GameObject prefab = PrefabManager.Instance.GetPrefab(type.BaseHull);
			Ship val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Ship>() : null);
			if ((Object)(object)val == (Object)null || (Object)(object)prefab.GetComponent<WearNTear>() == (Object)null || (Object)(object)prefab.GetComponent<Piece>() == (Object)null)
			{
				MeadworksLog? obj = log;
				if (obj != null)
				{
					obj.Warning("Keel " + type.Name + ": the game's " + type.BaseHull + " is missing; the ship is not built.");
				}
				return;
			}
			GameObject val2 = PrefabManager.Instance.CreateClonedPrefab(type.Prefab, prefab);
			Ship component = val2.GetComponent<Ship>();
			Built built = new Built(type, val2, component.m_stearForceOffset, component.m_waterLevelOffset);
			(float, float, float) tuple = Shape(built);
			val2.GetComponent<WearNTear>().m_health = type.Health;
			component.m_sailForceFactor = type.DefaultSailForce(val.m_sailForceFactor);
			component.m_rudderSpeed = type.DefaultRudderSpeed(val.m_rudderSpeed);
			component.m_sailForceOffset = val.m_sailForceOffset * tuple.Item2;
			Piece component2 = val2.GetComponent<Piece>();
			component2.m_name = KeelText.Token(type.TextKey);
			component2.m_description = KeelText.Token(type.DescriptionKey);
			ResolvedRecipe val3 = Resolve(built, (Func<string, GameObject?>)PrefabManager.Instance.GetPrefab);
			SetResources(component2, val3, (Func<string, GameObject?>)PrefabManager.Instance.GetPrefab);
			Hold(built.Prefab, built, scene: true);
			built.PenReady = KeelPen.Build(val2, type);
			if (type.HasPen && !built.PenReady)
			{
				MeadworksLog? obj2 = log;
				if (obj2 != null)
				{
					obj2.Warning("Keel " + type.Name + ": native wood meshes missing; ship stays hidden.");
				}
			}
			if (Plugin.AllowsUi)
			{
				KeelPaint.Apply(built);
				component2.m_icon = Icon(val2) ?? component2.m_icon;
			}
			PieceConfig val4 = new PieceConfig
			{
				PieceTable = "Hammer",
				CraftingStation = "piece_workbench",
				Name = component2.m_name,
				Description = component2.m_description
			};
			foreach (RecipeItem item in val3.Recipe.Items)
			{
				RecipeItem current = item;
				val4.AddRequirement(((RecipeItem)(ref current)).Prefab, ((RecipeItem)(ref current)).Amount, true);
			}
			if (!PieceManager.Instance.AddPiece(new CustomPiece(val2, false, val4)))
			{
				throw new InvalidOperationException("the hammer refused the piece");
			}
			KeelCatalog.built[type.Ship] = built;
			byPrefab[type.Prefab] = built;
		}

		private static Sprite? Icon(GameObject prefab)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return RenderManager.Instance.Render(prefab, RenderManager.IsometricRotation);
			}
			catch (Exception ex)
			{
				MeadworksLog? obj = log;
				if (obj != null)
				{
					obj.Verbose("Keel icon for " + ((Object)prefab).name + " not rendered: " + ex.GetType().Name);
				}
				return null;
			}
		}

		internal static void OnSceneReady()
		{
			if (!created || SteppedAside)
			{
				return;
			}
			foreach (Built b in built.Values)
			{
				Guard("scene " + b.Type.Prefab, delegate
				{
					Apply(b, scene: true);
				});
			}
			hammerDirty = true;
		}

		private static void Apply(Built b, bool scene)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			ApplyRecipe(b, scene);
			(float, float, float) tuple = Shape(b);
			foreach (IMonoUpdater instance in Ship.Instances)
			{
				Ship val = (Ship)(object)((instance is Ship) ? instance : null);
				if (!((Object)(object)val == (Object)null) && !(Utils.GetPrefabName(((Component)val).gameObject) != b.Type.Prefab))
				{
					((Component)val).transform.localScale = new Vector3(tuple.Item1, tuple.Item2, tuple.Item3);
					val.m_stearForceOffset = b.Prefab.GetComponent<Ship>().m_stearForceOffset;
					val.m_waterLevelOffset = b.Prefab.GetComponent<Ship>().m_waterLevelOffset;
					Hold(((Component)val).gameObject, b, scene: false);
				}
			}
			Hold(b.Prefab, b, scene: true);
			if (Plugin.AllowsUi)
			{
				KeelPaint.Apply(b);
			}
			hammerDirty = true;
		}

		private static ResolvedRecipe Resolve(Built b, Func<string, GameObject?> find)
		{
			return Recipe.Resolve(Plugin.Settings.Get<string>(b.Settings.Recipe), b.Type.Recipe, (Func<string, bool>)delegate(string item)
			{
				GameObject obj = find(item);
				return (Object)(object)((obj != null) ? obj.GetComponent<ItemDrop>() : null) != (Object)null;
			});
		}

		private static void ApplyRecipe(Built b, bool scene)
		{
			Func<string, GameObject> find = (string name) => (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(name) : null) ?? PrefabManager.Instance.GetPrefab(name);
			ResolvedRecipe val = Resolve(b, find);
			if (val.UsedFallback && val.Reason != b.RefusedReason)
			{
				MeadworksLog? obj = log;
				if (obj != null)
				{
					obj.Warning(KeelText.Format("recipe_invalid", b.Type.Name, val.Reason));
				}
			}
			b.RefusedReason = (val.UsedFallback ? val.Reason : "");
			string text = ((object)val.Recipe).ToString();
			if ((scene || !(text == b.AppliedRecipe)) && SetResources(b.Prefab.GetComponent<Piece>(), val, find))
			{
				b.AppliedRecipe = text;
				KeelShips.SetBuildCosts(b.Type.Prefab, val.Recipe.Items.Select((RecipeItem i) => ((RecipeItem)(ref i)).Amount).ToArray());
			}
		}

		private static bool SetResources(Piece piece, ResolvedRecipe recipe, Func<string, GameObject?> find)
		{
			if ((Object)(object)piece == (Object)null || recipe.Recipe.Items.Count == 0)
			{
				return false;
			}
			piece.m_resources = ((IEnumerable<RecipeItem>)recipe.Recipe.Items).Select((Func<RecipeItem, Requirement>)((RecipeItem i) => new Requirement
			{
				m_resItem = find(((RecipeItem)(ref i)).Prefab).GetComponent<ItemDrop>(),
				m_amount = ((RecipeItem)(ref i)).Amount,
				m_recover = true
			})).ToArray();
			return true;
		}

		private static (float X, float Y, float Z) Shape(Built b)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			KeelConfig settings = Plugin.Settings;
			CatalogSettings settings2 = b.Settings;
			(float, float, float) result = ShipShape.LocalScale(settings.Get<float>(settings2.Width), settings.Get<float>(settings2.Length), settings.Get<float>(settings2.Scale));
			b.Prefab.transform.localScale = new Vector3(result.Item1, result.Item2, result.Item3);
			(float, float, float) tuple = ShipShape.Offsets(b.SteerForceOffset, 0f, b.WaterLevelOffset, settings.Get<float>(settings2.Width), settings.Get<float>(settings2.Length), settings.Get<float>(settings2.Scale));
			Ship component = b.Prefab.GetComponent<Ship>();
			(component.m_stearForceOffset, _, component.m_waterLevelOffset) = tuple;
			return result;
		}

		internal static void Hold(GameObject root, Built b, bool scene)
		{
			Container val = (((Object)(object)root != (Object)null) ? root.GetComponentInChildren<Container>(true) : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			KeelConfig? settings = Plugin.Settings;
			int num = settings.Get<int>(b.Settings.CargoColumns);
			int num2 = settings.Get<int>(b.Settings.CargoRows);
			if ((Object)(object)root == (Object)(object)b.Prefab)
			{
				(val.m_width, val.m_height) = HoldSize.Resolve(num, num2, 0, 0);
				return;
			}
			Inventory inventory = val.GetInventory();
			if (inventory != null && !val.IsInUse())
			{
				(int, int) tuple2 = HoldSize.Needed(from i in inventory.GetAllItems()
					select (x: i.m_gridPos.x, y: i.m_gridPos.y));
				Resize(val, inventory, HoldSize.Resolve(num, num2, tuple2.Item1, tuple2.Item2));
			}
		}

		internal static void OnContainerAwake(Container container, Ship ship)
		{
			string prefabName = Utils.GetPrefabName(((Component)ship).gameObject);
			Name(container, ship);
			if (byPrefab.TryGetValue(prefabName, out Built value))
			{
				ZDO val = (((Object)(object)container.m_nview != (Object)null) ? container.m_nview.GetZDO() : null);
				Inventory inventory = container.GetInventory();
				if (val != null && inventory != null)
				{
					byte[] byteArray = val.GetByteArray(ZDOVars.s_items, (byte[])null);
					(int, int) tuple = HoldSize.NeededAtLoad(HoldSize.HasItems((byteArray != null) ? byteArray.Length : (-1)), val.GetInt("keel.hold_columns", 0), val.GetInt("keel.hold_rows", 0), value.Type.CargoColumns, value.Type.CargoRows);
					KeelConfig settings = Plugin.Settings;
					Resize(container, inventory, HoldSize.Resolve(settings.Get<int>(value.Settings.CargoColumns), settings.Get<int>(value.Settings.CargoRows), tuple.Item1, tuple.Item2));
				}
			}
		}

		private static void Resize(Container container, Inventory inventory, (int Columns, int Rows) size)
		{
			container.m_width = size.Columns;
			container.m_height = size.Rows;
			inventory.m_width = size.Columns;
			inventory.m_height = size.Rows;
			ZNetView nview = container.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner())
			{
				ZDO zDO = nview.GetZDO();
				if (zDO.GetInt("keel.hold_columns", 0) != size.Columns)
				{
					zDO.Set("keel.hold_columns", size.Columns);
				}
				if (zDO.GetInt("keel.hold_rows", 0) != size.Rows)
				{
					zDO.Set("keel.hold_rows", size.Rows);
				}
			}
		}

		internal static string ShipName(Ship ship)
		{
			if ((Object)(object)ship == (Object)null || (Object)(object)((Component)ship).gameObject == (Object)null || !Active || Plugin.Settings == null)
			{
				return "";
			}
			if (!byPrefab.ContainsKey(Utils.GetPrefabName(((Component)ship).gameObject)) && !Plugin.Settings.Get<bool>(Display.NameVanillaShips))
			{
				return "";
			}
			Piece component = ((Component)ship).GetComponent<Piece>();
			if (!((Object)(object)component != (Object)null) || Localization.instance == null)
			{
				return "";
			}
			return Localization.instance.Localize(component.m_name);
		}

		private static void Name(Container container, Ship ship)
		{
			if ((Object)(object)container == (Object)null || (Object)(object)ship == (Object)null || (Object)(object)((Component)ship).gameObject == (Object)null)
			{
				return;
			}
			string text = ShipName(ship);
			object obj;
			if (text.Length != 0)
			{
				obj = KeelText.Format("storage_name", text);
			}
			else
			{
				ZNetScene instance = ZNetScene.instance;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					GameObject prefab = instance.GetPrefab(Utils.GetPrefabName(((Component)ship).gameObject));
					obj = ((prefab == null) ? null : prefab.GetComponentInChildren<Container>(true)?.m_name);
				}
			}
			string text2 = (string)obj;
			if (!string.IsNullOrEmpty(text2) && !(container.m_name == text2))
			{
				container.m_name = text2;
				Inventory inventory = container.GetInventory();
				if (inventory != null)
				{
					inventory.m_name = text2;
				}
			}
		}

		private static void RenameLoadedHolds()
		{
			Ship[] array = Object.FindObjectsByType<Ship>((FindObjectsSortMode)0);
			foreach (Ship val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null))
				{
					Container componentInChildren = ((Component)val).GetComponentInChildren<Container>();
					if ((Object)(object)componentInChildren != (Object)null)
					{
						Name(componentInChildren, val);
					}
				}
			}
		}

		private static void OnPiecesRegistered()
		{
			hammerDirty = true;
		}

		private static void RefreshHammer()
		{
			PieceTable pieceTable = PieceManager.Instance.GetPieceTable("Hammer");
			if ((Object)(object)pieceTable == (Object)null || Plugin.Settings == null)
			{
				return;
			}
			Dictionary<string, GameObject> dictionary = (from p in pieceTable.m_pieces
				where (Object)(object)p != (Object)null
				group p by ((Object)p).name).ToDictionary<IGrouping<string, GameObject>, string, GameObject>((IGrouping<string, GameObject> g) => g.Key, (IGrouping<string, GameObject> g) => g.First(), StringComparer.Ordinal);
			List<string> list = (from p in pieceTable.m_pieces
				where (Object)(object)p != (Object)null && !byPrefab.ContainsKey(((Object)p).name)
				select ((Object)p).name).ToList();
			foreach (Built value2 in built.Values)
			{
				dictionary[value2.Type.Prefab] = value2.Prefab;
				if (Visible(value2))
				{
					list.Add(value2.Type.Prefab);
				}
			}
			IReadOnlyList<string> readOnlyList = HammerOrder.Arrange((IEnumerable<string>)list, (IEnumerable<ShipType>)ShipCatalog.All);
			pieceTable.m_pieces.Clear();
			foreach (string item in readOnlyList)
			{
				if (dictionary.TryGetValue(item, out var value))
				{
					pieceTable.m_pieces.Add(value);
				}
			}
		}

		private static bool Visible(Built b)
		{
			if (Active && Plugin.Settings.Get<bool>(b.Settings.Enabled))
			{
				if (!b.PenReady)
				{
					return !b.Type.HasPen;
				}
				return true;
			}
			return false;
		}

		private static void OnSettingChanged(object sender, SettingChangedEventArgs e)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (((e != null) ? e.ChangedSetting : null) != null && watched.TryGetValue(e.ChangedSetting, out var value))
			{
				dirty.Add(value);
			}
			if (((e != null) ? e.ChangedSetting : null) != null && Plugin.Settings != null && e.ChangedSetting == Plugin.Settings.Entry((KeelSetting)(object)Display.NameVanillaShips))
			{
				Guard("ship hold names", RenameLoadedHolds);
			}
		}

		private static void OnReloaded(object sender, EventArgs e)
		{
			RequestAll();
		}

		private static void OnSynchronized(object sender, ConfigurationSynchronizationEventArgs e)
		{
			RequestAll();
		}

		private static void Guard(string what, Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				MeadworksLog? obj = log;
				if (obj != null)
				{
					obj.Warning("Keel catalog " + what + ": " + ex.GetType().Name + ": " + ex.Message);
				}
			}
		}

		internal static void Warn(string where, Exception error)
		{
			MeadworksLog? obj = log;
			if (obj != null)
			{
				obj.Warning("Keel " + where + ": " + error.GetType().Name + ": " + error.Message);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class KeelContainerAwakePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Container __instance)
		{
			if ((Object)(object)__instance.m_rootObjectOverride == (Object)null && (Object)(object)((Component)__instance).GetComponent<Ship>() == (Object)null)
			{
				return;
			}
			Ship componentInParent = ((Component)__instance).GetComponentInParent<Ship>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return;
			}
			try
			{
				KeelCatalog.OnContainerAwake(__instance, componentInParent);
			}
			catch (Exception error)
			{
				KeelCatalog.Warn("ship hold", error);
			}
		}
	}
	[HarmonyPatch(typeof(ShipControlls), "GetHoverText")]
	internal static class KeelRudderHoverTextPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ShipControlls __instance, ref string __result)
		{
			RudderText.Rename(__instance, ref __result);
		}
	}
	[HarmonyPatch(typeof(ShipControlls), "GetHoverName")]
	internal static class KeelRudderHoverNamePatch
	{
		[HarmonyPostfix]
		private static void Postfix(ShipControlls __instance, ref string __result)
		{
			RudderText.Rename(__instance, ref __result);
		}
	}
	internal static class RudderText
	{
		public static void Rename(ShipControlls controls, ref string text)
		{
			try
			{
				if ((Object)(object)controls == (Object)null || string.IsNullOrEmpty(text) || Localization.instance == null)
				{
					return;
				}
				string text2 = KeelCatalog.ShipName(controls.m_ship);
				if (text2.Length != 0)
				{
					string text3 = Localization.instance.Localize(controls.m_hoverText);
					if (!string.IsNullOrEmpty(text3) && text.Contains(text3))
					{
						text = text.Replace(text3, KeelText.Format("rudder_use", text2));
					}
				}
			}
			catch (Exception error)
			{
				KeelCatalog.Warn("rudder text", error);
			}
		}
	}
	internal static class KeelPaint
	{
		private sealed class Slot
		{
			public Material Copy { get; }

			public bool Sail { get; }

			public Color Color { get; }

			public Texture? Texture { get; }

			public Texture2D? Generated { get; set; }

			public Slot(Material copy, bool sail)
			{
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				Copy = copy;
				Sail = sail;
				Color = (copy.HasProperty("_Color") ? copy.color : Color.white);
				Texture = (copy.HasProperty("_MainTex") ? copy.mainTexture : null);
			}
		}

		private sealed class State
		{
			public List<Slot> Slots { get; } = new List<Slot>();

			public string Key { get; set; } = "";
		}

		private static readonly Dictionary<KeelShip, State> states = new Dictionary<KeelShip, State>();

		public static void Apply(KeelCatalog.Built b)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			KeelConfig settings = Plugin.Settings;
			if (settings == null || (Object)(object)b?.Prefab == (Object)null)
			{
				return;
			}
			string text = settings.Get<string>(b.Settings.SailColor);
			string text2 = settings.Get<string>(b.Settings.HullColor);
			int num = settings.Get<int>(b.Settings.HullStripes);
			if (!states.TryGetValue(b.Type.Ship, out State value))
			{
				value = (states[b.Type.Ship] = Prepare(b.Prefab));
			}
			string text3 = text + "|" + text2 + "|" + num;
			if (value.Key == text3)
			{
				return;
			}
			value.Key = text3;
			Color b2 = Tint(text);
			Color? val = ((text2.Length == 0) ? ((Color?)null) : new Color?(Tint(text2)));
			foreach (Slot slot in value.Slots)
			{
				if (slot.Sail)
				{
					if (slot.Copy.HasProperty("_Color"))
					{
						slot.Copy.color = Multiply(slot.Color, b2);
					}
					continue;
				}
				Texture mainTexture = slot.Texture;
				Color color = slot.Color;
				if (val.HasValue && num <= 0)
				{
					color = Multiply(slot.Color, val.Value);
				}
				if ((Object)(object)slot.Generated != (Object)null)
				{
					Object.Destroy((Object)(object)slot.Generated);
					slot.Generated = null;
				}
				if (val.HasValue && num > 0 && (Object)(object)slot.Texture != (Object)null)
				{
					slot.Generated = Striped(slot.Texture, val.Value, num);
					if ((Object)(object)slot.Generated != (Object)null)
					{
						mainTexture = (Texture)(object)slot.Generated;
					}
				}
				if (slot.Copy.HasProperty("_Color"))
				{
					slot.Copy.color = color;
				}
				if (slot.Copy.HasProperty("_MainTex"))
				{
					slot.Copy.mainTexture = mainTexture;
				}
			}
		}

		public static void Dispose()
		{
			states.Clear();
		}

		private static State Prepare(GameObject prefab)
		{
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Expected O, but got Unknown
			State state = new State();
			Ship component = prefab.GetComponent<Ship>();
			WearNTear component2 = prefab.GetComponent<WearNTear>();
			Transform root = (((Object)(object)component != (Object)null && (Object)(object)component.m_sailObject != (Object)null) ? component.m_sailObject.transform : null);
			Transform root2 = (((Object)(object)component != (Object)null && (Object)(object)component.m_mastObject != (Object)null) ? component.m_mastObject.transform : null);
			Transform root3 = (((Object)(object)component != (Object)null && (Object)(object)component.m_rudderObject != (Object)null) ? component.m_rudderObject.transform : null);
			Transform[] array = (Transform[])(((Object)(object)component2 == (Object)null) ? ((Array)new Transform[0]) : ((Array)(from g in (IEnumerable<GameObject>)(object)new GameObject[3] { component2.m_new, component2.m_worn, component2.m_broken }
				where (Object)(object)g != (Object)null
				select g.transform).ToArray()));
			Dictionary<(Material, bool), Material> dictionary = new Dictionary<(Material, bool), Material>();
			Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (val is ParticleSystemRenderer || val is LineRenderer || val is TrailRenderer)
				{
					continue;
				}
				Transform t = ((Component)val).transform;
				bool flag = Under(t, root);
				bool flag2 = !flag && !Under(t, root2) && !Under(t, root3) && (array.Length == 0 || array.Any((Transform h) => Under(t, h)));
				if (!flag && !flag2)
				{
					continue;
				}
				Material[] sharedMaterials = val.sharedMaterials;
				for (int num2 = 0; num2 < sharedMaterials.Length; num2++)
				{
					Material val2 = sharedMaterials[num2];
					if (!((Object)(object)val2 == (Object)null))
					{
						if (!dictionary.TryGetValue((val2, flag), out var value))
						{
							value = new Material(val2)
							{
								name = ((Object)val2).name + " (" + ((Object)prefab).name + ")"
							};
							dictionary[(val2, flag)] = value;
							state.Slots.Add(new Slot(value, flag));
						}
						sharedMaterials[num2] = value;
					}
				}
				val.sharedMaterials = sharedMaterials;
			}
			return state;
		}

		private static bool Under(Transform t, Transform? root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return false;
			}
			Transform val = t;
			while ((Object)(object)val != (Object)null)
			{
				if ((Object)(object)val == (Object)(object)root)
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private static Color Tint(string hex)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			HexColor val = default(HexColor);
			if (!HexColor.TryParse(hex, ref val))
			{
				return Color.white;
			}
			return new Color(((HexColor)(ref val)).Red, ((HexColor)(ref val)).Green, ((HexColor)(ref val)).Blue, 1f);
		}

		private static Color Multiply(Color a, Color b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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)
			return new Color(a.r * b.r, a.g * b.g, a.b * b.b, a.a);
		}

		private static Texture2D? Striped(Texture source, Color paint, int stripes)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_0098: 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)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.Clamp(source.width, 4, 2048);
			int num2 = Mathf.Clamp(source.height, 4, 2048);
			RenderTexture temporary = RenderTexture.GetTemporary(num, num2, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2);
			RenderTexture active = RenderTexture.active;
			try
			{
				Graphics.Blit(source, temporary);
				RenderTexture.active = temporary;
				Texture2D val = new Texture2D(num, num2, (TextureFormat)4, true, false)
				{
					name = ((Object)source).name + " (Keel paint)",
					wrapMode = source.wrapMode,
					filterMode = source.filterMode,
					anisoLevel = source.anisoLevel
				};
				val.ReadPixels(new Rect(0f, 0f, (float)num, (float)num2), 0, 0);
				Color32[] pixels = val.GetPixels32();
				for (int i = 0; i < num2; i++)
				{
					if (HullPaint.Painted(((float)i + 0.5f) / (float)num2, stripes))
					{
						for (int j = 0; j < num; j++)
						{
							Color32 val2 = pixels[i * num + j];
							pixels[i * num + j] = new Color32((byte)((float)(int)val2.r * paint.r), (byte)((float)(int)val2.g * paint.g), (byte)((float)(int)val2.b * paint.b), val2.a);
						}
					}
				}
				val.SetPixels32(pixels);
				val.Apply(true, false);
				return val;
			}
			catch (Exception)
			{
				return null;
			}
			finally
			{
				RenderTexture.active = active;
				RenderTexture.ReleaseTemporary(temporary);
			}
		}
	}
	internal sealed class KeelPen : MonoBehaviour
	{
		private static readonly HashSet<KeelPen> instances = new HashSet<KeelPen>();

		[SerializeField]
		internal Bounds Box;

		[SerializeField]
		internal Transform[] Gates = Array.Empty<Transform>();

		[SerializeField]
		internal BoxCollider[] Lips = Array.Empty<BoxCollider>();

		[SerializeField]
		internal Vector3[] ClosedHinges = Array.Empty<Vector3>();

		[SerializeField]
		internal Vector3[] OpenHinges = Array.Empty<Vector3>();

		[SerializeField]
		internal float[] OpenStretch = Array.Empty<float>();

		[SerializeField]
		internal GameObject?[] Boarding = Array.Empty<GameObject>();

		private Ship? ship;

		private ZNetView? view;

		private Collider[] collision = Array.Empty<Collider>();

		private IDisposable? lease;

		private Tuple<Func<ZNetView, Bounds, int, IDisposable>, Func<Vector3, Tuple<ZNetView, int, bool>>>? provider;

		private int registeredCapacity = -1;

		private Vector3 registeredScale;

		private long generation = -1L;

		private double nextProvider;

		private bool warned;

		private readonly ChangeGate<bool> collisionOn = new ChangeGate<bool>();

		private readonly ChangeGate<bool> lipOn = new ChangeGate<bool>();

		private bool[] gateOpen = Array.Empty<bool>();

		private int[] posed = Array.Empty<int>();

		private static readonly int[] gateHashes = BuildGateHashes();

		internal ZNetView? View => view;

		internal bool Live
		{
			get
			{
				if (Plugin.Ready && KeelCatalog.Active && (Object)(object)view != (Object)null && view.IsValid())
				{
					return (Object)(object)ship != (Object)null;
				}
				return false;
			}
		}

		internal float Speed
		{
			get
			{
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)ship == (Object)null || (Object)(object)ship.m_body == (Object)null)
				{
					return 0f;
				}
				Vector3 linearVelocity = ship.m_body.linearVelocity;
				return PenRules.RestSpeed(linearVelocity.x, linearVelocity.y, linearVelocity.z);
			}
		}

		private static int[] BuildGateHashes()
		{
			int[] array = new int[2];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = StringExtensionMethods.GetStableHashCode(PenRules.GateKey(i));
			}
			return array;
		}

		internal bool Open(int gate)
		{
			if ((Object)(object)view != (Object)null && view.IsValid())
			{
				return view.GetZDO().GetBool(gateHashes[gate], false);
			}
			return false;
		}

		internal Vector3 GatePoint(int gate)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (gate >= ClosedHinges.Length)
			{
				return Gates[gate].position;
			}
			return ((Component)this).transform.TransformPoint(ClosedHinges[gate]);
		}

		internal static bool Build(GameObject root, ShipType type)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Invalid comparison between Unknown and I4
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Invalid comparison between Unknown and I4
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Invalid comparison between Unknown and I4
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)
			//IL_0325: Unknown result type (might be due to invalid IL or missing references)
			//IL_0387: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_0429: Unknown result type (might be due to invalid IL or missing references)
			//IL_0439: Unknown result type (might be due to invalid IL or missing references)
			//IL_0468: Unknown result type (might be due to invalid IL or missing references)
			//IL_046f: Expected O, but got Unknown
			//IL_0496: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0501: Unknown result type (might be due to invalid IL or missing references)
			//IL_051c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0556: Unknown result type (might be due to invalid IL or missing references)
			//IL_0564: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ae: Expected O, but got Unknown
			//IL_05da: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0725: Unknown result type (might be due to invalid IL or missing references)
			//IL_072a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0762: Unknown result type (might be due to invalid IL or missing references)
			//IL_0770: Unknown result type (might be due to invalid IL or missing references)
			//IL_0647: Unknown result type (might be due to invalid IL or missing references)
			//IL_064c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0684: Unknown result type (might be due to invalid IL or missing references)
			//IL_0692: Unknown result type (might be due to invalid IL or missing references)
			if (!type.HasPen)
			{
				return true;
			}
			GameObject prefab = PrefabManager.Instance.GetPrefab("wood_pole");
			GameObject prefab2 = PrefabManager.Instance.GetPrefab("wood_beam");
			MeshFilter val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponentInChildren<MeshFilter>(true) : null);
			MeshFilter val2 = (((Object)(object)prefab2 != (Object)null) ? prefab2.GetComponentInChildren<MeshFilter>(true) : null);
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val.sharedMesh) || !Object.op_Implicit((Object)(object)val2.sharedMesh) || !Object.op_Implicit((Object)(object)((Component)val).GetComponent<MeshRenderer>()) || !Object.op_Implicit((Object)(object)((Component)val2).GetComponent<MeshRenderer>()))
			{
				return false;
			}
			Bounds bounds = val.sharedMesh.bounds;
			if (Usable(((Bounds)(ref bounds)).size))
			{
				bounds = val2.sharedMesh.bounds;
				if (Usable(((Bounds)(ref bounds)).size))
				{
					GameObject val3 = new GameObject("Keel deck pen");
					val3.SetActive(false);
					val3.transform.SetParent(root.transform, false);
					try
					{
						KeelPen keelPen = val3.AddComponent<KeelPen>();
						float num = (((int)type.Ship == 5) ? 2f : 2.4f);
						float num2 = (((int)type.Ship == 5) ? 3f : 6.4f);
						float num3 = (((int)type.Ship == 7) ? 1.8f : 1.3f);
						float num4 = num / 2f;
						float num5 = num2 / 2f;
						float num6 = 1.6f;
						List<(float, float)>[] rails;
						float num7 = Measure(root, num4, out rails);
						keelPen.Box = new Bounds(new Vector3(0f, num7 + num3 / 2f, 0f), new Vector3(num, num3 + 0.8f, num2));
						List<BoxCollider> list = new List<BoxCollider>();
						float[] array = new float[2] { -1f, 1f };
						Vector3 center = default(Vector3);
						Vector3 center2 = default(Vector3);
						foreach (float num8 in array)
						{
							float[] array2 = new float[2] { -1f, 1f };
							foreach (float num9 in array2)
							{
								Member(val3.transform, val, new Vector3(num8 * num4, num7 + num3 / 2f, num9 * num5), Vector3.up, num3, 0.14f);
								float num10 = (num2 - num6) / 2f;
								((Vector3)(ref center))..ctor(num8 * num4, num7 + num3 / 2f, num9 * (num6 / 2f + num10 / 2f));
								Fence(val3.transform, val2, center, num10, num3, alongZ: true);
							}
							((Vector3)(ref center2))..ctor(0f, num7 + num3 / 2f, num8 * num5);
							Fence(val3.transform, val2, center2, num, num3, alongZ: false);
							list.Add(Block(val3.transform, new Vector3(num8 * num4, num7 + num3 + 1f, 0f), new Vector3(0.12f, 2f, num2)));
							list.Add(Block(val3.transform, new Vector3(0f, num7 + num3 + 1f, num8 * num5), new Vector3(num, 2f, 0.12f)));
						}
						array = new float[2] { -1f, 1f };
						foreach (float num11 in array)
						{
							list.Add(Block(val3.transform, new Vector3(num11 * num4, num7 + num3 / 2f, 0f), new Vector3(0.12f, num3, num6)));
						}
						keelPen.Lips = list.ToArray();
						keelPen.Gates = (Transform[])(object)new Transform[2];
						keelPen.ClosedHinges = (Vector3[])(object)new Vector3[2];
						keelPen.OpenHinges = (Vector3[])(object)new Vector3[2];
						keelPen.OpenStretch = new float[2];
						keelPen.Boarding = (GameObject?[])(object)new GameObject[2];
						float num12 = 0.1f;
						(float, float) tuple = PenRules.FloorSolid(num7);
						Block(val3.transform, new Vector3(0f, tuple.Item1, 0f), new Vector3(num, tuple.Item2, num2));
						for (int k = 0; k < keelPen.Gates.Length; k++)
						{
							float num13 = ((k != 0) ? 1 : (-1));
							GameObject val4 = new GameObject("Keel gate " + k);
							val4.transform.SetParent(val3.transform, false);
							val4.transform.localPosition = new Vector3(num13 * num4, num7, 0f);
							keelPen.Gates[k] = val4.transform;
							RampPose val5 = PenRules.OpenRamp(num4, num7, num3, (IEnumerable<ValueTuple<float, float>>)rails[k]);
							keelPen.ClosedHinges[k] = val4.transform.localPosition;
							keelPen.OpenHinges[k] = new Vector3(num13 * ((RampPose)(ref val5)).HingeOut, ((RampPose)(ref val5)).HingeHeight, 0f);
							keelPen.OpenStretch[k] = ((RampPose)(ref val5)).Length / num3;
							float num14 = PenRules.FloorReach(num4, val5);
							if (num14 > num4 + 0.01f)
							{
								Block(val3.transform, new Vector3(num13 * (num4 + num14) / 2f, num7 - num12 / 2f, 0f), new Vector3(num14 - num4, num12, num6));
							}
							if (((RampPose)(ref val5)).BoardingRise > 0.01f && ((RampPose)(ref val5)).BoardingLength > 0.05f)
							{
								GameObject val6 = new GameObject("Keel gate " + k + " boarding");
								val6.transform.SetParent(val3.transform, false);
								val6.transform.localPosition = new Vector3(num13 * ((RampPose)(ref val5)).BoardingStart, num7, 0f);
								val6.transform.localRotation = Quaternion.Euler(0f, 0f, PenRules.BoardingAngle(k, val5));
								float boardingLength = ((RampPose)(ref val5)).BoardingLength;
								float num15 = num6 - 0.1f;
								for (int l = 0; l < 8; l++)
								{
									Member(val6.transform, val2, new Vector3(0f, boardingLength / 2f, ((float)l - 3.5f) * num15 / 8f), Vector3.up, boardingLength, num15 / 8f);
								}
								Block(val6.transform, new Vector3(0f, boardingLength / 2f, 0f), new Vector3(0.15f, boardingLength, num15));
								val6.SetActive(false);
								keelPen.Boarding[k] = val6;
							}
							if (!((RampPose)(ref val5)).Walkable)
							{
								KeelCatalog.Warn("deck pen gate " + k, new InvalidOperationException("the boarding ramp over the rail is " + ((RampPose)(ref val5)).BoardingSlope.ToString("0") + " degrees, steeper than animals climb"));
							}
							for (int m = 0; m < 8; m++)
							{
								Member(val4.transform, val2, new Vector3(0f, num3 / 2f, ((float)m - 3.5f) * num6 / 8f), Vector3.up, num3, num6 / 8f);
							}
							((Component)Block(val4.transform, new Vector3(0f, num3 / 2f, 0f), new Vector3(0.15f, num3, num6))).gameObject.AddComponent<KeelPenGate>().Index = k;
						}
						val3.SetActive(true);
						return true;
					}
					catch (Exception error)
					{
						Object.Destroy((Object)(object)val3);
						KeelCatalog.Warn("deck pen construction", error);
						return false;
					}
				}
			}
			return false;
		}

		private static float Measure(GameObject root, float penSide, out List<(float Out, float Height)>[] rails)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Keel deck measurement")
			{
				hideFlags = (HideFlags)61,
				layer = 2
			};
			try
			{
				List<Collider> list = new List<Collider>();
				Collider[] componentsInChildren = root.GetComponentsInChildren<Collider>(true);
				foreach (Collider val2 in componentsInChildren)
				{
					if (val2.enabled && !val2.isTrigger && ActiveBelowRoot(((Component)val2).transform, root.transform))
					{
						GameObject val3 = new GameObject(((Object)val2).name)
						{
							layer = 2
						};
						val3.transform.SetParent(val.transform, false);
						val3.transform.SetPositionAndRotation(((Component)val2).transform.position, ((Component)val2).transform.rotation);
						val3.transform.localScale = ((Component)val2).transform.lossyScale;
						Collider val4 = CopyShape(val2, val3);
						if ((Object)(object)val4 != (Object)null)
						{
							list.Add(val4);
						}
					}
				}
				Physics.SyncTransforms();
				List<float> list2 = new List<float>();
				float[] array = new float[2] { -0.6f, 0.6f };
				foreach (float num in array)
				{
					float[] array2 = new float[2] { -1.4f, 1.4f };
					foreach (float num2 in array2)
					{
						float num3 = Highest(root, list, new Vector3(num, 4f, num2));
						if (!float.IsNegativeInfinity(num3))
						{
							list2.Add(num3);
						}
					}
				}
				if (list2.Count < 2)
				{
					throw new InvalidOperationException("native ship deck collider could not be measured");
				}
				list2.Sort();
				rails = new List<(float, float)>[2]
				{
					new List<(float, float)>(),
					new List<(float, float)>()
				};
				for (int k = 0; k < rails.Length; k++)
				{
					for (int l = 1; l <= 80; l++)
					{
						float num4 = penSide + (float)l * 0.05f;
						float num5 = float.NegativeInfinity;
						array = new float[3] { -0.6f, 0f, 0.6f };
						foreach (float num6 in array)
						{
							num5 = Mathf.Max(num5, Highest(root, list, new Vector3((k == 0) ? (0f - num4) : num4, 4f, num6)));
						}
						if (!float.IsNegativeInfinity(num5))
						{
							rails[k].Add((num4, num5));
						}
					}
				}
				return list2[(list2.Count - 1) / 2] + 0.04f;
			}
			finally
			{
				Object.DestroyImmediate((Object)(object)val);
			}
		}

		private static float Highest(GameObject root, List<Collider> colliders, Vector3 from)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			Ray val = default(Ray);
			((Ray)(ref val))..ctor(root.transform.TransformPoint(from), -root.transform.up);
			float num = float.NegativeInfinity;
			RaycastHit val2 = default(RaycastHit);
			foreach (Collider collider in colliders)
			{
				if (collider.Raycast(val, ref val2, 8f * root.transform.lossyScale.y))
				{
					num = Mathf.Max(num, root.transform.InverseTransformPoint(((RaycastHit)(ref val2)).point).y);
				}
			}
			return num;
		}

		private static bool ActiveBelowRoot(Transform node, Transform root)
		{
			Transform val = node;
			while ((Object)(object)val != (Object)(object)root)
			{
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeSelf)
				{
					return false;
				}
				val = val.parent;
			}
			return true;
		}

		private static Collider? CopyShape(Collider original, GameObject node)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			BoxCollider val = (BoxCollider)(object)((original is BoxCollider) ? original : null);
			if (val != null)
			{
				BoxCollider obj = node.AddComponent<BoxCollider>();
				obj.center = val.center;
				obj.size = val.size;
				return (Collider?)(object)obj;
			}
			MeshCollider val2 = (MeshCollider)(object)((original is MeshCollider) ? original : null);
			if (val2 != null && (Object)(object)val2.sharedMesh != (Object)null)
			{
				MeshCollider obj2 = node.AddComponent<MeshCollider>();
				obj2.cookingOptions = val2.cookingOptions;
				obj2.convex = val2.convex;
				obj2.sharedMesh = val2.sharedMesh;
				return (Collider?)(object)obj2;
			}
			SphereCollider val3 = (SphereCollider)(object)((original is SphereCollider) ? original : null);
			if (val3 != null)
			{
				SphereCollider obj3 = node.AddComponent<SphereCollider>();
				obj3.center = val3.center;
				obj3.radius = val3.radius;
				return (Collider?)(object)obj3;
			}
			CapsuleCollider val4 = (CapsuleCollider)(object)((original is CapsuleCollider) ? original : null);
			if (val4 != null)
			{
				CapsuleCollider obj4 = node.AddComponent<CapsuleCollider>();
				obj4.center = val4.center;
				obj4.radius = val4.radius;
				obj4.height = val4.height;
				obj4.direction = val4.direction;
				return (Collider?)(object)obj4;
			}
			return null;
		}

		private static void Fence(Transform parent, MeshFilter mesh, Vector3 center, float length, float height, bool alongZ)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: 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_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			Vector3 direction = (alongZ ? Vector3.forward : Vector3.right);
			Member(parent, mesh, center + Vector3.up * height * 0.25f, direction, length, 0.14f);
			Member(parent, mesh, center - Vector3.up * height * 0.25f, direction, length, 0.14f);
			Block(parent, center, alongZ ? new Vector3(0.14f, height, length) : new Vector3(length, height, 0.14f));
		}

		private static void Member(Transform parent, MeshFilter source, Vector3 center, Vector3 direction, float length, float thickness)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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_00db: Expected O, but got Unknown
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Mesh sharedMesh = source.sharedMesh;
			Bounds bounds = sharedMesh.bounds;
			int num = ((!(((Bounds)(ref bounds)).size.x >= ((Bounds)(ref bounds)).size.y) || !(((Bounds)(ref bounds)).size.x >= ((Bounds)(ref bounds)).size.z)) ? ((((Bounds)(ref bounds)).size.y >= ((Bounds)(ref bounds)).size.z) ? 1 : 2) : 0);
			Vector3 val = (Vector3)(num switch
			{
				1 => Vector3.up, 
				0 => Vector3.right, 
				_ => Vector3.forward, 
			});
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(thickness / ((Bounds)(ref bounds)).size.x, thickness / ((Bounds)(ref bounds)).size.y, thickness / ((Bounds)(ref bounds)).size.z);
			Vector3 size = ((Bounds)(ref bounds)).size;
			((Vector3)(ref val2))[num] = length / ((Vector3)(ref size))[num];
			GameObject val3 = new GameObject("Keel wood");
			val3.transform.SetParent(parent, false);
			val3.transform.localRotation = Quaternion.FromToRotation(val, direction);
			val3.transform.localScale = val2;
			val3.transform.localPosition = center - val3.transform.localRotation * Vector3.Scale(((Bounds)(ref bounds)).center, val2);
			val3.AddComponent<MeshFilter>().sharedMesh = sharedMesh;
			((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterials = ((Renderer)((Component)source).GetComponent<MeshRenderer>()).sharedMaterials;
		}

		private static bool Usable(Vector3 size)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (size.x > 0f && size.y > 0f && size.z > 0f && !float.IsInfinity(size.x) && !float.IsInfinity(size.y))
			{
				return !float.IsInfinity(size.z);
			}
			return false;
		}

		private static BoxCollider Block(Transform parent, Vector3 center, Vector3 size)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Keel pen collision")
			{
				layer = LayerMask.NameToLayer("piece")
			};
			val.transform.SetParent(parent, false);
			val.transform.localPosition = center;
			BoxCollider obj = val.AddComponent<BoxCollider>();
			obj.size = size;
			return obj;
		}

		private void Awake()
		{
			instances.Add(this);
			ship = ((Component)this).GetComponentInParent<Ship>();
			view = (((Object)(object)ship != (Object)null) ? ((Component)ship).GetComponent<ZNetView>() : null);
			collision = ((Component)this).GetComponentsInChildren<Collider>(true);
		}

		private void Update()
		{
			if (!Live)
			{
				Release();
				ApplyCollision(enabled: false);
				return;
			}
			if (gateOpen.Length != Gates.Length)
			{
				gateOpen = new bool[Gates.Length];
			}
			if (posed.Length != Gates.Length)
			{
				posed = new int[Gates.Length];
				for (int i = 0; i < posed.Length; i++)
				{
					posed[i] = -1;
				}
			}
			for (int j = 0; j < Gates.Length; j++)
			{
				gateOpen[j] = Open(j);
				int num = (gateOpen[j] ? 1 : 0);
				if (posed[j] != num)
				{
					Pose(j, gateOpen[j]);
					posed[j] = num;
				}
			}
			bool flag = PenRules.LipActive(Speed, Plugin.Settings.Get<float>(Pens.ContainmentSpeed), PenRules.EveryGateClosed(gateOpen));
			ApplyCollision(enabled: true);
			if (lipOn.Changed(flag))
			{
				BoxCollider[] lips = Lips;
				for (int k = 0; k < lips.Length; k++)
				{
					((Collider)lips[k]).enabled = flag;
				}
			}
			RegisterPen();
		}

		private void Pose(int i, bool open)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			Transform val = Gates[i];
			bool flag = i < ClosedHinges.Length && i < OpenHinges.Length && i < OpenStretch.Length;
			if (flag)
			{
				val.localPosition = (open ? OpenHinges[i] : ClosedHinges[i]);
			}
			val.localRotation = Quaternion.Euler(0f, 0f, PenRules.GateAngle(i, open));
			val.localScale = (Vector3)((open && flag && OpenStretch[i] > 0f) ? new Vector3(1f, OpenStretch[i], 1f) : Vector3.one);
			GameObject val2 = ((i < Boarding.Length) ? Boarding[i] : null);
			if ((Object)(object)val2 != (Object)null)
			{
				val2.SetActive(open);
			}
		}

		private void SetCollision(bool enabled)
		{
			Collider[] array = collision;
			for (int i = 0; i < array.Length; i++)
			{
				array[i].enabled = enabled;
			}
		}

		private void ApplyCollision(bool enabled)
		{
			if (collisionOn.Changed(enabled))
			{
				SetCollision(enabled);
				lipOn.Reset();
			}
		}

		private void RegisterPen()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			if (Time.realtimeSinceStartupAsDouble < nextProvider)
			{
				return;
			}
			nextProvider = Time.realtimeSinceStartupAsDouble + 0.5;
			CapabilityRegistry capabilities = Plugin.Services.Capabilities;
			IReadOnlyList<CapabilityMetadata> readOnlyList = capabilities.Providers("meadworks.livestock.pens");
			Tuple<Func<ZNetView, Bounds, int, IDisposable>, Func<Vector3, Tuple<ZNetView, int, bool>>> tuple = null;
			if (readOnlyList.Count == 1 && readOnlyList[0].Owner == "com.meadworks.shepherd")
			{
				capabilities.TryResolve<Tuple<Func<ZNetView, Bounds, int, IDisposable>, Func<Vector3, Tuple<ZNetView, int, bool>>>>("meadworks.livestock.pens", new Version(1, 0), ref tuple);
			}
			ShipType val2 = default(ShipType);
			ShipType val = (ShipCatalog.TryGetByPrefab(Utils.GetPrefabName(((Component)ship).gameObject), ref val2) ? val2 : null);
			int num = ((val != null) ? Plugin.Settings.Get<int>(KeelSettings.Catalog(val.Ship).PenCapacity) : 0);
			Vector3 lossyScale = ((Component)ship).transform.lossyScale;
			long num2 = Plugin.Session.Generation;
			if (provider == tuple && lease != null && num == registeredCapacity && lossyScale == registeredScale && generation == num2)
			{
				return;
			}
			Release();
			if (tuple == null)
			{
				return;
			}
			try
			{
				lease = tuple.Item1(view, Box, PenRules.HerdLimit(num));
				provider = tuple;
				registeredCapacity = num;
				registeredScale = lossyScale;
				generation = num2;
			}
			catch (Exception error)
			{
				if (!warned)
				{
					KeelCatalog.Warn("Shepherd pen registration", error);
				}
				warned = true;
			}
		}

		private void Release()
		{
			try
			{
				lease?.Dispose();
			}
			catch (Exception error)
			{
				if (!warned)
				{
					KeelCatalog.Warn("Shepherd pen disposal", error);
				}
				warned = true;
			}
			lease = null;
			provider = null;
		}

		private void OnDisable()
		{
			Release();
		}

		private void OnDestroy()
		{
			Release();
			instances.Remove(this);
		}

		internal static void Shutdown()
		{
			foreach (KeelPen item in new List<KeelPen>(instances))
			{
				if ((Object)(object)item != (Object)null)
				{
					item.Release();
					item.SetCollision(enabled: false);
					item.collisionOn.Reset();
					item.lipOn.Reset();
				}
			}
		}
	}
	internal sealed class KeelPenGate : MonoBehaviour, Hoverable, Interactable
	{
		[SerializeField]
		internal int Index;

		private KeelPen? Pen => ((Component)this).GetComponentInParent<KeelPen>();

		public string GetHoverName()
		{
			if (!((Object)(object)Pen != (Object)null))
			{
				return "";
			}
			return KeelCatalog.ShipName(((Component)Pen).GetComponentInParent<Ship>());
		}

		public float GetHoverOffset()
		{
			return 0f;
		}

		public string GetHoverText()
		{
			if (!((Object)(object)Pen != (Object)null) || !Pen.Live)
			{
				return "";
			}
			return GetHoverName() + "\n[<color=yellow><b>$KEY_Use</b></color>] " + KeelText.Text(Pen.Open(Index) ? "gate_close" : "gate_open");
		}

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

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}
	}
	internal static class KeelPenCommands
	{
		private sealed class Request
		{
			internal ZDOID Ship;

			internal ZDOID Character;

			internal int Gate;

			internal bool Open;

			internal long Actor;

			internal long Peer;
		}

		private sealed class Codec : IOperationCodec<Request, string>
		{
			public byte[] EncodeRequest(Request value)
			{
				using MemoryStream memoryStream = new MemoryStream();
				using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
				binaryWriter.Write(((ZDOID)(ref value.Ship)).UserID);
				binaryWriter.Write(((ZDOID)(ref value.Ship)).ID);
				binaryWriter.Write(value.Gate);
				binaryWriter.Write(value.Open);
				binaryWriter.Write(((ZDOID)(ref value.Character)).UserID);
				binaryWriter.Write(((ZDOID)(ref value.Character)).ID);
				binaryWriter.Write(value.Actor);
				binaryWriter.Write(value.Peer);
				return memoryStream.ToArray();
			}

			public Request DecodeRequest(byte[] bytes)
			{
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Unknown result type (might be due to invalid IL or missing references)
				//IL_0088: Unknown result type (might be due to invalid IL or missing references)
				//IL_008d: Unknown result type (might be due to invalid IL or missing references)
				if (bytes == null || bytes.Length != 45)
				{
					throw new InvalidDataException("Malformed gate request");
				}
				using MemoryStream input = new MemoryStream(bytes);
				using BinaryReader binaryReader = new BinaryReader(input);
				Request request = new Request
				{
					Ship = new ZDOID(binaryReader.ReadInt64(), binaryReader.ReadUInt32()),
					Gate = binaryReader.ReadInt32(),
					Open = binaryReader.ReadBoolean(),
					Character = new ZDOID(binaryReader.ReadInt64(), binaryReader.ReadUInt32()),
					Actor = binaryReader.ReadInt64(),
					Peer = binaryReader.ReadInt64()
				};
				if (request.Ship == ZDOID.None || request.Gate < 0 || request.Gate >= 2 || bytes[16] > 1)
				{
					throw new InvalidDataException("Invalid gate identity");
				}
				return request;
			}

			public byte[] EncodeReply(string value)
			{
				if (value != "ok" && value != "refused" && value != "moving")
				{
					throw new InvalidDataException("Invalid gate reply");
				}
				return Encoding.ASCII.GetBytes(value);
			}

			public string DecodeReply(byte[] bytes)
			{
				if (bytes == null || bytes.Length > 7)
				{
					throw new InvalidDataException("Malformed gate reply");
				}
				string text = Encoding.ASCII.GetString(bytes);
				EncodeReply(text);
				return text;
			}
		}

		private const string Operation = "meadworks.keel.gate";

		private const string OwnerCommand = "meadworks.keel.gate_owner";

		private static IDisposable? operation;

		private static IDisposable? owner;

		private static Task<OperationResult<string>>? pending;

		private static long pendingSession;

		private static Player? pendingActor;

		internal static void Initialize(ProductRegistration registration)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			operation = Plugin.Services.Operations.Register<Request, string>(registration, new OperationDescriptor("meadworks.keel.gate", new Version(1, 0)), (IOperationCodec<Request, string>)(object)new Codec(), (Func<AuthorityContext, Request, OperationExecution<string>>)Apply, (Func<RequestIdentity, OperationExecution<string>>)null);
			owner = Plugin.Services.PeerCommands.Register<Request, string>(registration, new OperationDescriptor("meadworks.keel.gate_owner", new Version(1, 0)), (IOperationCodec<Request, string>)(object)new Codec(), (Func<PeerCommandContext, Request, OperationExecution<string>>)ApplyOwner);
		}

		internal static bool Use(KeelPen pen, int gate)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!pen.Live || (Object)(object)localPlayer == (Object)null || gate < 0 || gate >= 2 || pending != null)
			{
				return false;
			}
			Request request = new Request
			{
				Ship = pen.View.GetZDO().m_uid,
				Gate = gate,
				Open = !pen.Open(gate)
			};
			pendingSession = Plugin.Session.Generation;
			pendingActor = localPlayer;
			pending = Plugin.RequestClient.InvokeAsync<Request, string>("meadworks.keel.gate", request, CancellationToken.None);
			return true;
		}

		private static OperationExecution<string> Apply(AuthorityContext context, Request request)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: 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)
			if (!KeelCatalog.Active || !Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer() || context.Session.Generation != Plugin.Session.Generation)
			{
				return OperationExecution<string>.Rejected("KeelInactive");
			}
			Player val = null;
			long result;
			if (context.IsLocal)
			{
				val = Player.m_localPlayer;
				result = ZNet.GetUID();
			}
			else
			{
				if (!long.TryParse(context.SenderId, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
				{
					return OperationExecution<string>.Rejected("ActorUnavailable");
				}
				ZNetPeer peer = ZNet.instance.GetPeer(result);
				if (peer == null || !peer.IsReady() || peer.m_rpc == null || !peer.m_rpc.IsConnected())
				{
					return OperationExecution<string>.Rejected("ActorUnavailable");
				}
				ZNetScene instance = ZNetScene.instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					GameObject obj2 = instance.FindInstance(peer.m_characterID);
					obj = ((obj2 != null) ? obj2.GetComponent<Player>() : null);
				}
				val = (Player)obj;
			}
			ZNetView val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<ZNetView>() : null);
			if ((Object)(object)val == (Object)null || ((Character)val).IsDead() || val.GetPlayerID() == 0L || (Object)(object)val2 == (Object)null || !val2.IsValid() || val2.GetZDO().GetOwner() != result)
			{
				return OperationExecution<string>.Rejected("ActorUnavailable");
			}
			request.Character = val2.GetZDO().m_uid;
			request.Actor = val.GetPlayerID();
			request.Peer = result;
			if (!Resolve(request, mustOwn: false, out KeelPen pen, out string reason))
			{
				return OperationExecution<string>.Success(reason);
			}
			long num = pen.View.GetZDO().GetOwner();
			context.MarkDecisionMade();
			return Poll(Plugin.PeerCommandClient.SendAsync<Request, string>(num.ToString(CultureInfo.InvariantCulture), "meadworks.keel.gate_owner", context.Identity.RequestId, request, CancellationToken.None));
		}

		private static OperationExecution<string> Poll(Task<OperationResult<string>> task)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			if (!task.IsCompleted)
			{
				return OperationExecution<string>.Pending((Func<OperationExecution<string>>)(() => Poll(task)));
			}
			if (task.Status != TaskStatus.RanToCompletion || (int)task.Result.State != 2)
			{
				return OperationExecution<string>.RecoveryRequired();
			}
			return OperationExecution<string>.Success(task.Result.Reply);
		}

		private static OperationExecution<string> ApplyOwner(PeerCommandContext context, Request request)
		{
			if ((!context.IsLocal && !context.ServerConnectionVerified) || context.Session.Generation != Plugin.Session.Generation)
			{
				return OperationExecution<string>.Rejected("AuthorityUnavailable");
			}
			if (!Resolve(request, mustOwn: true, out KeelPen pen, out string reason))
			{
				return OperationExecution<string>.Success(reason);
			}
			context.MarkDecisionMade();
			pen.View.GetZDO().Set(PenRules.GateKey(request.Gate), request.Open);
			return OperationExecution<string>.Success("ok");
		}

		private static bool Resolve(Request request, bool mustOwn, out KeelPen? pen, out string reason)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			pen = null;
			reason = "refused";
			if (!KeelCatalog.Active || request.Gate < 0 || request.Gate >= 2 || request.Actor == 0L || request.Peer == 0L)
			{
				return false;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject val = ((instance != null) ? instance.FindInstance(request.Ship) : null);
			pen = (((Object)(object)val != (Object)null) ? val.GetComponentInChildren<KeelPen>() : null);
			if ((Object)(object)pen == (Object)null || !pen.Live || pen.View.GetZDO().m_uid != request.Ship || (mustOwn && !pen.View.IsOwner()))
			{
				return false;
			}
			if (!KeelCatalog.TryGet(Utils.GetPrefabName(val), out KeelCatalog.Built ship) || !ship.Type.HasPen || !ship.PenReady)
			{
				return false;
			}
			ZDOMan instance2 = ZDOMan.instance;
			ZDO val2 = ((instance2 != null) ? instance2.GetZDO(request.Character) : null);
			GameObject val3 = ((val2 != null && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(val2.GetPrefab()) : null);
			if (val2 == null || val2.GetOwner() != request.Peer || val2.GetLong(ZDOVars.s_playerID, 0L) != request.Actor || val2.GetBool(ZDOVars.s_dead, false) || (Object)(object)val3 == (Object)null || (Object)(object)val3.GetComponent<Player>() == (Object)null)
			{
				return false;
			}
			Vector3 val4 = pen.GatePoint(request.Gate);
			Vector3 position = val2.GetPosition();
			if (!Finite(position) || !Finite(val4) || Vector3.Distance(position, val4) > 5f || !WardAccess(request.Actor, val4))
			{
				return false;
			}
			KeelConfig settings = Plugin.Settings;
			if ((int)PenRules.Gate(request.Gate, pen.Gates.Length, request.Open, pen.Speed, settings.Get<float>(Pens.ContainmentSpeed), settings.Get<bool>(Pens.GatesOpenAtRestOnly)) != 0)
			{
				reason = "moving";
				return false;
			}
			return true;
		}

		private static bool WardAccess(long actor, Vector3 point)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			foreach (PrivateArea allArea in PrivateArea.m_allAreas)
			{
				if (!((Object)(object)allArea == (Object)null) && allArea.IsEnabled() && allArea.IsInside(point, 0f))
				{
					flag = true;
					if (allArea.m_piece.GetCreator() == actor || allArea.IsPermitted(actor))
					{
						return true;
					}
				}
			}
			return !flag;
		}

		private static bool Finite(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (!float.IsNaN(p.x) && !float.IsInfinity(p.x) && !float.IsNaN(p.y) && !float.IsInfinity(p.y) && !float.IsNaN(p.z))
			{
				return !float.IsInfinity(p.z);
			}
			return false;
		}

		internal static void Tick()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Invalid comparison between Unknown and I4
			if (pending != null && pending.IsCompleted)
			{
				if ((Object)(object)pendingActor == (Object)(object)Player.m_localPlayer && pendingSession == Plugin.Session.Generation && (Object)(object)pendingActor != (Object)null && pending.Status == TaskStatus.RanToCompletion && (int)pending.Result.State == 2 && pending.Result.Reply == "moving")
				{
					((Character)pendingActor).Message((MessageType)2, KeelText.Text("gate_refused_moving"), 0, (Sprite)null, false);
				}
				pending = null;
				pendingActor = null;
			}
		}

		internal static void Shutdown()
		{
			operation?.Dispose();
			owner?.Dispose();
			operation = (owner = null);
			pending = null;
			pendingActor = null;
		}
	}
	public sealed class KeelConfig
	{
		private const string Product = "com.meadworks.keel";

		private static readonly HashSet<KeelSetting> Shortcuts = new HashSet<KeelSetting>((IEnumerable<KeelSetting>)KeelSettings.KeyboardShortcuts);

		private readonly ConfigFile file;

		private readonly Dictionary<KeelSetting, ConfigEntryBase> entries = new Dictionary<KeelSetting, ConfigEntryBase>();

		public ConfigFile File => file;

		public PlayerSettingsLocks? PlayerLocks { get; private set; }

		public ShipList AshlandsImmune => KeelCheats.ShipsInEffect(Cheats.AshlandsImmuneShips, CheatLocked(), Raw(Cheats.AshlandsImmuneShips), Cheats.AshlandsImmuneList, Get<string>(Cheats.AshlandsImmuneList));

		public ShipList Invulnerable => KeelCheats.ShipsInEffect(Cheats.InvulnerableShips, CheatLocked(), Raw(Cheats.InvulnerableShips), Cheats.InvulnerableList, Get<string>(Cheats.InvulnerableList));

		private KeelConfig(ConfigFile file)
		{
			this.file = file;
			bool saveOnConfigSet = file.SaveOnConfigSet;
			file.SaveOnConfigSet = false;
			try
			{
				foreach (KeelSetting item in KeelSettings.All)
				{
					BindSetting(item);
				}
			}
			finally
			{
				file.SaveOnConfigSet = saveOnConfigSet;
			}
			file.Save();
		}

		public void Tick()
		{
			PlayerLocks?.Tick();
		}

		public static KeelConfig Bind(ConfigFile file, MeadworksLog log)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (file == null)
			{
				throw new ArgumentNullException("file");
			}
			if (log == null)
			{
				throw new ArgumentNullException("log");
			}
			KeelConfig keelConfig = new KeelConfig(file);
			foreach (KeyValuePair<KeelSetting, ConfigEntryBase> item in keelConfig.entries.Where<KeyValuePair<KeelSetting, ConfigEntryBase>>((KeyValuePair<KeelSetting, ConfigEntryBase> p) => (int)p.Key.Scope != 0 && !p.Key.Hidden))
			{
				MeadworksBlockedOptions.Track("com.meadworks.keel", item.Value, Map(item.Key.Scope), true);
			}
			keelConfig.PlayerLocks = new PlayerSettingsLocks(file, log);
			log.Verbose("Configuration bound: " + Count((SettingScope)0) + " Local, " + Count((SettingScope)1) + " ServerAuthoritative, " + Count((SettingScope)2) + " Cheat.");
			return keelConfig;
		}

		public static ConfigScope Map(SettingScope scope)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected I4, but got Unknown
			return (ConfigScope)((int)scope switch
			{
				0 => 0, 
				1 => 1, 
				2 => 2, 
				_ => throw new ArgumentException("Unknown scope.", "scope"), 
			});
		}

		public static MischiefCategory CategoryOf(KeelSetting cheat)
		{
			if ((object)cheat == Cheats.AshlandsImmuneShips)
			{
				return (MischiefCategory)5;
			}
			if ((object)cheat == Cheats.InvulnerableShips)
			{
				return (MischiefCategory)2;
			}
			if ((object)cheat == Cheats.Tailwind || (object)cheat == Cheats.FastSailing)
			{
				return (MischiefCategory)0;
			}
			throw new ArgumentException(((cheat != null) ? cheat.Id : null) + " is not a Keel cheat.", "cheat");
		}

		public ConfigEntryBase Entry(KeelSetting setting)
		{
			if (!entries.TryGetValue(setting ?? throw new ArgumentNullException("setting"), out ConfigEntryBase value))
			{
				throw new ArgumentException("Not a Keel setting.", "setting");
			}
			return value;
		}

		public T Get<T>(KeelSetting<T> setting)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			if (Shortcuts.Contains((KeelSetting)(object)setting))
			{
				throw new ArgumentException(((KeelSetting)setting).Id + " is a keyboard shortcut; use Shortcut.", "setting");
			}
			ConfigEntry<T> val = (ConfigEntry<T>)(object)Entry((KeelSetting)(object)setting);
			SettingScope scope = ((KeelSetting)setting).Scope;
			if ((int)scope != 1)
			{
				if ((int)scope == 2)
				{
					return KeelCheats.Effective<T>(setting, CheatLocked(), val.Value);
				}
				return setting.Normalize((PlayerLocks != null) ? PlayerLocks.Effective(val, val.Value) : val.Value);
			}
			return setting.Normalize(KeelAuthority.Effective(val, (ConfigScope)1, setting.Default));
		}

		public KeyboardShortcut Shortcut(KeelSetting<string> setting)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			if (!Shortcuts.Contains((KeelSetting)(object)setting))
			{
				throw new ArgumentException(((KeelSetting)setting).Id + " is not a keyboard shortcut.", "setting");
			}
			ConfigEntry<KeyboardShortcut> val = (ConfigEntry<KeyboardShortcut>)(object)Entry((KeelSetting)(object)setting);
			return PlayerLocks?.Effective<KeyboardShortcut>(val, val.Value) ?? val.Value;
		}

		public ConfigEntry<KeyboardShortcut> ShortcutEntry(KeelSetting<string> setting)
		{
			if (!Shortcuts.Contains((KeelSetting)(object)setting))
			{
				throw new ArgumentException(((KeelSetting)setting).Id + " is not a keyboard shortcut.", "setting");
			}
			return (ConfigEntry<KeyboardShortcut>)(object)Entry((KeelSetting)(object)setting);
		}

		public bool ShortcutLocked(KeelSetting<string> setting)
		{
			return PlayerLocks?.Holds((ConfigEntryBase)(object)ShortcutEntry(setting)) ?? false;
		}

		public bool TryUseCheat(KeelSetting cheat, string? label = null)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			if (cheat == null || (int)cheat.Scope != 2)
			{
				throw new ArgumentException("Expected a Keel cheat.", "cheat");
			}
			return MeadworksBlockedOptions.TryUse("com.meadworks.keel", cheat.Id, (ConfigScope)2, label, true);
		}

		public void Shutdown()
		{
			PlayerLocks?.Dispose();
			PlayerLocks = null;
			foreach (ConfigEntryBase value in entries.Values)
			{
				MeadworksBlockedOptions.Untrack(value);
			}
			entries.Clear();
		}

		private bool Raw(KeelSetting<bool> cheat)
		{
			return ((ConfigEntry<bool>)(object)Entry((KeelSetting)(object)cheat)).Value;
		}

		private static bool CheatLocked()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			return (int)KeelAuthority.Decide((ConfigScope)2).Source == 2;
		}

		private void BindSetting(KeelSetting setting)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntryBase value;
			if (Shortcuts.Contains(setting))
			{
				string text = (string)setting.DefaultValue;
				value = (ConfigEntryBase)(object)MeadworksConfig.Bind<KeyboardShortcut>(file, setting.Section, setting.Key, (text.Length == 0) ? KeyboardShortcut.Empty : KeyboardShortcut.Deserialize(text), setting.Description, Map(setting.Scope), (AcceptableValueBase)null);
			}
			else if (!(setting is KeelSetting<bool> setting2))
			{
				if (!(setting is KeelSetting<int> val))
				{
					if (!(setting is KeelSetting<float> val2))
					{
						if (!(setting is KeelSetting<string> setting3))
						{
							if (!(setting is KeelSetting<SpeedUnit> setting4))
							{
								throw new NotSupportedException(setting.Id + ": unsupported value type " + setting.ValueType.Name + ".");
							}
							value = (ConfigEntryBase)(object)Bind<SpeedUnit>(setting4, null);
						}
						else
						{
							value = (ConfigEntryBase)(object)Bind<string>(setting3, null);
						}
					}
					else
					{
						value = (ConfigEntryBase)(object)Bind<float>(val2, (AcceptableValueBase?)(object)new AcceptableValueRange<float>((float)((KeelSetting)val2).Minimum, (float)((KeelSetting)val2).Maximum));
					}
				}
				else
				{
					value = (ConfigEntryBase)(object)Bind<int>(val, (AcceptableValueBase?)(object)new AcceptableValueRange<int>((int)((KeelSetting)val).Minimum, (int)((KeelSetting)val).Maximum));
				}
			}
			else
			{
				value = (ConfigEntryBase)(object)Bind<bool>(setting2, null);
			}
			entries.Add(setting, value);
		}

		private ConfigDescription Describe(KeelSetting setting, AcceptableValueBase? acceptable)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			ConfigDependency val = null;
			if ((object)setting == Sailing.ShipHealthAt100 || (object)setting == Sailing.ShipSpeedAt100 || (object)setting == Sailing.ExplorationRadiusAt100 || (object)setting == Sailing.HelmExperiencePerKilometre || (object)setting == Sailing.CrewExperienceShare)
			{
				val = new ConfigDependency((Func<bool>)(() => Get<bool>(Sailing.Enabled)), (Func<string>)(() => "Requires " + ((KeelSetting)Sailing.Enabled).Section + ": " + ((KeelSetting)Sailing.Enabled).Key + "."), (Func<bool>)null);
			}
			return new ConfigDescription(setting.Description, acceptable, (val == null) ? Array.Empty<object>() : new object[1] { val });
		}

		private ConfigEntry<T> Bind<T>(KeelSetting<T> setting, AcceptableValueBase? acceptable)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeelSetting)setting).Scope == 2)
			{
				return MeadworksConfig.BindCheat<T>(file, ((KeelSetting)setting).Section, ((KeelSetting)setting).Key, setting.Safe, ((KeelSetting)setting).Description, CategoryOf((KeelSetting)(object)setting), acceptable);
			}
			if (((KeelSetting)setting).Hidden)
			{
				return MeadworksConfig.BindHidden<T>(file, ((KeelSetting)setting).Section, ((KeelSetting)setting).Key, setting.Default, ((KeelSetting)setting).Description, Map(((KeelSetting)setting).Scope), acceptable);
			}
			return MeadworksConfig.Bind<T>(file, ((KeelSetting)setting).Section, ((KeelSetting)setting).Key, setting.Default, Describe((KeelSetting)(object)setting, acceptable), Map(((KeelSetting)setting).Scope));
		}

		private static int Count(SettingScope scope)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			return KeelSettings.All.Count((KeelSetting s) => s.Scope == scope && !s.Hidden);
		}
	}
	internal sealed class KeelSyncReceipt
	{
		private ZNet? network;

		private ZRpc? server;

		private string epoch = "";

		private long world;

		internal long CurrentGeneration
		{
			get
			{
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Invalid comparison between Unknown and I4
				if (!Plugin.IsReady || (Object)(object)network == (Object)null || server == null || !server.IsConnected() || network != ZNet.instance || network.IsServer() || server != network.GetServerRPC())
				{
					return -1L;
				}
				SessionSnapshot session = Plugin.Session;
				if ((int)session.Role != 3 || !session.AuthorityReady || session.WorldId == 0L || string.IsNullOrEmpty(session.SessionId))
				{
					return -1L;
				}
				if (epoch.Length == 0)
				{
					epoch = session.SessionId;
					world = session.WorldId;
				}
				if (!(epoch == session.SessionId) || world != session.WorldId)
				{
					return -1L;
				}
				return session.Generation;
			}
		}

		internal void Observe()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			network = ZNet.instance;
			server = (((Object)(object)network != (Object)null && !network.IsServer()) ? network.GetServerRPC() : null);
			epoch = "";
			world = 0L;
			if (server != null && server.IsConnected() && Plugin.IsReady)
			{
				SessionSnapshot session = Plugin.Session;
				if ((int)session.Role == 3 && !string.IsNullOrEmpty(session.SessionId))
				{
					epoch = session.SessionId;
					world = session.WorldId;
				}
			}
		}
	}
	public sealed class PlayerSettingsLocks : IDisposable
	{
		private readonly ConfigFile file;

		private readonly MeadworksLog log;

		private readonly ConfigEntry<string> listEntry;

		private readonly ConfigEntry<string> carrier;

		private readonly Dictionary<string, ConfigEntryBase> locals = new Dictionary<string, ConfigEntryBase>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, LockTarget> known = new Dictionary<string, LockTarget>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<ConfigEntryBase> tracked = new HashSet<ConfigEntryBase>();

		private readonly KeelSyncReceipt receipt = new KeelSyncReceipt();

		private LockList locks = LockList.Empty;

		private IReadOnlyDictionary<string, string> values = new Dictionary<string, string>();

		private string listText = "";

		private string valuesText = "";

		private float next;

		private bool disposed;

		private bool refreshing;

		private bool dirty = true;

		private readonly Dictionary<ConfigEntryBase, string> ids = new Dictionary<ConfigEntryBase, string>();

		public PlayerSettingsLocks(ConfigFile file, MeadworksLog log)
		{
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Invalid comparison between Unknown and I4
			this.file = file;
			this.log = log;
			ConfigEntryMetadata val2 = default(ConfigEntryMetadata);
			foreach (ConfigDefinition key in file.Keys)
			{
				ConfigEntryBase val = file[key];
				string text = key.Section + "." + key.Key;
				bool flag = MeadworksConfig.TryGetMetadata(val, ref val2) && val2.ProductId == "com.meadworks.keel" && (int)val2.Scope == 0;
				known[text] = new LockTarget(text, flag);
				if (flag)
				{
					locals[text] = val;
				}
			}
			listEntry = MeadworksConfig.Bind<string>(file, "Server", "Locked Player Settings", "", "Comma-separated Local Section.Key ids whose runtime values follow this server. Empty preserves player choices.", (ConfigScope)1, (AcceptableValueBase)null);
			carrier = MeadworksConfig.BindHidden<string>(file, "Server", "Locked Player Values", "", "Managed serialized server values for Locked Player Settings; do not edit.", (ConfigScope)1, (AcceptableValueBase)null);
			MeadworksBlockedOptions.Track("com.meadworks.keel", (ConfigEntryBase)(object)listEntry, (ConfigScope)1, true);
			file.SettingChanged += OnChanged;
			SynchronizationManager.OnConfigurationSynchronized += OnSynchronized;
			Refresh();
		}

		private void OnChanged(object sender, SettingChangedEventArgs args)
		{
			dirty = true;
		}

		private void OnSynchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("com.meadworks.keel") && Plugin.IsReady)
			{
				receipt.Observe();
				dirty = true;
				Refresh();
			}
		}

		private LockList Parse(string text)
		{
			LockTarget value;
			return LockList.Parse(text, (Func<string, LockTarget?>