Decompiled source of Heave Ho v1.0.0

plugins/HeaveHo.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.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HeaveHo.Shared;
using Microsoft.CodeAnalysis;
using Nick.Shared;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HeaveHo
{
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public bool? IsAdvanced;

		public bool? ReadOnly;
	}
	internal static class Cfg
	{
		private static ConfigFile file;

		internal static ServerSettings Synced;

		private static Dictionary<ConfigDefinition, string> orphans;

		private static int nextOrder = 100000;

		private static readonly HashSet<string> OldSections = new HashSet<string>
		{
			"0. Seats", "0. Controls", "1. Rowing", "2. Stamina", "3. Oars", "4. HUD", "5. Splashes", "9. Debug", "5. Oar rendering (experimental)", "3. Oars (all ships)",
			"4. Splashes", "5. Rower HUD", "9. Advanced"
		};

		internal static void Init(ConfigFile config, ServerSettings synced)
		{
			file = config;
			Synced = synced;
			orphans = AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(config) as Dictionary<ConfigDefinition, string>;
		}

		internal static ConfigEntry<T> Bind<T>(string section, string key, T def, string desc, AcceptableValueBase range = null, bool advanced = false, int? order = null, string from = null, bool local = false)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
			{
				Order = (order ?? nextOrder--),
				IsAdvanced = (advanced ? new bool?(true) : ((bool?)null))
			};
			if (!local && !desc.Contains("[Synced with Server]"))
			{
				desc += " [Synced with Server]";
			}
			ConfigEntry<T> val = file.Bind<T>(section, key, def, new ConfigDescription(desc, range, new object[1] { configurationManagerAttributes }));
			if (!local)
			{
				Synced?.Add<T>(val);
			}
			if (from != null)
			{
				Migrate((ConfigEntryBase)(object)val, from);
			}
			return val;
		}

		private static void Migrate(ConfigEntryBase entry, string from)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			if (orphans == null)
			{
				return;
			}
			string[] array = from.Split(';');
			foreach (string text in array)
			{
				int num = text.IndexOf('/');
				ConfigDefinition val = new ConfigDefinition(text.Substring(0, num), text.Substring(num + 1));
				if (orphans.TryGetValue(val, out var value))
				{
					orphans.Remove(val);
					entry.SetSerializedValue(value);
					Plugin.Log.LogInfo((object)("Config: moved '" + val.Section + " / " + val.Key + "' to '" + entry.Definition.Section + " / " + entry.Definition.Key + "' (" + value + ")."));
					break;
				}
			}
		}

		internal static void PurgeSection(string section)
		{
			if (orphans == null)
			{
				return;
			}
			List<ConfigDefinition> list = new List<ConfigDefinition>();
			foreach (ConfigDefinition key in orphans.Keys)
			{
				if (key.Section == section)
				{
					list.Add(key);
				}
			}
			foreach (ConfigDefinition item in list)
			{
				orphans.Remove(item);
			}
		}

		internal static void PurgeOldSections()
		{
			if (orphans != null)
			{
				List<ConfigDefinition> list = new List<ConfigDefinition>();
				foreach (ConfigDefinition key in orphans.Keys)
				{
					if (OldSections.Contains(key.Section))
					{
						list.Add(key);
					}
				}
				foreach (ConfigDefinition item in list)
				{
					orphans.Remove(item);
				}
			}
			file.Save();
		}

		internal static void Save()
		{
			file.Save();
		}
	}
	internal static class ObjLoader
	{
		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		internal static Mesh Load(string path)
		{
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Expected O, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			List<Vector3> list = new List<Vector3>();
			List<Vector2> list2 = new List<Vector2>();
			List<Vector3> list3 = new List<Vector3>();
			List<Vector3> list4 = new List<Vector3>();
			List<Vector2> list5 = new List<Vector2>();
			List<Vector3> list6 = new List<Vector3>();
			List<int> list7 = new List<int>();
			bool flag = true;
			string[] array = File.ReadAllLines(path);
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.Length == 0 || text2[0] == '#')
				{
					continue;
				}
				string[] array2 = text2.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
				switch (array2[0])
				{
				case "v":
					list.Add(new Vector3(F(array2[1]), F(array2[2]), 0f - F(array2[3])));
					break;
				case "vt":
					list2.Add(new Vector2(F(array2[1]), (array2.Length > 2) ? F(array2[2]) : 0f));
					break;
				case "vn":
					list3.Add(new Vector3(F(array2[1]), F(array2[2]), 0f - F(array2[3])));
					break;
				case "f":
				{
					int count = list4.Count;
					int num = array2.Length - 1;
					for (int j = 1; j < array2.Length; j++)
					{
						string[] array3 = array2[j].Split('/');
						list4.Add(list[Index(array3[0], list.Count)]);
						list5.Add((array3.Length > 1 && array3[1].Length > 0) ? list2[Index(array3[1], list2.Count)] : Vector2.zero);
						if (array3.Length > 2 && array3[2].Length > 0)
						{
							list6.Add(list3[Index(array3[2], list3.Count)]);
						}
						else
						{
							flag = false;
						}
					}
					for (int k = 1; k < num - 1; k++)
					{
						list7.Add(count);
						list7.Add(count + k + 1);
						list7.Add(count + k);
					}
					break;
				}
				}
			}
			if (list7.Count == 0)
			{
				throw new InvalidDataException("OBJ has no faces");
			}
			Mesh val = new Mesh
			{
				name = Path.GetFileNameWithoutExtension(path)
			};
			if (list4.Count > 65000)
			{
				val.indexFormat = (IndexFormat)1;
			}
			val.SetVertices(list4);
			val.SetUVs(0, list5);
			val.SetTriangles(list7, 0);
			if (flag && list6.Count == list4.Count)
			{
				val.SetNormals(list6);
			}
			else
			{
				val.RecalculateNormals();
			}
			val.RecalculateBounds();
			val.RecalculateTangents();
			return val;
		}

		internal static Texture2D LoadTexture(string objPath)
		{
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			string path = Path.GetDirectoryName(objPath) ?? "";
			List<string> list = new List<string>();
			string path2 = Path.ChangeExtension(objPath, ".mtl");
			if (File.Exists(path2))
			{
				string[] array = File.ReadAllLines(path2);
				foreach (string text in array)
				{
					string text2 = text.Trim();
					if (text2.StartsWith("map_Kd", StringComparison.OrdinalIgnoreCase))
					{
						string path3 = text2.Substring(6).Trim().Replace('\\', '/');
						list.Add(Path.Combine(path, Path.GetFileName(path3)));
					}
				}
			}
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(objPath);
			list.Add(Path.Combine(path, fileNameWithoutExtension + ".png"));
			list.Add(Path.Combine(path, fileNameWithoutExtension + ".jpg"));
			list.Add(Path.Combine(path, fileNameWithoutExtension + ".jpeg"));
			foreach (string item in list)
			{
				if (File.Exists(item))
				{
					Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true)
					{
						name = Path.GetFileName(item)
					};
					if (ImageConversion.LoadImage(val, File.ReadAllBytes(item)))
					{
						((Texture)val).wrapMode = (TextureWrapMode)0;
						((Texture)val).filterMode = (FilterMode)1;
						return val;
					}
				}
			}
			return null;
		}

		private static float F(string s)
		{
			return float.Parse(s, NumberStyles.Float, Inv);
		}

		private static int Index(string s, int count)
		{
			int num = int.Parse(s, Inv);
			if (num <= 0)
			{
				return count + num;
			}
			return num - 1;
		}
	}
	[BepInPlugin("nick.valheim.heaveho", "Heave Ho", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal class OarModel
		{
			public Mesh Mesh;

			public Texture2D Texture;

			public bool HullTexture;
		}

		internal delegate bool OarlockMeasure(out float distance, out float height);

		internal class ShipOarSettings
		{
			public ConfigEntry<float> Outboard;

			public ConfigEntry<float> Height;

			public ConfigEntry<string> Placement;

			public ConfigEntry<float> PerSeatHeight;

			public ConfigEntry<float> PerSeatOutboard;

			public ConfigEntry<float> Spacing;

			public ConfigEntry<float> Scale;

			public ConfigEntry<bool> Visuals;

			public ConfigEntry<bool> Physics;

			public ConfigEntry<bool> ShowAtRest;
		}

		public const string ModGuid = "nick.valheim.heaveho";

		public const string ModName = "Heave Ho";

		public const string ModVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static Plugin Instance;

		internal static ConfigEntry<float> RowerStrength;

		internal static ConfigEntry<float> EasyMultiplier;

		internal static ConfigEntry<float> BackMultiplier;

		internal static ConfigEntry<float> TurnStrength;

		internal static ConfigEntry<float> MaxRowingSpeed;

		internal static ConfigEntry<string> RowerBonuses;

		internal static ConfigEntry<float> StaminaPerSecond;

		internal static ConfigEntry<float> EasyStaminaMultiplier;

		internal static ConfigEntry<float> RecoverStamina;

		internal static ConfigEntry<bool> ShowOars;

		internal static ConfigEntry<bool> ShipUnderSail;

		internal static ConfigEntry<float> OarModelScale;

		internal static ConfigEntry<float> StrokesPerSecond;

		internal static ConfigEntry<bool> Splashes;

		internal static ConfigEntry<string> EntryEffects;

		internal static ConfigEntry<string> ExitEffects;

		internal static ConfigEntry<float> HudOutboard;

		internal static ConfigEntry<float> HudHeight;

		internal static ConfigEntry<string> RowingSeatAnimations;

		internal const float PlaceholderOarLength = 3.2f;

		internal const float DefaultOutboard = 1.1f;

		internal const float DefaultHeight = 0.5f;

		internal static GameObject OarPrefab;

		internal static Mesh OarMesh;

		internal static Texture2D OarTexture;

		private static readonly Dictionary<string, OarModel> shipOars = new Dictionary<string, OarModel>();

		private static string assetDir = "";

		private static readonly Dictionary<string, OarModel> shipFittings = new Dictionary<string, OarModel>();

		private Harmony harmony;

		internal static ServerSettings Synced;

		internal const string PlacementFixed = "Fixed";

		internal const string PlacementPerSeat = "Per seat";

		private static readonly Dictionary<string, ShipOarSettings> shipSettings = new Dictionary<string, ShipOarSettings>();

		private static readonly Dictionary<string, ConfigEntry<string>> seatFilters = new Dictionary<string, ConfigEntry<string>>();

		private static string cachedBonusText;

		private static float[] cachedBonuses = new float[0];

		internal static OarModel OarFor(string ship)
		{
			if (shipOars.TryGetValue(ship, out var value))
			{
				return value;
			}
			OarModel oarModel = LoadShipModel("oar_", ship) ?? (((Object)(object)OarMesh != (Object)null) ? new OarModel
			{
				Mesh = OarMesh,
				Texture = OarTexture
			} : null);
			shipOars[ship] = oarModel;
			return oarModel;
		}

		internal static OarModel FittingFor(string ship)
		{
			if (shipFittings.TryGetValue(ship, out var value))
			{
				return value;
			}
			OarModel oarModel = LoadShipModel("oarlock_", ship);
			shipFittings[ship] = oarModel;
			return oarModel;
		}

		private static OarModel LoadShipModel(string prefix, string ship)
		{
			//IL_0083: 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_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			string text = Path.Combine(assetDir, prefix + DisplayName(ship).ToLowerInvariant().Replace(' ', '_') + ".obj");
			if (!File.Exists(text))
			{
				return null;
			}
			try
			{
				Mesh val = ObjLoader.Load(text);
				((Object)val).name = "HeaveHo_" + prefix + ship;
				Texture2D val2 = ObjLoader.LoadTexture(text);
				ManualLogSource log = Log;
				object[] obj = new object[4]
				{
					ship,
					Path.GetFileName(text),
					val.vertexCount,
					null
				};
				Bounds bounds = val.bounds;
				obj[3] = ((Bounds)(ref bounds)).min.x;
				string text2 = string.Format("[{0}] Loaded {1} ({2} verts, x {3:F2} to ", obj);
				bounds = val.bounds;
				log.LogInfo((object)(text2 + $"{((Bounds)(ref bounds)).max.x:F2}), " + (((Object)(object)val2 != (Object)null) ? ("texture " + ((Object)val2).name) : "drawn with the hull's texture")));
				return new OarModel
				{
					Mesh = val,
					Texture = val2,
					HullTexture = ((Object)(object)val2 == (Object)null)
				};
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[" + ship + "] Couldn't load " + Path.GetFileName(text) + ": " + ex.Message));
				return null;
			}
		}

		private void Awake()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			Translations.Load((BaseUnityPlugin)(object)this);
			Synced = new ServerSettings("nick.valheim.heaveho", ((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger);
			Cfg.Init(((BaseUnityPlugin)this).Config, Synced);
			BindSettings();
			assetDir = AssetDir();
			if (!LoadOarObj())
			{
				LoadOarBundle();
			}
			RowerHud.LoadIcon(assetDir);
			harmony = new Harmony("nick.valheim.heaveho");
			harmony.PatchAll();
			RowerHud.Install(harmony);
			Ship[] array = Object.FindObjectsByType<Ship>((FindObjectsSortMode)0);
			foreach (Ship val in array)
			{
				if (!Object.op_Implicit((Object)(object)((Component)val).GetComponent<RowingShip>()))
				{
					((Component)val).gameObject.AddComponent<RowingShip>();
				}
			}
		}

		private static void BindSettings()
		{
			RowerStrength = Cfg.Bind("1. Rowing", "Rower strength", 2f, "How hard rowers push. Acceleration (m/s²) at Pull hard when the crew's combined bonus is 1.0.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), advanced: false, null, "1. Rowing/Rower strength");
			EasyMultiplier = Cfg.Bind("1. Rowing", "Easy stroke thrust", 0.5f, "Thrust of Easy stroke relative to Pull hard.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), advanced: false, null, "1. Rowing/Easy stroke thrust");
			BackMultiplier = Cfg.Bind("1. Rowing", "Back water thrust", 0.6f, "Reverse thrust of Back water relative to Pull hard.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), advanced: false, null, "1. Rowing/Back water thrust");
			TurnStrength = Cfg.Bind("1. Rowing", "Turning strength", 0.5f, "How much rowing on one side turns the ship. 0 = straight line only, 1 = full physics (a lone rower pulls the ship in circles).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), advanced: false, null, "1. Rowing/Turning strength");
			MaxRowingSpeed = Cfg.Bind("1. Rowing", "Max rowing speed", 12f, "Rowers stop adding thrust above this speed (m/s). Safety cap.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), advanced: true, null, "1. Rowing/Max rowing speed");
			RowerBonuses = Cfg.Bind("1. Rowing", "Rower bonuses", "0.25,0.15,0.10,0.08,0.06,0.05", "Share of thrust each extra rower adds (diminishing returns). The last value repeats for larger crews.", null, advanced: true, null, "1. Rowing/Rower bonuses");
			StaminaPerSecond = Cfg.Bind("2. Stamina", "Stamina per second", 3f, "Stamina spent per second at Pull hard.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), advanced: false, null, "2. Stamina/Stamina per second");
			EasyStaminaMultiplier = Cfg.Bind("2. Stamina", "Easy / back water stamina", 0.4f, "Stamina cost of Easy stroke and Back water relative to Pull hard.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), advanced: false, null, "2. Stamina/Easy / back water stamina");
			RecoverStamina = Cfg.Bind("2. Stamina", "Resume rowing at", 30f, "After running out, a rower rests until stamina recovers to this value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), advanced: false, null, "2. Stamina/Resume rowing at");
			ShowOars = Cfg.Bind("3. Oars and splashes", "Show oars", def: true, "Master switch for oar visuals on every ship (each ship also has its own switch).", null, advanced: false, null, "3. Oars (all ships)/Show oars;3. Oars/Show oars", local: true);
			OarModelScale = Cfg.Bind("3. Oars and splashes", "Oar size", 1f, "Size multiplier for the oar model on every ship (each ship also has its own size).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), advanced: false, null, "3. Oars (all ships)/Oar size;3. Oars/Oar model scale", local: true);
			StrokesPerSecond = Cfg.Bind("3. Oars and splashes", "Stroke rate", 0.55f, "Strokes per second at Easy stroke. Pull hard strokes faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), advanced: false, null, "3. Oars (all ships)/Stroke rate;3. Oars/Strokes per second", local: true);
			ShipUnderSail = Cfg.Bind("3. Oars and splashes", "Ship the oars under sail", def: false, "While the sail is up and the ship is moving, oars nobody is pulling slide in out of sight; they come back out the moment someone rows or the ship slows. Off: they stay where each ship's \"Oars at rest\" puts them. Live.", null, advanced: false, null, null, local: true);
			Splashes = Cfg.Bind("3. Oars and splashes", "Oar splashes", def: true, "Splash where each blade enters the water, and a smaller one as it lifts out.", null, advanced: false, null, "4. Splashes/Oar splashes;5. Splashes/Oar splashes", local: true);
			EntryEffects = Cfg.Bind("3. Oars and splashes", "Splash effects: blade enters", "vfx_water_surface_fish", "Comma-separated Valheim effects (visuals or sfx_ sounds) spawned where the blade bites. Water-related names are listed in LogOutput.log under [Splash]. Live.", null, advanced: false, null, "4. Splashes/Effects on blade entry;5. Splashes/Extra effects on blade entry", local: true);
			ExitEffects = Cfg.Bind("3. Oars and splashes", "Splash effects: blade lifts out", "vfx_watersplash_bathtub", "Same, for when the blade lifts out. Live.", null, advanced: false, null, "4. Splashes/Effects on blade exit;5. Splashes/Extra effects on blade exit", local: true);
			HudOutboard = Cfg.Bind("4. Rower HUD", "Arrows: distance outboard", 0.2f, "How far past your oarlock (m) the rowing arrows float.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-3f, 3f), advanced: false, null, null, local: true);
			HudHeight = Cfg.Bind("4. Rower HUD", "Arrows: height", 0.2f, "Height (m) of the rowing arrows above your oarlock.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 4f), advanced: false, null, null, local: true);
			RowingSeatAnimations = Cfg.Bind("9. Advanced", "Rowing seat animations", "attach_sitship", "Seat animations that count as rowing seats (helm and mast holds use others). Seats are listed in LogOutput.log per ship. Applies to newly loaded ships; all players should match.", null, advanced: true, null, "0. Seats/Rowing seat animations");
			Cfg.PurgeOldSections();
		}

		private string AssetDir()
		{
			string text = (string.IsNullOrEmpty(((BaseUnityPlugin)this).Info.Location) ? "" : (Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? ""));
			List<string> list = new List<string>();
			if (text != "")
			{
				list.Add(text);
			}
			list.Add(Path.Combine(Paths.BepInExRootPath, "scripts"));
			list.Add(Path.Combine(Paths.PluginPath, "HeaveHo"));
			foreach (string item in list)
			{
				string[] array = new string[3] { "oar.obj", "oarIcon.png", "heaveho" };
				foreach (string path in array)
				{
					if (File.Exists(Path.Combine(item, path)))
					{
						return item;
					}
				}
			}
			return text;
		}

		private bool LoadOarObj()
		{
			//IL_0069: 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_0071: 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_0085: 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)
			string text = Path.Combine(AssetDir(), "oar.obj");
			if (!File.Exists(text))
			{
				return false;
			}
			try
			{
				OarMesh = ObjLoader.Load(text);
				((Object)OarMesh).name = "HeaveHo_Oar";
				OarTexture = ObjLoader.LoadTexture(text);
				ManualLogSource log = Log;
				string text2 = $"Loaded custom oar from oar.obj ({OarMesh.vertexCount} verts, ";
				Bounds bounds = OarMesh.bounds;
				object arg = ((Bounds)(ref bounds)).min;
				bounds = OarMesh.bounds;
				log.LogInfo((object)(text2 + $"bounds {arg} to {((Bounds)(ref bounds)).max}), texture: " + (((Object)(object)OarTexture != (Object)null) ? ((Object)OarTexture).name : "none found")));
				return true;
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Couldn't load oar.obj: " + ex.Message));
				OarMesh = null;
				return false;
			}
		}

		private void LoadOarBundle()
		{
			string text = Path.Combine(AssetDir(), "heaveho");
			if (!File.Exists(text))
			{
				Log.LogInfo((object)"No 'heaveho' asset bundle next to the dll; using placeholder oars.");
				return;
			}
			AssetBundle val = AssetBundle.LoadFromFile(text);
			if ((Object)(object)val == (Object)null)
			{
				Log.LogWarning((object)"Couldn't load 'heaveho' asset bundle (built with a different Unity version?). Using placeholder oars.");
				return;
			}
			OarPrefab = val.LoadAsset<GameObject>("HeaveHo_Oar");
			if ((Object)(object)OarPrefab == (Object)null)
			{
				Log.LogWarning((object)("Bundle has no prefab named 'HeaveHo_Oar'. Assets in bundle: " + string.Join(", ", val.GetAllAssetNames()) + ". Using placeholder oars."));
			}
			else
			{
				Log.LogInfo((object)"Loaded custom oar model from 'heaveho'.");
			}
		}

		private static bool IsDrakkar(string ship)
		{
			return ship == "VikingShip_Ashlands";
		}

		internal static string DisplayName(string ship)
		{
			return ship switch
			{
				"Karve" => "Karve", 
				"VikingShip" => "Longship", 
				"VikingShip_Ashlands" => "Drakkar", 
				"Raft" => "Raft", 
				_ => ship, 
			};
		}

		private static string ShipSection(string ship)
		{
			return "5. Ship: " + DisplayName(ship);
		}

		private static string ShipFrom(string ship, string key, string oldestKey = null)
		{
			return "6. Ship: " + DisplayName(ship) + "/" + key + ((oldestKey != null) ? (";3. Oars - " + ship + "/" + oldestKey) : "");
		}

		internal static ConfigEntry<string> GetSeatNameFilter(string ship)
		{
			if (!seatFilters.TryGetValue(ship, out var value))
			{
				value = Cfg.Bind(ShipSection(ship), "Rowing seat names", IsDrakkar(ship) ? "sit_box" : "", "Seat name prefixes that row on the " + DisplayName(ship) + " (e.g. sit_box). Empty = all seats with a rowing animation. Applies to newly loaded ships; all players should match.", null, advanced: true, 10, ShipFrom(ship, "Rowing seat names", "Rowing seat names"));
				seatFilters[ship] = value;
				Cfg.Save();
			}
			return value;
		}

		internal static ShipOarSettings GetShipSettings(string ship, OarlockMeasure measure)
		{
			if (shipSettings.TryGetValue(ship, out var value))
			{
				return value;
			}
			ShipProfile shipProfile = ShipProfile.For(ship);
			bool flag = shipProfile != null;
			float num = 1.1f;
			float num2 = 0.5f;
			string text = "hand-tuned defaults";
			if (!flag && ship != "Karve" && measure(out var distance, out var height))
			{
				num = distance;
				num2 = height;
				text = "measured from the hull";
			}
			string text2 = ShipSection(ship);
			string text3 = DisplayName(ship);
			value = new ShipOarSettings
			{
				Visuals = Cfg.Bind(text2, "Oar visuals", def: true, "Show oars and splashes on the " + text3 + ". Live.", null, advanced: false, 100, ShipFrom(ship, "Oar visuals", "Oar visuals"), local: true),
				Physics = Cfg.Bind(text2, "Oar physics", def: true, "Seated rowers push and turn the " + text3 + " and spend stamina. Off = cosmetic rowing. Both off = ordinary seats. Live.", null, advanced: false, 99, ShipFrom(ship, "Oar physics", "Oar physics")),
				Scale = Cfg.Bind(text2, "Oar size", 1f, "Oar size on the " + text3 + ", on top of the all-ships size. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 3f), advanced: false, 98, ShipFrom(ship, "Oar size", "Oar size"), local: true),
				ShowAtRest = Cfg.Bind(text2, "Oars at rest", def: true, (shipProfile != null && shipProfile.StowAlongHull) ? ("Leave the " + text3 + "'s oars out, stowed along the hull, when nobody is rowing them. Off: an oar slides out when its rower starts and back in when they stop. Live.") : ("Leave the " + text3 + "'s oars out, level through their ports, when nobody is rowing them. Off: an oar slides out of its port when its rower starts and back in when they stop, so the ship keeps its own shape. Live."), null, advanced: false, 97, null, local: true)
			};
			if (flag)
			{
				value.PerSeatOutboard = Cfg.Bind(text2, "Oar position: out", 0f, "Move every oar outward (+) or inward (-) from where the hull puts it (m). Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), advanced: false, 80, ShipFrom(ship, "Oar position: out"));
				value.PerSeatHeight = Cfg.Bind(text2, "Oar position: up", 0f, "Move every oar up (+) or down (-) from where the hull puts it (m). Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), advanced: false, 79, ShipFrom(ship, "Oar position: up"));
			}
			else
			{
				value.Placement = Cfg.Bind(text2, "Oarlock placement", "Fixed", "Fixed: one distance and height for every bench. Per seat: the rail is measured beside each seat (for hulls whose benches span the beam). Applies to newly loaded ships.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Fixed", "Per seat" }), advanced: false, 90, ShipFrom(ship, "Oarlock placement", "Oarlock placement"));
				value.Outboard = Cfg.Bind(text2, "Fixed: distance from centerline", (float)Math.Round(num, 2), "Fixed placement: oar pivot distance (m) from the centerline. Default " + text + ". Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), advanced: false, 89, ShipFrom(ship, "Fixed: distance from centerline", "Oarlock distance from centerline"));
				value.Height = Cfg.Bind(text2, "Fixed: height above seat", (float)Math.Round(num2, 2), "Fixed placement: oar pivot height (m) above the benches. Default " + text + ". Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 4f), advanced: false, 88, ShipFrom(ship, "Fixed: height above seat", "Oarlock height above seat"));
				value.PerSeatOutboard = Cfg.Bind(text2, "Per seat: distance from rail", 0f, "Per seat placement: move every oarlock outward (+) or inward (-) from the measured rail (m). Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-3f, 3f), advanced: false, 80, ShipFrom(ship, "Per seat: distance from rail", "Per seat: distance from rail"));
				value.PerSeatHeight = Cfg.Bind(text2, "Per seat: height above rail", 0.05f, "Per seat placement: height (m) above the measured rail. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 2f), advanced: false, 79, ShipFrom(ship, "Per seat: height above rail", "Per seat: height above rail"));
				value.Spacing = Cfg.Bind(text2, "Per seat: oar spacing", 1f, "Per seat placement: fore-aft gap (m) between oars of rowers sharing one bench. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), advanced: false, 78, ShipFrom(ship, "Per seat: oar spacing", "Per seat: oar spacing"));
			}
			shipSettings[ship] = value;
			Cfg.PurgeSection("6. Ship: " + text3);
			Cfg.PurgeSection("3. Oars - " + ship);
			Cfg.Save();
			Log.LogInfo((object)("[" + ship + "] '" + text2 + "': visuals " + (value.Visuals.Value ? "on" : "off") + ", physics " + (value.Physics.Value ? "on" : "off") + ", " + ((!flag) ? $"placement {value.Placement.Value}, {value.Outboard.Value:F2} m out, {value.Height.Value:F2} m up" : (shipProfile.Fitting ? "oars in oarlocks on the gunwale" : "oars through the hull's oar ports")) + $", size {value.Scale.Value:F2}" + (flag ? "" : $" (fixed default {text}: {num:F2} / {num2:F2})")));
			return value;
		}

		private void Update()
		{
			Synced?.Update();
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Synced?.Dispose();
			Translations.Unload();
			RowerHud.Unload();
			RowingShip.DestroyAll();
			foreach (OarModel value in shipOars.Values)
			{
				if (value != null && (Object)(object)value.Mesh != (Object)(object)OarMesh)
				{
					Object.Destroy((Object)(object)value.Mesh);
					if ((Object)(object)value.Texture != (Object)null)
					{
						Object.Destroy((Object)(object)value.Texture);
					}
				}
			}
			shipOars.Clear();
			foreach (OarModel value2 in shipFittings.Values)
			{
				if (value2 != null)
				{
					Object.Destroy((Object)(object)value2.Mesh);
					if ((Object)(object)value2.Texture != (Object)null)
					{
						Object.Destroy((Object)(object)value2.Texture);
					}
				}
			}
			shipFittings.Clear();
			if ((Object)(object)OarMesh != (Object)null)
			{
				Object.Destroy((Object)(object)OarMesh);
			}
			if ((Object)(object)OarTexture != (Object)null)
			{
				Object.Destroy((Object)(object)OarTexture);
			}
			OarMesh = null;
			OarTexture = null;
			OarPrefab = null;
		}

		internal static float RowerBonus(int index)
		{
			string value = RowerBonuses.Value;
			if (value != cachedBonusText)
			{
				List<float> list = new List<float>();
				string[] array = value.Split(',');
				foreach (string text in array)
				{
					if (float.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
					{
						list.Add(Mathf.Max(0f, result));
					}
				}
				cachedBonuses = list.ToArray();
				cachedBonusText = value;
			}
			if (cachedBonuses.Length == 0)
			{
				return 0f;
			}
			return cachedBonuses[Mathf.Min(index, cachedBonuses.Length - 1)];
		}
	}
	[HarmonyPatch(typeof(Ship), "Awake")]
	internal static class ShipAwakePatch
	{
		private static void Postfix(Ship __instance)
		{
			if (!Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<RowingShip>()))
			{
				((Component)__instance).gameObject.AddComponent<RowingShip>();
			}
		}
	}
	[HarmonyPatch(typeof(Player), "SetControls")]
	internal static class PlayerSetControlsPatch
	{
		private static void Prefix(Player __instance, ref Vector3 __0)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				RowingShip localRowingShip = RowingShip.LocalRowingShip;
				if (!((Object)(object)localRowingShip == (Object)null))
				{
					localRowingShip.HandleGearInput(__0.z);
					__0 = Vector3.zero;
				}
			}
		}
	}
	internal static class RowerHud
	{
		internal static bool Working;

		private static bool initialized;

		private static Hud hudInstance;

		private static GameObject root;

		private static GameObject controlsRoot;

		private static GameObject arrowBack;

		private static GameObject arrowSlow;

		private static GameObject arrowForward;

		private static GameObject arrowFast;

		private static readonly List<GameObject> hideWhileRowing = new List<GameObject>();

		private static Sprite oarIcon;

		private static Image wheelIcon;

		private static readonly Color GameWheelColor = new Color(1f, 93f / 145f, 0f, 1f);

		private static Image powerIcon;

		private static Sprite powerSprite;

		private static Color powerColor;

		private static Vector3 powerScale;

		private static bool powerPreserveAspect;

		private static bool powerSwapped;

		private static Sprite wheelSprite;

		private static Color wheelColor;

		private static Quaternion wheelRotation;

		private static bool wheelPreserveAspect;

		private static bool iconSwapped;

		private static Sprite rowingIcon;

		private const string RootField = "m_shipHudRoot";

		private const string ControlsRootField = "m_shipControlsRoot";

		private const string BackField = "m_rudderBackward";

		private const string SlowField = "m_rudderSlow";

		private const string ForwardField = "m_rudderForward";

		private const string FastField = "m_rudderFastForward";

		private static readonly string[] HideFields = new string[5] { "m_rudderLeft", "m_rudderRight", "m_fullSail", "m_halfSail", "m_shipRudderIndicator" };

		private static readonly string[] WindFields = new string[2] { "m_shipWindIndicatorRoot", "m_shipWindIconRoot" };

		private static readonly List<GameObject> windParts = new List<GameObject>();

		private static bool windHidden;

		internal static void LoadIcon(string pluginDir)
		{
			oarIcon = LoadSprite(Path.Combine(pluginDir, "oarIcon.png"), "HeaveHo_oarIcon");
			rowingIcon = LoadSprite(Path.Combine(pluginDir, "rowingPowerIcon.png"), "HeaveHo_rowingPowerIcon");
		}

		private static Sprite LoadSprite(string path, string name)
		{
			//IL_000e: 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_001b: Expected O, but got Unknown
			//IL_0077: 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)
			if (!File.Exists(path))
			{
				return null;
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
			{
				name = name
			};
			if (!ImageConversion.LoadImage(val, File.ReadAllBytes(path)))
			{
				Plugin.Log.LogWarning((object)("Couldn't read " + Path.GetFileName(path) + "."));
				Object.Destroy((Object)(object)val);
				return null;
			}
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Texture)val).filterMode = (FilterMode)1;
			Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
			((Object)val2).name = name;
			Plugin.Log.LogInfo((object)$"Loaded {Path.GetFileName(path)} ({((Texture)val).width}x{((Texture)val).height}).");
			return val2;
		}

		internal static void Unload()
		{
			RestoreWheel();
			RestorePowerIcon();
			SetWind(visible: true);
			Sprite[] array = (Sprite[])(object)new Sprite[2] { oarIcon, rowingIcon };
			foreach (Sprite val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Object.Destroy((Object)(object)val.texture);
					Object.Destroy((Object)(object)val);
				}
			}
			oarIcon = null;
			rowingIcon = null;
			Working = false;
			initialized = false;
		}

		internal static void Install(Harmony harmony)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(Hud), "UpdateShipHud", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"Hud.UpdateShipHud not found; rowing gears will show as text instead.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(RowerHud), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Postfix(Hud __instance, Player __0)
		{
			//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)
			//IL_0144: 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_015e: Unknown result type (might be due to invalid IL or missing references)
			if (!initialized || (Object)(object)__instance != (Object)(object)hudInstance || (Working && (Object)(object)root == (Object)null))
			{
				Init(__instance);
			}
			if (!Working || (Object)(object)__0 == (Object)null || (Object)(object)__0 != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			RowingShip localRowingShip = RowingShip.LocalRowingShip;
			if ((Object)(object)localRowingShip == (Object)null)
			{
				RestoreWheel();
				RestorePowerIcon();
				SetWind(visible: true);
				return;
			}
			SwapWheelForOar();
			ShowPowerIcon(localRowingShip);
			SetWind(visible: false);
			int localGear = localRowingShip.LocalGear;
			root.SetActive(true);
			if ((Object)(object)controlsRoot != (Object)null)
			{
				controlsRoot.SetActive(true);
			}
			arrowBack.SetActive(localGear < 0);
			arrowSlow.SetActive(localGear == 1);
			arrowForward.SetActive(localGear == 2);
			arrowFast.SetActive(false);
			foreach (GameObject item in hideWhileRowing)
			{
				if (item.activeSelf)
				{
					item.SetActive(false);
				}
			}
			if (!((Object)(object)controlsRoot != (Object)null))
			{
				return;
			}
			Camera mainCamera = Utils.GetMainCamera();
			if ((Object)(object)mainCamera != (Object)null)
			{
				Vector3 val = mainCamera.WorldToScreenPoint(localRowingShip.LocalHudAnchor);
				if (val.z > 0f)
				{
					controlsRoot.transform.position = val;
				}
			}
		}

		private static void SetWind(bool visible)
		{
			if (windHidden == !visible)
			{
				return;
			}
			foreach (GameObject windPart in windParts)
			{
				if ((Object)(object)windPart != (Object)null)
				{
					windPart.SetActive(visible);
				}
			}
			windHidden = !visible;
		}

		private static void SwapWheelForOar()
		{
			//IL_0035: 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)
			if (!((Object)(object)oarIcon == (Object)null) && !((Object)(object)wheelIcon == (Object)null))
			{
				iconSwapped = true;
				wheelIcon.sprite = oarIcon;
				((Graphic)wheelIcon).color = Color.white;
				wheelIcon.preserveAspect = true;
				((Transform)((Graphic)wheelIcon).rectTransform).localRotation = Quaternion.identity;
				if (!((Component)wheelIcon).gameObject.activeSelf)
				{
					((Component)wheelIcon).gameObject.SetActive(true);
				}
			}
		}

		private static void ShowPowerIcon(RowingShip ship)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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)
			Sprite val = (((Object)(object)rowingIcon != (Object)null) ? rowingIcon : oarIcon);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)powerIcon == (Object)null))
			{
				powerSwapped = true;
				int localGear = ship.LocalGear;
				powerIcon.sprite = val;
				powerIcon.preserveAspect = true;
				((Graphic)powerIcon).color = (Color)((localGear == 0) ? new Color(1f, 1f, 1f, 0.45f) : Color.white);
				float num = ((localGear == 2) ? 1.3f : 1f) * (1f + ((localGear != 0) ? (0.1f * ship.LocalStrokeLift) : 0f));
				((Transform)((Graphic)powerIcon).rectTransform).localScale = new Vector3(powerScale.x * num * ((localGear < 0) ? (-1f) : 1f), powerScale.y * num, powerScale.z);
				if (!((Component)powerIcon).gameObject.activeSelf)
				{
					((Component)powerIcon).gameObject.SetActive(true);
				}
			}
		}

		private static void RestorePowerIcon()
		{
			//IL_0029: 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)
			if (powerSwapped && !((Object)(object)powerIcon == (Object)null))
			{
				powerIcon.sprite = powerSprite;
				((Graphic)powerIcon).color = powerColor;
				powerIcon.preserveAspect = powerPreserveAspect;
				((Transform)((Graphic)powerIcon).rectTransform).localScale = powerScale;
				powerSwapped = false;
			}
		}

		private static void RememberGameIcon(Image img, string spriteName, out Sprite sprite, out Color color, out Vector3 scale, out bool preserve)
		{
			//IL_0004: 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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_00d7: 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)
			sprite = null;
			color = Color.white;
			scale = Vector3.one;
			preserve = false;
			if ((Object)(object)img == (Object)null)
			{
				return;
			}
			if ((!((Object)(object)img.sprite != (Object)null) || !((Object)img.sprite).name.StartsWith("HeaveHo_")) && (Object)(object)img.sprite != (Object)null)
			{
				sprite = img.sprite;
				color = ((Graphic)img).color;
				scale = ((Transform)((Graphic)img).rectTransform).localScale;
				preserve = img.preserveAspect;
				return;
			}
			Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
			foreach (Sprite val in array)
			{
				if ((Object)(object)val != (Object)null && ((Object)val).name == spriteName)
				{
					sprite = val;
					break;
				}
			}
			if ((Object)(object)sprite != (Object)null)
			{
				img.sprite = sprite;
				((Graphic)img).color = Color.white;
				((Transform)((Graphic)img).rectTransform).localScale = Vector3.one;
				img.preserveAspect = false;
				Plugin.Log.LogInfo((object)("[HUD] put the game's '" + spriteName + "' icon back (an earlier copy of this mod had replaced it)."));
			}
		}

		private static void RestoreWheel()
		{
			//IL_0029: 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)
			if (iconSwapped && !((Object)(object)wheelIcon == (Object)null))
			{
				wheelIcon.sprite = wheelSprite;
				((Graphic)wheelIcon).color = wheelColor;
				wheelIcon.preserveAspect = wheelPreserveAspect;
				((Transform)((Graphic)wheelIcon).rectTransform).localRotation = wheelRotation;
				iconSwapped = false;
			}
		}

		private static void Init(Hud hud)
		{
			//IL_0120: 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_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			bool flag = !initialized;
			initialized = true;
			hudInstance = hud;
			iconSwapped = false;
			hideWhileRowing.Clear();
			if (flag)
			{
				LogShipHud(hud);
			}
			root = GetObject(hud, "m_shipHudRoot");
			controlsRoot = GetObject(hud, "m_shipControlsRoot");
			arrowBack = GetObject(hud, "m_rudderBackward");
			arrowSlow = GetObject(hud, "m_rudderSlow");
			arrowForward = GetObject(hud, "m_rudderForward");
			arrowFast = GetObject(hud, "m_rudderFastForward");
			object? obj = AccessTools.Field(typeof(Hud), "m_shipRudderIcon")?.GetValue(hud);
			wheelIcon = (Image)((obj is Image) ? obj : null);
			GameObject obj2 = GetObject(hud, "m_rudder");
			powerIcon = ((obj2 != null) ? obj2.GetComponent<Image>() : null);
			iconSwapped = false;
			powerSwapped = false;
			RememberGameIcon(wheelIcon, "rudder", out wheelSprite, out wheelColor, out var _, out wheelPreserveAspect);
			wheelRotation = (((Object)(object)wheelIcon != (Object)null) ? ((Transform)((Graphic)wheelIcon).rectTransform).localRotation : Quaternion.identity);
			wheelColor = GameWheelColor;
			if ((Object)(object)wheelIcon != (Object)null)
			{
				((Graphic)wheelIcon).color = wheelColor;
			}
			RememberGameIcon(powerIcon, "ship_rudder_icon", out powerSprite, out powerColor, out powerScale, out powerPreserveAspect);
			if ((Object)(object)wheelIcon == (Object)null && (Object)(object)oarIcon != (Object)null)
			{
				Plugin.Log.LogWarning((object)"[HUD] m_shipRudderIcon not found; oar icon won't replace the wheel.");
			}
			windParts.Clear();
			windHidden = false;
			string[] windFields = WindFields;
			foreach (string fieldName in windFields)
			{
				GameObject val = GetObject(hud, fieldName);
				if (!((Object)(object)val == (Object)null))
				{
					windParts.Add(val);
					if (!val.activeSelf)
					{
						val.SetActive(true);
					}
				}
			}
			string[] hideFields = HideFields;
			foreach (string fieldName2 in hideFields)
			{
				GameObject val2 = GetObject(hud, fieldName2);
				if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)controlsRoot && (Object)(object)val2 != (Object)(object)root && ((Object)(object)wheelIcon == (Object)null || (Object)(object)val2 != (Object)(object)((Component)wheelIcon).gameObject))
				{
					hideWhileRowing.Add(val2);
				}
			}
			Working = (Object)(object)root != (Object)null && (Object)(object)arrowBack != (Object)null && (Object)(object)arrowSlow != (Object)null && (Object)(object)arrowForward != (Object)null && (Object)(object)arrowFast != (Object)null;
			if (Working)
			{
				Plugin.Log.LogInfo((object)"Rower HUD hooked into the ship HUD.");
			}
			else
			{
				Plugin.Log.LogWarning((object)"Rower HUD: core ship HUD fields missing; using text messages instead. See the [HUD] lines above for the real names.");
			}
		}

		private static GameObject GetObject(Hud hud, string fieldName)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Hud), fieldName);
			if (fieldInfo == null)
			{
				Plugin.Log.LogWarning((object)("[HUD] field '" + fieldName + "' not found"));
				return null;
			}
			object value = fieldInfo.GetValue(hud);
			GameObject val = (GameObject)((value is GameObject) ? value : null);
			if (val != null)
			{
				return val;
			}
			Component val2 = (Component)((value is Component) ? value : null);
			if (val2 != null)
			{
				return val2.gameObject;
			}
			return null;
		}

		private static void LogShipHud(Hud hud)
		{
			StringBuilder stringBuilder = new StringBuilder("[HUD] ship-related Hud fields:\n");
			foreach (FieldInfo declaredField in AccessTools.GetDeclaredFields(typeof(Hud)))
			{
				string text = declaredField.Name.ToLowerInvariant();
				if (text.Contains("ship") || text.Contains("rudder") || text.Contains("sail") || text.Contains("wind"))
				{
					object value = declaredField.GetValue(declaredField.IsStatic ? null : hud);
					GameObject val = (GameObject)((value is GameObject) ? value : null);
					object obj;
					if (val == null)
					{
						Component val2 = (Component)((value is Component) ? value : null);
						obj = ((val2 != null) ? val2.gameObject : null);
					}
					else
					{
						obj = val;
					}
					GameObject val3 = (GameObject)obj;
					stringBuilder.AppendLine(((Object)(object)val3 != (Object)null) ? ("  " + declaredField.Name + " (" + declaredField.FieldType.Name + ") -> '" + HierarchyPath(val3.transform) + "'") : ("  " + declaredField.Name + " (" + declaredField.FieldType.Name + ")"));
				}
			}
			object? obj2 = AccessTools.Field(typeof(Hud), "m_shipHudRoot")?.GetValue(hud);
			GameObject val4 = (GameObject)((obj2 is GameObject) ? obj2 : null);
			if ((Object)(object)val4 != (Object)null)
			{
				stringBuilder.AppendLine("[HUD] ship HUD hierarchy:");
				DumpTree(val4.transform, 1, stringBuilder);
			}
			Plugin.Log.LogDebug((object)stringBuilder.ToString());
		}

		private static void DumpTree(Transform t, int depth, StringBuilder sb)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			if (depth > 5)
			{
				return;
			}
			Image component = ((Component)t).GetComponent<Image>();
			string text = (((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null) ? (" sprite=" + ((Object)component.sprite).name) : "");
			sb.AppendLine(new string(' ', depth * 2) + ((Object)t).name + text);
			foreach (Transform item in t)
			{
				Transform t2 = item;
				DumpTree(t2, depth + 1, sb);
			}
		}

		private static string HierarchyPath(Transform t)
		{
			string text = ((Object)t).name;
			while ((Object)(object)t.parent != (Object)null)
			{
				t = t.parent;
				text = ((Object)t).name + "/" + text;
			}
			return text;
		}
	}
	public class RowingShip : MonoBehaviour
	{
		private struct SeatPing
		{
			public float Time;

			public int Gear;
		}

		private class Oar
		{
			public Transform Pivot;

			public Transform Shaft;

			public Transform Blade;

			public Transform Model;

			public int Seat;

			public float Blend;

			public Transform Tip;

			public bool InWater;

			public float Out;

			public bool OutKnown;

			public bool WaterKnown;

			public float Water;

			public float WaterSmooth;
		}

		internal static RowingShip LocalRowingShip;

		private const int GearBack = -1;

		private const int GearNone = 0;

		private const int GearEasy = 1;

		private const int GearHard = 2;

		private const string RpcRow = "HeaveHo_Row";

		private const float PingInterval = 1f;

		private const float PingTimeout = 2.5f;

		private const int MaxSeats = 32;

		private const float OarInboard = 0.6f;

		private static readonly int ZdoRowMask = StringExtensionMethods.GetStableHashCode("HeaveHo_RowerMask");

		private static readonly int ZdoHardMask = StringExtensionMethods.GetStableHashCode("HeaveHo_HardMask");

		private static readonly int ZdoBackMask = StringExtensionMethods.GetStableHashCode("HeaveHo_BackMask");

		private static readonly FieldRef<Player, Transform> PlayerAttachPoint = AccessTools.FieldRefAccess<Player, Transform>("m_attachPoint");

		private static readonly List<RowingShip> All = new List<RowingShip>();

		private static readonly HashSet<string> loggedShips = new HashSet<string>();

		private static readonly HashSet<string> loggedHulls = new HashSet<string>();

		private static readonly FieldInfo ForceDisableInit = AccessTools.Field(typeof(ZNetView), "m_forceDisableInit");

		private bool preview;

		private ZNetView nview;

		private Rigidbody body;

		private Ship shipComponent;

		private bool sailing;

		private Vector3 lastPosition;

		private float speedSmoothed;

		private Chair[] seats = Array.Empty<Chair>();

		private Vector3[] seatLocal = Array.Empty<Vector3>();

		private float[] seatSide = Array.Empty<float>();

		private readonly Dictionary<int, SeatPing> seatPings = new Dictionary<int, SeatPing>();

		private readonly List<int> staleSeats = new List<int>();

		private int localSeat = -1;

		private int localGear;

		private bool localRowing;

		private int lastSentCode = int.MinValue;

		private bool exhausted;

		private float pingTimer;

		private bool upHeld;

		private bool downHeld;

		private Oar[] oars = Array.Empty<Oar>();

		private readonly float[] gearPhase = new float[3];

		private Plugin.ShipOarSettings shipCfg;

		private ShipProfile profile;

		private float[] portYaw = Array.Empty<float>();

		private Plugin.OarModel oarModel;

		private Transform[] fittings = Array.Empty<Transform>();

		private Vector3[] railPoint = Array.Empty<Vector3>();

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

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

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

		private Material fittingMaterial;

		private Material objMaterial;

		private static bool Headless => (int)SystemInfo.graphicsDeviceType == 4;

		private float Outboard
		{
			get
			{
				if (shipCfg == null || shipCfg.Outboard == null)
				{
					return 1.1f;
				}
				return shipCfg.Outboard.Value;
			}
		}

		private float OarlockHeight
		{
			get
			{
				if (shipCfg == null || shipCfg.Height == null)
				{
					return 0.5f;
				}
				return shipCfg.Height.Value;
			}
		}

		private bool PerSeat
		{
			get
			{
				if (profile == null)
				{
					if (shipCfg != null)
					{
						return shipCfg.Placement.Value == "Per seat";
					}
					return false;
				}
				return true;
			}
		}

		private bool VisualsOn
		{
			get
			{
				if (shipCfg != null)
				{
					return shipCfg.Visuals.Value;
				}
				return true;
			}
		}

		private bool PhysicsOn
		{
			get
			{
				if (shipCfg != null)
				{
					return shipCfg.Physics.Value;
				}
				return true;
			}
		}

		private float OarScale => ((shipCfg != null) ? shipCfg.Scale.Value : 1f) * Plugin.OarModelScale.Value;

		private bool Valid
		{
			get
			{
				if ((Object)(object)nview != (Object)null && nview.IsValid())
				{
					return nview.GetZDO() != null;
				}
				return false;
			}
		}

		internal int LocalGear => localGear;

		internal float LocalStrokeLift
		{
			get
			{
				if (localGear != 0)
				{
					return 0.5f + 0.5f * Mathf.Cos(gearPhase[(localGear != -1) ? ((localGear == 1) ? 1 : 2) : 0]);
				}
				return 0f;
			}
		}

		internal Vector3 LocalHudAnchor
		{
			get
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_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_0040: 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_0046: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
				//IL_00de: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
				if (localSeat < 0 || localSeat >= seats.Length)
				{
					return ((Component)this).transform.position + Vector3.up * 2f;
				}
				Vector3 val = OarlockLocal(localSeat);
				float num = Mathf.Min(Mathf.Abs(val.x - seatLocal[localSeat].x), 1.2f);
				val.x = seatLocal[localSeat].x + seatSide[localSeat] * num;
				val.x += seatSide[localSeat] * Plugin.HudOutboard.Value;
				return ((Component)this).transform.TransformPoint(val) + Vector3.up * Plugin.HudHeight.Value;
			}
		}

		private void Awake()
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			nview = ((Component)this).GetComponent<ZNetView>();
			preview = ForceDisableInit != null && (bool)ForceDisableInit.GetValue(null);
			body = ((Component)this).GetComponent<Rigidbody>();
			shipComponent = ((Component)this).GetComponent<Ship>();
			lastPosition = ((Component)this).transform.position;
			FindRowingSeats();
			All.Add(this);
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				nview.Register<int>("HeaveHo_Row", (Action<long, int>)RPC_Row);
			}
		}

		internal static void DestroyAll()
		{
			RowingShip[] array = All.ToArray();
			foreach (RowingShip rowingShip in array)
			{
				if ((Object)(object)rowingShip != (Object)null)
				{
					Object.Destroy((Object)(object)rowingShip);
				}
			}
			All.Clear();
			LocalRowingShip = null;
		}

		private void Start()
		{
			if (seats.Length != 0)
			{
				ResolveOarlock();
			}
			if (seats.Length != 0 && !Headless && (preview || ((Object)(object)nview != (Object)null && nview.IsValid())))
			{
				BuildOars();
			}
		}

		private void ResolveOarlock()
		{
			string prefabName = Utils.GetPrefabName(((Component)this).gameObject);
			shipCfg = Plugin.GetShipSettings(prefabName, MeasureOarlock);
			profile = ShipProfile.For(prefabName);
			if (PerSeat)
			{
				MeasurePerSeat();
			}
		}

		private Vector3 OarlockLocal(int s)
		{
			//IL_012c: 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_005b: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (PerSeat && s < railPoint.Length)
			{
				Vector3 result = (Vector3)(railFound[s] ? railPoint[s] : new Vector3(seatSide[s] * Outboard, seatLocal[s].y + OarlockHeight, seatLocal[s].z));
				result.x += seatSide[s] * shipCfg.PerSeatOutboard.Value;
				result.y += shipCfg.PerSeatHeight.Value;
				if (shipCfg.Spacing != null)
				{
					result.z += ((float)railGroupIndex[s] - (float)(railGroupCount[s] - 1) * 0.5f) * shipCfg.Spacing.Value;
				}
				return result;
			}
			return new Vector3(seatSide[s] * Outboard, seatLocal[s].y + OarlockHeight, seatLocal[s].z);
		}

		private bool MeasureOarlock(out float distance, out float height)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			int num = seats.Length / 2;
			if (MeasureRail(seatLocal[num], seatSide[num], out distance, out var railY))
			{
				height = railY - seatLocal[num].y;
				return true;
			}
			height = 0f;
			return false;
		}

		private bool MeasureRail(Vector3 seat, float side, out float distance, out float railY)
		{
			//IL_0028: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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)
			//IL_006c: 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_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			distance = 0f;
			railY = 0f;
			Ship component = ((Component)this).GetComponent<Ship>();
			Vector3 val = ((Component)this).transform.TransformDirection(new Vector3(0f - side, 0f, 0f));
			for (float num = 2.5f; num >= -0.3f; num -= 0.05f)
			{
				Vector3 val2 = ((Component)this).transform.TransformPoint(new Vector3(side * 20f, seat.y + num, seat.z));
				float num2 = -1f;
				RaycastHit[] array = Physics.RaycastAll(val2, val, 20f, -1, (QueryTriggerInteraction)1);
				for (int i = 0; i < array.Length; i++)
				{
					RaycastHit val3 = array[i];
					if (!((Object)(object)((RaycastHit)(ref val3)).collider == (Object)null) && !((Object)(object)((Component)((RaycastHit)(ref val3)).collider).GetComponentInParent<Ship>() != (Object)(object)component))
					{
						float num3 = Mathf.Abs(((Component)this).transform.InverseTransformPoint(((RaycastHit)(ref val3)).point).x);
						if (num3 > num2)
						{
							num2 = num3;
						}
					}
				}
				if (num2 > 0.2f)
				{
					distance = num2;
					railY = seat.y + num + 0.05f;
					return true;
				}
			}
			return false;
		}

		private void MeasurePerSeat()
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			int num = seats.Length;
			railPoint = (Vector3[])(object)new Vector3[num];
			railFound = new bool[num];
			railGroupIndex = new int[num];
			railGroupCount = new int[num];
			int num2 = 0;
			portYaw = new float[num];
			for (int i = 0; i < num; i++)
			{
				float distance;
				float railY;
				if (profile != null && profile.NearestPort(seatLocal[i].z, out var port))
				{
					railPoint[i] = new Vector3(seatSide[i] * port.X, profile.PortY, port.Z);
					railFound[i] = true;
					portYaw[i] = (0f - seatSide[i]) * port.Yaw;
					num2++;
				}
				else if (MeasureRail(seatLocal[i], seatSide[i], out distance, out railY))
				{
					railPoint[i] = new Vector3(seatSide[i] * distance, railY, seatLocal[i].z);
					railFound[i] = true;
					num2++;
				}
			}
			Dictionary<string, List<int>> dictionary = new Dictionary<string, List<int>>();
			for (int j = 0; j < num; j++)
			{
				string key = $"{seatSide[j]}:{Mathf.Round(seatLocal[j].z * 2f)}";
				if (!dictionary.TryGetValue(key, out var value))
				{
					value = (dictionary[key] = new List<int>());
				}
				value.Add(j);
			}
			foreach (List<int> value2 in dictionary.Values)
			{
				value2.Sort((int a, int b) => Mathf.Abs(seatLocal[a].x).CompareTo(Mathf.Abs(seatLocal[b].x)));
				for (int num3 = 0; num3 < value2.Count; num3++)
				{
					railGroupIndex[value2[num3]] = num3;
					railGroupCount[value2[num3]] = value2.Count;
				}
			}
			Plugin.Log.LogInfo((object)(string.Format("[{0}] per-seat oarlocks: {1} found for {2}/{3} seats, ", Utils.GetPrefabName(((Component)this).gameObject), (profile != null) ? "oar port" : "rail", num2, num) + $"{dictionary.Count} rail groups"));
		}

		private void OnDestroy()
		{
			All.Remove(this);
			if ((Object)(object)LocalRowingShip == (Object)(object)this)
			{
				LocalRowingShip = null;
			}
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				nview.Unregister("HeaveHo_Row");
			}
			Oar[] array = oars;
			foreach (Oar oar in array)
			{
				if (oar != null && (Object)(object)oar.Pivot != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)oar.Pivot).gameObject);
				}
			}
			Transform[] array2 = fittings;
			foreach (Transform val in array2)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			if ((Object)(object)objMaterial != (Object)null)
			{
				Object.Destroy((Object)(object)objMaterial);
			}
			if ((Object)(object)fittingMaterial != (Object)null)
			{
				Object.Destroy((Object)(object)fittingMaterial);
			}
		}

		private static Transform SeatPoint(Chair c)
		{
			if (!((Object)(object)c.m_attachPoint != (Object)null))
			{
				return ((Component)c).transform;
			}
			return c.m_attachPoint;
		}

		private void FindRowingSeats()
		{
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_037e: Unknown result type (might be due to invalid IL or missing references)
			//IL_038d: Unknown result type (might be due to invalid IL or missing references)
			//IL_039c: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>();
			string[] array = Plugin.RowingSeatAnimations.Value.Split(',');
			foreach (string text in array)
			{
				if (text.Trim().Length > 0)
				{
					hashSet.Add(text.Trim());
				}
			}
			List<string> list = new List<string>();
			string[] array2 = Plugin.GetSeatNameFilter(Utils.GetPrefabName(((Component)this).gameObject)).Value.Split(',');
			foreach (string text2 in array2)
			{
				if (text2.Trim().Length > 0)
				{
					list.Add(text2.Trim().ToLowerInvariant());
				}
			}
			List<Vector3> list2 = new List<Vector3>();
			ShipControlls[] componentsInChildren = ((Component)this).GetComponentsInChildren<ShipControlls>(true);
			foreach (ShipControlls val in componentsInChildren)
			{
				list2.Add(((Component)this).transform.InverseTransformPoint(((Object)(object)val.m_attachPoint != (Object)null) ? val.m_attachPoint.position : ((Component)val).transform.position));
			}
			Chair[] componentsInChildren2 = ((Component)this).GetComponentsInChildren<Chair>(true);
			List<Chair> list3 = new List<Chair>();
			HashSet<Chair> hashSet2 = new HashSet<Chair>();
			Chair[] array3 = componentsInChildren2;
			foreach (Chair val2 in array3)
			{
				if (!hashSet.Contains(val2.m_attachAnimation) || list3.Count >= 32)
				{
					continue;
				}
				string cn = ((Object)val2).name.ToLowerInvariant();
				if (list.Count <= 0 || list.Exists((string n) => cn.StartsWith(n)))
				{
					Vector3 at = ((Component)this).transform.InverseTransformPoint(SeatPoint(val2).position);
					if (list2.Exists((Vector3 h) => Vector3.Distance(h, at) < 2f))
					{
						hashSet2.Add(val2);
					}
					else
					{
						list3.Add(val2);
					}
				}
			}
			seats = list3.ToArray();
			seatLocal = (Vector3[])(object)new Vector3[seats.Length];
			seatSide = new float[seats.Length];
			for (int num = 0; num < seats.Length; num++)
			{
				seatLocal[num] = ((Component)this).transform.InverseTransformPoint(SeatPoint(seats[num]).position);
				seatSide[num] = ((Mathf.Abs(seatLocal[num].x) > 0.05f) ? Mathf.Sign(seatLocal[num].x) : ((num % 2 == 0) ? 1f : (-1f)));
			}
			string prefabName = Utils.GetPrefabName(((Component)this).gameObject);
			if (loggedShips.Add(prefabName))
			{
				Chair[] array4 = componentsInChildren2;
				foreach (Chair val3 in array4)
				{
					Vector3 val4 = ((Component)this).transform.InverseTransformPoint(SeatPoint(val3).position);
					string text3 = (list3.Contains(val3) ? "ROWS" : (hashSet2.Contains(val3) ? "excluded (helm)" : "excluded"));
					Plugin.Log.LogInfo((object)("[" + prefabName + "] seat '" + ((Object)val3).name + "' anim='" + val3.m_attachAnimation + "' " + $"sit=({val4.x:F2},{val4.y:F2},{val4.z:F2}) {text3}"));
				}
			}
		}

		private void Update()
		{
			if (!Valid)
			{
				if (preview)
				{
					UpdateOars(0, 0, 0, Mathf.Max(Time.deltaTime, 0.016f));
				}
				return;
			}
			float deltaTime = Time.deltaTime;
			if (!(deltaTime <= 0f))
			{
				UpdateLocalRower(deltaTime);
				ZDO zDO = nview.GetZDO();
				UpdateOars(zDO.GetInt(ZdoRowMask, 0), zDO.GetInt(ZdoHardMask, 0), zDO.GetInt(ZdoBackMask, 0), deltaTime);
			}
		}

		internal void HandleGearInput(float forward)
		{
			bool flag = forward > 0.5f;
			bool flag2 = forward < -0.5f;
			if (flag && !upHeld)
			{
				ShiftGear(1);
			}
			if (flag2 && !downHeld)
			{
				ShiftGear(-1);
			}
			upHeld = flag;
			downHeld = flag2;
		}

		private void ShiftGear(int delta)
		{
			int num = Mathf.Clamp(localGear + delta, -1, 2);
			if (num == localGear)
			{
				return;
			}
			localGear = num;
			if (!RowerHud.Working)
			{
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, GearName(localGear), 0, (Sprite)null, false);
				}
			}
		}

		private static string KeyToken(string keyboard, string gamepad)
		{
			return "<color=yellow><b>$KEY_" + (ZInput.IsGamepadActive() ? gamepad : keyboard) + "</b></color>";
		}

		private static string GearName(int gear)
		{
			return gear switch
			{
				-1 => "< " + Translations.T("heaveho_gear_back"), 
				1 => "> " + Translations.T("heaveho_gear_easy"), 
				2 => ">> " + Translations.T("heaveho_gear_hard"), 
				_ => Translations.T("heaveho_gear_none"), 
			};
		}

		private void UpdateLocalRower(float dt)
		{
			Player localPlayer = Player.m_localPlayer;
			int num = FindLocalSeat(localPlayer);
			if (num < 0)
			{
				if ((Object)(object)LocalRowingShip == (Object)(object)this)
				{
					LocalRowingShip = null;
				}
				localGear = 0;
				upHeld = (downHeld = false);
			}
			else if ((Object)(object)LocalRowingShip != (Object)(object)this)
			{
				LocalRowingShip = this;
				localGear = 0;
				upHeld = (downHeld = false);
				((Character)localPlayer).Message((MessageType)1, Translations.T("heaveho_hint", KeyToken("Forward", "LStickUp"), KeyToken("Backward", "LStickDown"), KeyToken("Jump", "Jump")), 0, (Sprite)null, false);
			}
			if (num >= 0 && localGear != 0)
			{
				float stamina = localPlayer.GetStamina();
				if (exhausted && stamina >= Plugin.RecoverStamina.Value)
				{
					exhausted = false;
				}
				else if (!exhausted && stamina <= 0.5f)
				{
					exhausted = true;
					((Character)localPlayer).Message((MessageType)2, Translations.T("heaveho_tired"), 0, (Sprite)null, false);
				}
			}
			bool flag = num >= 0 && localGear != 0 && !exhausted;
			if (flag)
			{
				float num2 = ((localGear == 2) ? 1f : Plugin.EasyStaminaMultiplier.Value);
				if (PhysicsOn)
				{
					((Character)localPlayer).UseStamina(Plugin.StaminaPerSecond.Value * num2 * dt);
				}
			}
			if (localRowing && (!flag || num != localSeat))
			{
				SendRow(-localSeat - 1);
				lastSentCode = int.MinValue;
			}
			if (flag)
			{
				int num3 = num * 4 + (localGear + 1);
				pingTimer -= dt;
				if (num3 != lastSentCode || pingTimer <= 0f)
				{
					SendRow(num3);
					lastSentCode = num3;
					pingTimer = 1f;
				}
			}
			localRowing = flag;
			localSeat = num;
		}

		private int FindLocalSeat(Player player)
		{
			if ((Object)(object)player == (Object)null || seats.Length == 0 || !((Character)player).IsAttached())
			{
				return -1;
			}
			if (!VisualsOn && !PhysicsOn)
			{
				return -1;
			}
			Transform val = PlayerAttachPoint.Invoke(player);
			if ((Object)(object)val == (Object)null)
			{
				return -1;
			}
			Chair componentInParent = ((Component)val).GetComponentInParent<Chair>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return -1;
			}
			return Array.IndexOf(seats, componentInParent);
		}

		private void SendRow(int code)
		{
			if (Valid)
			{
				nview.InvokeRPC("HeaveHo_Row", new object[1] { code });
			}
		}

		private void RPC_Row(long sender, int code)
		{
			if (nview.IsOwner() && code < 128 && code >= -32)
			{
				if (code >= 0)
				{
					seatPings[code / 4] = new SeatPing
					{
						Time = Time.time,
						Gear = code % 4 - 1
					};
				}
				else
				{
					seatPings.Remove(-code - 1);
				}
			}
		}

		private void FixedUpdate()
		{
			if (!Valid || !nview.IsOwner())
			{
				return;
			}
			float time = Time.time;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			staleSeats.Clear();
			foreach (KeyValuePair<int, SeatPing> seatPing in seatPings)
			{
				int key = seatPing.Key;
				if (time - seatPing.Value.Time > 2.5f || key < 0 || key >= seats.Length)
				{
					staleSeats.Add(key);
					continue;
				}
				num |= 1 << key;
				if (seatPing.Value.Gear == 2)
				{
					num2 |= 1 << key;
				}
				if (seatPing.Value.Gear == -1)
				{
					num3 |= 1 << key;
				}
			}
			foreach (int staleSeat in staleSeats)
			{
				seatPings.Remove(staleSeat);
			}
			ZDO zDO = nview.GetZDO();
			if (zDO.GetInt(ZdoRowMask, 0) != num)
			{
				zDO.Set(ZdoRowMask, num, false);
			}
			if (zDO.GetInt(ZdoHardMask, 0) != num2)
			{
				zDO.Set(ZdoHardMask, num2, false);
			}
			if (zDO.GetInt(ZdoBackMask, 0) != num3)
			{
				zDO.Set(ZdoBackMask, num3, false);
			}
			ApplyRowingForces(num, num2, num3);
		}

		private void ApplyRowingForces(int rowMask, int hardMask, int backMask)
		{
			//IL_0020: 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_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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: 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_012a: 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_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: 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)
			//IL_0141: 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)
			if (rowMask == 0 || (Object)(object)body == (Object)null || !PhysicsOn)
			{
				return;
			}
			Vector3 val = Vector3.ProjectOnPlane(((Component)this).transform.forward, Vector3.up);
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			float num = Vector3.Dot(body.linearVelocity, normalized);
			Vector3 worldCenterOfMass = body.worldCenterOfMass;
			Vector3 val2 = Vector3.zero;
			Vector3 val3 = Vector3.zero;
			int num2 = 0;
			for (int i = 0; i < seats.Length; i++)
			{
				if ((rowMask & (1 << i)) != 0)
				{
					bool flag = (backMask & (1 << i)) != 0;
					bool flag2 = (hardMask & (1 << i)) != 0;
					float num3 = (flag ? (-1f) : 1f);
					float num4 = (flag2 ? 1f : (flag ? Plugin.BackMultiplier.Value : Plugin.EasyMultiplier.Value));
					float num5 = Plugin.RowerBonus(num2++);
					if (!(num * num3 >= Plugin.MaxRowingSpeed.Value))
					{
						Vector3 val4 = normalized * (num3 * Plugin.RowerStrength.Value * num5 * num4 * body.mass);
						Vector3 val5 = ((Component)this).transform.TransformPoint(OarlockLocal(i));
						val2 += val4;
						val3 += Vector3.Cross(val5 - worldCenterOfMass, val4);
					}
				}
			}
			body.AddForce(val2, (ForceMode)0);
			Vector3 val6 = Vector3.Project(val3, ((Component)this).transform.up) * Plugin.TurnStrength.Value;
			body.AddTorque(val6, (ForceMode)0);
		}

		private void BuildOars()
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			MeshRenderer val = FindHullRenderer();
			Material val2 = (((Object)(object)val != (Object)null) ? ((Renderer)val).sharedMaterial : null);
			int layer = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject.layer : ((Component)this).gameObject.layer);
			oarModel = Plugin.OarFor(Utils.GetPrefabName(((Component)this).gameObject));
			BuildFittings(val2, layer);
			oars = new Oar[seats.Length];
			for (int i = 0; i < seats.Length; i++)
			{
				Transform transform = new GameObject("HeaveHo_Oar_" + i).transform;
				transform.SetParent(((Component)this).transform, false);
				if (oarModel != null)
				{
					oars[i] = new Oar
					{
						Pivot = transform,
						Model = SpawnObjModel(transform, val2, layer),
						Seat = i
					};
				}
				else if ((Object)(object)Plugin.OarPrefab != (Object)null)
				{
					oars[i] = new Oar
					{
						Pivot = transform,
						Model = SpawnModel(transform, val2, layer),
						Seat = i
					};
				}
				else
				{
					oars[i] = new Oar
					{
						Pivot = transform,
						Shaft = MakePart((PrimitiveType)2, transform, val2, layer),
						Blade = MakePart((PrimitiveType)3, transform, val2, layer),
						Seat = i
					};
				}
				Transform transform2 = new GameObject("HeaveHo_BladeTip").transform;
				transform2.SetParent(transform, false);
				oars[i].Tip = transform2;
			}
		}

		private void BuildFittings(Material hullMaterial, int layer)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			Plugin.OarModel oarModel = ((profile != null && profile.Fitting) ? Plugin.FittingFor(Utils.GetPrefabName(((Component)this).gameObject)) : null);
			if (oarModel != null && !((Object)(object)hullMaterial == (Object)null))
			{
				Material val = new Material(hullMaterial)
				{
					name = "HeaveHo_Oarlock"
				};
				if (val.HasProperty("_BumpMap"))
				{
					val.SetTexture("_BumpMap", (Texture)null);
				}
				val.DisableKeyword("_NORMALMAP");
				fittingMaterial = val;
				fittings = (Transform[])(object)new Transform[seats.Length];
				for (int i = 0; i < seats.Length; i++)
				{
					GameObject val2 = new GameObject("HeaveHo_Oarlock_" + i)
					{
						layer = layer
					};
					val2.transform.SetParent(((Component)this).transform, false);
					val2.AddComponent<MeshFilter>().sharedMesh = oarModel.Mesh;
					((Renderer)val2.AddComponent<MeshRenderer>()).sharedMaterial = val;
					fittings[i] = val2.transform;
				}
			}
		}

		internal static float WaterLevelAt(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			float liquidLevel = Floating.GetLiquidLevel(p, 1f, (LiquidType)10);
			if (liquidLevel > -1000f)
			{
				return liquidLevel;
			}
			if (!((Object)(object)ZoneSystem.instance != (Object)null))
			{
				return 30f;
			}
			return ZoneSystem.instance.m_waterLevel;
		}

		private void UpdateSplash(Oar oar, bool rowing, bool hard)
		{
			//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)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_009d: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)oar.Tip == (Object)null)
			{
				return;
			}
			if (!rowing || oar.Blend < 0.95f || !Plugin.Splashes.Value)
			{
				oar.InWater = false;
				return;
			}
			Vector3 position = oar.Tip.position;
			float num = (oar.WaterKnown ? oar.Water : WaterLevelAt(position));
			bool flag = (oar.InWater ? (position.y < num + 0.08f) : (position.y < num - 0.02f));
			if (flag != oar.InWater)
			{
				oar.InWater = flag;
				SplashEffects.Spawn(new Vector3(position.x, num + 0.02f, position.z), ((Component)this).transform.rotation, flag, hard);
			}
		}

		private static bool IsWaterMask(Renderer r, Material m)
		{
			string text = ((Object)r).name.ToLowerInvariant();
			string text2 = (((Object)(object)m.shader != (Object)null) ? ((Object)m.shader).name.ToLowerInvariant() : "");
			if (!text.Contains("watermask") && !text.Contains("water_mask") && !text2.Contains("watermask"))
			{
				return text2.Contains("water mask");
			}
			return true;
		}

		private Transform SpawnObjModel(Transform pivot, Material hullMaterial, int layer)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//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_0069: Expected O, but got Unknown
			//IL_00ae: 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_00f7: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("HeaveHo_OarModel");
			val.layer = layer;
			val.transform.SetParent(pivot, false);
			val.AddComponent<MeshFilter>().sharedMesh = oarModel.Mesh;
			if ((Object)(object)objMaterial == (Object)null && (Object)(object)hullMaterial != (Object)null)
			{
				objMaterial = new Material(hullMaterial)
				{
					name = "HeaveHo_OarObj"
				};
				if (objMaterial.HasProperty("_MainTex") && !oarModel.HullTexture)
				{
					objMaterial.SetTexture("_MainTex", (Texture)(object)oarModel.Texture);
					objMaterial.SetTextureScale("_MainTex", Vector2.one);
					objMaterial.SetTextureOffset("_MainTex", Vector2.zero);
				}
				if (objMaterial.HasProperty("_Color") && !oarModel.HullTexture)
				{
					objMaterial.SetColor("_Color", Color.white);
				}
				if (objMaterial.HasProperty("_BumpMap"))
				{
					objMaterial.SetTexture("_BumpMap", (Texture)null);
				}
				objMaterial.DisableKeyword("_NORMALMAP");
			}
			MeshRenderer val2 = val.AddComponent<MeshRenderer>();
			if ((Object)(object)objMaterial != (Object)null)
			{
				((Renderer)val2).sharedMaterial = objMaterial;
			}
			return val.transform;
		}

		private static Transform SpawnModel(Transform pivot, Material hullMaterial, int layer)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: 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)
			GameObject val = Object.Instantiate<GameObject>(Plugin.OarPrefab, pivot, false);
			((Object)val).name = "HeaveHo_OarModel";
			Collider[] componentsInChildren = val.GetComponentsInChildren<Collider>(true);
			foreach (Collider val2 in componentsInChildren)
			{
				Object.DestroyImmediate((Object)(object)val2);
			}
			Transform[] componentsInChildren2 = val.GetComponentsInChildren<Transform>(true);
			foreach (Transform val3 in componentsInChildren2)
			{
				((Component)val3).gameObject.layer = layer;
			}
			if ((Object)(object)hullMaterial != (Object)null)
			{
				Renderer[] componentsInChildren3 = val.GetComponentsInChildren<Renderer>(true);
				foreach (Renderer val4 in componentsInChildren3)
				{
					Material[] sharedMaterials = val4.sharedMaterials;
					for (int l = 0; l < sharedMaterials.Length; l++)
					{
						Material val5 = sharedMaterials[l];
						Material val6 = new Material(hullMaterial)
						{
							name = "HeaveHo_Oar_" + (((Object)(object)val5 != (Object)null) ? ((Object)val5).name : "mat")
						};
						if ((Object)(object)val5 != (Object)null)
						{
							if (val5.HasProperty("_MainTex") && val6.HasProperty("_MainTex"))
							{
								val6.SetTexture("_MainTex", val5.GetTexture("_MainTex"));
								val6.SetTextureScale("_MainTex", Vector2.one);
								val6.SetTextureOffset("_MainTex", Vector2.zero);
							}
							if (val5.HasProperty("_Color") && val6.HasProperty("_Color"))
							{
								val6.SetColor("_Color", val5.GetColor("_Color"));
							}
							if (val6.HasProperty("_BumpMap"))
							{
								val6.SetTexture("_BumpMap", (Texture)null);
							}
							val6.DisableKeyword("_NORMALMAP");
						}
						sharedMaterials[l] = val6;
					}
					val4.sharedMaterials = sharedMaterials;
				}
			}
			return val.transform;
		}

		private MeshRenderer FindHullRenderer()
		{
			//IL_0086: 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_008f: 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)
			MeshRenderer val = null;
			float num = float.MinValue;
			MeshRenderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<MeshRenderer>(true);
			foreach (MeshRenderer val2 in componentsInChildren)
			{
				Material sharedMaterial = ((Renderer)val2).sharedMaterial;
				if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null) && !IsWaterMask((Renderer)(object)val2, sharedMaterial) && ((Component)val2).gameObject.activeInHierarchy && !((Object)val2).name.StartsWith("HeaveHo_") && sharedMaterial.renderQueue < 2500)
				{
					Bounds bounds = ((Renderer)val2).bounds;
					Vector3 size = ((Bounds)(ref bounds)).size;
					float num2 = ((Vector3)(ref size)).sqrMagnitude;
					if (((Object)sharedMaterial.shader).name == "Custom/Piece")
					{
						num2 += 10000f;
					}
					if (((Object)val2).name.Equals("hull", StringComparison.OrdinalIgnoreCase))
					{
						num2 += 100000f;
					}
					if (num2 > num)
					{
						val = val2;
						num = num2;
					}
				}
			}
			if ((Object)(object)val != (Object)null && loggedHulls.Add(Utils.GetPrefabName(((Component)this).gameObject)))
			{
				Plugin.Log.LogInfo((object)("[" + Utils.GetPrefabName(((Component)this).gameObject) + "] oar material from '" + ((Object)val).name + "' (shader '" + ((Object)((Renderer)val).sharedMaterial.shader).name + "')"));
			}
			return val;
		}

		private static Transform MakePart(PrimitiveType type, Transform parent, Material mat, int layer)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = GameObject.CreatePrimitive(type);
			Object.DestroyImmediate((Object)(object)val.GetComponent<Collider>());
			val.layer = layer;
			val.transform.SetParent(parent, false);
			if ((Object)(object)mat != (Object)null)
			{
				((Renderer)val.GetComponent<MeshRenderer>()).sharedMaterial = mat;
			}
			return val.transform;
		}

		private void UpdateOars(int rowMask, int hardMask, int backMask, float dt)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: 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_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_06cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0904: Unknown result type (might be due to invalid IL or missing references)
			//IL_092c: Unknown result type (might be due to invalid IL or missing references)
			//IL_094c: Unknown result type (might be due to invalid IL or missing references)
			//IL_096e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0776: Unknown result type (might be due to invalid IL or missing references)
			//IL_072c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0731: Unknown result type (might be due to invalid IL or missing references)
			//IL_0735: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_087d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0515: Unknown result type (might be due to invalid IL or missing references)
			//IL_051a: Unknown result type (might be due to invalid IL or missing references)
			//IL_051e: Unknown result type (might be due to invalid IL or missing references)
			//IL_08b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0892: Unknown result type (might be due to invalid IL or missing references)
			//IL_0542: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_061b: Unknown result type (might be due to invalid IL or missing references)
			//IL_061d: Unknown result type (might be due to invalid IL or missing references)
			if (oars.Length == 0)
			{
				return;
			}
			float num = Plugin.StrokesPerSecond.Value * MathF.PI * 2f;
			gearPhase[0] = (gearPhase[0] + dt * num * 0.75f) % (MathF.PI * 2f);
			gearPhase[1] = (gearPhase[1] + dt * num * 0.75f) % (MathF.PI * 2f);
			gearPhase[2] = (gearPhase[2] + dt * num * 1.2f) % (MathF.PI * 2f);
			bool flag = Plugin.ShowOars.Value && VisualsOn;
			float num2 = 3.2f * ((shipCfg != null) ? shipCfg.Scale.Value : 1f);
			Vector3 val = Vector3.ProjectOnPlane(((Component)this).transform.position - lastPosition, Vector3.up);
			float num3 = ((Vector3)(ref val)).magnitude / dt;
			lastPosition = ((Component)this).transform.position;
			speedSmoothed = Mathf.Lerp(speedSmoothed, num3, 1f - Mathf.Exp(-2f * dt));
			bool flag2 = (Object)(object)shipComponent != (Object)null && shipComponent.IsSailUp();
			sailing = flag2 && speedSmoothed > (sailing ? 1f : 2f);
			bool flag3 = sailing && Plugin.ShipUnderSail.Value;
			for (int i = 0; i < fittings.Length; i++)
			{
				if (((Component)fittings[i]).gameObject.activeSelf != flag)
				{
					((Component)fittings[i]).gameObject.SetActive(flag);
				}
				if (flag)
				{
					fittings[i].localPosition = OarlockLocal(i);
					fittings[i].localRotation = Quaternion.Euler(profile.FittingPitch, profile.FittingYaw * seatSide[i], profile.FittingRoll * seatSide[i]);
				}
			}
			Oar[] array = oars;
			foreach (Oar oar in array)
			{
				if (((Component)oar.Pivot).gameObject.activeSelf != flag)
				{
					((Component)oar.Pivot).gameObject.SetActive(flag);
				}
				if (!flag)
				{
					continue;
				}
				int num4 = 1 << oar.Seat;
				bool flag4 = (rowMask & num4) != 0;
				bool flag5 = (backMask & num4) != 0;
				bool flag6 = (hardMask & num4) != 0;
				float num5 = (flag5 ? gearPhase[0] : (flag6 ? gearPhase[2] : gearPhase[1]));
				float num6 = seatSide[oar.Seat];
				oar.Blend = Mathf.MoveTowards(oar.Blend, flag4 ? 1f : 0f, dt * 1.5f);
				float num7 = Mathf.Sin(num5);
				float num8 = Mathf.Cos(num5);
				float num9 = (flag6 ? 32f : 24f);
				float num10 = num9 * num6 * num7 * (flag5 ? (-1f) : 1f);
				float num11 = -18f - 10f * num8;
				float num12 = 75f * num6;
				float num13 = 12f;
				float num14 = ((num6 > 0f) ? 0f : 180f);
				bool flag7 = profile != null && oar.Seat < portYaw.Length;
				float num15 = 0f;
				float num16 = 0f;
				if (flag4 && oar.Blend > 0.5f && (Object)(object)oar.Tip != (Object)null)
				{
					oar.Water = WaterLevelAt(oar.Tip.position);
					oar.WaterSmooth = (oar.WaterKnown ? Mathf.Lerp(oar.WaterSmooth, oar.Water, 1f - Mathf.Exp(-5f * dt)) : oar.Water);
					oar.WaterKnown = true;
				}
				else
				{
					oar.WaterKnown = false;
				}
				Bounds bounds;
				if (flag7)
				{
					num14 += portYaw[oar.Seat];
					float num17 = (flag6 ? profile.SweepHard : profile.SweepEasy);
					num10 = num17 * num6 * num7 * (flag5 ? (-1f) : 1f);
					num12 = (profile.StowAlongHull ? (profile.StowYaw * num6) : 0f);
					num13 = profile.StowDip;
					num16 = (profile.StowAlongHull ? 0f : 90f);
					num15 = 90f * (1f - Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(-0.25f, 0.15f, num8)));
					Vector3 val2 = OarlockLocal(oar.Seat);
					float num18;
					if (oarModel == null)
					{
						num18 = num2;
					}
					else
					{
						bounds = oarModel.Mesh.bounds;
						num18 = (((Bounds)(ref bounds)).max.x - profile.TipInset) * OarScale;
					}
					float num19 = num18;
					float num20 = (0f - Mathf.Asin(Mathf.Clamp((Mathf.Max(0.1f, val2.y - profile.Waterline) + 0.05f) / num19, 0f, 0.95f))) * 57.29578f;
					float num21 = num20;
					if (oar.WaterKnown)
					{
						float num22 = (num14 - num17 * num6 * (flag5 ? (-1f) : 1f)) * (MathF.PI / 180f);
						float num23 = ((Component)this).transform.right.y * Mathf.Cos(num22) - ((Component)this).transform.forward.y * Mathf.Sin(num22);
						float y = ((Component)this).transform.up.y;
						float num24 = Mathf.Sqrt(num23 * num23 + y * y);
						float num25 = (oar.WaterSmooth - 0.05f - ((Component)this).transform.TransformPoint(val2).y) / num19;
						float num26 = (Mathf.Asin(Mathf.Clamp(num25 / num24, -1f, 1f)) - Mathf.Atan2(num23, y)) * 57.29578f;
						num21 = Mathf.Clamp(num26, Mathf.Max(num20 - 14f, -60f), Mathf.Min(num20 + 8f, -1f));
					}
					num11 = num21 - profile.DipAmplitude * num8;
				}
				float num27 = Mathf.Lerp(num12, num10, oar.Blend);
				float num28 = Mathf.Lerp(num13, num11, oar.Blend);
				oar.Pivot.localPosition = OarlockLocal(oar.Seat);
				oar.Pivot.localRotation = Quaternion.Euler(0f, num14 + num27, num28);
				if ((Object)(object)oar.Tip != (Object)null)
				{
					float num29;
					if (!((Object)(object)oar.Model != (Object)null) || oarModel == null)
					{
						num29 = num2 - 0.35f;
					}
					else
					{
						bounds = oarModel.Mesh.bounds;
						num29 = (((Bounds)(ref bounds)).max.x - ((profile != null) ? profile.TipInset : 0.3f)) * OarScale;
					}
					float num30 = num29;
					oar.Tip.localPosition = new Vector3(num30, 0f, 0f);
				}
				UpdateSplash(oar, flag4, flag6);
				if ((Object)(object)oar.Model != (Object)null)
				{
					bool flag8 = (shipCfg == null || shipCfg.ShowAtRest.Value) && !flag3;
					float num31 = ((flag4 || flag8) ? 1f : 0f);
					oar.Out = (oar.OutKnown ? Mathf.MoveTowards(oar.Out, num31, dt * 1.5f) : num31);
					oar.OutKnown = true;
					float num32 = Mathf.SmoothStep(0f, 1f, oar.Out);
					if (((Component)oar.Model).gameObject.activeSelf != num32 > 0.001f)
					{
						((Component)oar.Model).gameObject.SetActive(num32 > 0.001f);
					}
					oar.Model.localScale = new Vector3(OarScale * Mathf.Max(num32, 0.001f), OarScale, OarScale);
					oar.Model.localRotation = (flag7 ? Quaternion.Euler(Mathf.Lerp(num16, num15, oar.Blend), 0f, 0f) : Quaternion.identity);
				}
				else
				{
					float num33 = num2 + 0.6f;
					oar.Shaft.localRotation = Quaternion.Euler(0f, 0f, 90f);
					oar.Shaft.localScale = new Vector3(0.07f, num33 * 0.5f, 0.07f);
					oar.Shaft.localPosition = new Vector3((num2 - 0.6f) * 0.5f, 0f, 0f);
					oar.Blade.localScale = new Vector3(0.7f, 0.22f, 0.04f);
					oar.Blade.localPosition = new Vector3(num2 - 0.35f, 0f, 0f);
				}
			}
		}
	}
	internal class ShipProfile
	{
		internal struct Port
		{
			public float X;

			public float Z;

			public float Yaw;

			public Port(float x, float z, float yaw)
			{
				X = x;
				Z = z;
				Yaw = yaw;
			}
		}

		internal Port[] Ports;

		internal float PortY;

		internal float Waterline;

		internal float SweepEasy;

		internal float SweepHard;

		internal float DipAmplitude;

		internal float StowDip;

		internal float StowYaw;

		internal float TipInset;

		internal bool StowAlongHull;

		internal bool OarsOutAtRest;

		internal bool Fitting;

		internal float FittingPitch;

		internal float FittingYaw;

		internal float FittingRoll;

		private static readonly ShipProfile Karve = new ShipProfile
		{
			Ports = new Port[1]
			{
				new Port(1f, 2.23f, 0f)
			},
			PortY = 0.98f,
			Waterline = 0.15f,
			SweepEasy = 24f,
			SweepHard = 32f,
			DipAmplitude = 9f,
			StowDip = 2f,
			StowYaw = 80f,
			TipInset = 0.1f,
			StowAlongHull = true,
			OarsOutAtRest = true,
			Fitting = true,
			FittingPitch = -6f,
			FittingYaw = -14f,
			FittingRoll = 18