Decompiled source of Rodnarok v0.1.1

Rodnarok.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.LowLevel;
using UnityEngine.Playables;
using UnityEngine.PlayerLoop;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("isimp")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 isimp")]
[assembly: AssemblyDescription("Fishing you can read: the bait decides the fish in any water, wary schools, bites and fights shown on the float and the crosshair, a line that snaps if held too long, fishing from a seat, and a trophy that calms the waves.")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+172c648bce8083a7fb13456aa7ca18030ea5f572")]
[assembly: AssemblyProduct("Rodnarok")]
[assembly: AssemblyTitle("Rodnarok")]
[assembly: InternalsVisibleTo("Rodnarok.SelfTest")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ServerSync
{
	[PublicAPI]
	public abstract class OwnConfigEntryBase
	{
		public object? LocalBaseValue;

		public bool SynchronizedConfig = true;

		public abstract ConfigEntryBase BaseConfig { get; }
	}
	[PublicAPI]
	public class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase()
	{
		public readonly ConfigEntry<T> SourceConfig = sourceConfig;

		public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig;

		public T Value
		{
			get
			{
				return SourceConfig.Value;
			}
			set
			{
				SourceConfig.Value = value;
			}
		}

		public void AssignLocalValue(T value)
		{
			if (LocalBaseValue == null)
			{
				Value = value;
			}
			else
			{
				LocalBaseValue = value;
			}
		}
	}
	public abstract class CustomSyncedValueBase
	{
		public object? LocalBaseValue;

		public readonly string Identifier;

		public readonly Type Type;

		private object? boxedValue;

		protected bool localIsOwner;

		public readonly int Priority;

		public object? BoxedValue
		{
			get
			{
				return boxedValue;
			}
			set
			{
				boxedValue = value;
				this.ValueChanged?.Invoke();
			}
		}

		public event Action? ValueChanged;

		protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority)
		{
			Priority = priority;
			Identifier = identifier;
			Type = type;
			configSync.AddCustomValue(this);
			localIsOwner = configSync.IsSourceOfTruth;
			configSync.SourceOfTruthChanged += delegate(bool truth)
			{
				localIsOwner = truth;
			};
		}
	}
	[PublicAPI]
	public sealed class CustomSyncedValue<T> : CustomSyncedValueBase
	{
		public T Value
		{
			get
			{
				return (T)base.BoxedValue;
			}
			set
			{
				base.BoxedValue = value;
			}
		}

		public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0)
			: base(configSync, identifier, typeof(T), priority)
		{
			Value = value;
		}

		public void AssignLocalValue(T value)
		{
			if (localIsOwner)
			{
				Value = value;
			}
			else
			{
				LocalBaseValue = value;
			}
		}
	}
	internal class ConfigurationManagerAttributes
	{
		[UsedImplicitly]
		public bool? ReadOnly = false;
	}
	[PublicAPI]
	public class ConfigSync
	{
		[HarmonyPatch(typeof(ZRpc), "HandlePackage")]
		private static class SnatchCurrentlyHandlingRPC
		{
			public static ZRpc? currentRpc;

			[HarmonyPrefix]
			private static void Prefix(ZRpc __instance)
			{
				currentRpc = __instance;
			}
		}

		[HarmonyPatch(typeof(ZNet), "Awake")]
		internal static class RegisterRPCPatch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance)
			{
				isServer = __instance.IsServer();
				foreach (ConfigSync configSync2 in configSyncs)
				{
					ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync);
					if (isServer)
					{
						configSync2.InitialSyncDone = true;
						Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections"));
					}
				}
				if (isServer)
				{
					((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges());
				}
				static void SendAdmin(List<ZNetPeer> peers, bool isAdmin)
				{
					ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1]
					{
						new PackageEntry
						{
							section = "Internal",
							key = "lockexempt",
							type = typeof(bool),
							value = isAdmin
						}
					});
					ConfigSync configSync = configSyncs.First();
					if (configSync != null)
					{
						((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package));
					}
				}
				static IEnumerator WatchAdminListChanges()
				{
					MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
					SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
					List<string> CurrentList = new List<string>(adminList.GetList());
					while (true)
					{
						yield return (object)new WaitForSeconds(30f);
						if (!adminList.GetList().SequenceEqual(CurrentList))
						{
							CurrentList = new List<string>(adminList.GetList());
							List<ZNetPeer> list = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p)
							{
								string hostName = p.m_rpc.GetSocket().GetHostName();
								return ((object)listContainsId != null) ? ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })) : adminList.Contains(hostName);
							}).ToList();
							SendAdmin(ZNet.instance.GetPeers().Except(list).ToList(), isAdmin: false);
							SendAdmin(list, isAdmin: true);
						}
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		private static class RegisterClientRPCPatch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance, ZNetPeer peer)
			{
				if (__instance.IsServer())
				{
					return;
				}
				foreach (ConfigSync configSync in configSyncs)
				{
					peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync);
				}
			}
		}

		private class ParsedConfigs
		{
			public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>();

			public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>();
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private class ResetConfigsOnShutdown
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				ProcessingServerUpdate = true;
				foreach (ConfigSync configSync in configSyncs)
				{
					configSync.resetConfigsFromServer();
					configSync.IsSourceOfTruth = true;
					configSync.InitialSyncDone = false;
				}
				ProcessingServerUpdate = false;
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private class SendConfigsAfterLogin
		{
			private class BufferingSocket : ZPlayFabSocket, ISocket
			{
				public volatile bool finished;

				public volatile int versionMatchQueued = -1;

				public readonly List<ZPackage> Package = new List<ZPackage>();

				public readonly ISocket Original;

				public BufferingSocket(ISocket original)
				{
					Original = original;
					((ZPlayFabSocket)this)..ctor();
				}

				public bool IsConnected()
				{
					return Original.IsConnected();
				}

				public ZPackage Recv()
				{
					return Original.Recv();
				}

				public int GetSendQueueSize()
				{
					return Original.GetSendQueueSize();
				}

				public int GetCurrentSendRate()
				{
					return Original.GetCurrentSendRate();
				}

				public bool IsHost()
				{
					return Original.IsHost();
				}

				public void Dispose()
				{
					Original.Dispose();
				}

				public bool GotNewData()
				{
					return Original.GotNewData();
				}

				public void Close()
				{
					Original.Close();
				}

				public string GetEndPointString()
				{
					return Original.GetEndPointString();
				}

				public void GetAndResetStats(out int totalSent, out int totalRecv)
				{
					Original.GetAndResetStats(ref totalSent, ref totalRecv);
				}

				public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec)
				{
					Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec);
				}

				public ISocket Accept()
				{
					return Original.Accept();
				}

				public int GetHostPort()
				{
					return Original.GetHostPort();
				}

				public bool Flush()
				{
					return Original.Flush();
				}

				public string GetHostName()
				{
					return Original.GetHostName();
				}

				public void VersionMatch()
				{
					if (finished)
					{
						Original.VersionMatch();
					}
					else
					{
						versionMatchQueued = Package.Count;
					}
				}

				public void Send(ZPackage pkg)
				{
					//IL_004c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0052: Expected O, but got Unknown
					int pos = pkg.GetPos();
					pkg.SetPos(0);
					int num = pkg.ReadInt();
					if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished)
					{
						ZPackage val = new ZPackage(pkg.GetArray());
						val.SetPos(pos);
						Package.Add(val);
					}
					else
					{
						pkg.SetPos(pos);
						Original.Send(pkg);
					}
				}
			}

			[HarmonyPriority(800)]
			[HarmonyPrefix]
			private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc)
			{
				//IL_0073: Unknown result type (might be due to invalid IL or missing references)
				if (!__instance.IsServer())
				{
					return;
				}
				BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket());
				AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket);
				object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
				ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
				if (val != null && (int)ZNet.m_onlineBackend != 0)
				{
					FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket");
					object? value = fieldInfo.GetValue(val);
					ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null);
					if (val2 != null)
					{
						typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId);
					}
					fieldInfo.SetValue(val, bufferingSocket);
				}
				if (__state == null)
				{
					__state = new Dictionary<Assembly, BufferingSocket>();
				}
				__state[Assembly.GetExecutingAssembly()] = bufferingSocket;
			}

			[HarmonyPostfix]
			private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc)
			{
				ZNetPeer peer;
				if (__instance.IsServer())
				{
					object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
					peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
					if (peer == null)
					{
						SendBufferedData();
					}
					else
					{
						((MonoBehaviour)__instance).StartCoroutine(sendAsync());
					}
				}
				void SendBufferedData()
				{
					if (rpc.GetSocket() is BufferingSocket bufferingSocket)
					{
						AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original);
						object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc });
						ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null);
						if (val != null)
						{
							AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original);
						}
					}
					BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()];
					bufferingSocket2.finished = true;
					for (int i = 0; i < bufferingSocket2.Package.Count; i++)
					{
						if (i == bufferingSocket2.versionMatchQueued)
						{
							bufferingSocket2.Original.VersionMatch();
						}
						bufferingSocket2.Original.Send(bufferingSocket2.Package[i]);
					}
					if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued)
					{
						bufferingSocket2.Original.VersionMatch();
					}
				}
				IEnumerator sendAsync()
				{
					foreach (ConfigSync configSync in configSyncs)
					{
						List<PackageEntry> list = new List<PackageEntry>();
						if (configSync.CurrentVersion != null)
						{
							list.Add(new PackageEntry
							{
								section = "Internal",
								key = "serverversion",
								type = typeof(string),
								value = configSync.CurrentVersion
							});
						}
						MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
						SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
						list.Add(new PackageEntry
						{
							section = "Internal",
							key = "lockexempt",
							type = typeof(bool),
							value = (((object)methodInfo == null) ? ((object)val.Contains(rpc.GetSocket().GetHostName())) : methodInfo.Invoke(ZNet.instance, new object[2]
							{
								val,
								rpc.GetSocket().GetHostName()
							}))
						});
						ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, list, partial: false);
						yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package));
					}
					SendBufferedData();
				}
			}
		}

		private class PackageEntry
		{
			public string section;

			public string key;

			public Type type;

			public object? value;
		}

		[HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")]
		private static class PreventSavingServerInfo
		{
			[HarmonyPrefix]
			private static bool Prefix(ConfigEntryBase __instance, ref string __result)
			{
				OwnConfigEntryBase ownConfigEntryBase = configData(__instance);
				if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase))
				{
					return true;
				}
				__result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType);
				return false;
			}
		}

		[HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")]
		private static class PreventConfigRereadChangingValues
		{
			[HarmonyPrefix]
			private static bool Prefix(ConfigEntryBase __instance, string value)
			{
				OwnConfigEntryBase ownConfigEntryBase = configData(__instance);
				if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null)
				{
					return true;
				}
				try
				{
					ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}");
				}
				return false;
			}
		}

		private class InvalidDeserializationTypeException : Exception
		{
			public string expected;

			public string received;

			public string field = "";
		}

		public static bool ProcessingServerUpdate;

		public readonly string Name;

		public string? DisplayName;

		public string? CurrentVersion;

		public string? MinimumRequiredVersion;

		public bool ModRequired;

		private bool? forceConfigLocking;

		private bool isSourceOfTruth = true;

		private static readonly HashSet<ConfigSync> configSyncs;

		private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>();

		private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>();

		private static bool isServer;

		private static bool lockExempt;

		private OwnConfigEntryBase? lockedConfig;

		private const byte PARTIAL_CONFIGS = 1;

		private const byte FRAGMENTED_CONFIG = 2;

		private const byte COMPRESSED_CONFIG = 4;

		private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>();

		private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>();

		private static long packageCounter;

		public bool IsLocked
		{
			get
			{
				bool? flag = forceConfigLocking;
				bool num;
				if (!flag.HasValue)
				{
					if (lockedConfig == null)
					{
						goto IL_0051;
					}
					num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0;
				}
				else
				{
					num = flag == true;
				}
				if (num)
				{
					return !lockExempt;
				}
				goto IL_0051;
				IL_0051:
				return false;
			}
			set
			{
				forceConfigLocking = value;
			}
		}

		public bool IsAdmin
		{
			get
			{
				if (!lockExempt)
				{
					return isSourceOfTruth;
				}
				return true;
			}
		}

		public bool IsSourceOfTruth
		{
			get
			{
				return isSourceOfTruth;
			}
			private set
			{
				if (value != isSourceOfTruth)
				{
					isSourceOfTruth = value;
					this.SourceOfTruthChanged?.Invoke(value);
				}
			}
		}

		public bool InitialSyncDone { get; private set; }

		public event Action<bool>? SourceOfTruthChanged;

		private event Action? lockedConfigChanged;

		static ConfigSync()
		{
			ProcessingServerUpdate = false;
			configSyncs = new HashSet<ConfigSync>();
			lockExempt = false;
			packageCounter = 0L;
			RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle);
		}

		public ConfigSync(string name)
		{
			Name = name;
			configSyncs.Add(this);
			new VersionCheck(this);
		}

		public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry)
		{
			OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry);
			SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>;
			if (syncedEntry == null)
			{
				syncedEntry = new SyncedConfigEntry<T>(configEntry);
				AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1]
				{
					new ConfigurationManagerAttributes()
				}.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray());
				configEntry.SettingChanged += delegate
				{
					if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig)
					{
						Broadcast(0L, (ConfigEntryBase)configEntry);
					}
				};
				allConfigs.Add(syncedEntry);
			}
			return syncedEntry;
		}

		public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible
		{
			if (lockedConfig != null)
			{
				throw new Exception("Cannot initialize locking ConfigEntry twice");
			}
			lockedConfig = AddConfigEntry<T>(lockingConfig);
			lockingConfig.SettingChanged += delegate
			{
				this.lockedConfigChanged?.Invoke();
			};
			return (SyncedConfigEntry<T>)lockedConfig;
		}

		internal void AddCustomValue(CustomSyncedValueBase customValue)
		{
			if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier))
			{
				throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)");
			}
			allCustomValues.Add(customValue);
			allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority));
			customValue.ValueChanged += delegate
			{
				if (!ProcessingServerUpdate)
				{
					Broadcast(0L, customValue);
				}
			};
		}

		private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package)
		{
			lockedConfigChanged += serverLockedSettingChanged;
			IsSourceOfTruth = false;
			if (HandleConfigSyncRPC(0L, package, clientUpdate: false))
			{
				InitialSyncDone = true;
			}
		}

		private void RPC_FromOtherClientConfigSync(long sender, ZPackage package)
		{
			HandleConfigSyncRPC(sender, package, clientUpdate: true);
		}

		private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate)
		{
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Expected O, but got Unknown
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			try
			{
				if (isServer && IsLocked)
				{
					ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc;
					object obj;
					if (currentRpc == null)
					{
						obj = null;
					}
					else
					{
						ISocket socket = currentRpc.GetSocket();
						obj = ((socket != null) ? socket.GetHostName() : null);
					}
					string text = (string)obj;
					if (text != null)
					{
						MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null);
						SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
						if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text }))))
						{
							return false;
						}
					}
				}
				cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv)
				{
					if (kv.Key < DateTimeOffset.Now.Ticks)
					{
						configValueCache.Remove(kv.Value);
						return true;
					}
					return false;
				});
				byte b = package.ReadByte();
				if ((b & 2) != 0)
				{
					long num = package.ReadLong();
					string text2 = sender.ToString() + num;
					if (!configValueCache.TryGetValue(text2, out var value))
					{
						value = new SortedDictionary<int, byte[]>();
						configValueCache[text2] = value;
						cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2));
					}
					int key = package.ReadInt();
					int num2 = package.ReadInt();
					value.Add(key, package.ReadByteArray());
					if (value.Count < num2)
					{
						return false;
					}
					configValueCache.Remove(text2);
					package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray());
					b = package.ReadByte();
				}
				ProcessingServerUpdate = true;
				if ((b & 4) != 0)
				{
					MemoryStream stream = new MemoryStream(package.ReadByteArray());
					MemoryStream memoryStream = new MemoryStream();
					using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
					{
						deflateStream.CopyTo(memoryStream);
					}
					package = new ZPackage(memoryStream.ToArray());
					b = package.ReadByte();
				}
				if ((b & 1) == 0)
				{
					resetConfigsFromServer();
				}
				ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package);
				ConfigFile val2 = null;
				bool saveOnConfigSet = false;
				foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues)
				{
					if (!isServer && configValue.Key.LocalBaseValue == null)
					{
						configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue;
					}
					if (val2 == null)
					{
						val2 = configValue.Key.BaseConfig.ConfigFile;
						saveOnConfigSet = val2.SaveOnConfigSet;
						val2.SaveOnConfigSet = false;
					}
					configValue.Key.BaseConfig.BoxedValue = configValue.Value;
				}
				if (val2 != null)
				{
					val2.SaveOnConfigSet = saveOnConfigSet;
					val2.Save();
				}
				foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues)
				{
					if (!isServer)
					{
						CustomSyncedValueBase key2 = customValue.Key;
						if (key2.LocalBaseValue == null)
						{
							key2.LocalBaseValue = customValue.Key.BoxedValue;
						}
					}
					customValue.Key.BoxedValue = customValue.Value;
				}
				Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name));
				if (!isServer)
				{
					serverLockedSettingChanged();
				}
				return true;
			}
			finally
			{
				ProcessingServerUpdate = false;
			}
		}

		private ParsedConfigs ReadConfigsFromPackage(ZPackage package)
		{
			ParsedConfigs parsedConfigs = new ParsedConfigs();
			Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c);
			Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c);
			int num = package.ReadInt();
			for (int num2 = 0; num2 < num; num2++)
			{
				string text = package.ReadString();
				string text2 = package.ReadString();
				string text3 = package.ReadString();
				Type type = Type.GetType(text3);
				if (text3 == "" || type != null)
				{
					object obj;
					try
					{
						obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type));
					}
					catch (InvalidDeserializationTypeException ex)
					{
						Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected));
						continue;
					}
					OwnConfigEntryBase value2;
					if (text == "Internal")
					{
						CustomSyncedValueBase value;
						if (text2 == "serverversion")
						{
							if (obj?.ToString() != CurrentVersion)
							{
								Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown")));
							}
						}
						else if (text2 == "lockexempt")
						{
							if (obj is bool flag)
							{
								lockExempt = flag;
							}
						}
						else if (dictionary2.TryGetValue(text2, out value))
						{
							if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3)
							{
								parsedConfigs.customValues[value] = obj;
								continue;
							}
							Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName));
						}
					}
					else if (dictionary.TryGetValue(text + "_" + text2, out value2))
					{
						Type type2 = configType(value2.BaseConfig);
						if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3)
						{
							parsedConfigs.configValues[value2] = obj;
							continue;
						}
						Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName));
					}
					else
					{
						Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match."));
					}
					continue;
				}
				Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs"));
				return new ParsedConfigs();
			}
			return parsedConfigs;
		}

		private static bool isWritableConfig(OwnConfigEntryBase config)
		{
			ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config));
			if (configSync == null)
			{
				return true;
			}
			if (!configSync.IsSourceOfTruth && config.SynchronizedConfig && config.LocalBaseValue != null)
			{
				if (!configSync.IsLocked)
				{
					if (config == configSync.lockedConfig)
					{
						return lockExempt;
					}
					return true;
				}
				return false;
			}
			return true;
		}

		private void serverLockedSettingChanged()
		{
			foreach (OwnConfigEntryBase allConfig in allConfigs)
			{
				configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig);
			}
		}

		private void resetConfigsFromServer()
		{
			ConfigFile val = null;
			bool saveOnConfigSet = false;
			foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null))
			{
				if (val == null)
				{
					val = item.BaseConfig.ConfigFile;
					saveOnConfigSet = val.SaveOnConfigSet;
					val.SaveOnConfigSet = false;
				}
				item.BaseConfig.BoxedValue = item.LocalBaseValue;
				item.LocalBaseValue = null;
			}
			if (val != null)
			{
				val.SaveOnConfigSet = saveOnConfigSet;
			}
			foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null))
			{
				item2.BoxedValue = item2.LocalBaseValue;
				item2.LocalBaseValue = null;
			}
			lockedConfigChanged -= serverLockedSettingChanged;
			serverLockedSettingChanged();
		}

		private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package)
		{
			ZRoutedRpc rpc = ZRoutedRpc.instance;
			if (rpc == null)
			{
				yield break;
			}
			byte[] data = package.GetArray();
			if (data != null && data.LongLength > 250000)
			{
				int fragments = (int)(1 + (data.LongLength - 1) / 250000);
				long packageIdentifier = ++packageCounter;
				int fragment = 0;
				while (fragment < fragments)
				{
					foreach (bool item in waitForQueue())
					{
						yield return item;
					}
					if (peer.m_socket.IsConnected())
					{
						ZPackage val = new ZPackage();
						val.Write((byte)2);
						val.Write(packageIdentifier);
						val.Write(fragment);
						val.Write(fragments);
						val.Write(data.Skip(250000 * fragment).Take(250000).ToArray());
						SendPackage(val);
						if (fragment != fragments - 1)
						{
							yield return true;
						}
						int num = fragment + 1;
						fragment = num;
						continue;
					}
					break;
				}
				yield break;
			}
			foreach (bool item2 in waitForQueue())
			{
				yield return item2;
			}
			SendPackage(package);
			void SendPackage(ZPackage pkg)
			{
				string text = Name + " ConfigSync";
				if (isServer)
				{
					peer.m_rpc.Invoke(text, new object[1] { pkg });
				}
				else
				{
					rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg });
				}
			}
			IEnumerable<bool> waitForQueue()
			{
				float timeout = Time.time + 30f;
				while (peer.m_socket.GetSendQueueSize() > 20000)
				{
					if (Time.time > timeout)
					{
						Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout");
						peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 });
						ZNet.instance.Disconnect(peer);
						break;
					}
					yield return false;
				}
			}
		}

		private IEnumerator sendZPackage(long target, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance))
			{
				return Enumerable.Empty<object>().GetEnumerator();
			}
			List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance);
			if (target != 0L)
			{
				list = list.Where((ZNetPeer p) => p.m_uid == target).ToList();
			}
			return sendZPackage(list, package);
		}

		private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance))
			{
				yield break;
			}
			byte[] array = package.GetArray();
			if (array != null && array.LongLength > 10000)
			{
				ZPackage val = new ZPackage();
				val.Write((byte)4);
				MemoryStream memoryStream = new MemoryStream();
				using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal))
				{
					deflateStream.Write(array, 0, array.Length);
				}
				val.Write(memoryStream.ToArray());
				package = val;
			}
			List<IEnumerator<bool>> writers = (from p in peers
				where p.IsReady()
				select distributeConfigToPeers(p, package)).ToList();
			writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext());
			while (writers.Count > 0)
			{
				yield return null;
				writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext());
			}
		}

		private void Broadcast(long target, params ConfigEntryBase[] configs)
		{
			if (!IsLocked || isServer)
			{
				ZPackage package = ConfigsToPackage(configs);
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package));
				}
			}
		}

		private void Broadcast(long target, params CustomSyncedValueBase[] customValues)
		{
			if (!IsLocked || isServer)
			{
				ZPackage package = ConfigsToPackage(null, customValues);
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package));
				}
			}
		}

		private static OwnConfigEntryBase? configData(ConfigEntryBase config)
		{
			return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault();
		}

		public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config)
		{
			return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault();
		}

		private static T configAttribute<T>(ConfigEntryBase config)
		{
			return config.Description.Tags.OfType<T>().First();
		}

		private static Type configType(ConfigEntryBase config)
		{
			return configType(config.SettingType);
		}

		private static Type configType(Type type)
		{
			if (!type.IsEnum)
			{
				return type;
			}
			return Enum.GetUnderlyingType(type);
		}

		private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>();
			List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>();
			ZPackage val = new ZPackage();
			val.Write(partial ? ((byte)1) : ((byte)0));
			val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0));
			foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>())
			{
				AddEntryToPackage(val, item);
			}
			foreach (CustomSyncedValueBase item2 in list2)
			{
				AddEntryToPackage(val, new PackageEntry
				{
					section = "Internal",
					key = item2.Identifier,
					type = item2.Type,
					value = item2.BoxedValue
				});
			}
			foreach (ConfigEntryBase item3 in list)
			{
				AddEntryToPackage(val, new PackageEntry
				{
					section = item3.Definition.Section,
					key = item3.Definition.Key,
					type = configType(item3),
					value = item3.BoxedValue
				});
			}
			return val;
		}

		private static void AddEntryToPackage(ZPackage package, PackageEntry entry)
		{
			package.Write(entry.section);
			package.Write(entry.key);
			package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type));
			AddValueToZPackage(package, entry.value);
		}

		private static string GetZPackageTypeString(Type type)
		{
			return type.AssemblyQualifiedName;
		}

		private static void AddValueToZPackage(ZPackage package, object? value)
		{
			Type type = value?.GetType();
			if (value is Enum)
			{
				value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture);
			}
			else
			{
				if (value is ICollection collection)
				{
					package.Write(collection.Count);
					{
						foreach (object item in collection)
						{
							AddValueToZPackage(package, item);
						}
						return;
					}
				}
				if ((object)type != null && type.IsValueType && !type.IsPrimitive)
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					package.Write(fields.Length);
					FieldInfo[] array = fields;
					foreach (FieldInfo fieldInfo in array)
					{
						package.Write(GetZPackageTypeString(fieldInfo.FieldType));
						AddValueToZPackage(package, fieldInfo.GetValue(value));
					}
					return;
				}
			}
			ZRpc.Serialize(new object[1] { value }, ref package);
		}

		private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type)
		{
			if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum)
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				int num = package.ReadInt();
				if (num != fields.Length)
				{
					throw new InvalidDeserializationTypeException
					{
						received = $"(field count: {num})",
						expected = $"(field count: {fields.Length})"
					};
				}
				object uninitializedObject = FormatterServices.GetUninitializedObject(type);
				FieldInfo[] array = fields;
				foreach (FieldInfo fieldInfo in array)
				{
					string text = package.ReadString();
					if (text != GetZPackageTypeString(fieldInfo.FieldType))
					{
						throw new InvalidDeserializationTypeException
						{
							received = text,
							expected = GetZPackageTypeString(fieldInfo.FieldType),
							field = fieldInfo.Name
						};
					}
					fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType));
				}
				return uninitializedObject;
			}
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >))
			{
				int num2 = package.ReadInt();
				IDictionary dictionary = (IDictionary)Activator.CreateInstance(type);
				Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments);
				FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
				FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
				for (int j = 0; j < num2; j++)
				{
					object obj = ReadValueWithTypeFromZPackage(package, type2);
					dictionary.Add(field.GetValue(obj), field2.GetValue(obj));
				}
				return dictionary;
			}
			if (type != typeof(List<string>) && type.IsGenericType)
			{
				Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]);
				if ((object)type3 != null && type3.IsAssignableFrom(type))
				{
					int num3 = package.ReadInt();
					object obj2 = Activator.CreateInstance(type);
					MethodInfo method = type3.GetMethod("Add");
					for (int k = 0; k < num3; k++)
					{
						method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) });
					}
					return obj2;
				}
			}
			ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo));
			AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type);
			List<object> source = new List<object>();
			ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source);
			return source.First();
		}
	}
	[PublicAPI]
	[HarmonyPatch]
	public class VersionCheck
	{
		private static readonly HashSet<VersionCheck> versionChecks;

		private static readonly Dictionary<string, string> notProcessedNames;

		public string Name;

		private string? displayName;

		private string? currentVersion;

		private string? minimumRequiredVersion;

		public bool ModRequired = true;

		private string? ReceivedCurrentVersion;

		private string? ReceivedMinimumRequiredVersion;

		private readonly List<ZRpc> ValidatedClients = new List<ZRpc>();

		private ConfigSync? ConfigSync;

		public string DisplayName
		{
			get
			{
				return displayName ?? Name;
			}
			set
			{
				displayName = value;
			}
		}

		public string CurrentVersion
		{
			get
			{
				return currentVersion ?? "0.0.0";
			}
			set
			{
				currentVersion = value;
			}
		}

		public string MinimumRequiredVersion
		{
			get
			{
				string text = minimumRequiredVersion;
				if (text == null)
				{
					if (!ModRequired)
					{
						return "0.0.0";
					}
					text = CurrentVersion;
				}
				return text;
			}
			set
			{
				minimumRequiredVersion = value;
			}
		}

		private static void PatchServerSync()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null));
			if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0)
			{
				return;
			}
			Harmony val = new Harmony("org.bepinex.helpers.ServerSync");
			foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) })
				where t.IsClass
				select t)
			{
				val.PatchAll(item);
			}
		}

		static VersionCheck()
		{
			versionChecks = new HashSet<VersionCheck>();
			notProcessedNames = new Dictionary<string, string>();
			typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1]
			{
				new Action(PatchServerSync)
			});
		}

		public VersionCheck(string name)
		{
			Name = name;
			ModRequired = true;
			versionChecks.Add(this);
		}

		public VersionCheck(ConfigSync configSync)
		{
			ConfigSync = configSync;
			Name = ConfigSync.Name;
			versionChecks.Add(this);
		}

		public void Initialize()
		{
			ReceivedCurrentVersion = null;
			ReceivedMinimumRequiredVersion = null;
			if (ConfigSync != null)
			{
				Name = ConfigSync.Name;
				DisplayName = ConfigSync.DisplayName;
				CurrentVersion = ConfigSync.CurrentVersion;
				MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion;
				ModRequired = ConfigSync.ModRequired;
			}
		}

		private bool IsVersionOk()
		{
			if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null)
			{
				return !ModRequired;
			}
			bool num = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion);
			bool flag = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion);
			return num && flag;
		}

		private string ErrorClient()
		{
			if (ReceivedMinimumRequiredVersion == null)
			{
				return DisplayName + " is not installed on the server.";
			}
			if (!(new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)))
			{
				return DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + ".";
			}
			return DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".";
		}

		private string ErrorServer(ZRpc rpc)
		{
			return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion;
		}

		private string Error(ZRpc? rpc = null)
		{
			if (rpc != null)
			{
				return ErrorServer(rpc);
			}
			return ErrorClient();
		}

		private static VersionCheck[] GetFailedClient()
		{
			return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray();
		}

		private static VersionCheck[] GetFailedServer(ZRpc rpc)
		{
			return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray();
		}

		private static void Logout()
		{
			Game.instance.Logout(true, true);
			AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3);
		}

		private static void DisconnectClient(ZRpc rpc)
		{
			rpc.Invoke("Error", new object[1] { 3 });
		}

		private static void CheckVersion(ZRpc rpc, ZPackage pkg)
		{
			CheckVersion(rpc, pkg, null);
		}

		private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action<ZRpc, ZPackage>? original)
		{
			string text = pkg.ReadString();
			string text2 = pkg.ReadString();
			string text3 = pkg.ReadString();
			bool flag = false;
			foreach (VersionCheck versionCheck in versionChecks)
			{
				if (!(text != versionCheck.Name))
				{
					Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + "."));
					versionCheck.ReceivedMinimumRequiredVersion = text2;
					versionCheck.ReceivedCurrentVersion = text3;
					if (ZNet.instance.IsServer() && versionCheck.IsVersionOk())
					{
						versionCheck.ValidatedClients.Add(rpc);
					}
					flag = true;
				}
			}
			if (flag)
			{
				return;
			}
			pkg.SetPos(0);
			if (original != null)
			{
				original(rpc, pkg);
				if (pkg.GetPos() == 0)
				{
					notProcessedNames.Add(text, text3);
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		[HarmonyPrefix]
		private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance)
		{
			VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient());
			if (array.Length == 0)
			{
				return true;
			}
			VersionCheck[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				Debug.LogWarning((object)array2[i].Error(rpc));
			}
			if (__instance.IsServer())
			{
				DisconnectClient(rpc);
			}
			else
			{
				Logout();
			}
			return false;
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		[HarmonyPrefix]
		private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance)
		{
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected O, but got Unknown
			notProcessedNames.Clear();
			IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc);
			if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")))
			{
				object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")];
				Action<ZRpc, ZPackage> action = (Action<ZRpc, ZPackage>)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
				peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage pkg)
				{
					CheckVersion(rpc, pkg, action);
				});
			}
			else
			{
				peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)CheckVersion);
			}
			foreach (VersionCheck versionCheck in versionChecks)
			{
				versionCheck.Initialize();
				if (versionCheck.ModRequired || __instance.IsServer())
				{
					Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + "."));
					ZPackage val = new ZPackage();
					val.Write(versionCheck.Name);
					val.Write(versionCheck.MinimumRequiredVersion);
					val.Write(versionCheck.CurrentVersion);
					peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val });
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		[HarmonyPrefix]
		private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				return;
			}
			foreach (VersionCheck versionCheck in versionChecks)
			{
				versionCheck.ValidatedClients.Remove(peer.m_rpc);
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		[HarmonyPostfix]
		private static void ShowConnectionError(FejdStartup __instance)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_014e: 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_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3)
			{
				return;
			}
			bool flag = false;
			VersionCheck[] failedClient = GetFailedClient();
			if (failedClient.Length != 0)
			{
				string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error()));
				TMP_Text connectionFailedError = __instance.m_connectionFailedError;
				connectionFailedError.text = connectionFailedError.text + "\n" + text;
				flag = true;
			}
			foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy((KeyValuePair<string, string> kv) => kv.Key))
			{
				if (!__instance.m_connectionFailedError.text.Contains(item.Key))
				{
					TMP_Text connectionFailedError2 = __instance.m_connectionFailedError;
					connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed.";
					flag = true;
				}
			}
			if (flag)
			{
				RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent<RectTransform>();
				Vector2 sizeDelta = component.sizeDelta;
				sizeDelta.x = 675f;
				component.sizeDelta = sizeDelta;
				__instance.m_connectionFailedError.ForceMeshUpdate(false, false);
				float num = __instance.m_connectionFailedError.renderedHeight + 105f;
				RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent<RectTransform>();
				component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f);
				sizeDelta = component.sizeDelta;
				sizeDelta.y = num;
				component.sizeDelta = sizeDelta;
			}
		}
	}
}
namespace Rodnarok
{
	internal static class CalmStands
	{
		private static readonly int SwitchKey = StringExtensionMethods.GetStableHashCode("rodnarok_calm");

		private const string SwitchCall = "Rodnarok_SwitchCalm";

		private static readonly int TrophyHash = StringExtensionMethods.GetStableHashCode("TrophySerpent");

		private const float LookEvery = 0.5f;

		private static readonly FieldRef<ItemStand, ZNetView> View = AccessTools.FieldRefAccess<ItemStand, ZNetView>("m_nview");

		private static readonly List<ItemStand> Stands = new List<ItemStand>();

		private static float _nextLook;

		[ForTest]
		public static bool SwitchedOnForTest(ItemStand stand)
		{
			ZNetView val = View.Invoke(stand);
			if (val != null && val.IsValid())
			{
				return val.GetZDO().GetBool(SwitchKey, false);
			}
			return false;
		}

		public static void Woke(ItemStand stand)
		{
			ZNetView val = View.Invoke(stand);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				val.Register("Rodnarok_SwitchCalm", (Action<long>)delegate
				{
					Switch(stand);
				});
				Stands.Add(stand);
			}
		}

		public static bool Interact(ItemStand stand, Humanoid user, bool hold, bool alt, ref bool result)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = View.Invoke(stand);
			if (!(user is Player) || (Object)(object)val == (Object)null || !val.IsValid())
			{
				return true;
			}
			if (!CalmStand.TapSwitches(hold, alt, Holds(val), stand.m_canBeRemoved))
			{
				return true;
			}
			if (!PrivateArea.CheckAccess(((Component)stand).transform.position, 0f, false, false))
			{
				return true;
			}
			bool flag = !val.GetZDO().GetBool(SwitchKey, false);
			val.InvokeRPC("Rodnarok_SwitchCalm", Array.Empty<object>());
			((Character)user).Message((MessageType)2, flag ? "The waters calm" : "The waters move again", 0, (Sprite)null, false);
			result = true;
			return false;
		}

		public static string Hover(ItemStand stand, string text)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = View.Invoke(stand);
			if (string.IsNullOrEmpty(text) || (Object)(object)val == (Object)null || !val.IsValid() || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return text;
			}
			if (!stand.m_canBeRemoved || Holds(val) != "TrophySerpent")
			{
				return text;
			}
			if (!PrivateArea.CheckAccess(((Component)stand).transform.position, 0f, false, false))
			{
				return text;
			}
			string text2 = string.Join("\n", CalmStand.HoverLines(val.GetZDO().GetBool(SwitchKey, false), ZInput.IsGamepadActive()));
			string text3 = Localization.instance.Localize(text2);
			int num = text.IndexOf('\n');
			if (num >= 0)
			{
				return text.Substring(0, num + 1) + text3 + text.Substring(num);
			}
			return text + "\n" + text3;
		}

		public static void Taken(ItemStand stand)
		{
			ZNetView val = View.Invoke(stand);
			if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && val.GetZDO().GetBool(SwitchKey, false))
			{
				val.GetZDO().Set(SwitchKey, false);
			}
		}

		public static void Tick()
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextLook)
			{
				return;
			}
			_nextLook = Time.time + 0.5f;
			Stands.RemoveAll((ItemStand s) => (Object)(object)s == (Object)null);
			List<Vector3> list = new List<Vector3>();
			foreach (ItemStand stand in Stands)
			{
				ZNetView val = View.Invoke(stand);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && CalmStand.Calms(Holds(val), val.GetZDO().GetBool(SwitchKey, false)))
				{
					list.Add(((Component)stand).transform.position);
				}
			}
			CalmWaters.CalmAround(list);
		}

		public static void Forget()
		{
			Stands.Clear();
			_nextLook = 0f;
		}

		private static void Switch(ItemStand stand)
		{
			ZNetView val = View.Invoke(stand);
			if (!((Object)(object)stand == (Object)null) && !((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && !(Holds(val) != "TrophySerpent"))
			{
				ZDO zDO = val.GetZDO();
				zDO.Set(SwitchKey, !zDO.GetBool(SwitchKey, false));
			}
		}

		private static string Holds(ZNetView view)
		{
			int num = view.GetZDO().GetInt(ZDOVars.s_item, 0);
			if (num != 0)
			{
				if (num != TrophyHash)
				{
					return "something";
				}
				return "TrophySerpent";
			}
			return null;
		}
	}
	internal static class CalmWaters
	{
		private static readonly float TileReach = 32f * Mathf.Sqrt(2f);

		private static readonly FieldRef<WaterVolume, float[]> Corners = AccessTools.FieldRefAccess<WaterVolume, float[]>("m_normalizedDepth");

		private static readonly FieldRef<WaterVolume, float> OneDepth = AccessTools.FieldRefAccess<WaterVolume, float>("m_oneDepth");

		private static readonly MethodInfo SetupMaterial = AccessTools.Method(typeof(WaterVolume), "SetupMaterial", (Type[])null, (Type[])null);

		private const float Moved = 0.5f;

		private static List<Vector3> _calming = new List<Vector3>();

		private static readonly HashSet<WaterVolume> Started = new HashSet<WaterVolume>();

		private static readonly Dictionary<WaterVolume, (float[] Corners, float One)> Own = new Dictionary<WaterVolume, (float[], float)>();

		private static bool _changed;

		private static float _reach = -1f;

		private static float _keep = -1f;

		[ForTest]
		public static IEnumerable<Vector3> CalmingForTest => _calming;

		public static void CalmAround(List<Vector3> places)
		{
			if (places.Count != _calming.Count || !places.All((Vector3 p) => _calming.Any((Vector3 c) => Vector3.Distance(c, p) < 0.5f)))
			{
				_calming = places;
				_changed = true;
			}
		}

		public static void TileStarted(WaterVolume tile)
		{
			Started.Add(tile);
			_changed = true;
		}

		public static void Tick()
		{
			FishingRules now = PlayRules.Now;
			if (!Mathf.Approximately(now.CalmReach, _reach) || !Mathf.Approximately(now.CalmKeep, _keep))
			{
				_reach = now.CalmReach;
				_keep = now.CalmKeep;
				_changed = true;
			}
			if (_changed)
			{
				_changed = false;
				Apply(now);
			}
		}

		private static void Apply(FishingRules rules)
		{
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			Started.RemoveWhere((WaterVolume t) => (Object)(object)t == (Object)null);
			foreach (WaterVolume item in Own.Keys.Where((WaterVolume t) => (Object)(object)t == (Object)null).ToList())
			{
				Own.Remove(item);
			}
			List<Spot> source = _calming.Select((Vector3 p) => new Spot(p.x, p.y, p.z)).ToList();
			foreach (WaterVolume item2 in Started)
			{
				if ((Object)(object)item2.m_heightmap == (Object)null)
				{
					continue;
				}
				Vector3 position = ((Component)item2).transform.position;
				Spot centre = new Spot(position.x, position.y, position.z);
				List<Spot> list = source.Where((Spot p) => p.Across(centre) <= rules.CalmReach + TileReach).ToList();
				if (list.Count == 0)
				{
					if (Own.TryGetValue(item2, out (float[], float) value))
					{
						Write(item2, value.Item1, value.Item2);
						Own.Remove(item2);
					}
					continue;
				}
				if (!Own.TryGetValue(item2, out (float[], float) value2))
				{
					value2 = ((float[])Corners.Invoke(item2).Clone(), OneDepth.Invoke(item2));
					Own[item2] = value2;
				}
				float[] calmed = CalmWater.Calmed(value2.Item1, centre, list, rules.CalmReach, rules.CalmKeep);
				Write(item2, calmed, calmed.All((float v) => Mathf.Approximately(v, calmed[0])) ? calmed[0] : (-1f));
			}
		}

		private static void Write(WaterVolume tile, float[] corners, float one)
		{
			float[] array = Corners.Invoke(tile);
			for (int i = 0; i < array.Length && i < corners.Length; i++)
			{
				array[i] = corners[i];
			}
			OneDepth.Invoke(tile) = one;
			SetupMaterial?.Invoke(tile, null);
		}

		public static void Forget()
		{
			_calming = new List<Vector3>();
			Started.Clear();
			Own.Clear();
		}
	}
	internal static class Commands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				string typed = ((args.Length > 1) ? (args.ArgsAll ?? "").Trim() : "");
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(SelfTestHost.Answer(typed) ?? "Rodnarök: the bait decides the fish, in any water. Settings are in BepInEx/config/isimp.Rodnarok.cfg.");
				}
			}
		}

		private const string Help = "Rodnarök: the bait decides the fish, in any water. Settings are in BepInEx/config/isimp.Rodnarok.cfg.";

		public static void Register()
		{
			//IL_0033: 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_0029: Expected O, but got Unknown
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					string typed = ((args.Length > 1) ? (args.ArgsAll ?? "").Trim() : "");
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(SelfTestHost.Answer(typed) ?? "Rodnarök: the bait decides the fish, in any water. Settings are in BepInEx/config/isimp.Rodnarok.cfg.");
					}
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("rodnarok", "- tells what Rodnarök does and where its settings are.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}
	}
	internal static class About
	{
		public const string Guid = "isimp.Rodnarok";

		public const string Name = "Rodnarök";

		public const string Version = "0.1.1";
	}
	internal static class AfterAnimation
	{
		private static class AfterPhysicsStep
		{
		}

		private static class AfterFrame
		{
		}

		public static bool Hook(UpdateFunction step)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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)
			PlayerLoopSystem system = PlayerLoop.GetCurrentPlayerLoop();
			if (!Insert(ref system, typeof(DirectorFixedUpdatePostPhysics), typeof(AfterPhysicsStep), step))
			{
				return false;
			}
			if (!Insert(ref system, typeof(DirectorUpdateAnimationEnd), typeof(AfterFrame), step))
			{
				return false;
			}
			PlayerLoop.SetPlayerLoop(system);
			return true;
		}

		[ForTest]
		public static int PlacesForTest()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Count(PlayerLoop.GetCurrentPlayerLoop());
		}

		private static int Count(PlayerLoopSystem system)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (system.subSystemList != null)
			{
				return system.subSystemList.Sum((PlayerLoopSystem s) => ((s.type == typeof(AfterPhysicsStep) || s.type == typeof(AfterFrame)) ? 1 : 0) + Count(s));
			}
			return 0;
		}

		public static void Unhook()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			PlayerLoopSystem system = PlayerLoop.GetCurrentPlayerLoop();
			Remove(ref system);
			PlayerLoop.SetPlayerLoop(system);
		}

		private static bool Insert(ref PlayerLoopSystem system, Type after, Type mark, UpdateFunction step)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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_008b: 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)
			PlayerLoopSystem[] subSystemList = system.subSystemList;
			if (subSystemList == null)
			{
				return false;
			}
			int num = Array.FindIndex(subSystemList, (PlayerLoopSystem s) => s.type == after);
			if (num >= 0)
			{
				List<PlayerLoopSystem> list = subSystemList.ToList();
				list.Insert(num + 1, new PlayerLoopSystem
				{
					type = mark,
					updateDelegate = step
				});
				system.subSystemList = list.ToArray();
				return true;
			}
			for (int num2 = 0; num2 < subSystemList.Length; num2++)
			{
				PlayerLoopSystem system2 = subSystemList[num2];
				if (Insert(ref system2, after, mark, step))
				{
					subSystemList[num2] = system2;
					return true;
				}
			}
			return false;
		}

		private static void Remove(ref PlayerLoopSystem system)
		{
			//IL_0049: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (system.subSystemList != null)
			{
				system.subSystemList = system.subSystemList.Where((PlayerLoopSystem s) => s.type != typeof(AfterPhysicsStep) && s.type != typeof(AfterFrame)).ToArray();
				for (int num = 0; num < system.subSystemList.Length; num++)
				{
					PlayerLoopSystem system2 = system.subSystemList[num];
					Remove(ref system2);
					system.subSystemList[num] = system2;
				}
			}
		}
	}
	internal static class Log
	{
		public static ManualLogSource Source { get; set; }

		public static void Info(string line)
		{
			ManualLogSource source = Source;
			if (source != null)
			{
				source.LogInfo((object)line);
			}
		}

		public static void Warning(string line)
		{
			ManualLogSource source = Source;
			if (source != null)
			{
				source.LogWarning((object)line);
			}
		}
	}
	internal static class PlayRules
	{
		[ForTest]
		public static FishingRules ForTest { get; set; }

		public static FishingRules Now => ForTest ?? Settings.Rules;
	}
	internal static class SelfTestHost
	{
		public interface IRunner
		{
			void Tick();

			string Answer(string typed);
		}

		private static IRunner _runner;

		[ForTest]
		public static void Register(IRunner runner)
		{
			_runner = runner;
		}

		public static void Tick()
		{
			_runner?.Tick();
		}

		public static string Answer(string typed)
		{
			return _runner?.Answer(typed);
		}
	}
	internal static class Settings
	{
		private const string Section = "Fishing";

		private static ConfigSync _sync;

		private static ConfigEntry<float> _waitMin;

		private static ConfigEntry<float> _waitMax;

		private static ConfigEntry<float> _hookWindow;

		private static ConfigEntry<float> _stallStamina;

		private static ConfigEntry<float> _calmReach;

		private static ConfigEntry<float> _calmKeep;

		private static ConfigEntry<bool> _schools;

		private static ConfigEntry<bool> _bumps;

		private static ConfigEntry<bool> _tension;

		private static ConfigEntry<float> _snapSeconds;

		private static ConfigEntry<float> _easeSeconds;

		private static ConfigEntry<float> _runSpeed;

		private static ConfigEntry<bool> _callWorldFish;

		private static ConfigEntry<bool> _wary;

		private static ConfigEntry<bool> _baitLost;

		private static ConfigEntry<bool> _castHints;

		private static ConfigEntry<bool> _seatedMotions;

		public static bool SeatedMotions
		{
			get
			{
				if (_seatedMotions == null)
				{
					return true;
				}
				return _seatedMotions.Value;
			}
		}

		public static bool CastHints
		{
			get
			{
				if (_castHints != null)
				{
					return _castHints.Value;
				}
				return false;
			}
		}

		public static FishingRules Rules
		{
			get
			{
				if (_waitMin != null)
				{
					FishingRules fishingRules = FishingRules.Default;
					float? hookWindow = _hookWindow.Value;
					float? waitMin = _waitMin.Value;
					float? waitMax = _waitMax.Value;
					float? stallStaminaPerSecond = _stallStamina.Value;
					float? calmReach = _calmReach.Value;
					float? calmKeep = _calmKeep.Value;
					bool? schools = _schools.Value;
					bool? bumps = _bumps.Value;
					bool? tension = _tension.Value;
					float? snapSeconds = _snapSeconds.Value;
					float? easeSeconds = _easeSeconds.Value;
					float? runSpeed = _runSpeed.Value;
					bool? callWorldFish = _callWorldFish.Value;
					bool? wary = _wary.Value;
					bool? baitLost = _baitLost.Value;
					return fishingRules.With(hookWindow, waitMin, waitMax, stallStaminaPerSecond, calmReach, calmKeep, schools, bumps, null, tension, snapSeconds, easeSeconds, runSpeed, callWorldFish, wary, baitLost);
				}
				return FishingRules.Default;
			}
		}

		[ForTest]
		public static bool SeatedMotionsForTest(bool on)
		{
			bool seatedMotions = SeatedMotions;
			if (_seatedMotions != null)
			{
				_seatedMotions.Value = on;
			}
			return seatedMotions;
		}

		public static void Bind(ConfigFile config)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Expected O, but got Unknown
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Expected O, but got Unknown
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Expected O, but got Unknown
			//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cc: Expected O, but got Unknown
			FishingRules fishingRules = FishingRules.Default;
			_sync = new ConfigSync("isimp.Rodnarok")
			{
				DisplayName = "Rodnarök",
				CurrentVersion = "0.1.1",
				ModRequired = true,
				IsLocked = true
			};
			_waitMin = Sync<float>(config.Bind<float>("Fishing", "WaitMin", fishingRules.WaitMin, new ConfigDescription("Shortest time, in seconds, a school takes to swim in from out of sight after the float lands. A long way in takes as long as its fish need to stay together.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())));
			_waitMax = Sync<float>(config.Bind<float>("Fishing", "WaitMax", fishingRules.WaitMax, new ConfigDescription("Longest time, in seconds, a school takes to swim in from out of sight after the float lands, unless a long way in needs longer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())));
			_hookWindow = Sync<float>(config.Bind<float>("Fishing", "HookWindow", fishingRules.HookWindow, new ConfigDescription("Seconds after a bite during which holding the line hooks the fish. The game gives half a second.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 5f), Array.Empty<object>())));
			_stallStamina = Sync<float>(config.Bind<float>("Fishing", "StallStamina", fishingRules.StallStaminaPerSecond, new ConfigDescription("Stamina a second for holding the line while a hooked fish struggles, before Fishing skill lowers it. The line gains nothing meanwhile.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>())));
			_calmReach = Sync<float>(config.Bind<float>("Fishing", "CalmReach", fishingRules.CalmReach, new ConfigDescription("How far an item stand calming the waters with a Serpent trophy reaches, in metres. Waves are calmed by the corners of the game's 64 m water tiles, so the calm water fades out over a tile. Below 46 m a stand in the middle of a tile may reach no corner and calm nothing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 128f), Array.Empty<object>())));
			_calmKeep = Sync<float>(config.Bind<float>("Fishing", "CalmKeep", fishingRules.CalmKeep, new ConfigDescription("The share of the waves left where a calming item stand reaches: 0 is flat water, 1 leaves the waves as they are.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())));
			_schools = Sync<bool>(config.Bind<bool>("Fishing", "Schools", fishingRules.Schools, "Whether the bait draws a school of its fish, as many as the world spawns together, of which one takes the bait. Off, a single fish comes."));
			_bumps = Sync<bool>(config.Bind<bool>("Fishing", "Bumps", fishingRules.Bumps, "Whether the fish that takes the bait first bumps the float lightly once or twice before it goes for the hook. A bump is a short, light dip, far lighter than the bite, and can never be hooked; reeling at one drags the float and startles a wary school. The real bite pulls the float under."));
			_tension = Sync<bool>(config.Bind<bool>("Fishing", "Tension", fishingRules.Tension, "Whether holding the line through a struggle strains it until it snaps, shown by the crosshair ring filling. Easing off lets the strain go while the fish takes line out. Off, holding through a struggle only stalls."));
			_snapSeconds = Sync<float>(config.Bind<float>("Fishing", "SnapSeconds", fishingRules.SnapSeconds, new ConfigDescription("Seconds of holding the line through a struggle until it snaps, with Tension on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())));
			_easeSeconds = Sync<float>(config.Bind<float>("Fishing", "EaseSeconds", fishingRules.EaseSeconds, new ConfigDescription("Seconds of easing off, or of the fish resting, until a line strained to snapping is unstrained, with Tension on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())));
			_runSpeed = Sync<float>(config.Bind<float>("Fishing", "RunSpeed", fishingRules.RunSpeed, new ConfigDescription("Metres of line a second a struggling fish takes out while the line is eased off with no strain left on it, with Tension on; with strain still on the line it runs slower, half as fast at half strain. The line breaks past the game's 30 m as ever.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())));
			_callWorldFish = Sync<bool>(config.Bind<bool>("Fishing", "CallWorldFish", fishingRules.CallWorldFish, "Whether a fish of the world's own that takes the bait, within the float's reach and able to swim to it, is called to the float instead of a school. Off, a school comes wherever the water allows one."));
			_baitLost = Sync<bool>(config.Bind<bool>("Fishing", "BaitLost", fishingRules.BaitLost, "Whether missed bites wear the bait down. On, each bite not hooked in time may take the bait, more likely each time (10%, 40%, 90%, then 98%): taken, the float is bare, nothing bites it again, the school swims off, and reeling it in gives no bait back. A fish that missed bites that float no more, and once every fish of the school has missed, the school swims off and leaves the bait. Off, fish bite the same bait again and again, as in the game."));
			_wary = Sync<bool>(config.Bind<bool>("Fishing", "Wary", fishingRules.Wary, "Whether a school is wary of the one fishing. On, a player walking or running close to the school (35% of the game's own noise: walking about 5 m, running about 10.5 m; sneaking, sitting and standing still make none) makes it dart 6 m off and come back once all is quiet for 3 s; a short reel with the school near makes it flinch 2.5 m aside, back once the float is still for 1.5 s. A dart counts 2 frights, a flinch 1; at 8, or with the float reeled through it for 2 s, the school leaves. Close in (within 4 m, not shy from 12 m) and in shallow water (under 1 m, not shy from 3 m) a fish hesitates up to 12 s before biting and up to 30% of such schools give up and swim off. Off, a school bites wherever it is."));
			_castHints = config.Bind<bool>("Display", "CastHints", false, "Whether each cast shows a short line at the top left saying which of the bait's fish can come to the water there, and which would need bigger or deeper water. Your own setting; the server's does not apply.");
			_seatedMotions = config.Bind<bool>("Display", "SeatedMotions", true, "Whether a seated player with a rod shows the game's casting, reeling and rod-holding motions on the arms and head while the hips and legs stay as seated, as your game sees every player. Your own setting; the server's does not apply.");
		}

		private static ConfigEntry<T> Sync<T>(ConfigEntry<T> entry)
		{
			_sync.AddConfigEntry<T>(entry).SynchronizedConfig = true;
			return entry;
		}
	}
	internal static class Bites
	{
		public const float Reach = 0.6f;

		private static readonly FieldRef<FishingFloat, float> NibbleTime = AccessTools.FieldRefAccess<FishingFloat, float>("m_nibbleTime");

		private static readonly FieldRef<FishingFloat, Fish> Nibbler = AccessTools.FieldRefAccess<FishingFloat, Fish>("m_nibbler");

		private static readonly FieldRef<FishingFloat, Rigidbody> Body = AccessTools.FieldRefAccess<FishingFloat, Rigidbody>("m_body");

		private static readonly FieldRef<FishingFloat, float> LineLength = AccessTools.FieldRefAccess<FishingFloat, float>("m_lineLength");

		private static readonly Dictionary<FishingFloat, float> PulledUntil = new Dictionary<FishingFloat, float>();

		private static readonly Dictionary<FishingFloat, float> BumpedUntil = new Dictionary<FishingFloat, float>();

		private static readonly FieldRef<FishingFloat, bool> BaitConsumed = AccessTools.FieldRefAccess<FishingFloat, bool>("m_baitConsumed");

		private static readonly Func<FishingFloat, Character> Owner = AccessTools.MethodDelegate<Func<FishingFloat, Character>>(AccessTools.Method(typeof(FishingFloat), "GetOwner", (Type[])null, (Type[])null), (object)null, true);

		private static readonly HashSet<FishingFloat> Bare = new HashSet<FishingFloat>();

		private static readonly Dictionary<FishingFloat, int> Misses = new Dictionary<FishingFloat, int>();

		private static readonly Dictionary<FishingFloat, HashSet<Fish>> Learned = new Dictionary<FishingFloat, HashSet<Fish>>();

		private const string NibbledText = "The fish nibbled at the bait";

		private const string SchoolOffText = "The fish nibbled at the bait, and the school swam off";

		private const string TakenText = "The fish took the bait";

		[ForTest]
		public static int BumpsForTest { get; private set; }

		[ForTest]
		public static float LastBiteDistanceForTest { get; private set; } = -1f;

		[ForTest]
		public static int MissesForTest(FishingFloat ff)
		{
			if (!((Object)(object)ff != (Object)null) || !Misses.TryGetValue(ff, out var value))
			{
				return 0;
			}
			return value;
		}

		[ForTest]
		public static bool BareForTest(FishingFloat ff)
		{
			if ((Object)(object)ff != (Object)null)
			{
				return Bare.Contains(ff);
			}
			return false;
		}

		[ForTest]
		public static bool BaitUsedForTest(FishingFloat ff)
		{
			if ((Object)(object)ff != (Object)null)
			{
				return BaitConsumed.Invoke(ff);
			}
			return false;
		}

		public static Vector3 Hook(FishingFloat ff)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)ff).transform.position + Vector3.down;
		}

		public static bool Counts(FishingFloat ff, Fish fish)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0013: 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_001f: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = Hook(ff);
			Vector3 position = ((Component)fish).transform.position;
			bool num = Bite.FishAtHook(new Spot(position.x, position.y, position.z), new Spot(val.x, val.y, val.z), 0.6f);
			if (num)
			{
				LastBiteDistanceForTest = Vector3.Distance(position, val);
			}
			return num;
		}

		public static void Bitten(FishingFloat ff)
		{
			if ((Object)(object)Nibbler.Invoke(ff) != (Object)null && Time.time - NibbleTime.Invoke(ff) < 0.01f)
			{
				PulledUntil[ff] = Time.time + 1f;
			}
		}

		public static void Bump(FishingFloat ff)
		{
			if (!((Object)(object)ff == (Object)null) && !((Object)(object)ff.GetCatch() != (Object)null))
			{
				BumpedUntil[ff] = Time.time + 0.25f;
				BumpsForTest++;
			}
		}

		public static void Pull(FishingFloat ff)
		{
			PullFor(ff, PulledUntil, 14f);
			PullFor(ff, BumpedUntil, 12f);
		}

		public static void Forget(FishingFloat ff)
		{
			PulledUntil.Remove(ff);
			BumpedUntil.Remove(ff);
			Bare.Remove(ff);
			Misses.Remove(ff);
			Learned.Remove(ff);
		}

		public static void WatchBait(FishingFloat ff)
		{
			if ((Object)(object)ff == (Object)null || Bare.Contains(ff))
			{
				return;
			}
			ZNetView component = ((Component)ff).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner())
			{
				return;
			}
			FishingRules now = PlayRules.Now;
			if (now.BaitLost && BaitLoss.Missed((Object)(object)Nibbler.Invoke(ff) != (Object)null, (Object)(object)ff.GetCatch() != (Object)null, Time.time - NibbleTime.Invoke(ff), now.HookWindow))
			{
				Fish val = Nibbler.Invoke(ff);
				Nibbler.Invoke(ff) = null;
				Misses.TryGetValue(ff, out var value);
				value = (Misses[ff] = value + 1);
				if (!Learned.TryGetValue(ff, out HashSet<Fish> value2))
				{
					value2 = (Learned[ff] = new HashSet<Fish>());
				}
				value2.Add(val);
				bool flag = BaitLoss.Taken(now.BaitLost, value, Random.value);
				bool flag2 = false;
				if (flag)
				{
					Bare.Add(ff);
					BaitConsumed.Invoke(ff) = true;
					Schools.BaitTaken(ff);
				}
				else
				{
					flag2 = Schools.Missed(ff, val);
				}
				Character obj = Owner(ff);
				Player val2 = (Player)(object)((obj is Player) ? obj : null);
				if (val2 != null && (Object)(object)val2 == (Object)(object)Player.m_localPlayer)
				{
					((Character)val2).Message((MessageType)1, flag ? "The fish took the bait" : (flag2 ? "The fish nibbled at the bait, and the school swam off" : "The fish nibbled at the bait"), 0, (Sprite)null, false);
				}
			}
		}

		public static bool Refuses(FishingFloat ff, Fish fish)
		{
			if ((Object)(object)ff != (Object)null)
			{
				if (!Bare.Contains(ff))
				{
					if ((Object)(object)fish != (Object)null && Learned.TryGetValue(ff, out HashSet<Fish> value))
					{
						return value.Contains(fish);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static void PullFor(FishingFloat ff, Dictionary<FishingFloat, float> pulls, float pull)
		{
			//IL_0048: 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)
			if (pulls.TryGetValue(ff, out var value))
			{
				if ((Object)(object)ff == (Object)null || Time.time >= value || (Object)(object)ff.GetCatch() != (Object)null)
				{
					pulls.Remove(ff);
				}
				else if (ff.IsInWater())
				{
					Body.Invoke(ff).AddForce(Vector3.down * pull, (ForceMode)5);
				}
			}
		}

		public static void StretchWindow(FishingFloat ff)
		{
			if (!((Object)(object)Nibbler.Invoke(ff) == (Object)null) && !((Object)(object)ff.GetCatch() != (Object)null) && Bite.InHookWindow(Time.time - NibbleTime.Invoke(ff), PlayRules.Now.HookWindow))
			{
				NibbleTime.Invoke(ff) = Time.time;
			}
		}

		public static float LineBefore(FishingFloat ff)
		{
			return LineLength.Invoke(ff);
		}

		public static void StallLine(FishingFloat ff, float before)
		{
			if (!((Object)(object)ff == (Object)null))
			{
				Fish val = ff.GetCatch();
				if ((Object)(object)val != (Object)null && val.IsEscaping() && LineLength.Invoke(ff) < before)
				{
					LineLength.Invoke(ff) = before;
				}
			}
		}

		public static float? StruggleFigure(Fish fish)
		{
			if (!fish.IsEscaping())
			{
				return null;
			}
			ItemDrop component = ((Component)fish).GetComponent<ItemDrop>();
			return Fight.StaminaFigureWhileStruggling(PlayRules.Now, ((Object)(object)component == (Object)null) ? 1 : component.m_itemData.m_quality);
		}
	}
	internal static class CrosshairRing
	{
		private static readonly FieldRef<FishingFloat, float> NibbleTime = AccessTools.FieldRefAccess<FishingFloat, float>("m_nibbleTime");

		private static readonly FieldRef<FishingFloat, Fish> Nibbler = AccessTools.FieldRefAccess<FishingFloat, Fish>("m_nibbler");

		private static Image _ring;

		private static Outline _thickness;

		private static Image _track;

		private static Outline _trackThickness;

		private const float Thickness = 1.5f;

		[ForTest]
		public static Image ImageForTest => _ring;

		[ForTest]
		public static Image TrackForTest => _track;

		public static void Draw(Hud hud, float bowDrawPercentage)
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: 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_0194: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)hud == (Object)null || (Object)(object)hud.m_crosshairBow == (Object)null)
			{
				return;
			}
			Image val = RingOf(hud);
			RingDraw ringDraw = RingDraw.For(RingFor(OwnFloat()));
			if (!ringDraw.Shown || bowDrawPercentage > 0f)
			{
				if (((Component)val).gameObject.activeSelf)
				{
					((Component)val).gameObject.SetActive(false);
				}
				if (((Component)_track).gameObject.activeSelf)
				{
					((Component)_track).gameObject.SetActive(false);
				}
				return;
			}
			if (!((Component)val).gameObject.activeSelf)
			{
				((Component)val).gameObject.SetActive(true);
			}
			Vector3 localScale = default(Vector3);
			((Vector3)(ref localScale))..ctor(ringDraw.Scale, ringDraw.Scale, ringDraw.Scale);
			((Component)val).transform.localScale = localScale;
			val.fillAmount = ringDraw.Fill;
			((Graphic)val).color = new Color(ringDraw.R, ringDraw.G, ringDraw.B, ringDraw.A);
			if ((Object)(object)_thickness != (Object)null)
			{
				((Shadow)_thickness).effectColor = ((Graphic)val).color;
			}
			bool flag = ringDraw.TrackA > 0f;
			if (((Component)_track).gameObject.activeSelf != flag)
			{
				((Component)_track).gameObject.SetActive(flag);
			}
			if (flag)
			{
				((Component)_track).transform.localScale = localScale;
				((Graphic)_track).color = new Color(ringDraw.R, ringDraw.G, ringDraw.B, ringDraw.TrackA);
				if ((Object)(object)_trackThickness != (Object)null)
				{
					((Shadow)_trackThickness).effectColor = ((Graphic)_track).color;
				}
			}
		}

		private static Image RingOf(Hud hud)
		{
			if ((Object)(object)_ring != (Object)null)
			{
				return _ring;
			}
			Image crosshairBow = hud.m_crosshairBow;
			_track = Make(crosshairBow, "RodnarokRingTrack", out _trackThickness);
			_ring = Make(crosshairBow, "RodnarokRing", out _thickness);
			return _ring;
		}

		private static Image Make(Image bow, string name, out Outline thickness)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)bow).gameObject, ((Component)bow).transform.parent);
			((Object)val).name = name;
			val.SetActive(false);
			Image component = val.GetComponent<Image>();
			component.type = (Type)3;
			component.fillMethod = (FillMethod)4;
			component.fillOrigin = 2;
			component.fillClockwise = true;
			component.fillAmount = 1f;
			thickness = val.AddComponent<Outline>();
			((Shadow)thickness).effectDistance = new Vector2(1.5f, 1.5f);
			((Shadow)thickness).useGraphicAlpha = true;
			return component;
		}

		private static Ring RingFor(FishingFloat ff)
		{
			if ((Object)(object)ff == (Object)null)
			{
				return Ring.Hidden;
			}
			Fish val = ff.GetCatch();
			if ((Object)(object)val != (Object)null)
			{
				if (!PlayRules.Now.Tension)
				{
					return Ring.ForFight(val.IsEscaping());
				}
				return Ring.ForFight(val.IsEscaping(), Lines.StrainOf(ff));
			}
			if ((Object)(object)Nibbler.Invoke(ff) == (Object)null)
			{
				return Ring.Hidden;
			}
			return Ring.ForBite(Time.time - NibbleTime.Invoke(ff), PlayRules.Now.HookWindow);
		}

		private static FishingFloat OwnFloat()
		{
			foreach (FishingFloat allInstance in FishingFloat.GetAllInstances())
			{
				if (!((Object)(object)allInstance == (Object)null))
				{
					ZNetView component = ((Component)allInstance).GetComponent<ZNetView>();
					if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner())
					{
						return allInstance;
					}
				}
			}
			return null;
		}
	}
	internal sealed class FishBook
	{
		private static FishBook _current;

		private static ZNetScene _readFor;

		private readonly Dictionary<string, GameObject> _prefabs = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		private readonly Dictionary<string, SpawnData> _spawns = new Dictionary<string, SpawnData>(StringComparer.Ordinal);

		public static FishBook Current
		{
			get
			{
				if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
				{
					return null;
				}
				if ((Object)(object)_readFor != (Object)(object)ZNetScene.instance)
				{
					_current = new FishBook();
					_readFor = ZNetScene.instance;
				}
				return _current;
			}
		}

		public BaitTable Baits { get; }

		private FishBook()
		{
			List<GameObject> list = ZNetScene.instance.m_prefabs.Where((GameObject p) => (Object)(object)p != (Object)null && (Object)(object)p.GetComponent<Fish>() != (Object)null).ToList();
			foreach (GameObject item in list)
			{
				_prefabs[((Object)item).name] = item;
			}
			Baits = new BaitTable(list.Select((GameObject p) => new FishKind(((Object)p).name, (from b in p.GetComponent<Fish>().m_baits
				where (Object)(object)b?.m_bait != (Object)null
				select ((Object)b.m_bait).name).ToList())));
			ZoneSystem instance = ZoneSystem.instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				GameObject zoneCtrlPrefab = instance.m_zoneCtrlPrefab;
				obj = ((zoneCtrlPrefab != null) ? zoneCtrlPrefab.GetComponent<SpawnSystem>() : null);
			}
			SpawnSystem val = (SpawnSystem)obj;
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			foreach (SpawnData item2 in val.m_spawnLists.Where((SpawnSystemList l) => (Object)(object)l != (Object)null).SelectMany((SpawnSystemList l) => l.m_spawners))
			{
				if (!((Object)(object)item2?.m_prefab == (Object)null) && item2.m_enabled && !((Object)(object)item2.m_prefab.GetComponent<Fish>() == (Object)null) && !_spawns.ContainsKey(((Object)item2.m_prefab).name))
				{
					_spawns[((Object)item2.m_prefab).name] = item2;
				}
			}
		}

		public GameObject Prefab(string name)
		{
			if (name == null || !_prefabs.TryGetValue(name, out GameObject value))
			{
				return null;
			}
			return value;
		}

		public GroupSize Group(string name)
		{
			if (name == null || !_spawns.TryGetValue(name, out SpawnData value))
			{
				return GroupSize.Unknown;
			}
			return new GroupSize(value.m_groupSizeMin, value.m_groupSizeMax);
		}

		public int RollQuality(string name, Vector3 place)
		{
			//IL_004d: 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)
			if (name != null && _spawns.TryGetValue(name, out SpawnData value))
			{
				return Stars.Roll(value.m_minLevel, value.m_maxLevel, SpawnSystem.GetLevelUpChance(place, value), () => Random.Range(0f, 100f));
			}
			return Stars.Roll(1, 5, SpawnSystem.GetLevelUpChance(place, 0f), () => Random.Range(0f, 100f));
		}
	}
	internal static class Lines
	{
		private sealed class Line
		{
			public Fish Fish;

			public float Strain;

			public bool Struggling;

			public int Runs;

			public float RunMetres;

			public float RunSeconds;

			public float HeldSeconds;

			public int NotCounted;

			public string NotCountedWhy;

			public int OutOfStamina;
		}

		private static readonly FieldRef<FishingFloat, float> LineLength = AccessTools.FieldRefAccess<FishingFloat, float>("m_lineLength");

		private static readonly Func<FishingFloat, Character> Owner = AccessTools.MethodDelegate<Func<FishingFloat, Character>>(AccessTools.Method(typeof(FishingFloat), "GetOwner", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Action<FishingFloat, string, bool> Message = AccessTools.MethodDelegate<Action<FishingFloat, string, bool>>(AccessTools.Method(typeof(FishingFloat), "Message", (Type[])null, (Type[])null), (object)null, true);

		private static readonly FieldRef<Character, bool> BlockPressed = AccessTools.FieldRefAccess<Character, bool>("m_blocking");

		private static readonly Dictionary<FishingFloat, Line> Strains = new Dictionary<FishingFloat, Line>();

		private static readonly HashSet<Fish> Running = new HashSet<Fish>();

		private static bool _snapping;

		[ForTest]
		public static int SnappedForTest { get; private set; }

		public static float StrainOf(FishingFloat ff)
		{
			if (!((Object)(object)ff != (Object)null) || !Strains.TryGetValue(ff, out Line value))
			{
				return 0f;
			}
			return value.Strain;
		}

		public static bool IsRunning(Fish fish)
		{
			if ((Object)(object)fish != (Object)null)
			{
				return Running.Contains(fish);
			}
			return false;
		}

		public static void Step(FishingFloat ff)
		{
			ZNetView val = (((Object)(object)ff != (Object)null) ? ((Component)ff).GetComponent<ZNetView>() : null);
			if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
			{
				Forget(ff);
				return;
			}
			Fish val2 = ff.GetCatch();
			Character val3 = Owner(ff);
			if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)
			{
				Forget(ff);
				return;
			}
			if (!Strains.TryGetValue(ff, out Line value) || (Object)(object)value.Fish != (Object)(object)val2)
			{
				if (value != null && (Object)(object)value.Fish != (Object)null)
				{
					Running.Remove(value.Fish);
				}
				value = new Line
				{
					Fish = val2
				};
				Strains[ff] = value;
			}
			FishingRules now = PlayRules.Now;
			bool flag = val2.IsEscaping();
			bool flag2 = val3.IsBlocking() && val3.HaveStamina(0f);
			float fixedDeltaTime = Time.fixedDeltaTime;
			if (value.Struggling && !flag)
			{
				TellStruggle(ff, value, "it rests");
			}
			if (flag && !value.Struggling)
			{
				StartStruggle(value);
			}
			value.Strain = Tension.Step(value.Strain, flag, flag2, fixedDeltaTime, now);
			if (Tension.Snaps(value.Strain))
			{
				if (value.Struggling)
				{
					TellStruggle(ff, value, "the line snapped");
				}
				Snap(ff, val2, val);
				return;
			}
			if (flag && flag2)
			{
				value.HeldSeconds += fixedDeltaTime;
			}
			float num = Tension.LineOut(value.Strain, flag, flag2, fixedDeltaTime, now);
			if (num > 0f)
			{
				if (Running.Add(val2))
				{
					CountRun(val3, value);
				}
				LineLength.Invoke(ff) += num;
				value.RunMetres += num;
				value.RunSeconds += fixedDeltaTime;
			}
			else
			{
				Running.Remove(val2);
			}
		}

		private static void StartStruggle(Line line)
		{
			line.Struggling = true;
			line.Runs = 0;
			line.RunMetres = 0f;
			line.RunSeconds = 0f;
			line.HeldSeconds = 0f;
			line.NotCounted = 0;
			line.NotCountedWhy = null;
			line.OutOfStamina = 0;
		}

		private static void CountRun(Character owner, Line line)
		{
			line.Runs++;
			if (BlockPressed.Invoke(owner))
			{
				if (owner.IsBlocking())
				{
					line.OutOfStamina++;
					return;
				}
				line.NotCounted++;
				line.NotCountedWhy = (owner.InAttack() ? "attacking, " : "") + (owner.InDodge() ? "dodging, " : "") + (owner.InPlaceMode() ? "building, " : "") + (owner.IsEncumbered() ? "overweight, " : "") + (owner.InMinorAction() ? "in a minor action, " : "") + (owner.IsStaggering() ? "staggered, " : "") + "on " + ((object)owner).GetType().Name;
			}
		}

		private static void TellStruggle(FishingFloat ff, Line line, string end)
		{
			line.Struggling = false;
			string text = "A struggle ended, " + end + ": the fish took " + line.RunMetres.ToString("0.0", CultureInfo.InvariantCulture) + " m in " + line.Runs.ToString(CultureInfo.InvariantCulture) + ((line.Runs == 1) ? " run" : " runs") + ", eased off " + line.RunSeconds.ToString("0.0", CultureInfo.InvariantCulture) + " s, held " + line.HeldSeconds.ToString("0.0", CultureInfo.InvariantCulture) + " s, line " + LineLength.Invoke(ff).ToString("0.0", CultureInfo.InvariantCulture) + " m";
			if (line.NotCounted > 0)
			{
				text = text + ", " + line.NotCounted.ToString(CultureInfo.InvariantCulture) + " with the block button down but not taken as blocking by the game (last " + line.NotCountedWhy + ")";
			}
			if (line.OutOfStamina > 0)
			{
				text = text + ", " + line.OutOfStamina.ToString(CultureInfo.InvariantCulture) + " with the block button down and stamina out";
			}
			Log.Info(text);
		}

		private static void Snap(FishingFloat ff, Fish fish, ZNetView view)
		{
			//IL_0050: 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_0063: 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)
			SnappedForTest++;
			Tell(ff, "held through a struggle until the strain snapped it");
			Forget(ff);
			_snapping = true;
			try
			{
				Message(ff, "$msg_fishing_linebroke", arg3: true);
			}
			finally
			{
				_snapping = false;
			}
			fish.OnHooked((FishingFloat)null);
			ff.m_lineBreakEffect.Create(((Component)ff).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			view.Destroy();
		}

		public static void BrokeMessage(FishingFloat ff, string msg)
		{
			if (!_snapping && !(msg != "$msg_fishing_linebroke"))
			{
				Tell(ff, "the game broke it: the float got more than " + ff.m_breakDistance.ToString("0", CultureInfo.InvariantCulture) + " m farther from the rod tip than the line is long, or more than " + ff.m_maxDistance.ToString("0", CultureInfo.InvariantCulture) + " m away");
			}
		}

		private static void Tell(FishingFloat ff, string why)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			Character val = Owner(ff);
			Fish val2 = ff.GetCatch();
			Transform val3 = (((Object)(object)val != (Object)null) ? Utils.FindChild(((Component)val).transform, "_RodTop", (IterativeSearchType)0) : null);
			Player val4 = (Player)(object)((val is Player) ? val : null);
			bool flag = (Object)(object)val4 != (Object)null && (((Character)val4).IsAttached() || SeatedFishing.SitsOnTheGround(val4));
			Log.Info("The line broke, " + why + ": line " + LineLength.Invoke(ff).ToString("0.0", CultureInfo.InvariantCulture) + " m" + (((Object)(object)val3 != (Object)null) ? (", float " + Vector3.Distance(val3.position, ((Component)ff).transform.position).ToString("0.0", Cu