Decompiled source of AllHandsOnboard v1.0.0

AllHandsOnboard.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
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.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0d80bcb34d13d9ebe4ed0008a925f4e132547939")]
[assembly: AssemblyProduct("AllHandsOnboard")]
[assembly: AssemblyTitle("AllHandsOnboard")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace AllHandsOnboard
{
	[BepInPlugin("com.allhands.onboard", "All hands onboard", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private const string ModId = "com.allhands.onboard";

		private const string ModName = "All hands onboard";

		private const string ModVersion = "1.0.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<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;

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

		private void Awake()
		{
			//IL_0563: Unknown result type (might be due to invalid IL or missing references)
			//IL_0572: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"=== All hands onboard v1.0.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.4f, "Target time between L/R strokes (sec). PERFECT window centered on this. Metronome alternates L/R at this rate.");
			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 short tick sound on each metronome beat");
			MetronomeVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Hud", "MetronomeVolume", 0.25f, "Volume of metronome tick (0.0-1.0)");
			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}");
			_harmony.PatchAll();
			int num = 0;
			foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
			{
				num++;
			}
			Log.LogInfo((object)$"Harmony PatchAll done - {num} method(s) patched");
			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.FindObjectsOfType<ShipOarsVisual>();
				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
			{
			}
			ManualLogSource log5 = Log;
			if (log5 != null)
			{
				log5.LogInfo((object)"Mod cleanup complete");
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "GetSailForce")]
	public static class GetSailForcePatch
	{
		private static int _logCounter;

		private static void Postfix(Ship __instance, ref Vector3 __result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if ((int)__instance.GetSpeedSetting() == 2)
			{
				float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(__instance);
				Vector3 val = __result;
				__result *= smoothedMultiplier;
				if (++_logCounter % 60 == 0)
				{
					Plugin.DebugLog($"[GetSailForce] Slow mode on '{((Object)((Component)__instance).gameObject).name}', mult={smoothedMultiplier:F2}, force={((Vector3)(ref val)).magnitude:F1}->{((Vector3)(ref __result)).magnitude:F1}");
				}
			}
		}
	}
	[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 float _lastMetronomePhase = -1f;

		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_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.ShowMetronome.Value)
			{
				float num = Mathf.Max(0.05f, Plugin.TargetStrokeInterval.Value);
				float num2 = Time.time / (num * 2f) % 1f;
				if (_lastMetronomePhase >= 0f)
				{
					bool flag = _lastMetronomePhase > 0.5f && num2 < 0.5f;
					bool flag2 = _lastMetronomePhase < 0.5f && num2 >= 0.5f;
					if (flag)
					{
						MetronomeAudio.PlayTick(high: true);
					}
					else if (flag2)
					{
						MetronomeAudio.PlayTick(high: false);
					}
				}
				_lastMetronomePhase = num2;
			}
			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.FindObjectsOfType<Ship>();
				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_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: 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_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Expected O, but got Unknown
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: 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_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: 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);
			Image val = _hudRoot.AddComponent<Image>();
			((Graphic)val).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 val2 = new GameObject("Verdict");
			val2.transform.SetParent(_hudRoot.transform, false);
			Text val3 = val2.AddComponent<Text>();
			val3.font = builtinResource;
			val3.fontSize = 22;
			val3.fontStyle = (FontStyle)1;
			val3.alignment = (TextAnchor)3;
			val3.text = "";
			Outline val4 = val2.AddComponent<Outline>();
			((Shadow)val4).effectColor = new Color(0f, 0f, 0f, 0.85f);
			((Shadow)val4).effectDistance = new Vector2(1.5f, -1.5f);
			RectTransform component2 = val2.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 = val3;
		}

		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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			Image val2 = val.AddComponent<Image>();
			((Graphic)val2).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 val3 = new GameObject("Label");
			val3.transform.SetParent(val.transform, false);
			Text val4 = val3.AddComponent<Text>();
			val4.font = font;
			val4.fontSize = 14;
			val4.fontStyle = (FontStyle)1;
			val4.alignment = (TextAnchor)4;
			((Graphic)val4).color = Color.white;
			val4.text = label;
			RectTransform component2 = val3.GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			component2.offsetMin = Vector2.zero;
			component2.offsetMax = Vector2.zero;
			return val2;
		}

		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_0007: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: 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
	{
		private static AudioClip _highClip;

		private static AudioClip _lowClip;

		public static AudioClip GetClip(bool high)
		{
			if (high && (Object)(object)_highClip != (Object)null)
			{
				return _highClip;
			}
			if (!high && (Object)(object)_lowClip != (Object)null)
			{
				return _lowClip;
			}
			float num = (high ? 1320f : 880f);
			float[] array = new float[3675];
			for (int i = 0; i < 3675; i++)
			{
				float num2 = (float)i / 44100f;
				float num3 = Mathf.Exp((0f - num2) * 35f);
				array[i] = Mathf.Sin(MathF.PI * 2f * num * num2) * num3 * 0.6f;
			}
			AudioClip val = AudioClip.Create(high ? "MetronomeHigh" : "MetronomeLow", 3675, 1, 44100, false);
			val.SetData(array, 0);
			if (high)
			{
				_highClip = val;
			}
			else
			{
				_lowClip = val;
			}
			return val;
		}

		public static void PlayTick(bool high)
		{
			//IL_003a: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.MetronomeSound.Value)
			{
				AudioClip clip = GetClip(high);
				if (!((Object)(object)clip == (Object)null))
				{
					AudioListener val = Object.FindObjectOfType<AudioListener>();
					Vector3 val2 = (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : Vector3.zero);
					float num = Mathf.Clamp01(Plugin.MetronomeVolume.Value);
					AudioSource.PlayClipAtPoint(clip, val2, num);
				}
			}
		}
	}
	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 != 0)
				{
					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 flag = (_leftTriggerPrev ? (num > 0.3f) : (num > 0.5f));
			if (flag && !_leftTriggerPrev)
			{
				_bufferedLeft = true;
			}
			_leftTriggerPrev = flag;
			float num2 = SafeGetAxis(Plugin.RightPadAxis.Value);
			bool flag2 = (_rightTriggerPrev ? (num2 > 0.3f) : (num2 > 0.5f));
			if (flag2 && !_rightTriggerPrev)
			{
				_bufferedRight = true;
			}
			_rightTriggerPrev = flag2;
		}

		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)
			{
				KeyCode val = (isLeft ? Plugin.LeftKey.Value : Plugin.RightKey.Value);
				return ((object)(KeyCode)(ref val)).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 ForceField = AccessTools.Field(typeof(Ship), "m_force");

		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 GetForce(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null) && !(ForceField == null))
			{
				return (float)ForceField.GetValue(ship);
			}
			return 0f;
		}

		public static void SetForce(Ship ship, float v)
		{
			ForceField?.SetValue(ship, v);
		}

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

		public static void SetBackwardForce(Ship ship, float v)
		{
			BackwardForceField?.SetValue(ship, v);
		}

		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))
			{
				ShipOarsVisual shipOarsVisual = ((Component)__instance).gameObject.AddComponent<ShipOarsVisual>();
				shipOarsVisual.Init(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "Awake")]
	public static class ShipAwakeRpcPatch
	{
		private static void Postfix(Ship __instance)
		{
			Ship __instance2 = __instance;
			if ((Object)(object)__instance2 == (Object)null)
			{
				return;
			}
			ZNetView nView = ShipAccess.GetNView(__instance2);
			if ((Object)(object)nView == (Object)null)
			{
				Plugin.Log.LogWarning((object)("[ShipAwakeRpc] Ship '" + ((Object)((Component)__instance2).gameObject).name + "' has NO ZNetView - RPC not registered!"));
				return;
			}
			Plugin.Log.LogDebug((object)$"[ShipAwakeRpc] Registering RPCs on '{((Object)((Component)__instance2).gameObject).name}' (instance {((Object)__instance2).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(__instance2, 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(__instance2, 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(__instance2, slot, playerId, time);
			});
			nView.Register<int, long>("Rowing_ReleaseSlot", (Action<long, int, long>)delegate(long sender, int slot, long playerId)
			{
				OnReleaseSlot(__instance2, 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();
				long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L);
				if (@long == 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();
				long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L);
				if (@long == 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 @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L);
				float @float = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[slot], 0f);
				if (@long == 0L || @long == playerId || timeSeconds - (double)@float > (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 {@long}, age={timeSeconds - (double)@float: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();
				long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L);
				if (@long == 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
	{
		public class State
		{
			public float SavedForce;

			public float SavedBackwardForce;

			public bool Modified;
		}

		private static int _logCounter;

		private static void Prefix(Ship __instance, float fixedDeltaTime, out State __state)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			__state = new State();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			bool flag = __instance.IsPlayerInBoat(localPlayer);
			bool flag2 = ShipAccess.IsPlayerSeatedOnShip(localPlayer, __instance);
			bool flag3 = (int)__instance.GetSpeedSetting() == 2;
			if (++_logCounter % 50 == 0)
			{
				Plugin.DebugLog($"[ShipTick] '{((Object)((Component)__instance).gameObject).name}' onShip={flag}, seated={flag2}, rowingMode={flag3}, mySlot={LocalRowingState.CurrentSlot}");
			}
			if (flag3)
			{
				ShipRowingManager.TickSmoothing(__instance, fixedDeltaTime);
				float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(__instance);
				__state.SavedForce = ShipAccess.GetForce(__instance);
				__state.SavedBackwardForce = ShipAccess.GetBackwardForce(__instance);
				__state.Modified = true;
				ShipAccess.SetForce(__instance, __state.SavedForce * smoothedMultiplier);
				ShipAccess.SetBackwardForce(__instance, __state.SavedBackwardForce * smoothedMultiplier);
				if (_logCounter % 50 == 0)
				{
					float totalSpeedMultiplier = ShipRowingManager.GetTotalSpeedMultiplier(__instance);
					Plugin.DebugLog($"[ShipTick] Force scaled: {__state.SavedForce:F1} -> {__state.SavedForce * smoothedMultiplier:F1} (smoothed={smoothedMultiplier:F2}, raw={totalSpeedMultiplier:F2})");
				}
			}
			if (!flag || !flag3 || !flag2)
			{
				if (LocalRowingState.CurrentSlot != -1 && (Object)(object)LocalRowingState.CurrentShip == (Object)(object)__instance)
				{
					Plugin.Log.LogDebug((object)$"[FixedUpdate] Releasing slot {LocalRowingState.CurrentSlot} on '{((Object)((Component)__instance).gameObject).name}' (onShip={flag}, seated={flag2}, rowing={flag3})");
					ShipRowingManager.ReleaseSlot(__instance, LocalRowingState.CurrentSlot, localPlayer.GetPlayerID());
					LocalRowingState.SetSlot(-1, null);
				}
			}
			else
			{
				if (LocalRowingState.CurrentSlot == -1)
				{
					int num = ShipRowingManager.ClaimSlot(__instance, localPlayer.GetPlayerID());
					Plugin.Log.LogDebug((object)$"[FixedUpdate] Player {localPlayer.GetPlayerID()} on '{((Object)((Component)__instance).gameObject).name}' in Slow mode -> ClaimSlot returned {num}");
					LocalRowingState.SetSlot(num, __instance);
				}
				LocalRowingState.Tick(fixedDeltaTime, __instance);
			}
		}

		private static void Postfix(Ship __instance, State __state)
		{
			if (__state != null && __state.Modified)
			{
				ShipAccess.SetForce(__instance, __state.SavedForce);
				ShipAccess.SetBackwardForce(__instance, __state.SavedBackwardForce);
			}
		}
	}
	public class ShipOarsVisual : MonoBehaviour
	{
		private Ship _ship;

		private GameObject[] _oarRoots;

		private Transform[] _oarShafts;

		private float[] _phases;

		private float[] _lastSeenStrokeTime;

		private float[] _strokePulse;

		private ShipTypeConfig.TypeData _typeData;

		private bool _built;

		private Material _woodMaterialBase;

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

		private void TryBuild()
		{
			if (_built || (Object)(object)_ship == (Object)null)
			{
				return;
			}
			_typeData = ShipTypeConfig.GetFor(_ship);
			int rowerCount = _typeData.RowerCount;
			_built = true;
			if (rowerCount != 0)
			{
				_oarRoots = (GameObject[])(object)new GameObject[rowerCount];
				_oarShafts = (Transform[])(object)new Transform[rowerCount];
				_phases = new float[rowerCount];
				_lastSeenStrokeTime = new float[rowerCount];
				_strokePulse = new float[rowerCount];
				for (int i = 0; i < rowerCount; i++)
				{
					CreateOar(i);
				}
			}
		}

		private void CreateOar(int slot)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject($"RowingOar_{slot}");
			val.transform.SetParent(((Component)this).transform, false);
			val.transform.localPosition = _typeData.MountPoints[slot];
			_oarRoots[slot] = val;
			GameObject val2 = GameObject.CreatePrimitive((PrimitiveType)2);
			((Object)val2).name = "Shaft";
			Collider component = val2.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			val2.transform.SetParent(val.transform, false);
			float oarLength = _typeData.OarLength;
			float shaftRadius = _typeData.ShaftRadius;
			val2.transform.localScale = new Vector3(shaftRadius * 2f, oarLength * 0.5f, shaftRadius * 2f);
			val2.transform.localPosition = new Vector3(0f, (0f - oarLength) * 0.5f, 0f);
			ApplyWoodMaterial(val2, darker: false);
			_oarShafts[slot] = val.transform;
			GameObject val3 = GameObject.CreatePrimitive((PrimitiveType)3);
			((Object)val3).name = "Blade";
			Collider component2 = val3.GetComponent<Collider>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.Destroy((Object)(object)component2);
			}
			val3.transform.SetParent(val2.transform, false);
			val3.transform.localScale = new Vector3(_typeData.BladeWidth / (shaftRadius * 2f), _typeData.BladeLength / (oarLength * 0.5f), 0.05f / (shaftRadius * 2f));
			val3.transform.localPosition = new Vector3(0f, -1f, 0f);
			ApplyWoodMaterial(val3, darker: true);
			bool flag = slot % 2 == 0;
			val.transform.localRotation = Quaternion.Euler(0f, 0f, flag ? 90f : (-90f));
		}

		private Material EnsureWoodBase()
		{
			if ((Object)(object)_woodMaterialBase != (Object)null)
			{
				return _woodMaterialBase;
			}
			if ((Object)(object)_ship == (Object)null)
			{
				return null;
			}
			MeshRenderer[] componentsInChildren = ((Component)_ship).GetComponentsInChildren<MeshRenderer>(true);
			MeshRenderer[] array = componentsInChildren;
			foreach (MeshRenderer val in array)
			{
				if ((Object)(object)val == (Object)null || ((Object)((Component)val).gameObject).name.StartsWith("RowingOar"))
				{
					continue;
				}
				Material sharedMaterial = ((Renderer)val).sharedMaterial;
				if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null))
				{
					string name = ((Object)sharedMaterial.shader).name;
					if (!(name == "Standard") && !(name == "Sprites/Default") && !name.Contains("Hidden") && !name.Contains("Default-Material"))
					{
						_woodMaterialBase = sharedMaterial;
						Plugin.DebugLog("[Oars] Using wood material from '" + ((Object)((Component)val).gameObject).name + "': shader=" + name);
						return _woodMaterialBase;
					}
				}
			}
			Plugin.Log.LogWarning((object)"[Oars] No usable wood material on ship - oars may render purple");
			return null;
		}

		private void ApplyWoodMaterial(GameObject go, bool darker)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			Renderer component = go.GetComponent<Renderer>();
			if (!((Object)(object)component == (Object)null))
			{
				Material val = EnsureWoodBase();
				Material val2 = new Material(((Object)(object)val != (Object)null) ? val : component.sharedMaterial);
				if (val2.HasProperty("_Color"))
				{
					val2.color = (darker ? new Color(0.55f, 0.42f, 0.28f, 1f) : new Color(0.78f, 0.62f, 0.42f, 1f));
				}
				component.material = val2;
			}
		}

		private void Update()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			if ((Object)(object)_ship == (Object)null)
			{
				return;
			}
			if (!_built)
			{
				TryBuild();
			}
			if (_typeData == null || _typeData.RowerCount == 0)
			{
				return;
			}
			bool flag = (int)_ship.GetSpeedSetting() == 2;
			SetVisible(flag);
			if (flag)
			{
				ShipRowingManager.SlotData[] slots = ShipRowingManager.GetSlots(_ship);
				float deltaTime = Time.deltaTime;
				for (int i = 0; i < _typeData.RowerCount; i++)
				{
					AnimateOar(i, slots[i], deltaTime);
				}
			}
		}

		private void SetVisible(bool visible)
		{
			if (_oarRoots == null)
			{
				return;
			}
			for (int i = 0; i < _oarRoots.Length; i++)
			{
				if ((Object)(object)_oarRoots[i] != (Object)null && _oarRoots[i].activeSelf != visible)
				{
					_oarRoots[i].SetActive(visible);
				}
			}
		}

		private void AnimateOar(int slot, ShipRowingManager.SlotData data, float dt)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			Transform val = _oarShafts[slot];
			if (!((Object)(object)val == (Object)null))
			{
				bool flag = data.PlayerId != 0;
				float num = (flag ? data.Tempo : 0f);
				float num2 = (float)data.LastUpdate;
				if (flag && num2 > _lastSeenStrokeTime[slot] + 0.05f)
				{
					_strokePulse[slot] = 1f;
					_lastSeenStrokeTime[slot] = num2;
					Vector3 position = val.position + val.TransformDirection(Vector3.down) * _typeData.OarLength;
					SplashAudio.PlayAt(position);
				}
				_strokePulse[slot] = Mathf.Max(0f, _strokePulse[slot] - dt * 4f);
				float num3 = 0.15f + num * 1.4f;
				_phases[slot] += num3 * MathF.PI * 2f * dt;
				float num4 = Mathf.Lerp(8f, 35f, num);
				float num5 = Mathf.Sin(_phases[slot]) * num4;
				float num6 = _strokePulse[slot] * 25f * Mathf.Sin(_strokePulse[slot] * MathF.PI);
				bool flag2 = slot % 2 == 0;
				float num7 = (flag ? Mathf.Lerp(45f, 75f, num) : 90f);
				float num8 = (flag2 ? (-1f) : 1f) * num7;
				val.localRotation = Quaternion.Euler(num6, num8, flag2 ? num5 : (0f - num5));
			}
		}

		private void OnDestroy()
		{
			if (_oarRoots == null)
			{
				return;
			}
			GameObject[] oarRoots = _oarRoots;
			foreach (GameObject val in oarRoots)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}
	}
	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 SlotData[] 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 @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L);
				float @float = zDO.GetFloat(RowingZdoKeys.SlotTempo[i], 0f);
				float float2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[i], 0f);
				float float3 = zDO.GetFloat(RowingZdoKeys.SlotLastStroke[i], 0f);
				int @int = zDO.GetInt(RowingZdoKeys.SlotStreak[i], 0);
				if (@long == 0L || num - (double)float2 > (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 = @long;
					value[i].Tempo = @float;
					value[i].LastUpdate = float2;
					value[i].LastStrokeAt = float3;
					value[i].Streak = @int;
				}
			}
			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;
			float num3 = Mathf.Min(num, num2);
			return num3 * 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 > 1.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 @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[j], 0L);
				float @float = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[j], 0f);
				if (@long == 0L || timeSeconds - (double)@float > (double)value)
				{
					Plugin.Log.LogDebug((object)$"[ClaimSlot] Player {playerId} claiming slot {j} on '{((Object)((Component)ship).gameObject).name}' (was pid={@long}, age={timeSeconds - (double)@float: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 TypeData 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			string text = (((Object)((Component)ship).gameObject).name ?? "").ToLowerInvariant();
			Bounds hull = EstimateHullBounds(ship);
			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", 4, hull, 3.5f, 0.45f) : MakeData("Drakkar", 6, hull, 4.2f, 0.65f))));
			Plugin.Log.LogDebug((object)$"[ShipTypeConfig] Built '{((Object)((Component)ship).gameObject).name}' -> type={typeData.Label}, rowers={typeData.RowerCount}, hull bounds(local)={((Bounds)(ref hull)).size}");
			return typeData;
		}

		private static Bounds EstimateHullBounds(Ship ship)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: 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_002a: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: 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_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			BoxCollider floatCollider = ShipAccess.GetFloatCollider(ship);
			if ((Object)(object)floatCollider != (Object)null)
			{
				Bounds bounds = ((Collider)floatCollider).bounds;
				Vector3 val = ((Component)ship).transform.InverseTransformPoint(((Bounds)(ref bounds)).center);
				Vector3 val2 = ((Component)ship).transform.InverseTransformVector(((Bounds)(ref bounds)).size);
				((Vector3)(ref val2))..ctor(Mathf.Abs(val2.x), Mathf.Abs(val2.y), Mathf.Abs(val2.z));
				return new Bounds(val, val2);
			}
			Collider[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Collider>();
			bool flag = true;
			Bounds bounds2 = default(Bounds);
			((Bounds)(ref bounds2))..ctor(((Component)ship).transform.position, Vector3.zero);
			Collider[] array = componentsInChildren;
			foreach (Collider val3 in array)
			{
				if (!((Object)(object)val3 == (Object)null) && !val3.isTrigger && !((Object)val3).name.StartsWith("RowingOar"))
				{
					if (flag)
					{
						bounds2 = val3.bounds;
						flag = false;
					}
					else
					{
						((Bounds)(ref bounds2)).Encapsulate(val3.bounds);
					}
				}
			}
			if (flag)
			{
				return new Bounds(Vector3.zero, new Vector3(2.5f, 1.5f, 8f));
			}
			Vector3 val4 = ((Component)ship).transform.InverseTransformPoint(((Bounds)(ref bounds2)).center);
			Vector3 val5 = ((Component)ship).transform.InverseTransformVector(((Bounds)(ref bounds2)).size);
			((Vector3)(ref val5))..ctor(Mathf.Abs(val5.x), Mathf.Abs(val5.y), Mathf.Abs(val5.z));
			return new Bounds(val4, val5);
		}

		private static TypeData MakeData(string label, int rowerCount, Bounds hull, float oarLen, float lengthSpread)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.Max(1, rowerCount / 2);
			Vector3[] array = (Vector3[])(object)new Vector3[rowerCount];
			float num2 = ((Bounds)(ref hull)).size.x * 0.62f;
			float num3 = ((Bounds)(ref hull)).center.y + ((Bounds)(ref hull)).size.y * 0.5f;
			float num4 = ((Bounds)(ref hull)).size.z * 0.5f * lengthSpread;
			for (int i = 0; i < num; i++)
			{
				float num5 = ((num == 1) ? 0.5f : ((float)i / (float)(num - 1)));
				float num6 = ((Bounds)(ref hull)).center.z + Mathf.Lerp(0f - num4, num4, num5);
				int num7 = i * 2;
				int num8 = i * 2 + 1;
				if (num7 < rowerCount)
				{
					array[num7] = new Vector3(0f - num2, num3, num6);
				}
				if (num8 < rowerCount)
				{
					array[num8] = new Vector3(num2, num3, num6);
				}
			}
			return new TypeData
			{
				Label = label,
				RowerCount = rowerCount,
				OarLength = oarLen,
				ShaftRadius = 0.06f,
				BladeWidth = 0.35f,
				BladeLength = 0.7f,
				MountPoints = array
			};
		}

		private static TypeData None(string label)
		{
			TypeData typeData = new TypeData();
			typeData.Label = label;
			typeData.RowerCount = 0;
			typeData.MountPoints = (Vector3[])(object)new Vector3[0];
			return typeData;
		}

		private static TypeData Fallback()
		{
			return None("Unknown");
		}
	}
	public static class SplashAudio
	{
		private static AudioClip _splashClip;

		public static AudioClip GetClip()
		{
			if ((Object)(object)_splashClip != (Object)null)
			{
				return _splashClip;
			}
			float[] array = new float[11025];
			Random random = new Random(42);
			for (int i = 0; i < 11025; i++)
			{
				float num = (float)i / 11025f;
				float num2 = Mathf.Exp((0f - num) * 8f);
				float num3 = (float)(random.NextDouble() * 2.0 - 1.0);
				array[i] = num3 * num2 * 0.4f;
			}
			for (int j = 1; j < 11025; j++)
			{
				array[j] = (array[j] + array[j - 1]) * 0.5f;
			}
			_splashClip = AudioClip.Create("RowingSplash", 11025, 1, 44100, false);
			_splashClip.SetData(array, 0);
			return _splashClip;
		}

		public static void PlayAt(Vector3 position, float volume = 0.3f)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			AudioClip clip = GetClip();
			if (!((Object)(object)clip == (Object)null))
			{
				AudioSource.PlayClipAtPoint(clip, position, volume);
			}
		}
	}
}