Decompiled source of Untamo Boats v1.0.5

BepInEx/plugins/Untamo.Boats.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Serializing;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Untamo.Boats")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Untamo.Boats")]
[assembly: AssemblyTitle("Untamo.Boats")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Untamo.Boats
{
	[BepInPlugin("Untamo.Boats", "Untamo.Boats", "1.0.5")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private readonly struct CollisionPair
		{
			internal readonly Collider Item;

			internal readonly Collider Boat;

			internal CollisionPair(Collider item, Collider boat)
			{
				Item = item;
				Boat = boat;
			}
		}

		public const string PluginGuid = "Untamo.Boats";

		public const string PluginName = "Untamo.Boats";

		public const string PluginVersion = "1.0.5";

		private const int BoatCost = 250;

		private const int MaximumBoats = 10;

		private ConfigEntry<KeyboardShortcut> _purchaseKey;

		private ConfigEntry<KeyboardShortcut> _removeAllKey;

		private bool _purchaseInProgress;

		private Boat _activeBoat;

		private Harmony _harmony;

		private readonly Dictionary<string, Boat> _savedBoats = new Dictionary<string, Boat>();

		private readonly HashSet<Boat> _restoringCustomBoats = new HashSet<Boat>();

		private BoatSaveFile _save = new BoatSaveFile();

		private string _savePath;

		private string _saveContextKey;

		private string _nextBoatName = "Custom Boat 1";

		private bool _restoreStarted;

		private BoatManager _knownBoatManager;

		private float _nextAutoSave;

		private bool _hasRuntimeSpawnAnchor;

		private Vector3 _runtimeSpawnAnchor;

		private bool _isIslandTransition;

		private readonly List<CollisionPair> _ignoredHeldItemCollisions = new List<CollisionPair>();

		private Component _collisionIgnoredItem;

		private Boat _collisionIgnoredBoat;

		private Boat _lastLocallyDrivenBoat;

		private bool _selectedMotorRestored;

		private bool _selectedSkinRestored;

		private bool _selectedControlSkinRestored;

		private Boat _selectedControlSkinRestoredBoat;

		private bool _selectedMotorMountRestored;

		private Boat _selectedMotorMountRestoredBoat;

		private Coroutine _selectedControlSkinRestoreRoutine;

		private bool _selectedRadarPositionRestored;

		private bool _originalHealthRestored;

		private bool _originalFuelRestored;

		private bool _originalCooknessRestored;

		private ILogHandler _previousUnityLogHandler;

		private GameObject _boostHudObject;

		private Component _boostTextComponent;

		private float _nextBoostHudRefresh;

		private float _nextNearbyBoatRefresh;

		private Boat _cachedNearbyBoat;

		private float _nextBoatCountRefresh;

		private int _cachedBoatCount;

		private float _nextOriginalBoatRefresh;

		private Boat _cachedOriginalBoat;

		private static int _drivenBoatCacheFrame = -1;

		private static Boat _cachedLocallyDrivenBoat;

		private float _interactionRecoveryHoldStart = -1f;

		private bool _interactionRecoveryTriggered;

		private const float DestructiveKeyHoldSeconds = 0.3f;

		private float _purchaseKeyHoldStart = -1f;

		private bool _purchaseKeyHoldTriggered;

		private float _removeAllKeyHoldStart = -1f;

		private bool _removeAllKeyHoldTriggered;

		private float _deleteKeyHoldStart = -1f;

		private bool _deleteKeyHoldTriggered;

		private float _insertKeyHoldStart = -1f;

		private bool _insertKeyHoldTriggered;

		private const float FuelSellHoldStepSeconds = 0.4f;

		private float _homeFuelHoldStart = -1f;

		private int _homeFuelSellSteps;

		internal static Plugin Instance { get; private set; }

		private void Awake()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			Instance = this;
			_purchaseKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "PurchaseBoatKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Buys and spawns a boat for 250. Up to four boats may exist.");
			_removeAllKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "RemoveAllBoatsKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Despawns every boat and clears all saved purchased boats.");
			_saveContextKey = null;
			_savePath = Path.Combine(Paths.ConfigPath, "Untamo.Boats.pending.boats.txt");
			_save = new BoatSaveFile();
			BoatCruiseRouting.ConfigurePersistence(1f, RememberCruisePower);
			try
			{
				_previousUnityLogHandler = Debug.unityLogger.logHandler;
				if (!(_previousUnityLogHandler is SkinKeywordNoiseLogHandler))
				{
					Debug.unityLogger.logHandler = (ILogHandler)(object)new SkinKeywordNoiseLogHandler(_previousUnityLogHandler);
				}
			}
			catch
			{
				_previousUnityLogHandler = null;
			}
			_harmony = new Harmony("Untamo.Boats");
			_harmony.PatchAll();
			BoatAdditionalDamageBridge.Install(_harmony, ((BaseUnityPlugin)this).Logger);
			BoatExactMeleeHitBridge.Install(_harmony, ((BaseUnityPlugin)this).Logger);
			BoatExactFistHitBridge.Install(_harmony, ((BaseUnityPlugin)this).Logger);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Press F8 to buy a boat for {2:N0}.", "Untamo.Boats", "1.0.5", 250));
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			RestoreHeldItemCollisions();
			if ((Object)(object)_boostHudObject != (Object)null)
			{
				Object.Destroy((Object)(object)_boostHudObject);
			}
			SaveBoats();
			try
			{
				if (_previousUnityLogHandler != null && Debug.unityLogger.logHandler is SkinKeywordNoiseLogHandler)
				{
					Debug.unityLogger.logHandler = _previousUnityLogHandler;
				}
			}
			catch
			{
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_035c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			EnsurePerGameSaveContext();
			BoatManager instance = BoatManager.Instance;
			bool flag = (Object)(object)instance != (Object)(object)_knownBoatManager;
			if (flag)
			{
				if (_savedBoats.Count > 0 && _save != null && !string.IsNullOrEmpty(_savePath))
				{
					SaveBoats();
				}
				_knownBoatManager = instance;
				_restoreStarted = false;
				_selectedMotorRestored = false;
				_selectedSkinRestored = false;
				_selectedControlSkinRestored = false;
				_selectedControlSkinRestoredBoat = null;
				_selectedMotorMountRestored = false;
				_selectedMotorMountRestoredBoat = null;
				if (_selectedControlSkinRestoreRoutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_selectedControlSkinRestoreRoutine);
					_selectedControlSkinRestoreRoutine = null;
				}
				_selectedRadarPositionRestored = false;
				BoatMotorSkinRouting.ClearAllRuntime();
				_originalHealthRestored = false;
				_originalFuelRestored = false;
				_originalCooknessRestored = false;
				_savedBoats.Clear();
				_restoringCustomBoats.Clear();
				if ((Object)(object)instance == (Object)null)
				{
					BoatGpsRouting.ClearRadarPositionRuntimeCache();
				}
				if (InstanceFinder.IsServerStarted)
				{
					StopAllBoatInput();
				}
				Boat locallyDrivenBoat = GetLocallyDrivenBoat();
				if ((Object)(object)locallyDrivenBoat == (Object)null && (Object)(object)_lastLocallyDrivenBoat != (Object)null && ((NetworkBehaviour)_lastLocallyDrivenBoat).IsSpawned && (Object)(object)Player.LocalPlayer != (Object)null && InstanceFinder.IsServerStarted)
				{
					BoatManager.SetBoat(BoatInteractionRouting.GetManagerColliders(_lastLocallyDrivenBoat), _lastLocallyDrivenBoat);
					_lastLocallyDrivenBoat.TrySetDriver(Player.LocalPlayer);
				}
			}
			SyncDrivenBoatWithManager();
			RestoreSelectedMotorIfNeeded();
			RestoreSelectedMotorSkinIfNeeded();
			RestoreSelectedSkinIfNeeded();
			RestoreSelectedControlSkinIfNeeded();
			RestoreSelectedMotorMountModeIfNeeded();
			RestoreSelectedRadarPositionIfNeeded();
			RestoreSelectedRadarSkinIfNeeded();
			RestoreOriginalHealthIfNeeded();
			RestoreOriginalFuelIfNeeded();
			BoatCommandRouting.UpdatePendingResets();
			UpdateIslandAnchor();
			UpdateBoatInventoryControls();
			UpdateBoatMotorAndGpsControls();
			UpdateHeldItemBoatCollisions();
			UpdateBoatBoostSelection();
			BoatHitboxRouting.Update();
			BoatDamageRouting.Update();
			BoatDiscNativeCollisionPatch.UpdateThrowState();
			BoatDamageVisualRouting.Update();
			BoatMotorSkinRouting.Update();
			BoatControlSkinRouting.Update();
			BoatRadarSkinRouting.Update();
			BoatFuelRouting.Update();
			BoatCooknessInspectRouting.Update();
			BoatPhysicsKeepAlive.Update();
			BoatInteractionRouting.Update();
			BoatClosestPhysicsRouting.Update();
			UpdateInteractionRecoveryHold();
			Boat locallyDrivenBoat2 = GetLocallyDrivenBoat();
			if ((Object)(object)locallyDrivenBoat2 != (Object)null && Input.GetKey((KeyCode)308))
			{
				bool key = Input.GetKey((KeyCode)304);
				if (Input.GetKeyDown((KeyCode)276))
				{
					MoveRadarAndSave(locallyDrivenBoat2, new Vector3(0f, 0f, -0.1f));
				}
				else if (Input.GetKeyDown((KeyCode)275))
				{
					MoveRadarAndSave(locallyDrivenBoat2, new Vector3(0f, 0f, 0.1f));
				}
				else if (Input.GetKeyDown((KeyCode)273))
				{
					MoveRadarAndSave(locallyDrivenBoat2, key ? new Vector3(0f, 0.1f, 0f) : new Vector3(-0.1f, 0f, 0f));
				}
				else if (Input.GetKeyDown((KeyCode)274))
				{
					MoveRadarAndSave(locallyDrivenBoat2, key ? new Vector3(0f, -0.1f, 0f) : new Vector3(0.1f, 0f, 0f));
				}
			}
			if ((Object)(object)locallyDrivenBoat2 != (Object)null)
			{
				_lastLocallyDrivenBoat = locallyDrivenBoat2;
			}
			else if (!flag && !_isIslandTransition)
			{
				_lastLocallyDrivenBoat = null;
			}
			KeyboardShortcut value;
			if (!_purchaseInProgress)
			{
				value = _purchaseKey.Value;
				if (IsHoldKeyTriggered(((KeyboardShortcut)(ref value)).MainKey, ref _purchaseKeyHoldStart, ref _purchaseKeyHoldTriggered, 0.3f))
				{
					((MonoBehaviour)this).StartCoroutine(TryPurchaseBoat());
				}
			}
			value = _removeAllKey.Value;
			if (IsHoldKeyTriggered(((KeyboardShortcut)(ref value)).MainKey, ref _removeAllKeyHoldStart, ref _removeAllKeyHoldTriggered, 0.3f))
			{
				RemoveAllBoats();
			}
			if (IsHoldKeyTriggered((KeyCode)127, ref _deleteKeyHoldStart, ref _deleteKeyHoldTriggered, 0.3f))
			{
				RemoveCurrentBoat();
			}
			if (IsHoldKeyTriggered((KeyCode)277, ref _insertKeyHoldStart, ref _insertKeyHoldTriggered, 0.3f))
			{
				TryRepairNearbyBoat();
			}
			UpdateHomeFuelBuySellControl();
			if ((Object)(object)locallyDrivenBoat2 != (Object)null && (Object)(object)BoatManager.Instance != (Object)null && (Object)(object)BoatManager.Boat != (Object)(object)locallyDrivenBoat2)
			{
				BoatManager.SetBoat(BoatInteractionRouting.GetManagerColliders(locallyDrivenBoat2), locallyDrivenBoat2);
			}
			if (!_restoreStarted && !string.IsNullOrEmpty(_saveContextKey) && !string.IsNullOrEmpty(_savePath) && InstanceFinder.IsServerStarted && (Object)(object)BoatManager.Instance != (Object)null)
			{
				_restoreStarted = true;
				((MonoBehaviour)this).StartCoroutine(RestoreSavedBoats());
			}
			if (InstanceFinder.IsServerStarted && Time.unscaledTime >= _nextAutoSave)
			{
				_nextAutoSave = Time.unscaledTime + 5f;
				SaveBoats();
			}
		}

		private void LateUpdate()
		{
			BoatCruiseRouting.UpdatePersistentCruise();
			BoatSkinRouting.UpdateCustomSkins();
			BoatSkinRouting.UpdateRainbowSkins();
		}

		private void FixedUpdate()
		{
			BoatFollowRouting.Update();
			BoatAnchorRouting.FixedUpdate();
			BoatDamageRouting.FixedUpdate();
		}

		private void UpdateHomeFuelBuySellControl()
		{
			if (Input.GetKeyDown((KeyCode)278))
			{
				_homeFuelHoldStart = Time.unscaledTime;
				_homeFuelSellSteps = 0;
			}
			if (Input.GetKey((KeyCode)278) && _homeFuelHoldStart >= 0f)
			{
				float num = Mathf.Max(0f, Time.unscaledTime - _homeFuelHoldStart);
				int num2 = Mathf.FloorToInt(num / 0.4f);
				while (_homeFuelSellSteps < num2)
				{
					_homeFuelSellSteps++;
					TrySellFuelNearbyBoat();
				}
			}
			if (Input.GetKeyUp((KeyCode)278))
			{
				if (_homeFuelHoldStart >= 0f && _homeFuelSellSteps == 0)
				{
					TryBuyFuelNearbyBoat();
				}
				_homeFuelHoldStart = -1f;
				_homeFuelSellSteps = 0;
			}
		}

		private void TrySellFuelNearbyBoat()
		{
			if (!InstanceFinder.IsServerStarted)
			{
				return;
			}
			Boat val = GetLocallyDrivenBoat();
			if ((Object)(object)val == (Object)null)
			{
				val = GetCachedNearbyBoat();
			}
			if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).IsSpawned)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Move close to a boat before holding Home to sell fuel.");
				return;
			}
			float fuel = BoatFuelRouting.GetFuel(val);
			float num = Mathf.Round(fuel * 100f) / 100f;
			if (num < 0.5f)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Boat needs at least {0.5f:0.00} Liter of fuel to sell. Fuel: {num:0.00} / 5 Liter.");
				return;
			}
			float fuel2 = Mathf.Max(0f, num - 0.5f);
			BoatFuelRouting.SetFuel(val, fuel2);
			if ((Object)(object)MoneyManager.Instance != (Object)null)
			{
				MoneyManager.AddMoney(1, Player.LocalPlayer);
			}
			SaveBoats();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Sold {0.5f:0.00} Liter of boat fuel for {1} game money. Fuel: {BoatFuelRouting.GetFuel(val):0.##} / 5 Liter.");
		}

		private void TryBuyFuelNearbyBoat()
		{
			if (!InstanceFinder.IsServerStarted)
			{
				return;
			}
			Boat val = GetLocallyDrivenBoat();
			if ((Object)(object)val == (Object)null)
			{
				val = GetCachedNearbyBoat();
			}
			if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).IsSpawned)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Move close to a boat before tapping Home to buy fuel.");
				return;
			}
			float fuel = BoatFuelRouting.GetFuel(val);
			float num = Mathf.Round(fuel * 100f) / 100f;
			if (num > 4.5f)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Boat fuel is above 4.50 Liter, so no more fuel can be purchased.");
				return;
			}
			if (num >= 5f)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Boat fuel tank is already full at 5 Liter.");
				return;
			}
			if ((Object)(object)MoneyManager.Instance == (Object)null || !MoneyManager.CanAfford(1))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You need 1 game money to buy 0.50 Liter of boat fuel.");
				return;
			}
			int money = MoneyManager.Money;
			MoneyManager.RemoveMoney(1, Player.LocalPlayer);
			int num2 = money - 1;
			if (MoneyManager.Money < num2)
			{
				int num3 = num2 - MoneyManager.Money;
				MoneyManager.AddMoney(num3, (Player)null);
			}
			BoatFuelRouting.SetFuel(val, Mathf.Min(5f, num + 0.5f));
			SaveBoats();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Bought {0.5f:0.##} Liter of boat fuel for {1} game money. Fuel: {BoatFuelRouting.GetFuel(val):0.##} / 5 Liter.");
		}

		private void TryRepairNearbyBoat()
		{
			if (!InstanceFinder.IsServerStarted)
			{
				return;
			}
			Boat val = GetLocallyDrivenBoat();
			if ((Object)(object)val == (Object)null)
			{
				val = GetCachedNearbyBoat();
			}
			if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).IsSpawned)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Move close to a boat before pressing Insert to repair it.");
				return;
			}
			if (BoatDamageRouting.IsSinking(val))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"This boat is already sinking and cannot be repaired.");
				return;
			}
			float health = BoatDamageRouting.GetHealth(val);
			float num = Mathf.Max(0f, 7500f - health);
			if (num <= 0.01f)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Boat health is already 7500. No repair needed and no money was charged.");
				return;
			}
			int boatRepairCost = GetBoatRepairCost(val);
			if ((Object)(object)MoneyManager.Instance == (Object)null || !MoneyManager.CanAfford(boatRepairCost))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"You need {boatRepairCost:N0} game money to repair this boat.");
				return;
			}
			int money = MoneyManager.Money;
			MoneyManager.RemoveMoney(boatRepairCost, Player.LocalPlayer);
			int num2 = money - boatRepairCost;
			if (MoneyManager.Money < num2)
			{
				int num3 = num2 - MoneyManager.Money;
				MoneyManager.AddMoney(num3, (Player)null);
			}
			BoatDamageRouting.SetSavedHealth(val, 7500f);
			SaveBoats();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Boat repaired to 7500 health for {boatRepairCost:N0} game money.");
		}

		private IEnumerator TryPurchaseBoat()
		{
			_purchaseInProgress = true;
			if ((Object)(object)BoatManager.Instance == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Boat manager is not ready. Unlock boats in the base game first.");
				_purchaseInProgress = false;
				yield break;
			}
			if (!InstanceFinder.IsServerStarted)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"The FishNet server is not running. In multiplayer, press F8 on the host.");
				_purchaseInProgress = false;
				yield break;
			}
			if (!IsBaseGameBoatUnlocked())
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Boats are not unlocked yet. Unlock the boat in the base game before using F8.");
				_purchaseInProgress = false;
				yield break;
			}
			Boat[] boatsBefore = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0);
			int purchaseCost = ((boatsBefore.Length != 0) ? 250 : 0);
			Boat steeringBoat = GetLocallyDrivenBoat();
			if ((Object)(object)steeringBoat == (Object)null)
			{
				steeringBoat = GetCachedNearbyBoat();
			}
			if (boatsBefore.Length != 0 && (Object)(object)steeringBoat == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Stand on/next to an existing boat or drive one before pressing F8.");
				_purchaseInProgress = false;
				yield break;
			}
			if (boatsBefore.Length >= 10)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"The maximum of {10} active boats has already been reached.");
				_purchaseInProgress = false;
				yield break;
			}
			if (purchaseCost > 0 && ((Object)(object)MoneyManager.Instance == (Object)null || !MoneyManager.CanAfford(purchaseCost)))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"You need {purchaseCost:N0} game money to buy a boat.");
				_purchaseInProgress = false;
				yield break;
			}
			object prefab = GetBoatPrefab();
			if (prefab == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"The game's boat prefab could not be found.");
				_purchaseInProgress = false;
				yield break;
			}
			Vector3 spawnPosition = GetBoatSpawnPosition(steeringBoat);
			Boat spawnedBoat = TrySpawnExtraBoat(rotation: (!((Object)(object)steeringBoat != (Object)null)) ? SpawnManager.BoatSpawnRot : (((Object)(object)steeringBoat.HiddenPhysicsRig != (Object)null) ? steeringBoat.HiddenPhysicsRig.rotation : ((Component)steeringBoat).transform.rotation), prefab: (Boat)prefab, position: spawnPosition);
			for (int frame = 0; frame < 60; frame++)
			{
				if (IsNetworkSpawned(spawnedBoat))
				{
					break;
				}
				yield return null;
			}
			bool spawned = IsNetworkSpawned(spawnedBoat);
			if (!spawned && (Object)(object)spawnedBoat != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)spawnedBoat).gameObject);
			}
			if (!spawned)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Boat spawn failed. In multiplayer, buy boats on the host. Solo games are supported.");
				_purchaseInProgress = false;
				yield break;
			}
			byte inheritedMotor = (((Object)(object)steeringBoat != (Object)null) ? steeringBoat.MotorIndex : (_save.hasSelectedMotor ? _save.selectedMotorIndex : spawnedBoat.MotorIndex));
			bool inheritedRadarUnlocked = (Object)(object)steeringBoat != (Object)null && BoatGpsRouting.HasRadar(steeringBoat);
			bool inheritedRadarVisible = inheritedRadarUnlocked && BoatGpsRouting.IsVisible(steeringBoat);
			byte inheritedMotorSkin = (((Object)(object)steeringBoat != (Object)null) ? BoatMotorSkinRouting.GetSkinIndex(steeringBoat) : byte.MaxValue);
			byte inheritedMotorFinish = (byte)(((Object)(object)steeringBoat != (Object)null) ? BoatMotorSkinRouting.GetFinishMode(steeringBoat) : (_save.hasSelectedMotorFinish ? _save.selectedMotorFinish : 0));
			byte inheritedRadarSkin = (((Object)(object)steeringBoat != (Object)null) ? BoatRadarSkinRouting.GetSkinIndex(steeringBoat) : byte.MaxValue);
			byte inheritedControlSkin = (byte)(((Object)(object)steeringBoat != (Object)null) ? BoatControlSkinRouting.GetSkinIndex(steeringBoat) : (_save.hasSelectedControlSkin ? _save.selectedControlSkinIndex : 14));
			byte inheritedHullSkin = (byte)(((Object)(object)steeringBoat != (Object)null) ? BoatSkinRouting.GetSkinIndex(steeringBoat) : (_save.hasSelectedSkin ? _save.selectedSkinIndex : 0));
			byte inheritedHullStyle = (byte)(((Object)(object)steeringBoat != (Object)null) ? BoatSkinRouting.GetStyleMode(steeringBoat) : (_save.hasSelectedSkin ? _save.selectedSkinStyleMode : 0));
			byte inheritedStripeSecondary = (byte)(((Object)(object)steeringBoat != (Object)null) ? BoatSkinRouting.GetStripeSecondarySkinIndex(steeringBoat) : (_save.hasSelectedSkin ? _save.selectedStripeSecondarySkinIndex : 14));
			bool inheritedMotorMountUsesControls = (((Object)(object)steeringBoat != (Object)null) ? BoatControlSkinRouting.IsMotorMountUsingControls(steeringBoat) : (_save.hasSelectedMotorMountMode && _save.selectedMotorMountUsesControls));
			Vector3 inheritedRadarOffset = (((Object)(object)steeringBoat != (Object)null) ? BoatGpsRouting.GetRadarOffset(steeringBoat) : Vector3.zero);
			EnsureBoatUnlocked(spawnedBoat);
			BoatGpsRouting.SetRadarUnlocked(spawnedBoat, inheritedRadarUnlocked);
			ActivateBoat(spawnedBoat);
			MotorRouting.ApplyMotor(spawnedBoat, inheritedMotor);
			BoatMotorSkinRouting.SetFinishMode(spawnedBoat, inheritedMotorFinish, reapplySkin: false);
			BoatMotorSkinRouting.ApplySkin(spawnedBoat, inheritedMotorSkin);
			BoatSkinRouting.SetSavedFinish(spawnedBoat, inheritedHullStyle);
			BoatSkinRouting.SetSavedStripeSecondarySkin(spawnedBoat, inheritedStripeSecondary);
			BoatSkinRouting.ApplySkin(spawnedBoat, inheritedHullSkin);
			BoatControlSkinRouting.SetMotorMountMode(spawnedBoat, inheritedMotorMountUsesControls, reapplyControls: false);
			if ((Object)(object)steeringBoat != (Object)null)
			{
				BoatControlSkinRouting.ApplySkin(spawnedBoat, inheritedControlSkin);
			}
			BoatGpsRouting.SetVisible(spawnedBoat, inheritedRadarVisible);
			BoatRadarSkinRouting.ApplySkin(spawnedBoat, inheritedRadarSkin);
			for (int inheritedRadarFrame = 0; inheritedRadarFrame < 120; inheritedRadarFrame++)
			{
				if (BoatGpsRouting.SetRadarOffset(spawnedBoat, inheritedRadarOffset))
				{
					break;
				}
				yield return null;
			}
			if ((Object)(object)steeringBoat != (Object)null)
			{
				for (int skinReadyFrame = 0; skinReadyFrame < 120; skinReadyFrame++)
				{
					if (BoatSkinRouting.AreSkinRenderersReady(spawnedBoat))
					{
						break;
					}
					yield return null;
				}
				BoatSkinRouting.SetSavedFinish(spawnedBoat, inheritedHullStyle);
				BoatSkinRouting.SetSavedStripeSecondarySkin(spawnedBoat, inheritedStripeSecondary);
				BoatSkinRouting.ApplySkin(spawnedBoat, inheritedHullSkin);
				BoatControlSkinRouting.SetMotorMountMode(spawnedBoat, inheritedMotorMountUsesControls, reapplyControls: false);
				BoatControlSkinRouting.ApplySkin(spawnedBoat, inheritedControlSkin);
				byte inheritedSkin = inheritedHullSkin;
				byte inheritedStyle = inheritedHullStyle;
				if ((inheritedStyle == 6 || inheritedStyle == 8) && inheritedSkin > 13)
				{
					for (int specialRetry = 0; specialRetry < 6; specialRetry++)
					{
						for (int settleFrame = 0; settleFrame < 15; settleFrame++)
						{
							yield return null;
						}
						if ((Object)(object)spawnedBoat == (Object)null || !((NetworkBehaviour)spawnedBoat).IsSpawned)
						{
							break;
						}
						BoatSkinRouting.SetSavedFinish(spawnedBoat, inheritedStyle);
						BoatSkinRouting.RebuildSavedSpecialFinishLikeManualSelection(spawnedBoat, inheritedSkin);
					}
				}
			}
			if ((Object)(object)steeringBoat != (Object)null)
			{
				for (int mountSettle = 0; mountSettle < 8; mountSettle++)
				{
					for (int mountWait = 0; mountWait < 5; mountWait++)
					{
						yield return null;
					}
					if ((Object)(object)spawnedBoat == (Object)null || !((NetworkBehaviour)spawnedBoat).IsSpawned)
					{
						break;
					}
					BoatControlSkinRouting.SetMotorMountMode(spawnedBoat, inheritedMotorMountUsesControls, reapplyControls: false);
					BoatControlSkinRouting.ApplySkin(spawnedBoat, inheritedControlSkin);
				}
			}
			BoatSkinRouting.SetSavedFinish(spawnedBoat, inheritedHullStyle);
			BoatSkinRouting.SetSavedStripeSecondarySkin(spawnedBoat, inheritedStripeSecondary);
			BoatSkinRouting.ApplySkin(spawnedBoat, inheritedHullSkin);
			BoatMotorSkinRouting.SetFinishMode(spawnedBoat, inheritedMotorFinish, reapplySkin: false);
			BoatMotorSkinRouting.ApplySkin(spawnedBoat, inheritedMotorSkin);
			BoatControlSkinRouting.SetMotorMountMode(spawnedBoat, inheritedMotorMountUsesControls, reapplyControls: false);
			BoatControlSkinRouting.ApplySkin(spawnedBoat, inheritedControlSkin);
			BoatRadarSkinRouting.ApplySkin(spawnedBoat, inheritedRadarSkin);
			BoatGpsRouting.SetVisible(spawnedBoat, inheritedRadarVisible);
			BoatGpsRouting.SetRadarOffset(spawnedBoat, inheritedRadarOffset);
			RememberSelectedMotor(inheritedMotor, saveImmediately: false);
			RememberSelectedRadar(inheritedRadarVisible, saveImmediately: false);
			RegisterPurchasedBoat(spawnedBoat);
			foreach (KeyValuePair<string, Boat> pair in _savedBoats)
			{
				if ((Object)(object)pair.Value != (Object)(object)spawnedBoat)
				{
					continue;
				}
				BoatSaveRecord newRecord = _save.boats.Find((BoatSaveRecord item) => item != null && item.id == pair.Key);
				if (newRecord != null)
				{
					newRecord.motorIndex = inheritedMotor;
					newRecord.skinIndex = inheritedHullSkin;
					newRecord.skinStyleMode = inheritedHullStyle;
					newRecord.stripeSecondarySkinIndex = inheritedStripeSecondary;
					newRecord.controlSkinIndex = BoatControlSkinRouting.NormalizeSkinIndex(inheritedControlSkin);
					newRecord.motorSkinIndex = BoatMotorSkinRouting.NormalizeSkinIndex(inheritedMotorSkin);
					newRecord.motorFinishMode = BoatMotorSkinRouting.NormalizeFinishMode(inheritedMotorFinish);
					newRecord.radarSkinIndex = BoatRadarSkinRouting.NormalizeSkinIndex(inheritedRadarSkin);
					newRecord.motorMountUsesControls = inheritedMotorMountUsesControls;
					newRecord.radarUnlocked = inheritedRadarUnlocked;
					newRecord.radarVisible = inheritedRadarVisible;
					newRecord.radarOffsetX = inheritedRadarOffset.x;
					newRecord.radarOffsetY = inheritedRadarOffset.y;
					newRecord.radarOffsetZ = inheritedRadarOffset.z;
				}
				break;
			}
			SaveBoats();
			if (purchaseCost > 0)
			{
				int balanceBefore = MoneyManager.Money;
				MoneyManager.RemoveMoney(purchaseCost, Player.LocalPlayer);
				yield return null;
				int expectedBalance = balanceBefore - purchaseCost;
				if (MoneyManager.Money < expectedBalance)
				{
					int refund = expectedBalance - MoneyManager.Money;
					MoneyManager.AddMoney(refund, (Player)null);
					((BaseUnityPlugin)this).Logger.LogWarning((object)$"The game removed too much money; Untamo.Boats refunded {refund:N0}.");
				}
			}
			if ((Object)(object)steeringBoat != (Object)null && (Object)(object)steeringBoat.Driver == (Object)(object)Player.LocalPlayer)
			{
				steeringBoat.TrySetDriver((Player)null);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Boat purchased for {purchaseCost:N0} and spawned near you at {spawnPosition}.");
			_purchaseInProgress = false;
		}

		private static bool IsBaseGameBoatUnlocked()
		{
			Boat boat = BoatManager.Boat;
			if ((Object)(object)boat != (Object)null && boat.BoatUnlocked)
			{
				return true;
			}
			object obj = AccessTools.Property(typeof(SaveManager), "CurServerSave")?.GetValue(null, null);
			if (obj != null && obj.GetType().GetField("UnlockedBoat", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is bool result)
			{
				return result;
			}
			return false;
		}

		private static void EnsureBoatUnlocked(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return;
			}
			try
			{
				boat.UnlockBoat();
			}
			catch
			{
			}
			if (boat.BoatUnlocked)
			{
				return;
			}
			PropertyInfo propertyInfo = AccessTools.Property(typeof(Boat), "BoatUnlocked");
			if (propertyInfo != null && propertyInfo.CanWrite)
			{
				try
				{
					propertyInfo.SetValue(boat, true, null);
				}
				catch
				{
				}
			}
			FieldInfo[] fields = typeof(Boat).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			for (int i = 0; i < fields.Length; i++)
			{
				if (boat.BoatUnlocked)
				{
					break;
				}
				FieldInfo fieldInfo = fields[i];
				string text = fieldInfo.Name.ToLowerInvariant();
				if (!text.Contains("unlock") || text.Contains("radar"))
				{
					continue;
				}
				try
				{
					if (fieldInfo.FieldType == typeof(bool))
					{
						fieldInfo.SetValue(boat, true);
						continue;
					}
					object value = fieldInfo.GetValue(boat);
					PropertyInfo propertyInfo2 = value?.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (propertyInfo2 != null && propertyInfo2.CanWrite && propertyInfo2.PropertyType == typeof(bool))
					{
						propertyInfo2.SetValue(value, true, null);
					}
				}
				catch
				{
				}
			}
		}

		internal static Boat GetLocallyDrivenBoat()
		{
			int frameCount = Time.frameCount;
			if (_drivenBoatCacheFrame == frameCount)
			{
				if ((Object)(object)_cachedLocallyDrivenBoat != (Object)null && (Object)(object)_cachedLocallyDrivenBoat.Driver == (Object)(object)Player.LocalPlayer)
				{
					return _cachedLocallyDrivenBoat;
				}
				if ((Object)(object)_cachedLocallyDrivenBoat == (Object)null)
				{
					return null;
				}
			}
			_drivenBoatCacheFrame = frameCount;
			_cachedLocallyDrivenBoat = null;
			Player localPlayer = Player.LocalPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return null;
			}
			Boat val = (((Object)(object)BoatManager.Instance == (Object)null) ? null : BoatManager.Boat);
			if ((Object)(object)val != (Object)null && (Object)(object)val.Driver == (Object)(object)localPlayer)
			{
				_cachedLocallyDrivenBoat = val;
				return val;
			}
			Boat[] array = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0);
			foreach (Boat val2 in array)
			{
				if ((Object)(object)val2 != (Object)null && (Object)(object)val2.Driver == (Object)(object)localPlayer)
				{
					_cachedLocallyDrivenBoat = val2;
					return val2;
				}
			}
			return null;
		}

		internal static void InvalidateDrivenBoatCache()
		{
			_drivenBoatCacheFrame = -1;
			_cachedLocallyDrivenBoat = null;
		}

		private Boat GetCachedNearbyBoat()
		{
			Player localPlayer = Player.LocalPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				_cachedNearbyBoat = null;
				return null;
			}
			if (Time.unscaledTime >= _nextNearbyBoatRefresh || (Object)(object)_cachedNearbyBoat == (Object)null || !((NetworkBehaviour)_cachedNearbyBoat).IsSpawned || (Object)(object)((Component)_cachedNearbyBoat).gameObject == (Object)null || !((Component)_cachedNearbyBoat).gameObject.activeInHierarchy)
			{
				_nextNearbyBoatRefresh = Time.unscaledTime + 0.15f;
				_cachedNearbyBoat = BoatClosestPhysicsRouting.FindNearestPhysicalBoat(localPlayer, 3.5f);
			}
			return _cachedNearbyBoat;
		}

		private int GetCachedBoatCount()
		{
			if (Time.unscaledTime >= _nextBoatCountRefresh)
			{
				_nextBoatCountRefresh = Time.unscaledTime + 1f;
				_cachedBoatCount = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0).Length;
			}
			return _cachedBoatCount;
		}

		private Boat GetCachedOriginalBoat()
		{
			HashSet<Boat> hashSet = new HashSet<Boat>(_savedBoats.Values);
			if (Time.unscaledTime < _nextOriginalBoatRefresh && (Object)(object)_cachedOriginalBoat != (Object)null && ((NetworkBehaviour)_cachedOriginalBoat).IsSpawned && (Object)(object)((Component)_cachedOriginalBoat).gameObject != (Object)null && ((Component)_cachedOriginalBoat).gameObject.activeInHierarchy && !hashSet.Contains(_cachedOriginalBoat) && !_restoringCustomBoats.Contains(_cachedOriginalBoat) && !IsSavedCustomBoatName(((Object)_cachedOriginalBoat).name))
			{
				return _cachedOriginalBoat;
			}
			_nextOriginalBoatRefresh = Time.unscaledTime + 1f;
			_cachedOriginalBoat = null;
			Boat[] array = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0);
			foreach (Boat val in array)
			{
				if (!((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).IsSpawned && !((Object)(object)((Component)val).gameObject == (Object)null) && ((Component)val).gameObject.activeInHierarchy && !hashSet.Contains(val) && !_restoringCustomBoats.Contains(val) && !IsSavedCustomBoatName(((Object)val).name))
				{
					_cachedOriginalBoat = val;
					return val;
				}
			}
			return null;
		}

		private void SyncDrivenBoatWithManager()
		{
			Boat locallyDrivenBoat = GetLocallyDrivenBoat();
			if (!((Object)(object)locallyDrivenBoat == (Object)null) && !BoatDamageRouting.IsSinking(locallyDrivenBoat) && !((Object)(object)BoatManager.Instance == (Object)null))
			{
				if ((Object)(object)BoatManager.Boat != (Object)(object)locallyDrivenBoat)
				{
					BoatManager.SetBoat(BoatInteractionRouting.GetManagerColliders(locallyDrivenBoat), locallyDrivenBoat);
				}
				_activeBoat = locallyDrivenBoat;
			}
		}

		private void UpdateBoatInventoryControls()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			Boat locallyDrivenBoat = GetLocallyDrivenBoat();
			Player localPlayer = Player.LocalPlayer;
			PlayerInventory val = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.Inventory);
			if ((Object)(object)locallyDrivenBoat == (Object)null || (Object)(object)val == (Object)null || (int)Cursor.lockState != 1)
			{
				return;
			}
			for (int i = 0; i < 8; i++)
			{
				if (Input.GetKeyDown((KeyCode)(49 + i)))
				{
					AccessTools.Method(typeof(PlayerInventory), "LocalTrySelectSlot", (Type[])null, (Type[])null)?.Invoke(val, new object[1] { i });
					break;
				}
			}
			if (!Input.GetKeyDown((KeyCode)98))
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(PlayerInventory), "GetNextAvailableBait", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Server), "ChangeBait", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null || (Object)(object)Server.Instance == (Object)null)
			{
				return;
			}
			try
			{
				byte b = (byte)methodInfo.Invoke(val, new object[1] { false });
				methodInfo2.Invoke(Server.Instance, new object[2] { localPlayer, b });
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not change bait while driving: " + ex.Message));
			}
		}

		private static bool IsHoldKeyTriggered(KeyCode key, ref float holdStart, ref bool triggered, float requiredSeconds)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if (!Input.GetKey(key))
			{
				holdStart = -1f;
				triggered = false;
				return false;
			}
			if (triggered)
			{
				return false;
			}
			if (holdStart < 0f)
			{
				holdStart = Time.unscaledTime;
				return false;
			}
			if (Time.unscaledTime - holdStart < requiredSeconds)
			{
				return false;
			}
			triggered = true;
			return true;
		}

		private void UpdateInteractionRecoveryHold()
		{
			if (!Input.GetKey((KeyCode)101))
			{
				_interactionRecoveryHoldStart = -1f;
				_interactionRecoveryTriggered = false;
			}
			else if (!_interactionRecoveryTriggered)
			{
				if (_interactionRecoveryHoldStart < 0f)
				{
					_interactionRecoveryHoldStart = Time.unscaledTime;
				}
				else if (!(Time.unscaledTime - _interactionRecoveryHoldStart < 3f))
				{
					_interactionRecoveryTriggered = true;
					RecoverBoatInteractionState();
				}
			}
		}

		private void RecoverBoatInteractionState()
		{
			Player localPlayer = Player.LocalPlayer;
			BoatManagerContactGuard.ClearAllExplicitSwitches();
			Boat[] array = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0);
			if ((Object)(object)localPlayer != (Object)null)
			{
				foreach (Boat val in array)
				{
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Driver != (Object)(object)localPlayer))
					{
						StopBoatInputAndMotion(val, stopMotion: false);
						try
						{
							val.TrySetDriver((Player)null);
						}
						catch
						{
						}
					}
				}
			}
			BoatClosestPhysicsRouting.ClearContext(localPlayer);
			_lastLocallyDrivenBoat = null;
			if ((Object)(object)BoatManager.Instance != (Object)null)
			{
				try
				{
					BoatManager.SetBoat(Array.Empty<Collider>(), (Boat)null);
				}
				catch
				{
				}
			}
			BoatInteractionRouting.ForceRefresh();
			BoatClosestPhysicsRouting.ForceRefresh();
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Boat interaction state reset after holding E for 3 seconds.");
		}

		private void MoveRadarAndSave(Boat boat, Vector3 localDelta)
		{
			//IL_0012: 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_001f: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: 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)
			if ((Object)(object)boat == (Object)null)
			{
				return;
			}
			BoatGpsRouting.MoveRadar(boat, localDelta);
			Vector3 radarOffset = BoatGpsRouting.GetRadarOffset(boat);
			bool flag = false;
			foreach (Boat value2 in _savedBoats.Values)
			{
				if ((Object)(object)value2 == (Object)(object)boat)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				_save.hasSelectedRadarPosition = true;
				_save.selectedRadarOffsetX = radarOffset.x;
				_save.selectedRadarOffsetY = radarOffset.y;
				_save.selectedRadarOffsetZ = radarOffset.z;
				_selectedRadarPositionRestored = false;
			}
			for (int i = 0; i < _save.boats.Count; i++)
			{
				if (_savedBoats.TryGetValue(_save.boats[i].id, out var value) && (Object)(object)value == (Object)(object)boat)
				{
					_save.boats[i].radarOffsetX = radarOffset.x;
					_save.boats[i].radarOffsetY = radarOffset.y;
					_save.boats[i].radarOffsetZ = radarOffset.z;
					break;
				}
			}
			SaveBoats();
		}

		private void UpdateBoatMotorAndGpsControls()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Invalid comparison between Unknown and I4
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Invalid comparison between Unknown and I4
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Invalid comparison between Unknown and I4
			Boat locallyDrivenBoat = GetLocallyDrivenBoat();
			Boat val = locallyDrivenBoat;
			if ((Object)(object)val == (Object)null)
			{
				val = GetCachedNearbyBoat();
			}
			if ((int)Cursor.lockState == 1 && Input.GetKeyDown((KeyCode)111) && (Object)(object)val != (Object)null)
			{
				BoatCruiseRouting.Toggle(val, ((BaseUnityPlugin)this).Logger);
			}
			if ((int)Cursor.lockState == 1 && (Object)(object)val != (Object)null && BoatCruiseRouting.EnabledFor(val))
			{
				if (Input.GetKeyDown((KeyCode)280))
				{
					BoatCruiseRouting.AdjustThrottle(val, 0.1f);
				}
				else if (Input.GetKeyDown((KeyCode)281))
				{
					BoatCruiseRouting.AdjustThrottle(val, -0.1f);
				}
			}
			if ((int)Cursor.lockState == 1 && Input.GetKeyDown((KeyCode)112))
			{
				if ((Object)(object)locallyDrivenBoat != (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Follow selection: exit the driver seat and stand on the boat you want to follow you, then press P.");
				}
				else
				{
					Boat boat = BoatClosestPhysicsRouting.FindNearestPhysicalBoat(Player.LocalPlayer, 3.5f);
					BoatFollowRouting.ToggleFollower(boat, ((BaseUnityPlugin)this).Logger);
				}
			}
			if ((int)Cursor.lockState == 1 && (Object)(object)locallyDrivenBoat == (Object)null && (Object)(object)val != (Object)null && BoatCruiseRouting.EnabledFor(val))
			{
				float num = 0f;
				if (Input.GetKey((KeyCode)276))
				{
					num -= 1f;
				}
				if (Input.GetKey((KeyCode)275))
				{
					num += 1f;
				}
				if (!Mathf.Approximately(num, 0f))
				{
					BoatCruiseRouting.AdjustAutopilotHeading(val, num * 55f * Time.unscaledDeltaTime);
				}
			}
			if ((int)Cursor.lockState != 1)
			{
				return;
			}
			Boat val2 = (((Object)(object)locallyDrivenBoat != (Object)null) ? locallyDrivenBoat : GetCachedNearbyBoat());
			if ((Object)(object)val2 != (Object)null)
			{
				if (Input.GetKeyDown((KeyCode)291))
				{
					BoatSkinRouting.DumpCustomSkinTargets(val2, ((BaseUnityPlugin)this).Logger);
				}
				MotorRouting.ObservePurchasedMotor(val2);
				int num2 = ((!Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)109)) ? ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1)) : 0);
				if (num2 != 0 && MotorRouting.TryGetCycledMotor(val2, num2, out var motorIndex))
				{
					if (InstanceFinder.IsServerStarted)
					{
						if (MotorRouting.ApplyMotor(val2, motorIndex))
						{
							RememberSelectedMotor(motorIndex, saveImmediately: true);
							((MonoBehaviour)this).StartCoroutine(ReapplyControlsAfterMotorChange(val2));
							SaveBoats();
						}
					}
					else if ((Object)(object)Server.Instance != (Object)null && (Object)(object)val2 == (Object)(object)locallyDrivenBoat)
					{
						RememberSelectedMotor(motorIndex, saveImmediately: true);
						Server.Instance.SetBoatSkin(MotorRouting.EncodeMotorRequest(motorIndex));
						((MonoBehaviour)this).StartCoroutine(ReapplyControlsAfterMotorChange(val2));
					}
				}
				if (Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)109))
				{
					int direction = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatMotorSkinRouting.TryGetCycledSkin(val2, direction, out var skinIndex) && BoatMotorSkinRouting.ApplySkin(val2, skinIndex))
					{
						BoatControlSkinRouting.ApplySkin(val2, BoatControlSkinRouting.GetSkinIndex(val2));
						RememberSelectedMotorSkin(val2);
						SaveBoats();
					}
				}
				if (Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)106))
				{
					int direction2 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatMotorSkinRouting.ToggleFinish(val2, direction2))
					{
						RememberSelectedMotorSkin(val2);
						RememberMotorFinish(val2);
						SaveBoats();
					}
				}
				if (Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)117) && !MotorRouting.HasNoMotor(val2) && BoatControlSkinRouting.ToggleMotorMountMode(val2))
				{
					RememberMotorMountMode(val2);
					SaveBoats();
				}
				if (!Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)106))
				{
					int direction3 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatSkinRouting.ToggleFinish(val2, direction3, ((BaseUnityPlugin)this).Logger))
					{
						RememberSelectedSkin(val2);
						SaveBoats();
					}
				}
				if (Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)107))
				{
					int direction4 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatControlSkinRouting.TryGetCycledSkin(val2, direction4, out var skinIndex2) && BoatControlSkinRouting.ApplySkin(val2, skinIndex2))
					{
						RememberSelectedControlSkin(val2);
						RememberCustomBoatControlSkin(val2, skinIndex2);
						SaveBoats();
					}
				}
				if (!Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)107))
				{
					int direction5 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatSkinRouting.TryGetCycledSkin(val2, direction5, out var skinIndex3))
					{
						BoatSkinRouting.ApplySkin(val2, skinIndex3);
						RememberSelectedSkin(val2);
						SaveBoats();
					}
				}
				if (!Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)59))
				{
					int direction6 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
					if (BoatSkinRouting.ToggleStripeSecondaryColor(val2, direction6, ((BaseUnityPlugin)this).Logger))
					{
						RememberSelectedSkin(val2);
						SaveBoats();
					}
				}
			}
			if ((Object)(object)val2 != (Object)null && Input.GetKeyDown((KeyCode)279))
			{
				BoatAnchorRouting.Toggle(val2, ((BaseUnityPlugin)this).Logger);
			}
			if ((Object)(object)val2 != (Object)null && Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)103))
			{
				int direction7 = ((!Input.GetKey((KeyCode)304) && !Input.GetKey((KeyCode)303)) ? 1 : (-1));
				if (BoatRadarSkinRouting.TryGetCycledSkin(val2, direction7, out var skinIndex4) && BoatRadarSkinRouting.ApplySkin(val2, skinIndex4))
				{
					RememberSelectedRadarSkin(val2);
					RememberCustomBoatRadarSkin(val2, skinIndex4);
					SaveBoats();
				}
			}
			if ((Object)(object)locallyDrivenBoat == (Object)null)
			{
				return;
			}
			if (!Input.GetKey((KeyCode)308) && Input.GetKeyDown((KeyCode)103))
			{
				BoatGpsRouting.Toggle(locallyDrivenBoat, ((BaseUnityPlugin)this).Logger);
				RememberSelectedRadar(BoatGpsRouting.IsVisible(locallyDrivenBoat), saveImmediately: true);
			}
			if (Input.GetKeyDown((KeyCode)104))
			{
				BoatHornRouting.Play(locallyDrivenBoat, ((BaseUnityPlugin)this).Logger);
			}
			if (((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && Input.GetKeyDown((KeyCode)114)) || ((Input.GetKeyDown((KeyCode)306) || Input.GetKeyDown((KeyCode)305)) && Input.GetKey((KeyCode)114)))
			{
				if (InstanceFinder.IsServerStarted)
				{
					BoatCommandRouting.ResetToDefaultSpawn(locallyDrivenBoat);
				}
				else if ((Object)(object)Server.Instance != (Object)null)
				{
					Server.Instance.SetBoatSkin((byte)239);
				}
			}
		}

		private void UpdateBoatBoostSelection()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			Boat locallyDrivenBoat = GetLocallyDrivenBoat();
			Boat val = (((Object)(object)locallyDrivenBoat != (Object)null) ? locallyDrivenBoat : GetCachedNearbyBoat());
			if ((Object)(object)val == (Object)null)
			{
				if ((Object)(object)_boostHudObject != (Object)null)
				{
					_boostHudObject.SetActive(false);
				}
				return;
			}
			EnsureBoostHud();
			if ((Object)(object)_boostHudObject != (Object)null)
			{
				_boostHudObject.SetActive(true);
			}
			if (Time.unscaledTime >= _nextBoostHudRefresh)
			{
				_nextBoostHudRefresh = Time.unscaledTime + 0.2f;
				SetBoostHudText(val);
			}
			if (!((Object)(object)locallyDrivenBoat == (Object)null))
			{
				float y = Input.mouseScrollDelta.y;
				if (!Mathf.Approximately(y, 0f))
				{
					float num = ((y > 0f) ? 0.25f : (-0.25f));
					BoatBoostRouting.SelectedMultiplier = Mathf.Clamp(BoatBoostRouting.SelectedMultiplier + num, 1.25f, 5f);
					_nextBoostHudRefresh = Time.unscaledTime + 0.2f;
					SetBoostHudText(val);
				}
			}
		}

		private void EnsureBoostHud()
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			//IL_0132: 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)
			//IL_0161: Expected O, but got Unknown
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Expected O, but got Unknown
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Expected O, but got Unknown
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Expected O, but got Unknown
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_048c: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_052a: Unknown result type (might be due to invalid IL or missing references)
			//IL_054b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_boostHudObject != (Object)null && (Object)(object)_boostTextComponent != (Object)null)
			{
				return;
			}
			Type type = FindRuntimeType("UnityEngine.Canvas");
			Type type2 = FindRuntimeType("UnityEngine.UI.CanvasScaler");
			Type type3 = FindRuntimeType("UnityEngine.UI.Text");
			Type type4 = FindRuntimeType("UnityEngine.UI.Shadow");
			Component val = FindNativeDamageTextTemplate();
			Type type5 = (((Object)(object)val != (Object)null) ? ((object)val).GetType() : type3);
			if (type == null || type2 == null || type5 == null)
			{
				return;
			}
			_boostHudObject = new GameObject("Untamo Boat Boost HUD");
			_boostHudObject.transform.SetParent((Transform)null, false);
			Object.DontDestroyOnLoad((Object)(object)_boostHudObject);
			Component target = _boostHudObject.AddComponent(type);
			SetEnumProperty(target, "renderMode", "ScreenSpaceOverlay");
			SetRuntimeProperty(target, "sortingOrder", 4000);
			Component target2 = _boostHudObject.AddComponent(type2);
			SetEnumProperty(target2, "uiScaleMode", "ScaleWithScreenSize");
			SetRuntimeProperty(target2, "referenceResolution", (object)new Vector2(1920f, 1080f));
			GameObject val2 = new GameObject("Boat Boost Panel", new Type[1] { typeof(RectTransform) });
			val2.transform.SetParent(_boostHudObject.transform, false);
			RectTransform val3 = (RectTransform)val2.transform;
			val3.anchorMin = new Vector2(0f, 1f);
			val3.anchorMax = new Vector2(0f, 1f);
			val3.pivot = new Vector2(0f, 1f);
			val3.anchoredPosition = new Vector2(20f, -20f);
			val3.sizeDelta = new Vector2(760f, 300f);
			GameObject val4 = new GameObject("Boat Boost Text", new Type[1] { typeof(RectTransform) });
			val4.transform.SetParent(val2.transform, false);
			RectTransform val5 = (RectTransform)val4.transform;
			val5.anchorMin = new Vector2(0f, 1f);
			val5.anchorMax = Vector2.one;
			val5.pivot = new Vector2(0f, 1f);
			val5.anchoredPosition = Vector2.zero;
			val5.sizeDelta = new Vector2(760f, 290f);
			_boostTextComponent = val4.AddComponent(type5);
			if ((Object)(object)val != (Object)null)
			{
				CopyNativeDamageTextFont(val, _boostTextComponent);
			}
			else
			{
				object obj = FindNativeGameUiFont();
				if (obj == null)
				{
					obj = typeof(Resources).GetMethod("GetBuiltinResource", new Type[2]
					{
						typeof(Type),
						typeof(string)
					})?.Invoke(null, new object[2]
					{
						FindRuntimeType("UnityEngine.Font"),
						"Arial.ttf"
					});
				}
				SetRuntimeProperty(_boostTextComponent, "font", obj);
			}
			if (((object)_boostTextComponent).GetType().FullName != null && ((object)_boostTextComponent).GetType().FullName.StartsWith("TMPro.", StringComparison.Ordinal))
			{
				SetRuntimeProperty(_boostTextComponent, "fontSize", 16f);
				SetEnumProperty(_boostTextComponent, "fontStyle", "Bold");
				SetEnumProperty(_boostTextComponent, "alignment", "TopLeft");
				SetRuntimeProperty(_boostTextComponent, "color", Color.white);
				SetRuntimeProperty(_boostTextComponent, "enableWordWrapping", false);
				SetRuntimeProperty(_boostTextComponent, "overflowMode", ParseEnumValue(_boostTextComponent, "overflowMode", "Overflow"));
			}
			else
			{
				SetRuntimeProperty(_boostTextComponent, "fontSize", 16);
				SetEnumProperty(_boostTextComponent, "fontStyle", "Bold");
				SetEnumProperty(_boostTextComponent, "alignment", "UpperLeft");
				SetRuntimeProperty(_boostTextComponent, "color", Color.white);
				SetEnumProperty(_boostTextComponent, "horizontalOverflow", "Overflow");
				SetEnumProperty(_boostTextComponent, "verticalOverflow", "Overflow");
			}
			SetRuntimeProperty(_boostTextComponent, "richText", true);
			SetRuntimeProperty(_boostTextComponent, "supportRichText", true);
			if (type4 != null)
			{
				Component target3 = val4.AddComponent(type4);
				SetRuntimeProperty(target3, "effectColor", (object)new Color(0f, 0f, 0f, 0.6f));
				SetRuntimeProperty(target3, "effectDistance", (object)new Vector2(1.5f, -1.5f));
			}
			Boat val6 = GetLocallyDrivenBoat();
			if ((Object)(object)val6 == (Object)null)
			{
				val6 = GetCachedNearbyBoat();
			}
			if ((Object)(object)val6 != (Object)null)
			{
				SetBoostHudText(val6);
			}
		}

		private static string GetFleetDriverNameFromSteamId(Player driver)
		{
			if ((Object)(object)driver == (Object)null)
			{
				return "Player";
			}
			try
			{
				Type type = ((object)driver).GetType();
				object obj = null;
				PropertyInfo property = type.GetProperty("SteamID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					obj = property.GetValue(driver, null);
				}
				if (obj == null)
				{
					FieldInfo field = type.GetField("_steamID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (field != null)
					{
						obj = field.GetValue(driver);
					}
				}
				if (obj == null)
				{
					return "Player";
				}
				Type type2 = Type.GetType("Steamworks.SteamFriends, com.rlabrecque.steamworks.net", throwOnError: false);
				if (type2 == null)
				{
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					foreach (Assembly assembly in assemblies)
					{
						if (!(assembly == null))
						{
							type2 = assembly.GetType("Steamworks.SteamFriends", throwOnError: false);
							if (type2 != null)
							{
								break;
							}
						}
					}
				}
				if (type2 == null)
				{
					return "Player";
				}
				MethodInfo[] methods = type2.GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo == null || methodInfo.Name != "GetFriendPersonaName")
					{
						continue;
					}
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters == null || parameters.Length != 1)
					{
						continue;
					}
					Type parameterType = parameters[0].ParameterType;
					object obj2 = obj;
					if (!parameterType.IsInstanceOfType(obj2))
					{
						ulong num;
						try
						{
							num = Convert.ToUInt64(obj, CultureInfo.InvariantCulture);
						}
						catch
						{
							PropertyInfo property2 = obj.GetType().GetProperty("m_SteamID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
							if (property2 == null)
							{
								continue;
							}
							num = Convert.ToUInt64(property2.GetValue(obj, null), CultureInfo.InvariantCulture);
						}
						try
						{
							obj2 = Activator.CreateInstance(parameterType, num);
						}
						catch
						{
							continue;
						}
					}
					string text = methodInfo.Invoke(null, new object[1] { obj2 }) as string;
					if (string.IsNullOrWhiteSpace(text))
					{
						continue;
					}
					return text.Trim();
				}
			}
			catch
			{
			}
			return "Player";
		}

		private static string GetFleetFuelHudText(Boat boat)
		{
			float fuel = BoatFuelRouting.GetFuel(boat);
			string text = "Fuel: " + fuel.ToString("0.00", CultureInfo.InvariantCulture) + " L";
			if (fuel >= 0.2f)
			{
				return text;
			}
			if (fuel <= 0.0001f)
			{
				return (((int)(Time.unscaledTime * 4f) & 1) == 0) ? ("<color=#FF3030>" + text + "</color>") : ("<color=#FF6B6B>" + text + "</color>");
			}
			if (((int)(Time.unscaledTime * 4f) & 1) != 0)
			{
				return text;
			}
			return "<color=#FF6B6B>" + text + "</color>";
		}

		private void SetBoostHudText(Boat selectedBoat)
		{
			if ((Object)(object)selectedBoat == (Object)null)
			{
				return;
			}
			int cachedBoatCount = GetCachedBoatCount();
			string boatDisplayName = GetBoatDisplayName(selectedBoat);
			string visibilityText = BoatGpsRouting.GetVisibilityText(selectedBoat);
			string text = (BoatFollowRouting.IsSelectedFollower(selectedBoat) ? " | Follow Selected" : string.Empty);
			string stateText = BoatAnchorRouting.GetStateText(selectedBoat);
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("\nFleet:");
			Boat cachedOriginalBoat = GetCachedOriginalBoat();
			if ((Object)(object)cachedOriginalBoat != (Object)null)
			{
				string value = (BoatDamageRouting.IsSinking(cachedOriginalBoat) ? "Sinking" : (BoatFollowRouting.IsSelectedFollower(cachedOriginalBoat) ? "Follow" : (BoatCruiseRouting.EnabledFor(cachedOriginalBoat) ? "Cruise" : ((!((Object)(object)cachedOriginalBoat.Driver != (Object)null)) ? "Idle" : ("Driving/" + GetFleetDriverNameFromSteamId(cachedOriginalBoat.Driver))))));
				stringBuilder.Append("\n");
				stringBuilder.Append(GetFleetBoatNameRichText(cachedOriginalBoat, "Original Boat"));
				stringBuilder.Append(" | ");
				stringBuilder.Append(value);
				stringBuilder.Append(" | ");
				stringBuilder.Append(GetFleetFuelHudText(cachedOriginalBoat));
				stringBuilder.Append(" | Health: ");
				stringBuilder.Append(BoatDamageRouting.GetHealthText(cachedOriginalBoat));
			}
			if (_save != null && _save.boats != null)
			{
				List<BoatSaveRecord> list = new List<BoatSaveRecord>();
				for (int i = 0; i < _save.boats.Count; i++)
				{
					BoatSaveRecord boatSaveRecord = _save.boats[i];
					if (boatSaveRecord != null)
					{
						Boat value2 = null;
						_savedBoats.TryGetValue(boatSaveRecord.id, out value2);
						if (!((Object)(object)value2 == (Object)null) && ((NetworkBehaviour)value2).IsSpawned && !((Object)(object)((Component)value2).gameObject == (Object)null) && ((Component)value2).gameObject.activeInHierarchy)
						{
							list.Add(boatSaveRecord);
						}
					}
				}
				list.Sort(delegate(BoatSaveRecord left, BoatSaveRecord right)
				{
					string strA = ((left == null || string.IsNullOrWhiteSpace(left.boatName)) ? string.Empty : left.boatName);
					string strB = ((right == null || string.IsNullOrWhiteSpace(right.boatName)) ? string.Empty : right.boatName);
					return string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
				});
				for (int num = 0; num < list.Count; num++)
				{
					BoatSaveRecord boatSaveRecord2 = list[num];
					if (boatSaveRecord2 != null)
					{
						Boat value3 = null;
						_savedBoats.TryGetValue(boatSaveRecord2.id, out value3);
						if (!((Object)(object)value3 == (Object)null) && ((NetworkBehaviour)value3).IsSpawned && !((Object)(object)((Component)value3).gameObject == (Object)null) && ((Component)value3).gameObject.activeInHierarchy)
						{
							string value4 = (BoatDamageRouting.IsSinking(value3) ? "Sinking" : (BoatFollowRouting.IsSelectedFollower(value3) ? "Follow" : (BoatCruiseRouting.EnabledFor(value3) ? "Cruise" : ((!((Object)(object)value3.Driver != (Object)null)) ? "Idle" : ("Driving/" + GetFleetDriverNameFromSteamId(value3.Driver))))));
							string boatName = (string.IsNullOrWhiteSpace(boatSaveRecord2.boatName) ? $"Custom Boat {num + 1}" : boatSaveRecord2.boatName);
							stringBuilder.Append("\n");
							stringBuilder.Append(GetFleetBoatNameRichText(value3, boatName));
							stringBuilder.Append(" | ");
							stringBuilder.Append(value4);
							stringBuilder.Append(" | ");
							stringBuilder.Append(GetFleetFuelHudText(value3));
							stringBuilder.Append(" | Health: ");
							stringBuilder.Append(BoatDamageRouting.GetHealthText(value3));
						}
					}
				}
			}
			SetRuntimeProperty(_boostTextComponent, "text", boatDisplayName + " | " + MotorRouting.GetDisplayName(selectedBoat) + " | Cruise " + BoatCruiseRouting.GetStateText(selectedBoat) + text + " | Anchor " + stateText + "\nBoat: Skin " + BoatSkinRouting.GetSkinDisplayName(selectedBoat) + BoatSkinRouting.GetStripeSecondaryHudText(selectedBoat) + BoatSkinRouting.GetFinishHudText(selectedBoat) + " | Motor: " + BoatMotorSkinRouting.GetHudDisplayName(selectedBoat) + " | Radar: " + BoatRadarSkinRouting.GetSkinDisplayName(selectedBoat) + " | Controls: Skin " + BoatControlSkinRouting.GetSkinDisplayName(selectedBoat) + "\n" + $"Boat Boost: {BoatBoostRouting.SelectedMultiplier:0.##}x | Speed: {BoatSpeedRouting.GetSpeedText(selectedBoat)} | Cruise Power: {BoatCruiseRouting.GetThrottleText(selectedBoat)}\n" + $"{cachedBoatCount}/{10} Boats" + stringBuilder.ToString());
		}

		private static string GetFleetBoatNameRichText(Boat boat, string boatName)
		{
			//IL_0089: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(boatName) || (Object)(object)boat == (Object)null)
			{
				return boatName ?? string.Empty;
			}
			if (BoatSkinRouting.IsRainbowSkin(boat))
			{
				return BoatFleetTextEffectRouting.BuildDripText(boatName);
			}
			if (!BoatSkinRouting.TryGetHudSkinColor(BoatSkinRouting.GetSkinIndex(boat), out var color))
			{
				return boatName;
			}
			if (BoatSkinRouting.IsStripeStyle(boat))
			{
				if (!BoatSkinRouting.TryGetHudSkinColor(BoatSkinRouting.GetStripeSecondarySkinIndex(boat), out var color2))
				{
					color2 = color;
				}
				return BoatFleetTextEffectRouting.BuildAlternatingText(boatName, color, color2);
			}
			return BoatFleetTextEffectRouting.WrapColor(boatName, color);
		}

		internal string GetBoatDisplayName(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return "Boat";
			}
			foreach (KeyValuePair<string, Boat> pair in _savedBoats)
			{
				if (!((Object)(object)pair.Value != (Object)(object)boat))
				{
					BoatSaveRecord boatSaveRecord = _save.boats.Find((BoatSaveRecord item) => item.id == pair.Key);
					if (boatSaveRecord != null && !string.IsNullOrWhiteSpace(boatSaveRecord.boatName))
					{
						return boatSaveRecord.boatName;
					}
				}
			}
			return "Original Boat";
		}

		private static Type FindRuntimeType(string fullName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				Type type = assemblies[i].GetType(fullName, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private static Component FindNativeDamageTextTemplate()
		{
			Component[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll<Component>();
			}
			catch
			{
				array = null;
			}
			if (array == null)
			{
				return null;
			}
			Component result = null;
			int num = 0;
			foreach (Component val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null)
				{
					continue;
				}
				PropertyInfo property = ((object)val).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				PropertyInfo property2 = ((object)val).GetType().GetProperty("font", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property == null || !property.CanWrite || property2 == null || !property2.CanRead)
				{
					continue;
				}
				int num2 = ScoreDamageTextComponent(val);
				if (num2 > num)
				{
					object obj2;
					try
					{
						obj2 = property2.GetValue(val, null);
					}
					catch
					{
						obj2 = null;
					}
					if (obj2 != null)
					{
						num = num2;
						result = val;
					}
				}
			}
			return result;
		}

		private static int ScoreDamageTextComponent(Component component)
		{
			if ((Object)(object)component == (Object)null)
			{
				return 0;
			}
			int num = 0;
			string text = ((object)component).GetType().Name.ToLowerInvariant();
			if (text.Contains("textmeshpro"))
			{
				num += 10;
			}
			Transform val = component.transform;
			int num2 = 0;
			while ((Object)(object)val != (Object)null && num2 < 8)
			{
				string text2 = (((Object)(object)((Component)val).gameObject == (Object)null) ? string.Empty : ((Object)((Component)val).gameObject).name.ToLowerInvariant());
				if (text2.Contains("damagenumber") || text2.Contains("damage number"))
				{
					num += 500;
				}
				if (text2.Contains("damage"))
				{
					num += 220;
				}
				if (text2.Contains("onhit"))
				{
					num += 300;
				}
				if (text2.Contains("hitmarker") || text2.Contains("hit marker"))
				{
					num += 180;
				}
				Component[] array;
				try
				{
					array = ((Component)val).GetComponents<Component>();
				}
				catch
				{
					array = null;
				}
				if (array != null)
				{
					foreach (Component val2 in array)
					{
						if (!((Object)(object)val2 == (Object)null))
						{
							string text3 = ((object)val2).GetType().Name.ToLowerInvariant();
							if (text3 == "onhitui")
							{
								num += 700;
							}
							else if (text3.Contains("damagenumber"))
							{
								num += 600;
							}
							else if (text3.Contains("hitmarker"))
							{
								num += 300;
							}
						}
					}
				}
				val = val.parent;
				num2++;
			}
			return num;
		}

		private static void CopyNativeDamageTextFont(Component source, Component destination)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)destination == (Object)null)
			{
				return;
			}
			string[] array = new string[3] { "font", "fontSharedMaterial", "fontMaterial" };
			foreach (string name in array)
			{
				PropertyInfo property = ((object)source).GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				PropertyInfo property2 = ((object)destination).GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property == null || property2 == null || !property.CanRead || !property2.CanWrite)
				{
					continue;
				}
				try
				{
					object value = property.GetValue(source, null);
					if (value != null && property2.PropertyType.IsInstanceOfType(value))
					{
						property2.SetValue(destination, value, null);
					}
				}
				catch
				{
				}
			}
		}

		private static object FindNativeGameUiFont()
		{
			Component val = FindNativeDamageTextTemplate();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			PropertyInfo property = ((object)val).GetType().GetProperty("font", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null || !property.CanRead)
			{
				return null;
			}
			try
			{
				return property.GetValue(val, null);
			}
			catch
			{
				return null;
			}
		}

		private static object ParseEnumValue(object target, string propertyName, string value)
		{
			if (target == null)
			{
				return null;
			}
			PropertyInfo property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null || !property.PropertyType.IsEnum)
			{
				return null;
			}
			try
			{
				return Enum.Parse(property.PropertyType, value, ignoreCase: true);
			}
			catch
			{
				return Activator.CreateInstance(property.PropertyType);
			}
		}

		private static void SetRuntimeProperty(object target, string name, object value)
		{
			if (target == null || value == null)
			{
				return;
			}
			PropertyInfo property = target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null || !property.CanWrite)
			{
				return;
			}
			try
			{
				object obj = value;
				if (!property.PropertyType.IsInstanceOfType(obj) && obj is IConvertible && typeof(IConvertible).IsAssignableFrom(property.PropertyType))
				{
					obj = Convert.ChangeType(obj, property.PropertyType, CultureInfo.InvariantCulture);
				}
				if (obj != null && property.PropertyType.IsInstanceOfType(obj))
				{
					property.SetValue(target, obj, null);
				}
			}
			catch
			{
			}
		}

		private static void SetEnumProperty(object target, string name, string value)
		{
			if (target != null)
			{
				PropertyInfo property = target.GetType().GetProperty(name);
				if (!(property == null) && property.CanWrite && property.PropertyType.IsEnum)
				{
					property.SetValue(target, Enum.Parse(property.PropertyType, value, ignoreCase: true), null);
				}
			}
		}

		private void UpdateHeldItemBoatCollisions()
		{
			Boat locallyDrivenBoat = GetLocallyDrivenBoat();
			Player localPlayer = Player.LocalPlayer;
			PlayerHolding val = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.Holding);
			Component val2 = (Component)(object)(((Object)(object)val == (Object)null) ? null : val.HeldItem);
			if ((Object)(object)locallyDrivenBoat == (Object)(object)_collisionIgnoredBoat && (Object)(object)val2 == (Object)(object)_collisionIgnoredItem)
			{
				return;
			}
			RestoreHeldItemCollisions();
			if ((Object)(object)locallyDrivenBoat == (Object)null || (Object)(object)val2 == (Object)null)
			{
				return;
			}
			Collider[] componentsInChildren = val2.GetComponentsInChildren<Collider>();
			Collider[] managerColliders = BoatInteractionRouting.GetManagerColliders(locallyDrivenBoat);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if ((Object)(object)componentsInChildren[i] == (Object)null)
				{
					continue;
				}
				for (int j = 0; j < managerColliders.Length; j++)
				{
					if (!((Object)(object)managerColliders[j] == (Object)null))
					{
						Physics.IgnoreCollision(componentsInChildren[i], managerColliders[j], true);
						_ignoredHeldItemCollisions.Add(new CollisionPair(componentsInChildren[i], managerColliders[j]));
					}
				}
			}
			_collisionIgnoredItem = val2;
			_collisionIgnoredBoat = locallyDrivenBoat;
		}

		private void RestoreHeldItemCollisions()
		{
			for (int i = 0; i < _ignoredHeldItemCollisions.Count; i++)
			{
				CollisionPair collisionPair = _ignoredHeldItemCollisions[i];
				if ((Object)(object)collisionPair.Item != (Object)null && (Object)(object)collisionPair.Boat != (Object)null)
				{
					Physics.IgnoreCollision(collisionPair.Item, collisionPair.Boat, false);
				}
			}
			_ignoredHeldItemCollisions.Clear();
			_collisionIgnoredItem = null;
			_collisionIgnoredBoat = null;
		}

		internal int GetBoatRepairCost(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return 0;
			}
			float health = BoatDamageRouting.GetHealth(boat);
			float num = Mathf.Max(0f, 7500f - health);
			if (num <= 0.01f)
			{
				return 0;
			}
			bool flag = true;
			foreach (KeyValuePair<string, Boat> savedBoat in _savedBoats)
			{
				if ((Object)(object)savedBoat.Value == (Object)(object)boat)
				{
					flag = false;
					break;
				}
			}
			int num2 = (flag ? 190 : 240);
			return Mathf.Clamp(Mathf.CeilToInt(num / 7500f * (float)num2), 1, num2);
		}

		internal int GetBoatSellValue(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return 0;
			}
			bool flag = false;
			foreach (KeyValuePair<string, Boat> savedBoat in _savedBoats)
			{
				if ((Object)(object)savedBoat.Value == (Object)(object)boat)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return 0;
			}
			float num = Mathf.Clamp(BoatDamageRouting.GetHealth(boat), 0f, 7500f);
			if (num <= 0f)
			{
				return 1;
			}
			return Mathf.Clamp(Mathf.CeilToInt(num / 7500f * 225f), 1, 225);
		}

		internal static int GetFuelSellRefundValue(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return 0;
			}
			float fuel = BoatFuelRouting.GetFuel(boat);
			float num = Mathf.Round(fuel * 100f) / 100f;
			int num2 = Mathf.FloorToInt(num / 0.5f);
			return Mathf.Max(0, num2);
		}

		private void RemoveCurrentBoat()
		{
			Boat val = GetLocallyDrivenBoat();
			if ((Object)(object)val == (Object)null)
			{
				val = GetCachedNearbyBoat();
			}
			if ((Object)(object)val == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Delete ignored: drive a boat or stand on/next to one.");
				return;
			}
			if (!IsBaseGameBoatUnlocked())
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Boats are not unlocked yet. Delete cannot remove boats until the base game boat is unlocked.");
				return;
			}
			string text = null;
			foreach (KeyValuePair<string, Boat> savedBoat in _savedBoats)
			{
				if ((Object)(object)savedBoat.Value == (Object)(object)val)
				{
					text = savedBoat.Key;
					break;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"The original/base-game boat cannot be sold or deleted.");
				return;
			}
			int boatSellValue = GetBoatSellValue(val);
			int fuelSellRefundValue = GetFuelSellRefundValue(val);
			int num = boatSellValue + fuelSellRefundValue;
			_savedBoats.Remove(text);
			for (int num2 = _save.boats.Count - 1; num2 >= 0; num2--)
			{
				if (_save.boats[num2].id == text)
				{
					_save.boats.RemoveAt(num2);
				}
			}
			_nextBoatName = GetNextCustomBoatName();
			RestoreHeldItemCollisions();
			BoatGpsRouting.ClearExplicitRadarOwnership(val);
			BoatFollowRouting.ClearForBoat(val);
			BoatSkinRouting.ClearForBoat(val);
			BoatControlSkinRouting.ClearForBoat(val);
			BoatManagerContactGuard.ClearExplicitSwitch(val);
			try
			{
				if ((Object)(object)val.Driver == (Object)(object)Player.LocalPlayer)
				{
					val.TrySetDriver((Player)null);
				}
			}
			catch
			{
			}
			if ((Object)(object)BoatManager.Instance != (Object)null && (Object)(object)BoatManager.Boat == (Object)(object)val)
			{
				try
				{
					BoatManager.SetBoat(Array.Empty<Collider>(), (Boat)null);
				}
				catch
				{
				}
			}
			StopBoatInputAndMotion(val, stopMotion: false);
			if (InstanceFinder.IsServerStarted && ((NetworkBehaviour)val).IsSpawned)
			{
				try
				{
					InstanceFinder.ServerManager.Despawn(((Component)val).gameObject, (DespawnType?)null);
				}
				catch
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
			}
			else if ((Object)(object)((Component)val).gameObject != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)val).gameObject);
			}
			_lastLocallyDrivenBoat = null;
			_activeBoat = null;
			BoatClosestPhysicsRouting.ClearContext(Player.LocalPlayer);
			BoatInteractionRouting.ForceRefresh();
			BoatClosestPhysicsRouting.ForceRefresh();
			SaveBoats();
			MoneyManager.AddMoney(num, Player.LocalPlayer);
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Sold the currently driven boat for {boatSellValue} plus {fuelSellRefundValue} fuel money = {num} total.");
		}

		private void RemoveAllBoats()
		{
			if (!IsBaseGameBoatUnlocked())
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Boats are not unlocked yet. F9 cannot remove boats until the base game boat is unlocked.");
				return;
			}
			List<Boat> list = new List<Boat>();
			foreach (KeyValuePair<string, Boat> savedBoat in _savedBoats)
			{
				Boat value = savedBoat.Value;
				if ((Object)(object)value != (Object)null)
				{
					list.Add(value);
				}
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < list.Count; i++)
			{
				Boat val = list[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				bool flag = ((NetworkBehaviour)val).IsSpawned && (Object)(object)((Component)val).gameObject != (Object)null && ((Component)val).gameObject.activeInHierarchy;
				int num3 = (flag ? GetBoatSellValue(val) : 0);
				int num4 = (flag ? GetFuelSellRefundValue(val) : 0);
				BoatGpsRouting.ClearExplicitRadarOwnership(val);
				BoatManagerContactGuard.ClearExplicitSwitch(val);
				try
				{
					if ((Object)(object)val.Driver == (Object)(object)Player.LocalPlayer)
					{
						val.TrySetDriver((Player)null);
					}
				}
				catch
				{
				}
				StopBoatInputAndMotion(val, stopMotion: false);
				if (InstanceFinder.IsServerStarted && ((NetworkBehaviour)val).IsSpawned)
				{
					try
					{
						InstanceFinder.ServerManager.Despawn(((Component)val).gameObject, (DespawnType?)null);
					}
					catch
					{
						if ((Object)(object)((Component)val).gameObject != (Object)null)
						{
							Object.Destroy((Object)(object)((Component)val).gameObject);
						}
					}
				}
				else if ((Object)(object)((Component)val).gameObject != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
				if (flag)
				{
					num++;
					num2 += num3 + num4;
				}
			}
			_savedBoats.Clear();
			_save.boats.Clear();
			_nextBoatName = "Custom Boat 1";
			_lastLocallyDrivenBoat = null;
			_activeBoat = null;
			BoatClosestPhysicsRouting.ClearContext(Player.LocalPlayer);
			BoatInteractionRouting.ForceRefresh();
			BoatClosestPhysicsRouting.ForceRefresh();
			SaveBoats();
			if (num2 > 0)
			{
				MoneyManager.AddMoney(num2, Player.LocalPlayer);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Sold {num} spawned purchased boat(s) for {num2}. The original boat was kept.");
		}

		internal void HandleDestroyedBoatAfterSink(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return;
			}
			bool flag = false;
			string text = null;
			foreach (KeyValuePair<string, Boat> savedBoat in _savedBoats)
			{
				if ((Object)(object)savedBoat.Value == (Object)(object)boat)
				{
					flag = true;
					text = savedBoat.Key;
					break;
				}
			}
			if (flag && !string.IsNullOrEmpty(text))
			{
				_savedBoats.Remove(text);
				if (_save != null && _save.boats != null)
				{
					for (int num = _save.boats.Count - 1; num >= 0; num--)
					{
						if (_save.boats[num].id == text)
						{
							_save.boats.RemoveAt(num);
						}
					}
				}
				_nextBoatName = GetNextCustomBoatName();
			}
			BoatGpsRouting.ClearExplicitRadarOwnership(boat);
			BoatFollowRouting.ClearForBoat(boat);
			BoatCruiseRouting.DisableForBoat(boat);
			BoatSkinRouting.ClearForBoat(boat);
			BoatManagerContactGuard.ClearExplicitSwitch(boat);
			try
			{
				if ((Object)(object)boat.Driver != (Object)null)
				{
					boat.TrySetDriver((Player)null);
				}
			}
			catch
			{
			}
			if ((Object)(object)BoatManager.Instance != (Object)null && (Object)(object)BoatManager.Boat == (Object)(object)boat)
			{
				try
				{
					BoatManager.SetBoat(Array.Empty<Collider>(), (Boat)null);
				}
				catch
				{
				}
			}
			BoatDamageRouting.ForgetBoat(boat);
			ForceRemoveBoat(boat);
			SaveBoats();
			if (!flag)
			{
				((MonoBehaviour)this).StartCoroutine(RespawnOriginalBoatAfterSink());
			}
		}

		private void ChargeOriginalBoatRespawnFee()
		{
			int money = MoneyManager.Money;
			MoneyManager.RemoveMoney(200, Player.LocalPlayer);
			int num = money - 200;
			if (MoneyManager.Money < num)
			{
				int num2 = num - MoneyManager.Money;
				MoneyManager.AddMoney(num2, (Player)null);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Original boat reached 0 HP and respawned for 200 game money.");
		}

		private IEnumerator RespawnOriginalBoatAfterSink()
		{
			float readyAt = Time.unscaledTime + 1f;
			while (Time.unscaledTime < readyAt)
			{
				yield return null;
			}
			if ((Object)(object)MoneyManager.Instance == (Object)null || !MoneyManager.CanAfford(200))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Original boat was destroyed, but you need 200 game money to respawn it. It will not respawn.");
				yield break;
			}
			for (int waitFrame = 0; waitFrame < 600; waitFrame++)
			{
				if (!InstanceFinder.IsServerStarted || (Object)(object)BoatManager.Instance == (Object)null)
				{
					yield return null;
					continue;
				}
				Boat existingOriginal = GetCachedOriginalBoat();
				if ((Object)(object)existingOriginal != (Object)null && ((NetworkBehaviour)existingOriginal).IsSpawned)
				{
					PlaceOriginalBoatAtDefaultSpawn(existingOriginal);
					BoatDamageRouting.SetSavedHealth(existingOriginal, 7500f);
					BoatFuelRouting.SetFuel(existingOriginal, 1.5f);
					if (_save != null && _save.hasSelectedSkin)
					{
						BoatSkinRouting.SetSavedFinish(existingOriginal, _save.selectedSkinStyleMode);
						BoatSkinRouting.SetSavedStripeSecondarySkin(existingOriginal, _save.selectedStripeSecondarySkinIndex);
						BoatSkinRouting.ApplySkin(existingOriginal, _save.selectedSkinIndex);
						if ((_save.selectedSkinStyleMode == 6 || _save.selectedSkinStyleMode == 8) && _save.selectedSkinIndex > 13)
						{
							((MonoBehaviour)this).StartCoroutine(RebuildOriginalSpecialFinishAfterLoad(existingOriginal, _save.selectedSkinIndex, _save.selectedSkinStyleMode));
						}
					}
					ChargeOriginalBoatRespawnFee();
					SaveBoats();
					yield break;
				}
				object prefabObject = GetBoatPrefab();
				if (!(prefabObject is Boat))
				{
					yield return null;
					continue;
				}
				Boat replacement = TrySpawnExtraBoat((Boat)prefabObject, SpawnManager.BoatSpawnPos, SpawnManager.BoatSpawnRot);
				if ((Object)(object)replacement == (Object)null)
				{
					yield return null;
					continue;
				}
				for (int spawnFrame = 0; spawnFrame < 180; spawnFrame++)
				{
					if (((NetworkBehaviour)replacement).IsSpawned)
					{
						break;
					}
					yield return null;
				}
				if (!((NetworkBehaviour)replacement).IsSpawned)
				{
					if ((Object)(object)((Component)replacement).gameObject != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)replacement).gameObject);
					}
					yield return null;
					continue;
				}
				PlaceOriginalBoatAtDefaultSpawn(replacement);
				EnsureBoatUnlocked(replacement);
				if (_save != null && _save.hasSelectedMotor)
				{
					MotorRouting.ApplyMotor(replacement, _save.selectedMotorIndex);
				}
				if (_save != null && _save.hasSelectedSkin)
				{
					BoatSkinRouting.SetSavedFinish(replacement, _save.selectedSkinStyleMode);
					BoatSkinRouting.SetSavedStripeSecondarySkin(replacement, _save.selectedStripeSecondarySkinIndex);
					BoatSkinRouting.ApplySkin(replacement, _save.selectedSkinIndex);
					if ((_save.selectedSkinStyleMode == 6 || _save.selectedSkinStyleMode == 8) && _save.selectedSkinIndex > 13)
					{
						((MonoBehaviour)this).StartCoroutine(RebuildOriginalSpecialFinishAfterLoad(replacement, _save.selectedSkinIndex, _save.selectedSkinStyleMode));
					}
				}
				BoatDamageRouting.SetSavedHealth(replacement, 7500f);
				BoatFuelRouting.SetFuel(replacement, 1.5f);
				_cachedOriginalBoat = replacement;
				_nextOriginalBoatRefresh = Time.unscaledTime + 1f;
				ChargeOriginalBoatRespawnFee();
				SaveBoats();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Original boat respawned at its default game location after sinking.");
				yield break;
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Original boat could not be respawned yet because the map/server boat prefab was not ready.");
		}

		private static void PlaceOriginalBoatAtDefaultSpawn(Boat boat)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)boat == (Object)null))
			{
				Vector3 boatSpawnPos = SpawnManager.BoatSpawnPos;
				Quaternion boatSpawnRot = SpawnManager.BoatSpawnRot;
				((Component)boat).transform.SetPositionAndRotation(boatSpawnPos, boatSpawnRot);
				Rigidbody val = boat.HiddenPhysicsRig;
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)boat).GetComponentInChildren<Rigidbody>();
				}
				if ((Object)(object)val != (Object)null)
				{
					val.position = boatSpawnPos;
					val.rotation = boatSpawnRot;
					val.linearVelocity = Vector3.zero;
					val.angularVelocity = Vector3.zero;
					val.WakeUp();
				}
			}
		}

		private static void ForceRemoveBoat(Boat boat)
		{
			if (!((Object)(object)boat == (Object)null))
			{
				DespawnBoat(boat);
				if ((Object)(object)boat != (Object)null && (Object)(object)((Component)boat).gameObject != (Object)null)
				{
					((Component)boat).gameObject.SetActive(false);
					Object.Destroy((Object)(object)((Component)boat).gameObject);
				}
			}
		}

		private static bool DespawnBoat(Boat boat)
		{
			if ((Object)(object)boat == (Object)null)
			{
				return false;
			}
			object networkObject = ((NetworkBehaviour)boat).NetworkObject;
			if (networkObject != null)
			{
				MethodInfo[] methods = networkObject.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				for (int i = 0; i < methods.Length; i++)
				{
					if (!(methods[i].Name != "Despawn"))
					{
						object[] parameters = BuildDefaultArguments(methods[i].GetParameters(), 0, null);
						try
						{
							methods[i].Invoke(networkObject, parameters);
							return true;
						}
						catch
						{
						}
					}
				}
			}
			MethodInfo[] methods2 = ((object)InstanceFinder.ServerManager).GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			for (int j = 0; j < methods2.Length; j++)
			{
				if (methods2[j].Name != "Despawn")
				{
					continue;
				}
				ParameterInfo[] parameters2 = methods2[j].GetParameters();
				if (parameters2.Length == 0)
				{
					continue;
				}
				object obj2 = (parameters2[0].ParameterType.IsInstanceOfType(((Component)boat).gameObject) ? ((Component)boat).gameObject : (parameters2[0].ParameterType.IsInstanceOfType(networkObject) ? networkObject : null));
				if (obj2 != null)
				{
					object[] parameters3 = BuildDefaultArguments(parameters2, 1, obj2);
					try
					{
						methods2[j].Invoke(InstanceFinder.ServerManager, parameters3);
						return true;
					}
					catch
					{
					}
				}
			}
			return false;
		}

		private static object[] BuildDefaultArguments(ParameterInfo[] parameters, int startIndex, object firstArgument)
		{
			object[] array = new object[parameters.Length];
			if (startIndex == 1)
			{
				array[0] = firstArgument;
			}
			for (int i = startIndex; i < parameters.Length; i++)
			{
				if (parameters[i].HasDefaultValue)
				{
					array[i] = parameters[i].DefaultValue;
				}
				else if (parameters[i].ParameterType.IsValueType)
				{
					array[i] = Activator.CreateInstance(parameters[i].ParameterType);
				}
				else
				{
					array[i] = null;
				}
			}
			return array;
		}

		private void ActivateBoat(Boat boat)
		{
			if (!((Object)(object)boat == (Object)null))
			{
				Collider[] managerColliders = BoatInteractionRouting.GetManagerColliders(boat);
				BoatManager.SetBoat(managerColliders, boat);
				_activeBoat = boat;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Active drivable boat changed to " + ((Object)boat).name + "."));
			}
		}

		private static bool IsNetworkSpawned(Boat boat)
		{
			return (Object)(object)boat != (Object)null && (Object)(object)((Component)boat).gameObject != (Object)null && ((Component)boat).gameObject.activeInHierarchy && ((NetworkBehaviour)boat).IsSpawned;
		}

		private static Vector3 GetBoatSpawnPosition(Boat steeringBoat)
		{
			//IL_000c: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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_00ad: 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_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)steeringBoat == (Object)null)
			{
				return SpawnManager.BoatSpawnPos;
			}
			Vector3 val = (((Object)(object)steeringBoat.HiddenPhysicsRig != (Object)null) ? steeringBoat.HiddenPhysicsRig.position : ((Component)steeringBoat).transform.position);
			Quaternion val2 = (((Object)(object)steeringBoat.HiddenPhysicsRig != (Object)null) ? steeringBoat.HiddenPhysicsRig.rotation : ((Component)steeringBoat).transform.rotation);
			Vector3 val3 = val2 * Vector3.forward;
			Vector3 val4 = Vector3.ProjectOnPlane(val3, Vector3.up);
			val3 = ((Vector3)(ref val4)).normalized;
			if (((Vector3)(ref val3)).sqrMagnitude < 0.01f)
			{
				val3 = Vector3.forward;
			}
			return val + val3 * 10f;
		}

		private static Vector3 SeparateFromExistingBoats(Vector3 position)
		{
			//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_000b: 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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = SpawnManager.BoatSpawnRot * Vector3.right;
			Vector3 val2 = Vector3.ProjectOnPlane(val, Vector3.up);
			val = ((Vector3)(ref val2)).normalized;
			if (((Vector3)(ref val)).sqrMagnitude < 0.01f)
			{
				val = Vector3.right;
			}
			Boat[] array = Object.FindObjectsByType<Boat>((FindObjectsSortMode)0);
			for (int i = 0; i < 3; i++)
			{
				bool flag = false;
				for (int j = 0; j < array.Length; j++)
				{
					if (!((Object)(object)array[j] == (Object)null))
					{
						Vector3 val3 = ((Component)array[j]).transform.position - position;
						val3.y = 0f;
						if (((Vector3)(ref val3)).sqrMagnitude < 9f)
						{
							flag = true;
							break;
						}
					}
				}
				if (!flag)
				{
					break;
				}
				position += val * 4f;
			}
			return position;
		}

		private Boat TrySpawnExtraBoat(Boat prefab, Vector3 position, Quaternion? rotation = null)
		{
			//IL_0002: 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_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			Boat val = Object.Instantiate<Boat>(prefab, position, (Quaternion)(((??)rotation) ?? SpawnManager.BoatSpawnRot));
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			try
			{
				InstanceFinder.ServerManager.Spawn(((Component)val).gameObject, (NetworkConnection)null, default(Scene));
				return val;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex);
				Object.Destroy((Object)(object)((Component)val).gameObject);
				return null;
			}
		}

		private static object GetBoatPrefab()
		{
			SpawnManager val = Object.FindAnyObjectByType<SpawnManager>();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return typeof(SpawnManager).GetField("_boatPrefab", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(val);
		}

		private string GetNextCustomBoatName()
		{
			HashSet<int> hashSet = new HashSet<int>();
			foreach (KeyValuePair<string, Boat> pair in _savedBoats)
			{
				Boat value = pair.Value;
				if ((Object)(object)value == (Object)null || !((NetworkBehaviour)value).IsSpawned || (Object)(object)((Component)value).gameObject == (Object)null || !((Component)value).gameObject.activeInHierarchy)
				{
					continue;
				}
				string name = ((Object)value).name;
				BoatSaveRecord boatSaveRecord = null;
				if (_save != null && _save.boats != null)
				{
					boatSaveRecord = _save.boats.Find((BoatSaveRecord item) => item.id == pair.Key);
				}
				if (boatSaveRecord != null && !string.IsNullOrWhiteSpace(boatSaveRecord.boatName))
				{
					name = boatSaveRecord.boatName;
				}
				if (TryGetCustomBoatNumber(name, out var number))
				{
					hashSet.Add(number);
				}
			}
			int num;
			for (num = 1; hashSet.Contains(num); num++)
			{
			}
			return $"Custom Boat {num}";
		}

		private void EnsureUniqueCustomBoatNames()
		{
			if (_save == null || _save.boats == null)
			{
				return;
			}
			HashSet<int> hashSet = new HashSet<int>();
			for (int i = 0; i < _save.boats.Count; i++)
			{
				BoatSaveRecord boatSaveRecord = _save.boats[i];
				if (TryGetCustomBoatNumber(boatSaveRecord.boatName, out var number) && !hashSet.Contains(number))
				{
					hashSet.Add(number);
					boatSaveRecord.boatName = $"Custom Boat {number}";
					continue;
				}
				int j;
				for (j = 1; hashSet.Contains(j); j++)
				{
				}
				hashSet.Add(j);
				boatSaveRecord.boatName = $"Custom Boat {j}";
			}
		}

		private static string NormalizeCustomBoatName(string name, int fallbackNumber)
		{
			if (TryGetCustomBoatNumber(name, out var number))
			{
				return $"Custom Boat {number}";
			}
			return $"Custom Boat {Mathf.Max(1, fallbackNumber)}";
		}

		private static bool TryGetCustomBoatNumber(string name, out int number)
		{
			number = 0;
			if (string.IsNullOrWhiteSpace(name))
			{
				return false;
			}
			string text = name.Trim();
			string text2 = null;
			if (text.StartsWith("Custom Boat ", StringComparison.OrdinalIgnoreCase))
			{
				text2 = text.Substring("Custom Boat ".Length);
			}
			else if (text.StartsWith("Boat ", StringComparison.OrdinalIgnoreCase))
			{
				text2 = text.Substring("Boat ".Length);
			}
			if (text2 == null || !int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1)
			{
				return false;
			}
			number = result;
			return true;
		}

		private void RegisterPurchasedBoat(Boat boat)
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			string nextCustomBoatName = GetNextCustomBoatName();
			BoatSaveRecord boatSaveRecord = new BoatSaveRecord
			{
				id = Guid.NewGuid().ToString("N"),
				ownerId = GetLocalOwnerId(),
				boatName = nextCustomBoatName,
				motorIndex = boat.MotorIndex,
				radarUnlocked = BoatGpsRouting.HasRadar(boat),
				radarVisible = BoatGpsRouting.IsVisible(boat),
				skinIndex = BoatSkinRouting.GetSkinIndex(boat),
				skinStyleMode = BoatSkinRouting.GetStyleMode(boat),
				stripeSecondarySkinIndex =