Decompiled source of AllHandsOnboard RowingRework v1.2.0

BepInEx/plugins/AllHandsOnboardRework/AllHandsOnboard.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AllHandsOnboard")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0")]
[assembly: AssemblyProduct("AllHandsOnboard")]
[assembly: AssemblyTitle("AllHandsOnboard")]
[assembly: AssemblyVersion("1.2.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 AllHandsOnboard
{
	public static class AudioDefaults
	{
		public const float Volume = 0.65f;

		public static float MigrateVolume(float current)
		{
			if (!(Math.Abs(current - 0.25f) < 0.0001f))
			{
				return current;
			}
			return 0.65f;
		}
	}
	public sealed class AudioRecoveryPolicy
	{
		private readonly Queue<double> _attempts = new Queue<double>();

		private double _lastAttempt = -1000.0;

		public bool TryRepair(double now, bool playbackExpected, bool failed, bool globallyMutedOrPaused)
		{
			if (!playbackExpected || !failed || globallyMutedOrPaused)
			{
				return false;
			}
			while (_attempts.Count > 0 && now - _attempts.Peek() >= 60.0)
			{
				_attempts.Dequeue();
			}
			if (now - _lastAttempt < 15.0 || _attempts.Count >= 3)
			{
				return false;
			}
			_attempts.Enqueue(now);
			_lastAttempt = now;
			return true;
		}
	}
	public static class BalrondCompatibility
	{
		public const string PluginId = "balrond.astafaraios.BalrondShipyard";

		private static Type _managerType;

		private static FieldInfo _oarsField;

		public static bool Installed { get; private set; }

		public static bool Enabled
		{
			get
			{
				if (Installed && Plugin.EnableBalrond != null)
				{
					return Plugin.EnableBalrond.Value;
				}
				return false;
			}
		}

		public static void Initialize()
		{
			Installed = Chainloader.PluginInfos.TryGetValue("balrond.astafaraios.BalrondShipyard", out var value);
			if (Installed)
			{
				_managerType = ((object)value.Instance).GetType().Assembly.GetType("BalrondShipyard.ShipExtensionManager", throwOnError: false);
				_oarsField = _managerType?.GetField("oars", BindingFlags.Instance | BindingFlags.Public);
				Plugin.Log.LogInfo((object)("Optional Balrond Shipyard detected: " + value.Metadata.Version?.ToString() + "; rowing support=" + Enabled));
				if (_oarsField == null)
				{
					Plugin.Log.LogWarning((object)"Shipyard oar renderer field not found; native-oar visibility integration unavailable for this version.");
				}
			}
		}

		public static GameObject GetNativeOars(Ship ship)
		{
			if (!Enabled || _managerType == null || _oarsField == null || (Object)(object)ship == (Object)null)
			{
				return null;
			}
			Component component = ((Component)ship).GetComponent(_managerType);
			if (!((Object)(object)component == (Object)null))
			{
				object? value = _oarsField.GetValue(component);
				return (GameObject)((value is GameObject) ? value : null);
			}
			return null;
		}
	}
	internal sealed class NativeOarVisibility
	{
		private readonly Dictionary<Renderer, bool> _saved = new Dictionary<Renderer, bool>();

		private float _nextScan;

		public void Update(Ship ship, bool hide)
		{
			if (!hide || !BalrondCompatibility.Enabled || !Plugin.HideBalrondOars.Value)
			{
				Restore();
				return;
			}
			if (Time.unscaledTime >= _nextScan)
			{
				_nextScan = Time.unscaledTime + 0.5f;
				GameObject nativeOars = BalrondCompatibility.GetNativeOars(ship);
				if ((Object)(object)nativeOars != (Object)null)
				{
					Renderer[] componentsInChildren = nativeOars.GetComponentsInChildren<Renderer>(true);
					foreach (Renderer val in componentsInChildren)
					{
						if (!_saved.ContainsKey(val))
						{
							_saved.Add(val, val.enabled);
						}
					}
				}
			}
			foreach (KeyValuePair<Renderer, bool> item in _saved)
			{
				if ((Object)(object)item.Key != (Object)null)
				{
					item.Key.enabled = false;
				}
			}
		}

		public void Restore()
		{
			foreach (KeyValuePair<Renderer, bool> item in _saved)
			{
				if ((Object)(object)item.Key != (Object)null)
				{
					item.Key.enabled = item.Value;
				}
			}
			_saved.Clear();
			_nextScan = 0f;
		}
	}
	public sealed class BalrondShipProfile
	{
		public string Label;

		public int Index;

		public int DefaultRowers;

		public float OarLength;

		public float Spread;
	}
	public static class BalrondShipCatalog
	{
		public static BalrondShipProfile Find(string objectName)
		{
			string text = (objectName ?? "").Trim().ToLowerInvariant().Replace("(clone)", "")
				.Trim();
			switch (text)
			{
			case "knarr":
			case "knarrbal":
				return new BalrondShipProfile
				{
					Label = "Knarr",
					Index = 3,
					DefaultRowers = 2,
					OarLength = 3f,
					Spread = 0f
				};
			case "snekke":
			case "snekkeship_bal":
				return new BalrondShipProfile
				{
					Label = "Snekke",
					Index = 4,
					DefaultRowers = 4,
					OarLength = 3.5f,
					Spread = 0.45f
				};
			default:
				if (!text.StartsWith("dverger", StringComparison.Ordinal))
				{
					return null;
				}
				goto case "holk";
			case "holk":
				return new BalrondShipProfile
				{
					Label = "Holk",
					Index = 5,
					DefaultRowers = 6,
					OarLength = 4.2f,
					Spread = 0.6f
				};
			}
		}
	}
	public sealed class BeatClock
	{
		private long _lastBeat = -1L;

		public void Reset()
		{
			_lastBeat = -1L;
		}

		public bool Advance(double seconds, double interval, out bool left)
		{
			long num = (long)Math.Floor(seconds / Math.Max(0.05, interval));
			left = (num & 1) == 0;
			if (num == _lastBeat)
			{
				return false;
			}
			_lastBeat = num;
			return true;
		}
	}
	[BepInPlugin("com.allhands.onboard", "All hands onboard", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private const string ModId = "com.allhands.onboard";

		private const string ModName = "All hands onboard";

		private const string ModVersion = "1.2.0";

		public const int MaxRowers = 6;

		public static ManualLogSource Log;

		public static ConfigEntry<float> MaxMultiplierPerRower;

		public static ConfigEntry<float> MinMultiplier;

		public static ConfigEntry<float> CapRatio;

		public static ConfigEntry<float> DecayPerSecond;

		public static ConfigEntry<float> BoostPerStroke;

		public static ConfigEntry<float> SyncIntervalSeconds;

		public static ConfigEntry<float> SlotTimeoutSeconds;

		public static ConfigEntry<float> TargetStrokeInterval;

		public static ConfigEntry<float> SmoothingRate;

		public static ConfigEntry<float> TooFastRatio;

		public static ConfigEntry<float> PerfectMinRatio;

		public static ConfigEntry<float> PerfectMaxRatio;

		public static ConfigEntry<float> SlowRatio;

		public static ConfigEntry<float> PerfectBonusMul;

		public static ConfigEntry<float> GoodBonusMul;

		public static ConfigEntry<float> SlowBonusMul;

		public static ConfigEntry<float> TooFastPenaltyMul;

		public static ConfigEntry<int> StreakMinForBonus;

		public static ConfigEntry<float> StreakBonusMul;

		public static ConfigEntry<int> MasteryStreakMin;

		public static ConfigEntry<int> MasteryStreakMax;

		public static ConfigEntry<float> MasteryFactorAtMin;

		public static ConfigEntry<float> MasteryFactorAtMax;

		public static ConfigEntry<float> CrewSyncWindowRatio;

		public static ConfigEntry<float> CrewSync2;

		public static ConfigEntry<float> CrewSync3;

		public static ConfigEntry<float> CrewSync4Plus;

		public static ConfigEntry<float> WrongSidePenaltyMul;

		public static ConfigEntry<bool> ShowHud;

		public static ConfigEntry<bool> ShowMetronome;

		public static ConfigEntry<bool> MetronomeSound;

		public static ConfigEntry<float> MetronomeVolume;

		public static ConfigEntry<KeyCode> DrumTestKey;

		public static ConfigEntry<KeyCode> DrumResetKey;

		public static ConfigEntry<bool> AudioDiagnostics;

		public static ConfigEntry<bool> AudioAutoRecover;

		public static ConfigEntry<bool> VerboseLogs;

		public static ConfigEntry<RowingInput.InputMode> InputModePref;

		public static ConfigEntry<KeyCode> LeftKey;

		public static ConfigEntry<KeyCode> RightKey;

		public static ConfigEntry<string> LeftPadAxis;

		public static ConfigEntry<string> RightPadAxis;

		public static ConfigEntry<float>[] OarWidth = new ConfigEntry<float>[6];

		public static ConfigEntry<float>[] OarHeight = new ConfigEntry<float>[6];

		public static ConfigEntry<float>[] OarForeAft = new ConfigEntry<float>[6];

		public static ConfigEntry<bool> EnableBalrond;

		public static ConfigEntry<bool> HideBalrondOars;

		public static ConfigEntry<int>[] BalrondRowers = new ConfigEntry<int>[3];

		private readonly Harmony _harmony = new Harmony("com.allhands.onboard");

		private void BindOarSettings()
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected O, but got Unknown
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			EnableBalrond = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility.BalrondShipyard", "Enabled", true, "Enable rowing on Knarr/Snekke/Holk when Balrond Shipyard is installed. Restart after changing.");
			HideBalrondOars = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility.BalrondShipyard", "HideNativeOarsWhileRowing", true, "Hide only Shipyard oar renderers while our rowing oars are visible. Upgrade state and rudder bonus are preserved.");
			string[] array = new string[6] { "Karve", "Longship", "Drakkar", "Knarr", "Snekke", "Holk" };
			for (int i = 0; i < array.Length; i++)
			{
				string text = "Oars." + array[i];
				if (i >= 3)
				{
					BalrondRowers[i - 3] = ((BaseUnityPlugin)this).Config.Bind<int>(text, "RowerCount", i switch
					{
						4 => 4, 
						3 => 2, 
						_ => 6, 
					}, new ConfigDescription("Maximum rowing players/visible oars (0 disables). Match across clients and restart after changing.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 0, 2, 4, 6 }), Array.Empty<object>()));
				}
				OarWidth[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "WidthScale", OarTuning.DefaultWidth(i), new ConfigDescription("Mount spacing multiplier. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>()));
				OarHeight[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "HeightOffset", 0f, new ConfigDescription("Mount height adjustment in metres. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 2f), Array.Empty<object>()));
				OarForeAft[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "ForeAftOffset", 0f, new ConfigDescription("Mount fore/aft adjustment in metres. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-3f, 3f), Array.Empty<object>()));
			}
		}

		private void Update()
		{
			MetronomeAudio.Tick();
		}

		private void Awake()
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			//IL_055f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0569: Expected O, but got Unknown
			//IL_0719: Unknown result type (might be due to invalid IL or missing references)
			//IL_0728: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"=== All hands onboard v1.2.0 starting ===");
			MaxMultiplierPerRower = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "MaxMultiplierPerRower", 1f, "Max speed multiplier each rower can contribute (at tempo=1.0)");
			MinMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "MinMultiplier", 0.3f, "Base ship speed multiplier even with no rowers (vanilla = 1.0)");
			CapRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "CapRatio", 0.85f, "Fraction of theoretical max sum that the speed bonus can reach");
			DecayPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "DecayPerSecond", 0.6f, "How fast tempo bleeds when not rowing (per second, after grace window)");
			BoostPerStroke = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "BoostPerStroke", 0.25f, "Base tempo boost per stroke (multiplied by verdict bonus and streak)");
			SyncIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SyncIntervalSeconds", 0.2f, "How often local tempo is replicated to other clients via ZDO");
			SlotTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SlotTimeoutSeconds", 3f, "After this many seconds with no sync, slot is considered free");
			TargetStrokeInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "TargetStrokeInterval", 0.5f, new ConfigDescription("Seconds between alternating strokes. 0.5 = 120 BPM; 0.6 = 100 BPM.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 1.5f), Array.Empty<object>()));
			ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "TimingMigration110", false, "One-time migration of the original 0.4-second default to 0.5 seconds.");
			if (!val.Value)
			{
				if (Mathf.Abs(TargetStrokeInterval.Value - 0.4f) < 0.0001f)
				{
					TargetStrokeInterval.Value = 0.5f;
				}
				val.Value = true;
			}
			BindOarSettings();
			ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "MountCalibration112", false, "One-time calibration of untouched Longship/Drakkar width settings.");
			if (!val2.Value)
			{
				for (int i = 0; i < 3; i++)
				{
					OarWidth[i].Value = OarTuning.MigrateWidth(i, OarWidth[i].Value);
				}
				val2.Value = true;
			}
			SmoothingRate = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SmoothingRate", 5f, "Speed multiplier smoothing rate (anti-bouncing). Higher = faster response, lower = smoother. ~63%% of target reached in 1/rate seconds.");
			TooFastRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "TooFastRatio", 0.4f, "Strokes faster than this ratio of target = TOO FAST (spam, penalty)");
			PerfectMinRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectMinRatio", 0.84f, "Lower bound of PERFECT window (ratio of target). 0.84 + 1.16 = ~125ms wide PERFECT at 0.4s target.");
			PerfectMaxRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectMaxRatio", 1.16f, "Upper bound of PERFECT window (ratio of target).");
			SlowRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "SlowRatio", 1.6f, "Strokes slower than this ratio of target = SLOW (no bonus, streak reset). Between PerfectMax and this = GOOD.");
			PerfectBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectBonusMul", 1.8f, "Multiplier on BoostPerStroke for PERFECT verdict");
			GoodBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "GoodBonusMul", 1.4f, "Multiplier on BoostPerStroke for GOOD verdict");
			SlowBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "SlowBonusMul", 1f, "Multiplier on BoostPerStroke for SLOW verdict (no penalty, no reward)");
			TooFastPenaltyMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "TooFastPenaltyMul", 0.3f, "Multiplier on BoostPerStroke for TOO FAST verdict (penalty)");
			StreakMinForBonus = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "StreakMinForBonus", 3, "Min streak count to start getting per-stroke streak bonus");
			StreakBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "StreakBonusMul", 1.5f, "Extra multiplier on stroke boost when streak >= MinForBonus");
			MasteryStreakMin = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "MasteryStreakMin", 20, "Streak required to start applying mastery factor to ship speed");
			MasteryStreakMax = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "MasteryStreakMax", 50, "Streak at which mastery factor reaches maximum (plateau above)");
			MasteryFactorAtMin = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "MasteryFactorAtMin", 1.15f, "Ship speed multiplier when highest streak == MasteryStreakMin (1.0 = no bonus)");
			MasteryFactorAtMax = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "MasteryFactorAtMax", 1.3f, "Ship speed multiplier when highest streak >= MasteryStreakMax");
			CrewSyncWindowRatio = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "WindowRatio", 0.4f, "How tight (relative to target interval) other rowers' strokes must cluster to count as 'in sync'");
			CrewSync2 = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor2", 1.15f, "Speed multiplier when 2 rowers are in sync");
			CrewSync3 = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor3", 1.3f, "Speed multiplier when 3 rowers are in sync");
			CrewSync4Plus = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor4Plus", 1.45f, "Speed multiplier when 4+ rowers are in sync");
			WrongSidePenaltyMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "WrongSidePenaltyMul", 0.5f, "Multiplier on BoostPerStroke subtracted from tempo when same key pressed twice in a row (WRONG SIDE). Always applied - rowing requires alternation.");
			ShowHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "ShowHud", true, "Show the rowing HUD widget");
			ShowMetronome = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "ShowMetronome", true, "Show L/R indicator boxes (flash on player strokes)");
			MetronomeSound = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "MetronomeSound", true, "Play alternating timpani cues on each rowing beat");
			MetronomeVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Hud", "MetronomeVolume", 0.65f, new ConfigDescription("Rowing timpani volume (0.0-1.0). Default 0.65.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DrumTestKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Audio", "TestKey", (KeyCode)289, "Plays four timpani cues outside rowing at the default audition level. Respects game master volume. Set None to disable.");
			DrumResetKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Audio", "ResetKey", (KeyCode)290, "Rebuild mod audio and audition it. Ctrl+Shift+this key also resets the game audio engine. None disables the shortcut.");
			AudioDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "Diagnostics", true, "Log state changes, tests/recovery, and one playback status every 30 seconds while audio is active.");
			AudioAutoRecover = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "AutoRecover", true, "Repair this mod's failed audio source, at most three times per minute. Never automatically resets the game audio engine.");
			ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "AudioLevel113", false, "One-time migration of the original 0.25 volume to 0.65. Custom levels and mute are preserved.");
			if (!val3.Value)
			{
				MetronomeVolume.Value = AudioDefaults.MigrateVolume(MetronomeVolume.Value);
				val3.Value = true;
			}
			VerboseLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogs", false, "Log per-frame events (input, ticks). Spammy - turn off when not debugging.");
			InputModePref = ((BaseUnityPlugin)this).Config.Bind<RowingInput.InputMode>("Input", "Mode", RowingInput.InputMode.Auto, "Input mode");
			LeftKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "LeftKey", (KeyCode)276, "Keyboard: left oar");
			RightKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "RightKey", (KeyCode)275, "Keyboard: right oar");
			LeftPadAxis = ((BaseUnityPlugin)this).Config.Bind<string>("Input", "LeftPadAxis", "JoyAxis 9", "Pad: LT axis");
			RightPadAxis = ((BaseUnityPlugin)this).Config.Bind<string>("Input", "RightPadAxis", "JoyAxis 10", "Pad: RT axis");
			Log.LogInfo((object)$"Configs bound. LeftKey={LeftKey.Value}, RightKey={RightKey.Value}, MaxRowers={6}");
			BalrondCompatibility.Initialize();
			_harmony.PatchAll();
			int num = 0;
			foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
			{
				_ = patchedMethod;
				num++;
			}
			Log.LogInfo((object)$"Harmony PatchAll done - {num} method(s) patched");
			MetronomeAudio.Initialize();
			ShipAccess.SelfTest();
			Log.LogInfo((object)"=== All hands onboard ready ===");
		}

		public static void DebugLog(string msg)
		{
			if (VerboseLogs != null && VerboseLogs.Value && Log != null)
			{
				Log.LogDebug((object)msg);
			}
		}

		private void OnDestroy()
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogInfo((object)"Mod cleanup starting");
			}
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("UnpatchSelf failed: " + ex.Message));
				}
			}
			try
			{
				ShipOarsVisual[] array = Object.FindObjectsByType<ShipOarsVisual>((FindObjectsSortMode)0);
				foreach (ShipOarsVisual shipOarsVisual in array)
				{
					if ((Object)(object)shipOarsVisual != (Object)null)
					{
						Object.Destroy((Object)(object)shipOarsVisual);
					}
				}
			}
			catch (Exception ex2)
			{
				ManualLogSource log3 = Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("Cleanup oars failed: " + ex2.Message));
				}
			}
			try
			{
				GameObject val = GameObject.Find("RowingTempoHud");
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			catch (Exception ex3)
			{
				ManualLogSource log4 = Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("Cleanup HUD failed: " + ex3.Message));
				}
			}
			try
			{
				ShipTypeConfig.ClearAll();
			}
			catch
			{
			}
			try
			{
				ShipRowingManager.ClearAllCache();
			}
			catch
			{
			}
			MetronomeAudio.Clear();
			ManualLogSource log5 = Log;
			if (log5 != null)
			{
				log5.LogInfo((object)"Mod cleanup complete");
			}
		}
	}
	public static class DrumSynthesis
	{
		public const int SampleRate = 44100;

		public static float[] Create(bool accented)
		{
			int num = 81585;
			double[] array = new double[num];
			Random random = new Random(accented ? 4109 : 8291);
			double num2 = (accented ? 65.406 : 97.999);
			double num3 = 0.0;
			double num4 = 0.0;
			double num5 = 0.0;
			double[] array2 = new double[6] { 1.0, 1.502, 2.008, 2.497, 3.018, 3.987 };
			double[] array3 = new double[6] { 1.0, 0.56, 0.36, 0.2, 0.13, 0.065 };
			for (int i = 0; i < num; i++)
			{
				double num6 = (double)i / 44100.0;
				num3 += Math.PI * 2.0 * num2 * (1.0 + 0.014 * Math.Exp((0.0 - num6) * 45.0)) / 44100.0;
				double num7 = 0.0;
				for (int j = 0; j < array2.Length; j++)
				{
					num7 += array3[j] * Math.Sin(num3 * array2[j]) * Math.Exp((0.0 - num6) * (4.8 + (double)j * 2.0));
				}
				num4 += 0.4 * (random.NextDouble() * 2.0 - 1.0 - num4);
				num5 += 0.025 * (num4 - num5);
				double num8 = (num4 - num5) * (accented ? 0.85 : 0.95) * Math.Exp((0.0 - num6) * 75.0);
				double num9 = 1.0 - Math.Exp((0.0 - num6) * 650.0);
				array[i] = (num7 + num8) * num9;
			}
			int[] array4 = new int[6] { 1327, 1559, 1789, 1999, 2137, 2381 };
			double[] array5 = new double[num];
			int[] array6 = array4;
			foreach (int num10 in array6)
			{
				double[] array7 = new double[num10];
				double num11 = Math.Pow(10.0, -3.0 * (double)num10 / 59535.00000000001);
				double num12 = 0.0;
				for (int l = 0; l < num; l++)
				{
					int num13 = l % num10;
					double num14 = array7[num13];
					num12 += 0.23 * (num14 - num12);
					array7[num13] = ((l >= 1103) ? array[l - 1103] : 0.0) + num12 * num11;
					array5[l] += num14 / (double)array4.Length;
				}
			}
			array6 = new int[2] { 223, 73 };
			foreach (int num15 in array6)
			{
				double[] array8 = new double[num15];
				for (int m = 0; m < num; m++)
				{
					int num16 = m % num15;
					double num17 = array5[m];
					array5[m] = array8[num16] - 0.55 * num17;
					array8[num16] = num17 + 0.55 * array5[m];
				}
			}
			float[] array9 = new float[num];
			double num18 = 0.0;
			double num19 = 0.0;
			for (int n = 0; n < num; n++)
			{
				double num20 = array[n] * 0.95 + array5[n] * 0.46;
				double num21 = num20 - num18 + 0.998 * num19;
				num18 = num20;
				num19 = num21;
				double num22 = Math.Min(1.0, (double)(num - 1 - n) / 9702.0);
				array9[n] = (float)(num21 * num22);
			}
			float val = 0f;
			float[] array10 = array9;
			foreach (float value in array10)
			{
				val = Math.Max(val, Math.Abs(value));
			}
			float num23 = (accented ? 0.72f : 0.68f) / Math.Max(val, 0.001f);
			for (int num24 = 0; num24 < num; num24++)
			{
				array9[num24] *= num23;
			}
			return array9;
		}
	}
	[HarmonyPatch(typeof(Hud), "Update")]
	public static class HudPatch
	{
		private static GameObject _hudRoot;

		private static Image _metronomeLeft;

		private static Image _metronomeRight;

		private static Text _streakLabel;

		private static Text _multiplierLabel;

		private static Text _verdictLabel;

		private static bool _built;

		private static void Postfix(Hud __instance)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Invalid comparison between Unknown and I4
			RowingInput.PollEdges();
			if (!Plugin.ShowHud.Value)
			{
				if ((Object)(object)_hudRoot != (Object)null)
				{
					_hudRoot.SetActive(false);
				}
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			Ship val = (((Object)(object)localPlayer != (Object)null) ? GetShipPlayerIsOn(localPlayer) : null);
			int num = (((Object)(object)val != (Object)null) ? ShipTypeConfig.GetFor(val) : null)?.RowerCount ?? 0;
			bool flag = (Object)(object)val != (Object)null && (int)val.GetSpeedSetting() == 2 && num > 0;
			if (!_built)
			{
				BuildHud(__instance);
			}
			if (!((Object)(object)_hudRoot == (Object)null))
			{
				_hudRoot.SetActive(flag);
				if (flag)
				{
					UpdateStreakAndMultiplier(val, localPlayer);
					UpdateVerdictLabel();
					UpdateMetronome();
				}
			}
		}

		private static void UpdateStreakAndMultiplier(Ship ship, Player local)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: 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 ((Object)(object)ship == (Object)null || (Object)(object)local == (Object)null)
			{
				return;
			}
			int streak = LocalRowingState.Streak;
			if ((Object)(object)_streakLabel != (Object)null)
			{
				if (streak >= 1)
				{
					_streakLabel.text = $"STREAK x{streak}";
					Color color = ((streak >= 20) ? new Color(1f, 0.85f, 0.4f, 1f) : ((streak >= 5) ? new Color(0.6f, 1f, 0.5f, 1f) : new Color(0.85f, 0.85f, 0.85f, 1f)));
					((Graphic)_streakLabel).color = color;
				}
				else
				{
					_streakLabel.text = "";
				}
			}
			if ((Object)(object)_multiplierLabel != (Object)null)
			{
				float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(ship);
				float crewSyncFactor = ShipRowingManager.GetCrewSyncFactor(ship);
				float masteryFactor = ShipRowingManager.GetMasteryFactor(ship);
				string text = "";
				if (crewSyncFactor > 1.01f)
				{
					text += " SYNC";
				}
				if (masteryFactor > 1.01f)
				{
					text += " MASTER";
				}
				_multiplierLabel.text = $"{smoothedMultiplier:F2}x{text}";
				((Graphic)_multiplierLabel).color = (Color)((crewSyncFactor > 1.01f || masteryFactor > 1.01f) ? new Color(1f, 0.9f, 0.4f, 1f) : Color.white);
			}
		}

		private static void UpdateVerdictLabel()
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_verdictLabel == (Object)null)
			{
				return;
			}
			LocalRowingState.RhythmVerdict lastVerdict = LocalRowingState.LastVerdict;
			float lastVerdictAge = LocalRowingState.LastVerdictAge;
			if (lastVerdict == LocalRowingState.RhythmVerdict.None || lastVerdictAge > 0.6f)
			{
				_verdictLabel.text = "";
				return;
			}
			float a = Mathf.Clamp01(1f - lastVerdictAge / 0.6f);
			string text;
			Color val = default(Color);
			switch (lastVerdict)
			{
			case LocalRowingState.RhythmVerdict.Perfect:
				text = $"PERFECT x{LocalRowingState.Streak}";
				((Color)(ref val))..ctor(0.4f, 1f, 0.4f);
				break;
			case LocalRowingState.RhythmVerdict.Good:
				text = "GOOD";
				((Color)(ref val))..ctor(0.6f, 1f, 0.5f);
				break;
			case LocalRowingState.RhythmVerdict.Slow:
				text = "SLOW";
				((Color)(ref val))..ctor(1f, 0.85f, 0.4f);
				break;
			case LocalRowingState.RhythmVerdict.TooFast:
				text = "TOO FAST";
				((Color)(ref val))..ctor(1f, 0.4f, 0.4f);
				break;
			case LocalRowingState.RhythmVerdict.WrongSide:
				text = "WRONG SIDE";
				((Color)(ref val))..ctor(1f, 0.3f, 0.9f);
				break;
			case LocalRowingState.RhythmVerdict.First:
				text = "START";
				val = Color.white;
				break;
			default:
				text = "";
				val = Color.white;
				break;
			}
			if (lastVerdict == LocalRowingState.RhythmVerdict.Perfect && LocalRowingState.Streak >= 20)
			{
				float num = 0.7f + 0.3f * Mathf.Sin(Time.time * 12f);
				val = Color.Lerp(val, new Color(1f, 0.9f, 0.4f), num * 0.6f);
			}
			val.a = a;
			_verdictLabel.text = text;
			((Graphic)_verdictLabel).color = val;
		}

		private static void UpdateMetronome()
		{
			//IL_0052: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: 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)
			Color inactive = default(Color);
			((Color)(ref inactive))..ctor(0.18f, 0.18f, 0.18f, 0.85f);
			Color flashCol = default(Color);
			((Color)(ref flashCol))..ctor(0.5f, 1f, 0.5f, 1f);
			if ((Object)(object)_metronomeLeft != (Object)null)
			{
				((Graphic)_metronomeLeft).color = FlashColor(LocalRowingState.LastLeftStrokeTime, 0.22f, inactive, flashCol);
			}
			if ((Object)(object)_metronomeRight != (Object)null)
			{
				((Graphic)_metronomeRight).color = FlashColor(LocalRowingState.LastRightStrokeTime, 0.22f, inactive, flashCol);
			}
		}

		private static Color FlashColor(float lastStrokeTime, float duration, Color inactive, Color flashCol)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			float num = Time.time - lastStrokeTime;
			if (num < 0f || num > duration)
			{
				return inactive;
			}
			float num2 = 1f - num / duration;
			return Color.Lerp(inactive, flashCol, num2);
		}

		private static Ship GetShipPlayerIsOn(Player p)
		{
			Ship controlledShip = p.GetControlledShip();
			if ((Object)(object)controlledShip != (Object)null)
			{
				return controlledShip;
			}
			List<Ship> currentShips = ShipAccess.GetCurrentShips();
			if (currentShips != null)
			{
				foreach (Ship item in currentShips)
				{
					if ((Object)(object)item != (Object)null && item.IsPlayerInBoat(p))
					{
						return item;
					}
				}
			}
			else
			{
				Ship[] array = Object.FindObjectsByType<Ship>((FindObjectsSortMode)0);
				foreach (Ship val in array)
				{
					if ((Object)(object)val != (Object)null && val.IsPlayerInBoat(p))
					{
						return val;
					}
				}
			}
			return null;
		}

		private static void BuildHud(Hud hud)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: 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_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			_built = true;
			Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf");
			_hudRoot = new GameObject("RowingTempoHud");
			_hudRoot.transform.SetParent(((Component)hud).transform, false);
			((Graphic)_hudRoot.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.55f);
			RectTransform component = _hudRoot.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 0f);
			component.anchorMax = new Vector2(0f, 0f);
			component.pivot = new Vector2(0f, 0f);
			component.anchoredPosition = new Vector2(16f, 240f);
			component.sizeDelta = new Vector2(190f, 54f);
			_metronomeLeft = CreateBox(_hudRoot.transform, "L_Box", "L", builtinResource, 6f, 26f, 22f);
			_metronomeRight = CreateBox(_hudRoot.transform, "R_Box", "R", builtinResource, 32f, 26f, 22f);
			_streakLabel = CreateText(_hudRoot.transform, "Streak", builtinResource, 13, bold: true, new Vector2(60f, 26f), new Vector2(124f, 22f), new Vector2(0f, 0f), (TextAnchor)3);
			_multiplierLabel = CreateText(_hudRoot.transform, "Multiplier", builtinResource, 18, bold: true, new Vector2(6f, 2f), new Vector2(178f, 22f), new Vector2(0f, 0f), (TextAnchor)3);
			GameObject val = new GameObject("Verdict");
			val.transform.SetParent(_hudRoot.transform, false);
			Text val2 = val.AddComponent<Text>();
			val2.font = builtinResource;
			val2.fontSize = 22;
			val2.fontStyle = (FontStyle)1;
			val2.alignment = (TextAnchor)3;
			val2.text = "";
			Outline obj = val.AddComponent<Outline>();
			((Shadow)obj).effectColor = new Color(0f, 0f, 0f, 0.85f);
			((Shadow)obj).effectDistance = new Vector2(1.5f, -1.5f);
			RectTransform component2 = val.GetComponent<RectTransform>();
			component2.anchorMin = new Vector2(0f, 1f);
			component2.anchorMax = new Vector2(0f, 1f);
			component2.pivot = new Vector2(0f, 0f);
			component2.anchoredPosition = new Vector2(4f, 6f);
			component2.sizeDelta = new Vector2(220f, 28f);
			_verdictLabel = val2;
		}

		private static Image CreateBox(Transform parent, string name, string label, Font font, float x, float y, float size)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: 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_00f1: 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_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			Image obj = val.AddComponent<Image>();
			((Graphic)obj).color = new Color(0.18f, 0.18f, 0.18f, 0.85f);
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 0f);
			component.anchorMax = new Vector2(0f, 0f);
			component.pivot = new Vector2(0f, 0f);
			component.anchoredPosition = new Vector2(x, y);
			component.sizeDelta = new Vector2(size, size);
			GameObject val2 = new GameObject("Label");
			val2.transform.SetParent(val.transform, false);
			Text obj2 = val2.AddComponent<Text>();
			obj2.font = font;
			obj2.fontSize = 14;
			obj2.fontStyle = (FontStyle)1;
			obj2.alignment = (TextAnchor)4;
			((Graphic)obj2).color = Color.white;
			obj2.text = label;
			RectTransform component2 = val2.GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			component2.offsetMin = Vector2.zero;
			component2.offsetMax = Vector2.zero;
			return obj;
		}

		private static Text CreateText(Transform parent, string name, Font font, int fontSize, bool bold, Vector2 anchoredPos, Vector2 size, Vector2 pivot, TextAnchor align)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			Text val2 = val.AddComponent<Text>();
			val2.font = font;
			val2.fontSize = fontSize;
			val2.fontStyle = (FontStyle)(bold ? 1 : 0);
			val2.alignment = align;
			((Graphic)val2).color = Color.white;
			val2.text = "";
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 0f);
			component.anchorMax = new Vector2(0f, 0f);
			component.pivot = pivot;
			component.anchoredPosition = anchoredPos;
			component.sizeDelta = size;
			return val2;
		}
	}
	public static class LocalRowingState
	{
		public enum RhythmVerdict
		{
			None,
			First,
			TooFast,
			Slow,
			Good,
			Perfect,
			WrongSide
		}

		private const int LeftStroke = -1;

		private const int RightStroke = 1;

		private const int NoStroke = 0;

		private static float _tempo;

		private static int _lastStroke = 0;

		private static float _lastStrokeTime;

		private static double _lastStrokeAbsTime;

		private static float _lastSyncTime;

		private static int _currentSlot = -1;

		private static int _streak;

		private static float _lastLeftStrokeTime = -10f;

		private static float _lastRightStrokeTime = -10f;

		private static RhythmVerdict _lastVerdict = RhythmVerdict.None;

		private static float _lastVerdictTime;

		private static int _tickLogCounter;

		public static float Tempo => _tempo;

		public static int CurrentSlot => _currentSlot;

		public static Ship CurrentShip { get; private set; }

		public static int Streak => _streak;

		public static RhythmVerdict LastVerdict => _lastVerdict;

		public static float LastVerdictAge => Time.time - _lastVerdictTime;

		public static double LastStrokeAbsTime => _lastStrokeAbsTime;

		public static float LastLeftStrokeTime => _lastLeftStrokeTime;

		public static float LastRightStrokeTime => _lastRightStrokeTime;

		public static void SetSlot(int slot, Ship ship)
		{
			if (_currentSlot != slot || (Object)(object)CurrentShip != (Object)(object)ship)
			{
				_currentSlot = slot;
				CurrentShip = ship;
				if (slot == -1)
				{
					_tempo = 0f;
					_lastStroke = 0;
					_streak = 0;
					_lastVerdict = RhythmVerdict.None;
					RowingInput.ClearBuffer();
				}
			}
		}

		public static void Tick(float dt, Ship ship)
		{
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			if (_currentSlot == -1 || (Object)(object)ship == (Object)null)
			{
				_tempo = 0f;
				_lastStroke = 0;
				_streak = 0;
				RowingInput.ClearBuffer();
				return;
			}
			float num = Plugin.TargetStrokeInterval.Value * 3f;
			if (Time.time - _lastStrokeTime > num)
			{
				_tempo = Mathf.Max(0f, _tempo - Plugin.DecayPerSecond.Value * dt);
				if (_streak > 0 && Time.time - _lastStrokeTime > num * 1.5f)
				{
					_streak = 0;
				}
			}
			if (_lastStroke != 0 && Time.time - _lastStrokeTime > Plugin.TargetStrokeInterval.Value * 4f)
			{
				_lastStroke = 0;
			}
			if (IsAnyUIBlockingInput())
			{
				if (++_tickLogCounter % 50 == 0)
				{
					Plugin.DebugLog("[Tick] UI blocking input - clearing buffer");
				}
				RowingInput.ClearBuffer();
				return;
			}
			bool flag = RowingInput.ConsumeLeftStroke();
			bool flag2 = RowingInput.ConsumeRightStroke();
			if (++_tickLogCounter % 50 == 0)
			{
				Plugin.DebugLog($"[Tick] slot={_currentSlot}, mode={RowingInput.CurrentMode}, leftDown={flag}, rightDown={flag2}, lastStroke={_lastStroke}, tempo={_tempo:F2}, streak={_streak}");
			}
			if (flag)
			{
				Plugin.DebugLog($"[Tick] LEFT consumed (KeyCode={Plugin.LeftKey.Value})");
			}
			if (flag2)
			{
				Plugin.DebugLog($"[Tick] RIGHT consumed (KeyCode={Plugin.RightKey.Value})");
			}
			if (flag && flag2)
			{
				if (_lastStroke == -1)
				{
					flag = false;
				}
				else
				{
					flag2 = false;
				}
			}
			if (flag && _lastStroke != -1)
			{
				RegisterStroke(-1, "LEFT");
			}
			else if (flag2 && _lastStroke != 1)
			{
				RegisterStroke(1, "RIGHT");
			}
			else if ((flag && _lastStroke == -1) || (flag2 && _lastStroke == 1))
			{
				HandleSameKeyTwice(flag);
			}
			if (Time.time - _lastSyncTime >= Plugin.SyncIntervalSeconds.Value)
			{
				SyncToZdo(ship);
				_lastSyncTime = Time.time;
			}
		}

		private static void HandleSameKeyTwice(bool leftPressed)
		{
			float time = Time.time;
			float num = time - _lastStrokeTime;
			string arg = (leftPressed ? "LEFT" : "RIGHT");
			_tempo = Mathf.Max(0f, _tempo - Plugin.BoostPerStroke.Value * Plugin.WrongSidePenaltyMul.Value);
			_streak = 0;
			_lastVerdict = RhythmVerdict.WrongSide;
			_lastVerdictTime = time;
			Plugin.DebugLog($"[Stroke] {arg} WRONG SIDE (same key 2x, gap={num:F2}s) - streak reset, tempo={_tempo:F2}");
		}

		private static void RegisterStroke(int stroke, string sideLabel)
		{
			float time = Time.time;
			float num = time - _lastStrokeTime;
			float value = Plugin.TargetStrokeInterval.Value;
			float num2 = num / Mathf.Max(0.01f, value);
			float num3;
			RhythmVerdict rhythmVerdict;
			if (_lastStroke == 0)
			{
				num3 = 1f;
				rhythmVerdict = RhythmVerdict.First;
			}
			else if (num2 < Plugin.TooFastRatio.Value)
			{
				num3 = Plugin.TooFastPenaltyMul.Value;
				rhythmVerdict = RhythmVerdict.TooFast;
			}
			else if (num2 >= Plugin.PerfectMinRatio.Value && num2 <= Plugin.PerfectMaxRatio.Value)
			{
				num3 = Plugin.PerfectBonusMul.Value;
				rhythmVerdict = RhythmVerdict.Perfect;
			}
			else if (num2 < Plugin.SlowRatio.Value)
			{
				num3 = Plugin.GoodBonusMul.Value;
				rhythmVerdict = RhythmVerdict.Good;
			}
			else
			{
				num3 = Plugin.SlowBonusMul.Value;
				rhythmVerdict = RhythmVerdict.Slow;
			}
			switch (rhythmVerdict)
			{
			case RhythmVerdict.Perfect:
				_streak++;
				break;
			case RhythmVerdict.TooFast:
			case RhythmVerdict.Slow:
				_streak = 0;
				break;
			}
			float num4 = ((_streak >= Plugin.StreakMinForBonus.Value) ? Plugin.StreakBonusMul.Value : 1f);
			_tempo = Mathf.Min(1f, _tempo + Plugin.BoostPerStroke.Value * num3 * num4);
			_lastStroke = stroke;
			_lastStrokeTime = time;
			_lastStrokeAbsTime = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0);
			switch (stroke)
			{
			case -1:
				_lastLeftStrokeTime = time;
				break;
			case 1:
				_lastRightStrokeTime = time;
				break;
			}
			_lastVerdict = rhythmVerdict;
			_lastVerdictTime = time;
			SendStrokeEvent(CurrentShip);
			string text = ((_streak >= 3) ? $" STREAK x{_streak}" : "");
			Plugin.DebugLog($"[Stroke] {sideLabel} {rhythmVerdict} (gap={num:F2}s/target={value:F2}s, bonus={num3 * num4:F2}x, tempo={_tempo:F2}{text})");
		}

		private static void SyncToZdo(Ship ship)
		{
			ZNetView nView = ShipAccess.GetNView(ship);
			if (!((Object)(object)nView == (Object)null) && nView.IsValid())
			{
				long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0);
				nView.InvokeRPC(ZNetView.Everybody, "Rowing_UpdateSlot", new object[4]
				{
					_currentSlot,
					_tempo,
					num,
					ZNet.instance.GetTimeSeconds()
				});
			}
		}

		private static void SendStrokeEvent(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null))
			{
				ZNetView nView = ShipAccess.GetNView(ship);
				if (!((Object)(object)nView == (Object)null) && nView.IsValid())
				{
					long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0);
					nView.InvokeRPC(ZNetView.Everybody, "Rowing_StrokeAt", new object[4] { _currentSlot, num, _lastStrokeAbsTime, _streak });
				}
			}
		}

		private static bool IsAnyUIBlockingInput()
		{
			try
			{
				if (InventoryGui.IsVisible())
				{
					return true;
				}
				if (Menu.IsVisible())
				{
					return true;
				}
				if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
				{
					return true;
				}
				if (TextInput.IsVisible())
				{
					return true;
				}
				if (StoreGui.IsVisible())
				{
					return true;
				}
				if (Minimap.IsOpen())
				{
					return true;
				}
				if ((Object)(object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible())
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}
	}
	public static class MetronomeAudio
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static AudioConfigurationChangeHandler <0>__OnAudioConfigurationChanged;
		}

		private static AudioClip _left;

		private static AudioClip _right;

		private static AudioSource _source;

		private static readonly BeatClock Clock = new BeatClock();

		private static bool _active;

		private static int _testBeats;

		private static float _testNext;

		private static float _testEnd;

		private static float _diagnosticAt = -1f;

		private static bool _loggedDisabled;

		private static readonly AudioRecoveryPolicy Recovery = new AudioRecoveryPolicy();

		private static bool _subscribed;

		private static volatile bool _deviceChanged;

		private static float _lastCueAt = -100f;

		private static float _nextHealthCheck;

		private static float _nextStatus;

		private static float _lastError = -100f;

		private static double _lastDsp = -1.0;

		private static long _cueCount;

		private static bool _lastLeft;

		private static float _lastVolume;

		private static string _lastHealth = "";

		public static void Initialize()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			if (!_subscribed)
			{
				object obj = <>O.<0>__OnAudioConfigurationChanged;
				if (obj == null)
				{
					AudioConfigurationChangeHandler val = OnAudioConfigurationChanged;
					<>O.<0>__OnAudioConfigurationChanged = val;
					obj = (object)val;
				}
				AudioSettings.OnAudioConfigurationChanged += (AudioConfigurationChangeHandler)obj;
				_subscribed = true;
			}
		}

		private static void OnAudioConfigurationChanged(bool deviceWasChanged)
		{
			_deviceChanged = true;
			Plugin.Log.LogInfo((object)("Timpani audio configuration changed; deviceChanged=" + deviceWasChanged));
		}

		private static void Rebuild(string reason)
		{
			Plugin.Log.LogWarning((object)("Timpani source rebuild: " + reason));
			if ((Object)(object)_source != (Object)null)
			{
				_source.Stop();
				Object.Destroy((Object)(object)((Component)_source).gameObject);
			}
			if ((Object)(object)_left != (Object)null)
			{
				Object.Destroy((Object)(object)_left);
			}
			if ((Object)(object)_right != (Object)null)
			{
				Object.Destroy((Object)(object)_right);
			}
			_source = null;
			_left = (_right = null);
			_lastCueAt = -100f;
			Clock.Reset();
		}

		private static void RecoverManually(bool resetEngine)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			LogAudioState("before manual recovery");
			if (resetEngine)
			{
				bool flag = AudioSettings.Reset(AudioSettings.GetConfiguration());
				Plugin.Log.LogWarning((object)("Manual game audio engine reset returned " + flag));
			}
			Rebuild(resetEngine ? "manual game audio reset" : "manual mod-only reset");
			_deviceChanged = false;
			BeginTest();
		}

		private static void CheckHealth()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _nextHealthCheck)
			{
				return;
			}
			_nextHealthCheck = unscaledTime + 1f;
			bool flag = AudioListener.pause || Time.timeScale == 0f;
			bool flag2 = AudioListener.volume <= 0f;
			int num = Object.FindObjectsByType<AudioListener>((FindObjectsSortMode)0).Count((AudioListener listener) => ((Behaviour)listener).isActiveAndEnabled);
			bool flag3 = (_active || unscaledTime < _testEnd) && unscaledTime - _lastCueAt >= 0.1f && unscaledTime - _lastCueAt < 1.5f;
			bool flag4 = (Object)(object)_source == (Object)null || !((Behaviour)_source).isActiveAndEnabled || _source.mute || _source.volume <= 0f || !_source.isPlaying;
			bool flag5 = flag3 && !flag && !flag2 && num > 0 && _lastDsp >= 0.0 && AudioSettings.dspTime == _lastDsp;
			string text = (flag ? "paused" : (flag2 ? "master muted" : ((num == 0) ? "no active listener" : (flag5 ? "DSP clock stalled" : ((flag3 && flag4) ? "playback expected but source not playing/enabled" : "ready")))));
			if (text != _lastHealth)
			{
				_lastHealth = text;
				if (Plugin.AudioDiagnostics.Value)
				{
					LogAudioState("state=" + text);
				}
				if (flag5)
				{
					Plugin.Log.LogWarning((object)"Audio DSP clock appears stalled. Try Ctrl+Shift+the audio reset key (default F9).");
				}
			}
			_lastDsp = AudioSettings.dspTime;
			if (Plugin.AudioAutoRecover.Value && Recovery.TryRepair(unscaledTime, flag3, flag4, flag || flag2 || num == 0))
			{
				bool lastLeft = _lastLeft;
				float lastVolume = _lastVolume;
				Rebuild("watchdog detected failed playback");
				PlayCue(lastLeft, lastVolume);
				_diagnosticAt = unscaledTime + 0.12f;
			}
			if (unscaledTime >= _nextStatus && Plugin.AudioDiagnostics.Value)
			{
				_nextStatus = unscaledTime + 30f;
				LogAudioState("periodic status");
			}
		}

		private static void BeginTest()
		{
			if ((Object)(object)_source != (Object)null)
			{
				_source.Stop();
			}
			_active = false;
			_lastCueAt = -100f;
			_testBeats = 4;
			_testNext = Time.unscaledTime;
			_testEnd = Time.unscaledTime + 3.5f;
			Clock.Reset();
			Plugin.Log.LogInfo((object)"Timpani sound test requested: four alternating hits at volume 0.65, independent of rowing and cue mute.");
			LogAudioState("test start");
		}

		private static void LogAudioState(string context)
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			int num = Object.FindObjectsByType<AudioListener>((FindObjectsSortMode)0).Count((AudioListener listener) => ((Behaviour)listener).isActiveAndEnabled);
			Plugin.Log.LogInfo((object)("Timpani " + context + ": listeners=" + num + ", master=" + AudioListener.volume + ", paused=" + AudioListener.pause + ", sourceEnabled=" + ((Object)(object)_source != (Object)null && ((Behaviour)_source).isActiveAndEnabled) + ", playing=" + ((Object)(object)_source != (Object)null && _source.isPlaying) + ", sourceMute=" + ((Object)(object)_source != (Object)null && _source.mute) + ", clipState=" + (((Object)(object)_left != (Object)null) ? ((object)_left.loadState/*cast due to .constrained prefix*/).ToString() : "not created") + ", sampleRate=" + AudioSettings.outputSampleRate + ", dspTime=" + AudioSettings.dspTime + ", submittedCues=" + _cueCount + ", cueVolume=" + _lastVolume));
		}

		public static AudioClip GetClip(bool left)
		{
			AudioClip val = (left ? _left : _right);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			float[] array = DrumSynthesis.Create(left);
			AudioClip val2 = AudioClip.Create(left ? "TimpaniLowC" : "TimpaniHighG", array.Length, 1, 44100, false);
			if (!val2.SetData(array, 0))
			{
				Object.Destroy((Object)(object)val2);
				throw new InvalidOperationException("Could not populate timpani audio clip.");
			}
			if (left)
			{
				_left = val2;
			}
			else
			{
				_right = val2;
			}
			return val2;
		}

		public static void Tick()
		{
			try
			{
				TickCore();
			}
			catch (Exception ex)
			{
				if (Time.unscaledTime - _lastError > 10f)
				{
					_lastError = Time.unscaledTime;
					Plugin.Log.LogError((object)("Timpani update failed; use the reset key (default F9). " + ex));
				}
			}
		}

		private static void TickCore()
		{
			//IL_0027: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Invalid comparison between Unknown and I4
			if (_deviceChanged)
			{
				_deviceChanged = false;
				Rebuild("audio device/configuration changed");
			}
			if (Plugin.DrumResetKey != null && (int)Plugin.DrumResetKey.Value != 0 && Input.GetKeyDown(Plugin.DrumResetKey.Value))
			{
				bool num = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305);
				bool flag = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303);
				RecoverManually(num && flag);
			}
			if (Plugin.DrumTestKey != null && (int)Plugin.DrumTestKey.Value != 0 && Input.GetKeyDown(Plugin.DrumTestKey.Value))
			{
				BeginTest();
			}
			if (_active || Time.unscaledTime < _testEnd)
			{
				CheckHealth();
			}
			if (_diagnosticAt >= 0f && Time.unscaledTime >= _diagnosticAt)
			{
				_diagnosticAt = -1f;
				LogAudioState("after PlayOneShot");
			}
			if (Time.unscaledTime < _testEnd)
			{
				if (_testBeats > 0 && Time.unscaledTime >= _testNext)
				{
					PlayCue(_testBeats % 2 == 0, 0.65f);
					if (_testBeats == 4)
					{
						_diagnosticAt = Time.unscaledTime + 0.12f;
					}
					_testBeats--;
					_testNext = Time.unscaledTime + 0.5f;
				}
				return;
			}
			if (Plugin.MetronomeSound == null || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				Stop();
				return;
			}
			Ship val = Player.m_localPlayer.GetControlledShip();
			if ((Object)(object)val == (Object)null)
			{
				val = Ship.GetLocalShip();
			}
			if (!((Object)(object)val != (Object)null) || (int)val.GetSpeedSetting() != 2 || ShipTypeConfig.GetFor(val).RowerCount <= 0)
			{
				_loggedDisabled = false;
				Stop();
			}
			else if (!Plugin.MetronomeSound.Value || Plugin.MetronomeVolume.Value <= 0f)
			{
				if (!_loggedDisabled)
				{
					Plugin.Log.LogInfo((object)("Timpani cues disabled by config: MetronomeSound=" + Plugin.MetronomeSound.Value + ", volume=" + Plugin.MetronomeVolume.Value + ". F8 auditions the sound independently."));
					_loggedDisabled = true;
				}
				Stop();
			}
			else if (Time.timeScale == 0f || AudioListener.pause)
			{
				Clock.Reset();
			}
			else
			{
				if (!_active)
				{
					_active = true;
					Plugin.Log.LogInfo((object)("Rowing timpani active: 2D playback; volume=" + Plugin.MetronomeVolume.Value));
					_diagnosticAt = Time.unscaledTime + 0.12f;
				}
				double seconds = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : ((double)Time.time));
				if (Clock.Advance(seconds, Plugin.TargetStrokeInterval.Value, out var left))
				{
					PlayTick(left);
				}
			}
		}

		public static void PlayTick(bool left)
		{
			if (Plugin.MetronomeSound.Value)
			{
				PlayCue(left, Mathf.Clamp01(Plugin.MetronomeVolume.Value));
			}
		}

		private static void PlayCue(bool left, float volume)
		{
			//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_0033: Expected O, but got Unknown
			_lastCueAt = Time.unscaledTime;
			_lastLeft = left;
			_lastVolume = volume;
			if ((Object)(object)_source == (Object)null)
			{
				GameObject val = new GameObject("AllHandsOnboard_Drums");
				Object.DontDestroyOnLoad((Object)val);
				_source = val.AddComponent<AudioSource>();
				_source.playOnAwake = false;
				_source.loop = false;
				_source.spatialBlend = 0f;
				_source.dopplerLevel = 0f;
				_source.volume = 1f;
				_source.priority = 32;
				_source.bypassReverbZones = true;
			}
			_source.PlayOneShot(GetClip(left), volume);
			_cueCount++;
		}

		private static void Stop()
		{
			if (_active && (Object)(object)_source != (Object)null)
			{
				_source.Stop();
			}
			_active = false;
			Clock.Reset();
		}

		public static void Clear()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			if (_subscribed)
			{
				object obj = <>O.<0>__OnAudioConfigurationChanged;
				if (obj == null)
				{
					AudioConfigurationChangeHandler val = OnAudioConfigurationChanged;
					<>O.<0>__OnAudioConfigurationChanged = val;
					obj = (object)val;
				}
				AudioSettings.OnAudioConfigurationChanged -= (AudioConfigurationChangeHandler)obj;
			}
			_subscribed = false;
			_deviceChanged = false;
			Stop();
			_testBeats = 0;
			_testEnd = 0f;
			_diagnosticAt = -1f;
			if ((Object)(object)_source != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_source).gameObject);
			}
			if ((Object)(object)_left != (Object)null)
			{
				Object.Destroy((Object)(object)_left);
			}
			if ((Object)(object)_right != (Object)null)
			{
				Object.Destroy((Object)(object)_right);
			}
			_source = null;
			_left = (_right = null);
		}
	}
	public sealed class OarMeshData
	{
		public float[] Positions;

		public float[] UV;

		public int[] Triangles;
	}
	public static class OarMeshGeometry
	{
		public static OarMeshData Create(float outboardLength)
		{
			float[] obj = new float[11]
			{
				0.85f, 0.78f, 0.3f, 0.08f, -0.08f, -0.18f, 0f, 0f, 0f, 0f,
				0f
			};
			obj[6] = (0f - outboardLength) * 0.64f;
			obj[7] = (0f - outboardLength) * 0.74f;
			obj[8] = (0f - outboardLength) * 0.81f;
			obj[9] = (0f - outboardLength) * 0.94f;
			obj[10] = 0f - outboardLength;
			float[] array = obj;
			float[] array2 = new float[11]
			{
				0.027f, 0.038f, 0.051f, 0.061f, 0.061f, 0.05f, 0.043f, 0.065f, 0.155f, 0.145f,
				0.09f
			};
			float[] array3 = new float[11]
			{
				0.027f, 0.038f, 0.051f, 0.061f, 0.061f, 0.05f, 0.039f, 0.023f, 0.018f, 0.017f,
				0.012f
			};
			List<float> list = new List<float>();
			List<float> list2 = new List<float>();
			List<int> list3 = new List<int>();
			for (int i = 0; i < array.Length; i++)
			{
				for (int j = 0; j < 8; j++)
				{
					double num = Math.PI * 2.0 * (double)j / 8.0;
					list.Add((float)Math.Cos(num) * array2[i]);
					list.Add(array[i]);
					list.Add((float)Math.Sin(num) * array3[i]);
					list2.Add((float)j / 8f);
					list2.Add((array[i] + outboardLength) / (outboardLength + 0.85f));
				}
			}
			for (int k = 0; k < array.Length - 1; k++)
			{
				for (int l = 0; l < 8; l++)
				{
					int num2 = k * 8 + l;
					int num3 = k * 8 + (l + 1) % 8;
					int num4 = num2 + 8;
					int num5 = num3 + 8;
					list3.AddRange(new int[6] { num2, num3, num4, num3, num5, num4 });
				}
			}
			for (int m = 0; m < 2; m++)
			{
				int num6 = ((m != 0) ? (array.Length - 1) : 0);
				int num7 = list.Count / 3;
				list.Add(0f);
				list.Add(array[num6]);
				list.Add(0f);
				list2.Add(0.5f);
				list2.Add((m == 0) ? 1 : 0);
				for (int n = 0; n < 8; n++)
				{
					int num8 = num6 * 8 + n;
					int num9 = num6 * 8 + (n + 1) % 8;
					list3.AddRange((m == 0) ? new int[3] { num7, num9, num8 } : new int[3] { num7, num8, num9 });
				}
			}
			return new OarMeshData
			{
				Positions = list.ToArray(),
				UV = list2.ToArray(),
				Triangles = list3.ToArray()
			};
		}
	}
	public static class OarPose
	{
		public static void Direction(bool left, double cycle, bool rowing, out float x, out float y, out float z)
		{
			double num = (rowing ? (Math.Sin(cycle * 2.0 * Math.PI) * 28.0 * Math.PI / 180.0) : 0.0);
			double num2 = (rowing ? (8.0 + 18.0 * Math.Cos(cycle * 2.0 * Math.PI)) : 6.0) * Math.PI / 180.0;
			x = (float)((double)((!left) ? 1 : (-1)) * Math.Cos(num2) * Math.Cos(num));
			y = (float)(0.0 - Math.Sin(num2));
			z = (float)(Math.Cos(num2) * Math.Sin(num));
		}
	}
	public static class OarTuning
	{
		public static float DefaultWidth(int type)
		{
			return type switch
			{
				2 => 1.3f, 
				1 => 0.75f, 
				_ => 1f, 
			};
		}

		public static float MigrateWidth(int type, float current)
		{
			if (!(Math.Abs(current - 1f) < 0.0001f))
			{
				return current;
			}
			return DefaultWidth(type);
		}
	}
	public static class RowingInput
	{
		public enum InputMode
		{
			Auto,
			Keyboard,
			Gamepad
		}

		private const float TriggerDownThreshold = 0.5f;

		private const float TriggerUpThreshold = 0.3f;

		private static bool _leftTriggerPrev;

		private static bool _rightTriggerPrev;

		private static InputMode _detectedMode = InputMode.Keyboard;

		private static float _lastKeyboardActivity = -10f;

		private static bool _bufferedLeft;

		private static bool _bufferedRight;

		public static InputMode CurrentMode
		{
			get
			{
				InputMode value = Plugin.InputModePref.Value;
				if (value != InputMode.Auto)
				{
					return value;
				}
				return _detectedMode;
			}
		}

		public static void UpdateDetection()
		{
			if (Input.anyKeyDown && !IsAnyMouseButtonDown())
			{
				_detectedMode = InputMode.Keyboard;
				_lastKeyboardActivity = Time.time;
			}
			else if (Time.time - _lastKeyboardActivity > 5f && IsAnyGamepadInput())
			{
				_detectedMode = InputMode.Gamepad;
			}
		}

		public static void PollEdges()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			UpdateDetection();
			if (CurrentMode == InputMode.Keyboard)
			{
				if (Input.GetKeyDown(Plugin.LeftKey.Value))
				{
					_bufferedLeft = true;
				}
				if (Input.GetKeyDown(Plugin.RightKey.Value))
				{
					_bufferedRight = true;
				}
			}
			else
			{
				UpdateGamepadEdges();
			}
		}

		private static void UpdateGamepadEdges()
		{
			float num = SafeGetAxis(Plugin.LeftPadAxis.Value);
			bool num2 = (_leftTriggerPrev ? (num > 0.3f) : (num > 0.5f));
			if (num2 && !_leftTriggerPrev)
			{
				_bufferedLeft = true;
			}
			_leftTriggerPrev = num2;
			float num3 = SafeGetAxis(Plugin.RightPadAxis.Value);
			bool num4 = (_rightTriggerPrev ? (num3 > 0.3f) : (num3 > 0.5f));
			if (num4 && !_rightTriggerPrev)
			{
				_bufferedRight = true;
			}
			_rightTriggerPrev = num4;
		}

		public static bool ConsumeLeftStroke()
		{
			bool bufferedLeft = _bufferedLeft;
			_bufferedLeft = false;
			return bufferedLeft;
		}

		public static bool ConsumeRightStroke()
		{
			bool bufferedRight = _bufferedRight;
			_bufferedRight = false;
			return bufferedRight;
		}

		public static void ClearBuffer()
		{
			_bufferedLeft = false;
			_bufferedRight = false;
		}

		private static float SafeGetAxis(string axisName)
		{
			try
			{
				return Input.GetAxisRaw(axisName);
			}
			catch
			{
				return 0f;
			}
		}

		private static bool IsAnyMouseButtonDown()
		{
			if (!Input.GetMouseButtonDown(0) && !Input.GetMouseButtonDown(1))
			{
				return Input.GetMouseButtonDown(2);
			}
			return true;
		}

		private static bool IsAnyGamepadInput()
		{
			if (SafeGetAxis(Plugin.LeftPadAxis.Value) > 0.1f)
			{
				return true;
			}
			if (SafeGetAxis(Plugin.RightPadAxis.Value) > 0.1f)
			{
				return true;
			}
			for (int i = 0; i < 20; i++)
			{
				if (Input.GetKey((KeyCode)(330 + i)))
				{
					return true;
				}
			}
			return false;
		}

		public static string GetCurrentBindingDescription(bool isLeft)
		{
			//IL_001c: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			if (CurrentMode == InputMode.Keyboard)
			{
				return ((object)(isLeft ? Plugin.LeftKey.Value : Plugin.RightKey.Value)/*cast due to .constrained prefix*/).ToString();
			}
			if (!isLeft)
			{
				return "RT";
			}
			return "LT";
		}
	}
	public static class RowingZdoKeys
	{
		public static readonly int[] SlotTempo;

		public static readonly int[] SlotPlayer;

		public static readonly int[] SlotLastUpdate;

		public static readonly int[] SlotLastStroke;

		public static readonly int[] SlotStreak;

		static RowingZdoKeys()
		{
			SlotTempo = new int[6];
			SlotPlayer = new int[6];
			SlotLastUpdate = new int[6];
			SlotLastStroke = new int[6];
			SlotStreak = new int[6];
			for (int i = 0; i < 6; i++)
			{
				SlotTempo[i] = StableHash($"rowing_tempo_{i}");
				SlotPlayer[i] = StableHash($"rowing_player_{i}");
				SlotLastUpdate[i] = StableHash($"rowing_last_{i}");
				SlotLastStroke[i] = StableHash($"rowing_strokeat_{i}");
				SlotStreak[i] = StableHash($"rowing_streak_{i}");
			}
		}

		private static int StableHash(string s)
		{
			int num = -2128831035;
			for (int i = 0; i < s.Length; i++)
			{
				num = (num ^ s[i]) * 16777619;
			}
			return num;
		}
	}
	internal static class ShipAccess
	{
		private static readonly FieldInfo NviewField = AccessTools.Field(typeof(Ship), "m_nview");

		private static readonly FieldInfo FloatColliderField = AccessTools.Field(typeof(Ship), "m_floatCollider");

		private static readonly FieldInfo CurrentShipsField = AccessTools.Field(typeof(Ship), "s_currentShips");

		private static readonly FieldInfo BackwardForceField = AccessTools.Field(typeof(Ship), "m_backwardForce");

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

		public static float GetBackwardForce(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null) && !(BackwardForceField == null))
			{
				return (float)BackwardForceField.GetValue(ship);
			}
			return 0f;
		}

		public static ZNetView GetNView(Ship ship)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ship == (Object)null)
			{
				return null;
			}
			ZNetView val = ((!(NviewField != null)) ? ((ZNetView)null) : ((ZNetView)NviewField.GetValue(ship)));
			if (!((Object)(object)val != (Object)null))
			{
				return ((Component)ship).GetComponent<ZNetView>();
			}
			return val;
		}

		public static BoxCollider GetFloatCollider(Ship ship)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			if ((Object)(object)ship == (Object)null || FloatColliderField == null)
			{
				return null;
			}
			return (BoxCollider)FloatColliderField.GetValue(ship);
		}

		public static List<Ship> GetCurrentShips()
		{
			if (CurrentShipsField == null)
			{
				return null;
			}
			return (List<Ship>)CurrentShipsField.GetValue(null);
		}

		public static bool IsPlayerSeatedOnShip(Player p, Ship ship)
		{
			if ((Object)(object)p == (Object)null || (Object)(object)ship == (Object)null)
			{
				return false;
			}
			if (!((Character)p).IsAttached())
			{
				return false;
			}
			object? obj = PlayerAttachPointField?.GetValue(p);
			Transform val = (Transform)((obj is Transform) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Transform transform = ((Component)ship).transform;
			Transform val2 = val;
			for (int i = 0; i < 16; i++)
			{
				if (!((Object)(object)val2 != (Object)null))
				{
					break;
				}
				if ((Object)(object)val2 == (Object)(object)transform)
				{
					return true;
				}
				val2 = val2.parent;
			}
			return false;
		}

		public static void SelfTest()
		{
			Plugin.Log.LogDebug((object)"[ShipAccess] SelfTest:");
			Plugin.Log.LogDebug((object)("  Ship.m_nview field        : " + ((NviewField != null) ? "FOUND" : "NULL - reflection failed!")));
			Plugin.Log.LogDebug((object)("  Ship.m_floatCollider field: " + ((FloatColliderField != null) ? "FOUND" : "NULL")));
			Plugin.Log.LogDebug((object)("  Ship.s_currentShips field : " + ((CurrentShipsField != null) ? "FOUND" : "NULL - HUD will fall back to FindObjectsOfType")));
			Plugin.Log.LogDebug((object)("  Player.m_attachPoint field: " + ((PlayerAttachPointField != null) ? "FOUND" : "NULL - seated check will return false!")));
			List<FieldInfo> declaredFields = AccessTools.GetDeclaredFields(typeof(Ship));
			Plugin.Log.LogDebug((object)$"  Ship has {declaredFields.Count} declared fields:");
			foreach (FieldInfo item in declaredFields)
			{
				Plugin.Log.LogDebug((object)("    " + item.FieldType.Name + " " + item.Name));
			}
			List<MethodInfo> declaredMethods = AccessTools.GetDeclaredMethods(typeof(Ship));
			Plugin.Log.LogDebug((object)$"  Ship has {declaredMethods.Count} declared methods (looking for Update/FixedUpdate variants):");
			foreach (MethodInfo item2 in declaredMethods)
			{
				string text = item2.Name.ToLowerInvariant();
				if (text.Contains("update") || text.Contains("fixed") || text.Contains("tick"))
				{
					string text2 = string.Join(",", from p in item2.GetParameters()
						select p.ParameterType.Name);
					Plugin.Log.LogDebug((object)("    " + item2.ReturnType.Name + " " + item2.Name + "(" + text2 + ")"));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "Awake")]
	[HarmonyPriority(200)]
	public static class ShipAwakeOarsPatch
	{
		private static void Postfix(Ship __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponent<ShipOarsVisual>() != (Object)null))
			{
				((Component)__instance).gameObject.AddComponent<ShipOarsVisual>().Init(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "Awake")]
	public static class ShipAwakeRpcPatch
	{
		private static void Postfix(Ship __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			ZNetView nView = ShipAccess.GetNView(__instance);
			if ((Object)(object)nView == (Object)null)
			{
				Plugin.Log.LogWarning((object)("[ShipAwakeRpc] Ship '" + ((Object)((Component)__instance).gameObject).name + "' has NO ZNetView - RPC not registered!"));
				return;
			}
			Plugin.Log.LogDebug((object)$"[ShipAwakeRpc] Registering RPCs on '{((Object)((Component)__instance).gameObject).name}' (instance {((Object)__instance).GetInstanceID()})");
			nView.Register<int, float, long, double>("Rowing_UpdateSlot", (Method<int, float, long, double>)delegate(long sender, int slot, float tempo, long playerId, double time)
			{
				OnUpdateSlot(__instance, slot, tempo, playerId, time);
			});
			nView.Register<int, long, double, int>("Rowing_StrokeAt", (Method<int, long, double, int>)delegate(long sender, int slot, long playerId, double atTime, int streak)
			{
				OnStrokeAt(__instance, slot, playerId, atTime, streak);
			});
			nView.Register<int, long, double>("Rowing_ClaimSlot", (Action<long, int, long, double>)delegate(long sender, int slot, long playerId, double time)
			{
				OnClaimSlot(__instance, slot, playerId, time);
			});
			nView.Register<int, long>("Rowing_ReleaseSlot", (Action<long, int, long>)delegate(long sender, int slot, long playerId)
			{
				OnReleaseSlot(__instance, slot, playerId);
			});
		}

		private static void OnUpdateSlot(Ship ship, int slot, float tempo, long playerId, double time)
		{
			ZNetView nView = ShipAccess.GetNView(ship);
			if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6)
			{
				ZDO zDO = nView.GetZDO();
				if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == playerId)
				{
					zDO.Set(RowingZdoKeys.SlotTempo[slot], Mathf.Clamp01(tempo));
					zDO.Set(RowingZdoKeys.SlotLastUpdate[slot], (float)time);
				}
			}
		}

		private static void OnStrokeAt(Ship ship, int slot, long playerId, double atTime, int streak)
		{
			ZNetView nView = ShipAccess.GetNView(ship);
			if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6)
			{
				ZDO zDO = nView.GetZDO();
				if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == playerId)
				{
					zDO.Set(RowingZdoKeys.SlotLastStroke[slot], (float)atTime);
					zDO.Set(RowingZdoKeys.SlotStreak[slot], streak, false);
				}
			}
		}

		private static void OnClaimSlot(Ship ship, int slot, long playerId, double time)
		{
			ZNetView nView = ShipAccess.GetNView(ship);
			if ((Object)(object)nView == (Object)null || !nView.IsOwner())
			{
				Plugin.DebugLog("[RPC ClaimSlot] Ignored - not owner of '" + ((Object)((Component)ship).gameObject).name + "'");
			}
			else if (slot >= 0 && slot < 6)
			{
				ZDO zDO = nView.GetZDO();
				double timeSeconds = ZNet.instance.GetTimeSeconds();
				float value = Plugin.SlotTimeoutSeconds.Value;
				long num = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L);
				float num2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[slot], 0f);
				if (num == 0L || num == playerId || timeSeconds - (double)num2 > (double)value)
				{
					Plugin.Log.LogDebug((object)$"[RPC ClaimSlot] Owner accepting: slot {slot} -> player {playerId} on '{((Object)((Component)ship).gameObject).name}'");
					zDO.Set(RowingZdoKeys.SlotPlayer[slot], playerId);
					zDO.Set(RowingZdoKeys.SlotTempo[slot], 0f);
					zDO.Set(RowingZdoKeys.SlotLastUpdate[slot], (float)timeSeconds);
				}
				else
				{
					Plugin.Log.LogDebug((object)$"[RPC ClaimSlot] REJECTED: slot {slot} held by {num}, age={timeSeconds - (double)num2:F1}s");
				}
			}
		}

		private static void OnReleaseSlot(Ship ship, int slot, long playerId)
		{
			ZNetView nView = ShipAccess.GetNView(ship);
			if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6)
			{
				ZDO zDO = nView.GetZDO();
				if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == playerId)
				{
					zDO.Set(RowingZdoKeys.SlotPlayer[slot], 0L);
					zDO.Set(RowingZdoKeys.SlotTempo[slot], 0f);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "OnDestroyed")]
	public static class ShipDestroyedPatch
	{
		private static void Prefix(Ship __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				ShipTypeConfig.Invalidate(__instance);
				ShipRowingManager.InvalidateCache(__instance);
				ShipOarsVisual component = ((Component)__instance).GetComponent<ShipOarsVisual>();
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "CustomFixedUpdate", new Type[] { typeof(float) })]
	public static class ShipFixedUpdatePatch
	{
		private static void Prefix(Ship __instance, float fixedDeltaTime)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			bool flag = (int)__instance.GetSpeedSetting() == 2;
			if (flag && ShipTypeConfig.GetFor(__instance).RowerCount > 0)
			{
				ShipRowingManager.TickSmoothing(__instance, fixedDeltaTime);
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			bool flag2 = __instance.IsPlayerInBoat(localPlayer) && ShipAccess.IsPlayerSeatedOnShip(localPlayer, __instance);
			if (!flag || !flag2)
			{
				if (LocalRowingState.CurrentSlot != -1 && (Object)(object)LocalRowingState.CurrentShip == (Object)(object)__instance)
				{
					ShipRowingManager.ReleaseSlot(__instance, LocalRowingState.CurrentSlot, localPlayer.GetPlayerID());
					LocalRowingState.SetSlot(-1, null);
				}
			}
			else
			{
				if (LocalRowingState.CurrentSlot == -1)
				{
					LocalRowingState.SetSlot(ShipRowingManager.ClaimSlot(__instance, localPlayer.GetPlayerID()), __instance);
				}
				LocalRowingState.Tick(fixedDeltaTime, __instance);
			}
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			FieldInfo drive = typeof(Ship).GetField("m_backwardForce", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo replacement = typeof(ShipFixedUpdatePatch).GetMethod("GetDriveForce", BindingFlags.Static | BindingFlags.NonPublic);
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, drive))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = replacement;
					replaced++;
				}
				yield return instruction;
			}
			if (replaced == 0)
			{
				Plugin.Log.LogWarning((object)"No paddle-force reads found: rowing boost disabled; vanilla flotation is unchanged.");
			}
		}

		internal static float GetDriveForce(Ship ship)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			float backwardForce = ShipAccess.GetBackwardForce(ship);
			if ((int)ship.GetSpeedSetting() != 2 || ShipTypeConfig.GetFor(ship).RowerCount == 0)
			{
				return backwardForce;
			}
			return backwardForce * ShipRowingManager.GetSmoothedMultiplier(ship);
		}
	}
	public class ShipOarsVisual : MonoBehaviour
	{
		private Ship _ship;

		private readonly NativeOarVisibility _nativeOars = new NativeOarVisibility();

		private ShipTypeConfig.TypeData _typeData;

		private GameObject[] _roots;

		private double[] _lastStroke;

		private bool _built;

		private Mesh _mesh;

		private Material _material;

		private Texture2D _grain;

		public void Init(Ship ship)
		{
			_ship = ship;
		}

		private void TryBuild()
		{
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ee: Expected O, but got Unknown
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Expected O, but got Unknown
			//IL_0374: Unknown result type (might be due to invalid IL or missing references)
			if (_built || (Object)(object)_ship == (Object)null)
			{
				return;
			}
			_typeData = ShipTypeConfig.GetFor(_ship);
			_built = true;
			if (_typeData.RowerCount == 0)
			{
				return;
			}
			Material val = null;
			MeshRenderer[] componentsInChildren = ((Component)_ship).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)
				{
					continue;
				}
				string name = ((Object)sharedMaterial.shader).name;
				if (!name.Contains("Hidden") && !name.Contains("Particle") && !(name == "Standard") && sharedMaterial.HasProperty("_MainTex"))
				{
					val = sharedMaterial;
					if (((Object)val2).name.ToLowerInvariant().Contains("hull"))
					{
						break;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"No compatible ship material found; oar visuals disabled for this ship.");
				return;
			}
			_material = new Material(val);
			((Object)_material).name = "RowingOar_OiledWood";
			_grain = MakeWoodGrain();
			_material.SetTexture("_MainTex", (Texture)(object)_grain);
			_material.SetTextureScale("_MainTex", Vector2.one);
			_material.SetTextureOffset("_MainTex", Vector2.zero);
			if (_material.HasProperty("_Color"))
			{
				_material.SetColor("_Color", Color.white);
			}
			if (_material.HasProperty("_BumpMap"))
			{
				_material.SetTexture("_BumpMap", (Texture)null);
			}
			if (_material.HasProperty("_MetallicGlossMap"))
			{
				_material.SetTexture("_MetallicGlossMap", (Texture)null);
			}
			if (_material.HasProperty("_Metallic"))
			{
				_material.SetFloat("_Metallic", 0f);
			}
			if (_material.HasProperty("_Glossiness"))
			{
				_material.SetFloat("_Glossiness", 0.15f);
			}
			OarMeshData oarMeshData = OarMeshGeometry.Create(_typeData.OarLength);
			Vector3[] array = (Vector3[])(object)new Vector3[oarMeshData.Positions.Length / 3];
			Vector2[] array2 = (Vector2[])(object)new Vector2[array.Length];
			for (int j = 0; j < array.Length; j++)
			{
				array[j] = new Vector3(oarMeshData.Positions[j * 3], oarMeshData.Positions[j * 3 + 1], oarMeshData.Positions[j * 3 + 2]);
				array2[j] = new Vector2(oarMeshData.UV[j * 2], oarMeshData.UV[j * 2 + 1]);
			}
			_mesh = new Mesh
			{
				name = "VikingRowingOar",
				vertices = array,
				uv = array2,
				triangles = oarMeshData.Triangles
			};
			_mesh.RecalculateNormals();
			_mesh.RecalculateBounds();
			_roots = (GameObject[])(object)new GameObject[_typeData.RowerCount];
			_lastStroke = new double[_roots.Length];
			for (int k = 0; k < _roots.Length; k++)
			{
				GameObject val3 = new GameObject("RowingOar_" + k);
				val3.transform.SetParent(((Component)this).transform, false);
				val3.transform.localPosition = _typeData.MountPoints[k];
				val3.AddComponent<MeshFilter>().sharedMesh = _mesh;
				((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterial = _material;
				_roots[k] = val3;
				Pose(val3.transform, k, 0.0, rowing: false);
			}
		}

		private static Texture2D MakeWoodGrain()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//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)
			Texture2D val = new Texture2D(64, 256, (TextureFormat)3, false);
			((Object)val).name = "OarWoodGrain";
			((Texture)val).wrapMode = (TextureWrapMode)0;
			Color[] array = (Color[])(object)new Color[16384];
			for (int i = 0; i < 256; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					float num = Mathf.Sin((float)j * 1.7f + Mathf.Sin((float)i * 0.037f) * 0.6f);
					float num2 = 0.9f + 0.1f * num + 0.035f * Mathf.Sin((float)j * 4.3f + (float)i * 0.12f);
					array[i * 64 + j] = new Color(0.53f * num2, 0.32f * num2, 0.15f * num2);
				}
			}
			val.SetPixels(array);
			val.Apply(false, true);
			return val;
		}

		private static void Pose(Transform root, int slot, double cycle, bool rowing, bool smooth = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			OarPose.Direction(slot % 2 == 0, cycle, rowing, out var x, out var y, out var z);
			Quaternion val = Quaternion.FromToRotation(Vector3.down, new Vector3(x, y, z));
			root.localRotation = (smooth ? Quaternion.Slerp(root.localRotation, val, 1f - Mathf.Exp(-18f * Time.deltaTime)) : val);
		}

		private void Update()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_ship == (Object)null)
			{
				return;
			}
			if (!_built)
			{
				TryBuild();
			}
			if (_roots == null)
			{
				_nativeOars.Restore();
				return;
			}
			bool flag = (int)_ship.GetSpeedSetting() == 2;
			_nativeOars.Update(_ship, flag);
			ShipRowingManager.SlotData[] array = (flag ? ShipRowingManager.GetSlots(_ship) : null);
			double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : ((double)Time.time));
			double num2 = Plugin.TargetStrokeInterval.Value;
			for (int i = 0; i < _roots.Length; i++)
			{
				GameObject val = _roots[i];
				val.SetActive(flag);
				if (flag)
				{
					ShipRowingManager.SlotData slotData = array[i];
					double num3 = num - slotData.LastStrokeAt;
					bool flag2 = slotData.PlayerId != 0L && slotData.LastStrokeAt > 0.0 && num3 >= 0.0 && num3 < num2;
					Pose(val.transform, i, flag2 ? (num3 / num2) : 0.0, flag2, smooth: true);
					if (flag2 && slotData.LastStrokeAt > _lastStroke[i])
					{
						_lastStroke[i] = slotData.LastStrokeAt;
						SplashAudio.PlayAt(val.transform.TransformPoint(Vector3.down * _typeData.OarLength), 0.2f);
					}
				}
			}
		}

		private void OnDisable()
		{
			_nativeOars.Restore();
		}

		private void OnDestroy()
		{
			_nativeOars.Restore();
			if (_roots != null)
			{
				GameObject[] roots = _roots;
				foreach (GameObject val in roots)
				{
					if ((Object)(object)val != (Object)null)
					{
						Object.Destroy((Object)(object)val);
					}
				}
			}
			if ((Object)(object)_mesh != (Object)null)
			{
				Object.Destroy((Object)(object)_mesh);
			}
			if ((Object)(object)_material != (Object)null)
			{
				Object.Destroy((Object)(object)_material);
			}
			if ((Object)(object)_grain != (Object)null)
			{
				Object.Destroy((Object)(object)_grain);
			}
		}
	}
	public static class ShipRowingManager
	{
		public class SlotData
		{
			public long PlayerId;

			public float Tempo;

			public double LastUpdate;

			public double LastStrokeAt;

			public int Streak;
		}

		private static readonly Dictionary<int, SlotData[]> _cachedSlots = new Dictionary<int, SlotData[]>();

		private static readonly Dictionary<int, float> _smoothedMultiplier = new Dictionary<int, float>();

		public static int GetActiveSlotCount(Ship ship)
		{
			return ShipTypeConfig.GetFor(ship).RowerCount;
		}

		public static SlotData[] GetSlots(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return EmptySlots();
			}
			ZNetView nView = ShipAccess.GetNView(ship);
			if ((Object)(object)nView == (Object)null || !nView.IsValid())
			{
				return EmptySlots();
			}
			ZDO zDO = nView.GetZDO();
			int instanceID = ((Object)ship).GetInstanceID();
			if (!_cachedSlots.TryGetValue(instanceID, out var value))
			{
				value = EmptySlots();
				_cachedSlots[instanceID] = value;
			}
			double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0);
			float value2 = Plugin.SlotTimeoutSeconds.Value;
			int activeSlotCount = GetActiveSlotCount(ship);
			for (int i = 0; i < 6; i++)
			{
				if (i >= activeSlotCount)
				{
					value[i].PlayerId = 0L;
					value[i].Tempo = 0f;
					value[i].LastUpdate = 0.0;
					value[i].LastStrokeAt = 0.0;
					value[i].Streak = 0;
					continue;
				}
				long num2 = zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L);
				float tempo = zDO.GetFloat(RowingZdoKeys.SlotTempo[i], 0f);
				float num3 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[i], 0f);
				float num4 = zDO.GetFloat(RowingZdoKeys.SlotLastStroke[i], 0f);
				int streak = zDO.GetInt(RowingZdoKeys.SlotStreak[i], 0);
				if (num2 == 0L || num - (double)num3 > (double)value2)
				{
					value[i].PlayerId = 0L;
					value[i].Tempo = 0f;
					value[i].LastUpdate = 0.0;
					value[i].LastStrokeAt = 0.0;
					value[i].Streak = 0;
				}
				else
				{
					value[i].PlayerId = num2;
					value[i].Tempo = tempo;
					value[i].LastUpdate = num3;
					value[i].LastStrokeAt = num4;
					value[i].Streak = streak;
				}
			}
			return value;
		}

		public static float GetTotalSpeedMultiplier(Ship ship)
		{
			int activeSlotCount = GetActiveSlotCount(ship);
			if (activeSlotCount == 0)
			{
				return 1f;
			}
			SlotData[] slots = GetSlots(ship);
			float num = Plugin.MinMultiplier.Value;
			for (int i = 0; i < activeSlotCount; i++)
			{
				if (slots[i].PlayerId != 0L)
				{
					num += slots[i].Tempo * Plugin.MaxMultiplierPerRower.Value;
				}
			}
			float num2 = Plugin.MinMultiplier.Value + (float)activeSlotCount * Plugin.MaxMultiplierPerRower.Value * Plugin.CapRatio.Value;
			return Mathf.Min(num, num2) * GetCrewSyncFactor(ship) * GetMasteryFactor(ship);
		}

		public static float GetMasteryFactor(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return 1f;
			}
			SlotData[] slots = GetSlots(ship);
			int activeSlotCount = GetActiveSlotCount(ship);
			double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0);
			float num2 = Plugin.TargetStrokeInterval.Value * 6f;
			int num3 = 0;
			for (int i = 0; i < activeSlotCount; i++)
			{
				if (slots[i].PlayerId != 0L && !(slots[i].LastStrokeAt <= 0.0) && !(num - slots[i].LastStrokeAt > (double)num2) && slots[i].Streak > num3)
				{
					num3 = slots[i].Streak;
				}
			}
			int value = Plugin.MasteryStreakMin.Value;
			int value2 = Plugin.MasteryStreakMax.Value;
			float value3 = Plugin.MasteryFactorAtMin.Value;
			float value4 = Plugin.MasteryFactorAtMax.Value;
			if (num3 < value)
			{
				return 1f;
			}
			if (num3 >= value2)
			{
				return value4;
			}
			float num4 = (float)(num3 - value) / (float)Mathf.Max(1, value2 - value);
			return Mathf.Lerp(value3, value4, num4);
		}

		public static float GetCrewSyncFactor(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return 1f;
			}
			int activeSlotCount = GetActiveSlotCount(ship);
			if (activeSlotCount < 2)
			{
				return 1f;
			}
			SlotData[] slots = GetSlots(ship);
			double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0);
			float num2 = Plugin.TargetStrokeInterval.Value * Plugin.CrewSyncWindowRatio.Value;
			List<double> list = new List<double>(activeSlotCount);
			for (int i = 0; i < activeSlotCount; i++)
			{
				if (slots[i].PlayerId != 0L && !(slots[i].LastStrokeAt <= 0.0) && !(num - slots[i].LastStrokeAt > (double)Plugin.TargetStrokeInterval.Value * 2.0))
				{
					list.Add(slots[i].LastStrokeAt);
				}
			}
			if (list.Count < 2)
			{
				return 1f;
			}
			list.Sort();
			int num3 = 1;
			for (int j = 0; j < list.Count; j++)
			{
				int num4 = 1;
				for (int k = j + 1; k < list.Count && list[k] - list[j] <= (double)num2 * 2.0; k++)
				{
					num4++;
				}
				if (num4 > num3)
				{
					num3 = num4;
				}
			}
			if (num3 >= 4)
			{
				return Plugin.CrewSync4Plus.Value;
			}
			return num3 switch
			{
				3 => Plugin.CrewSync3.Value, 
				2 => Plugin.CrewSync2.Value, 
				_ => 1f, 
			};
		}

		public static void TickSmoothing(Ship ship, float dt)
		{
			if (!((Object)(object)ship == (Object)null))
			{
				int instanceID = ((Object)ship).GetInstanceID();
				float totalSpeedMultiplier = GetTotalSpeedMultiplier(ship);
				if (!_smoothedMultiplier.TryGetValue(instanceID, out var value))
				{
					_smoothedMultiplier[instanceID] = totalSpeedMultiplier;
					return;
				}
				float num = 1f - Mathf.Exp((0f - dt) * Plugin.SmoothingRate.Value);
				_smoothedMultiplier[instanceID] = Mathf.Lerp(value, totalSpeedMultiplier, num);
			}
		}

		public static float GetSmoothedMultiplier(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return 1f;
			}
			int instanceID = ((Object)ship).GetInstanceID();
			if (_smoothedMultiplier.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			float totalSpeedMultiplier = GetTotalSpeedMultiplier(ship);
			_smoothedMultiplier[instanceID] = totalSpeedMultiplier;
			return totalSpeedMultiplier;
		}

		public static int ClaimSlot(Ship ship, long playerId)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return -1;
			}
			ZNetView nView = ShipAccess.GetNView(ship);
			if ((Object)(object)nView == (Object)null || !nView.IsValid())
			{
				return -1;
			}
			int activeSlotCount = GetActiveSlotCount(ship);
			if (activeSlotCount == 0)
			{
				return -1;
			}
			ZDO zDO = nView.GetZDO();
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			float value = Plugin.SlotTimeoutSeconds.Value;
			for (int i = 0; i < activeSlotCount; i++)
			{
				if (zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L) == playerId)
				{
					return i;
				}
			}
			for (int j = 0; j < activeSlotCount; j++)
			{
				long num = zDO.GetLong(RowingZdoKeys.SlotPlayer[j], 0L);
				float num2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[j], 0f);
				if (num == 0L || timeSeconds - (double)num2 > (double)value)
				{
					Plugin.Log.LogDebug((object)$"[ClaimSlot] Player {playerId} claiming slot {j} on '{((Object)((Component)ship).gameObject).name}' (was pid={num}, age={timeSeconds - (double)num2:F1}s)");
					nView.InvokeRPC(ZNetView.Everybody, "Rowing_ClaimSlot", new object[3] { j, playerId, timeSeconds });
					return j;
				}
			}
			Plugin.Log.LogWarning((object)$"[ClaimSlot] Player {playerId} - no free slot on '{((Object)((Component)ship).gameObject).name}' (max={activeSlotCount})");
			return -1;
		}

		public static void ReleaseSlot(Ship ship, int slot, long playerId)
		{
			if (!((Object)(object)ship == (Object)null) && slot >= 0 && slot < 6)
			{
				ZNetView nView = ShipAccess.GetNView(ship);
				if (!((Object)(object)nView == (Object)null) && nView.IsValid())
				{
					nView.InvokeRPC(ZNetView.Everybody, "Rowing_ReleaseSlot", new object[2] { slot, playerId });
				}
			}
		}

		public static void InvalidateCache(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null))
			{
				int instanceID = ((Object)ship).GetInstanceID();
				_cachedSlots.Remove(instanceID);
				_smoothedMultiplier.Remove(instanceID);
			}
		}

		public static void ClearAllCache()
		{
			_cachedSlots.Clear();
			_smoothedMultiplier.Clear();
		}

		private static SlotData[] EmptySlots()
		{
			SlotData[] array = new SlotData[6];
			for (int i = 0; i < 6; i++)
			{
				array[i] = new SlotData();
			}
			return array;
		}
	}
	public static class ShipTypeConfig
	{
		public class TypeData
		{
			public string Label;

			public int RowerCount;

			public float OarLength;

			public float ShaftRadius;

			public float BladeWidth;

			public float BladeLength;

			public Vector3[] MountPoints;
		}

		private static readonly Dictionary<int, TypeData> _cache = new Dictionary<int, TypeData>();

		public static TypeData GetFor(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return Fallback();
			}
			int instanceID = ((Object)ship).GetInstanceID();
			if (_cache.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			TypeData typeData = Build(ship);
			_cache[instanceID] = typeData;
			return typeData;
		}

		public static void Invalidate(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null))
			{
				_cache.Remove(((Object)ship).GetInstanceID());
			}
		}

		public static void ClearAll()
		{
			_cache.Clear();
		}

		private static TypeData Build(Ship ship)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: 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_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			string text = (((Object)((Component)ship).gameObject).name ?? "").ToLowerInvariant();
			Bounds hull = EstimateHullBounds(ship);
			BalrondShipProfile balrondShipProfile = BalrondShipCatalog.Find(text);
			if (balrondShipProfile != null)
			{
				if (!BalrondCompatibility.Enabled)
				{
					return None(balrondShipProfile.Label);
				}
				int value = Plugin.BalrondRowers[balrondShipProfile.Index - 3].Value;
				if (value == 0)
				{
					return None(balrondShipProfile.Label);
				}
				return MakeData(balrondShipProfile.Label, value, hull, balrondShipProfile.OarLength, (value <= 2) ? 0f : ((balrondShipProfile.Spread > 0f) ? balrondShipProfile.Spread : 0.4f), balrondShipProfile.Index);
			}
			TypeData typeData = (text.Contains("raft") ? None("Raft") : (text.Contains("karve") ? MakeData("Karve", 2, hull, 2.6f, 0f) : ((!text.Contains("drakkar") && !text.Contains("ashland")) ? MakeData("Longship"