Decompiled source of Tommys Ship Speed v1.2.0

plugins\TommysShipSpeed\TommysShipSpeed.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Extensions;
using Jotunn.Utils;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace TommyMods.TommysShipSpeed;

[BepInPlugin("tommy.valheim.shipspeed", "Tommy's Ship Speed", "1.2.0")]
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class ShipSpeedPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "tommy.valheim.shipspeed";

	public const string PluginName = "Tommy's Ship Speed";

	public const string PluginVersion = "1.2.0";

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<bool> DebugLogging;

	internal static ConfigEntry<bool> AlwaysWindAtBack;

	internal static ConfigEntry<bool> BoardShipAnywhere;

	internal static ConfigEntry<bool> UseGlobalValues;

	internal static ConfigEntry<float> GlobalRowSpeedMultiplier;

	internal static ConfigEntry<float> GlobalSailSpeedMultiplier;

	internal static ConfigEntry<float> GlobalTurningSpeedMultiplier;

	internal static ConfigEntry<float> GlobalPropulsionRampSeconds;

	internal static ConfigEntry<bool> GlobalRowersAffectSailing;

	internal static ConfigEntry<float> GlobalPerRowerBonus;

	internal static ShipConfig Raft;

	internal static ShipConfig Karve;

	internal static ShipConfig Longship;

	internal static ShipConfig Drakkar;

	private Harmony _harmony;

	internal static ManualLogSource Log;

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

	private static readonly MethodInfo GetAllPlayersMethod = AccessTools.Method(typeof(Player), "GetAllPlayers", (Type[])null, (Type[])null);

	private static readonly MethodInfo GetControlledShipMethod = AccessTools.Method(typeof(Player), "GetControlledShip", (Type[])null, (Type[])null);

	private static readonly FieldInfo PlayerAttachPointField = AccessTools.Field(typeof(Player), "m_attachPoint");

	private void Awake()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Expected O, but got Unknown
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Expected O, but got Unknown
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Expected O, but got Unknown
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Expected O, but got Unknown
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Expected O, but got Unknown
		//IL_026e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0275: Expected O, but got Unknown
		//IL_03a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ac: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		LoadExistingConfigValues();
		ConfigFile config = ((BaseUnityPlugin)this).Config;
		int? num = 100;
		ConfigurationManagerAttributes val = new ConfigurationManagerAttributes();
		val.DispName = "Enabled";
		Enabled = ConfigFileExtensions.BindConfig<bool>(config, "General", "Enabled", true, "Master switch for the mod.", true, num, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val);
		ConfigFile config2 = ((BaseUnityPlugin)this).Config;
		int? num2 = 90;
		ConfigurationManagerAttributes val2 = new ConfigurationManagerAttributes();
		val2.DispName = "DebugLogging";
		DebugLogging = ConfigFileExtensions.BindConfig<bool>(config2, "General", "DebugLogging", false, "Write ship mode, prefab and rower-count information to the BepInEx log.", false, num2, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val2);
		ConfigFile config3 = ((BaseUnityPlugin)this).Config;
		int? num3 = 80;
		ConfigurationManagerAttributes val3 = new ConfigurationManagerAttributes();
		val3.DispName = "AlwaysWindatBack";
		AlwaysWindAtBack = ConfigFileExtensions.BindConfig<bool>(config3, "General", "AlwaysWindAtBack", false, "If enabled, while the local player is actively sailing at half/full sail, Valheim's reported wind direction is forced behind the controlled ship.", true, num3, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val3);
		ConfigFile config4 = ((BaseUnityPlugin)this).Config;
		int? num4 = 75;
		ConfigurationManagerAttributes val4 = new ConfigurationManagerAttributes();
		val4.DispName = "BoardShipAnywhere";
		BoardShipAnywhere = ConfigFileExtensions.BindConfig<bool>(config4, "General", "BoardShipAnywhere", false, "If enabled, press Use (E by default) on a non-interactive part of a ship hull to board near its normal ladder landing.", true, num4, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val4);
		BoardShipAnywhere.SettingChanged += delegate
		{
			ShipBoardingInstaller.RefreshAllShips();
		};
		Enabled.SettingChanged += delegate
		{
			ShipBoardingInstaller.RefreshAllShips();
		};
		ConfigFile config5 = ((BaseUnityPlugin)this).Config;
		int? num5 = 70;
		ConfigurationManagerAttributes val5 = new ConfigurationManagerAttributes();
		val5.DispName = "Use global values?";
		UseGlobalValues = ConfigFileExtensions.BindConfig<bool>(config5, "General", "UseGlobalValues", false, "If enabled, the global values below override each ship unless that ship has PerShipValues enabled. Per-ship settings are never deleted.", true, num5, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val5);
		GlobalRowSpeedMultiplier = BindHalfStep("General", "RowSpeedMultiplier", 1f, "Global rowing propulsion multiplier for both forward and reverse rowing when UseGlobalValues is enabled.", 0.05f, 20f, 60, "Row Speed Multiplier");
		GlobalSailSpeedMultiplier = BindHalfStep("General", "SailingSpeedMultiplier", 1f, "Global sailing propulsion multiplier for both half and full sail when UseGlobalValues is enabled.", 0.05f, 10f, 50, "Sailing Speed Multiplier");
		GlobalTurningSpeedMultiplier = BindHalfStep("General", "TurningSpeedMultiplier", 1f, "Global steering/rudder response multiplier used when UseGlobalValues is enabled.", 0.05f, 20f, 40, "Turn Speed Multiplier");
		GlobalPropulsionRampSeconds = BindHalfStep("General", "PropulsionRampSeconds", 0.5f, "Seconds used to smoothly ramp modded propulsion when starting or changing speed. 0 disables smoothing.", 0f, 10f, 30, "Ramp Up Speed");
		ConfigFile config6 = ((BaseUnityPlugin)this).Config;
		int? num6 = 20;
		ConfigurationManagerAttributes val6 = new ConfigurationManagerAttributes();
		val6.DispName = "Rower Affects Sailing?";
		GlobalRowersAffectSailing = ConfigFileExtensions.BindConfig<bool>(config6, "General", "RowersAffectSailing", false, "If enabled, occupied rowers also increase sailing propulsion when global values apply.", true, num6, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val6);
		GlobalPerRowerBonus = BindHalfStep("General", "PerOccupiedRowerBonus", 0.5f, "Global extra propulsion per occupied rower when global values apply. 0.5 means +50% per rower.", 0f, 5f, 10, "Rower Bonus");
		MigrateFloatIfNeeded("General", "RowSpeedMultiplier", GlobalRowSpeedMultiplier, "General", "GlobalForwardRowingMultiplier");
		MigrateFloatIfNeeded("General", "SailingSpeedMultiplier", GlobalSailSpeedMultiplier, "General", "GlobalFullSailSpeedMultiplier");
		MigrateFloatIfNeeded("General", "TurningSpeedMultiplier", GlobalTurningSpeedMultiplier, "General", "GlobalTurningSpeedMultiplier");
		MigrateBoolIfNeeded("General", "RowersAffectSailing", GlobalRowersAffectSailing, "General", "GlobalRowersAffectSailing");
		MigrateFloatIfNeeded("General", "PerOccupiedRowerBonus", GlobalPerRowerBonus, "General", "GlobalPerOccupiedRowerBonus");
		Raft = CreateShipConfig("Raft", 0);
		Karve = CreateShipConfig("Karve", 2);
		Longship = CreateShipConfig("Longship", 4);
		Drakkar = CreateShipConfig("Drakkar", 8);
		_harmony = new Harmony("tommy.valheim.shipspeed");
		_harmony.PatchAll(typeof(ShipSpeedPlugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Tommy's Ship Speed 1.2.0 loaded for Valheim 1.0.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Supported vanilla prefabs: Raft, Karve, VikingShip, VikingShip_Ashlands.");
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
	}

	private void LoadExistingConfigValues()
	{
		_existingConfigValues.Clear();
		string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
		if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath))
		{
			return;
		}
		string text = string.Empty;
		string[] array = File.ReadAllLines(configFilePath);
		foreach (string text2 in array)
		{
			string text3 = text2.Trim();
			if (text3.Length == 0 || text3.StartsWith("#", StringComparison.Ordinal))
			{
				continue;
			}
			if (text3.StartsWith("[", StringComparison.Ordinal) && text3.EndsWith("]", StringComparison.Ordinal))
			{
				text = text3.Substring(1, text3.Length - 2).Trim();
				continue;
			}
			int num = text3.IndexOf('=');
			if (num > 0 && text.Length != 0)
			{
				string text4 = text3.Substring(0, num).Trim();
				string value = text3.Substring(num + 1).Trim();
				_existingConfigValues[text + "\n" + text4] = value;
			}
		}
	}

	private bool ExistingValue(string section, string key, out string value)
	{
		return _existingConfigValues.TryGetValue(section + "\n" + key, out value);
	}

	private void MigrateFloatIfNeeded(string newSection, string newKey, ConfigEntry<float> entry, string oldSection, string oldKey)
	{
		if (!ExistingValue(newSection, newKey, out var _) && ExistingValue(oldSection, oldKey, out var value2) && float.TryParse(value2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			entry.Value = result;
		}
	}

	private void MigrateBoolIfNeeded(string newSection, string newKey, ConfigEntry<bool> entry, string oldSection, string oldKey)
	{
		if (!ExistingValue(newSection, newKey, out var _) && ExistingValue(oldSection, oldKey, out var value2) && bool.TryParse(value2, out var result))
		{
			entry.Value = result;
		}
	}

	private ConfigEntry<float> BindHalfStep(string section, string key, float defaultValue, string description, float min, float max, int order, string displayName)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Expected O, but got Unknown
		ConfigFile config = ((BaseUnityPlugin)this).Config;
		int? num = order;
		AcceptableValueRange<float> obj = new AcceptableValueRange<float>(min, max);
		ConfigurationManagerAttributes val = new ConfigurationManagerAttributes();
		val.DispName = displayName;
		ConfigEntry<float> entry = ConfigFileExtensions.BindConfig<float>(config, section, key, defaultValue, description, true, num, (AcceptableValueBase)(object)obj, (Action<ConfigEntryBase>)null, val);
		entry.SettingChanged += delegate
		{
			float value = entry.Value;
			value = Mathf.Round(value * 2f) * 0.5f;
			if (min > 0f && value < 0.5f)
			{
				value = 0.5f;
			}
			value = Mathf.Clamp(value, min, max);
			if (Math.Abs(entry.Value - value) > 0.0001f)
			{
				entry.Value = value;
			}
		};
		return entry;
	}

	private ShipConfig CreateShipConfig(string section, int seatCount)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Expected O, but got Unknown
		ShipConfig shipConfig = new ShipConfig();
		shipConfig.SeatCount = seatCount;
		ConfigFile config = ((BaseUnityPlugin)this).Config;
		int? num = 70;
		ConfigurationManagerAttributes val = new ConfigurationManagerAttributes();
		val.DispName = "Per Ship Values?";
		shipConfig.PerShipValues = ConfigFileExtensions.BindConfig<bool>(config, section, "PerShipValues", false, "When UseGlobalValues is enabled, use this ship's own values instead of the global values.", true, num, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val);
		shipConfig.RowSpeedMultiplier = BindHalfStep(section, "RowSpeedMultiplier", 1f, "Propulsion multiplier for both forward and reverse rowing on this ship.", 0.05f, 20f, 60, "Row Speed");
		shipConfig.SailSpeedMultiplier = BindHalfStep(section, "SailingSpeedMultiplier", 1f, "Sailing propulsion multiplier for both half and full sail on this ship.", 0.05f, 10f, 50, "Sail Speed");
		shipConfig.TurningSpeedMultiplier = BindHalfStep(section, "TurningSpeedMultiplier", 1f, "Multiplier for steering/rudder response and turning force on this ship. 1 = vanilla, 2 = twice as responsive, 0.5 = half speed.", 0.05f, 20f, 40, "Turn Speed");
		shipConfig.PropulsionRampSeconds = BindHalfStep(section, "PropulsionRampSeconds", (GlobalPropulsionRampSeconds != null) ? GlobalPropulsionRampSeconds.Value : 0.5f, "Seconds used to smoothly ramp rowing and sailing propulsion for this ship. 0 disables smoothing.", 0f, 10f, 30, "Ramp Speed");
		ConfigFile config2 = ((BaseUnityPlugin)this).Config;
		int? num2 = 20;
		ConfigurationManagerAttributes val2 = new ConfigurationManagerAttributes();
		val2.DispName = "Rower Affects Sailing?";
		shipConfig.RowersAffectSailing = ConfigFileExtensions.BindConfig<bool>(config2, section, "RowersAffectSailing", false, "If enabled, occupied rowers also multiply sailing propulsion for this ship.", true, num2, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val2);
		shipConfig.PerRowerBonus = BindHalfStep(section, "PerOccupiedRowerBonus", 0.5f, "Extra propulsion per additional seated/attached rower. 0.5 means +50% per rower.", 0f, 5f, 10, "Rower Bonus");
		MigrateFloatIfNeeded(section, "RowSpeedMultiplier", shipConfig.RowSpeedMultiplier, section, "ForwardRowingMultiplier");
		MigrateFloatIfNeeded(section, "SailingSpeedMultiplier", shipConfig.SailSpeedMultiplier, section, "FullSailSpeedMultiplier");
		return shipConfig;
	}

	internal static bool UsesGlobalValues(ShipConfig cfg)
	{
		if (cfg != null && UseGlobalValues != null && UseGlobalValues.Value)
		{
			if (cfg.PerShipValues != null)
			{
				return !cfg.PerShipValues.Value;
			}
			return true;
		}
		return false;
	}

	internal static float GetSailSpeedMultiplier(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.SailSpeedMultiplier.Value;
		}
		return GlobalSailSpeedMultiplier.Value;
	}

	internal static float GetRowSpeedMultiplier(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.RowSpeedMultiplier.Value;
		}
		return GlobalRowSpeedMultiplier.Value;
	}

	internal static float GetPerRowerBonus(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.PerRowerBonus.Value;
		}
		return GlobalPerRowerBonus.Value;
	}

	internal static bool GetRowersAffectSailing(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.RowersAffectSailing.Value;
		}
		return GlobalRowersAffectSailing.Value;
	}

	internal static float GetTurningSpeedMultiplier(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.TurningSpeedMultiplier.Value;
		}
		return GlobalTurningSpeedMultiplier.Value;
	}

	internal static float GetPropulsionRampSeconds(ShipConfig cfg)
	{
		if (!UsesGlobalValues(cfg))
		{
			return cfg.PropulsionRampSeconds.Value;
		}
		return GlobalPropulsionRampSeconds.Value;
	}

	internal static ShipConfig GetConfigForShip(Ship ship)
	{
		if ((Object)(object)ship == (Object)null || (Object)(object)((Component)ship).gameObject == (Object)null)
		{
			return null;
		}
		string text = ((Object)((Component)ship).gameObject).name ?? string.Empty;
		int num = text.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase);
		if (num >= 0)
		{
			text = text.Substring(0, num);
		}
		text = text.Trim();
		if (string.Equals(text, "Raft", StringComparison.OrdinalIgnoreCase))
		{
			return Raft;
		}
		if (string.Equals(text, "Karve", StringComparison.OrdinalIgnoreCase))
		{
			return Karve;
		}
		if (string.Equals(text, "VikingShip", StringComparison.OrdinalIgnoreCase))
		{
			return Longship;
		}
		if (string.Equals(text, "VikingShip_Ashlands", StringComparison.OrdinalIgnoreCase))
		{
			return Drakkar;
		}
		return null;
	}

	internal static int CountActiveRowers(Ship ship, int max)
	{
		if ((Object)(object)ship == (Object)null || max <= 0 || GetAllPlayersMethod == null || PlayerAttachPointField == null)
		{
			return 0;
		}
		IEnumerable enumerable;
		try
		{
			enumerable = GetAllPlayersMethod.Invoke(null, null) as IEnumerable;
		}
		catch
		{
			return 0;
		}
		if (enumerable == null)
		{
			return 0;
		}
		int num = 0;
		Transform transform = ((Component)ship).transform;
		foreach (object item in enumerable)
		{
			Player val = (Player)((item is Player) ? item : null);
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			Transform val2;
			try
			{
				object? value = PlayerAttachPointField.GetValue(val);
				val2 = (Transform)((value is Transform) ? value : null);
			}
			catch
			{
				continue;
			}
			if ((Object)(object)val2 == (Object)null || !IsTransformUnderShip(val2, transform))
			{
				continue;
			}
			if (GetControlledShipMethod != null)
			{
				try
				{
					object objA = GetControlledShipMethod.Invoke(val, null);
					if (object.ReferenceEquals(objA, ship))
					{
						continue;
					}
				}
				catch
				{
				}
			}
			num++;
			if (num >= max)
			{
				break;
			}
		}
		return num;
	}

	private static bool IsTransformUnderShip(Transform child, Transform shipTransform)
	{
		Transform val = child;
		for (int i = 0; i < 24; i++)
		{
			if (!((Object)(object)val != (Object)null))
			{
				break;
			}
			if ((Object)(object)val == (Object)(object)shipTransform)
			{
				return true;
			}
			val = val.parent;
		}
		return false;
	}

	internal static bool IsSailing(Ship ship)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ship == (Object)null)
		{
			return false;
		}
		string a = ((object)ship.GetSpeedSetting()).ToString();
		if (!string.Equals(a, "Half", StringComparison.OrdinalIgnoreCase))
		{
			return string.Equals(a, "Full", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	internal static bool ShouldUseTailwind(Ship ship)
	{
		if (Enabled == null || !Enabled.Value || (Object)(object)ship == (Object)null || !IsSailing(ship))
		{
			return false;
		}
		if (AlwaysWindAtBack != null)
		{
			return AlwaysWindAtBack.Value;
		}
		return false;
	}

	internal static void Debug(string text)
	{
		if (DebugLogging != null && DebugLogging.Value && Log != null)
		{
			Log.LogInfo((object)text);
		}
	}
}
internal sealed class ShipConfig
{
	internal ConfigEntry<bool> PerShipValues;

	internal ConfigEntry<float> RowSpeedMultiplier;

	internal ConfigEntry<float> SailSpeedMultiplier;

	internal ConfigEntry<float> TurningSpeedMultiplier;

	internal ConfigEntry<float> PropulsionRampSeconds;

	internal ConfigEntry<bool> RowersAffectSailing;

	internal ConfigEntry<float> PerRowerBonus;

	internal int SeatCount;
}
[HarmonyPatch(typeof(Ship), "Awake")]
internal static class ShipBoardingAwakePatch
{
	private static void Postfix(Ship __instance)
	{
		ShipBoardingInstaller.ConfigureShip(__instance);
	}
}
internal static class ShipBoardingInstaller
{
	internal static void RefreshAllShips()
	{
		try
		{
			Ship[] array = Object.FindObjectsOfType<Ship>();
			if (array != null)
			{
				for (int i = 0; i < array.Length; i++)
				{
					ConfigureShip(array[i]);
				}
			}
		}
		catch (Exception ex)
		{
			ShipSpeedPlugin.Debug("BoardShipAnywhere refresh failed: " + ex.Message);
		}
	}

	internal static void ConfigureShip(Ship ship)
	{
		if ((Object)(object)ship == (Object)null)
		{
			return;
		}
		if (ShipSpeedPlugin.Enabled == null || !ShipSpeedPlugin.Enabled.Value || ShipSpeedPlugin.BoardShipAnywhere == null || !ShipSpeedPlugin.BoardShipAnywhere.Value)
		{
			RemoveBoarders(ship);
			return;
		}
		Transform landing = FindLadderLanding(ship);
		BoardShipHullInteractable boardShipHullInteractable = ((Component)ship).gameObject.GetComponent<BoardShipHullInteractable>();
		if ((Object)(object)boardShipHullInteractable == (Object)null)
		{
			boardShipHullInteractable = ((Component)ship).gameObject.AddComponent<BoardShipHullInteractable>();
		}
		boardShipHullInteractable.Initialize(ship, landing);
		Collider[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Collider>(true);
		if (componentsInChildren == null)
		{
			return;
		}
		foreach (Collider val in componentsInChildren)
		{
			if (!((Object)(object)val == (Object)null) && !val.isTrigger && !HasExistingInteractionBeforeShipRoot(((Component)val).transform, ((Component)ship).transform))
			{
				BoardShipHullInteractable boardShipHullInteractable2 = ((Component)val).GetComponent<BoardShipHullInteractable>();
				if ((Object)(object)boardShipHullInteractable2 == (Object)null)
				{
					boardShipHullInteractable2 = ((Component)val).gameObject.AddComponent<BoardShipHullInteractable>();
				}
				boardShipHullInteractable2.Initialize(ship, landing);
			}
		}
	}

	private static void RemoveBoarders(Ship ship)
	{
		BoardShipHullInteractable[] componentsInChildren = ((Component)ship).GetComponentsInChildren<BoardShipHullInteractable>(true);
		if (componentsInChildren == null)
		{
			return;
		}
		for (int i = 0; i < componentsInChildren.Length; i++)
		{
			if ((Object)(object)componentsInChildren[i] != (Object)null)
			{
				Object.Destroy((Object)(object)componentsInChildren[i]);
			}
		}
	}

	private static bool HasExistingInteractionBeforeShipRoot(Transform start, Transform shipRoot)
	{
		Transform val = start;
		while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)shipRoot)
		{
			MonoBehaviour[] components = ((Component)val).GetComponents<MonoBehaviour>();
			foreach (MonoBehaviour val2 in components)
			{
				if (!((Object)(object)val2 == (Object)null) && !(val2 is BoardShipHullInteractable) && (val2 is Interactable || val2 is Hoverable))
				{
					return true;
				}
			}
			val = val.parent;
		}
		return false;
	}

	private static Transform FindLadderLanding(Ship ship)
	{
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		Transform[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Transform>(true);
		if (componentsInChildren == null || componentsInChildren.Length == 0)
		{
			return null;
		}
		Transform result = null;
		float num = float.MinValue;
		foreach (Transform val in componentsInChildren)
		{
			if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)((Component)ship).transform)
			{
				continue;
			}
			string text = BuildLowerPath(val, ((Component)ship).transform, 5);
			if (text.IndexOf("ladder", StringComparison.Ordinal) >= 0)
			{
				string text2 = (((Object)val).name ?? string.Empty).ToLowerInvariant();
				float num2 = ((Component)ship).transform.InverseTransformPoint(val.position).y;
				if (text2.Contains("landing"))
				{
					num2 += 1000f;
				}
				if (text2.Contains("exit"))
				{
					num2 += 900f;
				}
				if (text2.Contains("attach"))
				{
					num2 += 800f;
				}
				if (text2.Contains("top"))
				{
					num2 += 700f;
				}
				if (text2.Contains("stand"))
				{
					num2 += 600f;
				}
				if (text2.Contains("ladder"))
				{
					num2 += 100f;
				}
				if (num2 > num)
				{
					num = num2;
					result = val;
				}
			}
		}
		return result;
	}

	private static string BuildLowerPath(Transform t, Transform root, int depth)
	{
		string text = (((Object)t).name ?? string.Empty).ToLowerInvariant();
		Transform parent = t.parent;
		int num = 0;
		while (num < depth && (Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root)
		{
			text = (((Object)parent).name ?? string.Empty).ToLowerInvariant() + "/" + text;
			num++;
			parent = parent.parent;
		}
		return text;
	}
}
internal sealed class BoardShipHullInteractable : MonoBehaviour, Hoverable, Interactable
{
	private Ship _ship;

	private Transform _landing;

	internal void Initialize(Ship ship, Transform landing)
	{
		_ship = ship;
		_landing = landing;
	}

	public string GetHoverText()
	{
		if (!IsEnabled())
		{
			return string.Empty;
		}
		string text = "[<color=yellow><b>$KEY_Use</b></color>] Board ship";
		if (Localization.instance == null)
		{
			return text;
		}
		return Localization.instance.Localize(text);
	}

	public string GetHoverName()
	{
		if (!IsEnabled())
		{
			return string.Empty;
		}
		return "Ship";
	}

	public float GetHoverOffset()
	{
		return 0f;
	}

	public bool Interact(Humanoid user, bool hold, bool alt)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: 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_0050: 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)
		if (hold || !IsEnabled())
		{
			return false;
		}
		Player val = (Player)(object)((user is Player) ? user : null);
		if ((Object)(object)val == (Object)null || (Object)(object)_ship == (Object)null)
		{
			return false;
		}
		Vector3 boardPoint = GetBoardPoint();
		try
		{
			Rigidbody component = ((Component)val).GetComponent<Rigidbody>();
			if ((Object)(object)component != (Object)null)
			{
				component.linearVelocity = Vector3.zero;
				component.angularVelocity = Vector3.zero;
				component.position = boardPoint;
			}
			((Component)val).transform.position = boardPoint;
			return true;
		}
		catch (Exception ex)
		{
			ShipSpeedPlugin.Debug("BoardShipAnywhere interaction failed: " + ex.Message);
			return false;
		}
	}

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

	private bool IsEnabled()
	{
		if ((Object)(object)_ship != (Object)null && ShipSpeedPlugin.Enabled != null && ShipSpeedPlugin.Enabled.Value && ShipSpeedPlugin.BoardShipAnywhere != null)
		{
			return ShipSpeedPlugin.BoardShipAnywhere.Value;
		}
		return false;
	}

	private Vector3 GetBoardPoint()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: 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_0070: 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_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = (((Object)(object)_landing != (Object)null) ? _landing.position : FallbackDeckPoint());
		Vector3 val2 = ((Component)_ship).transform.position - val;
		val2.y = 0f;
		if (((Vector3)(ref val2)).sqrMagnitude < 0.01f)
		{
			val2 = -((Component)_ship).transform.right;
		}
		((Vector3)(ref val2)).Normalize();
		return val + val2 * 0.75f + Vector3.up * 0.45f;
	}

	private Vector3 FallbackDeckPoint()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_00b9: 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_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_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: 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)
		Collider[] componentsInChildren = ((Component)_ship).GetComponentsInChildren<Collider>(true);
		bool flag = false;
		Bounds bounds = default(Bounds);
		((Bounds)(ref bounds))..ctor(((Component)_ship).transform.position, Vector3.zero);
		if (componentsInChildren != null)
		{
			foreach (Collider val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !val.isTrigger)
				{
					if (!flag)
					{
						bounds = val.bounds;
						flag = true;
					}
					else
					{
						((Bounds)(ref bounds)).Encapsulate(val.bounds);
					}
				}
			}
		}
		if (!flag)
		{
			return ((Component)_ship).transform.position + Vector3.up * 1.5f;
		}
		return new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y + 0.1f, ((Bounds)(ref bounds)).center.z);
	}
}
[HarmonyPatch(typeof(Ship), "CustomFixedUpdate", new Type[] { typeof(float) })]
internal static class ShipCustomFixedUpdatePatch
{
	private sealed class RampState
	{
		internal string Mode = "Stop";

		internal float Sail = 1f;

		internal float Row = 1f;

		internal int LastSeenFrame;
	}

	internal sealed class State
	{
		internal bool Modified;

		internal float SailForceFactor;

		internal float BackwardForce;

		internal bool TurningModified;

		internal float SteerVelocityForceFactor;

		internal float SteerForce;

		internal float RudderSpeed;
	}

	private static readonly FieldInfo NViewField = AccessTools.Field(typeof(Ship), "m_nview");

	private static readonly FieldInfo SteerVelocityForceFactorField = AccessTools.Field(typeof(Ship), "m_stearVelForceFactor");

	private static readonly FieldInfo SteerForceField = AccessTools.Field(typeof(Ship), "m_stearForce");

	private static readonly FieldInfo RudderSpeedField = AccessTools.Field(typeof(Ship), "m_rudderSpeed");

	private static readonly Dictionary<int, RampState> RampStates = new Dictionary<int, RampState>();

	private static int _lastRampCleanupFrame;

	private static float SmoothMultiplier(float current, float target, float dt, ShipConfig cfg)
	{
		float num = ((cfg != null) ? ShipSpeedPlugin.GetPropulsionRampSeconds(cfg) : 0f);
		if (num <= 0.001f)
		{
			return target;
		}
		float num2 = Math.Abs(target - 1f) * Math.Max(0f, dt) / num;
		if (num2 < 0.001f)
		{
			num2 = 0.001f;
		}
		return Mathf.MoveTowards(current, target, num2);
	}

	private static RampState GetRampState(Ship ship, string mode)
	{
		int instanceID = ((Object)ship).GetInstanceID();
		if (!RampStates.TryGetValue(instanceID, out var value))
		{
			value = new RampState();
			RampStates[instanceID] = value;
		}
		value.LastSeenFrame = Time.frameCount;
		if (string.Equals(value.Mode, "Stop", StringComparison.OrdinalIgnoreCase) && !string.Equals(mode, "Stop", StringComparison.OrdinalIgnoreCase))
		{
			value.Sail = 1f;
			value.Row = 1f;
		}
		if (string.Equals(mode, "Stop", StringComparison.OrdinalIgnoreCase))
		{
			value.Sail = 1f;
			value.Row = 1f;
		}
		value.Mode = mode;
		if (Time.frameCount - _lastRampCleanupFrame > 3600)
		{
			_lastRampCleanupFrame = Time.frameCount;
			List<int> list = null;
			foreach (KeyValuePair<int, RampState> rampState in RampStates)
			{
				if (Time.frameCount - rampState.Value.LastSeenFrame > 7200)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(rampState.Key);
				}
			}
			if (list != null)
			{
				foreach (int item in list)
				{
					RampStates.Remove(item);
				}
			}
		}
		return value;
	}

	private static ZNetView GetNView(Ship ship)
	{
		if ((Object)(object)ship == (Object)null)
		{
			return null;
		}
		if (NViewField != null)
		{
			object? value = NViewField.GetValue(ship);
			ZNetView val = (ZNetView)((value is ZNetView) ? value : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
		}
		return ((Component)ship).GetComponent<ZNetView>();
	}

	private static void Prefix(Ship __instance, float __0, out State __state)
	{
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		__state = new State();
		if (ShipSpeedPlugin.Enabled == null || !ShipSpeedPlugin.Enabled.Value)
		{
			return;
		}
		ZNetView nView = GetNView(__instance);
		if ((Object)(object)__instance == (Object)null || (Object)(object)nView == (Object)null || !nView.IsValid() || !nView.IsOwner())
		{
			return;
		}
		ShipConfig configForShip = ShipSpeedPlugin.GetConfigForShip(__instance);
		if (configForShip == null)
		{
			return;
		}
		string text = ((object)__instance.GetSpeedSetting()).ToString();
		int num = ShipSpeedPlugin.CountActiveRowers(__instance, configForShip.SeatCount);
		float num2 = 1f + (float)num * ShipSpeedPlugin.GetPerRowerBonus(configForShip);
		float num3 = 1f;
		float num4 = 1f;
		bool flag = false;
		bool flag2 = false;
		if (string.Equals(text, "Half", StringComparison.OrdinalIgnoreCase))
		{
			num3 = ShipSpeedPlugin.GetSailSpeedMultiplier(configForShip);
			if (ShipSpeedPlugin.GetRowersAffectSailing(configForShip))
			{
				num3 *= num2;
			}
			flag = true;
		}
		else if (string.Equals(text, "Full", StringComparison.OrdinalIgnoreCase))
		{
			num3 = ShipSpeedPlugin.GetSailSpeedMultiplier(configForShip);
			if (ShipSpeedPlugin.GetRowersAffectSailing(configForShip))
			{
				num3 *= num2;
			}
			flag = true;
		}
		else if (string.Equals(text, "Slow", StringComparison.OrdinalIgnoreCase))
		{
			num4 = ShipSpeedPlugin.GetRowSpeedMultiplier(configForShip) * num2;
			flag2 = true;
		}
		else if (string.Equals(text, "Back", StringComparison.OrdinalIgnoreCase))
		{
			num4 = ShipSpeedPlugin.GetRowSpeedMultiplier(configForShip) * num2;
			flag2 = true;
		}
		RampState rampState = GetRampState(__instance, text);
		if (flag)
		{
			rampState.Sail = SmoothMultiplier(rampState.Sail, num3, __0, configForShip);
			num3 = rampState.Sail;
		}
		else
		{
			rampState.Sail = 1f;
		}
		if (flag2)
		{
			rampState.Row = SmoothMultiplier(rampState.Row, num4, __0, configForShip);
			num4 = rampState.Row;
		}
		else
		{
			rampState.Row = 1f;
		}
		float turningSpeedMultiplier = ShipSpeedPlugin.GetTurningSpeedMultiplier(configForShip);
		bool flag3 = Math.Abs(turningSpeedMultiplier - 1f) > 0.0001f;
		if (!flag && !flag2 && !flag3)
		{
			return;
		}
		__state.SailForceFactor = __instance.m_sailForceFactor;
		__state.BackwardForce = __instance.m_backwardForce;
		__state.Modified = true;
		if (flag3)
		{
			__state.TurningModified = true;
			if (SteerVelocityForceFactorField != null)
			{
				__state.SteerVelocityForceFactor = (float)SteerVelocityForceFactorField.GetValue(__instance);
				SteerVelocityForceFactorField.SetValue(__instance, __state.SteerVelocityForceFactor * turningSpeedMultiplier);
			}
			if (SteerForceField != null)
			{
				__state.SteerForce = (float)SteerForceField.GetValue(__instance);
				SteerForceField.SetValue(__instance, __state.SteerForce * turningSpeedMultiplier);
			}
			if (RudderSpeedField != null)
			{
				__state.RudderSpeed = (float)RudderSpeedField.GetValue(__instance);
				RudderSpeedField.SetValue(__instance, __state.RudderSpeed * turningSpeedMultiplier);
			}
		}
		if (flag)
		{
			__instance.m_sailForceFactor = __state.SailForceFactor * num3;
		}
		if (flag2)
		{
			__instance.m_backwardForce = __state.BackwardForce * num4;
		}
		ShipSpeedPlugin.Debug("Ship=" + ((Object)((Component)__instance).gameObject).name + " mode=" + text + " rowers=" + num + " crewX=" + num2.ToString("0.00") + " sailX=" + num3.ToString("0.00") + " rowX=" + num4.ToString("0.00") + " turnX=" + turningSpeedMultiplier.ToString("0.00"));
	}

	private static void Postfix(Ship __instance, State __state)
	{
		if ((Object)(object)__instance == (Object)null || __state == null || !__state.Modified)
		{
			return;
		}
		__instance.m_sailForceFactor = __state.SailForceFactor;
		__instance.m_backwardForce = __state.BackwardForce;
		if (__state.TurningModified)
		{
			if (SteerVelocityForceFactorField != null)
			{
				SteerVelocityForceFactorField.SetValue(__instance, __state.SteerVelocityForceFactor);
			}
			if (SteerForceField != null)
			{
				SteerForceField.SetValue(__instance, __state.SteerForce);
			}
			if (RudderSpeedField != null)
			{
				RudderSpeedField.SetValue(__instance, __state.RudderSpeed);
			}
		}
	}
}
[HarmonyPatch(typeof(EnvMan), "GetWindDir")]
internal static class SailingWindDirectionPatch
{
	private static readonly MethodInfo GetControlledShipMethod = AccessTools.Method(typeof(Player), "GetControlledShip", (Type[])null, (Type[])null);

	private static Ship GetLocallyControlledSailingShip()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || GetControlledShipMethod == null)
		{
			return null;
		}
		Ship val = null;
		try
		{
			object? obj = GetControlledShipMethod.Invoke(localPlayer, null);
			val = (Ship)((obj is Ship) ? obj : null);
		}
		catch
		{
			return null;
		}
		if ((Object)(object)val == (Object)null || !ShipSpeedPlugin.ShouldUseTailwind(val))
		{
			return null;
		}
		return val;
	}

	private static void Postfix(ref Vector3 __result)
	{
		//IL_0016: 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_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		Ship locallyControlledSailingShip = GetLocallyControlledSailingShip();
		if (!((Object)(object)locallyControlledSailingShip == (Object)null))
		{
			Vector3 forward = ((Component)locallyControlledSailingShip).transform.forward;
			forward.y = 0f;
			if (!(((Vector3)(ref forward)).sqrMagnitude <= 0.0001f))
			{
				__result = ((Vector3)(ref forward)).normalized;
			}
		}
	}
}