Decompiled source of Dive In v1.0.9

DiveIn.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("DiveIn")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("DiveIn")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")]
[assembly: AssemblyFileVersion("1.0.9")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.9.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class ExtensionMarkerAttribute : Attribute
	{
		private readonly string <Name>k__BackingField;

		public string Name => <Name>k__BackingField;

		public ExtensionMarkerAttribute(string name)
		{
			<Name>k__BackingField = name;
		}
	}
}
namespace ServerSyncModTemplate
{
	[BepInPlugin("sighsorry.DiveIn", "DiveIn", "1.0.9")]
	[BepInIncompatibility("Searica.Valheim.UnderTheSea")]
	[BepInIncompatibility("blacks7ar.VikingsDoSwim")]
	public class ServerSyncModTemplatePlugin : BaseUnityPlugin
	{
		private sealed class MonsterDiveYamlRoot : Dictionary<string, MonsterDiveYamlGroup>
		{
			public MonsterDiveYamlRoot()
				: base((IEqualityComparer<string>?)StringComparer.OrdinalIgnoreCase)
			{
			}
		}

		private sealed class MonsterDiveYamlGroup
		{
			public float PassiveMinDepth { get; set; }

			public float PassiveCenterDepth { get; set; }

			public float PassiveMaxDepth { get; set; }

			public float? ActiveDepthAdjustSpeed { get; set; }

			public List<string> Prefabs { get; set; } = new List<string>();

		}

		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		private class ConfigurationManagerAttributes
		{
			[UsedImplicitly]
			public int? Order;

			[UsedImplicitly]
			public bool? Browsable;

			[UsedImplicitly]
			public string? Category;

			[UsedImplicitly]
			public Action<ConfigEntryBase>? CustomDrawer;
		}

		private class AcceptableShortcuts : AcceptableValueBase
		{
			public AcceptableShortcuts()
				: base(typeof(KeyboardShortcut))
			{
			}

			public override object Clamp(object value)
			{
				return value;
			}

			public override bool IsValid(object value)
			{
				return true;
			}

			public override string ToDescriptionString()
			{
				return "# Acceptable values: " + string.Join(", ", UnityInput.Current.SupportedKeyCodes);
			}
		}

		private readonly struct PassiveDepthProfile
		{
			public readonly float CenterDepth;

			public readonly float MinDepth;

			public readonly float MaxDepth;

			public PassiveDepthProfile(float centerDepth, float minDepth, float maxDepth)
			{
				CenterDepth = centerDepth;
				MinDepth = minDepth;
				MaxDepth = maxDepth;
			}
		}

		private readonly struct ConfiguredDiveProfile
		{
			public readonly string GroupName;

			public readonly PassiveDepthProfile PassiveDepthProfile;

			public readonly float ActiveDepthAdjustSpeed;

			public ConfiguredDiveProfile(string groupName, PassiveDepthProfile passiveDepthProfile, float activeDepthAdjustSpeed)
			{
				GroupName = groupName;
				PassiveDepthProfile = passiveDepthProfile;
				ActiveDepthAdjustSpeed = activeDepthAdjustSpeed;
			}
		}

		private readonly struct OriginalDiveFlags
		{
			public readonly MonsterAI MonsterAI;

			public readonly bool AvoidWater;

			public readonly bool AvoidLand;

			public readonly bool CanSwim;

			public OriginalDiveFlags(MonsterAI monsterAI, bool avoidWater, bool avoidLand, bool canSwim)
			{
				MonsterAI = monsterAI;
				AvoidWater = avoidWater;
				AvoidLand = avoidLand;
				CanSwim = canSwim;
			}
		}

		private readonly struct MovePlanCacheEntry
		{
			public readonly float Time;

			public readonly Vector3Int PositionBucket;

			public readonly Vector3Int TargetBucket;

			public readonly bool HasRoute;

			public readonly Vector3 Direction;

			public MovePlanCacheEntry(float time, Vector3Int positionBucket, Vector3Int targetBucket, bool hasRoute, Vector3 direction)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: 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_001e: 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)
				Time = time;
				PositionBucket = positionBucket;
				TargetBucket = targetBucket;
				HasRoute = hasRoute;
				Direction = direction;
			}
		}

		private readonly struct UnderwaterNavigationPlan
		{
			public readonly bool HasRoute;

			public readonly Vector3 Direction;

			public UnderwaterNavigationPlan(bool hasRoute, Vector3 direction)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				HasRoute = hasRoute;
				Direction = direction;
			}
		}

		private readonly struct SwimDepthGoal
		{
			public readonly float DesiredDepth;

			public readonly float ClampedTargetY;

			public readonly bool RequestedOutsideRange;

			public readonly float AdjustSpeed;

			public SwimDepthGoal(float desiredDepth, float clampedTargetY, bool requestedOutsideRange, float adjustSpeed)
			{
				DesiredDepth = desiredDepth;
				ClampedTargetY = clampedTargetY;
				RequestedOutsideRange = requestedOutsideRange;
				AdjustSpeed = adjustSpeed;
			}
		}

		[HarmonyPatch(typeof(MonsterAI), "Awake")]
		private static class MonsterAIAwakePatch
		{
			private static void Postfix(MonsterAI __instance)
			{
				if (IsConfiguredMonster(__instance))
				{
					EnsureDiveFlags(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(MonsterAI), "UpdateAI")]
		private static class MonsterAIUpdateAIPatch
		{
			private static void Prefix(MonsterAI __instance)
			{
				if (IsConfiguredMonster(__instance))
				{
					EnsureDiveFlags(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(BaseAI), "HavePath")]
		private static class BaseAIHavePathPatch
		{
			private static bool Prefix(BaseAI __instance, Vector3 target, ref bool __result)
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				if (!TryGetConfiguredMonster(__instance, out MonsterAI monsterAI) || !ShouldUseWaterDiveMode(monsterAI))
				{
					return true;
				}
				Character character = ((BaseAI)monsterAI).m_character;
				if ((Object)(object)character == (Object)null)
				{
					return true;
				}
				__result = BuildUnderwaterNavigationPlan(__instance, character, target).HasRoute;
				return false;
			}
		}

		[HarmonyPatch(typeof(BaseAI), "MoveTo")]
		private static class BaseAIMoveToPatch
		{
			private static bool Prefix(BaseAI __instance, float dt, Vector3 point, float dist, bool run, ref bool __result)
			{
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_005f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00df: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
				//IL_0106: Unknown result type (might be due to invalid IL or missing references)
				//IL_010b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0129: Unknown result type (might be due to invalid IL or missing references)
				if (!TryGetConfiguredMonster(__instance, out MonsterAI monsterAI) || !ShouldUseWaterDiveMode(monsterAI))
				{
					return true;
				}
				Character character = ((BaseAI)monsterAI).m_character;
				if ((Object)(object)character == (Object)null)
				{
					return true;
				}
				SwimDepthGoal goal = CalculateSwimDepthGoal(monsterAI, character, point);
				UnderwaterNavigationPlan underwaterNavigationPlan = BuildUnderwaterNavigationPlan(__instance, character, point);
				ApplySwimDepthGoal(character, goal, dt);
				float num = Mathf.Max(dist, run ? 1f : 0.5f);
				float num2 = Utils.DistanceXZ(point, ((Component)__instance).transform.position);
				float num3 = Mathf.Abs(point.y - ((Component)__instance).transform.position.y);
				float num4 = Mathf.Abs(goal.ClampedTargetY - ((Component)__instance).transform.position.y);
				bool flag = num3 < 0.75f || (goal.RequestedOutsideRange && num4 < 0.35f);
				if (num2 < num && flag)
				{
					__instance.StopMoving();
					__result = true;
					return false;
				}
				Vector3 val = point - ((Component)__instance).transform.position;
				if (((Vector3)(ref val)).sqrMagnitude <= 0.0001f)
				{
					__instance.StopMoving();
					__result = true;
					return false;
				}
				Vector3 direction = underwaterNavigationPlan.Direction;
				if (((Vector3)(ref direction)).sqrMagnitude <= 0.0001f)
				{
					__instance.StopMoving();
					__result = true;
					return false;
				}
				__instance.MoveTowards(underwaterNavigationPlan.Direction, run);
				__result = false;
				return false;
			}
		}

		internal const string ModName = "DiveIn";

		internal const string ModVersion = "1.0.9";

		internal const string Author = "sighsorry";

		private const string ModGUID = "sighsorry.DiveIn";

		private static readonly string MonsterDiveYamlFileName = "DiveIn.yaml";

		private static readonly string MonsterDiveYamlFileFullPath;

		private static readonly object MonsterDiveYamlLock;

		private static readonly IDeserializer MonsterDiveYamlDeserializer;

		private FileSystemWatcher _monsterDiveYamlWatcher;

		private DateTime _lastMonsterDiveYamlReloadTime;

		private static CustomSyncedValue<string> _monsterDiveYamlSync;

		internal const float DefaultUnderwaterDarknessFactor = 0.5f;

		internal const float DefaultUnderwaterVisibilityFalloff = 0.25f;

		internal const float DefaultUnderwaterCameraMinWaterDistance = -5000f;

		internal static ConfigEntry<string> _waterEquipmentBlacklist;

		internal static ConfigEntry<float> _surfaceStaminaRegenRateMultiplier;

		internal static ConfigEntry<float> _midwaterStaminaRegenRateMultiplier;

		internal static ConfigEntry<float> _surfaceEitrRegenRateMultiplier;

		internal static ConfigEntry<float> _midwaterEitrRegenRateMultiplier;

		internal static ConfigEntry<float> _midwaterIdleStaminaDrainPerDepth;

		internal static ConfigEntry<float> _swimStaminaDrainMultiplierPerDepth;

		internal static ConfigEntry<float> _playerSwimSkillSpeedMultiplier;

		internal static ConfigEntry<float> _fastSwimSpeedMultiplier;

		internal static ConfigEntry<float> _fastSwimStaminaDrainMultiplier;

		internal static ConfigEntry<KeyboardShortcut> _playerDiveAscendShortcut;

		internal static ConfigEntry<KeyboardShortcut> _playerDiveDescendShortcut;

		internal static ConfigEntry<float> _underwaterDarknessFactor;

		internal static ConfigEntry<float> _underwaterVisibilityFalloff;

		private static readonly object WaterEquipmentBlacklistLock;

		private static string _lastWaterEquipmentBlacklistRaw;

		private static HashSet<string> _waterEquipmentBlacklistSet;

		private static readonly string ConfigFileName;

		private static readonly string ConfigFileFullPath;

		private readonly Harmony _harmony = new Harmony("sighsorry.DiveIn");

		public static readonly ManualLogSource ServerSyncModTemplateLogger;

		private static readonly ConfigSync ConfigSync;

		private FileSystemWatcher _watcher;

		private readonly object _reloadLock = new object();

		private DateTime _lastConfigReloadTime;

		private const long RELOAD_DELAY = 10000000L;

		private static ConfigEntry<Toggle> _serverConfigLocked;

		private static IReadOnlyDictionary<string, ConfiguredDiveProfile> _configuredDiveProfilesByPrefabName;

		private static readonly Dictionary<int, OriginalDiveFlags> OriginalDiveFlagsByInstance;

		private const int MaxCacheEntries = 2048;

		private const float PassiveWavePeriodSeconds = 12f;

		private const float ActiveSwimDepthMin = 0.25f;

		private const float ActiveSwimDepthMax = 300f;

		private const float SwimDepthAdjustSpeed = 2f;

		private const float MovePlanCacheSeconds = 0.1f;

		private const float MovePlanCacheCellSize = 0.1f;

		private const int AvoidanceSampleCount = 8;

		private static readonly float[] SteerAngles;

		private static readonly Dictionary<int, MovePlanCacheEntry> MovePlanCache;

		private void InitializeMonsterDiveYaml()
		{
			_monsterDiveYamlSync = new CustomSyncedValue<string>(ConfigSync, "MonsterDiveYaml", string.Empty);
			_monsterDiveYamlSync.ValueChanged += OnMonsterDiveYamlValueChanged;
			ConfigSync.SourceOfTruthChanged += OnMonsterDiveSourceOfTruthChanged;
			if (ConfigSync.IsSourceOfTruth)
			{
				LoadMonsterDiveYamlFromDisk(forceWriteDefaultIfMissing: true, syncToPeers: true, "startup");
			}
			else if (!string.IsNullOrWhiteSpace(_monsterDiveYamlSync.Value))
			{
				ApplyMonsterDiveYaml(_monsterDiveYamlSync.Value, "startup synced value");
			}
		}

		private void SetupMonsterDiveYamlWatcher()
		{
			_monsterDiveYamlWatcher = new FileSystemWatcher(Paths.ConfigPath, MonsterDiveYamlFileName);
			_monsterDiveYamlWatcher.Changed += ReadMonsterDiveYamlValues;
			_monsterDiveYamlWatcher.Created += ReadMonsterDiveYamlValues;
			_monsterDiveYamlWatcher.Renamed += ReadMonsterDiveYamlValues;
			_monsterDiveYamlWatcher.IncludeSubdirectories = true;
			_monsterDiveYamlWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			_monsterDiveYamlWatcher.EnableRaisingEvents = true;
		}

		private void DisposeMonsterDiveYamlWatcher()
		{
			if (_monsterDiveYamlSync != null)
			{
				_monsterDiveYamlSync.ValueChanged -= OnMonsterDiveYamlValueChanged;
			}
			ConfigSync.SourceOfTruthChanged -= OnMonsterDiveSourceOfTruthChanged;
			if (_monsterDiveYamlWatcher != null)
			{
				_monsterDiveYamlWatcher.Changed -= ReadMonsterDiveYamlValues;
				_monsterDiveYamlWatcher.Created -= ReadMonsterDiveYamlValues;
				_monsterDiveYamlWatcher.Renamed -= ReadMonsterDiveYamlValues;
				_monsterDiveYamlWatcher.Dispose();
				_monsterDiveYamlWatcher = null;
			}
		}

		private void ReadMonsterDiveYamlValues(object sender, FileSystemEventArgs e)
		{
			DateTime now = DateTime.Now;
			if (now.Ticks - _lastMonsterDiveYamlReloadTime.Ticks < 10000000)
			{
				return;
			}
			_lastMonsterDiveYamlReloadTime = now;
			lock (_reloadLock)
			{
				if (!ConfigSync.IsSourceOfTruth)
				{
					ServerSyncModTemplateLogger.LogInfo((object)"Ignoring local monster dive YAML reload because remote synced values are active.");
					return;
				}
				try
				{
					LoadMonsterDiveYamlFromDisk(forceWriteDefaultIfMissing: true, syncToPeers: true, "yaml reload");
					ServerSyncModTemplateLogger.LogInfo((object)"Monster dive YAML reload complete.");
				}
				catch (Exception ex)
				{
					ServerSyncModTemplateLogger.LogError((object)("Error reloading monster dive YAML: " + ex.Message));
				}
			}
		}

		private void OnMonsterDiveSourceOfTruthChanged(bool isSourceOfTruth)
		{
			if (isSourceOfTruth)
			{
				LoadMonsterDiveYamlFromDisk(forceWriteDefaultIfMissing: true, syncToPeers: true, "source of truth changed to local");
			}
			else if (!string.IsNullOrWhiteSpace(_monsterDiveYamlSync.Value))
			{
				ApplyMonsterDiveYaml(_monsterDiveYamlSync.Value, "source of truth changed to remote");
			}
		}

		private void OnMonsterDiveYamlValueChanged()
		{
			if (!ConfigSync.IsSourceOfTruth && !string.IsNullOrWhiteSpace(_monsterDiveYamlSync.Value))
			{
				ApplyMonsterDiveYaml(_monsterDiveYamlSync.Value, "synced value changed");
			}
		}

		private void LoadMonsterDiveYamlFromDisk(bool forceWriteDefaultIfMissing, bool syncToPeers, string reason)
		{
			lock (MonsterDiveYamlLock)
			{
				if (!File.Exists(MonsterDiveYamlFileFullPath))
				{
					if (!forceWriteDefaultIfMissing)
					{
						return;
					}
					string contents = BuildDefaultMonsterDiveYaml();
					Directory.CreateDirectory(Paths.ConfigPath);
					File.WriteAllText(MonsterDiveYamlFileFullPath, contents);
				}
				string text = File.ReadAllText(MonsterDiveYamlFileFullPath);
				if (ApplyMonsterDiveYaml(text, reason) && syncToPeers && _monsterDiveYamlSync.Value != text)
				{
					_monsterDiveYamlSync.Value = text;
				}
			}
		}

		private static bool ApplyMonsterDiveYaml(string yamlText, string reason)
		{
			if (string.IsNullOrWhiteSpace(yamlText))
			{
				ServerSyncModTemplateLogger.LogWarning((object)("Monster dive YAML is empty during " + reason + ". Keeping previous settings."));
				return false;
			}
			MonsterDiveYamlRoot root;
			try
			{
				root = MonsterDiveYamlDeserializer.Deserialize<MonsterDiveYamlRoot>(yamlText) ?? new MonsterDiveYamlRoot();
			}
			catch (YamlException ex)
			{
				string text = ((ex.Start.Line > 0) ? $" at line {ex.Start.Line}, column {ex.Start.Column}" : string.Empty);
				ServerSyncModTemplateLogger.LogError((object)("Failed to parse monster dive YAML during " + reason + text + ": " + ex.Message));
				return false;
			}
			catch (Exception ex2)
			{
				ServerSyncModTemplateLogger.LogError((object)("Failed to parse monster dive YAML during " + reason + ": " + ex2.Message));
				return false;
			}
			Dictionary<string, MonsterDiveYamlGroup> definedGroups = GetDefinedGroups(root);
			Dictionary<string, ConfiguredDiveProfile> dictionary = new Dictionary<string, ConfiguredDiveProfile>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, MonsterDiveYamlGroup> item in definedGroups)
			{
				string key = item.Key;
				MonsterDiveYamlGroup value = item.Value;
				PassiveDepthProfile passiveDepthProfile = NormalizePassiveDepthProfile(key, value.PassiveMinDepth, value.PassiveCenterDepth, value.PassiveMaxDepth);
				float activeDepthAdjustSpeed = NormalizeActiveDepthAdjustSpeed(key, value.ActiveDepthAdjustSpeed);
				AddYamlGroupEntries(configuredDiveProfile: new ConfiguredDiveProfile(key, passiveDepthProfile, activeDepthAdjustSpeed), configuredProfilesByPrefabName: dictionary, mobs: value.Prefabs);
			}
			if (dictionary.Count == 0)
			{
				ServerSyncModTemplateLogger.LogWarning((object)("Monster dive YAML loaded during " + reason + ", but no prefabs are assigned to any group."));
			}
			_configuredDiveProfilesByPrefabName = dictionary;
			int num = RestoreRemovedMonsterDiveFlags();
			ClearRuntimeCaches();
			ServerSyncModTemplateLogger.LogInfo((object)$"Loaded monster dive YAML ({reason}). passiveGroups={definedGroups.Count}, prefabs={dictionary.Count}, active[min={0.25f:F2}, max={300f:F2}, defaultAdjust={2f:F2}], restoredRemovedInstances={num}.");
			return true;
		}

		private static float NormalizeActiveDepthAdjustSpeed(string groupName, float? activeDepthAdjustSpeed)
		{
			if (!activeDepthAdjustSpeed.HasValue)
			{
				return 2f;
			}
			float value = activeDepthAdjustSpeed.Value;
			float num = Mathf.Max(0f, value);
			if (!Mathf.Approximately(num, value))
			{
				ServerSyncModTemplateLogger.LogWarning((object)("Monster dive YAML normalized active profile '" + groupName + "': active_depth_adjust_speed " + value.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num.ToString("0.###", CultureInfo.InvariantCulture) + "."));
			}
			return num;
		}

		private static PassiveDepthProfile NormalizePassiveDepthProfile(string groupName, float minDepth, float centerDepth, float maxDepth)
		{
			float num = minDepth;
			float num2 = centerDepth;
			float num3 = maxDepth;
			float num4 = Mathf.Max(0f, num);
			float num5 = Mathf.Max(0f, num3);
			if (num5 < num4)
			{
				float num6 = num5;
				num5 = num4;
				num4 = num6;
			}
			float num7 = Mathf.Clamp(num2, num4, num5);
			if (!Mathf.Approximately(num4, num) || !Mathf.Approximately(num5, num3) || !Mathf.Approximately(num7, num2))
			{
				ServerSyncModTemplateLogger.LogWarning((object)("Monster dive YAML normalized passive profile '" + groupName + "': passive_min_depth " + num.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num4.ToString("0.###", CultureInfo.InvariantCulture) + ", passive_center_depth " + num2.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num7.ToString("0.###", CultureInfo.InvariantCulture) + ", passive_max_depth " + num3.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num5.ToString("0.###", CultureInfo.InvariantCulture) + "."));
			}
			return new PassiveDepthProfile(num7, num4, num5);
		}

		private static Dictionary<string, MonsterDiveYamlGroup> GetDefinedGroups(MonsterDiveYamlRoot root)
		{
			Dictionary<string, MonsterDiveYamlGroup> dictionary = new Dictionary<string, MonsterDiveYamlGroup>(StringComparer.OrdinalIgnoreCase);
			if (root.Count == 0)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, MonsterDiveYamlGroup> item in root)
			{
				string text = item.Key?.Trim() ?? string.Empty;
				if (text.Length == 0)
				{
					ServerSyncModTemplateLogger.LogWarning((object)"Monster dive YAML contains an empty top-level group name. Skipping it.");
				}
				else
				{
					dictionary[text] = item.Value ?? new MonsterDiveYamlGroup();
				}
			}
			return dictionary;
		}

		private static void AddYamlGroupEntries(Dictionary<string, ConfiguredDiveProfile> configuredProfilesByPrefabName, IEnumerable<string>? mobs, ConfiguredDiveProfile configuredDiveProfile)
		{
			if (mobs == null)
			{
				return;
			}
			foreach (string mob in mobs)
			{
				string text = mob?.Trim() ?? string.Empty;
				if (text.Length != 0)
				{
					if (configuredProfilesByPrefabName.ContainsKey(text))
					{
						ServerSyncModTemplateLogger.LogWarning((object)("Monster dive YAML duplicate mob '" + text + "' found in " + configuredDiveProfile.GroupName + ". Keeping first assignment."));
					}
					else
					{
						configuredProfilesByPrefabName[text] = configuredDiveProfile;
					}
				}
			}
		}

		private static string BuildDefaultMonsterDiveYaml()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("# Monster dive configuration for DiveIn.");
			stringBuilder.AppendLine("# Unknown keys and duplicate keys are treated as errors and keep the previous applied settings.");
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "surface_patrol", 0f, 10f, 20f, 2f, includeGroupHeaderComment: true, includeFieldComments: true, new string[4] { "Leech", "Abomination", "Serpent", "BonemawSerpent" });
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "mid_water", 0f, 15f, 30f, 2f);
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "deep_patrol", 10f, 20f, 30f, 2f);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("## Mod prefabs sample");
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "mods_surface", 0f, 10f, 20f, 2f, includeGroupHeaderComment: false, includeFieldComments: false, new string[30]
			{
				"Neck_RtD", "Animal_Dolphin_RtD", "Animal_Cod_RtD", "Monster_GreatWhiteShark_RtD", "Animal_Turtle_RtD", "Mirmaid_RtD", "BoneFish_RtD", "BoneSquid_RtD", "LuminousLooker_RtD", "MurkPod_RtD",
				"Animal_HumpbackWhale_RtD", "RDB_crocodile", "RDB_white_shark", "RDB_turtle", "Shark_TW", "ArcticSerpent_TW", "SA_Orca", "SA_Dolphin", "SA_WhiteShark", "SA_HumboldtSquid",
				"SA_LeatherbackSeaTurtle", "SA_RightWhale", "SA_WhaleShark", "SA_BlueShark", "SA_HammerHeadShark", "SA_TigerShark", "SA_BlueTurtle", "SA_GreenTurtle", "SA_RedTurtle", "SA_YellowTurtle"
			});
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "mods_midwater", 0f, 15f, 30f, 2f, includeGroupHeaderComment: false, includeFieldComments: false, new string[6] { "Belzor_RtD", "Monster_HammerheadShark_RtD", "Animal_Marlin_RtD", "Shark_RtD", "Animal_SpermWhale_RtD", "Monster_Orca_RtD" });
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "mods_deep", 10f, 20f, 30f, 2f, includeGroupHeaderComment: false, includeFieldComments: false, new string[2] { "Animal_Tuna_RtD", "Animal_Squid_RtD" });
			stringBuilder.AppendLine();
			AppendDefaultGroup(stringBuilder, "mods_bottom", 20f, 30f, 40f, 2f, includeGroupHeaderComment: false, includeFieldComments: false, new string[4] { "CatFish_RtD", "Reptile_RtD", "MirRake_RtD", "Animal_Manta_RtD" });
			return stringBuilder.ToString();
		}

		private static void AppendDefaultGroup(StringBuilder builder, string groupName, float minDepth, float centerDepth, float maxDepth, float activeDepthAdjustSpeed, bool includeGroupHeaderComment = false, bool includeFieldComments = false, IEnumerable<string>? examplePrefabs = null)
		{
			string text = (includeGroupHeaderComment ? " # You can use any group name. Add your own groups" : string.Empty);
			string text2 = (includeFieldComments ? " # Shallowest passive dive depth used while the monster has no target and is not alerted." : string.Empty);
			string text3 = (includeFieldComments ? " # Center depth used by the passive sine-wave swimming pattern." : string.Empty);
			string text4 = (includeFieldComments ? " # Deepest passive dive depth used while the monster has no target and is not alerted." : string.Empty);
			string text5 = (includeFieldComments ? " # How quickly this group adjusts swim depth while alerted or chasing a target." : string.Empty);
			string text6 = (includeFieldComments ? " # Monster prefab names assigned to this passive profile group." : string.Empty);
			builder.AppendLine(groupName + ":" + text);
			builder.AppendLine("  passive_min_depth: " + FormatYamlFloat(minDepth) + text2);
			builder.AppendLine("  passive_center_depth: " + FormatYamlFloat(centerDepth) + text3);
			builder.AppendLine("  passive_max_depth: " + FormatYamlFloat(maxDepth) + text4);
			builder.AppendLine("  active_depth_adjust_speed: " + FormatYamlFloat(activeDepthAdjustSpeed) + text5);
			if (examplePrefabs != null)
			{
				string[] array = examplePrefabs.Where((string prefab) => !string.IsNullOrWhiteSpace(prefab)).ToArray();
				if (array.Length != 0)
				{
					builder.AppendLine("  prefabs:" + text6);
					string[] array2 = array;
					foreach (string text7 in array2)
					{
						builder.AppendLine("    - " + text7);
					}
					return;
				}
			}
			builder.AppendLine("  prefabs: []" + text6);
		}

		private static string FormatYamlFloat(float value)
		{
			return value.ToString("0.###", CultureInfo.InvariantCulture);
		}

		private void InitializePlayerDiveConfig()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Expected O, but got Unknown
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Expected O, but got Unknown
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Expected O, but got Unknown
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b4: Expected O, but got Unknown
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Expected O, but got Unknown
			//IL_031a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_034f: Expected O, but got Unknown
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_0393: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Expected O, but got Unknown
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ed: Expected O, but got Unknown
			//IL_0431: Unknown result type (might be due to invalid IL or missing references)
			//IL_043c: Expected O, but got Unknown
			_waterEquipmentBlacklist = config("2 - Player Diving", "Water Equipment Blacklist", "", new ConfigDescription("Comma-separated item prefab names that remain restricted in water. Everything not listed is allowed in water by default. Example: BowFineWood,ShieldBronzeBuckler.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 100
				}
			}));
			_surfaceStaminaRegenRateMultiplier = config("3 - Swim Resources", "Surface Stamina Regen Rate", 0.5f, new ConfigDescription("Multiplier applied to vanilla stamina regeneration while swimming on the surface with your head above water. 0 matches vanilla swimming behavior, 1 matches normal non-swimming stamina regeneration timing and rate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 110
				}
			}));
			_midwaterStaminaRegenRateMultiplier = config("3 - Swim Resources", "Midwater Stamina Regen Rate", 0f, new ConfigDescription("Multiplier applied to vanilla stamina regeneration while your head is underwater. 0 makes stamina recover only after surfacing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 109
				}
			}));
			_surfaceEitrRegenRateMultiplier = config("3 - Swim Resources", "Surface Eitr Regen Rate", 0.7f, new ConfigDescription("Multiplier applied to vanilla eitr regeneration while swimming on the surface with your head above water. 0 disables eitr regeneration while surface swimming, 1 keeps vanilla eitr regeneration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 108
				}
			}));
			_midwaterEitrRegenRateMultiplier = config("3 - Swim Resources", "Midwater Eitr Regen Rate", 0.3f, new ConfigDescription("Multiplier applied to vanilla eitr regeneration while your head is underwater. 0 makes eitr recover only after surfacing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 107
				}
			}));
			_midwaterIdleStaminaDrainPerDepth = config("3 - Swim Resources", "Midwater Idle Stamina Drain Per Depth", 0.02f, new ConfigDescription("Idle stamina drained per second per 1m of current liquid depth while your head is underwater. 0 disables idle underwater stamina drain. Example: 0.1 drains 3 stamina per second at 30m depth.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 106
				}
			}));
			_swimStaminaDrainMultiplierPerDepth = config("3 - Swim Resources", "Swim Stamina Drain Multiplier Per Depth", 2.5f, new ConfigDescription("Additional moving swim stamina drain percent per 1m of current liquid depth. 1 means 30% extra at 30m; 2.5 means 75% extra at 30m. Applied multiplicatively with Fast Swim stamina drain.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 105
				}
			}));
			_fastSwimSpeedMultiplier = config("4 - Swim Speed", "Fast Swim Speed Multiplier", 2f, new ConfigDescription("Swim speed multiplier while Fast Swim is toggled on with the vanilla run key. 1 disables Fast Swim and hides its key hint. Swim skill separately increases base swim speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 99
				}
			}));
			_fastSwimStaminaDrainMultiplier = config("4 - Swim Speed", "Fast Swim Stamina Drain Multiplier", 2f, new ConfigDescription("Moving swim stamina drain multiplier while Fast Swim is toggled on. Applied multiplicatively with Swim Stamina Drain Multiplier Per Depth.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 98
				}
			}));
			_playerSwimSkillSpeedMultiplier = config("4 - Swim Speed", "Swim Skill Speed Multiplier", 1.5f, new ConfigDescription("Base swim speed multiplier at Swim skill 100. 1.5 means +50%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 100
				}
			}));
			_playerDiveAscendShortcut = config<KeyboardShortcut>("2 - Player Diving", "Dive Ascend Key", new KeyboardShortcut((KeyCode)32, Array.Empty<KeyCode>()), new ConfigDescription("Client-side key used to ascend while swimming underwater.", (AcceptableValueBase)(object)new AcceptableShortcuts(), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 110
				}
			}), synchronizedSetting: false);
			_playerDiveDescendShortcut = config<KeyboardShortcut>("2 - Player Diving", "Dive Descend Key", new KeyboardShortcut((KeyCode)306, Array.Empty<KeyCode>()), new ConfigDescription("Client-side key used to descend while swimming.", (AcceptableValueBase)(object)new AcceptableShortcuts(), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 109
				}
			}), synchronizedSetting: false);
			_underwaterDarknessFactor = config("2 - Player Diving", "Darkness Factor", 0.5f, new ConfigDescription("Underwater darkness added per meter of swim depth. 1 means 1% per meter, so 30m gives 30%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 92
				}
			}));
			_underwaterVisibilityFalloff = config("2 - Player Diving", "Murkiness Factor", 0.25f, new ConfigDescription("Underwater fog density added per meter of swim depth. 1 means 1% per meter, so 30m adds 30%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 91
				}
			}));
		}

		internal static bool IsUnderwaterVisualStylingEnabled()
		{
			return true;
		}

		internal static float GetUnderwaterCameraMinWaterDistance()
		{
			return -5000f;
		}

		internal static float GetUnderwaterDarknessFactor()
		{
			return Mathf.Max(0f, _underwaterDarknessFactor.Value) * 0.01f;
		}

		internal static float GetUnderwaterVisibilityFalloff()
		{
			return Mathf.Max(0f, _underwaterVisibilityFalloff.Value) * 0.01f;
		}

		internal static bool IsSwimRunEnabled()
		{
			if (_fastSwimSpeedMultiplier != null)
			{
				return _fastSwimSpeedMultiplier.Value > 1.001f;
			}
			return false;
		}

		internal static bool IsDiveAscendInputHeld()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyboardShortcut> playerDiveAscendShortcut = _playerDiveAscendShortcut;
			if (playerDiveAscendShortcut == null || !playerDiveAscendShortcut.Value.IsKeyHeld())
			{
				return ZInput.GetButton("JoyJump");
			}
			return true;
		}

		internal static bool IsDiveDescendInputHeld()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyboardShortcut> playerDiveDescendShortcut = _playerDiveDescendShortcut;
			if (playerDiveDescendShortcut == null || !playerDiveDescendShortcut.Value.IsKeyHeld())
			{
				return ZInput.GetButton("JoyCrouch");
			}
			return true;
		}

		internal static string GetDiveAscendKeyHint()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (ShouldShowGamepadKeyHints())
			{
				return GetBoundKeyHint("JoyJump", "A");
			}
			return FormatShortcutForKeyHint((KeyboardShortcut)(((??)_playerDiveAscendShortcut?.Value) ?? new KeyboardShortcut((KeyCode)32, Array.Empty<KeyCode>())));
		}

		internal static string GetDiveDescendKeyHint()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (ShouldShowGamepadKeyHints())
			{
				return GetBoundKeyHint("JoyCrouch", "B");
			}
			return FormatShortcutForKeyHint((KeyboardShortcut)(((??)_playerDiveDescendShortcut?.Value) ?? new KeyboardShortcut((KeyCode)306, Array.Empty<KeyCode>())));
		}

		internal static string GetDiveRunKeyHint()
		{
			if (!ShouldShowGamepadKeyHints())
			{
				return GetBoundKeyHint("Run", "Left Shift");
			}
			return GetBoundKeyHint("JoyRun", "LT");
		}

		private static bool ShouldShowGamepadKeyHints()
		{
			return ZInput.IsGamepadActive();
		}

		private static string GetBoundKeyHint(string bindingName, string fallback)
		{
			ZInput instance = ZInput.instance;
			string text = ((instance != null) ? instance.GetBoundKeyString(bindingName, true) : null) ?? string.Empty;
			if (string.IsNullOrWhiteSpace(text))
			{
				return fallback;
			}
			if (Localization.instance == null)
			{
				return text;
			}
			return Localization.instance.Localize(text);
		}

		private static string FormatShortcutForKeyHint(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0)
			{
				return "None";
			}
			List<string> list = ((KeyboardShortcut)(ref shortcut)).Modifiers.Where((KeyCode key) => (int)key > 0).Select(FormatKeyCodeForHint).ToList();
			list.Add(FormatKeyCodeForHint(((KeyboardShortcut)(ref shortcut)).MainKey));
			return string.Join(" + ", list);
		}

		private static string FormatKeyCodeForHint(KeyCode key)
		{
			//IL_0000: 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_0024: Expected I4, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected I4, but got Unknown
			return (key - 303) switch
			{
				3 => "Left Ctrl", 
				2 => "Right Ctrl", 
				1 => "Left Shift", 
				0 => "Right Shift", 
				5 => "Left Alt", 
				4 => "Right Alt", 
				_ => (key - 323) switch
				{
					0 => "Mouse-1", 
					1 => "Mouse-2", 
					2 => "Mouse-3", 
					3 => "Mouse-4", 
					4 => "Mouse-5", 
					5 => "Mouse-6", 
					6 => "Mouse-7", 
					_ => ((object)(KeyCode)(ref key)).ToString(), 
				}, 
			};
		}

		internal static bool IsWaterRestrictedItem(ItemData? item)
		{
			if (item == null || (Object)(object)item.m_dropPrefab == (Object)null)
			{
				return false;
			}
			RefreshWaterEquipmentBlacklistIfNeeded();
			string prefabName = Utils.GetPrefabName(item.m_dropPrefab);
			if (!string.IsNullOrEmpty(prefabName))
			{
				return _waterEquipmentBlacklistSet.Contains(prefabName);
			}
			return false;
		}

		internal static bool HumanoidHasWaterRestrictedEquipment(Humanoid? humanoid)
		{
			if ((Object)(object)humanoid == (Object)null)
			{
				return false;
			}
			if (!IsWaterRestrictedItem(humanoid.m_rightItem) && !IsWaterRestrictedItem(humanoid.m_hiddenRightItem) && !IsWaterRestrictedItem(humanoid.m_leftItem) && !IsWaterRestrictedItem(humanoid.m_hiddenLeftItem) && !IsWaterRestrictedItem(humanoid.m_chestItem) && !IsWaterRestrictedItem(humanoid.m_legItem) && !IsWaterRestrictedItem(humanoid.m_helmetItem) && !IsWaterRestrictedItem(humanoid.m_shoulderItem) && !IsWaterRestrictedItem(humanoid.m_utilityItem))
			{
				return IsWaterRestrictedItem(humanoid.m_trinketItem);
			}
			return true;
		}

		private static void RefreshWaterEquipmentBlacklistIfNeeded(bool force = false)
		{
			string text = _waterEquipmentBlacklist?.Value ?? string.Empty;
			if (!force && string.Equals(text, _lastWaterEquipmentBlacklistRaw, StringComparison.Ordinal))
			{
				return;
			}
			lock (WaterEquipmentBlacklistLock)
			{
				if (force || !string.Equals(text, _lastWaterEquipmentBlacklistRaw, StringComparison.Ordinal))
				{
					_waterEquipmentBlacklistSet = (from entry in text.Split(new char[5] { ',', ';', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries)
						select entry.Trim() into entry
						where entry.Length > 0
						select entry).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
					_lastWaterEquipmentBlacklistRaw = text;
				}
			}
		}

		public void Awake()
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			_serverConfigLocked = config("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.");
			ConfigSync.AddLockingConfigEntry<Toggle>(_serverConfigLocked);
			InitializePlayerDiveConfig();
			InitializeMonsterDiveYaml();
			ClearRuntimeCaches();
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			DiveLocalization.Register();
			SetupWatcher();
			SetupMonsterDiveYamlWatcher();
			((BaseUnityPlugin)this).Config.Save();
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private void OnDestroy()
		{
			_harmony.UnpatchSelf();
			UnderwaterVisualState.ResetAll();
			int num = RestoreAllTrackedMonsterDiveFlags();
			if (num > 0)
			{
				ServerSyncModTemplateLogger.LogInfo((object)$"Restored original dive flags for {num} monster instances.");
			}
			ClearRuntimeCaches();
			SaveWithRespectToConfigSet();
			DisposeConfigWatcher();
			DisposeMonsterDiveYamlWatcher();
		}

		private void SetupWatcher()
		{
			_watcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
			_watcher.Changed += ReadConfigValues;
			_watcher.Created += ReadConfigValues;
			_watcher.Renamed += ReadConfigValues;
			_watcher.IncludeSubdirectories = true;
			_watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			_watcher.EnableRaisingEvents = true;
		}

		private void DisposeConfigWatcher()
		{
			if (_watcher != null)
			{
				_watcher.Changed -= ReadConfigValues;
				_watcher.Created -= ReadConfigValues;
				_watcher.Renamed -= ReadConfigValues;
				_watcher.Dispose();
				_watcher = null;
			}
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			DateTime now = DateTime.Now;
			if (now.Ticks - _lastConfigReloadTime.Ticks < 10000000)
			{
				return;
			}
			_lastConfigReloadTime = now;
			lock (_reloadLock)
			{
				if (!File.Exists(ConfigFileFullPath))
				{
					ServerSyncModTemplateLogger.LogWarning((object)"Config file does not exist. Skipping reload.");
					return;
				}
				try
				{
					SaveWithRespectToConfigSet(reload: true);
					UnderwaterVisualState.ResetAll();
					ClearRuntimeCaches();
					ServerSyncModTemplateLogger.LogInfo((object)"Configuration reload complete.");
				}
				catch (Exception ex)
				{
					ServerSyncModTemplateLogger.LogError((object)("Error reloading configuration: " + ex.Message));
				}
			}
		}

		private void SaveWithRespectToConfigSet(bool reload = false)
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			if (reload)
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			((BaseUnityPlugin)this).Config.Save();
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, description.Tags);
			ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val);
			ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting;
			return val2;
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description, bool synchronizedSetting = true)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting);
		}

		private static bool TryGetConfiguredDiveProfile(MonsterAI monsterAI, out ConfiguredDiveProfile configuredDiveProfile)
		{
			if ((Object)(object)monsterAI == (Object)null)
			{
				configuredDiveProfile = default(ConfiguredDiveProfile);
				return false;
			}
			string prefabName = Utils.GetPrefabName(((Component)monsterAI).gameObject);
			return _configuredDiveProfilesByPrefabName.TryGetValue(prefabName, out configuredDiveProfile);
		}

		private static bool IsConfiguredMonster(MonsterAI monsterAI)
		{
			ConfiguredDiveProfile configuredDiveProfile;
			return TryGetConfiguredDiveProfile(monsterAI, out configuredDiveProfile);
		}

		private static bool TryGetConfiguredMonster(BaseAI ai, out MonsterAI monsterAI)
		{
			MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null);
			if (val != null && IsConfiguredMonster(val))
			{
				monsterAI = val;
				return true;
			}
			monsterAI = null;
			return false;
		}

		private static bool ShouldUseWaterDiveMode(MonsterAI monsterAI)
		{
			Character character = ((BaseAI)monsterAI).m_character;
			if ((Object)(object)character == (Object)null)
			{
				return false;
			}
			if (character.InWater())
			{
				return character.InLiquidDepth() > 0.05f;
			}
			return false;
		}

		private static bool IsPassiveDiveState(MonsterAI monsterAI)
		{
			if (!((BaseAI)monsterAI).IsAlerted() && (Object)(object)monsterAI.m_targetCreature == (Object)null)
			{
				return (Object)(object)monsterAI.m_targetStatic == (Object)null;
			}
			return false;
		}

		private static void EnsureDiveFlags(MonsterAI monsterAI)
		{
			TrackOriginalDiveFlags(monsterAI);
			if (((BaseAI)monsterAI).m_avoidWater)
			{
				((BaseAI)monsterAI).m_avoidWater = false;
			}
			EnsureAvoidLandForCurrentDiveState(monsterAI);
			Character character = ((BaseAI)monsterAI).m_character;
			if ((Object)(object)character != (Object)null && !character.m_canSwim)
			{
				character.m_canSwim = true;
			}
		}

		private static void EnsureAvoidLandForCurrentDiveState(MonsterAI monsterAI)
		{
			if (ShouldUseWaterDiveMode(monsterAI))
			{
				if (monsterAI.m_avoidLand)
				{
					monsterAI.m_avoidLand = false;
				}
				return;
			}
			int instanceID = ((Object)monsterAI).GetInstanceID();
			if (OriginalDiveFlagsByInstance.TryGetValue(instanceID, out var value) && monsterAI.m_avoidLand != value.AvoidLand)
			{
				monsterAI.m_avoidLand = value.AvoidLand;
			}
		}

		private static void TrackOriginalDiveFlags(MonsterAI monsterAI)
		{
			if (!Object.op_Implicit((Object)(object)monsterAI))
			{
				return;
			}
			int instanceID = ((Object)monsterAI).GetInstanceID();
			if (!OriginalDiveFlagsByInstance.ContainsKey(instanceID))
			{
				Character character = ((BaseAI)monsterAI).m_character;
				OriginalDiveFlagsByInstance[instanceID] = new OriginalDiveFlags(monsterAI, ((BaseAI)monsterAI).m_avoidWater, monsterAI.m_avoidLand, (Object)(object)character != (Object)null && character.m_canSwim);
				if (OriginalDiveFlagsByInstance.Count > 2048)
				{
					PruneTrackedOriginalDiveFlags();
				}
			}
		}

		private static int RestoreRemovedMonsterDiveFlags()
		{
			if (OriginalDiveFlagsByInstance.Count == 0)
			{
				return 0;
			}
			List<int> list = new List<int>();
			int num = 0;
			foreach (KeyValuePair<int, OriginalDiveFlags> item in OriginalDiveFlagsByInstance)
			{
				int key = item.Key;
				OriginalDiveFlags value = item.Value;
				MonsterAI monsterAI = value.MonsterAI;
				if (!Object.op_Implicit((Object)(object)monsterAI))
				{
					list.Add(key);
				}
				else if (!IsConfiguredMonster(monsterAI))
				{
					RestoreOriginalDiveFlags(value);
					list.Add(key);
					num++;
				}
			}
			foreach (int item2 in list)
			{
				OriginalDiveFlagsByInstance.Remove(item2);
			}
			return num;
		}

		private static int RestoreAllTrackedMonsterDiveFlags()
		{
			if (OriginalDiveFlagsByInstance.Count == 0)
			{
				return 0;
			}
			int num = 0;
			foreach (OriginalDiveFlags value in OriginalDiveFlagsByInstance.Values)
			{
				if (Object.op_Implicit((Object)(object)value.MonsterAI))
				{
					RestoreOriginalDiveFlags(value);
					num++;
				}
			}
			OriginalDiveFlagsByInstance.Clear();
			return num;
		}

		private static void RestoreOriginalDiveFlags(OriginalDiveFlags originalFlags)
		{
			MonsterAI monsterAI = originalFlags.MonsterAI;
			if (Object.op_Implicit((Object)(object)monsterAI))
			{
				((BaseAI)monsterAI).m_avoidWater = originalFlags.AvoidWater;
				monsterAI.m_avoidLand = originalFlags.AvoidLand;
				Character character = ((BaseAI)monsterAI).m_character;
				if ((Object)(object)character != (Object)null)
				{
					character.m_canSwim = originalFlags.CanSwim;
				}
			}
		}

		private static void PruneTrackedOriginalDiveFlags()
		{
			if (OriginalDiveFlagsByInstance.Count == 0)
			{
				return;
			}
			List<int> list = new List<int>();
			foreach (KeyValuePair<int, OriginalDiveFlags> item in OriginalDiveFlagsByInstance)
			{
				int key = item.Key;
				if (!Object.op_Implicit((Object)(object)item.Value.MonsterAI))
				{
					list.Add(key);
				}
			}
			foreach (int item2 in list)
			{
				OriginalDiveFlagsByInstance.Remove(item2);
			}
		}

		private static Vector3Int ToCacheBucket(Vector3 value)
		{
			//IL_0000: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3Int(Mathf.RoundToInt(value.x / 0.1f), Mathf.RoundToInt(value.y / 0.1f), Mathf.RoundToInt(value.z / 0.1f));
		}

		private static void ClearRuntimeCaches()
		{
			MovePlanCache.Clear();
		}

		private static void TrimCachesIfNeeded()
		{
			if (MovePlanCache.Count > 2048)
			{
				MovePlanCache.Clear();
			}
		}

		private static float GetPassiveDesiredDepth(MonsterAI monsterAI, PassiveDepthProfile profile)
		{
			int num = Mathf.Abs(((Object)monsterAI).GetInstanceID());
			float num2 = Mathf.Sin(Mathf.Repeat(Time.time + (float)(num % 997) * 0.173f, 12f) / 12f * (float)Math.PI * 2f);
			float num3 = Mathf.Max(0f, profile.CenterDepth - profile.MinDepth);
			float num4 = Mathf.Max(0f, profile.MaxDepth - profile.CenterDepth);
			if (!(num2 >= 0f))
			{
				return profile.CenterDepth + num2 * num3;
			}
			return profile.CenterDepth + num2 * num4;
		}

		private static SwimDepthGoal CalculateSwimDepthGoal(MonsterAI monsterAI, Character character, Vector3 point)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			TryGetConfiguredDiveProfile(monsterAI, out var configuredDiveProfile);
			float liquidLevel = character.GetLiquidLevel();
			float adjustSpeed;
			float num;
			bool flag;
			if (IsPassiveDiveState(monsterAI))
			{
				num = GetPassiveDesiredDepth(monsterAI, configuredDiveProfile.PassiveDepthProfile);
				flag = true;
				adjustSpeed = 2f;
			}
			else
			{
				float num2 = liquidLevel - point.y;
				num = Mathf.Clamp(num2, 0.25f, 300f);
				flag = num2 < 0.25f || num2 > 300f;
				adjustSpeed = configuredDiveProfile.ActiveDepthAdjustSpeed;
			}
			float num3 = num;
			num = ClampSwimDepthForBottomContact(character, num);
			flag = flag || num < num3 - 0.001f;
			float clampedTargetY = liquidLevel - num;
			return new SwimDepthGoal(num, clampedTargetY, flag, adjustSpeed);
		}

		private static void ApplySwimDepthGoal(Character character, SwimDepthGoal goal, float dt)
		{
			if (goal.AdjustSpeed <= 0f)
			{
				character.m_swimDepth = goal.DesiredDepth;
				return;
			}
			float num = goal.AdjustSpeed * Mathf.Max(dt, 0.01f);
			character.m_swimDepth = Mathf.MoveTowards(character.m_swimDepth, goal.DesiredDepth, num);
		}

		private static float ClampSwimDepthForBottomContact(Character character, float desiredDepth)
		{
			return UnderwaterDepthUtils.ClampDepthAboveBottom(character, desiredDepth, 0.25f);
		}

		private static UnderwaterNavigationPlan BuildUnderwaterNavigationPlan(BaseAI ai, Character character, Vector3 targetPoint)
		{
			//IL_000d: 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_0018: 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_001e: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			int instanceID = ((Object)ai).GetInstanceID();
			Vector3Int val = ToCacheBucket(((Component)ai).transform.position);
			Vector3Int val2 = ToCacheBucket(targetPoint);
			float time = Time.time;
			if (MovePlanCache.TryGetValue(instanceID, out var value) && time - value.Time <= 0.1f && value.PositionBucket == val && value.TargetBucket == val2)
			{
				return new UnderwaterNavigationPlan(value.HasRoute, value.Direction);
			}
			UnderwaterNavigationPlan result = CalculateUnderwaterNavigationPlan(ai, character, targetPoint);
			TrimCachesIfNeeded();
			MovePlanCache[instanceID] = new MovePlanCacheEntry(time, val, val2, result.HasRoute, result.Direction);
			return result;
		}

		private static UnderwaterNavigationPlan CalculateUnderwaterNavigationPlan(BaseAI ai, Character character, Vector3 targetPoint)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = targetPoint - ((Component)ai).transform.position;
			if (((Vector3)(ref val)).sqrMagnitude <= 0.0001f)
			{
				return new UnderwaterNavigationPlan(hasRoute: true, Vector3.zero);
			}
			((Vector3)(ref val)).Normalize();
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(val.x, 0f, val.z);
			float radius = character.GetRadius();
			float num = Utils.DistanceXZ(targetPoint, ((Component)ai).transform.position);
			float num2 = Mathf.Clamp(num, radius + 1f, 6f);
			if (num <= radius + 0.6f)
			{
				return new UnderwaterNavigationPlan(hasRoute: true, val);
			}
			if (((Vector3)(ref val2)).sqrMagnitude <= 0.0001f)
			{
				return new UnderwaterNavigationPlan(hasRoute: true, val);
			}
			((Vector3)(ref val2)).Normalize();
			Vector3 centerPoint = character.GetCenterPoint();
			int num3 = Mathf.Clamp(8, 3, SteerAngles.Length);
			Vector3 val3 = val2;
			bool hasRoute = false;
			float num4 = float.NegativeInfinity;
			for (int i = 0; i < num3; i++)
			{
				float num5 = SteerAngles[i];
				Vector3 val4 = Quaternion.Euler(0f, num5, 0f) * val2;
				float num6;
				bool flag;
				if (ai.CanMove(val4, radius, num2))
				{
					num6 = 1000f - Mathf.Abs(num5);
					flag = true;
				}
				else
				{
					float num7 = ai.Raycast(centerPoint, val4, num2 * 2f, 0.1f);
					num6 = num7 - Mathf.Abs(num5) * 0.01f;
					flag = num7 >= num2 * 0.9f;
				}
				if (num6 > num4)
				{
					num4 = num6;
					val3 = val4;
					hasRoute = flag;
				}
			}
			Vector3 val5 = default(Vector3);
			((Vector3)(ref val5))..ctor(val3.x, val.y, val3.z);
			Vector3 direction = ((((Vector3)(ref val5)).sqrMagnitude > 0.0001f) ? ((Vector3)(ref val5)).normalized : val);
			return new UnderwaterNavigationPlan(hasRoute, direction);
		}

		static ServerSyncModTemplatePlugin()
		{
			string configPath = Paths.ConfigPath;
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			MonsterDiveYamlFileFullPath = configPath + directorySeparatorChar + MonsterDiveYamlFileName;
			MonsterDiveYamlLock = new object();
			MonsterDiveYamlDeserializer = new DeserializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).WithDuplicateKeyChecking().Build();
			_monsterDiveYamlSync = null;
			_waterEquipmentBlacklist = null;
			_surfaceStaminaRegenRateMultiplier = null;
			_midwaterStaminaRegenRateMultiplier = null;
			_surfaceEitrRegenRateMultiplier = null;
			_midwaterEitrRegenRateMultiplier = null;
			_midwaterIdleStaminaDrainPerDepth = null;
			_swimStaminaDrainMultiplierPerDepth = null;
			_playerSwimSkillSpeedMultiplier = null;
			_fastSwimSpeedMultiplier = null;
			_fastSwimStaminaDrainMultiplier = null;
			_playerDiveAscendShortcut = null;
			_playerDiveDescendShortcut = null;
			_underwaterDarknessFactor = null;
			_underwaterVisibilityFalloff = null;
			WaterEquipmentBlacklistLock = new object();
			_lastWaterEquipmentBlacklistRaw = string.Empty;
			_waterEquipmentBlacklistSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			ConfigFileName = "sighsorry.DiveIn.cfg";
			string configPath2 = Paths.ConfigPath;
			directorySeparatorChar = Path.DirectorySeparatorChar;
			ConfigFileFullPath = configPath2 + directorySeparatorChar + ConfigFileName;
			ServerSyncModTemplateLogger = Logger.CreateLogSource("DiveIn");
			ConfigSync = new ConfigSync("sighsorry.DiveIn")
			{
				DisplayName = "DiveIn",
				CurrentVersion = "1.0.9",
				MinimumRequiredVersion = "1.0.9",
				ModRequired = true
			};
			_serverConfigLocked = null;
			_configuredDiveProfilesByPrefabName = new Dictionary<string, ConfiguredDiveProfile>(StringComparer.OrdinalIgnoreCase);
			OriginalDiveFlagsByInstance = new Dictionary<int, OriginalDiveFlags>();
			SteerAngles = new float[8] { 0f, -35f, 35f, -70f, 70f, -120f, 120f, 180f };
			MovePlanCache = new Dictionary<int, MovePlanCacheEntry>();
		}
	}
	internal static class DiveLocalization
	{
		private readonly struct DiveHintTranslation
		{
			internal string FastSwimOn { get; }

			internal string FastSwimOff { get; }

			internal string Descend { get; }

			internal string Ascend { get; }

			internal DiveHintTranslation(string fastSwimOn, string fastSwimOff, string descend, string ascend)
			{
				FastSwimOn = fastSwimOn;
				FastSwimOff = fastSwimOff;
				Descend = descend;
				Ascend = ascend;
			}
		}

		internal const string FastSwimOnKey = "$divein_fast_swim_on";

		internal const string FastSwimOffKey = "$divein_fast_swim_off";

		internal const string DescendKey = "$divein_descend";

		internal const string AscendKey = "$divein_ascend";

		private const string FastSwimOnWord = "divein_fast_swim_on";

		private const string FastSwimOffWord = "divein_fast_swim_off";

		private const string DescendWord = "divein_descend";

		private const string AscendWord = "divein_ascend";

		private const string EnglishLanguage = "english";

		private static readonly DiveHintTranslation English = new DiveHintTranslation("Fast Swim On", "Fast Swim Off", "Descend", "Ascend");

		private static readonly Dictionary<string, DiveHintTranslation> Translations = new Dictionary<string, DiveHintTranslation>(StringComparer.OrdinalIgnoreCase)
		{
			["english"] = English,
			["swedish"] = new DiveHintTranslation("Snabbsim på", "Snabbsim av", "Ner", "Upp"),
			["french"] = new DiveHintTranslation("Nage rapide ON", "Nage rapide OFF", "Descendre", "Monter"),
			["italian"] = new DiveHintTranslation("Nuoto veloce ON", "Nuoto veloce OFF", "Scendi", "Sali"),
			["german"] = new DiveHintTranslation("Schnellschwimmen an", "Schnellschwimmen aus", "Abtauchen", "Auftauchen"),
			["spanish"] = new DiveHintTranslation("Nado rápido ON", "Nado rápido OFF", "Bajar", "Subir"),
			["russian"] = new DiveHintTranslation("Быстрое плавание вкл.", "Быстрое плавание выкл.", "Вниз", "Вверх"),
			["finnish"] = new DiveHintTranslation("Nopea uinti päällä", "Nopea uinti pois", "Alas", "Ylös"),
			["danish"] = new DiveHintTranslation("Hurtig svømning til", "Hurtig svømning fra", "Ned", "Op"),
			["norwegian"] = new DiveHintTranslation("Rask svømming på", "Rask svømming av", "Ned", "Opp"),
			["turkish"] = new DiveHintTranslation("Hızlı yüzme açık", "Hızlı yüzme kapalı", "Alçal", "Yüksel"),
			["lithuanian"] = new DiveHintTranslation("Greitas plaukimas įj.", "Greitas plaukimas išj.", "Žemyn", "Aukštyn"),
			["czech"] = new DiveHintTranslation("Rychlé plavání zap.", "Rychlé plavání vyp.", "Dolů", "Nahoru"),
			["hungarian"] = new DiveHintTranslation("Gyors úszás be", "Gyors úszás ki", "Le", "Fel"),
			["slovak"] = new DiveHintTranslation("Rýchle plávanie zap.", "Rýchle plávanie vyp.", "Dole", "Hore"),
			["polish"] = new DiveHintTranslation("Szybkie pływanie wł.", "Szybkie pływanie wył.", "W dół", "W górę"),
			["dutch"] = new DiveHintTranslation("Snel zwemmen aan", "Snel zwemmen uit", "Omlaag", "Omhoog"),
			["portuguese_european"] = new DiveHintTranslation("Nado rápido ligado", "Nado rápido desligado", "Descer", "Subir"),
			["portuguese_brazilian"] = new DiveHintTranslation("Nado rápido ligado", "Nado rápido desligado", "Descer", "Subir"),
			["chinese"] = new DiveHintTranslation("快速游泳开", "快速游泳关", "下潜", "上浮"),
			["chinese_trad"] = new DiveHintTranslation("快速游泳開", "快速游泳關", "下潛", "上浮"),
			["japanese"] = new DiveHintTranslation("高速泳ぎ ON", "高速泳ぎ OFF", "潜る", "浮上"),
			["korean"] = new DiveHintTranslation("빠른 수영 켬", "빠른 수영 끔", "하강", "상승"),
			["thai"] = new DiveHintTranslation("ว\u0e48ายเร\u0e47ว เป\u0e34ด", "ว\u0e48ายเร\u0e47ว ป\u0e34ด", "ลง", "ข\u0e36\u0e49น"),
			["greek"] = new DiveHintTranslation("Γρήγορη κολύμβηση ON", "Γρήγορη κολύμβηση OFF", "Κάτω", "Πάνω"),
			["ukrainian"] = new DiveHintTranslation("Швидке плавання увімк.", "Швидке плавання вимк.", "Вниз", "Вгору"),
			["latvian"] = new DiveHintTranslation("Ātra peldēšana iesl.", "Ātra peldēšana izsl.", "Lejup", "Augšup")
		};

		internal static void Register()
		{
			if (Localization.instance != null)
			{
				Register(Localization.instance);
			}
		}

		internal static void Register(Localization localization)
		{
			if (localization != null)
			{
				DiveHintTranslation translation = GetTranslation(NormalizeLanguageName(localization.GetSelectedLanguage()));
				localization.AddWord("divein_fast_swim_on", translation.FastSwimOn);
				localization.AddWord("divein_fast_swim_off", translation.FastSwimOff);
				localization.AddWord("divein_descend", translation.Descend);
				localization.AddWord("divein_ascend", translation.Ascend);
			}
		}

		internal static string Localize(string key)
		{
			if (Localization.instance == null)
			{
				return GetEnglishText(key);
			}
			string text = Localization.instance.Localize(key);
			if (!text.Contains("$"))
			{
				return text;
			}
			return GetEnglishText(key);
		}

		private static DiveHintTranslation GetTranslation(string languageName)
		{
			if (!Translations.TryGetValue(languageName, out var value))
			{
				return English;
			}
			return value;
		}

		private static string NormalizeLanguageName(string languageName)
		{
			if (!string.IsNullOrWhiteSpace(languageName))
			{
				return languageName.Trim().Replace(" ", string.Empty).ToLowerInvariant();
			}
			return "english";
		}

		private static string GetEnglishText(string key)
		{
			return key switch
			{
				"$divein_fast_swim_on" => English.FastSwimOn, 
				"$divein_fast_swim_off" => English.FastSwimOff, 
				"$divein_descend" => English.Descend, 
				"$divein_ascend" => English.Ascend, 
				_ => key, 
			};
		}
	}
	[HarmonyPatch(typeof(Localization), "SetupLanguage")]
	internal static class LocalizationSetupLanguageDivePatch
	{
		private static void Postfix(Localization __instance)
		{
			DiveLocalization.Register(__instance);
		}
	}
	internal static class PlayerDiveUtils
	{
		internal static PlayerDiveController? EnsureLocalDiver()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return null;
			}
			PlayerDiveController playerDiveController = PlayerDiveController.LocalInstance;
			if ((Object)(object)playerDiveController != (Object)null && (Object)(object)playerDiveController.Player == (Object)(object)localPlayer)
			{
				return playerDiveController;
			}
			if (!((Component)localPlayer).TryGetComponent<PlayerDiveController>(ref playerDiveController))
			{
				playerDiveController = ((Component)localPlayer).gameObject.AddComponent<PlayerDiveController>();
			}
			return playerDiveController;
		}

		internal static bool TryGetLocalDiver(Player player, out PlayerDiveController diver)
		{
			if (!IsValidLocalPlayer(player))
			{
				diver = null;
				return false;
			}
			PlayerDiveController playerDiveController = EnsureLocalDiver();
			if ((Object)(object)playerDiveController == (Object)null)
			{
				diver = null;
				return false;
			}
			diver = playerDiveController;
			return true;
		}

		internal static bool IsValidLocalPlayer(Player player)
		{
			if ((Object)(object)player != (Object)null)
			{
				return (Object)(object)player == (Object)(object)Player.m_localPlayer;
			}
			return false;
		}

		internal static bool TryGetUnderwaterLocalDiver(Player player, out PlayerDiveController diver)
		{
			diver = null;
			if (IsValidLocalPlayer(player) && TryGetLocalDiver(player, out diver))
			{
				return diver.ShouldTreatAsSwimming();
			}
			return false;
		}
	}
	internal sealed class PlayerDiveController : MonoBehaviour
	{
		private const float HeadUnderwaterTolerance = 0.01f;

		private const float MinimumSurfaceSwimDepth = 0.1f;

		private const float DivingSwimDepthOffset = 1.1f;

		private const float BottomAscendDepthStep = 0.75f;

		private const float CombatMovementSuppressionDuration = 0.1f;

		private float _surfaceSwimDepth = 2f;

		private bool _underwaterMovementActive;

		private bool _fastSwimEnabled;

		private bool _hasSwimSpeedOverride;

		private float _originalSwimSpeed;

		private float _activeSwimRunStaminaDrainMultiplier = 1f;

		private float _combatMovementSuppressedUntilTime;

		private int _swimmingUpdateContextDepth;

		private int _swimmingUpdateContextFrame = -1;

		internal static PlayerDiveController? LocalInstance { get; private set; }

		internal Player Player { get; private set; }

		private void Awake()
		{
			Player = ((Component)this).GetComponent<Player>();
			if ((Object)(object)Player == (Object)null)
			{
				Object.Destroy((Object)(object)this);
				return;
			}
			if ((Object)(object)Player != (Object)(object)Player.m_localPlayer)
			{
				Object.Destroy((Object)(object)this);
				return;
			}
			LocalInstance = this;
			_surfaceSwimDepth = Mathf.Max(0.1f, ((Character)Player).m_swimDepth);
		}

		private void OnDestroy()
		{
			if ((Object)(object)LocalInstance == (Object)(object)this)
			{
				LocalInstance = null;
			}
		}

		internal void DisableUnderwaterMovement()
		{
			_underwaterMovementActive = false;
			_fastSwimEnabled = false;
			ResetSwimDepthToDefault();
			ResetSwimSpeedOverride();
		}

		internal void ResetSwimDepthIfNotInWater()
		{
			if (!((Character)Player).InWater())
			{
				DisableUnderwaterMovement();
			}
		}

		internal void ResetSwimDepthToDefault()
		{
			((Character)Player).m_swimDepth = _surfaceSwimDepth;
		}

		internal bool CanDive()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			if (ShouldForceDive())
			{
				return true;
			}
			if (!((Character)Player).InWater() || ((Character)Player).IsOnGround() || !((Character)Player).IsSwimming())
			{
				return false;
			}
			float num = default(float);
			Vector3 val = default(Vector3);
			if (((Character)Player).GetGroundHeight(((Component)Player).transform.position, ref num, ref val) && ((Component)Player).transform.position.y - num < 1f)
			{
				return false;
			}
			return true;
		}

		internal bool IsHeadUnderwater()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			float num = (((Object)(object)((Character)Player).m_eye != (Object)null) ? ((Character)Player).m_eye.position.y : ((Component)Player).transform.position.y);
			return ((Character)Player).GetLiquidLevel() - num > 0.01f;
		}

		internal void RefreshUnderwaterMovementState()
		{
			if (!((Character)Player).InWater() || !IsHeadUnderwater())
			{
				_underwaterMovementActive = false;
			}
			else if (IsUnderSurface())
			{
				_underwaterMovementActive = true;
			}
		}

		internal bool ShouldForceSwimming()
		{
			if (_underwaterMovementActive && ((Character)Player).InWater())
			{
				return IsHeadUnderwater();
			}
			return false;
		}

		internal bool ShouldShowDiveKeyHints()
		{
			return ShouldTreatAsSwimming();
		}

		internal bool ShouldTreatAsSwimming()
		{
			if (((Character)Player).InWater())
			{
				if (!((Character)Player).IsSwimming())
				{
					return ShouldForceSwimming();
				}
				return true;
			}
			return false;
		}

		internal bool IsFastSwimEnabled()
		{
			if (ServerSyncModTemplatePlugin.IsSwimRunEnabled())
			{
				return _fastSwimEnabled;
			}
			return false;
		}

		internal void UpdateFastSwimToggle()
		{
			if (!ShouldShowDiveKeyHints() || !ServerSyncModTemplatePlugin.IsSwimRunEnabled())
			{
				_fastSwimEnabled = false;
			}
			else if (ZInput.GetButtonDown("Run") || ZInput.GetButtonDown("JoyRun"))
			{
				_fastSwimEnabled = !_fastSwimEnabled;
			}
		}

		internal void SuppressMovementForCombat()
		{
			_combatMovementSuppressedUntilTime = Mathf.Max(_combatMovementSuppressedUntilTime, Time.time + 0.1f);
		}

		internal bool IsMovementSuppressedForCombat()
		{
			return Time.time <= _combatMovementSuppressedUntilTime;
		}

		internal bool ShouldForceDive()
		{
			if (ShouldForceSwimming())
			{
				return !((Character)Player).IsOnGround();
			}
			return false;
		}

		internal void PrepareForcedSwimming()
		{
			ClampSwimDepthForBottomContact();
			((Character)Player).m_body.WakeUp();
			((Character)Player).m_lastGroundTouch = 0.3f;
			((Character)Player).m_swimTimer = 0f;
		}

		internal bool IsUnderSurface()
		{
			return ((Character)Player).m_swimDepth > _surfaceSwimDepth + 0.01f;
		}

		internal bool IsDiving()
		{
			return ((Character)Player).m_swimDepth > _surfaceSwimDepth + 1.1f;
		}

		internal bool IsSurfacing()
		{
			if (!IsDiving())
			{
				return IsUnderSurface();
			}
			return false;
		}

		internal bool IsIdleInWater()
		{
			//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)
			if (ShouldTreatAsSwimming())
			{
				Vector3 velocity = ((Character)Player).GetVelocity();
				return ((Vector3)(ref velocity)).magnitude < 1f;
			}
			return false;
		}

		internal void BeginSwimmingUpdateContext()
		{
			_swimmingUpdateContextDepth++;
			_swimmingUpdateContextFrame = Time.frameCount;
		}

		internal void EndSwimmingUpdateContext()
		{
			if (_swimmingUpdateContextDepth > 0)
			{
				_swimmingUpdateContextDepth--;
			}
			if (_swimmingUpdateContextDepth == 0)
			{
				_swimmingUpdateContextFrame = -1;
			}
		}

		internal bool IsInSwimmingUpdateContext()
		{
			if (_swimmingUpdateContextDepth > 0)
			{
				return _swimmingUpdateContextFrame == Time.frameCount;
			}
			return false;
		}

		internal void RegenWaterStamina(float dt)
		{
			float num = (IsHeadUnderwater() ? ServerSyncModTemplatePlugin._midwaterStaminaRegenRateMultiplier.Value : ServerSyncModTemplatePlugin._surfaceStaminaRegenRateMultiplier.Value);
			if (!(num <= 0f))
			{
				float maxStamina = ((Character)Player).GetMaxStamina();
				float num2 = 1f;
				if (((Character)Player).IsBlocking())
				{
					num2 *= 0.8f;
				}
				if (((Character)Player).InAttack() || ((Character)Player).InDodge() || ((Character)Player).m_wallRunning || ((Character)Player).IsEncumbered())
				{
					num2 = 0f;
				}
				float num3 = (Player.m_staminaRegen + (1f - Player.m_stamina / maxStamina) * Player.m_staminaRegen * Player.m_staminaRegenTimeMultiplier) * num2;
				float num4 = 1f;
				((Character)Player).m_seman.ModifyStaminaRegen(ref num4);
				num3 *= num4;
				num3 *= num;
				if (Player.m_stamina < maxStamina && Player.m_staminaRegenTimer <= 0f)
				{
					Player.m_stamina = Mathf.Min(maxStamina, Player.m_stamina + num3 * dt * Game.m_staminaRegenRate);
				}
			}
		}

		internal void ApplyIdleMidwaterStaminaDrain(float dt)
		{
			float num = Mathf.Max(0f, ServerSyncModTemplatePlugin._midwaterIdleStaminaDrainPerDepth.Value);
			if (!(num <= 0f) && IsHeadUnderwater() && IsIdleInWater())
			{
				float num2 = Mathf.Max(0f, ((Character)Player).InLiquidDepth()) * num;
				if (!(num2 <= 0f))
				{
					((Character)Player).UseStamina(num2 * dt);
				}
			}
		}

		internal void ApplyExtraSwimStaminaDrain(float dt)
		{
			float extraSwimStaminaDrainMultiplier = GetExtraSwimStaminaDrainMultiplier();
			if (!(extraSwimStaminaDrainMultiplier <= 1f))
			{
				float modifiedSwimStaminaDrain = GetModifiedSwimStaminaDrain();
				float num = extraSwimStaminaDrainMultiplier - 1f;
				if (!(num <= 0f))
				{
					((Character)Player).UseStamina(dt * modifiedSwimStaminaDrain * Game.m_moveStaminaRate * num);
				}
			}
		}

		internal void UpdateSwimSpeed()
		{
			ResetSwimSpeedOverride();
			float swimSkillSpeedMultiplier = GetSwimSkillSpeedMultiplier();
			float swimRunSpeedMultiplier = GetSwimRunSpeedMultiplier();
			_activeSwimRunStaminaDrainMultiplier = GetSwimRunStaminaDrainMultiplier();
			float num = swimSkillSpeedMultiplier * swimRunSpeedMultiplier;
			if (!Mathf.Approximately(num, 1f))
			{
				_originalSwimSpeed = ((Character)Player).m_swimSpeed;
				_hasSwimSpeedOverride = true;
				Player player = Player;
				((Character)player).m_swimSpeed = ((Character)player).m_swimSpeed * num;
			}
		}

		internal void ResetSwimSpeedOverride()
		{
			if (!_hasSwimSpeedOverride)
			{
				_activeSwimRunStaminaDrainMultiplier = 1f;
				return;
			}
			((Character)Player).m_swimSpeed = _originalSwimSpeed;
			_hasSwimSpeedOverride = false;
			_activeSwimRunStaminaDrainMultiplier = 1f;
		}

		private float GetSwimSkillSpeedMultiplier()
		{
			float skillFactor = Player.m_skills.GetSkillFactor((SkillType)103);
			float num = Mathf.Max(1f, ServerSyncModTemplatePlugin._playerSwimSkillSpeedMultiplier.Value);
			return Mathf.Lerp(1f, num, skillFactor);
		}

		private float GetSwimRunSpeedMultiplier()
		{
			if (!IsFastSwimEnabled() || !((Character)Player).HaveStamina(0f))
			{
				return 1f;
			}
			return Mathf.Max(1f, ServerSyncModTemplatePlugin._fastSwimSpeedMultiplier.Value);
		}

		private float GetSwimRunStaminaDrainMultiplier()
		{
			if (!IsFastSwimEnabled() || !((Character)Player).HaveStamina(0f))
			{
				return 1f;
			}
			return Mathf.Max(1f, ServerSyncModTemplatePlugin._fastSwimStaminaDrainMultiplier.Value);
		}

		private float GetModifiedSwimStaminaDrain()
		{
			float skillFactor = Player.m_skills.GetSkillFactor((SkillType)103);
			float num = Mathf.Lerp(Player.m_swimStaminaDrainMinSkill, Player.m_swimStaminaDrainMaxSkill, skillFactor);
			num += num * ((Character)Player).GetEquipmentSwimStaminaModifier();
			((Character)Player).m_seman.ModifySwimStaminaUsage(num, ref num, true);
			return num;
		}

		internal void Dive(float dt, bool ascend, out Vector3? defaultMoveDir)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			defaultMoveDir = ((Character)Player).m_moveDir;
			((Character)Player).m_moveDir = GetDiveDirection(ascend);
			if (ascend)
			{
				EnsureAscendTargetFromBottom();
			}
			Vector3 val = CalculateSwimVelocity();
			float num = ((Character)Player).m_swimDepth - val.y * dt;
			((Character)Player).m_swimDepth = Mathf.Max(num, _surfaceSwimDepth);
		}

		private void EnsureAscendTargetFromBottom()
		{
			float num = ((Character)Player).InLiquidDepth();
			if (!(num <= _surfaceSwimDepth) && UnderwaterDepthUtils.IsAtUnderwaterBottom((Character)(object)Player))
			{
				float num2 = Mathf.Max(_surfaceSwimDepth, num - 0.75f);
				if (((Character)Player).m_swimDepth > num2)
				{
					((Character)Player).m_swimDepth = num2;
				}
				((Character)Player).m_body.WakeUp();
			}
		}

		private void ClampSwimDepthForBottomContact()
		{
			((Character)Player).m_swimDepth = UnderwaterDepthUtils.ClampDepthAboveBottom((Character)(object)Player, ((Character)Player).m_swimDepth, _surfaceSwimDepth);
		}

		private Vector3 GetDiveDirection(bool ascend)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = (ascend ? Vector3.up : Vector3.down);
			Vector3 val2 = ((Character)Player).m_moveDir;
			if (((Vector3)(ref val2)).magnitude < 0.1f)
			{
				float scale = ((ascend && IsSurfacing()) ? 0.6f : 0.05f);
				val2 = GetHorizontalLookDirection(scale);
			}
			Vector3 val3 = val2 + val;
			return ((Vector3)(ref val3)).normalized;
		}

		private Vector3 GetHorizontalLookDirection(float scale)
		{
			//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_001f: 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)
			Vector3 lookDir = ((Character)Player).m_lookDir;
			lookDir.y = 0f;
			((Vector3)(ref lookDir)).Normalize();
			return lookDir * scale;
		}

		private Vector3 CalculateSwimVelocity()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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_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_0084: Unknown result type (might be due to invalid IL or missing references)
			float num = ((Character)Player).m_swimSpeed * ((Character)Player).GetAttackSpeedFactorMovement();
			if (((Character)Player).InMinorActionSlowdown())
			{
				num = 0f;
			}
			((Character)Player).m_seman.ApplyStatusEffectSpeedMods(ref num, ((Character)Player).m_moveDir);
			Vector3 val = ((Character)Player).m_moveDir * num;
			val = Vector3.Lerp(((Character)Player).m_currentVel, val, ((Character)Player).m_swimAcceleration);
			((Character)Player).AddPushbackForce(ref val);
			return val;
		}

		private float GetExtraSwimStaminaDrainMultiplier()
		{
			return GetDepthSwimStaminaDrainMultiplier() * Mathf.Max(1f, _activeSwimRunStaminaDrainMultiplier);
		}

		private float GetDepthSwimStaminaDrainMultiplier()
		{
			float num = Mathf.Max(0f, ServerSyncModTemplatePlugin._swimStaminaDrainMultiplierPerDepth.Value);
			float num2 = Mathf.Max(0f, ((Character)Player).InLiquidDepth());
			return 1f + num2 * num / 100f;
		}
	}
	[HarmonyPatch]
	internal static class PlayerDivePatches
	{
		private readonly struct SwimmingUpdateState
		{
			internal PlayerDiveController? Diver { get; }

			internal Vector3? OriginalMoveDir { get; }

			internal SwimmingUpdateState(PlayerDiveController? diver, Vector3? originalMoveDir)
			{
				Diver = diver;
				OriginalMoveDir = originalMoveDir;
			}
		}

		private readonly struct ResourceUpdateState
		{
			internal PlayerDiveController? Diver { get; }

			internal float Eitr { get; }

			internal ResourceUpdateState(PlayerDiveController? diver, float eitr)
			{
				Diver = diver;
				Eitr = eitr;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "Awake")]
		private static void PlayerAwakePostfix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				PlayerDiveUtils.EnsureLocalDiver();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Character), "UpdateMotion")]
		private static void CharacterUpdateMotionPrefix(Character __instance)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
			if (!((Object)(object)playerDiveController == (Object)null))
			{
				playerDiveController.ResetSwimDepthIfNotInWater();
				playerDiveController.RefreshUnderwaterMovementState();
				playerDiveController.UpdateFastSwimToggle();
				if (playerDiveController.ShouldForceSwimming())
				{
					playerDiveController.PrepareForcedSwimming();
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Character), "UpdateSwimming")]
		private static void CharacterUpdateSwimmingPrefix(Character __instance, float dt, out SwimmingUpdateState __state)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				__state = new SwimmingUpdateState(null, null);
				return;
			}
			PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
			if ((Object)(object)playerDiveController == (Object)null)
			{
				__state = new SwimmingUpdateState(null, null);
				return;
			}
			playerDiveController.BeginSwimmingUpdateContext();
			__state = new SwimmingUpdateState(playerDiveController, null);
			playerDiveController.UpdateSwimSpeed();
			bool flag = playerDiveController.IsMovementSuppressedForCombat();
			if (!flag && ServerSyncModTemplatePlugin.IsDiveAscendInputHeld() && playerDiveController.IsUnderSurface())
			{
				playerDiveController.Dive(dt, ascend: true, out var defaultMoveDir);
				__state = new SwimmingUpdateState(playerDiveController, defaultMoveDir);
			}
			else if (!flag && ServerSyncModTemplatePlugin.IsDiveDescendInputHeld() && playerDiveController.CanDive())
			{
				playerDiveController.Dive(dt, ascend: false, out var defaultMoveDir2);
				__state = new SwimmingUpdateState(playerDiveController, defaultMoveDir2);
			}
			else if (__instance.IsOnGround() || !playerDiveController.IsDiving())
			{
				playerDiveController.ResetSwimDepthToDefault();
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Character), "UpdateSwimming")]
		private static void CharacterUpdateSwimmingPostfix(Character __instance, ref SwimmingUpdateState __state)
		{
			//IL_003c: 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)
			__state.Diver?.ResetSwimSpeedOverride();
			__state.Diver?.EndSwimmingUpdateContext();
			if (__state.OriginalMoveDir.HasValue)
			{
				__instance.m_moveDir = __state.OriginalMoveDir.Value;
				__state = default(SwimmingUpdateState);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Character), "UpdateRotation")]
		private static void CharacterUpdateRotationPrefix(Character __instance, out Quaternion? __state)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				__state = null;
				return;
			}
			PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
			if ((Object)(object)playerDiveController != (Object)null && playerDiveController.IsInSwimmingUpdateContext())
			{
				__state = ((Component)__instance).transform.rotation;
			}
			else
			{
				__state = null;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Character), "UpdateRotation")]
		private static void CharacterUpdateRotationPostfix(Character __instance, float turnSpeed, float dt, ref Quaternion? __state)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			if (__state.HasValue && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !(((Component)__instance).transform.rotation != __state.Value))
			{
				PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
				if (!((Object)(object)playerDiveController == (Object)null) && playerDiveController.IsInSwimmingUpdateContext() && playerDiveController.IsUnderSurface())
				{
					Player player = playerDiveController.Player;
					Quaternion val = ((((Character)player).AlwaysRotateCamera() || ((Character)player).m_moveDir == Vector3.zero) ? ((Character)player).m_lookYaw : Quaternion.LookRotation(((Character)player).m_moveDir));
					float num = turnSpeed * ((Character)player).GetAttackSpeedFactorRotation();
					((Component)player).transform.rotation = Quaternion.RotateTowards(((Component)player).transform.rotation, val, num * dt);
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "OnSwimming")]
		private static void PlayerOnSwimmingPrefix(Player __instance, Vector3 targetVel, float dt)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
				if (!((Object)(object)playerDiveController == (Object)null))
				{
					playerDiveController.RegenWaterStamina(dt);
					playerDiveController.ApplyIdleMidwaterStaminaDrain(dt);
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "OnSwimming")]
		private static void PlayerOnSwimmingPostfix(Player __instance, Vector3 targetVel, float dt)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !(((Vector3)(ref targetVel)).magnitude < 0.1f))
			{
				PlayerDiveController playerDiveController = PlayerDiveUtils.EnsureLocalDiver();
				if (!((Object)(object)playerDiveController == (Object)null))
				{
					playerDiveController.ApplyExtraSwimStaminaDrain(dt);
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })]
		private static void PlayerUpdateStatsPrefix(Player __instance, out ResourceUpdateState __state)
		{
			__state = default(ResourceUpdateState);
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && PlayerDiveUtils.TryGetLocalDiver(__instance, out PlayerDiveController diver) && diver.ShouldTreatAsSwimming())
			{
				__state = new ResourceUpdateState(diver, __instance.m_eitr);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })]
		private static void PlayerUpdateStatsPostfix(Player __instance, ref ResourceUpdateState __state)
		{
			if ((Object)(object)__state.Diver == (Object)null)
			{
				return;
			}
			float num = __instance.m_eitr - __state.Eitr;
			if (num <= 0f)
			{
				return;
			}
			float num2 = (__state.Diver.IsHeadUnderwater() ? ServerSyncModTemplatePlugin._midwaterEitrRegenRateMultiplier.Value : ServerSyncModTemplatePlugin._surfaceEitrRegenRateMultiplier.Value);
			if (!(num2 >= 1f))
			{
				__instance.m_eitr = Mathf.Min(((Character)__instance).GetMaxEitr(), __state.Eitr + num * Mathf.Clamp01(num2));
				if ((Object)(object)((Character)__instance).m_nview != (Object)null && ((Character)__instance).m_nview.IsValid())
				{
					((Character)__instance).m_nview.GetZDO().Set(ZDOVars.s_eitr, __instance.m_eitr);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class PlayerDiveKeyHints
	{
		private enum InputHintMode
		{
			Keyboard,
			Gamepad
		}

		private sealed class DiveHintCell
		{
			public GameObject Root { get; }

			private TMP_Text? Label { get; }

			private TMP_Text? Key { get; }

			private TMP_Text[] ExtraTexts { get; }

			public bool IsValid
			{
				get
				{
					if (Object.op_Implicit((Object)(object)Root) && (Object)(object)Label != (Object)null)
					{
						return (Object)(object)Key != (Object)null;
					}
					return false;
				}
			}

			public DiveHintCell(GameObject root)
			{
				Root = root;
				TMP_Text[] array = root.GetComponentsInChildren<TMP_Text>(true).ToArray();
				Key = FindKeyText(array);
				Label = FindLabelText(array, Key);
				ExtraTexts = array.Where((TMP_Text text) => (Object)(object)text != (Object)(object)Label && (Object)(object)text != (Object)(object)Key).ToArray();
			}

			public void Configure(string label, string keyText)
			{
				if (IsValid)
				{
					((Component)Label).gameObject.SetActive(true);
					Label.text = label;
					TMP_Text[] extraTexts = ExtraTexts;
					for (int i = 0; i < extraTexts.Length; i++)
					{
						((Component)extraTexts[i]).gameObject.SetActive(false);
					}
					((Component)Key).gameObject.SetActive(true);
					Key.text = keyText;
				}
			}

			public void SetActive(bool active)
			{
				if (Object.op_Implicit((Object)(object)Root))
				{
					Root.SetActive(active);
				}
			}

			public void Destroy()
			{
				if (Object.op_Implicit((Object)(object)Root))
				{
					Object.Destroy((Object)(object)Root);
				}
			}
		}

		private sealed class DiveHintSet
		{
			public GameObject? Root { get; }

			public DiveHintCell RunHint { get; }

			public DiveHintCell DescendHint { get; }

			public DiveHintCell AscendHint { get; }

			private bool ShowRunHint { get; set; } = true;


			public bool IsValid
			{
				get
				{
					if (RunHint.IsValid && DescendHint.IsValid && AscendHint.IsValid)
					{
						if (!((Object)(object)Root == (Object)null))
						{
							return Object.op_Implicit((Object)(object)Root);
						}
						return true;
					}
					return false;
				}
			}

			public DiveHintSet(GameObject? root, DiveHintCell runHint, DiveHintCell descendHint, DiveHintCell ascendHint)
			{
				Root = root;
				RunHint = runHint;
				DescendHint = descendHint;
				AscendHint = ascendHint;
			}

			public void Configure(bool showRunHint, string fastSwimLabel, string runKey, string descendKey, string ascendKey)
			{
				ShowRunHint = showRunHint;
				if (showRunHint)
				{
					RunHint.Configure(fastSwimLabel, runKey);
				}
				else
				{
					RunHint.SetActive(active: false);
				}
				DescendHint.Configure(DiveLocalization.Localize("$divein_descend"), descendKey);
				AscendHint.Configure(DiveLocalization.Localize("$divein_ascend"), ascendKey);
			}

			public void SetActive(bool active)
			{
				RunHint.SetActive(active && ShowRunHint);
				DescendHint.SetActive(active);
				AscendHint.SetActive(active);
				if (Object.op_Implicit((Object)(object)Root))
				{
					Root.SetActive(active);
				}
			}

			public void RebuildLayout()
			{
				PlayerDiveKeyHints.RebuildLayout((GameObject?)(((Object)(object)Root != (Object)null) ? ((object)Root) : ((object)DescendHint.Root)));
			}

			public void Destroy()
			{
				if (Object.op_Implicit((Object)(object)Root))
				{
					Object.Destroy((Object)(object)Root);
					return;
				}
				RunHint.Destroy();
				DescendHint.Destroy();
				AscendHint.Destroy();
			}
		}

		private const string SwimmingHintsRootName = "DiveIn_SwimmingHints";

		private const string RunHintName = "DiveIn_RunHint";

		private const string DescendHintName = "DiveIn_DescendHint";

		private const string AscendHintName = "DiveIn_AscendHint";

		private static KeyHints? _owner;

		private static DiveHintSet? _swimmingHints;

		private static DiveHintSet? _combatHints;

		private static InputHintMode _hintMode;

		private static readonly string[] KeyTextNameTokens = new string[6] { "key", "bind", "binding", "shortcut", "input", "button" };

		private static readonly string[] LabelTextNameTokens = new string[4] { "label", "action", "name", "title" };

		[HarmonyPostfix]
		[HarmonyPatch(typeof(KeyHints), "Awake")]
		private static void KeyHintsAwakePostfix(KeyHints __instance)
		{
			EnsureHints(__instance);
			UpdateDiveHints(__instance);
		}

		[Harmon